如何将多个WAV格式音频Blob合并为单个Blob并下载?
多音频Blob合并解决方案
问题根因
你遇到的问题本质是WAV(或其他音频格式)文件本身包含独立的文件头元数据,直接拼接多个音频Blob的二进制内容,只会保留第一个文件的有效头部,后续文件的头部会被播放器识别为无效音频帧,因此只能播放第一段内容。
可行解决方案:使用Web Audio API解码后合并
该方案先把所有音频Blob解码为原始音频缓冲区,再将缓冲区合并后重新编码为WAV格式,即可得到可正常播放的完整音频,代码示例如下:
// 合并音频Blob的核心方法 async function mergeAudioBlobs(blobArray, outputType = 'audio/wav') { // 1. 创建AudioContext实例 const audioContext = new (window.AudioContext || window.webkitAudioContext)(); // 2. 解码所有Blob为AudioBuffer const bufferArray = await Promise.all( blobArray.map(blob => new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = async (e) => { try { const buffer = await audioContext.decodeAudioData(e.target.result); resolve(buffer); } catch (err) { reject(err); } }; reader.onerror = reject; reader.readAsArrayBuffer(blob); }) ) ); // 3. 计算合并后的总长度,创建新的AudioBuffer const totalLength = bufferArray.reduce((sum, buf) => sum + buf.length, 0); const sampleRate = bufferArray[0].sampleRate; const numberOfChannels = bufferArray[0].numberOfChannels; const mergedBuffer = audioContext.createBuffer(numberOfChannels, totalLength, sampleRate); // 4. 逐个复制音频数据到新缓冲区 let offset = 0; bufferArray.forEach(buf => { for (let channel = 0; channel < numberOfChannels; channel++) { mergedBuffer.copyToChannel(buf.getChannelData(channel), channel, offset); } offset += buf.length; }); // 5. 将AudioBuffer转换为WAV格式的Blob const wavBuffer = audioBufferToWav(mergedBuffer); return new Blob([wavBuffer], { type: outputType }); } // AudioBuffer转WAV的工具方法 function audioBufferToWav(buffer) { const length = buffer.length * buffer.numberOfChannels * 2 + 44; const arrayBuffer = new ArrayBuffer(length); const view = new DataView(arrayBuffer); const channels = []; let offset = 0; let pos = 0; // 写入WAV头的工具函数 const setUint16 = (data) => { view.setUint16(pos, data, true); pos += 2; } const setUint32 = (data) => { view.setUint32(pos, data, true); pos += 4; } // 写入RIFF标识符 setUint32(0x46464952); // 写入文件长度 setUint32(length - 8); // 写入WAVE标识符 setUint32(0x45564157); // 写入fmt子块 setUint32(0x20746d66); setUint32(16); setUint16(1); // 音频格式为PCM setUint16(buffer.numberOfChannels); // 声道数 setUint32(buffer.sampleRate); // 采样率 setUint32(buffer.sampleRate * 2 * buffer.numberOfChannels); // 字节率 setUint16(buffer.numberOfChannels * 2); // 块对齐 setUint16(16); // 位深度为16位 // 写入data子块 setUint32(0x61746164); setUint32(length - pos - 4); // 写入PCM音频数据 for (let i = 0; i < buffer.numberOfChannels; i++) { channels.push(buffer.getChannelData(i)); } while (pos < length) { for (let i = 0; i < buffer.numberOfChannels; i++) { let sample = Math.max(-1, Math.min(1, channels[i][offset])); sample = sample < 0 ? sample * 0x8000 : sample * 0x7FFF; view.setInt16(pos, sample, true); pos += 2; } offset++; } return arrayBuffer; } // 调用示例 mergeAudioBlobs(fullBlobArray, 'audio/wav').then(mergedBlob => { const url = window.URL.createObjectURL(mergedBlob); const a = document.createElement("a"); document.body.appendChild(a); a.style.display = "none"; a.href = url; a.download = "testing.wav"; a.click(); URL.revokeObjectURL(url); a.remove(); window.close(); });
注意事项
- 所有待合并的音频需要保持相同的采样率、声道数、位深度,否则合并前需要先做重采样统一参数
- 源音频如果是mp3、ogg等浏览器兼容的音频格式,解码步骤也可以正常支持
内容的提问来源于stack exchange,提问作者Vikash
相关产品推荐
相关产品推荐

