diff --git a/services/capture/src/capture-manager.js b/services/capture/src/capture-manager.js index e2a38af..60cf556 100644 --- a/services/capture/src/capture-manager.js +++ b/services/capture/src/capture-manager.js @@ -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',