12、REMBB与Transport-CC:Transport-wide CC报文格式、REMBB反馈机制、接收端与发送端角色

这一章我们来聊聊WebRTC拥塞控制里两个最核心的协议组件——REMBB和Transport-CC。说实话,很多刚接触WebRTC的同学会把这两个东西搞混,觉得它们都是做带宽估计的。嗯,其实它们分工很明确:Transport-CC负责把传输层的丢包和延迟信息从接收端反馈给发送端,而REMBB则是接收端主动告诉发送端“我觉得你应该用多少码率”。

我个人习惯把Transport-CC看作是“数据采集器”,把REMBB看作是“建议书”。发送端最终听谁的?两个都听,但权重不同。下面我们一个一个拆开讲。

Transport-wide CC报文格式

Transport-wide Congestion Control,简称Transport-CC。它和传统的RTCP不同,不是每个RTP流单独反馈,而是把同一个传输通道上的所有RTP流打包在一起反馈。说白了,就是接收端每隔一段时间,给发送端发一个报文,告诉它:“你这段时间发的包,哪些到了,哪些丢了,延迟是多少。”

这个报文格式长什么样?我直接给你看结构:

// Transport-CC RTCP报文结构(简化版)
0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|  FMT=15 |   PT=205      |          length               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     SSRC of packet sender                     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      base sequence number                     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          packet status count          |     reference time    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          reference time (cont.)       |    ...                |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   packet status chunk 1              |   packet status chunk 2|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   ...                                                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   recv delta chunk 1                |   recv delta chunk 2   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   ...                                                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

这里有几个关键字段我要强调一下:

  • base sequence number:起始序列号。接收端从这个号开始报告,发送端就知道对应的是哪个包。
  • packet status count:本次报告覆盖了多少个包。注意,不是所有包都要报告,接收端可以只报告一个窗口内的包。
  • reference time:参考时间戳。接收端用这个时间作为基准,后续的延迟增量都是相对于这个时间的。
  • packet status chunk:每个包的状态。有三种:未收到、已收到(小延迟)、已收到(大延迟)。
  • recv delta chunk:每个已收到包的延迟增量,单位是微秒。

我在项目中遇到过一个问题:有些实现把reference time的单位搞错了,导致发送端算出来的延迟全是负数。嗯,这里要注意,reference time的单位是64毫秒的倍数,不是毫秒也不是微秒。

REMBB反馈机制

REMBB全称是Remote Bitrate Estimation,说白了就是接收端自己算一个带宽,然后告诉发送端。它的报文格式比Transport-CC简单得多:

// REMBB RTCP报文结构
0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|  FMT=15 |   PT=206      |          length               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     SSRC of packet sender                     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     SSRC of media source                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Unique identifier 'R' 'E' 'M' 'B'                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Num SSRC     | BR Exp | BR Mantissa                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   SSRC of flow 1 (if any)                                    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|   ...                                                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

REMBB的核心就两个东西:BR Exp(指数)和BR Mantissa(尾数)。它们组合起来表示一个浮点数,就是建议的码率。公式是:

bitrate = mantissa * 2^exp  // 单位是bps

举个例子,如果exp=10,mantissa=800,那么建议码率就是800 * 1024 = 819200 bps,约800kbps。

我曾经踩过一个坑:REMBB报文里的SSRC of media source字段,有些实现写的是发送端的SSRC,有些写的是接收端的SSRC。标准规定是写发送端的SSRC,但有些老版本Chrome写反了。如果你在做互操作性测试,这个字段一定要做兼容处理。

接收端与发送端角色

现在我们来聊聊这两个角色在拥塞控制中的分工。你想想看,整个拥塞控制流程其实就是一个闭环:

  1. 发送端:负责发送RTP包,同时记录每个包的发送时间和序列号。
  2. 接收端:收到包后,记录到达时间,然后定期生成Transport-CC和REMBB反馈。
  3. 发送端:收到反馈后,计算延迟梯度、丢包率,然后调整发送码率。

我画了一张图,帮你理清这个流程:

发送端 接收端 RTP包发送 + 时间戳记录 带宽估计器(延迟+丢包) 码率调整 + 编码器控制 包到达时间记录 Transport-CC 报文生成 REMBB 带宽建议生成 RTP包流 Transport-CC反馈 REMBB建议 反馈路径 数据路径

从这张图你可以看到,接收端其实做了两件事:

  • Transport-CC:被动反馈。接收端只是如实报告每个包的到达情况,不做任何计算。发送端拿到数据后自己算延迟和丢包。
  • REMBB:主动建议。接收端自己跑一个带宽估计算法(比如Google Congestion Control算法),然后把算出来的码率告诉发送端。

那么问题来了:发送端到底听谁的?

我个人习惯的做法是:以Transport-CC为主,以REMBB为辅。为什么?因为Transport-CC的数据更原始、更准确,发送端可以根据自己的算法做更精细的调整。而REMBB只是一个建议,接收端的算法可能和发送端的不一致,甚至可能因为实现bug给出错误的建议。

核心要点:在WebRTC的拥塞控制中,Transport-CC是“事实依据”,REMBB是“参考意见”。发送端最终做决策时,应该优先信任Transport-CC的原始数据,而不是直接采纳REMBB的建议。

避坑指南

我曾经在项目中遇到过几个典型问题,分享给你:

  • Transport-CC报文丢失:如果接收端发送的Transport-CC报文在网络中丢失了,发送端就收不到反馈。这时候发送端应该怎么做?我建议设置一个超时定时器,如果超过一定时间(比如500ms)没有收到任何反馈,就主动降低码率,避免网络进一步恶化。
  • REMBB和Transport-CC冲突:有时候REMBB建议降低码率,但Transport-CC显示延迟很低、丢包率为0。这时候该信谁?我的经验是:如果Transport-CC数据正常,就忽略REMBB的建议。因为REMBB可能基于过时的数据或者错误的算法。
  • 多流场景下的SSRC映射:如果你有多个RTP流(比如视频+屏幕共享),每个流都有自己的SSRC。Transport-CC报文里只报告传输层的包状态,不区分SSRC。发送端需要自己维护一个映射表,把传输层序列号映射到具体的RTP流上。

重要提醒:REMBB在WebRTC标准中已经被标记为“可选实现”,而Transport-CC是“必须实现”。如果你在做新的WebRTC项目,建议优先实现Transport-CC,REMBB可以作为备选方案。Chrome从M75版本开始,已经默认使用Transport-CC作为主要的拥塞控制反馈机制。

总结

这一章我们聊了Transport-CC和REMBB的报文格式、反馈机制,以及接收端和发送端各自的角色。说白了,Transport-CC是“数据采集器”,REMBB是“建议书”。发送端拿到这两份数据后,自己做决策。

下一章我们会深入发送端的带宽估计算法,看看它到底是怎么根据Transport-CC的数据算出码率的。嗯,那个算法才是真正的核心。


公众号:蓝海资料掘金营,微信deep3321