feat(capture): AUDIO_OFFSET_MS knob for fixed A/V alignment trim

The deltacast bridge captures audio and video on separate VHD streams; any
constant capture-path latency difference shows as a fixed A/V offset (e.g.
audio slightly ahead of video) even though stream lengths stay locked (no
drift, verified ~1 frame over 461s). AUDIO_OFFSET_MS applies an -itsoffset on
the SDI/Deltacast audio input only: positive DELAYS audio (audio-ahead case),
negative advances it. Default 0 = no change, fully non-destructive, clamped
to +/-1000ms. Lets an operator dial out residual offset with a lipsync loop
without a code change.
This commit is contained in:
OpenCode 2026-06-05 05:27:27 +00:00
parent e64281c9fd
commit c40de38c45

View file

@ -197,6 +197,21 @@ const CAPTURE_GPU_INDEX = (() => {
// as an encoder option right after -c:v. Returns [] when no pin is configured.
const nvencGpuSel = () => (CAPTURE_GPU_INDEX != null ? ['-gpu', String(CAPTURE_GPU_INDEX)] : []);
// Optional fixed A/V alignment trim for the SDI/Deltacast audio input. The
// deltacast bridge captures audio and video on separate VHD streams; any
// constant capture-path latency difference between them shows as a fixed A/V
// offset (e.g. audio slightly ahead of video) even though stream LENGTHS stay
// locked (no drift). AUDIO_OFFSET_MS lets an operator dial that out without a
// rebuild: POSITIVE value DELAYS audio (use when audio is AHEAD of video),
// NEGATIVE advances it. Applied as ffmpeg `-itsoffset` on the audio input only.
// Default 0 = no change (fully non-destructive). Range-clamped to ±1000 ms.
const audioOffsetArgs = () => {
const raw = parseFloat(process.env.AUDIO_OFFSET_MS || '0');
if (!Number.isFinite(raw) || raw === 0) return [];
const ms = Math.max(-1000, Math.min(1000, raw));
return ['-itsoffset', (ms / 1000).toFixed(4)];
};
function hevcNvencArgs(framerate, growing) {
const base = ['-c:v', 'hevc_nvenc', ...nvencGpuSel(), '-preset', 'p4', '-rc', 'vbr', '-profile:v', 'main10'];
if (growing) {
@ -821,6 +836,8 @@ class CaptureManager {
'-f', 's16le',
'-ar', '48000',
'-ac', '2',
// Optional fixed A/V trim (env AUDIO_OFFSET_MS); default empty = no shift.
...audioOffsetArgs(),
'-i', audioFifoPath,
],
isNetwork: false,