Fix: use filter_complex for sequence concatenation to fix sample rate mismatch causing speed up and noise

This commit is contained in:
2026-08-01 22:46:23 +08:00
parent e009f05a61
commit 2fae7305f6
+49 -34
View File
@@ -1235,7 +1235,7 @@ function getOrCreateHlsStreamState(streamName) {
return hlsStreams[streamName];
}
function buildConcatListFile(seq) {
function resolveSequenceFiles(seq) {
ensureSilenceWav();
const files = [];
@@ -1264,24 +1264,23 @@ function buildConcatListFile(seq) {
for (let i = 0; i < HLS_TAIL_SILENCE_SEC; i++) files.push(silence1sWavPath);
}
if (files.length === 0) return null;
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;
return files;
}
function buildSequenceWavFile(seq) {
const listPath = buildConcatListFile(seq);
if (!listPath) return null;
const files = resolveSequenceFiles(seq);
if (files.length === 0) return null;
const fileName = `sequence-${Date.now()}-${Math.random().toString(16).slice(2)}.wav`;
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 {
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 durationSec = getWavDurationSec(buffer) || 0;
setTimeout(() => {
@@ -1293,15 +1292,16 @@ function buildSequenceWavFile(seq) {
streamUrl: getManagedMediaUrl('temp', fileName),
durationSec
};
} finally {
try { fs.unlinkSync(listPath); } catch {}
} catch (e) {
log('ERROR', `buildSequenceWavFile failed: ${e.message}`);
return null;
}
}
function runFfmpegRtmpPush(streamName, seq) {
const state = getOrCreateHlsStreamState(streamName);
const listPath = buildConcatListFile(seq);
if (!listPath) {
const files = resolveSequenceFiles(seq);
if (files.length === 0) {
state.status = 'idle';
state.lastError = 'No playable wav files in sequence';
return Promise.resolve();
@@ -1315,17 +1315,25 @@ function runFfmpegRtmpPush(streamName, seq) {
if (HLS_PREBUILD) {
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 = [
'-hide_banner',
'-loglevel',
'error',
'-y',
'-f',
'concat',
'-safe',
'0',
'-i',
listPath,
'-y'
];
files.forEach(f => { buildArgs.push('-i', f); });
buildArgs.push(
'-filter_complex',
filterComplex,
'-map',
'[a]',
'-ac',
'1',
'-ar',
@@ -1337,7 +1345,7 @@ function runFfmpegRtmpPush(streamName, seq) {
'-f',
'adts',
tmpAacPath
];
);
state.status = 'building';
state.lastStartAt = Date.now();
@@ -1396,7 +1404,6 @@ function runFfmpegRtmpPush(streamName, seq) {
state.lastError = (stderr || `ffmpeg exited with code ${code}`).trim();
log('ERROR', `HLS push failed for ${streamName}: ${state.lastError}`);
}
try { fs.unlinkSync(listPath); } catch {}
try { fs.unlinkSync(tmpAacPath); } catch {}
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 = [
'-hide_banner',
'-loglevel',
'error',
'-re',
'-f',
'concat',
'-safe',
'0',
'-i',
listPath,
'-re'
];
files.forEach(f => { args.push('-i', f); });
args.push(
'-filter_complex',
filterComplex,
'-map',
'[a]',
'-ac',
'1',
'-ar',
@@ -1426,7 +1440,7 @@ function runFfmpegRtmpPush(streamName, seq) {
'-f',
'flv',
rtmpUrl
];
);
state.status = 'pushing';
state.lastStartAt = Date.now();
@@ -2800,7 +2814,8 @@ app.post('/api/temp-broadcast/publish', (req, res) => {
const memberName = onlineMembers[idx];
const ws = getStationWs(memberName);
enqueueStationAudioTask(memberName, ws, {
audioFormat: 'stream',
audioFormat: 'wav-stream-live',
sourcePath: wavPath,
streamUrl,
durationSec,
priority,