You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 03:12:18