WebRTC自适应控制算法

该文章系总结性文章,主要参考一下文章:
1、webrtc视频jitterbuffer原理机制(描述版) - 简书
http://www.jianshu.com/p/bd10d60cebcd
2、WebRTC视频接收缓冲区基于KalmanFilter的延迟模型 - 简书
http://www.jianshu.com/p/bb34995c549a
3、WebRTC基于GCC的拥塞控制(上) - 算法分析 - 简书
http://www.jianshu.com/p/0f7ee0e0b3be

此三篇文章共同构成WebRTC带宽自适应的算法体系(以下简称自适应算法)!之所以作总结,是因为这三篇文章针对不同的侧重点进行叙述,而又互相影响,很容易搞混这里面的相关概念、作用、关系是什么。本篇文章通过把这三篇文章做一个串讲,并加上自己的理解,进而还原一个更加清晰的完整的算法流程,而不是针对某一个点来理解。

开始之前,首先要了解RTP/RTCP协议,简答来说就是,TCP有丢包重传机制保证包的的可靠性,但是UDP没有,于是引入RTP/RTCP,通过把UDP封装成RTP包,添加各种其他信息,由RTCP的来控制RTP包的乱序、丢包、抖动等等,其实是TCP的会话的“中庸版本”,为的就是在保证一定延时的情况下,增加数据包的可靠性!具体细节请参考其他文章,本文不做详述!

好,开始进入正题!

一、
首先要理解自适应算法的目的是什么?简单来讲就是“拥塞控制”!
但是具体点呢?优化延时、丢包、抖动的平衡!
再具体呢?调整发端速率、修正接收缓冲

其中优化丢包策略包括:
1、通过降低码率,缓解拥塞来实现;
2、发起重传来解决!——>将增大延时
3、增大接收等待延时!——>将增大延时
而优化延策略包括:
1、降低码率,缓解拥塞;
最后优化抖动的策略包括
1、降低码率,缓解拥塞!
2、为了对抗已经产生的延时抖动带来的影响,需要增加接收缓冲区。

我们可以到:优化丢包和抖动是在牺牲延时的基础上进行的!

二、
了解了目的,我们开始讲解实际优化的方法!

对于码率:
GCC算法使用发端基于丢包的算法来计算当前带宽,在接收端基于排队延时的算法来计算当前呆卵。然后接收端把自己估计的带宽返回给发端,发端通过比较来选取合适的算法;
对于抖动:
基于排队延时等参数(后续详述)计算抖动时延,从而确认接收缓冲区大小和等待渲染时间,以消除抖动带来的影响!

所以收端其实做了两件事:带宽估计、抖动优化!其中带宽估计本身就优化了发送码率,降低下一时刻的网络抖动,而抖动优化是为了对上一时刻受抖动影响的数据包进行优化,使其平稳渲染!

那么怎么计算带宽和抖动延时呢?
这里面的算法的有两层:一层是谷歌自己提出的计算公式;另一层是卡尔曼滤波!

首先谷歌建立了一个模型,认为两帧之间的延迟具有下列关系:

/////////////////////////////////////////////////////////////////
d(i) = t(i) – t(i-1) – (T(i) – T(i-1)) 
     = [t(i) - T(i)] - [t(i-1) - T(i-1)]
     =FrameSize(i)/c(i) - FrameSize(i-1)/c(i-1)
/////////////////////////////////////////////////////////////////

T(i)是第i个数据包组中第一个数据包的发送时间,t(i)是第i个数据包组中最后一个数据包的到达时间,它们的差就是从发端开始到收端接收完需要的时间,正好等于(数据包长度 除以 信道速率)——联想火车过桥的数学问题就是这样计算的;于是问题就来了,信道长度和信道速率是个时变的函数,即每时每刻都不是一个定值,这个时候就用到随机过程了。此时我们假设链路长度和速率在i时刻和i-1时刻是相等的,那么:

/////////////////////////////////////////////////////////////
           = [FrameSize(i)- FrameSize(i-1)]/C + w(i)
          = deltaFSBytes/C + m(i) + v(i)
/////////////////////////////////////////////////////////////

其中w(i)就是为了修正我们的假设导致的不正确而引入的白色高斯过程,也就是由于链路状态不稳定带来的随机延时:这里我们认为,白色高斯过程由两部分组成:排队时延m(i)+白色噪声v(i)(0均值噪声)

这里我们就看到了一个很重要的参数:排队时延!基于延时的带宽估计就根据这个值来算出来的。而这个值得计算过程就是卡尔曼滤波计算出来的——卡尔曼滤波请移步相关参考文档!

这里面通过卡尔曼滤波计算出来的m(i)结合阈值γ等参数来确定当前网络的三种状态:过载、空闲、保持,从而调整速率!——具体调整算法移步相关参考文档!

但是,卡尔曼滤波计算出来的值、以及计算的带宽值不仅仅用在这里,还用在抖动延时的优化计算中了!也就是JitterDelay的计算,在GCC算法中,JitterDelay由两部分延迟造成:传输大帧引起的延迟和网络噪声引起的延迟:

///////////////////////////////////////////////////////////////
JitterDelay =  (MaxFSAvgFS)/c + noise
            =    (MaxFSAvgFS)/c + [noiseStdDevs * sqrt(varNoise) – noiseStdDevOffset]
//////////////////////////////////////////////////////////////

C已经由上述带宽估计算法估算出来,MaxFS是自会话开始以来所收到的最大帧大小,AvgFS表示平均帧大小
noiseStdDevs表示噪声系数2.33,varNoise表示噪声方差,noiseStdDevOffset是噪声扣除常数30。
所以,我们求JitterDelay实际上即使求noise——>实际上就是求varNoise!

///////////////////////////////////////////////////////////////
varNoise = alpha*varNoise + (1 – alpha)*(residual – avgNoise)^2 
residual = d – (deltaFSBytes/c + m) 
avgNoise = alpha*avgNoise + (1-alpha)*residual
alpha = pow(399/400, 30/fps)
////////////////////////////////////////////////////////////////

其中alpha表示概率系数,受帧率fps影响:当fps变低时,alpha会变小,表示当前噪声变大,噪声方差受当前噪声影响更大一些。实时帧率越接近30 fps越好。residual计算延迟残差(反映网络噪声的大小),avgNoise表示自开始以来的平均噪声。deltaFS表示帧间大小差值,c表示信道传输速率,m表示网络排队延迟。( d – (c * deltaFSBytes + m)就是上文的 d(i) = deltaFSBytes/C + m(i) + v(i)引入的),通过上述计算,我们更新了“当前”JitterDelay,从而调整渲染等待时间、缓冲区大小,进而消除抖动带来的不平滑渲染,但这是一牺牲延时为代价的!——具体调整方法请移步参考文档!

所以可以看出,卡尔曼滤波是极其重要的一环:一方面计算出来的m(i)值(排队时延)用于调整发送速率c(i);另一方面要通过排队时延m(i),和由排队时延计算出来的发送速率c(i)计算JitterDelay,来调整等待渲染后时间!

再次注意!!!!!本文为了减少不必要的重复性,卡尔曼滤波、带宽估计(包括发端和收端)、Jitterbuffer等知识都没有叙述!本文只是搭建了这几个重要算法的桥梁联系作用,用于读者明白这几个算法之间是如何相互影响的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章