JavaScript AudioBuffer转换成WAV格式

时间:2024-03-11 14:46:18

1. 使用npm已有包

安装包:npm install audiobuffer-to-wav --save
用例:

var toWav = require(\'audiobuffer-to-wav\')
var xhr = require(\'xhr\')
var context = new AudioContext()

// request the MP3 as binary arraybuffer
xhr({
  uri: \'audio/track.mp3\',
  responseType: \'arraybuffer\'
}, function (err, body, resp) {
  if (err) throw err
  // decode the MP3 arraybuffer into an AudioBuffer
  audioContext.decodeAudioData(resp, function (buffer) {
    // encode AudioBuffer to WAV ArrayBuffer
    var wav = toWav(buffer)//实际上是在audio前加wav的头
    
    // do something with the WAV ArrayBuffer ...
  })
})

引用:
audiobuffer-to-wav

2. 自己实现

(实际上是项目要求需要自己实现,把上面的又自己改写了一下……)

export function audioBufferToWav(buffer: AudioBuffer, opt?: any) {
  opt = opt || {};
  const numChannels = buffer.numberOfChannels;
  const sampleRate = opt.sampleRate || buffer.sampleRate;
  const format = opt.float32 ? 3 : 1;
  const bitDepth = format === 3 ? 32 : 16;
  let result;
  if (numChannels === 2) {
    result = interleave(buffer.getChannelData(0), buffer.getChannelData(1));
  } else {
    result = buffer.getChannelData(0);
  }

  return encodeWAV(result, format, sampleRate, numChannels, bitDepth);
}

function encodeWAV(samples: Float32Array, format: number, sampleRate: number, numChannels: number, bitDepth: number) {
  const bytesPerSample = bitDepth / 8;
  const blockAlign = numChannels * bytesPerSample;

  let buffer = new ArrayBuffer(44 + samples.length * bytesPerSample);
  let view = new DataView(buffer);

  writeString(view, 0, "RIFF");
  view.setUint32(4, 36 + samples.length * bytesPerSample, true);
  writeString(view, 8, "WAVE");
  writeString(view, 12, "fmt ");
  view.setUint32(16, 16, true);
  view.setUint16(20, format, true);
  view.setUint16(22, numChannels, true);
  view.setUint32(24, sampleRate, true);
  view.setUint32(28, sampleRate * blockAlign, true);
  view.setUint16(32, blockAlign, true);
  view.setUint16(34, bitDepth, true);
  writeString(view, 36, "data");
  view.setUint32(40, samples.length * bytesPerSample, true);
  if (format === 1) {
    floatTo16BitPCM(view, 44, samples);
  } else {
    writeFloat32(view, 44, samples);
  }

  return buffer;
}

function interleave(inputL: Float32Array, inputR: Float32Array) {
  let length = inputL.length + inputR.length;
  let result = new Float32Array(length);

  let index = 0;
  let inputIndex = 0;

  while (index < length) {
    result[index++] = inputL[inputIndex];
    result[index++] = inputR[inputIndex];
    inputIndex++;
  }
  return result;
}

function writeFloat32(output: DataView, offset: number, input: Float32Array) {
  for (let i = 0; i < input.length; i++, offset += 4) {
    output.setFloat32(offset, input[i], true);
  }
}

function floatTo16BitPCM(output: DataView, offset: number, input: Float32Array) {
  for (let i = 0; i < input.length; i++, offset += 2) {
    let s = Math.max(-1, Math.min(1, input[i]));
    output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
  }
}

function writeString(view: DataView, offset: number, string:string) {
  for (let i = 0; i < string.length; i++) {
    view.setUint8(offset + i, string.charCodeAt(i));
  }
}

3. Arraybuffer加头

const _writeString = (view: DataView, offset: number = 0, value: string = "") => {
  _.forEach(value.split(""), char => view.setUint8(offset++, char.charCodeAt(0)));
};

export const AddRiffHeader = (
  data: ArrayBuffer,
  sampleRate: number = 16000,
  numberOfChannels: number = 1,
  bitsPerSample = 16
) => {
  const headerSize = 44;
  const dataID = "data";
  const dataSize = data.byteLength;
  const fmtChunkID = "fmt ";
  const fmtPcmChunkSize = 16;
  const audioPcmFormat = 1;
  const blockAlign = bitsPerSample / 8;
  const riffChunkID = "RIFF";
  const riffChunkSize = 4 + (8 + fmtPcmChunkSize) + (8 + dataSize);
  const riffChunkFormat = "WAVE";

  const header = new ArrayBuffer(headerSize);
  const headerView = new DataView(header);

  _writeString(headerView, 0, riffChunkID);
  headerView.setUint32(4, riffChunkSize, true);
  _writeString(headerView, 8, riffChunkFormat);
  _writeString(headerView, 12, fmtChunkID);
  headerView.setUint32(16, fmtPcmChunkSize, true);

  headerView.setUint16(20, audioPcmFormat, true);
  headerView.setUint16(22, numberOfChannels, true);
  headerView.setUint32(24, sampleRate, true);
  headerView.setUint32(28, sampleRate * blockAlign, true);
  headerView.setUint16(32, blockAlign, true);
  headerView.setUint16(34, bitsPerSample, true);
  _writeString(headerView, 36, dataID);
  headerView.setUint32(40, dataSize, true);

  const result = new Uint8Array(header.byteLength + data.byteLength);
  result.set(new Uint8Array(header), 0);
  result.set(new Uint8Array(data), header.byteLength);
  return result.buffer;
};