From 4da7bf8b413002af2f56b28793d032c4b8003fe1 Mon Sep 17 00:00:00 2001 From: zgaetano Date: Mon, 1 Jun 2026 07:54:17 -0400 Subject: [PATCH] feat(capture): replace deltacast _buildInputArgs stub with real bridge spawn --- services/capture/src/capture-manager.js | 77 ++++++++++++++----------- 1 file changed, 43 insertions(+), 34 deletions(-) diff --git a/services/capture/src/capture-manager.js b/services/capture/src/capture-manager.js index bf57fa5..2bc99ad 100644 --- a/services/capture/src/capture-manager.js +++ b/services/capture/src/capture-manager.js @@ -195,41 +195,50 @@ class CaptureManager { return { inputArgs: ['-probesize','32M','-analyzeduration','10M','-fflags','+genpts','-i', sourceUrl], isNetwork: true }; } - // Deltacast SDI via VideoMaster SDK FFmpeg plugin. - // FFmpeg input format is 'deltacast', device address is 'deltacast://'. - // When the physical device is absent (/dev/deltacast missing), fall back - // to a lavfi test card so development and integration testing work without hardware. if (sourceType === 'deltacast') { - const idx = (typeof device === 'number' || /^\d+$/.test(String(device))) - ? parseInt(device, 10) - : 0; - const { existsSync } = await import('node:fs'); - const deviceNode = `/dev/deltacast${idx}`; - if (existsSync(deviceNode)) { - console.log(`[capture] Deltacast index ${idx} → ${deviceNode} (hardware)`); - return { - inputArgs: ['-f', 'deltacast', '-i', `deltacast://${idx}`], - isNetwork: false, - }; - } else { - // No hardware — lavfi test card with port label + timecode burn-in. - // Matches the deltacast-sdi-recorder standalone app fallback exactly so - // recorded files look right in the MAM library during dev. - console.warn(`[capture] Deltacast device ${deviceNode} not found — using lavfi test card for port ${idx}`); - const testSrc = [ - `testsrc2=size=1920x1080:rate=30`, - `drawtext=text='DELTACAST PORT ${idx} — TEST MODE':fontsize=48:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2`, - `drawtext=text='%{localtime\\:%H\\:%M\\:%S}':fontsize=32:fontcolor=yellow:x=10:y=10`, - ].join(','); - return { - inputArgs: [ - '-f', 'lavfi', '-i', testSrc, - '-f', 'lavfi', '-i', 'sine=frequency=1000:sample_rate=48000', - '-map', '0:v:0', '-map', '1:a:0', - ], - isNetwork: false, - }; - } + const idx = (typeof device === 'number' || /^\d+$/.test(String(device))) + ? parseInt(device, 10) : 0; + const audioFifo = `/tmp/dc-audio-${this._sessionIdForBridge}`; + + // Create the audio FIFO before spawning the bridge. + const { execSync: _exec } = await import('child_process'); + try { _exec(`mkfifo ${audioFifo}`); } catch (_) { /* may already exist */ } + + const bridge = spawn('deltacast-capture', [ + '--device', String(idx), + '--port', String(idx), + '--audio-pipe', audioFifo, + '--signal-timeout', '30', + ], { stdio: ['ignore', 'pipe', 'pipe'] }); + + // Log bridge stderr after the first line (non-JSON diagnostic output) + let firstLineDone = false; + bridge.stderr.on('data', (d) => { + if (firstLineDone) console.error(`[deltacast-bridge] ${d}`); + else if (d.toString().includes('\n')) firstLineDone = true; + }); + + const fmt = await readFirstStderrLine(bridge, 35_000); + // fmt: { width, height, fps_num, fps_den, interlaced, pix_fmt, + // audio_channels, audio_rate, device, port } + + return { + inputArgs: [ + '-f', 'rawvideo', + '-pix_fmt', fmt.pix_fmt, + '-video_size', `${fmt.width}x${fmt.height}`, + '-framerate', `${fmt.fps_num}/${fmt.fps_den}`, + '-i', 'pipe:0', + '-f', 's16le', + '-ar', String(fmt.audio_rate), + '-ac', String(fmt.audio_channels), + '-i', audioFifo, + ], + isNetwork: false, + bridgeProcess: bridge, + audioFifo, + interlaced: !!fmt.interlaced, + }; } // Default: SDI via DeckLink