合并音频Blob数组时仅保留第一个Blob内容的问题求助
问题原因
你遇到的核心问题是:每个ondataavailable回调返回的Blob都是完整的WAV文件(包含独立的文件头),直接用new Blob()合并时,多个WAV头会堆叠在一起。音频播放器只会解析第一个文件头,读取对应长度的音频数据,后面的Blob内容会被忽略,所以只能听到第一个块的声音。
解决方案
方案1:改用原生MediaRecorder API(推荐)
原生MediaRecorder会自动处理分片媒体数据的拼接,生成单个完整的媒体文件,无需手动合并时处理文件头问题。替换你的代码如下:
// 初始化原生MediaRecorder,指定WAV格式 const mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/wav' }); const recordedBlobs = []; mediaRecorder.ondataavailable = (event) => { if (event.data.size > 0) { recordedBlobs.push(event.data); // 原生MediaRecorder返回的是媒体片段,临时转为Blob用于预览 const tempBlob = new Blob([event.data], { type: 'audio/wav' }); wavesurfer.loadBlob(tempBlob); } }; // 启动录制 mediaRecorder.start(); // 停止录制时合并生成完整Blob mediaRecorder.onstop = () => { const recordedBlob = new Blob(recordedBlobs, { type: 'audio/wav' }); // 此时recordedBlob是完整可播放的WAV文件 };
方案2:手动处理WAV文件头(仅当必须使用MediaStreamRecorder时)
如果一定要用MediaStreamRecorder,需要剥离每个WAV块的文件头,只保留原始PCM音频数据,合并后再重新生成统一的WAV文件头。
代码实现
// 合并多个WAV Blob的函数 async function mergeWavBlobs(wavBlobs) { const pcmChunks = []; let totalPcmSize = 0; // 遍历所有Blob,剥离每个WAV的文件头(前44字节) for (const blob of wavBlobs) { const arrayBuffer = await blob.arrayBuffer(); // WAV文件头通常为44字节,取后面的PCM数据 const pcmData = arrayBuffer.slice(44); pcmChunks.push(pcmData); totalPcmSize += pcmData.byteLength; } // 生成新的WAV文件头(参数需和录制时一致) const wavHeader = createWavHeader( totalPcmSize, 1, // 声道数,和你的audioChannels一致 44100, // 采样率,根据MediaStreamRecorder的实际设置调整 16 // 位深度,通常为16位 ); // 合并文件头和所有PCM数据 const mergedBuffer = new ArrayBuffer(wavHeader.byteLength + totalPcmSize); const view = new Uint8Array(mergedBuffer); // 写入文件头 view.set(wavHeader, 0); // 写入PCM数据 let offset = wavHeader.byteLength; for (const pcm of pcmChunks) { view.set(new Uint8Array(pcm), offset); offset += pcm.byteLength; } return new Blob([mergedBuffer], { type: 'audio/wav' }); } // 生成标准WAV文件头的工具函数 function createWavHeader(pcmLength, channels, sampleRate, bitDepth) { const buffer = new ArrayBuffer(44); const dataView = new DataView(buffer); // RIFF chunk dataView.setUint8(0, 0x52); // R dataView.setUint8(1, 0x49); // I dataView.setUint8(2, 0x46); // F dataView.setUint8(3, 0x46); // F dataView.setUint32(4, 36 + pcmLength, true); // 文件总长度 // WAVE chunk dataView.setUint8(8, 0x57); // W dataView.setUint8(9, 0x41); // A dataView.setUint8(10, 0x56); // V dataView.setUint8(11, 0x45); // E // fmt subchunk dataView.setUint8(12, 0x66); // f dataView.setUint8(13, 0x6D); // m dataView.setUint8(14, 0x74); // t dataView.setUint8(15, 0x20); // 空格 dataView.setUint32(16, 16, true); // subchunk长度(PCM固定为16) dataView.setUint16(20, 1, true); // 音频格式(PCM为1) dataView.setUint16(22, channels, true); // 声道数 dataView.setUint32(24, sampleRate, true); // 采样率 dataView.setUint32(28, sampleRate * channels * (bitDepth / 8), true); // 字节率 dataView.setUint16(32, channels * (bitDepth / 8), true); // 块对齐 dataView.setUint16(34, bitDepth, true); // 位深度 // data subchunk dataView.setUint8(36, 0x64); // d dataView.setUint8(37, 0x61); // a dataView.setUint8(38, 0x74); // t dataView.setUint8(39, 0x61); // a dataView.setUint32(40, pcmLength, true); // PCM数据长度 return new Uint8Array(buffer); } // 停止录制后调用合并函数 mediaRecorder.onstop = async () => { const recordedBlob = await mergeWavBlobs(recordedBlobs); // 此时recordedBlob是完整可播放的WAV文件 };
注意事项
- 确保
createWavHeader中的参数(声道数、采样率、位深度)和你MediaStreamRecorder的录制设置完全一致,否则合并后的音频会出现播放速度异常、无声音等问题。 - MediaStreamRecorder的默认采样率可能因浏览器或设备不同而变化,建议手动指定:
mediaRecorder.sampleRate = 44100;
内容的提问来源于stack exchange,提问作者INTODAN
相关产品推荐
相关产品推荐

