fix(capture): skip video-only pre-roll in standby to stop A/V pitch drift
The pre-roll drained only the video pipe (fc_pipe) while the audio FIFO kept buffering, so ffmpeg read ~PRE_ROLL_SECONDS of surplus pre-roll audio — making audio longer than video, which when synced compresses audio ~0.5% (pitch-up, measured: 2591573 audio samples vs 2579395 expected for the video duration). In standby the framecache slot is already warm (no unstable startup frames), so the drain is unnecessary; skipping it lets ffmpeg open video and audio together from the same instant. Cold on-demand spawns keep the brief drain.
This commit is contained in:
parent
07eea02109
commit
51b66d882f
1 changed files with 16 additions and 3 deletions
|
|
@ -1078,13 +1078,26 @@ exit "$BMXRC"
|
|||
if (sourceAudioChannels && wantAudioChannels) audioChannels = effAudioChannels;
|
||||
|
||||
// ── Pre-roll: discard initial unstable frames ────────────────────────────
|
||||
if (bridgeProcess && (sourceType === 'deltacast' || sourceType === 'blackmagic' || sourceType === 'sdi')) {
|
||||
// CRITICAL A/V-SYNC NOTE: this drains ONLY the VIDEO pipe (fc_pipe stdout).
|
||||
// The audio FIFO is opened by ffmpeg, not here, so it keeps BUFFERING during
|
||||
// the drain — when ffmpeg starts it reads that buffered pre-roll audio,
|
||||
// making the audio stream ~PRE_ROLL_SECONDS longer than video. Synced to the
|
||||
// video length that surplus audio is compressed → a slight pitch-up.
|
||||
//
|
||||
// In STANDBY mode the framecache slot has been warm for a long time, so there
|
||||
// are NO unstable startup frames to discard — skip the asymmetric drain
|
||||
// entirely and let ffmpeg open video (fc_pipe) and audio (FIFO) together so
|
||||
// both streams start from the same instant. Only the legacy on-demand spawn
|
||||
// (cold slot) still needs the brief drain.
|
||||
if (bridgeProcess && !_standbyMode
|
||||
&& (sourceType === 'deltacast' || sourceType === 'blackmagic' || sourceType === 'sdi')) {
|
||||
console.log(`[capture] pre-rolling: discarding ${PRE_ROLL_SECONDS}s of frames`);
|
||||
// Attach temporary drain listener.
|
||||
bridgeProcess.stdout.on('data', () => {});
|
||||
bridgeProcess.stdout.on('data', () => {});
|
||||
await new Promise(r => setTimeout(r, PRE_ROLL_SECONDS * 1000));
|
||||
bridgeProcess.stdout.removeAllListeners('data');
|
||||
console.log(`[capture] pre-roll complete.`);
|
||||
} else if (bridgeProcess) {
|
||||
console.log('[capture] standby/warm slot — skipping video-only pre-roll to keep A/V aligned');
|
||||
}
|
||||
|
||||
const startedAt = new Date().toISOString();
|
||||
|
|
|
|||
Loading…
Reference in a new issue