本文在文章[1]的基礎上,從源代碼實現(xiàn)角度對WebRTC的GCC算法進行分析。主要內容包括: RTCP RR的數(shù)據(jù)源、報文構造和接收,接收端基于數(shù)據(jù)包到達延遲的碼率估計,發(fā)送端碼率的計算以及生效于目標模塊。
擁塞控制是實時流媒體應用的重要服務質量保證。通過本文和文章[1][2],從數(shù)學基礎、算法步驟到實現(xiàn)細節(jié),對WebRTC的擁塞控制GCC算法有一個全面深入的理解,為進一步學習WebRTC奠定良好基礎。
1 GCC算法框架再學習
本節(jié)內容基本上是文章[1]第1節(jié)的復習,目的是再次復習GCC算法的主要框架,梳理其算法流程中的數(shù)據(jù)流和控制流,以此作為后續(xù)章節(jié)的行文提綱。GCC算法的數(shù)據(jù)流和控制流如圖1所示。
圖1 GCC算法數(shù)據(jù)流和控制流
對發(fā)送端來講,GCC算法主要負責兩件事:1)接收來自接收端的數(shù)據(jù)包信息反饋,包括來自RTCP RR報文的丟包率和來自RTCP REMB報文的接收端估計碼率,綜合本地的碼率配置信息,計算得到目標碼率A。2)把目標碼率A生效于目標模塊,包括PacedSender模塊,RTPSender模塊和ViEEncoder模塊等。
對于接收端來講,GCC算法主要負責兩件事:1)統(tǒng)計RTP數(shù)據(jù)包的接收信息,包括丟包數(shù)、接收RTP數(shù)據(jù)包的最高序列號等,構造RTCP RR報文,發(fā)送回發(fā)送端。2)針對每一個到達的RTP數(shù)據(jù)包,執(zhí)行基于到達時間延遲的碼率估計算法,得到接收端估計碼率,構造RTCP REMB報文,發(fā)送回發(fā)送端。
由此可見,GCC算法由發(fā)送端和接收端配合共同實現(xiàn),接收端負責碼率反饋數(shù)據(jù)的生成,發(fā)送端負責根據(jù)碼率反饋數(shù)據(jù)計算目標碼率,并生效于目標模塊。本文接下來基于本節(jié)所述的GCC算法的四項子任務,分別詳細分析之。
2 RTCP RR報文構造及收發(fā)
關于WebRTC上的RTP/RTCP協(xié)議的具體實現(xiàn)細節(jié),可參考文章[3]。本節(jié)主要從RR報文的數(shù)據(jù)流角度,對其數(shù)據(jù)源、報文構造和收發(fā)進行分析。其數(shù)據(jù)源和報文構造如圖2所示,報文接收和作用于碼率控制模塊如圖3所示。
在數(shù)據(jù)接收端,RTP數(shù)據(jù)包從Network線程到達Worker線程,經(jīng)過Call對象,VideoReceiveStream對象到達RtpStreamReceiver對象。在該對象中,主要執(zhí)行三項任務:1)接收端碼率估計;2) 轉發(fā)RTP數(shù)據(jù)包到VCM模塊;3)接收端數(shù)據(jù)統(tǒng)計。其中1)是下一節(jié)的重點,2)是RTP數(shù)據(jù)包進一步組幀和解碼的地方;3)是統(tǒng)計RTP數(shù)據(jù)包接收信息,作為RTCP RR報文和其他數(shù)據(jù)統(tǒng)計模塊的數(shù)據(jù)來源,是我們本節(jié)重點分析的部分。
在RtpStreamReceiver對象中,RTP數(shù)據(jù)包經(jīng)過解析得到頭部信息,作為輸入?yún)?shù)調用ReceiveStatistianImpl::IncomingPacket()。該函數(shù)中分別調用UpdateCounters()和NotifyRtpCallback(),前者用來更新對象內部的統(tǒng)計信息,如接收數(shù)據(jù)包計數(shù)等,后者用來更新RTP回調對象的統(tǒng)計信息,該信息用來作為getStats調用的數(shù)據(jù)源。
圖2 RTCP RR報文數(shù)據(jù)源及報文構造
RTCP發(fā)送模塊在ModuleProcess線程中工作,RTCP報文周期性發(fā)送。當線程判斷需要發(fā)送RTCP報文時,調用SendRTCP()進行發(fā)送。接下來調用PrepareReport()準備各類型RTCP報文的數(shù)據(jù)。對于我們關心的RR報文,會調用AddReportBlock()獲取數(shù)據(jù)源并構造ReportBlock對象:該函數(shù)首先通過ReceiveStatistianImpl::GetStatistics()拿到類型為RtcpStatistics的數(shù)據(jù)源,然后以此填充ReportBlock對象。GetStatistics()會調用CalculateRtcpStatistics()計算ReportBlock的每一項數(shù)據(jù),包括丟包數(shù)、接收數(shù)據(jù)包最高序列號等。ReportBlock對象會在接下來的報文構造環(huán)節(jié)通過BuildRR()進行序列化。RTCP報文進行序列化之后,交給Network線程進行網(wǎng)絡層發(fā)送。
圖3 RTCP RR報文接收及反饋
在發(fā)送端(即RTCP報文接收端),RTCP報文經(jīng)過Network線程到達Worker線程,最后到達ModuleRtpRtcpImpl模塊調用IncomingRtcpPacket()進行報文解析工作。解析完成以后,調用TriggerCallbacksFromRTCPPackets()反饋到回調模塊。在碼率估計方面,會反饋到BitrateController模塊。ReportBlock消息最終會到達BitrateControllerImpl對象,進行下一步的目標碼率確定。
至此,關于RTCP RR報文在擁塞控制中的執(zhí)行流程分析完畢。
3 接收端基于延遲的碼率估計
接收端基于數(shù)據(jù)包到達延遲的碼率估計是整個GCC算法最復雜的部分,本節(jié)在分析WebRTC代碼的基礎上,闡述該部分的實現(xiàn)細節(jié)。
接收端基于延遲碼率估計的基本思想是:RTP數(shù)據(jù)包的到達時間延遲m(i)反映網(wǎng)絡擁塞狀況。當延遲很小時,說明網(wǎng)絡擁塞不嚴重,可以適當增大目標碼率;當延遲變大時,說明網(wǎng)絡擁塞變嚴重,需要減小目標碼率;當延遲維持在一個低水平時,目標碼率維持不變。其主要由三個模塊組成:到達時間濾波器,過載檢查器和速率控制器。
在實現(xiàn)上,WebRTC定義該模塊為遠端碼率估計模塊RemoteBitrateEstimator,整個模塊的工作流程如圖4所示。需要注意的是,該模塊需要RTP報文擴展頭部abs-send-time的支持,用以記錄RTP數(shù)據(jù)包在發(fā)送端的絕對發(fā)送時間,詳細請參考文獻[4]。
圖4 GCC算法基于延遲的碼率估計
接收端收到RTP數(shù)據(jù)包后,經(jīng)過一系列調用到RtpStreamReceiver對象,由該對象調用遠端碼率估計模塊的總控對象RemoteBitrateEstimatorAbsSendTime,由該對象的總控函數(shù)IncomingPacketInfo()負責整個碼率估計流程,如圖4所示,算法從左到右依次調用子對象的功能函數(shù)。
總控函數(shù)首先調用InterArrival::ComputeDeltas()函數(shù),用以計算相鄰數(shù)據(jù)包組的到達時間相對延遲,該部分對應文章[1]的3.1節(jié)內容。在計算到達時間相對延遲時,用到了RTP報文頭部擴展abs-send-time。另外,實現(xiàn)細節(jié)上要注意數(shù)據(jù)包組的劃分,以及對亂序和突發(fā)時間的處理。
接下來算法調用OveruseEstimator::Update()函數(shù),用以估計數(shù)據(jù)包的網(wǎng)絡延遲,該部分對應文章[1]的3.2節(jié)內容。對網(wǎng)絡延遲的估計用到了Kalman濾波,算法的具體細節(jié)請參考文章[2]。Kalman濾波的結果為網(wǎng)絡延遲m(i),作為下一階段網(wǎng)絡狀態(tài)檢測的輸入?yún)?shù)。
算法接著調用OveruseDetector::Detect(),用來檢測當前網(wǎng)絡的擁塞狀況,該部分對應文章[1]的3.2節(jié)內容。網(wǎng)絡狀態(tài)檢測用當前網(wǎng)絡延遲m(i)和閾值gamma_1進行比較,判斷出overuse,underuse和normal三種網(wǎng)絡狀態(tài)之一。在算法細節(jié)上,要注意overuse的判定相對復雜一些:當m(i) > gamma_1時,計算處于當前狀態(tài)的持續(xù)時間t(ou),如果t(ou) > gamma_2,并且m(i) > m(i-1),則發(fā)出網(wǎng)絡過載信號Overuse。如果m(i)小于m(i-1),即使高于閥值gamma_1也不需要發(fā)出過載信號。在判定網(wǎng)絡擁塞狀態(tài)之后,還要調用UpdateThreshold()更新閾值gamma_1。
算法接著調用AimdRateControl::Update()和UpdateBandwidthEstimate()函數(shù),用以估計當前網(wǎng)絡狀態(tài)下的目標碼率Ar,該部分對應文章[1]的3.3節(jié)。算法基于當前網(wǎng)絡狀態(tài)和碼率變化趨勢有限狀態(tài)機,采用AIMD(Additive Increase Multiplicative Decrease)方法計算目標碼率,具體計算公式請參考文章[1]。需要注意的是,當算法處于開始階段時,會采用Multiplicative Increase方法快速增加碼率,以加快碼率估計速度。
此時,我們已經(jīng)拿到接收端估計的目標碼率Ar。接下來以Ar為參數(shù)調用VieRemb對象的OnReceiveBitrateChange()函數(shù),發(fā)送REMB報文到發(fā)送端。REMB報文會推送到RTCP模塊,并設置REMB報文發(fā)送時間為立即發(fā)送。關于REMB報文接下來的發(fā)送和接收流程,和第1節(jié)描述的RTCP報文一般處理流程是一樣的,即經(jīng)過序列化發(fā)送到網(wǎng)絡,然后發(fā)送端收到以后,反序列化出描述結構,最后通過回調函數(shù)到達發(fā)送端碼率控制模塊BitrateControllerImpl。
至此,接收端基于延遲的碼率估計過程描述完畢。
4 發(fā)送端碼率計算及生效
在發(fā)送端,目標碼率計算和生效是異步進行的,即Worker線程從RTCP接收模塊經(jīng)回調函數(shù)拿到丟包率和REMB碼率之后,計算得到目標碼率A;然后ModuleProcess線程異步把目標碼率A生效到目標模塊如PacedSender和ViEEncoder等。下面分別描述碼率計算和生效過程。
圖5 發(fā)送端碼率計算過程
碼率計算過程如圖5所示:Worker線程從RTCPReceiver模塊經(jīng)過回調函數(shù)拿到RTCP RR報文和REMB報文的數(shù)據(jù),到達BitrateController模塊。RR報文中的丟包率會進入Update()函數(shù)中計算碼率,碼率計算公式如文章[1]第2節(jié)所述。然后算法流程進入CapBitrateToThreshold()函數(shù),和配置的最大最小碼率和遠端估計碼率進行比較后,確定最終目標碼率。而REMB報文的接收端估計碼率Ar則直接進入CapBitrateToThreshold()函數(shù)參與目標碼率的確定。目標碼率由文章[1]的3.4節(jié)所示公式進行確定。需要注意的是,RR報文和REMB報文一般不在同一個RTCP報文里。
圖6 發(fā)送端碼率生效過程
發(fā)送端碼率生效過程如圖6所示:ModuleProcess線程調用擁塞控制總控對象CongestionController周期性從碼率控制模塊BitrateControllerImpl中獲取當前最新目標碼率A,然后判斷目標碼率是否有變化。若是,則把最新目標碼率設置到相關模塊中,主要包括PacedSender模塊,RTPSender模塊和ViEEncoder模塊。
對于PacedSender模塊,設置碼率主要是為了平滑RTP數(shù)據(jù)包的發(fā)送速率,盡量避免數(shù)據(jù)包Burst造成碼率波動。對于RTPSender模塊,設置碼率是為了給NACK模塊預留碼率,如果預留碼率過小,則在某些情況下對于NACK報文請求選擇不響應。對于ViEEncoder模塊,設置碼率有兩個用途:1)控制發(fā)送端丟幀策略,根據(jù)設定碼率和漏桶算法決定是否丟棄當前幀。2)控制編碼器內部碼率控制,設定碼率作為參數(shù)傳輸?shù)骄幋a器內部,參與內部碼率控制過程。
至此,發(fā)送端碼率計算和生效過程分析完畢。
5 總結
本文結合文章[1],深入WebRTC代碼內部,詳細分析了WebRTC的GCC算法的實現(xiàn)細節(jié)。通過本文,對WebRTC的代碼結構和擁塞控制實現(xiàn)細節(jié)有了更深層次的理解,為進一步學習WebRTC奠定良好基礎。
[1]
WebRTC基于GCC的擁塞控制(上) – 算法分析
http://www.jianshu.com/p/0f7ee0e0b3be
[2] WebRTC視頻接收緩沖區(qū)基于KalmanFilter的延遲模型.
http://www.jianshu.com/p/bb34995c549a
[3] WebRTC中RTP/RTCP協(xié)議實現(xiàn)分析
http://www.jianshu.com/p/c84be6f3ddf3
[4] abs-send-time.?https://webrtc.org/experiments/rtp-hdrext/abs-send-time/