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

如何将多个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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.15 15:54:05