RTP(Real-time Transport Protocol)协议,全称是实时传输协议。它主要用于音视频数据的传输。
一般在实时通信的时候,需要传输音频和视频数据。通常是这样做的,先将原始数据经过编码压缩之后,再将编码码流传输到接收端。在传输的时候通常不会直接将编码码流进行传输,而是先将码流打包成一个个 RTP 包再进行发送。
之所以要打包,是因为接收端要能够正确地使用这些音视频编码数据,不仅仅需要原始的编码码流,还需要一些额外的信息, 如视频编码标准(H264、H265、VP8、VP9 或 AV1)、视频播放速度等。
RTP 包包括两个部分:
有了 RTP 协议,就能够将码流打包成 RTP 包发给接收端了。如果只负责传输 RTP 包,而不需要管传输过程中有没有丢包,以及传输 RTP 包的时候有没有引起网络拥塞的话,那只需要使用 RTP 协议就可以了。比如说,选择使用 TCP 协议传输 RTP 包的话就可以不用管这些事情,因为 TCP 协议具有丢包重传、拥塞控制等功能。
通常情况下,在传输音视频数据的时候不会使用 TCP 协议作为传输层协议。因为 TCP 协议更适合传输文本和文件等数据,而不适合传输实时音频流和视频流数据,所以通常会使用 UDP 协议作为音视频数据的传输层协议。但 UDP 协议不具有丢包重传和拥塞控制的功能,需要自己实现。
RTCP(Real-time Transport Control Protocol)协议,全称是实时传输控制协议。它是辅助 RTP 协议使用的。RTCP 报文有很多种,分别负责不同的功能。常用的报文有发送端报告(SR)、接收端报告(RR)、RTP 反馈报告(RTPFB)等。而每一种报告的有效载荷都是不同的。通过这些报告在接收端和发送端传递当前统计的 RTP 包的传输情况的。使用这些统计信息来做丢包重传,以及预测带宽。
RTCP 协议只是用来传递 RTP 包的传输统计信息,本身不具有丢包重传和带宽预测的功能,而这些功能需要自己来实现。
RTP 是用来传输实际的视频数据的。它就像一个快递盒,先装好视频,然后填好运送的视频基本信息和收件人信息,最后将视频运送到收件人手上。而 RTCP 协议则像是一个用来统计快递运送情况的记录表。
RTP H264 码流打包分为三种方式:
怎么选择使用哪种方式打包呢?
一般来说,在一个 H264 码流中会混合使用多种 RTP 打包方式。一般来说,对于小的 P 帧、B 帧还有 SPS、PPS 可以使用单个 NALU 封包方式。而对于大的 I 帧、P 帧或 B 帧,使用分片封包方式。
一般情况下,音视频场景中的拥塞控制和丢包重传等算法的基础就是 RTP 和 RTCP 协议。需要通过 RTP 包的信息和 RTCP 包中传输的统计信息来做拥塞控制和丢包重传等操作。
带宽预测,就是实时预测当前的网络带宽大小。预测出实际的带宽之后,就可以控制音视频数据的发送数据量。如控制音视频数据的编码码率或者直接控制发送 RTP 包的速度,这都是可以的。控制住音视频发送的数据量是为了不会在网络带宽不够的时候,还发送超过网络带宽承受能力的数据量,最后导致网络出现长延时和高丢包等问题,继而引发接收端出现延时高或者卡顿的问题。
现在的网络中,大多存在两种类型的网络设备:
因为互联网中这两种类型的网络设备都存在,为了能够兼顾这两种类型的网络,WebRTC 中设计了两个主要的带宽预测算法:一个是基于延时的带宽预测算法;一个是基于丢包的带宽预测算法。
基于延时的带宽预测算法主要是通过计算一组 RTP 包它们的发送时长和接收时长,来判断当前延时的变化趋势,并根据当前的延时变化趋势来调整更新预测的带宽值。
基于延时的带宽预测算法,主要有 4 个步骤:
能直接使用延时来判断网络的好坏吗? 不能直接使用这个延时来判断网络的好坏,因为网络变化很快而且存在噪声,有的时候延时会因为网络噪声突然变大或变小。因此,需要通过当前延时和历史延时数据来判断延时变化的趋势,来平滑掉网络噪声引起的单个延时抖动。
基于丢包的带宽预测算法相比基于延时的带宽预测算法简单很多,整体思路就是根据 Transport-CC 报文反馈的信息计算丢包率,然后再根据丢包率的多少直接进行带宽调整更新。
最大带宽探测算法主要过程:
WebRTC 中带宽预测主要分为基于延时的带宽预测算法、基于丢包的带宽预测算法以及最大带宽探测算法。
基于延时的带宽预测算法主要是解决网络中含有大缓冲网络设备场景的带宽预测。基于丢包的带宽预测算法主要是解决网络中有小缓冲或无缓冲网络设备场景的带宽预测。最终预估带宽等于这两者预测到的带宽值中的最小值。
为了防止出现发送码率大幅低于实际网络带宽而导致网络带宽预估偏低的问题,还引入了最大带宽探测算法,可以周期性的探测网络的最大带宽。如果当前网络不是处于过载状态同时又探测到了最大带宽的话,就将预估带宽更新为探测到的最大带宽。
带宽预测的作用
如果不能够很好地预测出实际带宽,那有可能引起数据超发,导致发送数据量大于实际网络的承受能力,继而引起视频画面的延时和卡顿;也有可能预测的带宽太低,导致发送的数据量远低于实际网络的承受能力,不能很好地利用网络带宽,最终导致视频画面模糊和很明显的马赛克现象。
好的带宽预测算法还只是开始,如何在预测出带宽之后能够控制数据的发送码率,使其尽量符合当前的网络带宽也是非常重要的。如果没有做好发送码率的控制,想发送多少数据就发送多少数据的话,那跟没有网络带宽预测是一样的效果。要不就画面卡顿,要不就很模糊。
码率控制,是编码器的一个重要模块,主要的作用就是用算法来控制编码器输出码流的大小。虽然它是编码器的一个非常重要的部分,但是它并不是编码标准的一部分,也就是说,标准并没有给码控设定规则。平时用的编码器的码控都是编码器程序自己实现的。
码控的原理就是为每一帧编码图像选择一个合适的 QP 值的过程。
当一帧图像的画面确定了之后,画面的复杂度和 QP 值几乎决定了它编码之后的大小。由于编码器无法决定画面的复杂度,因此,码控的目标就是选择一个合适的 QP 值,以此来控制编码后码流的大小。当然有些码控算法是可以直接外部指定使用哪个 QP 值去编码的,就不需要编码器的码控算法去做决策了。
为了实现恒定码率,需要做很多个步骤,一步步的将输出码率逼近目标码率,而不是一步到位确定 QP 就可以实现恒定码率的目标的。所以,会分很多级做调整,分别是帧组级、帧级、宏块组 GOM(Group of MB)级。
具体处理过程如下:
需要能够保证在不同的画面复杂度和不同的运动程度的情况下,并且输出码率都要尽量接近目标码率的话,还需要先计算得到当前帧的复杂度。复杂度能够大概衡量当前帧在做完预测之后残差值的总体大小的。残差的大小和 QP 值决定了最后图像编码后的大小。
根据帧类型复杂度求解可以分为两种算法:
CBR 虽然是恒定码率,但它的意思是保证一段时间内的输出码率接近目标码率,比如说 1 秒或者几百毫秒,而不是保证每一帧输出都严格接近目标码率的。
算法是根据一段时间内前面已经编码的结果来调节还未编码帧的 QP,从而来达到一组帧的输出大小尽量接近目标码率的。因此,在开始的时候,需要根据目标码率来确定帧组的目标大小,之后再确定帧组内每一帧的目标大小。
先根据设定的目标码率和帧率值将两者相除,就可以计算得到每一帧的平均大小。然后将帧组的帧数(一般 8 个帧作为一组)乘以帧的平均大小,就是帧组的目标大小了。
在编码器刚开始编码的时候,帧组的剩余大小就是帧组的目标大小。当编码帧组中第一帧的时候,将帧组的剩余大小除以帧组的帧数,就得到帧组中第一帧的目标帧大小。当帧组中的第一帧编码完成之后,需要用第一帧的实际编码后的大小来更新帧组的剩余大小。随着帧组中的一帧帧不断编码,不断更新帧组的剩余大小,不断调整帧的目标大小。
根据前面计算得到的当前编码帧的帧复杂度和目标帧大小,再加上前面已经编码完成了的帧的复杂度和编码使用的 QStep(与 QP 一一对应)以及使用这个 QStep 编码之后实际的编码大小来计算当前帧的 SliceQP 。
大体思想:一帧编码后的大小应该是和帧的复杂度成正比的,并且跟帧使用的 QStep 是成反比的。
在开始编码一个 GOM 之前,需要计算一下帧的实际剩余大小和帧的目标剩余大小。帧的实际剩余大小是用帧的目标大小减去帧中已编码 GOM 的实际大小。再使用帧的实际剩余大小加上前一个 GOM 的实际编码大小,减去该 GOM 的目标大小,就是帧的目标剩余大小。
还有一个步骤需要做,就是需要计算一下当前 GOM 的目标大小,以备下一个 GOM 编码的时候做 GOM 级码控计算的时候使用。
CBR 码控算法的整体流程
在实际情况中,很多时候还会遇到各种各样的卡顿和花屏的问题。Jitter Buffer 模块,是好几个卡顿和花屏问题的处理模块。
Jitter Buffer 工作在接收端,主要功能就是在接收端收到包之后进行组帧,并判断帧的完整性、可解码性、发送丢包重传请求、发送关键帧请求以及估算网络抖动的。
一般来说,人眼在帧率达到 10fps 并且均匀播放时就不太能看出来卡顿了。如果两帧之间的播放时间间隔超过了 200ms,人眼就可以明显看出卡顿了。
采集到渲染这条链路中每一个都可能引起卡顿问题:
以视频通话为例,在一对一的场景中,发送端网络好,接收端网络较差时,发送端可以通过基于延时和基于丢包的带宽预测算法估算出发送端到接收端之间的网络带宽值。得得到这个带宽值之后,发送端的视频码控算法就会将码率降下来,同时,码率下降引起 QP 上升,画面质量下降,但是流畅性变好,不会一直卡死。
但是,当多人视频通话时,如果按最低带宽设置,会大概率牺牲其他用户的体验。
SVC 是指一个码流当中,可以分成好几层,如分成三层:
并且,第 0 层质量最低,第 0 层加第 1 层次之,三层加在一起的时候质量最高。这里的质量不是直接指的画面质量,而是帧率、分辨率的高低所代表的质量。
分层的好处是编码一个码流,可以组合出好几个不同的可解码码流出来。如说上面三层 SVC 的例子:第 0 层就是一个可以独立解码的码流;第 0 层加 上第 1 层也是一个可以独立解码的码流;第 0 层加上第 1 层和第 2 层也是一个可以解码的码流。这样可以根据不同接收端的带宽情况,由服务器转发不同组合的码流,每个接收端都能获得最佳的画面质量。
首先,需要一些字段来描述码流中当前帧的层号、帧序号等 SVC 信息。因为这些字段只有在编码器编码的时候才知道。需要在编码出来一帧之后,在 RTP 包里面打包上这些信息发送给服务器和接收端。
服务器到接收端的链路上,服务器是发送端,在服务器上也需要做带宽预测,预测算法是一样的。
服务器会预测得到每一个接收端和服务器之间链路的带宽值。发送端发送 RTP 包到服务器,服务器需要通过计算 RTP 包的大小和当前 RTP 包所属的帧属于哪一层得到每一层对应的码率。这样服务器在转发的时候,就可以根据到接收端之间链路的带宽值和对应的每一层的码率来选择到底转发几层。
参考 VP8 编码的 RTP 协议标准。VP8 的 RTP 协议在 RTP 头和 VP8 码流数据的中间还有一个 RTP 描述头,这个描述头主要用来放帧号、层号等信息的。
服务器可以从 RTP 描述头得到 RTP 包对应的层号,就可以通过 RTP 的层号和 RTP 的包大小来估算每一层的码率了。而接收端可以根据帧号、层号和层同步标志位等信息来判断当前帧是不是可以解码,而不用去解码视频码流。
服务器就可以通过丢层的方式来实现对不同带宽的接收端下发不同帧率码率的码流。