WebRTC 拥塞控制 | Transport-CC 之 RTP 头部扩展与 RTCP Feedback 报文-阿里云开发者社区

开发者社区> 阿里云视频云> 正文
登录阅读全文

WebRTC 拥塞控制 | Transport-CC 之 RTP 头部扩展与 RTCP Feedback 报文

简介: 本文是 WebRTC 拥塞控制 第 5 篇

作者:泰一
来源:码神说公众号

  • 导读
  • RTP 头部扩展
  • 扩展格式
    • 三问 transport-wide sequence number
    • TransportFeedback 报文
  • 格式总览
    • 记录 RTP 包到达状态之 Packet Chunk
    • 记录 RTP 包到达时间之 Receive Delta
    • 抓包分析

导读

在 WebRTC 的 Send-side BWE 中,大多数拥塞控制逻辑被放到了发送端,这样做除了方便维护,也增加了相关算法的灵活性,而这一切正是基于 Transport-CC(Transport-wide Congestion Control)。

为了使用 Transport-CC,WebRTC 做了两件事:一是针对于发送端,增加 RTP 头部扩展 Transport-wide Sequence Number,二是针对于接收端,增加新的 RTCP 反馈报文 TransportFeedback

Transport-CC 的大致逻辑为:发送端发送带有 Transport-wide Sequence Number 头部扩展的 RTP 包,接收端接收这些 RTP 包,缓存它们的到达时间并构造 TransportFeedback 报文反馈给发送端,发送端进行最终的网络拥塞控制。

从本篇开始,将围绕 TransportFeedback 报文,介绍其格式以及 Send-side BWE 在接收端以及发送端的拥塞控制策略。

RTP 头部扩展

扩展格式

RTP 头部 Transport-wide Sequence Number 扩展格式定义如下:
image.png

RTP 头部 Transport-wide Sequence Number 扩展

该扩展以 0xBEDE 开头,transport-wide sequence number 字段占用两个字节,从 1 开始递增到 65535 后会向前回绕到 1。

使用 wireshark 对带有 transport-wide sequence number 扩展的 RTP 流进行抓包,结果如下图:
image.png

带有 transport-wide sequence number 头部扩展的 RTP 包
可以看到,该 RTP 包的 transport-wide sequence number 为 0x09c0(2496)。

三问 transport-wide sequence number

  • 什么是 transport-wide sequence number?

transport-wide sequence number 对经由同一个 PeerConnection(亦或 socket)发送出去的 RTP 包进行计数,是网络传输层的数据包的 sequence number。

  • 为什么要使用 transport-wide sequence number?

我们知道,RTP 包头部已经含有 sequence number 字段用于记录 RTP 包的序列号,那么为何还要增加 transport-wide sequence number 呢?

这是因为,RTP 包头部的 sequence number 字段是针对单路媒体流(MediaStream)进行计数,并不能对某个 PeerConnection 通道进行计数,从而估计该 PeerConnection 通道的带宽使用情况。

一般情况下,一个 PeerConnection 只会包含一路流(无论是用于推流还是拉流),此时 RTP 包头部的 sequence number 可以满足需求。但是,一个 PeerConnection 也可能会包含多路流。比如在视频连麦中, 用户既要推自己的流又要拉对方的流,这就是所谓的 PeerConnection 多路复用。

由于 RTP 包头部的 sequence number 针对每一路流单独计数,此时便不再满足需求。因此,为了能对某个 PeerConnection 通道进行带宽估计,在 RTP 头部增加一个网络传输层扩展 transport-wide sequence number,使用统一的计数器对该通道下所有的视频流进行计数。

举个例子,假设同一个 PeerConnection 下传输两个视频流 A 与 B,它们的 RTP 包记为 Ra (n, m),Rb (n, m),n 表示 sequence number,m 表示 transport-wide 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-wide-cc 草案 [1] 说得那样,使用 transport-wide sequence number 的好处有两点:

  1. 更适用于拥塞控制。

因为拥塞控制算法不是在媒体流上运行,而是在传输层的数据包流上运行。

  1. 更快速的丢包检测与恢复。

比如两路流 A 和 B,当 stream A 发生丢包,则不必等到 stream A 的下一个包到来再触发丢包检测机制,从而进行 nack 请求。来自 stream B 的包也同样可以触发丢包检测机制,因为两路流使用统一的 transport-wide sequence number 进行计数。

  • 如何使用 transport-wide sequence number?

