HTML5网络音频 - 将音频缓冲区转换为wav文件

pra*_*ech 13 html5 web-audio-api

我有一个使用的音频缓冲区webkitOfflineAudioContext.现在,我希望将其导出为WAV文件.我该怎么做?我尝试使用recorder.js但无法弄清楚如何使用它.这是我的代码:http://jsfiddle.net/GBQV8/.

Kev*_*nis 11

这是一个应该有所帮助的要点:https://gist.github.com/kevincennis/9754325.

我实际上没有测试过这个,所以可能会有一个愚蠢的错字或其他东西,但基本的方法会起作用(我以前做过).

从本质上讲,您将直接使用Recorder.js中的Web worker,以便您可以一次性处理一个大的AudioBuffer,而不是实时增量地记录它.

我也会在这里粘贴代码,以防万一...

// assuming a var named `buffer` exists and is an AudioBuffer instance


// start a new worker 
// we can't use Recorder directly, since it doesn't support what we're trying to do
var worker = new Worker('recorderWorker.js');

// initialize the new worker
worker.postMessage({
  command: 'init',
  config: {sampleRate: 44100}
});

// callback for `exportWAV`
worker.onmessage = function( e ) {
  var blob = e.data;
  // this is would be your WAV blob
};

// send the channel data from our buffer to the worker
worker.postMessage({
  command: 'record',
  buffer: [
    buffer.getChannelData(0), 
    buffer.getChannelData(1)
  ]
});

// ask the worker for a WAV
worker.postMessage({
  command: 'exportWAV',
  type: 'audio/wav'
});
Run Code Online (Sandbox Code Playgroud)


小智 7

我想我会分享一个可行的解决方案,我设法从凯文的答案中整理出来.

这是waveWorker.js脚本:

self.onmessage = function( e ){
  var wavPCM = new WavePCM( e['data']['config'] );
  wavPCM.record( e['data']['pcmArrays'] );
  wavPCM.requestData();
};

var WavePCM = function( config ){
  this.sampleRate = config['sampleRate'] || 48000;
  this.bitDepth = config['bitDepth'] || 16;
  this.recordedBuffers = [];
  this.bytesPerSample = this.bitDepth / 8;
};

WavePCM.prototype.record = function( buffers ){
  this.numberOfChannels = this.numberOfChannels || buffers.length;
  var bufferLength = buffers[0].length;
  var reducedData = new Uint8Array( bufferLength * this.numberOfChannels * this.bytesPerSample );

  // Interleave
  for ( var i = 0; i < bufferLength; i++ ) {
    for ( var channel = 0; channel < this.numberOfChannels; channel++ ) {

      var outputIndex = ( i * this.numberOfChannels + channel ) * this.bytesPerSample;
      var sample = buffers[ channel ][ i ];

      // Check for clipping
      if ( sample > 1 ) {
        sample = 1;
      }

      else if ( sample < -1 ) {
        sample = -1;
      }

      // bit reduce and convert to uInt
      switch ( this.bytesPerSample ) {
        case 4:
          sample = sample * 2147483648;
          reducedData[ outputIndex ] = sample;
          reducedData[ outputIndex + 1 ] = sample >> 8;
          reducedData[ outputIndex + 2 ] = sample >> 16;
          reducedData[ outputIndex + 3 ] = sample >> 24;
          break;

        case 3:
          sample = sample * 8388608;
          reducedData[ outputIndex ] = sample;
          reducedData[ outputIndex + 1 ] = sample >> 8;
          reducedData[ outputIndex + 2 ] = sample >> 16;
          break;

        case 2:
          sample = sample * 32768;
          reducedData[ outputIndex ] = sample;
          reducedData[ outputIndex + 1 ] = sample >> 8;
          break;

        case 1:
          reducedData[ outputIndex ] = ( sample + 1 ) * 128;
          break;

        default:
          throw "Only 8, 16, 24 and 32 bits per sample are supported";
      }
    }
  }

  this.recordedBuffers.push( reducedData );
};

WavePCM.prototype.requestData = function(){
  var bufferLength = this.recordedBuffers[0].length;
  var dataLength = this.recordedBuffers.length * bufferLength;
  var headerLength = 44;
  var wav = new Uint8Array( headerLength + dataLength );
  var view = new DataView( wav.buffer );

  view.setUint32( 0, 1380533830, false ); // RIFF identifier 'RIFF'
  view.setUint32( 4, 36 + dataLength, true ); // file length minus RIFF identifier length and file description length
  view.setUint32( 8, 1463899717, false ); // RIFF type 'WAVE'
  view.setUint32( 12, 1718449184, false ); // format chunk identifier 'fmt '
  view.setUint32( 16, 16, true ); // format chunk length
  view.setUint16( 20, 1, true ); // sample format (raw)
  view.setUint16( 22, this.numberOfChannels, true ); // channel count
  view.setUint32( 24, this.sampleRate, true ); // sample rate
  view.setUint32( 28, this.sampleRate * this.bytesPerSample * this.numberOfChannels, true ); // byte rate (sample rate * block align)
  view.setUint16( 32, this.bytesPerSample * this.numberOfChannels, true ); // block align (channel count * bytes per sample)
  view.setUint16( 34, this.bitDepth, true ); // bits per sample
  view.setUint32( 36, 1684108385, false); // data chunk identifier 'data'
  view.setUint32( 40, dataLength, true ); // data chunk length

  for (var i = 0; i < this.recordedBuffers.length; i++ ) {
    wav.set( this.recordedBuffers[i], i * bufferLength + headerLength );
  }

  self.postMessage( wav, [wav.buffer] );
  self.close();
};
Run Code Online (Sandbox Code Playgroud)

以下是如何使用它:

async function audioBufferToWaveBlob(audioBuffer) {

  return new Promise(function(resolve, reject) {

    var worker = new Worker('./waveWorker.js');

    worker.onmessage = function( e ) {
      var blob = new Blob([e.data.buffer], {type:"audio/wav"});
      resolve(blob);
    };

    let pcmArrays = [];
    for(let i = 0; i < audioBuffer.numberOfChannels; i++) {
      pcmArrays.push(audioBuffer.getChannelData(i));
    }

    worker.postMessage({
      pcmArrays,
      config: {sampleRate: audioBuffer.sampleRate}
    });

  });

}
Run Code Online (Sandbox Code Playgroud)

它很快被黑客攻击所以当然可以免费(当然)修复它并在评论中发布一个更好的版本的链接:)