本文来自即构科技技术副总裁冼牛在LiveVideoStackCon 2019上海大会的演讲,详细介绍了即构科技在实时流媒体传输协议选择,抖动处理,拥塞控制等多方面的实践。
文 / 冼牛
整理 / LiveVideoStack
大家好,我是冼牛,此次分享和实时音视频、流媒传输的思考与实践有关。主要会为大家介绍实时音视频的超低延迟架构并简要阐述实时视频调度系统,同时结合拥塞控制策略和信道纠错策略,进一步探索解决网络延迟的最优方法。
分享内容包括四个方面,第一方面简要说明一下低延迟的架构,第二是关于实时视频调度系统方面的问题,第三和第四点是拥塞控制和信道纠错方面对于解决延迟重要性的问题。
在讨论实时语音和视频通讯的问题时,怎样做到低延迟便是其中不可回避的问题之一。延迟的产生包括网络传输,网络渠道不良的原因,同时也不能忽略整个视频传输的链条包括采集,前处理,编码,推流,拉流,解码和渲染,这其中各个环节出现问题都有可能带来延迟的增加。
实时架构可以简单的分为两个方面。其中负载均衡、就“近”接入、质量评估和动态路由这四点属于调度方面的问题,算法流控属于流媒体传输方面的问题。前四点又可以划分为两个方面,第一个方面,负载均衡和就“近”接入属于第一公里和最后一公里的处理,质量评估和动态路由属于网络调度与路由。
负载均衡指的是对包括网络边缘节点和传输终节点的容量管理,确保每一个节点的容量不会出现空置或被挤爆。就“近”接入指的是网络逻辑上的就近接入,我们可以从两个纬度来考虑,一个是确保用户接入了同一个运营商的网络,另外要确保用户接入的地域。
质量评估是一种事后的做法,根据网络长期运营下来的数据积累作为策略来指导写入的路由。如果这些基建综合起来还是不能够实现良好的用户体验时,我们就会在上行的推流和下行的拉流做一些算法上的流控。
流媒体传输之中包含调度系统与传输算法,那么两者相比谁更加重要?简单的说调度系统所做的事情就是告诉每一个节点传输的方向。在实时网络传输中,调度系统相当于一个Leader,能够指导每一个节点往哪去传,该怎么去传,采取什么样的策略。调度系统与传输算法两者之间的关系我通过下图来做一个简单的比喻。
调度系统能够充分利用基建资源,在优质的基础条件下保证相对较好的传输质量,从而提供更好的用户体验,此时对于算法的要求就没有那么苛刻。但在实际情况下,网络资源并不能够实现良好的传输质量,因此用户体验也无法保证,那么在这种情况下传输算法就变得尤为重要。
实时调度系统中需要关注的有几个方面。上行的推流,其中比较重要的两个部分之前已经提到过,即就近接入与负载均衡。同时在上行推流时我们还会做一些码率自适应的工作,在 “最后一公里”,上行拉流的时候则会进行分层编码的工作。传输的过程分为两层,调度中心会帮助并指导节点进行动态回源;服务节点,负责节点跟节点之间的转码工作。
调度的模式有两种。一种是单节点调度模式。一种是多节点调度模式。
单节点的调度模式是成本优先的,在国内一般采取的都是单点的调度模式,而在跨国的环境中才会考虑用到多点的调度模式。
在讨论拥塞控制策略之前,首先介绍下什么叫做“拥塞”?下面以漏斗的模型为例来进行说明。我们可以将漏斗看作信道,如果漏斗中堆积的液体越来越多,我们就认为它出现了拥塞。如果说液体可以稳定的从漏斗中穿过,漏斗内没有任何液体堆积的现象发生,保持一种稳定、平衡的状态,那么我们认为它现在是正常状态。如果漏斗中的液体没有满也没有增加,或者说漏斗中的液体变得越来越少则表明处于空载状态。拥塞控制被定义了三个状态,分别是:Normal,Underuse,Overuse。其中Underuse并不表示目前的状况是良性的,它往往表示拥塞发生以后的恢复阶段,处于一种不稳定阶段。此时的码率肯定要被提高,但提高的具体数值不能确定,所以应适当采取一些保守的策略。
评估拥塞的方法有两种,一种是基于丢包率的拥塞控制,一种是基于延迟的丢包控制。通过这两种方法进行评估,可以清楚地判断当前网络状况处于Underuse还是Overuse的状态。
在做控制拥塞时有三件事情要处理,第一要发现拥塞,发现拥塞以后去解决拥塞,解决拥塞以后把梳理给调上来。那么就说呢,在实际网络里如果发现丢包的情况出现,其实拥塞已经发生了,此时再去执行解决办法已经为时过晚,所以往往会倾向于通过观察排队延迟来发现拥塞。发现拥塞以后有两个处理方法,一个方法是扩大间距,保证安全距离。第二种方法是降低码率。
最后一个环节在于拥塞缓解之后,对Underuse如何处理。首先当务之急是提高码率恢复清晰度,但调节的速率问题也是需要考虑的。
拥塞控制的方法有两种,一种是基于丢包的拥塞控制,一种是基于排队延迟的拥塞控制,这两种方法孰优孰劣很难比较,一般都是采用两种方法相结合的形式。
基于丢包的拥塞控制的优势是比较简单,只需要知道丢包率,就可以根据丢包率确认码率调整的方向与大小;劣势就是当我们发现丢包的时候,拥塞已经发生,在此基础上进行控制为时已晚。
而基于延迟的拥塞控制,排队延迟本质上就是抖动,是拥塞的一种征兆。这种方法的劣势之一是比较复杂,必须经过很多步骤才能做出判断,在此之前需要从接收端评估出抖动的程度,确定当前网络的状态,拥塞、Underuse还是Overuse,然后根据情况评估如何调整码率,整体来说是比较复杂的。
第二个缺点,抖动本质上就是路由器上排队延迟队列的长短变化。基于延迟的拥塞控制存在的缺点很明显,以上图为例如果允许的抖动缓冲队列的长度很长,很容易就能看出拥塞正在发生。但如果长度很短,不管有没有拥塞都很难看的出来。但如果队列很长,那么相对应的延迟也会很高
接下来详细介绍如何控制拥塞。如上图,左边是发送端,右边是接收端。在这里需要解决两个问题,在哪里做评估?在哪里做控制?评估时需要得到两方面的信息,一个是丢包率,另外一个是抖动情况,也就是排队延迟的情况。做评估最适合的地方是接收端,接收端的缓冲队列里面可以获取这些信息。而发送端则比较适合做控制,因为最终码率大小的控制权在编码器,评估出的目标码率要交由编码器来进行编码。
基于延迟的Congestion Control以及基于丢包的拥塞控制这两种方法。通过逆向思维的方式进行推理,拥塞控制的最终目标都是要得到一个目标码率(在拥塞控制发生的时候,码率需要调到什么样的数值),在得到码率之后就要考虑用什么样的速率调整到目标码率。 而调整码率的方式需要评估目前网络的状况再做决定,这是第三步。第二步是如何评估负载状态,是按照Underuse、Normal还是Overuse,怎样利用排队的延迟等一系列数值来做评估。这个评估是基于排队延迟的,所以第一步就要监控排队延迟,获得一系列排队延迟的时间,然后去评估它处于什么样的状态。
接下来详细介绍拥塞控制的四个步骤。第一步,监控排队延迟,如上图左边是发送段,右边是接收端,发送端的大T是发送的时间,接收端的小t是包到达的时间,可以看大T的Ti-T(i-1),这是两个包发送的时间差,T1是到达的时间差。如果用下面小T的差减去大T的差可以得到排队延迟的差,这是观察得到的数据,做数据分析的时候需要考虑观察到的东西永远和实际客观的东西是有差别的,一方面得到的这些排队,抖动里面排队的延迟存在异常值,另外一方面,发送的每个帧长度不一定是一样的,要做到消除帧的差别。最后网络本身它是有噪音的,需要通过这一系列的去除以后才能得到比较接近网络排队延迟的抖动数值。
以上公式就表示接收端的包到达两个帧之间的时间差,减去发送端的包发送出去的时间差的差,这是观察到的数据,数据里面是有正常的差和网络噪音带来的异常值,可以通过kalman滤过器去掉异常的值,kalman滤过器它能做的事情就是把M一系列的值里面的一些比较异常的值抹掉,留下其他正常的值。那么正常的DL(i)除去C(i)得到是正常之间的差异所带来的时间的差异。最终把这两个公式结合,就可以得到一系列的在路由器这一侧排队的延迟,最终的数据也只是接近结果,永远不是真实的数值。
第一步最终得到结果是一系列M的值,M1,M2,M3,M4在路由器排队的时间的延迟,根据这些时间延迟去判断它处于Underuse,Normal还是Overuse状态。一般的做法是,如果排队的延迟超过了一个域值,就判断它是Overuse状态,如果是低于某个域值的话就是Underuse状态,处于某个域值之间为Normal状态。域值有两种方法定义,一种是规定一个固定值,固定值往往是根据经验摸索出来的结果。另外一种做法是让域值变得能够自适应,这两种方法各有优劣点。
上图为评估负载状态列了两个图和公式,其中解决了两个问题,一个问题是评估出网络状态是按照Underuse、Normal还是Overuse。另外一个事情是把域值gamma做成自适应的。第一个问题,可以把左边图跟右边图结合起来看,它解决的是M和gamma之间的关系,红色的线是gamma的正值和它的负值,加起来就是一个通道。其实它就是一个管道,在管道中间处于一种比较正常的状态,是可以接受的Normal值,如果在通道上面就是Overuse值,再下面是Underuse值。
反映到代码上面来说,一般IF语句,我就先看else,else如果是负值的话,对应的就在右边图的最下面,此时它处于一种Underuse的状态,如果按照use的状态应该是Normal,同时把等待的时间给清零。如果说它应该是处于中间管道以内的全Normal的状态,如果它的MR大于gamma,说明它在上面,或者在下面,我们首先看在下面的情况。如果在下面的情况,M是一个负值的,负值的情况下应该把等待的时间清零,变成-1 Underuse的状态。
自适应的基本的原理是让gamma值以上一个时刻的值做为基础,不断地去微调。如果gamma是正数,就往上调,如果是负数的话,就往下调,一般上调的时候是采取一种比较保守的状态慢慢上调,如果往下调就调的比较快,最终拿到的结果就是gamma值,它会跟着网络抖动的延迟不断地调整,而且是相对平滑的去波动。
上一步最终的结果是得出了目前网络状况的三种情况,分别是Normal、Underuse和Overuse,根据这三种情况去做下一步的动作,这里用GCC的自动机来表征下一步该做什么,即构科技采用的方法不见得完全跟它一样,但思路基本上是比较接近的。
如果是在正常的情况下,自动机总是会不断去尝试把码率调高,这是在Normal的情况下,如果是在Overuse的情况下,基于把码率调低的这种趋势它会表现的比较激进。如果处于Normal情况下,自动机会处于Underuse的情况下不断地调高。即构科技在细节上的处理有些不一样,在Normal情况下有两种方法,一种方法是根据网络状况,如果网络状况处于逐步恢复的状态,它会比较保守的来提高,如果发现网络状况一下子变得很好,丢包率很低且没有抖动的情况下会把码率在短时间内调的很高。在Overuse情况下,GCC的建立逐步向上的,即构科技的做法是直接降到可用的目标码率之下,甚至更低,具体变得多低要根据拥塞的情况来具体分析,如果拥塞很严重的话就降的很低,就要给足够的时间做缓冲去恢复带宽,如果拥塞不是那么严重的话,就降到比实际可用带宽低一些就可以了。
Underuse的状态是一种不稳定的状态,这时候采取的策略一般是比较保守的,在Underuse的状态下是可以把码率调上去的,但是能调多少,往上调多久,都是有一定不确定因素存在的,需要要一步一步去试探。
上图是GCC评估调整码率的方式。第一行的公式阐述是1.05,它表示的是逐步缓慢的上调。右边的R是过去五百毫秒里面接受抖动缓冲队列里面的包数,实际上表示过去五百毫秒里的最大码率,乘上α就是0.85,这是GCC的做法。
其实上述公式所表示的下降也是逐步下降的,如果之后的状态码率不会发生变化,根据具体不同的情况下会采取一些比较激进的做法。
网络拥塞的情况下追丢包需要处理三个问题,第一个问题,首先要基于丢包率获得码率,因为需要根据已有码率才能调整码率,这是最重要的信息。在接收端根据抖动缓冲队列里面统计得到丢包率,通过RTC包反过来给到发送端,发送端继续判断究竟码率应该是多少,最终得到想要的码率。
这里列了GCC做的公式,GCC公式第一行列了两个例子,一个是丢包率10%,一个是丢包率2%,超过10%的话我们一般认为已经发生了拥塞,网络处于比较差的状态。低于2%我们认为网络处于比较好的状态。GCC采用(1 - 0.5)丢包率表示是比较缓慢在上调码率,如果网络处于一个比较好的状态,丢包率低于2%,上调的幅度也不会太大,这是GCC的做法。即构科技的做法相对会比较激进。
基于丢包的拥塞控制还有一个情况需要去考虑,就是根据不同的网络它会有不同的表现,如果是有线网络的话,数据一般比较可靠,但如果是无线的网络的情况下,或者说跨国网络的情况推算出来的结果往往是不准的,这时候需要把这种方法和刚才说的delay Congestion Control 结合起来使用。
最后总结一下,之前的公式的最终目标就是上限以下的最低码率,因为我们最终可以得到两个码率,一个是基于丢包,一个是基于延迟,然后加起开发者设置的上下限,就得到一个最终码率。基于丢包和基于延迟都存在一定的优缺点,可以将它们结合起来使用。
前向纠错的缺点是不精准,比较低效,最重要的是费用较高。它的好处是不受RTT的影响,只需要发送一次。
ARQ的劣势是受RTT的影响较为明显,需要重传并且有可能引起重传风暴。ARQ的好处是实现比较简单,算法复杂度较低并且十分精准,最重要的一点是省钱。
即构科技在比较之下偏好使用ARQ,这很大部分归结于成本的原因,但是ARQ不能解决所有的问题,RTT比较小的情况下,它可以做到精准和低延迟,但RTT很大的时候,例如跨国的情况下,需要结合FEC来用。即构科技关于结合的做法是尽量的使用ARQ,如果发现当前情况ARQ解决不了,需要重传的时候要保证重传这次必须成功,我们的做法是加冗余,重复发两次,或者说用FEC来保证这次重传必须是成功的。
关于RTT判断的问题,RTT在国内一般是在一百毫秒以内,普遍认为是比较小的。跨国的情况下,比如从深圳到纽约,RTT会达到140左右,这个情况下算比较大的。冗余会降到可用的带宽,所以做冗余的时候需要慎重。
总结以上分享内容,关于实时网络传输延迟和实时RTC通信,它的延迟不仅仅是来自延迟传输,终端处理也十分重要。对网络传输调控的主要手段包括刚调度系统、拥塞控制以及信道纠错,同时在下行要结合分层编码。在网络方面,码率取决于用户是否有一个良好的网络环境。