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;
|
if (sourceAudioChannels && wantAudioChannels) audioChannels = effAudioChannels;
|
||||||
|
|
||||||
// ── Pre-roll: discard initial unstable frames ────────────────────────────
|
// ── 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`);
|
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));
|
await new Promise(r => setTimeout(r, PRE_ROLL_SECONDS * 1000));
|
||||||
bridgeProcess.stdout.removeAllListeners('data');
|
bridgeProcess.stdout.removeAllListeners('data');
|
||||||
console.log(`[capture] pre-roll complete.`);
|
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();
|
const startedAt = new Date().toISOString();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue