Repository navigation
fix(recording): keep startup frame drops from shifting video ahead of audio - #2573
Merged
Merged
Conversation
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Member
Author
|
hey @greptileai, please re-review the PR |
Member
Author
|
hey @greptileai, please re-review the PR |
richiemcilroy
added a commit
that referenced
this pull request
Oct 15, 2026
Frame-level logs of one instrumented run (38753929239, full tier x5 per OS, logs of every source hand-off, ScreenCaptureKit buffer with its status, Cap callback send, pipeline input and output and mux call) split the intermittent failures into two causes. Faulty input, proven by logs independent of the recorder, is now detected directly and the run is retried as inconclusive (a case that stays inconclusive fails as environment): - The stimulus runs its own small ScreenCaptureKit stream, another client in another process. When it receives no frame for more than four stimulus periods while pictures are presented (outside holds), the window server or capture service stalled for every client: in the logged failure the recorder process froze 293 ms with the rest of the VM, the capture service reported Idle while the stimulus changed its picture, and then delivered nothing for 1002 ms while the recorder was idle and responsive. - The synthetic camera and microphone run on their own user-interactive threads (a capture device's callback thread) instead of the recorder's async runtime, where hand-offs ran 20 to 140 ms late, and a hand-off more than 100 ms late inside the recording means the host stalled the source. Cap's own defect gets a dedicated class and strict expectation: past the 2 s drift anchor the recorder stamps camera frames from the processing wall clock instead of their capture time (written pts minus source time 0.00 ms before the anchor, -1.1 to +71 ms after). Camera frame checks before the anchor stay strict timestamp checks (the #2573 class lives there); past it they and the camera-mic offset are reported as camera-clock, and a strict check that past-anchor offsets stay within 4 ms (the microphone's limit) proves the defect in every camera recording. No threshold changed.
richiemcilroy
added a commit
that referenced
this pull request
Oct 15, 2026
Frame-level logs of one instrumented run (38753929239, full tier x5 per OS, logs of every source hand-off, ScreenCaptureKit buffer with its status, Cap callback send, pipeline input and output and mux call) split the intermittent failures into two causes. Faulty input, proven by logs independent of the recorder, is now detected directly and the run is retried as inconclusive (a case that stays inconclusive fails as environment): - The stimulus runs its own small ScreenCaptureKit stream, another client in another process. When it receives no frame for more than four stimulus periods while pictures are presented (outside holds), the window server or capture service stalled for every client: in the logged failure the recorder process froze 293 ms with the rest of the VM, the capture service reported Idle while the stimulus changed its picture, and then delivered nothing for 1002 ms while the recorder was idle and responsive. - The synthetic camera and microphone run on their own user-interactive threads (a capture device's callback thread) instead of the recorder's async runtime, where hand-offs ran 20 to 140 ms late, and a hand-off more than 100 ms late inside the recording means the host stalled the source. Cap's own defect gets a dedicated class and strict expectation: past the 2 s drift anchor the recorder stamps camera frames from the processing wall clock instead of their capture time (written pts minus source time 0.00 ms before the anchor, -1.1 to +71 ms after). Camera frame checks before the anchor stay strict timestamp checks (the #2573 class lives there); past it they and the camera-mic offset are reported as camera-clock, and a strict check that past-anchor offsets stay within 4 ms (the microphone's limit) proves the defect in every camera recording. No threshold changed.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every video frame dropped while a recording starts (capture pool still full while VideoToolbox initialises: 0 to 6 drops per run on main) shifted video one frame (16.7 ms at 60 fps) early relative to audio for the whole recording. Natural 60 s recordings on main measured a 44 ms median audio-video offset instead of the constant ~21 ms AAC priming.
Root cause
Upper line:
main. Lower line: this PR (only the first frame after the skip stays on the snapped slot).crates/timestamp/src/master_clock.rs:294-309snaps any frame within 70 ms ofnext_expectedontonext_expected. After a skip the next frame is a whole slot late, and it is snapped back onto the skipped slot.master_clock.rs:318derives the next expected time from the snapped value, so the ladder stays a slot behind for as long as frames keep arriving inside 70 ms. Up to 4 consecutive drops at 60 fps stick (5+ exceed the threshold and reset).crates/recording/src/output_pipeline/core.rs:1188emits that lagged time for the first 2 s,core.rs:1196takes the drift anchor from it, andcore.rs:1206emits anchor + wall clock afterwards, so the lag is frozen into the rest of the recording.The snap already bounds how far the ladder may lead the source. Nothing bounded the lag.
Fix
SourceClockState::detecting_skipped_frames()(opt in, used only by the video mux task on macOS, where screen and camera frames carry their CMSampleBuffer capture time; the audio mixer's remap is untouched). In the snap branch:VideoDriftTracker::defer_anchor: the 2 s anchor is not taken on a frame that is still pending, so a skip that lands on the anchor frame cannot freeze a lag either.remap_preserving_cadenceand is unaffected.Results
Synthetic recorder driving the real macOS output pipelines (3024x1964 NV12 60 fps through VideoToolbox, mic + system audio, 1 s flash/click markers), base
68d6c31b4vs this PR, interleaved A/B on the same host. Offset is audio minus video per marker; ~21 ms is the AAC priming constant (instant mode has none, so ~0 there).yesx8), 30 sReal capture,
cap selftest av-sync(on-screen flashes and speaker beeps recorded through ScreenCaptureKit and system audio, Studio mode, 20 s, alternating runs). On a 120 Hz display ScreenCaptureKit delivers most frames 25 ms apart, so the 60 fps ladder sees half-slot lags all the time:mainmainfreezes video two to three slots ahead of its capture time for most of each recording; this PR keeps it within half a slot, so the offset stops depending on how many frames dropped at start.Runs on
mainwith 0, 5 or 8 forced drops still lagged whenever the busy host caused natural drops inside the first 2 s (the 86.3 and 54.3 outliers).No-skip behaviour is unchanged:
skip_count0), so their remapped timestamps matchmainframe for frame. Muxed frame counts, durations, decode errors (0) and manifest completeness match across all classes.
Confidence Score: 5/5
The PR appears safe to merge.Summary
Corrects startup frame-drop timing and keeps the drift anchor off frames whose lag is still pending.
Reviews (3) · Last reviewed commit: "docs(timestamp): note skipped-frame dete..." · Reviewed by Greptile