如何優化 WebRTC 提升直播體驗?
全民快樂資深音視訊工程師郭奕在LiveVideoStackCon 2018音視訊技術大會的演講中從工程師的角度講述瞭如何利用WebRTC打造出具備實時互動能力的應用,包括從信令的互動到媒體的傳輸需要完成的工作。LiveVideoStack對演講內容進行了整理。
文 / 郭奕
整理 / LiveVideoStack
大家好,我是來自全民快樂科技有限公司的郭奕,接下來我將從一個工程師的角度為大家分享如何更好地利用WebRTC為應用賦能。本次分享將以“給音視訊實時通訊應用打分“為線索,與大家一起探索如何提升以直播連麥、傳統音視訊會議等為主要應用場景的實時互動音視訊通訊使用者體驗。
1. 給音視訊實時通訊應用打個分
如果按照顏值為上圖中的幾位人物打分,我想絕大多數人會把最高分留給右二的秋香,這其實告訴我們一定要結合實際場景才能為音視訊實時通訊應用打出客觀準確的分數。
本次賦能的目標APP為StarMaker——全民快樂旗下一款活躍使用者主要集中在印度、印尼、中東等地區的音樂視訊社交APP,Android端使用者規模遠大於iOS端。
雖然StarMaker的品類應當被定義為社交應用,但卻具備非常多的音樂基因,這就使得單純的人聲清晰已無法滿足使用者對這款APP的需求,我們需要進一步提高此款產品在音樂方面的綜合質量。
在我們引入RTC之前此應用就已具備直播等功能,且已經具備一套非常完善的音視訊採集、處理體系。考慮到平臺的可維護性與模組的複用性,我們需要在對其進行改造的同時儘可能保持其核心模組的完整與穩定。
一款優秀的RTC應用具備哪些特質呢?最重要的應當是實時性。優秀的RTC應用能夠給使用者近乎面對面交流的快感,使用者需要做的也僅是聯網後開啟APP即可。如果不考慮服務本身與伺服器架構,在良好的網路環境與理想的終端條件下實現上述目標並不是一件十分困難的事情,而這顯然是不現實的。因此面對複雜的網路環境與碎片化的終端情況我們能做的只有努力適應與提高相容性,這也是實現良好使用者體驗的必由之路。
首先我們給自己定一個小目標:為實現70分的RTC應用我們應當做出什麼努力?
首先從裝置端到伺服器的往返時延需要被控制在100ms,且在此基礎上控制丟包率在30%;脣音同步也是一項需要達到的關鍵指標,而端對端的延遲需小於400毫秒;最後一點需達到的便是能夠為2015年以後的裝置提供流暢完整的服務。這些資料並非簡單的確立,經過統計我們發現北京辦公室與一個部署在印度的機房之間的RTT大約在150ms左右,而印度機房中裡有80%以上的RTT都在100ms以下;30%的隨機丟包則是一個RCT SDK較為入門的達標水準,而根據Google最新的官方統計大約70%以上的Android使用者所使用的移動終端已經預裝或升級至Android 6.0以上的系統,且Android 6.0 的釋出時間在2015年4月左右。由於我們的使用者多集中於 Android 平臺,這就要求我們在設計之初需要考慮到平臺可在NEXUS 6與iPhone 6以後的裝置上穩定執行。
2. 整合WebRTC
我們的工作就是將WebRTC整合至應用,主要從伺服器端與客戶端兩方面入手工作。雖然WebRTC是一開始是按照P2P設計的,但是為提高服務穩定性我們需要背後需要強大的伺服器作為支撐;而從信令角度來說WebRTC也不能完全算作P2P。
作為建立通話實現控制的基礎,信令伺服器在WebRTC所需伺服器中至關重要,而NAT穿透伺服器則是WebRTC中建立媒體過程必需的伺服器支援;媒體伺服器則是為完成諸如多方通訊、視訊錄製等較為繁重的媒體處理任務必不可少的關鍵一環。其中媒體伺服器主要分為RTP轉發與混流,前者是我們較為熟悉的SFU而後者則是MCU。
上圖展示的是一個互動直播所需的基本框架,可以看到我們使用了SFU與MCU。SFU的優點在於可節省一部分寶貴的上行頻寬而MCU的優點則是可明顯節省流量成本並將多路流混成一路流,再將其轉為RTMP並轉推至CDN。結合連麥場景,上圖左側連線SFU並傳輸媒體流的三個裝置可以理解為連麥的三方,SFU在接受來自連麥三方的媒體流的同時會將此三方媒體流轉至MCU並進行混流與RTMP流轉換處理,處理完成的媒體流會被推送至CDN從而讓觀眾端可從CDN上拉去相應媒體流並觀看視訊。
如何快速搭建可完成上述處理流程的伺服器框架?這裡我們就需要藉助開源的力量,上圖展示了一些我們所參與社群提供的良好解決方案:SFU的開源伺服器解決方案有Licode、Janus、Jitsi與Mediasoup,在選擇時我們需要考慮整個團隊的技術棧情況,若團隊技術棧偏向於底層,那麼推薦選擇更多使用C++的Janus方案,而如果習慣基於Java開發那麼Jitsi則是不錯的選擇;這裡需要提醒的是,Licode中包含一個官方稱之為MCU的模組,但實際上其並不具備混流的功能。
如果是MCU的開源伺服器解決方案我們推薦選擇Kurento,其內部使用了GStreamer而最底層則使用glib;但Kurento的學習曲線非常陡峭這樣的好處在於其整個介面的靈活性非常出色,但出色的靈活性也意味著內部的高複雜性,這也是我們在選擇此方案是需要重點關注的方面。如果對MCU的要求沒有如此嚴苛,我們也可以使用FFmpeg自研的伺服器。
我們的客戶端集成了WebRTC,在iOS平臺的Safari瀏覽器支援WebRTC後移動端整合WebRTC的方式主要分為以下三種:依賴手機瀏覽器的Web方式與直接將WebRTC原生程式碼整合至應用端的原生方式,以及兼具二者特性的混合方式。
混合方式的好處在於其可跨越平臺限制為Web端帶來接近於原生的特性與互動體驗,其代表有Cordova與React Native;但這兩種方案還遠不能滿足我們期待的一個Web在所有平臺都能提供一致體驗的需求且Cordova的版本更新迭代頻繁,因此選擇此方案的前提是技術棧更偏向前端而底層如WebRTC則需支援,同時混合方式的試錯成本也比較高。
當然我們也可以選擇原生的方式,前提是並沒有非常強的跨平臺需求。由於我們的業務不需要PC端僅依賴移動端開展,原生自然而然成為我們整合WebTRTC的首選方式。
上圖展示的是我們的Android原生應用軟體框架圖,主要基於以下幾個關鍵點進行架構:首先框架需要具有一定移植性,允許我們在Android端完成開發後將平臺快速移植至iOS端;其次請觀察圖中標為橙紅色的三個基於WebRTC C++原生程式碼庫建立的模組,分別為通話管理、媒體引擎與信令模組;而在最上層使用紅框標記的部分則是API介面。其中第一層與應用相關,根據不同應用場景區分;左側的Android API則包含傳統的RTC通話等。當我們將應用遷移至iOS時所需完成的工作量可以明顯減小,僅需要將上層介面換成OC,媒體引擎做一些適就可以了。
原生移動應用信令的選擇是接下來需要我們關注的工作內容。考慮到信令模組的移植性,我們通常會通過以下三種方式完成對原生移動信令的選擇:第一種是自定義信令,之所以考慮這種信令是因為WebRTC並沒有限制信令的用途,我們只需選擇一種合適的信令型別並將足夠的資訊傳遞給WebRTC即可,因此Websocket或Unix Socket都是我們考慮的方式;而Jingle也是我們考慮的一種,多用於早期WebRTC版本;SIP是滿足傳統VoIP裝置相容的不二選擇,發展至今也有許多非常成熟的開源解決方案。完成以上整合WebRTC的步驟,一個70分的RTC應用便初步構建完成。
3. 滿足現有應用需求
為了讓整合的應用初步滿足現有需求,接下來我們需要完成的工作是外部音訊與視訊的採集。
上圖展示了外部音訊採集的大致流程,其中紅色部分為SDK的使用者也就是APP所需完成的工作,黑色部分則是WebRTC的SDK負責的模組,(紅框部分則是我們需要重點實現的模組)。外部採集到的音訊會首先進入External Audio Device Module流程,隨後交由Fine Buffer處理;隨後Fine Buffer會將傳入的音訊流按照10毫秒的粒度傳送至Audio Processing Module這一WebRTC的重要模組,其中音訊會經過回聲消除等處理;經由Audio Processing Module處理完成後的音訊資料會被推回至呼叫者以做音效處理最終被傳輸至編碼器進行編碼以為傳送至網路做好準備。
外部視訊採集流程與音訊有所不同,本地視訊的渲染工作交由外部流程完成,採集到的視訊流會首先傳輸至H.264 Video Capturer處理,通過不參與渲染與編碼的Broadcaster統計重要資訊並通知Fake Video Encoder對來自Broadcaster的視訊資料流進行編碼處理。
為了確保音訊質量符合StarMaker的較高需求,我們選擇業界較為優秀的AAC而非Opus作為音訊編碼器。
為了更提高相容性與控制轉碼負擔,我們選擇了傳統的H.264而非VP8/VP9作為視訊編碼器。雖然在效能與頻寬節省方面H.264不算最優秀的編碼方案,考慮到在我們的實際應用場景中有RTMP推流的工作,H.264對整個硬體生態的支援更佳符合我們的需求。
4. 適應網路環境
單純的滿足現有應用需求距離我們的目標還遠遠不夠,適應網路環境尤其如何對抗弱網是擺在我們面前的另一項關鍵命題。
來自於Facebook的開源工具Augmented Traffic Control是我們選擇的一個物美價廉的網路環境模擬方案,其本質為底層基於Linux的一個TC工具,同時提供了非常完善的上下性丟包、頻寬限制、壞包、亂序等模擬方式,其簡單高效甚至可以像上圖右側那樣使用微控制器進行部署。
我們使用以下兩種型別的工具箱作為對抗弱網的方案:如矛般包含擁塞演算法可實現主動攻擊的ARC自動位元速率控制,也被稱為GCC或Client Side BWE,主要從客戶端進行頻寬估計;而如盾般進行被動防禦的有ARQ自動重傳請求(WebRTC中還有與ARQ類似的選擇性重傳)、FEC前項編碼糾錯與PLC丟包補償。可以看出WebRTC在此方面做出了大量努力,如果存在一款整合以上所有工具的編碼器是否會為我們帶來較為出色的弱網對抗效果呢?事實也的確如此,如Opus就集成了FEC與PLC。由於視訊的頻寬資源更多,ARC更多用於視訊場景,可調節空間也較為充裕。WebRTC中也集成了針對音訊的類似於ARC的模組,其被稱為ANA(Audio Network Adaptor),作用主要是對音訊位元速率進行微調,但僅針對Opus。這裡需要提醒的是,PLC與ARQ、FEC有所不同,PLC主要通過訊號處理的原理人為構造出流暢度較高的音訊。
但在實際應用當中我們一開始並沒有選擇FEC,主要原因如下:
谷歌並不推薦在H.264條件下同時使用FEC與NACK,二者只能選一運用,混合使用FEC與NACK主要針對於VP8、VP9。如上圖所示,以上7個Packet中Packet 1~Packet 3為一幀影象而Packet 4~Packet 5則為另外一幀影象,中間的FEC 1與FEC 2兩個包則是用於視訊恢復的冗餘資料。假設FEC 1與FEC 2發生丟失現象即會出現首先我們需要知道的是RTP包中的Sequence Number必須連續,我們才能根據Sequence Number判斷哪些包丟失,而H.264即通過此方式判斷丟包;當FEC 1與FEC 2丟失之後,H.264會判斷丟失一個關鍵幀或P幀,實際上僅丟失一段冗餘資訊而已,但此時H.264便會發起丟包重傳,這無疑是一種對頻寬的浪費。之所以VP8、VP9不存在類似的問題,是因為VP8、VP9具有非常豐富的RTP Payload Header,不僅包括各種的邊界檢查,也攜帶了更多的額外資訊。其中最重要的便是用於標識包屬於哪一幀並正確排序的Picture ID,可在出現FEC 1與FEC 2 丟失情況時偵測到Packet 3之後的Packet 4依舊存在,從而有效避免了不必要的重傳操作,這便是我們不選擇FEC的一個重要原因。
既然我們在音訊與視訊都使用了NACK,就需要注意以下幾個關鍵點:首先NACK僅適用於低延遲場景,所經歷的RTT時間並不長;如果用於高延遲場景便會出現重傳效果不盡如人意的狀況,因此面對高延遲場景我們應當有效控制重傳頻率,極力避免反覆重傳甚至重傳風暴的現象發生。其次,丟包率的統計也易受到影響,RTCP的丟包統計嚴重依賴所收到包的數量,接收到兩個完全一樣包的概率可能會增加,其會極大影響丟包率的準確性,也許會造成丟包率資料符合要求而實際丟包控制效果卻十分糟糕的情況;加之丟包率會對擁塞控制演算法產生影響,一旦相關引數設定對丟包的敏感不夠就會令擁塞控制形同虛設。
經過上述優化,獲得一個70分的RTC應用便不再成為一件困難的事情。
5. 更上一層樓
當然,70分還遠遠不夠,我們應該給自己設立更高的目標。如何實現出色的RTC應用,便是我們接下來探索的方向。
AEC是第一個需要改進的方面,WebRTC會優先選擇AEC處理。對於iOS而言其AEC整體效能較為出色,而對Android來說其AEC依舊具備非常大的提升空間,有些Android裝置的AEC甚至並沒有發揮其應有的效果。由於Android平臺的碎片化特徵,我們需要儘可能通過整合在軟體內部的AEC解決方案實現滿足較為一致的處理效果。WebRTC中的AECM處理演算法專用於移動端的回聲消除,考慮到整個移動端包括CPU在內的硬體整體計算能力,AECM被簡化了許多環節,這樣帶來的副作用便如上圖展示的那樣,對比AECM處理前後的音訊頻譜我們可以發現部分音訊會被直接刪去,這一定不是我們期待的優化結果。因此,我們可以針對以上缺點對AECM優化。
除了AEC,上圖展示的那樣是一種基於丟包閾值與延遲閾值制定的混合抗丟包解決方案也是一個優化的方向:在高延遲條件下我們儘量採用FEC方案,當延遲低於某一閾值時則採用NACK方案;在高丟包率條件下我們採用ARC處理以實現對編位元速率的控制,而在低丟包率條件下則不使用ARC。
點選【 閱讀原文 】或掃描圖中二維碼瞭解更多LiveVideoStackCon 2019 上海 音視訊技術大會 講師資訊。