fix(capture): disable concurrent SDI proxy ffmpeg — DeckLink Duo 2 rejects second reader
DeckLink Duo 2 does not support two simultaneous ffmpeg processes on the
same port. The second (proxy) process immediately gets 'Cannot Autodetect
input stream or No signal', producing an empty upload that could crash the
container before the hires upload completes.
Fix: remove the parallel proxy spawn for SDI entirely. proxyKey is now
always null for SDI recordings (same as SRT/RTMP). needsProxy=true is
already set when proxyKey is null, so the BullMQ worker generates the
proxy from the hires master after stop — same pattern that works for
network sources.
Also revert bad regex change: ffmpeg -sources decklink output on this
hardware uses hex-address format ('81:76669a80:00000000 [DeckLink Duo (1)]')
not bare indented names — original regex was correct.
This commit is contained in:
parent
354731a363
commit
b6f5b9b407
2 changed files with 20 additions and 52 deletions
|
|
@ -149,10 +149,9 @@ class CaptureManager {
|
|||
const out = execSync('ffmpeg -hide_banner -sources decklink 2>&1', { encoding: 'utf-8', timeout: 5000 });
|
||||
const names = [];
|
||||
for (const line of out.split('\n')) {
|
||||
// Device name lines are indented (start with one or more spaces).
|
||||
// Header/blank lines are skipped.
|
||||
const m = line.match(/^ {2,}(.+?)\s*$/);
|
||||
if (m && m[1]) names.push(m[1]);
|
||||
// DeckLink source lines: " 81:76669a80:00000000 [DeckLink Duo (1)] (none)"
|
||||
const m = line.match(/^\s+[0-9a-f:]+\s+\[([^\]]+)\]/);
|
||||
if (m) names.push(m[1]);
|
||||
}
|
||||
if (names[idx]) {
|
||||
deckLinkName = names[idx];
|
||||
|
|
@ -230,12 +229,16 @@ class CaptureManager {
|
|||
catch (err) { console.error('[capture] could not create growing dir:', err.message); }
|
||||
}
|
||||
|
||||
// Network sources cannot be opened by two FFmpeg processes simultaneously
|
||||
// (one socket = one consumer). For SRT/RTMP the BullMQ worker generates
|
||||
// the proxy after the recording stops.
|
||||
const proxyKey = (sourceType === 'sdi' && proxyEnabled)
|
||||
? `projects/${projectId}/proxies/${clipName}.${proxyExt}`
|
||||
: null;
|
||||
// DeckLink hardware does NOT support concurrent capture from the same port.
|
||||
// Opening a second ffmpeg process on the same DeckLink input while the first
|
||||
// is already capturing causes "Cannot Autodetect input stream or No signal"
|
||||
// on the second process — making the proxy empty and potentially crashing the
|
||||
// container before the hires upload completes.
|
||||
//
|
||||
// Treat SDI the same as SRT/RTMP: set proxyKey=null here and let the BullMQ
|
||||
// worker generate the proxy from the hires master after the recording stops.
|
||||
// The stop handler sets needsProxy=true so the worker picks it up.
|
||||
const proxyKey = null;
|
||||
|
||||
const startedAt = new Date().toISOString();
|
||||
|
||||
|
|
@ -318,39 +321,8 @@ class CaptureManager {
|
|||
}
|
||||
});
|
||||
|
||||
// SDI only: spawn a second ffmpeg for the proxy.
|
||||
// DeckLink cards allow concurrent reads; network sockets do not.
|
||||
if (!isNetwork && proxyEnabled) {
|
||||
const proxyCodecArgs = buildEncodeArgs({
|
||||
codec: proxyVideoCodec,
|
||||
videoBitrate: proxyVideoBitrate,
|
||||
framerate: proxyFramerate,
|
||||
audioCodec: proxyAudioCodec,
|
||||
audioBitrate: proxyAudioBitrate,
|
||||
audioChannels: proxyAudioChannels,
|
||||
container: proxyContainer,
|
||||
isNetwork: false,
|
||||
isProxy: true,
|
||||
});
|
||||
|
||||
console.log('[capture] proxy ffmpeg args:', proxyCodecArgs.join(' '));
|
||||
|
||||
const proxyProcess = spawn('ffmpeg', [
|
||||
...inputArgs,
|
||||
...sdiFilterArgs,
|
||||
...proxyCodecArgs,
|
||||
'-movflags', '+frag_keyframe+empty_moov',
|
||||
'pipe:1',
|
||||
], { stdio: ['ignore', 'pipe', 'pipe'] });
|
||||
|
||||
const proxyUpload = createUploadStream(S3_BUCKET, proxyKey, proxyProcess.stdout);
|
||||
processes.proxy = proxyProcess;
|
||||
uploads.proxy = proxyUpload;
|
||||
|
||||
proxyProcess.stderr.on('data', (data) => {
|
||||
console.error(`[PROXY] ${data}`);
|
||||
});
|
||||
}
|
||||
// Proxy is generated after stop by the BullMQ worker (same as SRT/RTMP).
|
||||
// DeckLink hardware does not support two concurrent readers on the same port.
|
||||
|
||||
this.state.recording = true;
|
||||
this.state.sessionId = sessionId;
|
||||
|
|
|
|||
|
|
@ -96,17 +96,13 @@ router.get('/devices', (req, res) => {
|
|||
}
|
||||
|
||||
// Parse ffmpeg output for DeckLink device names.
|
||||
// ffmpeg -sources decklink output:
|
||||
// Auto-detected sources for decklink:
|
||||
// DeckLink Duo 2
|
||||
// DeckLink Duo 2 (2)
|
||||
// Device name lines are indented (2+ leading spaces); header/blank lines are not.
|
||||
// DeckLink source lines: " 81:76669a80:00000000 [DeckLink Duo (1)] (none)"
|
||||
const lines = output.split('\n');
|
||||
let deviceIndex = 0;
|
||||
|
||||
for (const line of lines) {
|
||||
const match = line.match(/^ {2,}(.+?)\s*$/);
|
||||
if (match && match[1]) {
|
||||
const match = line.match(/^\s+[0-9a-f:]+\s+\[([^\]]+)\]/);
|
||||
if (match) {
|
||||
devices.push({
|
||||
index: deviceIndex,
|
||||
name: match[1],
|
||||
|
|
@ -144,8 +140,8 @@ router.post('/probe', async (req, res) => {
|
|||
const raw = execSync('ffmpeg -hide_banner -sources decklink 2>&1', { encoding: 'utf-8', timeout: 5000 });
|
||||
const devices = [];
|
||||
for (const line of raw.split('\n')) {
|
||||
const m = line.match(/^ {2,}(.+?)\s*$/);
|
||||
if (m && m[1]) devices.push(m[1]);
|
||||
const m = line.match(/^\s+[0-9a-f:]+\s+\[([^\]]+)\]/);
|
||||
if (m) devices.push(m[1]);
|
||||
}
|
||||
return res.json({ ok: true, source_type, devices });
|
||||
} catch (err) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue