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];
|
||||
}
|
||||
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user