Skip to content

[Bug]: Linux screen capture time-compresses under frame drops, desyncing webcam/audio/cursor #511

Description

@Beetix

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

  1. On Linux/PipeWire, record the screen under enough GPU/CPU load to starve the capture pipeline (an active app while the machine is busy).
  2. Stop and ffprobe the result:
    ffprobe -select_streams v:0 -count_frames \
      -show_entries stream=nb_read_frames,avg_frame_rate,duration <file>.mp4
    
  3. Compare nb_read_frames / fps against the session's real wall-clock length (and the sibling -webcam.webm duration). 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions