dailz 1e792f191c fix(state_portal): use webrtc_thread.is_some() for PTS mode gating (really fixes #24)
Previous commit 079611a claimed to fix #24 but the gating condition was
wrong, making the entire fix dead code:

  src/state_portal.rs:467
  - let pts = if self.webrtc.is_some() {        // ALWAYS false here
  + let pts = if self.webrtc_thread.is_some() {  // correct lifecycle check

Why self.webrtc was wrong:

  WebRtcState lifecycle in Portal path:
  1. StatePortal::new() sets self.webrtc = Some(...) if args.port > 0
  2. First frame arrives -> WaitingForFormat branch
  3. state_portal.rs:274 does self.webrtc.take() and moves WebRtcState
     into the webrtc thread
  4. Subsequent frames -> Streaming branch -> handle_pw_frame
  5. By this point self.webrtc is None

So my gating check 'if self.webrtc.is_some()' at handle_pw_frame ALWAYS
returned false, and compute_capture_pts was NEVER called. Confirmed by
debug instrumentation showing 0 invocations across a 174s WebRTC session.

Net effect: #24's PTS fix was completely inert. RTP timestamps were
still computed from sequential frame counter (old broken behavior).
User reports of 'latency got worse' were due to other test conditions,
not the dead code.

The correct check is self.webrtc_thread.is_some() because:
- webrtc_thread is set AFTER WebRtcState is moved into it (line 301)
- webrtc_thread stays Some for the entire WebRTC session
- webrtc_thread is None for MP4 mode (no thread spawned)

So this check correctly distinguishes WebRTC mode from MP4 mode at the
point where PTS is computed for each frame in handle_pw_frame.

Lesson learned:
- Oracle review (rounds 1 and 2) verified the design and code structure
  but did not catch the lifecycle issue because they reasoned about the
  code statically.
- Runtime verification via debug instrumentation was needed to confirm
  the function was never called.
- This is why the user ran the test BEFORE I committed - their feedback
  that latency got worse was the canary that exposed the dead code.

Verification plan (next user test):
- Run with --stats and confirm rtp= field in write_h264 debug logs
  shows VARIABLE jumps (not uniform 3000 increments)
- During static periods (capture_fps < 5), rtp should jump by 30000+
- During active periods (capture_fps > 30), rtp increments may still
  look sequential due to 1/fps time_base quantization (acceptable)
- Browser jitter buffer should stabilize at < 500ms
2026-06-20 22:15:50 +08:00
2026-06-13 22:46:41 +08:00

wl-webrtc

Wayland screen capture and encoding tool.

Prerequisites

  • Rust toolchain (1.70+): rustup default stable
  • FFmpeg 6.0+ dev libraries with VAAPI support:
    • Arch: pacman -S ffmpeg
    • Ubuntu/Debian: apt install libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libva-dev
    • Fedora: dnf install ffmpeg-devel libva-devel
  • Wayland dev libraries:
    • Arch: pacman -S wayland-protocols
    • Ubuntu/Debian: apt install libwayland-dev wayland-protocols
    • Fedora: dnf install wayland-devel wayland-protocols-devel
  • DRM dev libraries:
    • Arch: pacman -S libdrm
    • Ubuntu/Debian: apt install libdrm-dev
    • Fedora: dnf install libdrm-devel

Build

cargo build --release

Run

# Basic capture to file
wl-webrtc --output output.mp4

# With custom FPS and bitrate
wl-webrtc --output output.mp4 --fps 60 --bitrate 8000000

# Specify DRM device for hardware encoding
wl-webrtc --output output.mp4 --drm-device /dev/dri/renderD128

# Verbose mode
wl-webrtc --output output.mp4 -v

CLI Arguments

Argument Default Description
-o, --output (required) Output file path (e.g., output.mp4)
--output-name auto Wayland output name to capture
--fps 30 Target frames per second
--codec h264 Video codec (h264 only for MVP)
--hw-accel vaapi Hardware acceleration method
--drm-device auto DRM render device path
--bitrate auto Target bitrate in bps
--gop-size auto Group of Pictures size
-v, --verbose false Enable verbose logging
--port 0 WebTransport server port (unused in MVP)
S
Description
No description provided
Readme
2.2 MiB
Languages
Rust 98.7%
Shell 1.2%