dailz 2f0b858920 fix(state_portal): remove filler + accept Wayland damage-driven delivery (fixes #15, fixes #18)
Paradigm shift: stop treating KWin's damage-driven frame delivery as an
anomaly. Static content = no new frames is correct Wayland behavior.

Root causes (combined #15 + #18):

#15: stall detection threshold was 100ms (max(100ms, 3*frame_interval)).
KWin/PipeWire damage-driven delivery meant normal static periods triggered
WARN 'compositor frame delivery stalled' continuously. Test3 data showed
55% stall rate during active streaming (38 stalls in 69s). User perceived
severe stutter pattern 'cardboard-effect freeze-release-freeze'.

#18: filler mechanism (maybe_send_filler_frame) cloned 3MB NV12 data and
sent to encode thread during stalls. Encode thread hashed Y plane, found
duplicate, skipped via dedup. Net: wasted CPU + channel bandwidth with
zero visual benefit (the dedup path was already catching it).

Oracle review revealed the two issues are causally linked: filler is the
failed response to the false stall alarm. Removing both together is correct.

Fixes (all in state_portal.rs):

1. Remove filler mechanism entirely:
   - Remove fields: last_fillable_frame, next_filler_at, filler_frames_sent
   - Remove method: maybe_send_filler_frame (49 lines)
   - Remove constant: MAX_FILLER_DURATION
   - Remove fillable_frame clone cascade in handle_pw_frame (10 lines
     of 3MB NV12 cloning per frame, the largest CPU/memory win)
   - Remove filler_frames_sent from stats output

2. Redefine stall as idle (Oracle-revised):
   - Rename: stall_start -> idle_log_start (semantic clarity)
   - Change threshold: 100ms -> 5s (CAPTURE_IDLE_LOG_THRESHOLD)
   - Change log level: WARN -> DEBUG
   - Change wording: 'compositor frame delivery stalled' ->
     'portal capture idle; no damage frames received (normal Wayland behavior)'
   - One-shot log per idle episode (not repeated every second)
   - Use last_capture_arrival as idle start for accurate elapsed duration
     (old code set stall_start=now at first detection, undercounting by
     threshold value)

Explicit product decision (Oracle flagged trade-off):

  Static-content PLI repair is deferred. When WebRTC client sends PLI
  during static content:
  - Server sets force_keyframe_pending in encode thread
  - Encode thread blocks on input_rx.recv() (no frames coming)
  - Client may send more PLIs (all rate-limited by #23 to 1/sec)
  - When user interacts -> KWin delivers frame -> encode thread produces IDR

  This means during fully static content, client may wait for next damage
  to receive keyframe. Acceptable because static content is by definition
  unchanged - the last received frame is still visually accurate. If user
  reports unacceptable PLI latency on static screens, follow-up with
  event-driven one-shot IDR mechanism (Option B per Oracle).

Verification expectation:
  - Zero WARN 'stalled' messages during normal session
  - Optional DEBUG 'portal capture idle' after 5s of no frames
  - Optional DEBUG 'portal capture resumed after idle period' on recovery
  - capture_fps will still vary with content activity (this is correct)
  - encoded_fps will only count real frames (filler no longer inflates it)

Out of scope:
  - Event-driven cached-frame IDR (Option B): follow-up if needed
  - PipeWire CursorFromCache negotiation: separate enhancement
  - capture_fps expectations documentation: defer to #20 stats rework

Tests:
  - cargo build --release: clean, no new warnings
  - cargo test: 91 passed + 3 passed + 0 failed
  - SAFETY comments preserved verbatim
  - Net change: -80 lines (20 insertions, 100 deletions)

Closes #15, closes #18.
2026-06-20 20:56:34 +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%