如何识别rtp报文(实时传输控制协议RTCP-Transport-cc)
如何识别rtp报文(实时传输控制协议RTCP-Transport-cc)这样进行带宽估计时,通过transport sequence number我们就能关心到这条传输通道下所有数据包的情况了。这里我们使用前面的例子,视频流A与B,它们的RTP包记为Ra(n,m),Rb(n m),n表示sequence number,m表示transport sequence number,这样同一个PeerConnection下,视频流按如下形式传输:Ra(1 1) Ra(2 2) Rb(1 3) Rb(2 4) Ra(3 5) Ra(4 6) Rb(3 7) Rb(4 8)这里举个例子,假设同一个PeerConnection下,我们传输两个视频流A与B,它们的RTP包记为Ra(n),Rb(n),n表示sequence number,这样我们观察同一个PeerConnection下,视频流按如下形式传输:Ra(1) Ra(2) Rb(1) Rb(2) Ra(3) Ra(4)
Transport-cc指的是Transport-wide Congestion Control。WebRTC最新的拥塞控制算法(Sendside BWE)基于Transport-cc,接收端记录数据包到达时间,构造相关RTCP包,然后反馈给发送端,在发送端做带宽估计,从而进行拥塞控制。之所以基于Transport-cc,放到发送端进行带宽估计,除了方便维护,也增加了相关算法的灵活性,因为大多数处理逻辑都放到了发送端。WebRTC中为了使用Transport-cc,需要用到RTP报头扩展以及增加新的RTCP类型。这里我们介绍下Transport-cc中的RTP以及RTCP。
RTP Header扩展Transport sequence number首先我们先来复习下RTP固定报头结构:
图1 - RTP头
可以看到有一个sequence number字段,用于记录RTP包的序列号。一般情况下我们一个传输通道(PeerConnection)只包含一路视频流,这个sequence number能满足大多数需求。但是在一些情况下,我们一个连接可能传输多个视频流,这些视频流复用一个传输通道,例如simulcast或者single PC场景,一个PeerConnection可能包含多个不同的视频流。在这些视频流中,RTP报头的sequence number是单独计数的。
这里举个例子,假设同一个PeerConnection下,我们传输两个视频流A与B,它们的RTP包记为Ra(n),Rb(n),n表示sequence number,这样我们观察同一个PeerConnection下,视频流按如下形式传输:
Ra(1) Ra(2) Rb(1) Rb(2) Ra(3) Ra(4) Rb(3) Rb(4)
在对某条PeerConnection进行带宽估计时,我们需要估计整条PeerConnection下所有视频流,而不是单独某个流。这样为了做一个RTP session(传输层)级别的带宽估计,原有各个流的sequence number就不能满足我们需要了。
为此Transport-cc中,使用了RTP报头扩展,用于记录transport sequence number,同一个PeerConnection连接下的所有流的transport sequence number,使用统一的计数器进行计数,方便进行同一个PeerConnection下的带宽估计。
这里我们使用前面的例子,视频流A与B,它们的RTP包记为Ra(n,m),Rb(n m),n表示sequence number,m表示transport sequence number,这样同一个PeerConnection下,视频流按如下形式传输:
Ra(1 1) Ra(2 2) Rb(1 3) Rb(2 4) Ra(3 5) Ra(4 6) Rb(3 7) Rb(4 8)
这样进行带宽估计时,通过transport sequence number我们就能关心到这条传输通道下所有数据包的情况了。
RTP transport sequence number报头定义如下:
图2 -transport sequence number报头
由于属于RTP报头扩展,所以可以看到以0xBEDE固定字段开头,表示One-Byte Header类型的扩展。One-Byte Header相关知识请参考:实时传输协议RTP(一)
transport sequence number占两个字节,存储在One-Byte Header的Extension data字段。由于按4字节对齐,所以还有值为0的填充数据。
对于同一个PeerConnection下的每个包,这个transport sequence number是从1开始递增的。这里我们看下Wireshark中对带transport sequence numberRTP报头扩展的解析:
图3 - wirshark抓包
One-Byte Header中Extension data字段为0x0028,可知该RTP包的transport sequence number为0x0028。
代码导读WebRTC中,要发送的数据都会经过Pacing模块,用于平滑发送处理,要发送数据会送到pacer thread,在pacer thread中的PacketRouter::SendPacket,对要发送的RTP数据包打上统一计数的TransportSequenceNumber扩展。
图4 - 代码导读
TransportFeedback RTCP报文格式Transport-cc中,接收端通过TransportFeedback RTCP向发送端反馈收到的各个RTP包的到达时间信息。首先我们看下TransportFeedback包格式定义:
图5 - TransportFeedback RTCP
base sequence number:2字节,TransportFeedback包中记录的第一个RTP包的transport sequence number,在反馈的各个TransportFeedback RTCP包中,这个字段不一定是递增的,也有可能比之前的RTCP包小。
packet status count:2字节,表示这个TransportFeedback包记录了多少个RTP包信息,这些RTP的transport sequence number与base sequence number为基准。比如记录的第一个RTP包的transport sequence number为base sequence number,那么记录的第二个RTP包transport sequence number为base sequence number 1。
reference time:3字节,表示参考时间,以64ms为单位,RTCP包记录的RTP包到达时间信息以这个reference time为基准进行计算。
feedback packet count:1字节,用于计数发送的每个TransportFeedback包,相当于RTCP包的序列号。可用于检测TransportFeedback包的丢包情况。
packet chunk:2字节,记录RTP包的到达状态,记录的这些RTP包transport sequence number通过base sequence number计算得到。
recv delta: 1字节,对于"packet received"状态的包,也就是收到的RTP包,在recv delta列表中添加对应的的到达时间间隔信息,用于记录RTP包到达时间信息。通过前面的reference time以及recv delta信息,我们就可以得到RTP包到达时间。
packet chunk首先先了解下RTP包状态,目前定义了如下四种状态,每个状态值2bits,用来标识RTP包的到达状态,以及与前面RTP包的时间间隔大小信息:
- 00-Packet not received
- 01-Packet received small delta
- 10-Packet received large or negative delta
- 11-[Reserved]
packet chunk有两种类型,Run length chunk(行程长度编码数据块)与Status vector chunk(状态矢量编码数据块),对应packet chunk结构的两种编码方式。packet chunk的第一bit标识chunk类型。
①Run length chunk(行程长度)编码:一种简单的数据压缩算法,其基本思想是将重复且连续出现多次的字符使用“连续出现次数 字符”来描述,例如:aaabbbcdddd通过Run length编码就可以压缩为3a3bc4d。Run length chunk中就使用了Run length编码标识连续多个相同状态的包。
Run length chunk第一bit为0,后面跟着packet status以及run length。格式如下:
图6 - packet chunk
chunk type (T):1 bit,值为0。
packet status symbol (S):2 bits,标识包状态。
run length (L):13 bits,行程长度,标识有多少个连续包为相同状态。
图7 - 举例
packet status为00,由前面包状态可知为"Packet not received"状态,run lengh为221(11011101),说明连续有221个包为"Packet not received"状态。
②Status Vector Chunk 第一bit为1,后面跟着symbol size以及symbol list。格式如下:
图8 - Status Vector Chunk
- chunk type (T):1 bit,值为1。
- symbol size(S):1 bit,为0表示只包含"packet not received" (0)以及"packet received"(1)状态,每个状态使用1bit表示,这样后面14bits的symbol list能标识14个包的状态。为1表示使用2bits来标识包状态,这样symbol list中我们只能标识7个包的状态。
- symbol list:14 bits,标识一系列包的状态 总共能标识7或14个包的状态。
图9 - 举例
symbol size为0,这样能标识14个包的状态。第一个包状态为"packet not received"(0),接着后面5个包状态为"packet received"(1),再接着三个包状态为"packet not received",再接着三个包状态为"packet received",最后两个包状态为"packet not received"。
图10 - 举例
symbol size为1,这样只能标识7个包的状态。第一个包为"packet not received"(00)状态,第二个包为 "packet received w/o timestamp"(11)状态,再接着三个包为"packet received"(01)状态,最后两个包为"packet not received"(00)状态。
Receive Delta以250us(0.25ms)为单位,表示RTP包到达时间与前面一个RTP包到达时间的间隔,对于记录的第一个RTP包,该包的时间间隔是相对reference time的。
如果在packet chunk记录了一个"Packet received small delta"状态的包,那么就会在receive delta列表中添加一个无符号1字节长度receive delta,无符号1字节取值范围[0 255],由于Receive Delta以0.25ms为单位,故此时Receive Delta取值范围[0 63.75]ms。
如果在packet chunk记录了一个"Packet received large or negative delta"状态的包,那么就会在receive delta列表中添加一个有符号2字节长度的receive delta,范围[-8192.0 8191.75] ms。如果时间间隔超过了最大限制,那么就会构建一个新的TransportFeedback RTCP包,由于reference time长度为3字节,所以目前的包中3字节长度能够覆盖很大范围了。
总结起来就是:对于收到的RTP包在TransportFeedback RTCP receive delta列表中通过时间间隔记录到达时间,如果与前面包时间间隔小,那么使用1字节表示,否则2字节,超过最大取值范围,就另起新RTCP包了。
对于"Packet received small delta"状态的包来说,receive delta最大值63.75ms,那么一秒时间跨度最少能标识1000/63.75~=16个包。由于receive delta为250us的倍数,所以一秒时间跨度最多能标识4000个包。
packet chunk以及receive delta的使用是为了尽可能减小RTCP包大小。packet chunk用到了不同编码方式,对于收到的RTP包才添加到达时间信息,而且是通过时间间隔的方式记录到达时间。
代码导读在RemoteEstimatorProxy中处理RTP包的到达时间,构造Transport-cc报文,反馈给发送端。大概函数调用流程如下:
RemoteEstimatorProxy::IncomingPacket↓
RemoteEstimatorProxy::Process↓
RemoteEstimatorProxy::SendPeriodicFeedbacks↓
RemoteEstimatorProxy::BuildFeedbackPacket
RemoteEstimatorProxy::IncomingPacket中,如果RTP包带有TransportSequenceNumber扩展,会记录该RTP包的到达时间,然后添加到构造的Transport-cc报文中。我们看下主要处理的代码:
图11 - 代码导读
在RTCPReceiver中处理收到的Transport-cc报文,然后送给TransportFeedbackObserver处理。大概函数调用流程如下:
RTCPReceiver::IncomingPacket↓
RTCPReceiver::TriggerCallbacksFromRtcpPacket↓
TransportFeedbackObserver::OnTransportFeedback
在webrtc::rtcp::TransportFeedback中负责TransportFeedback包的解析以及构造。
总结熟悉上面这些有助于更好理解WebRTC中的带宽估计机制。