如何在Discord.js v14语音流中插入静音片段?
解决Discord.js v14语音录制静音插入与音频合并问题
一、正确实现静音插入(用Transform流替代Readable)
你之前的Readable流写法问题在于每次read()都会重复绑定data事件,导致同一音频块被多次推送,进而拉伸音频时长。改用Transform流可以更精准地控制数据流转,在无音频输入时定时插入静音帧:
const { Transform } = require('stream'); const SILENCE = Buffer.from([0xf8, 0xff, 0xfe]); // 20ms Opus静音帧 const SILENCE_INTERVAL = 20; // 与Discord音频帧时长匹配(ms) function createSilenceInjectStream() { let silenceTimer; return new Transform({ transform(chunk, encoding, callback) { // 收到音频块时清除静音定时器 clearTimeout(silenceTimer); // 推送当前音频块 this.push(chunk); // 重置定时器:20ms内无新数据则推送静音,且持续循环直到有新输入 silenceTimer = setTimeout(() => { this.push(SILENCE); silenceTimer = setTimeout(() => this.push(SILENCE), SILENCE_INTERVAL); }, SILENCE_INTERVAL); callback(); }, flush(callback) { // 流结束时清理定时器 clearTimeout(silenceTimer); callback(); } }); } async function createListeningStream(connection, userId) { const receiverStream = connection.receiver.subscribe(userId, { end: { behavior: EndBehaviorType.Manual } }); // 将接收流接入静音插入流 const userStream = receiverStream.pipe(createSilenceInjectStream()); // 后续写入文件逻辑保持不变(注意输出为Opus格式,可后续转码) }
二、判断用户是否发声,避免空文件
可以通过两种方式实现:
- 监听Speaking事件:
在录制开始时监听语音连接的speaking事件,记录用户是否有过发声行为:
let userSpoke = false; connection.on('speaking', (user, speaking) => { if (user.id === userId && speaking.bitfield !== 0) { userSpoke = true; } }); // 录制结束后判断 if (!userSpoke) { // 删除对应空文件 fs.unlinkSync(userRecordPath); }
- 检测流中音频数据:
在Transform流中判断音频块是否为静音帧,记录是否收到过有效音频:
function createSilenceInjectStream() { let silenceTimer; let hasValidAudio = false; return new Transform({ transform(chunk, encoding, callback) { clearTimeout(silenceTimer); // 判断当前块是否为非静音帧 if (!chunk.equals(SILENCE)) { hasValidAudio = true; } this.push(chunk); silenceTimer = setTimeout(() => { this.push(SILENCE); silenceTimer = setTimeout(() => this.push(SILENCE), SILENCE_INTERVAL); }, SILENCE_INTERVAL); callback(); }, flush(callback) { clearTimeout(silenceTimer); this.hasValidAudio = hasValidAudio; callback(); } }); } // 使用时 const silenceInjectStream = createSilenceInjectStream(); const userStream = receiverStream.pipe(silenceInjectStream); // 录制结束后判断 if (!silenceInjectStream.hasValidAudio) { fs.unlinkSync(userRecordPath); }
三、音频合集(多用户音频叠加)
直接用Node流叠加音频容易出现同步问题,推荐用ffmpeg处理:
- 先将每个用户的Opus文件转成48kHz单声道WAV(与Discord音频参数一致);
- 用
amix滤镜合并多个WAV文件:
const ffmpeg = require('fluent-ffmpeg'); function mergeAudioFiles(userAudioPaths, outputPath) { const command = ffmpeg(); userAudioPaths.forEach(path => command.input(path)); command .complexFilter([`amix=inputs=${userAudioPaths.length}:duration=longest`]) .output(outputPath) .on('end', () => console.log('音频合并完成')) .run(); }
内容的提问来源于stack exchange,提问作者Anon
相关产品推荐
相关产品推荐

