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 @@ -187,7 +187,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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice fix. Minor tweak: consider reusing the existing samples_to_nanos() helper here to avoid a potential divide-by-zero and to keep overflow/rounding behavior consistent.

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

);
VideoStartGateAction::UseFrame(AudioFrame::new(trimmed, frame.timestamp + trim_duration))
}
None => {
warn!(
Expand Down Expand Up @@ -4492,6 +4500,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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same thought in the test: using samples_to_nanos() keeps the conversion logic centralized and avoids info.sample_rate == 0 panicking in the future.

Suggested change
expected_trim as u64 * 1_000_000_000 / info.sample_rate as u64,
let trim_duration = Duration::from_nanos(
samples_to_nanos(expected_trim as u64, info.sample_rate),
);

);
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