diff --git a/crates/recording/src/output_pipeline/start_gate.rs b/crates/recording/src/output_pipeline/start_gate.rs index 428407d3a7a..595f6e59919 100644 --- a/crates/recording/src/output_pipeline/start_gate.rs +++ b/crates/recording/src/output_pipeline/start_gate.rs @@ -50,7 +50,7 @@ impl RecordingStartGate { /// timeline starts where audio anchored at the recording epoch does. /// Instant merges its tracks by timestamp; Studio aligns them with each /// track's recorded start time instead and leaves this off. - pub(crate) fn set_video_origin_at_arm(&self) { + pub fn set_video_origin_at_arm(&self) { self.0.video_origin_at_arm.store(true, Ordering::Release); } diff --git a/crates/verify/expectations.toml b/crates/verify/expectations.toml index 94233df4a7f..f4a44b29d51 100644 --- a/crates/verify/expectations.toml +++ b/crates/verify/expectations.toml @@ -112,20 +112,6 @@ reason = "NEW: same anchor, loopback-style system audio (WASAPI delivers packets link = "" -[[xfail]] -case = "export/instant-gated-*mic*/output/mp4/native" -class = "sync-mic" -bound = { count = 1, offset-ms = [5, 34] } -reason = "NEW: after the start gate opens, the screen and audio pipelines each take their own first admitted frame as zero, so audio is late by up to one video frame (33.3 ms at 30 fps; 12.6 to 17.2 ms measured with the gate opening between frames)" -link = "" - -[[xfail]] -case = "export/instant-gated-system-*/output/mp4/native" -class = "sync-system" -bound = { count = 1, offset-ms = [5, 34] } -reason = "NEW: the gate's per-pipeline zero, as the gated sync-mic entry: system audio late by up to one video frame (12.2 to 15.1 ms measured)" -link = "" - [[xfail]] case = "export/instant-gated-system-only/output/mp4/native" class = "audio-system" @@ -134,21 +120,6 @@ bound = { count = 1, missing = 2, lost = 2 } reason = "NEW: gated loopback-only system audio sometimes loses markers (2 of 11 lost in 1 of 2 fresh recordings, with the track 958 ms early). Bound: at most 2 markers lost, none unexpected" link = "" -[[xfail]] -case = "export/instant/output/mp4/native" -class = "sync-mic" -bound = { count = 1, offset-ms = [5, 34] } -reason = "NEW: see export/instant-gated-*: mic audio up to one video frame late after the start gate (14.2 to 15.3 ms measured)" -link = "" - - -[[xfail]] -case = "export/instant/output/mp4/native" -class = "sync-system" -bound = { count = 1, offset-ms = [5, 34], mic-offset-ms = [5, 34], system-minus-mic-ms = [-1, 1] } -reason = "NEW: the mixer places system audio on the microphone's timeline (system minus mic 0 ms), so it shares the gated mic offset of the sync-mic entry above (13.3 to 15.4 ms measured)" -link = "" - [[xfail]] case = "*/basic/transition/*60*/avf" class = "stale" diff --git a/crates/verify/src/pipeline.rs b/crates/verify/src/pipeline.rs index cf127cc6512..75512c0feff 100644 --- a/crates/verify/src/pipeline.rs +++ b/crates/verify/src/pipeline.rs @@ -606,9 +606,13 @@ pub async fn record_instant(spec: &InstantSpec, dir: &Path) -> anyhow::Result<() std::fs::create_dir_all(&content)?; let timestamps = Timestamps::now(); let base = timestamps.instant(); - let gate = spec - .arm_at - .map(|_| cap_recording::RecordingStartGate::new()); + // Mirrors the product's Instant gate: the screen track starts at the arm + // point, where the epoch-anchored audio below starts. + let gate = spec.arm_at.map(|_| { + let gate = cap_recording::RecordingStartGate::new(); + gate.set_video_origin_at_arm(); + gate + }); let mut emitted = Emitted { fps: spec.screen.fps, ..Default::default() diff --git a/crates/verify/src/report.rs b/crates/verify/src/report.rs index c35b36d83dc..1020029116d 100644 --- a/crates/verify/src/report.rs +++ b/crates/verify/src/report.rs @@ -645,43 +645,4 @@ mod tests { Some("track absent") ); } - #[test] - fn instant_system_audio_expectation_requires_the_microphone_timeline() { - let entries = cap_verify_expect::parse(include_str!("../expectations.toml")).unwrap(); - let entries: Vec<_> = entries - .iter() - .filter(|entry| { - entry.class == "sync-system" && entry.case == "export/instant/output/mp4/native" - }) - .collect(); - assert_eq!(entries.len(), 1); - assert!(!entries[0].flaky); - let metrics = |offset: f64, mic: f64| { - BTreeMap::from([ - ("count".into(), cap_verify_expect::Range::at(1.0)), - ("offset-ms".into(), cap_verify_expect::Range::at(offset)), - ("mic-offset-ms".into(), cap_verify_expect::Range::at(mic)), - ( - "system-minus-mic-ms".into(), - cap_verify_expect::Range::at(((offset - mic) * 10.0_f64).round() / 10.0), - ), - ]) - }; - let accepts = |metrics: &BTreeMap| { - cap_verify_expect::within(&entries[0].bound, Some(metrics)).is_ok() - }; - for (offset, mic) in [(14.0, 14.0), (13.3, 13.3), (15.4, 15.0)] { - assert!(accepts(&metrics(offset, mic))); - } - // The -60 ms and -6.7 ms mixer modes, and system audio off the mic. - for (offset, mic) in [(-46.1, 13.9), (7.8, 14.5), (16.0, 14.5), (14.0, 35.0)] { - assert!(!accepts(&metrics(offset, mic))); - } - let mut absent = metrics(14.0, 14.0); - absent.remove("system-minus-mic-ms"); - assert!(!accepts(&absent)); - for platform in ["macos", "windows", "linux"] { - assert!(entries[0].matches("export/instant/output/mp4/native", platform)); - } - } } diff --git a/crates/verify/tests/export.rs b/crates/verify/tests/export.rs index 46d062216ee..a4080e83494 100644 --- a/crates/verify/tests/export.rs +++ b/crates/verify/tests/export.rs @@ -654,7 +654,6 @@ fn instant_case(spec: cap_verify::pipeline::InstantSpec) -> Option { } let audio = media::decode_audio(&output, None).expect("decode instant audio"); let mono = audio.mono(); - let mut mic_offset_ms = None; for source in [AudioSource::Mic, AudioSource::System] { let expected: Vec<(u32, f64)> = emitted .audio @@ -795,21 +794,7 @@ fn instant_case(spec: cap_verify::pipeline::InstantSpec) -> Option { ), ); } - if source == AudioSource::Mic { - mic_offset_ms = Some((av * 1e4).round() / 10.0); - } if av.abs() > 0.005 { - if source == AudioSource::System - && case.id == "export/instant/output/mp4/native" - && let Some(mic_offset_ms) = mic_offset_ms - { - case.measure(&sync_class, "mic-offset-ms", mic_offset_ms); - case.measure( - &sync_class, - "system-minus-mic-ms", - ((av * 1e3 - mic_offset_ms) * 10.0).round() / 10.0, - ); - } case.measure(&sync_class, "offset-ms", (av * 1e4).round() / 10.0); case.problem( &sync_class,