1. 程式人生 > >白話解讀 WebRTC 音訊 NetEQ 及優化實踐

白話解讀 WebRTC 音訊 NetEQ 及優化實踐

NetEQ 是 WebRTC 音視訊核心技術之一,對於提高 VoIP 質量有明顯的效果,本文將從更為巨集觀的視角,用通俗白話介紹 WebRTC 中音訊 NetEQ 的相關概念背景和框架原理,以及相關的優化實踐。 作者| 良逸 審校| 泰一 ## 為什麼要 “白話” NetEQ? 隨便搜尋一下,我們就能在網上找到很多關於 WebRTC 中音訊 NetEQ 的文章,比如下面的幾篇文章都是非常不錯的學習資料和參考。特別是西安電子科技大學 2013 年吳江銳的碩士論文《WebRTC 語音引擎中 NetEQ 技術的研究》,非常詳盡地介紹了 NetEQ 實現細節,也被引用到了很多很多的文章中。 * [《WebRTC 語音引擎中 NetEQ 技術的研究》](http://www.doc88.com/p-7068376881984.html?spm=a2c6h.12873639.0.0.5e54504c4dHXyK) * [NetEQ 演算法](https://blog.csdn.net/u014338577/article/details/46010983?spm=a2c6h.12873639.0.0.5e54504c4dHXyK) * [WebRTC 中音訊相關的 NetEQ](https://blog.csdn.net/ahilll/article/details/81868878?spm=a2c6h.12873639.0.0.5e54504c4dHXyK) 這些文章大部分從比較 “學術” 的或 “演算法” 的角度,對 NetEQ 的細節做了非常透徹的分析,所以這裡我想從更巨集觀一些的角度,說一下我個人的理解。白話更容易被大家接受,爭取一個數學公式都不用,一行程式碼都不上就把思路說清楚,有理解不對的地方,還請大家不吝賜教。 ## 丟包、抖動和優化的理解 在音視訊實時通訊領域,特別是移動辦公(4G),疫情下的居家辦公和線上課堂 (WIFI),網路環境成了影響音視訊質量最關鍵的因素,在差的網路質量面前,再好的音視訊演算法都顯得有些杯水車薪。**網路質量差的表現主要有延時、亂序、丟包、抖動**,誰能處理和平衡好這幾類問題,誰就能獲得更好的音視訊體驗。由於網路的基礎延時是鏈路的選擇決定的,需優化鏈路排程層來解決;而亂序在大部分網路條件下並不是很多,而且亂序的程度也不是很嚴重,所以接下來我們主要會討論丟包和抖動。 抖動是資料在網路上的傳輸忽快忽慢,丟包是資料包經過網路傳輸,因為各種原因被丟掉了,經過幾次重傳後被成功收到是恢復包,重傳也失敗的或者恢復包過時的,都會形成真正的丟包,需要丟包恢復 PLC 演算法來無中生有的產生一些假資料來補償。丟包和抖動從時間維度上又是統一的,等一會來了的是抖動,遲到很久才來的是重傳包,等一輩子也不來的就是 “真丟包”,我們的目標就是要儘量降低資料包變成 “真丟包” 的概率。 優化,直觀來講就是某個資料指標,經過一頓猛如虎的操作之後,從 xxx 提升到了 xxx。但我覺得,評判優化好壞不能僅僅停留在這個維度,優化是要 “知己知彼”,己是自己的產品需求,彼是現有演算法的能力,己彼合一才是最好的優化,不管演算法是簡單還是複雜,只要能完美的匹配自己的產品需求,就是最好的演算法,“能捉到老鼠的就是好貓”。 ## NetEQ 及相關模組 ### NetEQ 的出處 [《GIPS NetEQ 原始文件》](http://www.gipscorp.alcatrazconsulting.com/files/english/datasheets/NetEQ.pdf?spm=a2c6h.12873639.0.0.5e54504c4dHXyK&file=NetEQ.pdf),這是由 GIPS 公司提供的最原始的 NetEQ 的說明文件([中文翻譯](https://blog.csdn.net/lhl_blog/article/details/10993605?spm=a2c6h.12873639.0.0.5e54504c4dHXyK)),裡面介紹了什麼是 NetEQ 以及對其效能的簡單說明。NetEQ 本質上就是一個音訊的 JitterBuffer(抖動緩衝器),名字起的非常貼切,Network Equalizer(網路均衡器)。大家都知道 Audio Equalizer 是用來均衡聲音的效果器,而這裡的 NetEQ 是用來均衡網路抖動的效果器。而且 GIPS 還給這個名字註冊了商標,所以很多地方看到的是 NetEQ (TM) 。 上面的官方文件中,有一條很重要資訊,“最小化抖動緩衝帶來的延時影響”,這說明 NetEQ 的設計目標之一就是:**“追求極低延時”**。這個資訊很關鍵,為我們後續的優化提供了重要線索。 ![](https://ucc.alicdn.com/pic/developer-ecology/d56d48f67e414a75bba63a8a5aa6d41a.png) ![](https://ucc.alicdn.com/pic/developer-ecology/a479e1657cee464eb66c2f83feaa9efe.png) ![](https://ucc.alicdn.com/pic/developer-ecology/d13e59b49586457682fae1a36e45170d.png) ### NetEQ 在音視訊通訊 QoS 流程中的位置 音視訊通訊對於普通使用者來說,只要網路是通的,WIFI 和 4G 都可以,一個呼叫過去,看到人且聽到聲音,就 OK 了,很簡單的事情,但對於底層的實現卻沒有看起來那麼簡單。單 WebRTC 開源引擎的相關程式碼檔案數量就有 20 萬個左右,程式碼行數不知道有沒有人具體算過,應該也是千萬數量級的了。不知道多少碼農為此掉光了頭髮 :)。 下面這張圖,是對實際上更復雜的音視訊通訊流程的抽象和簡化。左邊是傳送 (推流) 側:經過採集、編碼、封裝、傳送;中間經過網路傳輸;右邊是接收 (拉流) 側:接收、解包、解碼、播放;這裡重點體現了 QoS(Quality of Service,服務質量)的幾個大的功能,以及跟推拉流資料主要流程的關係。可以看到 QoS 功能分散在音視訊通訊流程中的各個位置,導致要了解整個流程之後才能對 QoS 有比較全面的理解。圖上看起來左邊傳送側的 QoS 功能要多一些,這是因為 QoS 的目的就是要解決通訊過程中的使用者體驗問題,要解決問題,最好就是找到問題的源頭,能從源頭解決的,都是比較好的解決方式。但總有一部分問題是不能從源頭來解決的,比如在多人會議的場景,一個人的收流側網路壞了,不能影響其它人的開會體驗,不能出現 “一顆老鼠屎壞掉一鍋粥” 的情況,不能汙染源頭。所以收流也要做 QoS 的功能,目前收流側的必備功能就是 JitterBuffer,包括視訊的和音訊的,本文重點分析音訊的 JitterBuffer -- NetEQ。 ![](https://ucc.alicdn.com/pic/developer-ecology/01a8ef1521ea4de088a2f4eb1e2d7cc6.png) ### NetEQ 原理及相關模組的關係 ![](https://ucc.alicdn.com/pic/developer-ecology/f5ba5bfea2254f4d814dd0cf320261a2.png) 上面這張圖是對 NetEQ 及其相關模組工作流程的抽象,主要包含 4 個部分,NetEQ 的輸入、NetEQ 的輸出、音訊重傳 Nack 請求模組、音視訊同步模組。為什麼要把 Nack 請求模組和音視訊同步模組也放進 NetEQ 的分析中?因為這兩個模組都直接跟 NetEQ 有依賴,相互影響。圖裡面的虛線,標識每個模組依賴的其它模組的資訊,以及這些資訊的來源。接下來介紹一下整個流程。 #### 1. 首先是 NetEQ 的輸入部分: 底層 Socket 收到一個 UDP 包後,觸發從 UDP 包到 RTP 包的解析,經過對 SSRC 和 PayloadType 的匹配,找到對應的音訊流接收的 Channel,然後從 `InsertPacketInternal` 輸入到 NetEQ 的接收模組中。 收到的音訊 RTP 包很可能會帶有 RED 冗餘包(redundance),按照 RFC2198 的標準或者一些私有的封裝格式,對其進行解包,還原出原始包,重複的原始包將會被忽略掉。解出來的原始 RTP 資料包會被按一定的演算法插入到 packet buffer 快取裡面去。之後會將收到的每一個原始包的序列號,通過 `UpdateLastReceivedPacket` 函式更新到 Nack 重傳請求模組,Nack 模組會通過 RTP 收包或定時器觸發兩種模式,呼叫 `GetNackList` 函式來生成重傳請求,以 NACK RTCP 包的格式傳送給推流側。 同時,解完的每一個原始包,得到了時間軸上唯一的一個接收時刻,包和包之間的接收時間差也能算出來了,**這個接收時間差除以每個包的打包時長就是 NetEQ 內部用來做抖動估計的 IAT(interarrival time)**,比如,兩個包時間差是 120ms,而打包時長是 20ms,則當前包的 IAT 值就是 120/20=6。之後每個包的 IAT 值經過核心的網路抖動估計模組(DelayManager)處理之後,得到最終的目標水位(TargetLevel),到此 NetEQ 的輸入處理部分就結束了。 #### 2. 其次是 NetEQ 的輸出部分: 輸出是由音訊硬體播放裝置的播放執行緒定時觸發的,播放裝置會每 10ms 通過 `GetAudioInternal` 介面從 NetEQ 裡面取 10ms 長度的資料來播放。 進入 `GetAudioInternal` 的函式之後,第一步要決策如何應對當前資料請求,這個任務交給操作決策模組來完成,決策模組根據之前的和當前的資料和操作的狀態,給出最終的操作型別判斷。**NetEQ 裡面定義了幾種操作型別:正常、加速、減速、融合、拉伸(丟包補償)、靜音**,這幾種操作的意義,後面再詳細的說。有了決策的操作型別,再從輸入部分的包快取(packet buffer)裡面取出一個 RTP 包,送給抽象的解碼器,抽象的解碼器通過 `DecodeLoop ` 函式層層呼叫到真正的解碼器進行解碼,並把解碼後的 PCM 音訊資料放到 `DecodedBuffer ` 裡面去。然後就是開始執行不同的操作了,NetEQ 裡面為每一種操作都實現了不同的音訊數字訊號處理演算法(DSP),除了 “正常” 操作會直接使用 `DecodedBuffer ` 裡的解碼資料,其它操作都會結合解碼的資料進行二次 DSP 處理,處理結果會先被放到演算法快取(Algorithm Buffer)裡面去,然後再插入到 Sync Buffer 裡面。Sync Buffer 是一個迴圈 buffer,設計的比較巧妙,存放了已經播放過的資料、解碼後未播放的資料,剛剛從演算法快取裡插入的資料放在 Sync Buffer 的末尾,如上圖所示。最後就是從 Sync Buffer 取出最早解碼後的資料,送出去給外部的混音模組,混音之後再送到音訊硬體來播放。 另外,從圖上可以看出決策模組(BufferLevelFilter)會結合當前包快取 packet buffer 裡快取的時長,和 Sync Buffer 裡快取的資料時長,經過演算法過濾後得到音訊當前的快取水位。音視訊同步模組會使用當前音訊快取水位,和視訊當前快取水位,結合最新 RTP 包的時間戳和音視訊的 SR 包獲得的時間戳,計算出音視訊的不同步程度,再通過 SetMinimumPlayoutDelay 最終設定到 NetEQ 裡面的最小目標水位,來控制 TargetLevel,實現音視訊同步。 ## NetEQ 內部模組 ### NetEQ 抖動估計模組(DelayManager) #### 1. 平穩抖動估計部分: 將每個包的 IAT 值,按照一定的比例(取多少比例是由下面的遺忘因子部分的計算決定的),累加到下面的 IAT 統計的直方圖裡面,最後計算從左往右累加值的 0.95 位置,此位置的 IAT 值作為最後的抖動 IAT 估計值。例如下圖,假定目標水位 TargetLevel 是 9,意味著目標快取資料時長將會是 180ms(假定打包時長 20ms)。 ![](https://ucc.alicdn.com/pic/developer-ecology/1463a1184af443f0bc2289549a7d95df.png) #### 2. 平穩抖動遺忘因子計算: 遺忘因子是用來控制當前包的 IAT 值取多少比例累加到上面的直方圖裡面去的係數,計算過程用了一個看起來比較複雜的公式,經過分析,其本質就是下面的黃色曲線,意思是開始的時候遺忘因子小,會取更多的當前包的 IAT 值來累加,隨著時間推移,遺忘因子逐漸變大,會取更少的當前包 IAT 值來累加。這個過程搞的有點複雜,從工程角度看完全可以簡化成直線之類的,因為測試下來 5s 左右的時間,基本就收斂到目標值 0.9993 了,其實這個 0.9993 才是影響抖動估計的最主要的因素,很多優化也是直接修改這個係數來調節估計的靈敏度。 ![](https://ucc.alicdn.com/pic/developer-ecology/37dbdcdba81e4ead8503760e6d1338a6.png) #### 3. 峰值抖動估計: DelayManager 中有一個峰值檢測器 PeakDetector 用來識別峰值,如果頻繁檢測到峰值,會進入峰值抖動的估計狀態,取最大的峰值作為最終估計結果,而且一旦進入這個狀態會一直維持 20s 時間,不管當前抖動是否已經恢復正常了。下面是一個示意圖。 ![](https://ucc.alicdn.com/pic/developer-ecology/838a8f5da4d341e3923d3ec5852d3749.png) ### NetEQ 操作決策模組(DecisionLogic) 決策模組的簡化後的基本判定邏輯,如下圖所示,比較簡潔不用解釋。這裡解釋一下下面這幾個操作型別的意義: * ComfortNoise:是用來產生舒適噪聲的,比單純的靜音包聽起來會更舒服的靜音狀態; * Expand(PLC):丟包補償,最重要的無中生有演算法模組,解決 “真丟包” 時沒資料的問題,造假專業戶 ; * Merge:如果上一次是 Expand 造假出來的資料,那為了聽起來更舒服一些,會跟正常資料包做一次融合演算法; * Accelerate:變聲不變調的加速播放演算法; * PreemptiveExpand:變聲不變調的減速播放演算法; * Normal:正常的解碼播放,不額外引入假資料; ![](https://ucc.alicdn.com/pic/developer-ecology/d434170713c94775bdbd0d8923284185.png) ## NetEQ 相關模組優化點 ### NetEQ 抗抖動優化 1. 由於 NetEQ 的設計目標是 “極低延時”,不能很好的匹配,視訊會議,線上課堂,直播連麥等非極低延時場景,需要對其敏感度進行調整,主要調整抖動估計模組相關的靈敏度; 2. 直播場景,由於對於延時敏感度可以到秒級以上,所以需要啟用 StreamMode 的功能(新版本中好像去掉了),而且也需要對其中引數進行適配; 3. 服務於極低延時目標,原始的包快取 packetbuffer 太小,容易造成 flush,需要按業務需要調大一些; 4. 還有一些業務會根據自己的業務場景主動識別網路狀況,然後直接設定最小 TargetLevel,簡單粗暴的控制 NetEQ 的水位。 ![](https://ucc.alicdn.com/pic/developer-ecology/e3062c43a80f4608946e91682c08321e.png) ### NetEQ 抗丟包優化: 1. 原始的 WebRTC 的 Nack 丟包請求的觸發機制是用包觸發的,在弱網下會惡化重傳效果,可以改為定時觸發來解決; 2. 丟包場景會有重傳,但如果 buffer 太小,重傳也會被丟棄,所以為了提高重傳效率,增加 ARQ 延時預留功能,可明顯降低拉伸率; 3. 比較演算法級的優化是對丟包補償 PLC 演算法的優化,調整現有 NetEQ 的拉伸機制,優化聽感效果; 4. 開啟 Opus 的 Dtx 功能之後,在丟包場景會導致音訊 Buffer 變大,需要單獨優化 Dtx 相關處理邏輯。 ![](https://ucc.alicdn.com/pic/developer-ecology/cdf164f429e2428f81abeedaf2b41f04.png) 下面是 ARQ 延時預留功能開啟後的效果對比,平均拉伸率降低 50%,延時也會相應增加: ![](https://ucc.alicdn.com/pic/developer-ecology/62d3c90e485a40238351936b16fb8003.png) ### 音視訊同步優化: ![](https://ucc.alicdn.com/pic/developer-ecology/be3fa0835268429fa335c01e7f88eb8e.png) 1. 原始的 WebRTC 的 P2P 音視訊同步演算法是沒有問題的,但是目前架構上面一般都有媒體轉發伺服器(SFU),而伺服器的 SR 包生成演算法可能會由於某些限制或者錯誤會不完全正確,導致無法正常同步,為規避 SR 包生成錯誤,需要優化音視訊同步模組的計算方式,使用水位為主要參考來同步,即在接收端保證音視訊的快取時間是差不多大小的。下面是優化效果的對比: ![](https://ucc.alicdn.com/pic/developer-ecology/3b2ae6d5276c4911a1425c8537091e2a.png) ![](https://ucc.alicdn.com/pic/developer-ecology/8a532841b6704aa39e4a1696f67df51c.png) 2. 還有一種音視訊同步的問題,其實不是音視訊同步機制導致的,而是裝置效能有問題,不能及時處理視訊的解碼和渲染,導致視訊資料累積,從而形成的音視訊不同步。這種問題可以通過對比不同步時長的趨勢,跟視訊解碼和渲染時長的趨勢,兩者匹配度會很高,如下圖所示: ![](https://ucc.alicdn.com/pic/developer-ecology/ade073d8f2cd42519c1882e1057d2eb9.png) ## 總結 **NetEQ 作為音訊接收側的核心功能**,基本上包含了各個方面,所以很多很多音視訊通訊的技術實現裡都會有它的蹤跡,乘著 WebRTC 開源快 10 年的東風,NetEQ 也變的非常普及,希望這篇白話文章能幫大家更好的理解 NetEQ。 作者最後的話:需求不停歇,優化無止境! >「視訊雲技術」你最值得關注的音視訊技術公眾號,每週推送來自阿里雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流