Send-side BWE 启动的关键在于开启 RTP 头部扩展 transport-wide sequence number,这样,接收端才能反馈 TransportFeedback 报文。开启头部扩展的方式很简单,只需要在 sdp 信息中加入相应的 extmap 属性,如下:

a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01

相应的,WebRTC 在获取视频引擎能力的函数中开启 transport-cc RTP 头部扩展,代码如下:

RtpCapabilities GetCapabilities() const {
  RtpCapabilities capabilities;
  capabilities.header_extensions.push_back(webrtc::RtpExtension(
    webrtc::RtpExtension::kTransportSequenceNumberUri,
    webrtc::RtpExtension::kTransportSequenceNumberDefaultId));
}

TransportFeedback 报文

正如 Remb 作为 Receive-side BWE 的关键 RTCP 报文,Transport Feedback 则作为 Send-side BWE 的关键 RTCP 报文,它们是拥塞控制算法架在发送端与接收端之间的一座桥梁。

这两种与拥塞控制相关的 RTCP 报文的类型如下表所示:
image.png

在 WebRTC 中,接收端发送 TransportFeedback 报文的时间间隔是一个动态值(50ms ~ 250ms)。发送间隔会根据码率值的 5% 进行计算,也就是说 TransportFeedback 报文至多占用总带宽的 5%。

同理,开启 transport-cc 的 TransportFeedback 报文反馈,需要在 sdp 中增加 rtcp-fb 属性。

a=rtcp-fb:107 transport-cc

WebRTC 视频编解码器默认开启 transport-cc 的 rtcp-fb 属性,代码如下:

void AddDefaultFeedbackParams(VideoCodec* codec) {
  codec->AddFeedbackParam(FeedbackParam(
    kRtcpFbParamTransportCc,
    kParamValueEmpty));
}

格式总览

先整体看下 TransportFeedback 包的格式:
image.png

TransportFeedback RTCP 报文格式


整体格式为:4 字节 RTCP 固定头部 + 8 字节 ssrc 信息 + 真正的 TransportFeedback 报文的反馈信息 Feedback Control Info
  • packet status count

2 字节,表示这个 TransportFeedback 包记录了多少个 RTP 包的到达信息。

  • base sequence number

2 字节,TransportFeedback 包中记录的第一个 RTP 包的 transport-wide sequence number。

一般情况下,当前 TransportFeedback 包的 base sequence number 加上 packet status count 等于下一个 TransportFeedback 包的 base sequence number。

但是注意,在反馈的各个 TransportFeedback 包中,base sequence number 不一定是递增的,也有可能比之前的小。在 RTP 包乱序到达的场景下会发生这种情况,这会导致之前已经被反馈的 RTP 包的到达信息再次被反馈。

  • feedback packet count

1 字节,每发送一个 TransportFeedback 包计数器都会加 1,相当于 RTCP 的 sequence number,用于检测 TransportFeedback 包的丢包情况,取值范围 [0, 255]

  • reference time

3 字节,表示参考时间,单位为 64ms,TransportFeedback 包记录的 RTP 包的到达时间以它作为基准进行计算。

  • packet chunk

2 字节,记录 RTP 包的到达状态。注意,本文所指的 RTP 包的到达状态包括包到达和包未到达。

  • recv delta

1 字节或者 2 字节,记录 RTP 包之间的到达时间偏移。

记录 RTP 包到达状态之 Packet Chunk

Packet Status Symbols

在介绍 packet chunk 之前,我们先来看一下 RTP 包的三种到达状态( Packet Status Symbols)。

  • 00 Packet not received

到达状态为包未到达,无到达时间偏移。

  • 01 Packet received, small delta

到达状态为包到达,到达时间偏移小。

  • 10 Packet received, large or negative delta

到达状态为包到达,到达时间偏移大或者为负值。

  • 11 [Reserved]

保留,目前不处理这种情况。

