前端webrtc基礎 —— 錄音篇

1、概念

聲音:這裏的聲音是指經過麥克風會產生一連串的電壓變化,能夠獲得許多[-1,1]之間的數字。若是想要播放,需轉換成pcm格式git

PCM:pcm格式經過三個參數來描述【採樣頻率、採樣位數、聲道數】,從網上找到一張圖:github


pcm的核心思想水平和垂直分割成若干小塊,而後用這些座標上的點近似的描述一個波(聲音)

輸入採樣頻率:指麥克風收集聲音的頻率,由於麥克風須要將波形的聲音轉換成[-1,1]的信號,用它來指定在單位時間內收集多少個樣本web

輸出採樣頻率:單位時間內播放多少個採樣,通常保持與輸入採樣頻率一致canvas

2、實踐場景

    下面實現一個demo,經過google瀏覽器打開電腦麥克風,利用webrtc相關api錄音,而後轉換成pcm、wav格式,而且用audio標籤進行播放,用cavans畫出音域圖,大體流程以下:api

3、實現步驟

一、獲取麥克風權限

這裏使用的是 navigator.getUserMedia 方法,固然若是隻是用谷歌瀏覽器,能夠不用兼容處理,主要結構代碼以下瀏覽器

navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia
navigator.getUserMedia({ 
  audio: true   // 這裏面有video 和 audio 兩個參數,視頻選擇video
}, (stream) => {
  <!--這個stream 就是採集pcm數據的音源-->
}, (error) => {
  console.log(error)
})
複製代碼

二、pcm數據獲取

下面就用 window.AudioContext進行解析麥克風信息,重點用到createMediaStreamSource、createScriptProcessor、onaudioprocess三個方法,具體結構代碼以下緩存

<!--首先new一個AudioContext對象,做爲聲源的載體 -->
let audioContext = window.AudioContext || window.webkitAudioContext
const context = new audioContext()

<!--將聲音輸入這個對像,stream 就是上面返回音源-->
let audioInput = context.createMediaStreamSource(stream)

<!--建立聲音的緩存節點,createScriptProcessor方法的第二個和第三個參數指的是輸入和輸出都是聲道數,第一個參數緩存大小,通常數值爲1024,2048,4096,這裏選用4096-->
let recorder = context.createScriptProcessor(config.bufferSize, config.channelCount, config.channelCount) // 這裏config是自定義,後面會附帶源碼

<!--此方法音頻緩存,這裏audioData是自定義對象,這個對象會實現wav文件轉換,緩存pcm數據等-->
recorder.onaudioprocess = (e) => {
    audioData.input(e.inputBuffer.getChannelData(0))
}
複製代碼

可是在獲取的過程當中要有個觸發點,好比說本實踐的demo最終效果圖以下:bash


因此在錄音的過程當中,經過點擊gif圖中的錄製按鈕,經過點擊事件(onclick)觸發下面的兩行代碼,若是不是點擊的時候(也能夠是其餘事件)觸發該代碼,onaudioprocess方法將接收不到你在打開麥克風權限後所錄得音源信息

audioInput.connect(recorder) //聲音源連接過濾處理器
recorder.connect(context.destination) //過濾處理器連接揚聲器
複製代碼

連接完成後,createScriptProcessor的onaudioprocess方法能夠持續不斷的返回採樣數據,這些數據範圍在[-1,1]之間,類型是Float32。如今要作的就是將它們收集起來,將它轉成pcm文件數據。ide

三、audioData定義

首先定義個 audioData 對象,用來處理數據,總體結構以下,具體見下面源碼:ui

let audioData = {
  size: 0,        //錄音文件長度
  buffer: [],     //錄音緩存  
  inputSampleRate: context.sampleRate,    //輸入採樣率
  inputSampleBits: 16, //輸入採樣數位 8, 16 
  outputSampleRate: config.sampleRate,    //輸出採樣率
  oututSampleBits: config.sampleBits,    //輸出採樣數位 8, 16
  input: function(data) { // 實時存儲錄音的數據
  },
  getRawData: function() { //合併壓縮  
  },
  covertWav: function() { // 轉換成wav文件數據
  },
  getFullWavData: function() { // 用blob生成文件
  },
  closeContext: function(){ //關閉AudioContext不然錄音屢次會報錯
  },
  reshapeWavData: function(sampleBits, offset, iBytes, oData) { // 8位採樣數位
  },
  getWavBuffer: function() { // 用於繪圖wav格式的buffer數據
  },
  getPcmBuffer: function() { // pcm buffer 數據
  }
}
複製代碼

