1. 程式人生 > >使用Web Audio API實現基於瀏覽器的Web端錄音

使用Web Audio API實現基於瀏覽器的Web端錄音

有時候會有通過Web端錄音的需求,那麼如何實現呢,通過Web Audio API能夠實現,具體可以檢視官網的API,下面是實現單聲道錄音(壓縮音訊大小)的一個例子,共需要兩個js檔案,具體如下:

第一個js檔案: recorder.js

(function(window){
 
  var WORKER_PATH = 'resources/js/recorderWorker.js'; //自己設定recorderWorker.js的路徑
 
  var Recorder = function(source, cfg){
    var config = cfg || {};
    var bufferLen = config.bufferLen || 4096;   
    this.context = source.context;
    this.node = this.context.createScriptProcessor(bufferLen, 1, 1);
    var worker = new Worker(config.workerPath || WORKER_PATH);
    worker.postMessage({
      command: 'init',
      config: {
        sampleRate: this.context.sampleRate //預設的是48000
      //sampleRate:11025
      }
    });
    var recording = false,
      currCallback;
 
    this.node.onaudioprocess = function(e){
      if (!recording) return;
      worker.postMessage({
        command: 'record',
        buffer: [
          e.inputBuffer.getChannelData(0)
          //e.inputBuffer.getChannelData(1)
        ]
      });
    }
 
    this.configure = function(cfg){
      for (var prop in cfg){
        if (cfg.hasOwnProperty(prop)){
          config[prop] = cfg[prop];
        }
      }
    }
 
    this.record = function(){
      recording = true;
    }
 
    this.stop = function(){
      recording = false;
    }
 
    this.clear = function(){
      worker.postMessage({ command: 'clear' });
    }
 
    this.getBuffer = function(cb) {
      currCallback = cb || config.callback;
      worker.postMessage({ command: 'getBuffer' })
    }
 
    this.exportWAV = function(cb, type){
      currCallback = cb || config.callback;
      type = type || config.type || 'audio/wav';
      if (!currCallback) throw new Error('Callback not set');
      worker.postMessage({
        command: 'exportWAV',
        type: type
      });
    }
 
    worker.onmessage = function(e){
      var blob = e.data;
      currCallback(blob);
    }
 
    source.connect(this.node);
    this.node.connect(this.context.destination);    //this should not be necessary
  };
 
  Recorder.forceDownload = function(blob, filename){
    var url = (window.URL || window.webkitURL).createObjectURL(blob);
    var link = window.document.createElement('a');
    link.href = url;
    link.download = filename || 'output.wav';
    var click = document.createEvent("Event");
    click.initEvent("click", true, true);
    link.dispatchEvent(click);
  }
 
  window.Recorder = Recorder;
 
})(window);

第二個js檔案: recorderWorker.js  

var recLength = 0,
  recBuffersL = [],
  recBuffersR = [],
  sampleRate;
 
this.onmessage = function(e){
  switch(e.data.command){
    case 'init':
      init(e.data.config);
      break;
    case 'record':
      record(e.data.buffer);
      break;
    case 'exportWAV':
      exportWAV(e.data.type);
      break;
    case 'getBuffer':
      getBuffer();
      break;
    case 'clear':
      clear();
      break;
  }
};
 
function init(config){
  sampleRate = config.sampleRate;
}
 
function record(inputBuffer){
  recBuffersL.push(inputBuffer[0]);
  //recBuffersR.push(inputBuffer[1]);
  recLength += inputBuffer[0].length;
}
 
function exportWAV(type){
  var bufferL = mergeBuffers(recBuffersL, recLength);
  var interleaved = interleave(bufferL);
  var dataview = encodeWAV(interleaved);
  var audioBlob = new Blob([dataview], { type: type });
 
  this.postMessage(audioBlob);
}
 
function getBuffer() {
  var buffers = [];
  buffers.push( mergeBuffers(recBuffersL, recLength) );
  this.postMessage(buffers);
}
 
function clear(){
  recLength = 0;
  recBuffersL = [];
  recBuffersR = [];
}
 
function mergeBuffers(recBuffers, recLength){
  var result = new Float32Array(recLength);
  var offset = 0;
  for (var i = 0; i < recBuffers.length; i++){
    result.set(recBuffers[i], offset);
    offset += recBuffers[i].length;
  }
  return result;
}
 