在网络质量高的情况下,RTP 包的到达时间偏移一般很小,属于 small delta`。

注意,在 RTP 包乱序的场景下,比如,下一个期望接收的包号是 3,结果来的是 5,对于 4 号包的处理,WebRTC 的做法是:将其到达状态标记为 Packet not received ,设置其到达时间偏移为 0,即没有 recv delta。不过,4 号包不一定真的丢失,如果接下来 4 号包到达,那么依然会被标记为 Packet received 状态并以 TransportFeedback RTCP 报文的形式反馈给发送端。

接下来介绍 packet chunk。

packet chunk 有两种类型,Run length chunk(行程长度编码数据块)与 Status vector chunk(状态矢量编码数据块),对应 packet chunk 结构的两种编码方式。

两种类型的 chunk 的长度都是 2 字节,数据包状态以 chunk 块的形式描述。

Run Length Chunk(行程长度编码数据块)

先来了解下 Run Length(行程长度)编码。

Run Length 编码是一种简单的数据压缩算法,其基本思想是将重复且连续出现多次的字符使用 连续出现次数+字符 来描述。例如:aaabbbcdddd 通过 Run Length 编码就可以压缩为 3a3bc4d。Run Length Chunk 中就使用了 Run Length 编码标识连续多个相同状态的包。

Run length chunk 第一个 bit 为 0,后面跟着包的到达状态 packet status symbol 以及具有该到达状态的包的数量 run length,格式如下:
image.png

run length chunk format

  • chunk type (T)

1 bit,值为 0。

  • packet status symbol (S)

2 bits,标识包的到达状态(00、01、10)。

  • run length (L)

13 bits,行程长度,标识有多少个连续包具有相同的到达状态。

下面举个例子说明。
image.png

run length chunk example

chunk type 为 0 ,可知为 RLE chunk。

packet status symbol 为 00,可知包的到达状态为 Packet not received

run length 为 0000011011101,即 221,可知有 221 个连续的包的到达状态为 Packet not received

Status Vector Chunk(状态矢量编码数据块)

Status vector chunk 第一个 bit 为 1,后面跟着 1 bit symbol size,决定 symbol list 的大小为 7 还是 14,格式如下:
image.png

status vector chunk format

  • chunk type (T)

1 bit,值为 1。

  • symbol list

14 bits,packet status symbol list,标识一系列包的到达状态,根据 symbol size 值的不同,具有不同的编码方式,总共能标识 7 或 14 个包的到达状态。

  • symbol size (S)

注意,这个 S 并不是 packet status symbol,而是 packet status symbol size,它决定包的到达状态的编码方式,即 packet status symbol 用 1 个 bit 表示还是用 2 个 bit 表示。

S=0,表示每 1 个 bit 表示 1 个包的到达状态,1 bit 可表示的状态只有 packet not received (00) 与 packet received (01) 两种,此时 symbol list 可以标识 14 个包的到达状态。

S=1,表示每 2 个 bit 表示 1 个包的到达状态,2 bit 可表示目前已定义的全部状态:packet not received (00) 、packet received (01)、和 packet received (10),此时 symbol list 可以标识 7 个包的到达状态。

可以看出,这样极致的利用每一个 bit,是为了尽可能用更小的空间表示更多的包的到达状态。

下面举个例子说明。

image.png

status vector chunk example

chunk type 为 1 ,可知为 Status Vector chunk。

symbol size 为 0,可知包的到达状态用 1 bit 来表示,一共可以标识 14 个包的到达状态。

symbol list 标识的包的到达状态依次是:

1x packet not received

5x packet received

3x packet not received

3x packet received

2x packet not received

记录 RTP 包到达时间之 Receive Delta

到达时间偏移,表示 RTP 包到达时间与前面一个 RTP 包到达时间的间隔,单位是 250us(0.25ms)。

对于当前 TransportFeedback 包中记录的第一个 RTP 包,虽然它的前面没有 RTP 包,但是也会计算它的到达时间偏移。不过该包的到达时间偏移是相对于 reference time 的,至于这样做的原因,我猜测可能是为了统一处理的逻辑吧。

packet chunk 用到了不同编码方式,对于收到的 RTP 包才添加到达时间信息,而且是通过时间间隔的方式记录到达时间。

  • 如果在 packet chunk 中记录了一个 Packet received, small delta 状态的包,那么会在 receive delta 列表中添加一个 8-bit unsigned 长度的 receive delta ,取值范围为 [0, 255]。由于 receive delta 单位为 0.25ms,所以此时 receive delta,即到达时间偏移量的取值范围为 [0ms, 63.75ms]
  • 如果在 packet chunk 中记录了一个 Packet received, large or negative delta 状态的包,那么会在 receive delta 列表中添加一个 16-bit signed 长度的 receive delta ,取值范围为 [-32768, 32767]。由于 receive delta 单位为 0.25ms,所以此时 receive delta,即到达时间偏移量的取值范围为 [-8192.0ms, 8191.75ms]
  • 如果到达时间偏移超过了最大限制,那么会构建一个新的 TransportFeedback RTCP 包,由于 reference time 长度为 3 字节,所以目前的包中 3 字节长度能够覆盖很大范围了。

我们发现,packet chunk 中包的三种到达状态:0(未到达,可能丢失)、1(到达,小的到达时间偏移)、2(到达,大的到达时间偏移) ,恰好对应 receive delta 的三种不同的长度:0 字节,1 字节,2 字节。因此,总结一下:对于每一个到达状态为 Packet received 的 RTP 包:

  • 如果到达状态为 01,即到达时间偏移小,那么 receive delta 使用 1 字节表示。
  • 如果到达状态为 10,即到达时间偏移大,那么 receive delta 使用 2 字节表示。
  • 如果到达时间偏移已经超出了最大限制,另起新的 TransportFeedback 包。

对于到达状态为 Packet not received 的 RTP 包,因为根本未到达,所以不需要 receive delta 标识。在 WebRTC 的实现中执行 AddDeltaSize(0) 函数,来标记这是一个未到达的包。

可以看出,对于不同大小的到达时间偏移,receive delta 占用的字节长度也不同,与 packet chunk 不同的编码方式 一样,这样做的目的是尽可能减小 TransportFeedback 包的大小,尽量降低 TransportFeedback 反馈报文的带宽占用。

最后,对于 Packet received, small delta 状态的包来说:

  • receive delta 最大值为 63.75ms,这意味着 1 秒时间跨度最少能标识 1000/63.75~=16 个包,如果包大小为 1200 bytes,那么码率可达 150 Kbps。
  • receive delta 时间单位为 0.25ms,这意味着 1 秒时间跨度最多能标识 1000/0.25 = 4000 个包,如果包大小为 1200 bytes,那么码率可达 38.4 Mbps。

抓包分析

下面,我们抓包来实际看下 TransportFeedback 报文的格式,如下图:
image.png

TransportFeedback RTCP 报文抓包分析


红色方框中的部分是 RTCP 报文的通用格式,其中,前 8 个字节(固定 4 字节头部和 sender ssrc 字段)是所有 RTCP 报文所共有的,而有没有 media source ssrc 字段则要视具体的 RTCP 报文而定。

比如 Nack、Pli、TransportFeedback 报文就有这个字段,Sender report、Receiver report 则没有这个字段,而 Fir、Remb 报文虽然有这个字段但是却设置为 unused 状态(值为 0)。

绿色方框中的部分是真正的 TransportFeedback 报文的格式,总体分为三大部分:头部、记录包到达状态的 chunk 以及记录包到达时间偏移的 recv delta。

观察上图,我总结了几个比较重要的点以帮助你更好的理解这个报文:

  1. 该报文 base sequence number 为 1089,packet status count 为 66,那么下一个要发送的 TransportFeedback 报文的 base sequence number 为 1089 + 66 = 1155。
  2. 该报文携带了全部三种不同编码方式的 chunks。对于 1 bit status vector chunk,N 代表 packet not received,R 代表 packet received,small delta,对于 2 bit status vector chunk,NR 代表 packet not received,SD (small delta) 代表 packet received,small delta,LD (large delta) 代表 packet received,large delta
  3. 到达状态为 packet not received 的包没有 recv delta。观察上图,packet status count 值为 66,不妨数一下 chunks 记录的包的到达状态的数量,为 14 + 7 + 7 + 14 + 14 + 10 = 66,二者一致。然而 recv delta 的数量却只有 17,因为只有真正收到了包,才会记录其到达时间偏移,这应该很好理解。
  4. small delta 的取值范围是 [0ms, 63.75ms],观察 recv delta,存在一个 large delta 为 131ms,已经超出了 63.75ms,其他的 small delta 都是在 63.75ms 之内。

至此,我们了解了 transport-cc 的关键策略:增加 RTP 头部扩展和 RTCP Feedback 报文,本篇着重介绍了它们的格式。下一篇将围绕着 TransportFeedback 报文,深入源码,介绍 transport-cc 在接收端以及发送端的逻辑框架,感谢阅读。

参考资料
[1]transport-wide-cc 草案: https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01

「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。
image.png

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:

分享视频云行业和技术趋势,打造“新内容”、“新交互”。

官方博客
官网链接