fix(capture): map deltacast audio from input 1; per-recorder channel/port; fix bridge stdin pipe
- Audio map: the deltacast bridge delivers audio on a separate FIFO wired as ffmpeg input 1, so the finalized master + HLS preview (and the growing orchestrator) now map audio via `audioMap` (1🅰️0? for deltacast, 0🅰️0? for DeckLink SDI / network) instead of an unconditional 0🅰️0?. Without this the deltacast master/preview carried no audio. - Channel/port: spawn the bridge with --device = board index (default 0) and --port = source_config.port (falling back to the device index), so a recorder can capture from any of the board's 8 channels. Adds `port`/`board` params to start() and _buildInputArgs(). - Bridge stdin: the finalized-master ffmpeg reads the bridge's raw video from pipe:0, so its stdin must be 'pipe' when a bridge is present (was 'ignore', which made hiresProcess.stdin null and threw "Cannot read properties of null (reading 'on')" at bridgeProcess.stdout.pipe(...)). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
3d3c8c48de
commit
d6e515e1a8
1 changed files with 28 additions and 10 deletions
|
|
@ -559,7 +559,7 @@ class CaptureManager {
|
|||
* Returns { inputArgs, isNetwork }
|
||||
* @private
|
||||
*/
|
||||
async _buildInputArgs({ sourceType, sourceBackend = 'blackmagic', device, sourceUrl, listen, listenPort, streamKey }) {
|
||||
async _buildInputArgs({ sourceType, sourceBackend = 'blackmagic', device, port, board, sourceUrl, listen, listenPort, streamKey }) {
|
||||
if (sourceType === 'srt') {
|
||||
let url;
|
||||
if (listen) {
|
||||
|
|
@ -599,9 +599,17 @@ class CaptureManager {
|
|||
throw new Error(`Failed to create audio FIFO ${audioFifo}: ${e.message}`);
|
||||
}
|
||||
|
||||
// ONE board (index 0) carries 8 channels (ports 0-7). --device is the
|
||||
// board index, --port is the selected channel. board defaults to 0; the
|
||||
// capture channel comes from source_config.port, falling back to the
|
||||
// legacy device index so existing single-value recorders keep working.
|
||||
const boardIdx = (typeof board === 'number' || /^\d+$/.test(String(board)))
|
||||
? parseInt(board, 10) : 0;
|
||||
const portIdx = (typeof port === 'number' || /^\d+$/.test(String(port)))
|
||||
? parseInt(port, 10) : idx;
|
||||
const bridge = spawn('deltacast-capture', [
|
||||
'--device', String(idx),
|
||||
'--port', String(idx),
|
||||
'--device', String(boardIdx),
|
||||
'--port', String(portIdx),
|
||||
'--audio-pipe', audioFifo,
|
||||
'--signal-timeout', '30',
|
||||
], { stdio: ['ignore', 'pipe', 'pipe'] });
|
||||
|
|
@ -669,7 +677,7 @@ class CaptureManager {
|
|||
*
|
||||
* Returns the argv for spawn('bash', argv).
|
||||
*/
|
||||
_buildGrowingOrchestrator({ inputArgs, videoBitrate, resolution, framerate, audioChannels, outPath, hlsDir, videoCodec }) {
|
||||
_buildGrowingOrchestrator({ inputArgs, videoBitrate, resolution, framerate, audioChannels, outPath, hlsDir, videoCodec, audioMap = '0:a:0?' }) {
|
||||
const { rawFlag, frameRate, ffRate } = deriveGrowingRaster(resolution, framerate);
|
||||
const vb = videoBitrate || GROWING_DEFAULT_BITRATE;
|
||||
const ach = audioChannels ? Number(audioChannels) : 2;
|
||||
|
|
@ -691,7 +699,7 @@ class CaptureManager {
|
|||
'-r', ffRate,
|
||||
'-f', 'mpeg2video', '@VF@',
|
||||
// (b) PCM s16le audio → "$AF"
|
||||
'-map', '0:a:0?',
|
||||
'-map', audioMap,
|
||||
'-c:a', 'pcm_s16le', '-ar', '48000', '-ac', String(ach),
|
||||
'-f', 's16le', '@AF@',
|
||||
];
|
||||
|
|
@ -699,7 +707,7 @@ class CaptureManager {
|
|||
if (hlsDir) {
|
||||
ffHls = [
|
||||
// (c) H.264 HLS preview — GPU-gated, unchanged behaviour.
|
||||
'-map', '[vlo]', '-map', '0:a:0?',
|
||||
'-map', '[vlo]', '-map', audioMap,
|
||||
...buildHlsVideoArgs(videoCodec, framerate),
|
||||
'-c:a', 'aac', '-b:a', '128k', '-ar', '44100',
|
||||
'-f', 'hls', '-hls_time', '2', '-hls_list_size', '15',
|
||||
|
|
@ -813,6 +821,10 @@ exit "$BMXRC"
|
|||
binId,
|
||||
clipName,
|
||||
device,
|
||||
// Deltacast: one board (index 0) with 8 channels. `port` selects the
|
||||
// channel; `board` selects the physical board (default 0).
|
||||
port,
|
||||
board,
|
||||
sourceType = 'sdi',
|
||||
// Source-backend selection for SDI capture (issue #168). Defaults to
|
||||
// `blackmagic` (DeckLink) so existing recorders are unaffected.
|
||||
|
|
@ -893,9 +905,14 @@ exit "$BMXRC"
|
|||
|
||||
this._sessionIdForBridge = sessionId;
|
||||
const { inputArgs, isNetwork, bridgeProcess = null, audioFifo = null, interlaced = false } = await this._buildInputArgs({
|
||||
sourceType, sourceBackend, device, sourceUrl, listen, listenPort, streamKey,
|
||||
sourceType, sourceBackend, device, port, board, sourceUrl, listen, listenPort, streamKey,
|
||||
});
|
||||
|
||||
// Audio input index: the deltacast bridge delivers audio on a separate
|
||||
// FIFO wired as ffmpeg input 1, whereas DeckLink SDI and network sources
|
||||
// carry audio inside input 0. (bridgeProcess is set only for deltacast.)
|
||||
const audioMap = bridgeProcess ? '1:a:0?' : '0:a:0?';
|
||||
|
||||
// Non-growing master: ffmpeg muxes the finalized MOV directly. Growing
|
||||
// master: raw2bmx muxes the OP1a from elementary FIFOs (handled below via
|
||||
// the orchestrator), so we don't build ffmpeg codec args here for it.
|
||||
|
|
@ -935,7 +952,7 @@ exit "$BMXRC"
|
|||
catch (err) { console.error('[capture] could not create temp master dir:', err.message); }
|
||||
}
|
||||
const hiresOutput = localMasterPath;
|
||||
const hiresStdio = ['ignore', 'ignore', 'pipe'];
|
||||
const hiresStdio = [bridgeProcess ? 'pipe' : 'ignore', 'ignore', 'pipe'];
|
||||
|
||||
// For SDI we cannot open the DeckLink device a second time for a preview
|
||||
// tee, so the live HLS preview is produced as a SECOND OUTPUT of the hires
|
||||
|
|
@ -967,6 +984,7 @@ exit "$BMXRC"
|
|||
outPath: growingPath,
|
||||
hlsDir: (sourceType === 'sdi') ? sdiHlsDir : null,
|
||||
videoCodec,
|
||||
audioMap,
|
||||
});
|
||||
console.log('[capture] growing master via raw2bmx; orchestrator script length=' + orchArgs[1].length);
|
||||
hiresProcess = spawn('bash', orchArgs, { stdio: ['ignore', 'ignore', 'pipe'], detached: true });
|
||||
|
|
@ -978,14 +996,14 @@ exit "$BMXRC"
|
|||
...inputArgs,
|
||||
'-filter_complex', '[0:v]yadif=mode=1:deint=1,split=2[vhi][vlo]',
|
||||
// Output 0 — ProRes/MOV master (local temp, uploaded to S3 on stop)
|
||||
'-map', '[vhi]', '-map', '0:a:0?',
|
||||
'-map', '[vhi]', '-map', audioMap,
|
||||
...hiresCodecArgs,
|
||||
hiresOutput,
|
||||
// Output 1 — low-latency H.264 HLS preview for the UI monitor.
|
||||
// GPU-encoded (h264_nvenc) when the GPU is attached to this sidecar,
|
||||
// otherwise libx264 (issue #164). GOP is pinned to one IDR per HLS
|
||||
// segment so segments start on keyframes (avoids black/flashing).
|
||||
'-map', '[vlo]', '-map', '0:a:0?',
|
||||
'-map', '[vlo]', '-map', audioMap,
|
||||
...buildHlsVideoArgs(videoCodec, framerate),
|
||||
'-c:a', 'aac', '-b:a', '128k', '-ar', '44100',
|
||||
'-f', 'hls', '-hls_time', '2', '-hls_list_size', '15',
|
||||
|
|
|
|||
Loading…
Reference in a new issue