B站在實時音影片技術領域的探索與實踐

陶然陶然發表於2022-11-15

   01 背景

  直播行業從傳統的娛樂直播發展到教育直播、電商直播等形式,產生了很多新的玩法。傳統的直播是一位主播展示才藝,觀眾透過彈幕、送禮物等方式進行互動。隨著網路質量不斷地提高,使用者也對直播平臺產生的新的要求,實時互動直播的場景就出現了,觀眾可以同時觀看多位主播之間互動的畫面,讓直播間的氣氛更好。B站直播的連麥PK、影片連線業務就提供了這個能力。主播看到的是對方主播實時的流(延遲400ms以內),而觀眾看到的是“準實時”的流(延遲2~5s左右)。本文講述搭建這樣一套系統需要了解的背景知識以及系統的整體架構,希望對大家有幫助。

   02 實時音影片關鍵技術

  從0到1搭建一套實時音影片系統並支撐現有的業務,如果沒有接觸過這方面的東西會感覺無從下手。我們可以看到,1996年IETF就推出了RTP協議用於實時音影片傳輸,2011年Google推出了WebRTC用於網頁端實時音視訊通話。從這些現有的協議和專案中,我們可以發現實時音影片技術的關鍵點,評估自身現有的基礎元件支援情況並結合業務場景尋找適合自己的解決方案。

  2.1 傳輸協議

  RTP協議(Real-time Transport Protocol)定義了在網際網路上傳輸實時音影片資料的標準格式,屬於應用層協議。RFC 3550描述RTP協議的傳輸層主要使用UDP,RFC 4571描述了RTP協議的TCP傳輸方式,在我們的實時音影片場景中應當優先選擇UDP,理由如下:

  TCP保證資料流的可靠性和順序性。TCP的超時重傳策略為了保證通用和公平,相對比較保守,重傳超時時間(RTO)可能會變的很大。假如中途丟失一個包,後續的包即使先到達也要快取起來等待重傳完成以後才能送到應用層。在網路狀況不佳的情況下,使用TCP傳輸會產生較大的延遲。

  UDP允許資料包丟失、亂序和重複。即使資料丟失也不會阻塞接收緩衝區等待重傳,這就為實時性提供了保障。在上層的RTP協議中,協議頭部包含了時間戳和序列號,可以對資料包進行重排和丟棄,解決了亂序和重複的問題。如果接收端監測到丟包,並且丟失的包是必要的且無法恢復,則傳送NACK訊息通知傳送端重傳(下一節會詳細探討這個話題)。

  UDP雖然在低延遲領域上有壓倒性的優勢,但是使用者側有可能存在防火牆攔截所有的UDP包。考慮到在網路環境足夠好的情況下使用TCP也能達到不錯的效果,因此我們做了一個降級策略,優先使用UDP,當且僅當UDP不通的時候使用TCP。

  2.2 丟包補償

  前面講到我們的傳輸層協議優先選擇UDP,那麼就需要引入一些機制解決丟包問題。

  前向糾錯FEC(Forward Error Correction)指的是傳送端傳送原始資料的同時附加部分冗餘的資訊,如果接收端檢測到原始資料丟失則嘗試使用冗餘的資訊進行恢復。傳送端傳送n個資料包,同時根據原始資料生成k個冗餘的資料包,將n+k個資料包傳送出去,接收端只要收到至少n個資料包就可以得到全部的原始資料。

  FEC演算法的關鍵在於異或。異或(Exclusive OR)是一個數學運算子,數學符號為“⊕”,兩邊數值轉換成二進位制按位運算,相同時為0,不同時為1。

  以一階冗餘演算法為例,n個資料包生成1個冗餘包,傳送n+1個資料包。我們傳送三個數值分別為a、b、c,生成冗餘資料x=a ⊕b ⊕ c一起傳送。假如數值b在傳輸中丟失了,計算a ⊕c ⊕ x即可得到b。

  在實際應用中,FEC沒有這麼簡單,WebRTC實現了UlpFEC和FlexFEC,UlpFEC可以針對資料包的重要程度實施不同程度的保護以充分利用頻寬,FlexFEC還支援對列做冗餘,同時WebRTC預設的音訊編碼器Opus本身就支援FEC。

  前向糾錯適合少量隨機丟包的場景,可以無視網路延遲時間,但是增加了頻寬消耗。

  後向糾錯包括ARQ(Automatic Repeat Request)和PLC(Packet Loss Concealment)。ARQ指的是接收端檢測到資料丟失的時候傳送NACK報文請求傳送端重傳,適合突發大量丟包的場景,沒有額外的頻寬消耗,但是時效性取決於RTT,如果存在很多接收端還要考慮避免NACK風暴造成雪崩。PLC用於音訊,當資料缺失時使用模型根據前後資料預測丟失的資料。

  總之,前向糾錯和後向糾錯各有優缺點,需要搭配使用。

  2.3 流量控制

  流量控制指的是根據網路狀況的波動估算可用頻寬,根據頻寬的變化自動調節音影片位元速率和傳送速率。當網路質量變差的時候迅速降低資料量以確保實時性,網路較好時則慢慢提升資料量帶來更清晰的畫面。在WebRTC中提供了優秀的Google Congestion Control演算法,包括基於延遲的評估和基於丟包率的評估,取兩種評估方式的最小值作為目標頻寬通知編碼器和資料傳送模組。

  基於延遲的評估演算法包括Transport-CC和Goog-REMB,目前最新版的WebRTC預設使用的是Transport-CC。Transport-CC在傳送端進行頻寬評估,接收端透過TransportFeedback RTCP包向傳送端反饋每個RTP包的到達時間,傳送端在一個時間視窗內計算每個RTP包到達時間與傳送時間之差,透過Trendline濾波器處理後預測網路狀況。假設我們當前處於Hold狀態,如果檢測到網路狀態為OverUse,此時應該減小資料量,變更為Decrease狀態;如果檢測到網路狀態為Normal,此時可以嘗試增加資料量,變更為Increase狀態。  

  基於丟包的評估演算法是當網路突發大量丟包時的兜底策略。如果丟包率在2%以下的時候說明網路質量好,目標頻寬增加8%;如果丟包率在在2%~10%說明當前傳送資料的頻寬和網路質量相匹配可以保持不變;如果丟包率大於10%說明網路質量差,目標頻寬減小到(1-丟包率*0.5)* 當前頻寬。  

  2.4 資料緩衝

  如果我們只考慮實時性,那麼收到資料就立刻解碼並渲染必然是好的選擇,但是網路並不穩定,延遲、亂序、丟包、重複包都有可能發生。如果採用上面的策略,音訊可能因為網路的抖動變的斷斷續續,影片可能因為丟包導致缺少參考幀從而出現黑屏或破圖,所以有必要引入一個緩衝區,增加一點可以接受的延遲來保證使用者體驗。

  在WebRTC中,影片包會被放入JitterBuffer模組進行處理,JitterBuffer會進行影片包的排序、組裝成完整的幀、確保參考幀有效,然後把資料送到解碼器。同時,根據網路狀況自適應地調節緩衝區的長度。音訊包會被放入NetEQ中,它維護了音訊的緩衝區,同時負責將音訊同步到影片。我們做播放器一般都是以音訊的時間為基準同步影片,但是WebRTC剛好相反,它是以影片為基準的。當音訊資料堆積的時候加速音訊播放,音訊資料不足的時候降低速度把音訊拉長。

  2.5 回聲消除

  在語音通話的場景中,麥克風採集到的聲音傳送給遠端,遠端的揚聲器播放出來以後又被遠端的麥克風採集到這個聲音並傳送回來,這樣講話的人會感覺到有回聲,影響體驗。

  WebRTC提供了回聲消除演算法AEC,時延估計(Delay Estimation)模組找到揚聲器訊號和麥克風訊號的時延,線性自適應濾波器(Linear Adaptive Filter)參考揚聲器訊號估算回聲訊號並將其剪去,最後透過非線性處理(Nonlinear Processing)模組消除殘留的回聲。

  2.6 優秀路徑

  實時音影片對網路的要求非常高,如果通話雙方距離很遠,那麼通話質量是很難保證的。城市A的裝置給城市D的裝置傳送資料,直接傳送未必是合適的選擇,從城市B和城市C中轉一下有可能更快。理想的解決方案是在全球部署加速節點,使用者就近接入。根據加速節點之間的實時網路質量探測資料,找到一條合適的傳輸路徑,避開網路的擁堵。  

   03 WebRTC分析

  剛才介紹了實時音影片系統實現過程中所需的關鍵技術,多次提到了WebRTC。顯然,對於絕大多數團隊來說,這些內容如果全部自主研發幾乎是不可能的事情,而我們站在WebRTC的基礎上去設計自己的系統是較為明智的選擇。WebRTC的程式碼非常複雜,想要把它搞清楚是一件非常困難的任務,我第一次看到WebRTC的程式碼根本就不知道從哪裡下手。幸運的是,WebRTC官方提供了架構圖,可以先幫助我們對它進行一個宏觀的瞭解。  

  WebRTC整體架構大概可以分為介面層、會話層、引擎層和裝置I/O層。

  介面層包括Web API和WebRTC C++ API,Web API給Web開發者提供了JavaScript介面,這樣Web端就具備了接入WebRTC的能力;WebRTC C++ API面向的是瀏覽器開發者,讓瀏覽器開發商具備整合WebRTC的能力。當然,WebRTC C++ API也可以用於Native客戶端接入。

  會話層主要包含信令相關的邏輯,比如媒體協商,P2P連線管理等。

  引擎層是WebRTC最核心的功能,包括音訊引擎、影片引擎和傳輸模組。音訊引擎包含音訊編解碼器(Opus)、NetEQ和著名的3A(回聲消除、自動增益、降噪)演算法;影片引擎包括影片編解碼器(VP8、VP9、H264)、JitterBufer和影像增強(降噪)演算法;傳輸模組包含SRTP、多路複用和P2P模組。

  裝置I/O層主要和硬體互動,包括音影片採集和渲染,以及網路I/O。

  上面一節描述的實時音影片關鍵技術中,WebRTC實現了除“優秀路徑”之外的全部內容。WebRTC幾乎每個模組都是可以按需替換的,便於我們增加定製的內容。我們可以根據實際需求決定如何使用WebRTC,Native客戶端可以透過PeerConnection介面接入,服務端拿到RTP/RTCP包以後完全可以直接呼叫引擎層處理拿到最終的YUV和PCM資料,甚至只把WebRTC內部模組摳出來用在自己的系統上也是沒問題的。

   04 系統架構

  我們回到B站的連麥PK業務場景,兩位主播進行互動PK,同時大量觀眾在直播間觀看PK的過程。

  顯然,兩位主播通話要求低延遲,必須使用實時音影片系統互動;而觀眾觀看直播的延遲要求相對沒那麼嚴格,可以採取傳統直播的模式,透過RTMP或者SRT推流到CDN,使用者從CDN拉流。然後我們要思考兩個問題:

  問題1.主播之間的音視訊通話是採用P2P還是伺服器中轉?

  首先對P2P和伺服器中轉兩種方案做個對比:  

  對於P2P方案來說,只需要部署STUN和TURN伺服器,如果成功建立P2P連線那麼後續媒體資料傳輸就不需要經過伺服器,所以具有成本優勢。然而,P2P的缺點也很明顯,如果打洞失敗還是需要TURN伺服器中轉,且建立連線的過程耗時較高,使用者之間距離較遠的情況下網路質量不可控,而且現有的第三方網路加速服務基本上都不支援P2P。

  我們這裡選擇伺服器中轉的方案,因為實時音影片本身對網路的要求比較高,不會設定過高的位元速率,所以網路傳輸的資料量是可控的,成本能夠接受。而且我們的實時音影片資料要對接AI稽核,還要實現伺服器混流,這是P2P方案做不到的。

  問題2.推送給觀眾的流到CDN,這個工作放在主播客戶端還是伺服器?

  兩位主播PK對應的是兩路流,觀眾只從CDN拉一路流,所以必須有個地方做混流。這裡的混流指的是把兩位主播的影片進行拼接、音訊進行混合,然後打包成一路流。主播客戶端能收到對方的流,可以和自己的流做混流;在前面提到的伺服器中轉方案中,伺服器有雙方的流,同樣也可以完成混流。我們先對比一下兩種方案的優劣:  

  伺服器進行混流需要先解碼再編碼,這需要消耗大量計算資源,所以成本很高;主播客戶端進行混流需要額外增加一路流的編碼和上傳,對裝置效能和上行頻寬來說也是很大的挑戰。主播客戶端需要等待伺服器把對方的流傳送過來才能混流,所以從延遲的角度來看伺服器混流稍微佔據優勢,不過這個延遲相比CDN的延遲可以忽略不計。如果後期對通話質量要求變高,主播的裝置效能和上行頻寬跟不上,我們可以很容易增加伺服器來擴充套件計算資源和頻寬,所以在可擴充套件性方面伺服器混流勝出。另外,當主播從正常直播切換到連麥PK狀態的時候,採用伺服器混流必須先把直播的流停掉再由伺服器接管,中間的時間差可能會產生卡頓或黑屏影響觀眾體驗,而主播客戶端混流可以做到無縫切換。

  所以,這兩種方案各有優缺點,我們採取折衷的辦法:如果主播的裝置負載較低且上行頻寬比較充足,優先採用主播客戶端混流的方式,否則降級為伺服器混流。

  上面兩個問題分析清楚了,就可以開始設計了,這是我們的系統整體架構:  

  rtc-service主要提供信令、頻道管理、主播管理、公有云上媒體伺服器叢集的健康檢查和節點分配、同步主播狀態到業務伺服器、記錄通話流水。

  rtc-job是對rtc-service的補充,定期檢查當前線上主播的狀態,發現主播異常下線時觸發兜底邏輯。

  rtc-router負責收發主播的音影片資料。主播可以收到同一個頻道內其他人的音影片流。如果需要伺服器混流,則訪問註冊中心並採用Google的有界負載一致性雜湊演算法(Consistent Hashing with Bounded Loads)選取rtc-mixer節點,並往對應節點推送主播的音影片流。

  rtc-mixer負責混流,根據需求拼接畫面和混音,然後推送到CDN,觀眾透過CDN拉流。

  主播的客戶端並沒有直接向rtc-router傳送資料,而是透過第三方的四層加速網路轉發。我們前面提到了“優秀路徑”的概念,第三方的四層加速服務可以讓使用者接入最近的加速節點,然後尋找合適路徑把資料轉發到我們的公有云節點。客戶端只能看到第三方的加速節點IP,看不到我們公有云媒體伺服器的IP,這在一定程度上可以防止伺服器遭到攻擊;其次,我們可以在保證異地多活的前提下讓公有云叢集相對比較集中,節省成本。

  服務的可用性和容錯性也是一個很重要的問題,假如在主播PK即將勝利的時刻服務出現故障,彈出"PK異常終止請重新再來",這很令人絕望。我們不僅要保證服務可用,還要盡最大可能保證服務出現故障時減小對使用者的影響,讓流程能夠走下去。接下來討論系統中每個風險模組為了實現這個目標所採取的措施:

  四層加速網路故障。這個屬於第三方廠商提供的服務,每個廠商提供的接入方式大同小異,基本上就是附加的header有差別,所以同時對接多家廠商對客戶端來說是很容易做到的。客戶端進行連通性檢查,只要存在至少一家廠商的服務可用,就不會影響業務。

  公有云上的rtc-router和rtc-mixer故障。在公有云上部署服務,儘量要多廠商、多區域部署,防止單機房整體當機。我們同樣準備了多個叢集,每個叢集都部署了多臺rtc-router、rtc-mixer和ZooKeeper,單個叢集可以獨立工作,如果單個叢集不可用或者負載達到上限則會被熔斷。核心機房的rtc-service會對公有云上的叢集進行健康檢查,如果rtc-router當機,rtc-service會透過信令通道通知客戶端切換到同叢集中其他伺服器,當同叢集沒有可用機器時則切換叢集。如果rtc-mixer當機,rtc-router會透過ZooKeeper重新選擇一臺接管混流任務。

  核心機房的rtc-service和rtc-job故障。這部分內容和B站大部分核心服務部署在同樣的叢集,複用了B站比較成熟的高可用架構。這部分內容可以參考其他文章,這裡不再贅述。

   05 總結

  如果我們把實時音影片技術比作一座富麗堂皇的城池,這篇文章只能帶領大家來到城門口。我們也不會停止探索的腳步。希望大家讀到這裡能夠有所收穫,如有疏漏,歡迎批評指正。

來自 “ 嗶哩嗶哩技術 ”, 原文作者:馬家憶;原文連結:http://server.it168.com/a2022/1115/6775/000006775040.shtml,如有侵權,請聯絡管理員刪除。

相關文章