js實現音訊模擬訊號轉數字訊號
前篇文章也講了如何通過webrtc中的getUserMedia()方法獲取音訊的模擬訊號,接下來,我們就將這些模擬訊號轉為數字訊號。
模擬訊號
什麼是模擬訊號呢?非通訊專業的我,引用來自維基百科的一段話:模擬訊號(英語: Analog Signal ),是指在 時域 上數學形式為連續函式的訊號。(時域是描述數學函式或物理訊號對時間的關係。例如一個訊號的時域波形可以表達訊號隨著時間的變化。)
而我們先前錄製的音訊訊號是帶有音效的有規律的聲波的頻率、幅度變化資訊載體。 類似於:
不過,這兒應該是很多很密的類似於正弦函式的曲線。
pcm編碼
PCM(Pulse Code Modulation),脈衝編碼調製,是一種模擬訊號的數字化方法。
模擬訊號數字化需要經過三個過程,即抽樣、量化和編碼。
抽樣
又可以稱為取樣頻率,輸入的取樣率是固定的,可以獲取sampleRate得到。
var context = new (window.AudioContext || window.webkitAudioContext)(); console.log(context.sampleRate);// 輸入音訊取樣率(HZ)
即橫向座標(可以理解為x軸)在單位時間內採集了48000(我的chrome輸出這麼多)次樣本。從維基百科上偷張圖來,加深下理解(先不管y軸)。因為採集的樣本多,所以未處理的pcm編碼佔的空間都比較大,但是他未經過任何編碼和壓縮處理,是種無失真壓縮的格式,也能得到相當好的音質效果。
取樣頻率一般共分為22.05KHz、44.1KHz、48KHz三個等級,取樣頻率越高,音質越精確。正常人聽覺的頻率範圍大約在20Hz~20kHz之間,根據奈奎斯特取樣理論(只有取樣頻率高於聲音訊號最高頻率的兩倍時,才能把數字訊號表示的聲音還原成為原來的聲音),為了保證聲音不失真,取樣頻率應該在40kHz左右。所以對於高於48KHz的取樣頻率人耳已無法辨別出來了,所以並沒有什麼實用價值。
量化
量化就是用有限個幅度值近似還原原來連續變化的幅度值。
依舊是前面盜來的圖,我們來關注下y軸上的資訊,y軸共分為了16份,也就是2的4次方,即使用4bit就可以儲存這些資訊了,這兒的取樣位數是4。將這些點連起來,是不是並不能完全(較好)地恢復原有的曲線?如果16份恢復地不夠完美,那麼32,64更多份數是不是就能恢復這波形圖了呢?
一般情況下,取樣位數是8或16,8位的可以劃分為2^8=256份,範圍是0-255。16位的可以劃分位2^16=65536份,範圍是-32768到32767。
到這就是量化了,取樣位數這個數值越大,解析度就越高,錄製和回放的聲音就越真實。
編碼
前一步已經量化好了,現在就是轉化成0與1資料的時候了。
可以列印看下先確我們蒐集到的模擬資料,這些資料都是在[-1, 1]之間的,要轉成8位的形式,負數*128,正數*127,然後整體向上平移128(+128),即可得到[0,255]範圍的資料。16位的資料,只需要對負數*32768,對正數*32767即可。
注:實際儲存都是二進位制形式的。
另外有個聲道概念也要在此提及下。
聲道
聲道有單聲道和雙聲道的區別,當然也有8位和16位之分,
每個取樣佔用8bit,也就是一個位元組。
16位的就是double了。
雙聲道的話,在audioprocess事件種就要特殊處理下,可以用e.inputBuffer.getChannelData(1)取第二個聲道的資料,當然,createScriptProcessor方法中的聲道數也要設定成2。
js實現轉化
概念裡的差不多了,是時候用js將前面的模擬資料數字化了。接著前面的程式碼,當時我們已經拿到了模擬資料。首先,在audioprocess事件中,是用inputData陣列收集的,即是二維陣列,為了後邊處理方便,現拉成一位陣列。
收集資料簡單處理
function decompress() { // 合併 var data = new Float32Array(size); var offset = 0; // 偏移量計算 // 將二維資料,轉成一維資料 for (var i = 0; i < inputData.length; i++) { data.set(inputData[i], offset); offset += inputData[i].length; } return data; };
此處將二維的資料轉成Float32Array型別,關於該型別的詳細介紹可以檢視這篇文章:前端二進位制學習(三)。
注:比如阿里雲的語音識別,要求是16000取樣率的pcm音訊,所以,此處會有壓縮的過程,由於48k與16k是三倍關係,即三個樣本中要刪除一個樣本,利用迴圈過濾就可以了,不理解的可以檢視 recorder 中的compress函式,我這簡單處理了。
編碼
接下來就是編碼了,我們這預設設定取樣位數為16位。
var oututSampleBits = 16; // 輸出取樣數位
由於8位剛好一位元組,此處若是16位的取樣位數,需要兩倍的大小。所以,在取arraybuffer時,需要處理下,
let bytes = decompress(), sampleBits = oututSampleBits, dataLength = bytes.length * (sampleBits / 8), buffer = new ArrayBuffer(dataLength), data = new DataView(buffer);
在量化的過程中,其實已經透露過演算法了,當取樣位數是8位的,只要將負數*128,正數*127,然後整體向上平移128(+128)就可以了。
if (sampleBits === 8) { for (var i = 0; i < bytes.length; i++, offset++) { // 範圍[-1, 1] var s = Math.max(-1, Math.min(1, bytes[i])); // 對於8位的話,負數*128,正數*127,然後整體向上平移128(+128),即可得到[0,255]範圍的資料。 var val = s < 0 ? s * 128 : s * 127; val = parseInt(val + 128); data.setInt8(offset, val, true); } }
當時16位的,只需要對負數*32768,對正數*32767就行了,記得offset取2,並使用setInt16,第三個引數要置為true,牽扯到大端和小端位元組序。
for (var i = 0; i < bytes.length; i++, offset += 2) { var s = Math.max(-1, Math.min(1, bytes[i])); // 16位直接乘就行了 data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true); }
此時,data的資料就是pcm流資料了。
程式碼地址見: js實現音訊模擬訊號轉數字訊號 ,完整的錄音程式碼見: recorder.js 。
總結
到這,我已經成功地將模擬訊號轉成數字訊號了。可惜,瀏覽器並不能播放pcm的音訊資料,在下一篇中,將講述下如何把pcm轉成wav格式的音訊檔案,並在瀏覽器中播放。
歡迎關注微信公眾號[ 我不會前端 ]或掃描下方二維碼!