Fix: use filter_complex for sequence concatenation to fix sample rate mismatch causing speed up and noise
This commit is contained in:
+49
-34
@@ -1235,7 +1235,7 @@ function getOrCreateHlsStreamState(streamName) {
|
|||||||
return hlsStreams[streamName];
|
return hlsStreams[streamName];
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildConcatListFile(seq) {
|
function resolveSequenceFiles(seq) {
|
||||||
ensureSilenceWav();
|
ensureSilenceWav();
|
||||||
const files = [];
|
const files = [];
|
||||||
|
|
||||||
@@ -1264,24 +1264,23 @@ function buildConcatListFile(seq) {
|
|||||||
for (let i = 0; i < HLS_TAIL_SILENCE_SEC; i++) files.push(silence1sWavPath);
|
for (let i = 0; i < HLS_TAIL_SILENCE_SEC; i++) files.push(silence1sWavPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (files.length === 0) return null;
|
return files;
|
||||||
|
|
||||||
const listPath = path.join(tmpDir, `concat-${Date.now()}-${Math.random().toString(16).slice(2)}.txt`);
|
|
||||||
const content = files
|
|
||||||
.map(p => `file '${p.replace(/\\/g, '/').replace(/'/g, "\\'")}'`)
|
|
||||||
.join('\n');
|
|
||||||
fs.writeFileSync(listPath, content, 'utf8');
|
|
||||||
return listPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSequenceWavFile(seq) {
|
function buildSequenceWavFile(seq) {
|
||||||
const listPath = buildConcatListFile(seq);
|
const files = resolveSequenceFiles(seq);
|
||||||
if (!listPath) return null;
|
if (files.length === 0) return null;
|
||||||
|
|
||||||
const fileName = `sequence-${Date.now()}-${Math.random().toString(16).slice(2)}.wav`;
|
const fileName = `sequence-${Date.now()}-${Math.random().toString(16).slice(2)}.wav`;
|
||||||
const outWavPath = path.join(tempBroadcastDir, fileName);
|
const outWavPath = path.join(tempBroadcastDir, fileName);
|
||||||
|
|
||||||
|
const inputs = files.map(f => `-i "${f}"`).join(' ');
|
||||||
|
const filterResamples = files.map((_, i) => `[${i}:a]aformat=sample_rates=48000:channel_layouts=mono,aresample=48000[a${i}]`).join(';');
|
||||||
|
const filterConcat = files.map((_, i) => `[a${i}]`).join('') + `concat=n=${files.length}:v=0:a=1[a]`;
|
||||||
|
const filterComplex = `${filterResamples};${filterConcat}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
execSync(`ffmpeg -hide_banner -loglevel error -y -f concat -safe 0 -i "${listPath}" -ac 1 -ar 48000 -c:a pcm_s16le "${outWavPath}"`);
|
execSync(`ffmpeg -hide_banner -loglevel error -y ${inputs} -filter_complex "${filterComplex}" -map "[a]" -ac 1 -ar 48000 -c:a pcm_s16le "${outWavPath}"`);
|
||||||
const buffer = fs.readFileSync(outWavPath);
|
const buffer = fs.readFileSync(outWavPath);
|
||||||
const durationSec = getWavDurationSec(buffer) || 0;
|
const durationSec = getWavDurationSec(buffer) || 0;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -1293,15 +1292,16 @@ function buildSequenceWavFile(seq) {
|
|||||||
streamUrl: getManagedMediaUrl('temp', fileName),
|
streamUrl: getManagedMediaUrl('temp', fileName),
|
||||||
durationSec
|
durationSec
|
||||||
};
|
};
|
||||||
} finally {
|
} catch (e) {
|
||||||
try { fs.unlinkSync(listPath); } catch {}
|
log('ERROR', `buildSequenceWavFile failed: ${e.message}`);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function runFfmpegRtmpPush(streamName, seq) {
|
function runFfmpegRtmpPush(streamName, seq) {
|
||||||
const state = getOrCreateHlsStreamState(streamName);
|
const state = getOrCreateHlsStreamState(streamName);
|
||||||
const listPath = buildConcatListFile(seq);
|
const files = resolveSequenceFiles(seq);
|
||||||
if (!listPath) {
|
if (files.length === 0) {
|
||||||
state.status = 'idle';
|
state.status = 'idle';
|
||||||
state.lastError = 'No playable wav files in sequence';
|
state.lastError = 'No playable wav files in sequence';
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@@ -1315,17 +1315,25 @@ function runFfmpegRtmpPush(streamName, seq) {
|
|||||||
|
|
||||||
if (HLS_PREBUILD) {
|
if (HLS_PREBUILD) {
|
||||||
const tmpAacPath = path.join(tmpDir, `prebuild-${Date.now()}-${Math.random().toString(16).slice(2)}.aac`);
|
const tmpAacPath = path.join(tmpDir, `prebuild-${Date.now()}-${Math.random().toString(16).slice(2)}.aac`);
|
||||||
|
const inputs = files.map(f => `-i "${f}"`).join(' ');
|
||||||
|
const filterResamples = files.map((_, i) => `[${i}:a]aformat=sample_rates=48000:channel_layouts=mono,aresample=48000[a${i}]`).join(';');
|
||||||
|
const filterConcat = files.map((_, i) => `[a${i}]`).join('') + `concat=n=${files.length}:v=0:a=1[a]`;
|
||||||
|
const filterComplex = `${filterResamples};${filterConcat}`;
|
||||||
|
|
||||||
const buildArgs = [
|
const buildArgs = [
|
||||||
'-hide_banner',
|
'-hide_banner',
|
||||||
'-loglevel',
|
'-loglevel',
|
||||||
'error',
|
'error',
|
||||||
'-y',
|
'-y'
|
||||||
'-f',
|
];
|
||||||
'concat',
|
|
||||||
'-safe',
|
files.forEach(f => { buildArgs.push('-i', f); });
|
||||||
'0',
|
|
||||||
'-i',
|
buildArgs.push(
|
||||||
listPath,
|
'-filter_complex',
|
||||||
|
filterComplex,
|
||||||
|
'-map',
|
||||||
|
'[a]',
|
||||||
'-ac',
|
'-ac',
|
||||||
'1',
|
'1',
|
||||||
'-ar',
|
'-ar',
|
||||||
@@ -1337,7 +1345,7 @@ function runFfmpegRtmpPush(streamName, seq) {
|
|||||||
'-f',
|
'-f',
|
||||||
'adts',
|
'adts',
|
||||||
tmpAacPath
|
tmpAacPath
|
||||||
];
|
);
|
||||||
|
|
||||||
state.status = 'building';
|
state.status = 'building';
|
||||||
state.lastStartAt = Date.now();
|
state.lastStartAt = Date.now();
|
||||||
@@ -1396,7 +1404,6 @@ function runFfmpegRtmpPush(streamName, seq) {
|
|||||||
state.lastError = (stderr || `ffmpeg exited with code ${code}`).trim();
|
state.lastError = (stderr || `ffmpeg exited with code ${code}`).trim();
|
||||||
log('ERROR', `HLS push failed for ${streamName}: ${state.lastError}`);
|
log('ERROR', `HLS push failed for ${streamName}: ${state.lastError}`);
|
||||||
}
|
}
|
||||||
try { fs.unlinkSync(listPath); } catch {}
|
|
||||||
try { fs.unlinkSync(tmpAacPath); } catch {}
|
try { fs.unlinkSync(tmpAacPath); } catch {}
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
@@ -1404,17 +1411,24 @@ function runFfmpegRtmpPush(streamName, seq) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filterResamples = files.map((_, i) => `[${i}:a]aformat=sample_rates=48000:channel_layouts=mono,aresample=48000[a${i}]`).join(';');
|
||||||
|
const filterConcat = files.map((_, i) => `[a${i}]`).join('') + `concat=n=${files.length}:v=0:a=1[a]`;
|
||||||
|
const filterComplex = `${filterResamples};${filterConcat}`;
|
||||||
|
|
||||||
const args = [
|
const args = [
|
||||||
'-hide_banner',
|
'-hide_banner',
|
||||||
'-loglevel',
|
'-loglevel',
|
||||||
'error',
|
'error',
|
||||||
'-re',
|
'-re'
|
||||||
'-f',
|
];
|
||||||
'concat',
|
|
||||||
'-safe',
|
files.forEach(f => { args.push('-i', f); });
|
||||||
'0',
|
|
||||||
'-i',
|
args.push(
|
||||||
listPath,
|
'-filter_complex',
|
||||||
|
filterComplex,
|
||||||
|
'-map',
|
||||||
|
'[a]',
|
||||||
'-ac',
|
'-ac',
|
||||||
'1',
|
'1',
|
||||||
'-ar',
|
'-ar',
|
||||||
@@ -1426,7 +1440,7 @@ function runFfmpegRtmpPush(streamName, seq) {
|
|||||||
'-f',
|
'-f',
|
||||||
'flv',
|
'flv',
|
||||||
rtmpUrl
|
rtmpUrl
|
||||||
];
|
);
|
||||||
|
|
||||||
state.status = 'pushing';
|
state.status = 'pushing';
|
||||||
state.lastStartAt = Date.now();
|
state.lastStartAt = Date.now();
|
||||||
@@ -2800,7 +2814,8 @@ app.post('/api/temp-broadcast/publish', (req, res) => {
|
|||||||
const memberName = onlineMembers[idx];
|
const memberName = onlineMembers[idx];
|
||||||
const ws = getStationWs(memberName);
|
const ws = getStationWs(memberName);
|
||||||
enqueueStationAudioTask(memberName, ws, {
|
enqueueStationAudioTask(memberName, ws, {
|
||||||
audioFormat: 'stream',
|
audioFormat: 'wav-stream-live',
|
||||||
|
sourcePath: wavPath,
|
||||||
streamUrl,
|
streamUrl,
|
||||||
durationSec,
|
durationSec,
|
||||||
priority,
|
priority,
|
||||||
|
|||||||
Reference in New Issue
Block a user