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],本文只回顾其算法框架,并分析其在实际应用中存在的问题。
图1 GCC算法整体结构
GCC算法分两部分:发送端基于丢包率的码率控制和接收端基于延迟的码率控制。基于丢包率的码率控制运行在发送端,依靠RTCP RR报文进行工作。WebRTC在发送端收到来自接收端的RTCP RR报文,根据其Report Block中携带的丢包率信息,动态调整发送端码率As。基于延迟的码率控制运行在接收端,WebRTC根据数据包到达的时间延迟,通过到达时间滤波器,估算出网络延迟m(t),然后经过过载检测器判断当前网络的拥塞状况,最后在码率控制器根据规则计算出远端估计最大码率Ar。得到Ar之后,通过RTCP REMB报文返回发送端。发送端综合As、Ar和预配置的上下限,计算出最终的目标码率A,该码率会作用到Encoder、RTP和PacedSender等模块,控制发送端的码率。
发送端基于丢包率的码率估计计算公式:
图2 GCC发送端基于丢包率的码率估计
接收端基于延迟的码率估计计算公式:
图3 GCC接收端基于延迟的码率估计
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算法和GCC算法的实现和对比[8]
图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算法的异同。
表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所示。
图6 TransportSequenceNumber扩展格式
注意这里的传输层序列号,和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所示:
图7 TransportCC报文格式
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]。
图8 Sendside-BWE和GCC算法对比
该图表明,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.jianshu.com/p/bb34995c549a
[6] WebRTC基于GCC的拥塞控制(上) - 算法分析 https://www.jianshu.com/p/0f7ee0e0b3be
[7] WebRTC基于GCC的拥塞控制(下) - 实现分析 https://www.jianshu.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
作者:weizhenwei
链接:https://www.jianshu.com/p/ab32a8a3552f
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。