function interleave(inputL){
//修改取樣率時 , 要做如下修改
var compression = 44100 / 11025;    //計算壓縮率
var length = inputL.length / compression;
var result = new Float32Array(length);
var index = 0,
inputIndex = 0;
while (index < length) {
result[index] = inputL[inputIndex];
inputIndex += compression;//每次都跳過3個數據
index++;
}
return result;
}
 
function floatTo16BitPCM(output, offset, input){
  for (var i = 0; i < input.length; i++, offset+=2){
    var s = Math.max(-1, Math.min(1, input[i]));
    output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
  }
}


function floatTo8BitPCM(output, offset, input) {
for (var i = 0; i < input.length; i++, offset++) {    //這裡只能加1了
var s = Math.max(-1, Math.min(1, input[i]));
var val = s < 0 ? s * 0x8000 : s * 0x7FFF;
val = parseInt(255 / (65535 / (val + 32768)));     //這裡有一個轉換的程式碼,這個是我個人猜測的,就是按比例轉換
output.setInt8(offset, val, true);
}
}


function writeString(view, offset, string){
  for (var i = 0; i < string.length; i++){
    view.setUint8(offset + i, string.charCodeAt(i));
  }
}
 
function encodeWAV(samples){
//修改取樣率  時 , 要新增混合聲道時的處理
var dataLength = samples.length;
var buffer = new ArrayBuffer(44 + dataLength);
var view = new DataView(buffer);
var sampleRateTmp = 11205 ;//sampleRate;//寫入新的取樣率
var sampleBits = 8;
var channelCount = 1;
var offset = 0;
/* 資源交換檔案識別符號 */
writeString(view, offset, 'RIFF'); offset += 4;
/* 下個地址開始到檔案尾總位元組數,即檔案大小-8 */
view.setUint32(offset, /*32*/ 36 + dataLength, true); offset += 4;
/* WAV檔案標誌 */
writeString(view, offset, 'WAVE'); offset += 4;
/* 波形格式標誌 */
writeString(view, offset, 'fmt '); offset += 4;
/* 過濾位元組,一般為 0x10 = 16 */
view.setUint32(offset, 16, true); offset += 4;
/* 格式類別 (PCM形式取樣資料) */
view.setUint16(offset, 1, true); offset += 2;
/* 通道數 */
view.setUint16(offset, channelCount, true); offset += 2;
/* 取樣率,每秒樣本數,表示每個通道的播放速度 */
view.setUint32(offset, sampleRateTmp, true); offset += 4;
/* 波形資料傳輸率 (每秒平均位元組數) 通道數×每秒資料位數×每樣本資料位/8 */
view.setUint32(offset, sampleRateTmp * channelCount * (sampleBits / 8), true); offset +=4;
/* 快資料調整數 取樣一次佔用位元組數 通道數×每樣本的資料位數/8 */
view.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2;
/* 每樣本資料位數 */
view.setUint16(offset, sampleBits, true); offset += 2;
/* 資料識別符號 */
writeString(view, offset, 'data'); offset += 4;
/* 取樣資料總數,即資料總大小-44 */
view.setUint32(offset, dataLength, true); offset += 4;
/* 取樣資料 */
floatTo8BitPCM(view, 44, samples);
return view;
}

使用方法:

1.使能HTML5錄音功能

window.URL = window.URL;
navigator.getUserMedia = navigator.webkitGetUserMedia;
if (navigator.getUserMedia) {
    navigator.getUserMedia({video:true,audio:true}, function(localMediaStream){
        var context = new AudioContext();
        var mediaStreamSource = context.createMediaStreamSource(localMediaStream);
        window.MediaStreamSource = mediaStreamSource;
    }, function(){


    });
}else {
    Ext.Msg.alert(“訊息提示”,“你的瀏覽器不支援錄音”);
}

2.開始錄音

var rec = new Recorder(MediaStreamSource);
window.rec=rec;

3.停止錄音

window.rec.stop();
rec.exportWAV(function(blob) {

//匯出錄音過程產生的blob資料,傳給後臺實現音訊的儲存

});
    console.log(blob);