1引言
眾所周知,WebRTC的擁塞控制和碼率估計算法采用GCC算法[1]。該算法充分考慮了網絡丟包和網絡延遲對碼率估計的不同影響,分別基于丟包率和網絡延遲進行碼率估計,最后綜合這另種碼率得出最優值。在算法實現上,基于丟包率的碼率估計在發送端進行,基于網絡延遲的碼率估計在接收端進行。最后在發送端計算出最優值,作用于Codec和PacedSender模塊。GCC算法能夠較好地基于網絡實時狀況估計網絡帶寬,為網絡實時通信應用打下堅實基礎[2][3][4]。
然而,隨著時間推移,在實際測試中發現GCC算法逐漸顯出一些弊端,比如不能適應所有網絡模型,應對網絡峰值能力差,等等。為此,Google官方從M55版本引進最新的擁塞控制算法Sendside-BWE,把所有碼率計算模塊都移到發送端進行,并采用全新的Trendline濾波器取代之前的Kalman濾波器[5]。實測表明,新的算法實現能夠更好更快地進行碼率估計和網絡過載恢復。
本文基于WebRTC的M66版本和相關RFC,深度分析學習最新Sendside-BWE算法的實現。
2 GCC算法回顧
關于GCC算法已經有很多分析和論述[6][7],本文只回顧其算法框架,并分析其在實際應用中存在的問題。
GCC算法分兩部分:發送端基于丟包率的碼率控制和接收端基于延遲的碼率控制。基于丟包率的碼率控制運行在發送端,依靠RTCP RR報文進行工作。WebRTC在發送端收到來自接收端的RTCP RR報文,根據其Report Block中攜帶的丟包率信息,動態調整發送端碼率As。基于延遲的碼率控制運行在接收端,WebRTC根據數據包到達的時間延遲,通過到達時間濾波器,估算出網絡延遲m(t),然后經過過載檢測器判斷當前網絡的擁塞狀況,最后在碼率控制器根據規則計算出遠端估計最大碼率Ar。得到Ar之后,通過RTCP REMB報文返回發送端。發送端綜合As、Ar和預配置的上下限,計算出最終的目標碼率A,該碼率會作用到Encoder、RTP和PacedSender等模塊,控制發送端的碼率。
發送端基于丟包率的碼率估計計算公式:
接收端基于延遲的碼率估計計算公式:
GCC算法充分考慮丟包率和延遲對碼率的影響,在實時通訊應用(如視頻會議)中能夠發揮良好效果。然而,在某些特定應用場景下(比如實時在線編輯),GCC算法的表現不太讓人滿意,主要體現在它應對峰值流量的能力上,具體表現在:1)算法一開始基于Increase狀態增加碼率,當檢測到Decrease狀態時調用Ar[t(i)] = Alpha * Rr[t(i)],這個時候實時碼率Rr(ti)可能遠小于Ar[t(i-1)],這樣在后續過程中Ar處于較低水平;此時若有視頻關鍵幀沖擊,則數據包大量在PacedSender的隊列中排隊,造成較大排隊延遲。2)基于1)中論述的情況,碼率估計模塊反饋給Codec的編碼碼率很低,但編碼器需要編碼關鍵幀時,內部的碼率控制模塊控制出的最小碼率仍然大于反饋碼率。這兩種情況都會造成較大的發送端排隊延遲,進而在接收端造成較大的JitterBuffer延遲,最終導致端到端延遲到達500ms的水平,這在實時在線編輯應用中是無法容忍的。
基于此,Google官方從WebRTC M55開始引入新的碼率估計算法,把所有碼率計算模塊都移動到發送端,并采用全新的Trendline濾波器,基于碼率探測機制快速準確地估計出實時碼率。
3 Sendside-BWE算法框架
從本節開始系統分析Sendside-BWE算法的框架和實現,圖4顯示該算法的基本實現框架,以及和GCC算法的對比。
圖4中棕色線是Sendside-BWE算法的數據控制流回路:發送端在發送RTP數據包時,在RTP頭部擴展中設置傳輸層序列號TransportSequenceNumber;數據包到達接收端后記錄該序列號和包到達時間,然后接收端基于此構造TransportCC報文返回到發送端;發送端解析該報文,并執行Sendside-BWE算法,計算得到基于延遲的碼率Ar;最終Ar和基于丟包率的碼率As進行比較得到最終目標碼率,作用到PacedSender和Codec模塊,形成一個完整的反饋回路。圖4中紅色線是GCC算法的數據控制流回路:發送端在發送RTP數據包時,在RTP頭部擴展中設置絕對發送時間AbsSendTime;數據包到達接收端后記錄該絕對到達時間,然后基于此執行GCC算法得到Ar,最后構造REMB報文把Ar發送回發送端;發送端基于Ar和As得到最終目標碼率,作用到PacedSender和Codec模塊,形成一個完整的反饋回路。
從中可以看出,Sendside-BWE算法充分復用GCC算法的框架和實現,整個反饋回路基本類似:發送端在RTP頭部擴展中記錄碼率估計元數據,碼率估計模塊基于此元數據估計出碼率As,在發送端基于丟包率計算Ar,發送端綜合As和Ar得到最終目標碼率,并作用于Codec和PacedSender模塊。所不同的是:對于GCC算法,RTP報文頭部添加AbsSendTime擴展,在接收端執行基于延遲的碼率估計,網絡延遲濾波器采用Kalman Filter,返回給發送端的是REMB報文;對于Sendside-BWE算法,RTP報文頭部添加TransportSequenceNumber擴展,在發送端執行基于延遲的碼率估計,網絡延遲濾波器采用Trandline,返回給發送端的是TransportCC報文。表5總結出GCC算法和Sendside-BWE算法的異同。
需要注意的是,從WebRTC M55開始啟用Sendside-BWE后,其GCC算法就只做前向兼容而沒有進一步的功能開發、性能優化和bug修正。因此,GCC是過去,Sendside-BWE是未來。
4 Sendside-BWE算法實現
本節論述Sendside-BWE算法的實現細節,在論述上力求不過度注釋代碼,以免陷入細節無法自拔。本節按照如下順序論述Sendside-BWE的實現細節:SDP協商,發送端發送RTP報文,接收端接受RTP報文及信息存儲,接收端構造TransportCC報文,發送端解析TransportCC報文,發送端碼率估計。
4.1 Sendside-BWE在SDP層協商
TransportCC和remb一樣都是Codec的feedback params的一部分。為支持TransportCC,Codec在收集feedback params時需添加額外一條:
codec->AddFeedbackParam(FeedbackParam(kRtcpFbParamTransportCc, kParamValueEmpty));
其中,TransportCC在最終生成的SDP中體現為如下一條attribute:
a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
然后,SDP協商過程按照常規操作進行。
4.2 發送端發送RTP報文
發送端在發送RTP報文時,需要在RTP頭部添加新的擴展TransportSequenceNumber,該擴展格式如圖6所示。
注意這里的傳輸層序列號,和RTP報文格式中的媒體層序列號不是同一個東西。傳輸層序列號關注數據的傳輸特性,主要作用是碼率估計;媒體曾序列號關注數據的媒體特性,主要作用是組幀和抗丟包。它們的初始值不一樣,賦值點也不一樣,其中媒體層序列號在RTPSender::AssignSequenceNumber()處賦值,而傳輸層序列號在RTPSender::UpdateTransportSequenceNumber()處賦值。RTP報文在發送時構造該頭部擴展的函數調用棧如下:
=> RTPSender::TimeToSendPacket();
?=> RTPSender::PrepareAndSendPacket();
??=> RTPSender::UpdateTransportSequenceNumber();
???=> RTPSender::AddPacketToTransportFeedback();s
????=> SendSideCongestionController::AddPacket();
然后RTP報文走正常的構造發送路徑發送到網絡。
4.3 接收端接收RTP并構造TransportCC報文
接收端worker線程在收到RTP報文后,解析并檢查其頭部擴展,根據其是否有TransportSN擴展,決定采用Sendside-BWE還是GCC,注意這兩種擁塞控制器是互斥的。對于Sendside-BWE,接收端代理解析擴展拿到傳輸層序列號,并記錄RTP報文的到達時間,構造(transport-sn,arrival_time_ms)鍵值對,存儲在隊列中。整個過程的函數調用棧如下:
=> Call::DeliverRtp();
?=> NotifyBweOfReceivedPacket();
?=> RtpPacketReceived::GetHeader();
?=> ReceiveSideCongestionController::OnReceivedPacket();
??=> RemoteEstimatorProxy::IncomingPacket();
???=> OnPacketArrival(transport-sn, arrival_time_ms):
??????Packet_arrival_times_[seq] = arrival_time;
RemoteEstimatorProxy作為Sendside-BWE在接收端的代理,其實現遵從WebRTC的模塊機制,在Process線程以100ms為發送周期發送TransportCC報文[9],發送周期會根據當前碼率動態調整,其取值范圍在[50ms, 250ms]之間,其本身可用的發送碼率為當前可用碼率的5%。TransportFeedback報文是一種RTP 傳輸層feedback報文(pt=205),FMT為15。其格式如圖7所示:
TransportCC報文采用base + bitmap的思想,其各個字段的具體解釋請參考文獻[9],一個TransportCC報文能最多攜帶16個RTP報文的有效信息,每個RTP報文信息包括其傳輸層序列號和包到達時間。TransportCC報文在發送端構造和發送的函數調用棧如下:
=> RemoteEstimatorProxy::Process();
?=> RemoteEstimatorProxy::BuildFeedbackPacket();
??=> TransportFeedback::AddReceivedPacket()
???=> PacketRouter::SendTransportFeedback(fbpacket);
????=> RTCPSender::SendFeedbackPacket(fbpacket);
然后按照常規RTCP報文流程發送到發送端。
4.4 發送端接收TransportCC報文并解析
接收端接收操作就是常規的RTCP接收、解析并回調的流程,在worker線程中:
=> WebRtcVideoChannel::OnRtcpReceived();
?=> Call::DeliverRtcp();
??=> RTCPReceiver::HandleTransportFeedback();
???=> RTCPReceiver::TriggerCallbacksFromRtcpPacket();
????=> TransportFeedbackObserver::OnTransportFeedback();
?????=> SendSideCongestionController::OnTransportFeedback();
然后就是Send-side BWE算法在發送端的核心實現。
4.5 SendSideCongestionController碼率估計
SendSideCongestionController是Sendside-BWE算法在發送端的核心實現,關于其的分析全部是細節描述。本節限于篇幅,僅勾勒出其大致的函數調用棧和流程說明。
?=> SendSideCongestionController::OnTransportFeedback();
?=> AcknowledBitrateEstimator::IncomingPacketFeedbackVector();
?=> DelayBasedBwe::IncomingPacketFeedbackVector();
?=> BitrateControllerImpl::OnDelayBasedBweResult();
?=> SendSideCongestionController::MaybeTriggerOnNetworkChanged();
?=> ProbeController::RequestProbe();
在SendSideCongestionController的OnTransportFeedback()函數中,首先調用ALR碼率估計器得到一個實時碼率,然后以此為參數調用DelayBasedBwe計算得到最新的估計碼率Ar,把Ar經過BitrateController對象和As綜合,得到最新的目標碼率。最后通過函數MaybeTriggerOnNetworkChanged()把最新目標碼率作用到Codec和PacedSender模塊。如果本次碼率估計從網絡過載中恢復,則調用ProbeController對象發起下一次碼率探測。
DelayBasedBwe對象是真正實現碼率估計的地方,其內部調用函數棧如下:
=> DelayBasedBwe::IncomingPacketFeedbackVector();
=> DelayBasedBwe::IncomingPacketFeedback();
?=> InterArrival::ComputeDeltas();
?=> TrendlineEstimator::Update();
=> DelayBasedBwe::MaybeUpdateEstimate();
?=> ProbeBitrateEstimator::FetchAndResetLastEstimatedBitrateBps();
?=> AimdRateControl::SetEstimator();
DelayBasedBwe首先調用IncomingPacketFeedback針對每個RTP報文信息進行碼率估計,其內部邏輯和GCC算法的相關步驟一致,在此不再贅述。需要注意的是,其內部網絡延遲濾波器采用TrendlineEstimator。然后DelayBasedBwe調用MaybeUpdateEstimate()根據本次判定的網絡狀態計算得到最終的Ar,如GCC算法一樣。
Trendline濾波器的原理就是最小二乘法線性回歸求得網絡延遲波動的斜率,每個散列點表示為(arrival_time, smoothed_delay),其中arrival_time為RTP包到達時間,smoothed_delay為平滑后的發送接收相對延遲。最后根據當前散列點集合,采用最小二乘法線性回歸計算得到本次估計的網絡延遲m(i)。其計算過程調用如下:
=> TrendlineEstimator::Update();
?=> LinearFitSlope();
?=> TrendlineEstimator::Detect();
??=> TrendlineEstimator::UpdateThreshold();
至此,關于Sendside-BWE算法的實現初步分析完畢。
5 Sendside-BWE實測數據
實際測試表明,和GCC算法相比,Sendside-BWE算法在快速碼率估計、碼率估計準確性、抗網絡抖動等方面都具有非常大改善。由于保密原因,此處不能發布內部測試數據,僅貼出一組公開渠道獲得的快速碼率估計比較圖[10]。
該圖表明,Sendside-BWE算法能夠在第一次TransportCC報文返回時即估計出實時網絡帶寬,相比GCC算法更快速更準確。
6 總結
本文在總結對比GCC和Sendside-BWE算法基礎上,深入學習Sendside-BWE算法的框架和實現細節,為進一步學習WebRTC擁塞控制算法和優化算法細節打下堅實基礎。
參考文獻
[1] A Google Congestion Control Algorithm for Real-Time Communication. draft-alvestrand-rmcat-congestion-03
[2] Understanding the Dynamic Behaviour of the Google Congestion Control for RTCWeb.
[3] Experimental Investigation of the Google Congestion Control for Real-Time Flows.
[4] Analysis and Design of the Google Congestion Control for Web Real-time Communication (WebRTC). MMSys’16, May 10-13, 2016, Klagenfurt, Austria
[5] WebRTC視頻接收緩沖區基于KalmanFilter的延遲模型.http://www.lxweimin.com/p/bb34995c549a
[6] WebRTC基于GCC的擁塞控制(上) - 算法分析 http://www.lxweimin.com/p/0f7ee0e0b3be
[7] WebRTC基于GCC的擁塞控制(下) - 實現分析 http://www.lxweimin.com/p/5259a8659112
[8] WebRTC的擁塞控制和帶寬策略 https://mp.weixin.qq.com/s/Ej63-FTe5-2pkxyXoXBUTw
[9] RTP Extensions for Transport-wide Congestion Control
draft-holmer-rmcat-transport-wide-cc-extensions-01
[10] Bandwidth Estimation in WebRTC (and the new Sender Side BWE) http://www.rtcbits.com/2017/01/bandwidth-estimation-in-webrtc-and-new.html