使用MediaRecorder录制音频转AudioBuffer时后续块解码失败问题
解决MediaRecorder后续音频块无法解码为AudioBuffer的问题
问题根源
你遇到的错误是因为MediaRecorder.ondataavailable返回的后续数据块是不包含文件头的WebM裸音频帧,而decodeAudioData仅能解码带有完整文件头的音频文件——只有第一个数据块包含完整的WebM文件头,所以能正常解码,后续片段则会失败。另外每次创建新的AudioContext属于冗余操作,应该复用单一实例。
方案1:实时获取PCM数据(推荐,适合实时发送场景)
直接通过AudioContext从MediaStream抓取原始PCM数据,跳过MediaRecorder的编码/解码环节,从根源避免片段解码问题:
let audioContext = null; let scriptProcessor = null; const record = (stream) => { audioContext = new AudioContext({ sampleRate: 16000 }); const source = audioContext.createMediaStreamSource(stream); // 创建ScriptProcessorNode,每次处理4096帧数据(可根据需求调整) scriptProcessor = audioContext.createScriptProcessor(4096, 1, 1); source.connect(scriptProcessor); scriptProcessor.connect(audioContext.destination); scriptProcessor.onaudioprocess = (e) => { // 获取实时生成的AudioBuffer(单声道,16000采样率) const audioBuffer = e.inputBuffer; console.log(audioBuffer); // 示例:将AudioBuffer转换为16位整数格式的ArrayBuffer,用于发送到服务器 const pcmFloatData = audioBuffer.getChannelData(0); const int16Array = new Int16Array(pcmFloatData.length); for (let i = 0; i < pcmFloatData.length; i++) { const sample = Math.max(-1, Math.min(1, pcmFloatData[i])); int16Array[i] = sample < 0 ? sample * 0x8000 : sample * 0x7FFF; } const arrayBuffer = int16Array.buffer; // 此处添加发送arrayBuffer到服务器的逻辑 }; } // 使用标准的现代API替代过时的getUserMedia navigator.mediaDevices.getUserMedia({ audio: { channelCount: 1, sampleRate: 16000, echoCancellation: true // 根据需求开启/关闭 } }).then(record).catch((e) => { console.error(e); });
方案2:收集所有片段后一次性解码(适合录制完整音频后发送)
如果不需要实时处理,而是录制完成后再发送完整音频,可以收集所有ondataavailable返回的Blob,拼接成完整的WebM文件后再解码:
let recorder = null; let audioBlobs = []; const audioContext = new AudioContext(); const record = (stream) => { recorder = new MediaRecorder(stream, { mimeType: "audio/webm;codecs=opus" }); recorder.ondataavailable = (e) => { if (e.data.size > 0) { audioBlobs.push(e.data); } }; recorder.onstop = () => { // 拼接所有Blob为完整的WebM音频文件 const fullAudioBlob = new Blob(audioBlobs, { type: "audio/webm;codecs=opus" }); const reader = new FileReader(); reader.onloadend = () => { audioContext.decodeAudioData(reader.result, (audioBuffer) => { console.log(audioBuffer); // 此处添加发送完整AudioBuffer到服务器的逻辑 }).catch((err) => { console.error("解码失败:", err); }); }; reader.readAsArrayBuffer(fullAudioBlob); audioBlobs = []; // 重置数据数组 }; recorder.start(500); // 示例:5秒后停止录制,实际场景可根据业务逻辑触发stop setTimeout(() => recorder.stop(), 5000); } navigator.mediaDevices.getUserMedia({ audio: { channelCount: 1, sampleRate: 16000, } }).then(record).catch((e) => { console.error(e); });
补充说明
- 方案1直接获取原始PCM数据,无编码解码损耗,更适合实时流式传输场景。
- 方案2仅适用于需要完整音频文件的场景,必须等待录制结束才能完成解码。
- 注意:
navigator.getUserMedia是过时API,已被标准的navigator.mediaDevices.getUserMedia替代。
内容的提问来源于stack exchange,提问作者Farman Ahmed
相关产品推荐
相关产品推荐

