1e792f191cf24af49b609494cba72390ec2f24cd
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
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
- Arch:
- Wayland dev libraries:
- Arch:
pacman -S wayland-protocols - Ubuntu/Debian:
apt install libwayland-dev wayland-protocols - Fedora:
dnf install wayland-devel wayland-protocols-devel
- Arch:
- DRM dev libraries:
- Arch:
pacman -S libdrm - Ubuntu/Debian:
apt install libdrm-dev - Fedora:
dnf install libdrm-devel
- Arch:
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) |
Languages
Rust
98.7%
Shell
1.2%