單獨編譯和使用webrtc音訊回聲消除模組(附完整原始碼+測試音訊檔案)
說實話很不想寫這篇文章,因為這和我一貫推崇的最好全部編譯並使用webrtc音訊處理模組相悖。可是不知不覺已經把降噪和增益寫出來,回聲消除如果使用者可以得到完美利用也不失為一個很好的方法。但是還是那句話,最好還是全部編譯和使用webrtc的整個音訊處理模組。另外這篇文章已經不單單的回聲消除模組了,其中包括了降噪,增益,靜音檢測,如果有需要可以選擇其中的一部分單獨提取除錯。
相對而言回聲消除比起其他模組要複雜很多,不光光是計算量大(之前在一部380MHz的攝像頭中測試過,其消耗的時間為降噪的三倍以上,如果音訊延遲更大甚至根本跑不起來),而且其中也涉及到一個delay的計算,delay計算是否精確完全影響回聲消除的效果。
至於delay的計算,網上有很多說明,有複雜也有簡單的。在這裡我還是簡單的提一下:
這張圖很多東西可以無視,我們重點看T0,T1,T2三項。
T0代表著聲音從揚聲器傳到麥克風的時間,這個時間可以忽略,因為一般來說話筒和揚聲器之間距離不會太遠,考慮到聲音340米每秒的速度,這個時間都不會超過1毫秒。
T1代表遠處傳到你這來的聲音,這個聲音被傳遞到回聲消除遠端介面(WebRtcAec_BufferFarend)的到播放出來的時間。一般來說接收到的音訊資料傳入這個介面的時候也就是上層傳入揚聲器的時刻,所以可以理解成該聲音防到播放佇列中開始計時,到播放出來的時間。
T2代表一段聲音被揚聲器採集到,然後到被送到近端處理函式(WebRtcAec_Process)的時刻,由於聲音被採集到馬上會做回聲消除處理,所以這個時間可以理解成麥克風採集到聲音開始計時,然後到你的程式碼拿到音訊PCM資料所用的時間。
好了,delay=T0+T1+T2,其實也就是T1+T2。
一般來說,一個裝置如果能找到合適的delay,那麼這個裝置再做回聲消除處理就和降噪增益一樣幾乎沒什麼難度了。從網上看iPhone的固定delay是60ms,不過不確定,MacBook掛了,在等四季度新MacBook上市,所以暫時沒辦法做驗證。
因為回聲消除過程,可以理解成已經把藍黑兩種顏色的墨水完全混合,然後分離出來所需要的藍色或者黑色顏色。事實上完全混在一起的音訊資料是無法徹底分開的,但是我們可以把混在一起的聲音理解成兩段聲音,其中有一段聲音可以找到一段幾乎相近的對比原聲,然後在混音中找到和原聲近似的資料,這樣就可以分離了。delay的意義顯而易見就是要把原聲和混在一起的聲音資料作對比時,校正時刻所用,這個時刻越是精確,那麼回聲消除的效果越好。不過從實際效果來看這個delay也並不需要特別精確,在這段測試音訊資料裡面範圍可以接近100毫秒。
從兩段音訊波形起始位置看delay的時間應該超過100毫秒,那麼可以用一段程式碼做測試,測試程式碼和音訊檔案來自於網上:
1 int WebRtcAecTest() 2 { 3 #define NN 160 4 short far_frame[NN]; 5 short near_frame[NN]; 6 short out_frame[NN]; 7 8 void *aecmInst = NULL; 9 FILE *fp_far = fopen("speaker.pcm", "rb"); 10 FILE *fp_near = fopen("micin.pcm", "rb"); 11 FILE *fp_out = fopen("out.pcm", "wb"); 12 13 do 14 { 15 if(!fp_far || !fp_near || !fp_out) 16 { 17 printf("WebRtcAecTest open file err \n"); 18 break; 19 } 20 21 WebRtcAec_Create(&aecmInst); 22 WebRtcAec_Init(aecmInst, 8000, 8000); 23 24 AecConfig config; 25 config.nlpMode = kAecNlpConservative; 26 WebRtcAec_set_config(aecmInst, config); 27 28 while(1) 29 { 30 if (NN == fread(far_frame, sizeof(short), NN, fp_far)) 31 { 32 fread(near_frame, sizeof(short), NN, fp_near); 33 WebRtcAec_BufferFarend(aecmInst, far_frame, NN);//對參考聲音(回聲)的處理 34 35 WebRtcAec_Process(aecmInst, near_frame, NULL, out_frame, NULL, NN,109,0);//回聲消除 36 fwrite(out_frame, sizeof(short), NN, fp_out); 37 } 38 else 39 { 40 break; 41 } 42 } 43 } while (0); 44 45 fclose(fp_far); 46 fclose(fp_near); 47 fclose(fp_out); 48 WebRtcAec_Free(aecmInst); 49 return 0; 50 }