根據上面的gif圖:

a、第一步點擊錄製會執行章節 一、獲取麥克風權限二、pcm數據獲取 對應流程, 而後onaudioprocess方法中調用audioData對象input方法,用來存儲buffer數據;

b、點擊「下載pcm」標籤,會依次執行audioData對象getRawData、getPcmBuffer方法,可是下載的是txt文件,並不是是pcm文件,因爲不知道如何在js環境將txt文件轉成pcm文件,因此本人在將txt文件下載下來後直接手動修改了拓展名,固然此修改後的文件是能夠播放的,操做流程以下

pcm文件在線播放連接,由於本demo是8位的採樣位數,因此選擇的時候注意一下

四、pcm轉wav

pcm是沒有頭信息的,只要增長44個字節的頭信息便可轉換成wav,頭信息都是固定的,直接用便可,借用網上千篇一概的代碼片斷

let writeString = function (str) {  
  for (var i = 0; i < str.length; i++) {  
    data.setUint8(offset + i, str.charCodeAt(i))
  }  
}
// 資源交換文件標識符   
writeString('RIFF'); offset += 4
// 下個地址開始到文件尾總字節數,即文件大小-8   
data.setUint32(offset, 36 + dataLength, true); offset += 4
// WAV文件標誌  
writeString('WAVE'); offset += 4
// 波形格式標誌   
writeString('fmt '); offset += 4
// 過濾字節,通常爲 0x10 = 16   
data.setUint32(offset, 16, true); offset += 4 
// 格式類別 (PCM形式採樣數據)   
data.setUint16(offset, 1, true); offset += 2
// 通道數   
data.setUint16(offset, config.channelCount, true); offset += 2
// 採樣率,每秒樣本數,表示每一個通道的播放速度   
data.setUint32(offset, sampleRate, true); offset += 4
// 波形數據傳輸率 (每秒平均字節數) 單聲道×每秒數據位數×每樣本數據位/8   
data.setUint32(offset, config.channelCount * sampleRate * (sampleBits / 8), true); offset += 4
// 快數據調整數 採樣一次佔用字節數 單聲道×每樣本的數據位數/8   
data.setUint16(offset, config.channelCount * (sampleBits / 8), true); offset += 2 
// 每樣本數據位數   
data.setUint16(offset, sampleBits, true); offset += 2
// 數據標識符   
writeString('data'); offset += 4
// 採樣數據總數,即數據總大小-44   
data.setUint32(offset, dataLength, true); offset += 4
// 寫入採樣數據
data = this.reshapeWavData(sampleBits, offset, bytes, data)
複製代碼

五、數據轉音域圖

轉成音域圖重點用到AudioContext中的createAnalyser方法,它能夠將音波分解,具體步驟以下:

window.audioBufferSouceNode = context.createBufferSource() //建立聲源對象
audioBufferSouceNode.buffer = buffer  /聲源buffer文件流
gainNode = context.createGain() //建立音量控制器
gainNode.gain.value = 2 
audioBufferSouceNode.connect(gainNode) //聲源連接音量控制器
let analyser = context.createAnalyser()  //建立分析器
analyser.fftSize = 256
gainNode.connect(analyser)  //音量控制器連接分析器
analyser.connect(context.destination)  //分析器連接揚聲器
複製代碼

而後拿 analyser.frequencyBinCount 數據用canvas進行繪製,主要代碼以下:

let drawing = function() {
  let array = new Uint8Array(analyser.frequencyBinCount)
  analyser.getByteFrequencyData(array)
  ctx.clearRect(0, 0, 600, 200)
  for(let i = 0; i < array.length; i++) {
    let _height = array[i]
    if(!top[i] || (_height > top[i])) {//帽頭落下
      top[i] = _height
    } else {
      top[i] -= 1
    }
    ctx.fillRect(i * 20, 200 - _height, 4, _height)
    ctx.fillRect(i * 20, 200 - top[i] -6.6, 4, 3.3)//繪製帽頭
    ctx.fillStyle = gradient
  }
  requestAnimationFrame(drawing)
}
複製代碼

4、源碼地址

源碼github地址:audio

相關文章
相關標籤/搜索