Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion crates/recording/src/output_pipeline/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,15 @@ pub(crate) async fn apply_video_start_gate(
offset_ns = offset_ns as i64,
"Trimmed leading audio samples for encoder-pair alignment"
);
VideoStartGateAction::UseFrame(AudioFrame::new(trimmed, frame.timestamp))
// Advance the timestamp to the first *committed* sample so that
// first_timestamp (and therefore mic_start_time in metadata) reflects
// the actual capture time after the trim, not the pre-trim buffer start.
// Without this, the editor's mic_offset = display_start - mic_start equals
// trim_duration and causes it to skip that same duration a second time.
let trim_duration = Duration::from_nanos(
trim_samples as u64 * 1_000_000_000 / sample_rate as u64,
);
Comment on lines +204 to +206

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The trim_samples * 1_000_000_000 / sample_rate math is safe here given current buffer sizes, but it’s a bit brittle if this ever changes. Using u128 for the intermediate avoids accidental overflow and makes intent clearer.

Suggested change
let trim_duration = Duration::from_nanos(
trim_samples as u64 * 1_000_000_000 / sample_rate as u64,
);
let trim_duration_ns =
(trim_samples as u128) * 1_000_000_000u128 / sample_rate as u128;
let trim_duration = Duration::from_nanos(trim_duration_ns as u64);

VideoStartGateAction::UseFrame(AudioFrame::new(trimmed, frame.timestamp + trim_duration))
}
None => {
warn!(
Expand Down Expand Up @@ -4706,6 +4714,21 @@ mod tests {
let expected_trim =
ns_to_sample_count(video_start_ns, info.sample_rate) as usize;
assert_eq!(new_frame.inner.samples(), 2048 - expected_trim);
// Timestamp must be advanced by the trim so that mic_start_time in
// metadata reflects the first committed sample, preventing the editor
// from double-counting the same gap as a skip offset.
let trim_duration = Duration::from_nanos(
expected_trim as u64 * 1_000_000_000 / info.sample_rate as u64,
);
let expected_ts = Timestamp::Instant(start + trim_duration);
assert_eq!(
new_frame
.timestamp
.signed_duration_since_secs(master_clock.timestamps()),
expected_ts
.signed_duration_since_secs(master_clock.timestamps()),
"gate UseFrame timestamp must be advanced past the trimmed samples"
);
}
other => panic!("expected UseFrame when audio leads, got {other:?}"),
}
Expand Down
Loading