Repository navigation
[Bug]: Linux screen capture time-compresses under frame drops, desyncing webcam/audio/cursor #511
Description
Describe the bug
On Linux (PipeWire capture), the recorded screen video silently time-compresses when the capture pipeline drops frames under load, so the screen track ends up shorter than real elapsed time and plays fast, drifting ahead of the webcam, audio, and the cursor overlay.
The screen encoder writes constant frame rate with PTS = frame index (electron/native/pipewire-capture/src/encoder.rs, time_base = 1/fps). The pacing loop (capture.rs::advance + current_index) is meant to hold the last picture across gaps and backfill missed 60 fps ticks, but the backfill is bounded (MAX_CATCHUP_FRAMES = 8 per advance() call) and advance() only runs from two event-loop arms (FrameReady and the idle Timeout heartbeat). Under sustained GPU/CPU load the loop is starved and the catch-up cannot keep up, so next_index — which is simultaneously the PTS and the frame counter — permanently lags the wall clock. The lost real-time interval simply disappears: file duration == frames_encoded / fps, with no dependence on elapsed time, and nothing reconciles the deficit at stop.
Because every other track is wall-clock based (audio sample-count PTS, webcam WebM, cursor timeMs), the screen is the only track on a "frame-index" clock, so the moment any frame drops it drifts against everything else and cameraTrack.offsetMs (a small constant nudge) cannot compensate a proportional stretch.
Field-diagnosed measurements from one real recording (61.06 s session):
| Track | Duration | Notes |
|---|---|---|
| Screen (H.264) | 55.2 s | 3313 frames @ constant 60 fps |
| Audio (AAC) | 61.01 s | wall-clock correct |
| Webcam (webm) | 61.06 s | wall-clock correct |
| Cursor (cursor.json) | 60.92 s | wall-clock correct |
~350 video frames (~5.9 s) were silently lost and the survivors stamped at a uniform 60 fps → 61 s of activity compressed into a 55.2 s file that plays ~10% fast. Three other same-session recordings that didn't hit the load had screen/webcam deltas of only ~80–90 ms and looked fine.
Expected behavior
The recorded screen video's duration should equal real elapsed wall-clock time even when frames are dropped, so it stays in sync with audio, webcam, and the cursor overlay. Dropped frames should be represented as timing (a longer-held frame), never as removed time.
To Reproduce
- On Linux/PipeWire, record the screen under enough GPU/CPU load to starve the capture pipeline (an active app while the machine is busy).
- Stop and
ffprobethe result:ffprobe -select_streams v:0 -count_frames \ -show_entries stream=nb_read_frames,avg_frame_rate,duration <file>.mp4 - Compare
nb_read_frames / fpsagainst the session's real wall-clock length (and the sibling-webcam.webmduration). A shortfall = dropped frames not reflected in the timeline; the screen plays ahead of the webcam.
OS
Linux
OS Version
Any Linux/Wayland with PipeWire ScreenCast (observed on GNOME/mutter + AMD).
Additional context
- Root cause is pre-existing in the original constant-frame-rate pacing design (
capture.rs/encoder.rs), independent of the dmabuf/VAAPI zero-copy work ([Bug]: Whole-screen recordings freeze/judder on GNOME/Wayland (AMD) — capture forced onto shm, mutter under-delivers vs dmabuf #507/fix(capture): zero-copy dmabuf→VAAPI capture — fix whole-screen recording freeze on GNOME/AMD (#507) #508). It affects both the shm and dmabuf paths — any Linux recording that drops frames. The dmabuf path makes drops (and heartbeat starvation) more likely, which is how it surfaced. - Proposed fix: stamp each encoded frame with its real capture wall-clock time (rescaled into
time_base) and mux VFR, so file duration == real elapsed time even under drops; keep the clock heartbeat so a static screen still extends duration. The editor/compositor already seek/play by decoded PTS (same path as the already-VFR webcam), so VFR is tolerated. Add capture-side telemetry that warns whendurationdiverges from measured wall-clock beyond a small epsilon, so this can't regress silently. - Full engineering write-up lives in
technical-documentation/engineering/linux-capture-frame-drop-desync-bug.md.
Activity
Merge fix/linux-capture-vfr-walltime-pts into staging (wall-clock PTS, …
fix(capture-linux): measure the cursor against the window, not its mo…
fix(capture): stamp Linux video frames with wall-clock PTS to stop ti…
fix(capture): pass the dmabuf argument the three new tests omitted
fix(capture): stamp Linux video frames with wall-clock PTS to stop ti…
fix(capture): pass the dmabuf argument the three new tests omitted
fix(capture): pass the dmabuf argument the three new tests omitted
github-actions commentedon Sep 10, 2026
Fixed by #512 and merged into
main.This change is assigned to the
Next Releaserelease milestone and is not necessarily available in the latest downloadable desktop release yet. It is currently marked asstatus: pending releaseuntil a packaged release containing it is published.fix(capture): stamp Linux video frames with wall-clock PTS to stop ti…
fix(capture-linux): measure the cursor against the window, not its mo…
fix(capture-linux): measure the cursor against the window, not its mo…
chore(sync): port upstream fixes and cursor-dwell auto-zoom through f…
EtienneLescot commentedon Sep 15, 2026
Moved into
v1.12.0as part of the pre-release cut.