iOS WebRTC RTCP之transport--CC

转载一篇作者《剑痴乎》大神的文章,以防文章找不到,如有侵权等问题,请大神及时联系,立即删除!附上原文章连接:https://blog.jianchihu.net/webrtc-research-transport-cc-rtp-rtcp.html

另外附上大神的文章目录:https://blog.jianchihu.net/big-talk-webrtc.html

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固定报头结构:


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|X|  CC   |M|     PT      |       sequence number         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           timestamp                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           synchronization source (SSRC) identifier            |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|            contributing source (CSRC) identifiers             |
|                             ....                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

可以看到有一个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 numberm表示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报头定义如下:



 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  
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 | 0xBE    |    0xDE | length=1            |

 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 |  ID |  L=1 |transport-wide sequence number  |  zero padding  |

 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

由于属于RTP报头扩展,所以可以看到以0xBEDE固定字段开头,表示One-Byte Header类型的扩展。

One-Byte Header相关知识请参考:WebRTC研究:RTP报头扩展

transport sequence number占两个字节,存储在One-Byte Header的Extension data字段。由于按4字节对齐,所以还有值为0的填充数据。

对于同一个PeerConnection下的每个包,这个transport sequence number是从1开始递增的。这里我们看下Wireshark中对带transport sequence numberRTP报头扩展的解析:

image

One-Byte Header中Extension data字段为0x0028,可知该RTP包的transport sequence number为0x0028。

代码导读

WebRTC中,要发送的数据都会经过Pacing模块,用于平滑发送处理,要发送数据会送到pacer thread,在pacer thread中的PacketRouter::SendPacket,对要发送的RTP数据包打上统一计数的TransportSequenceNumber扩展。

void  PacketRouter::SendPacket(std::unique_ptr<RtpPacketToSend>  packet,

                              const  PacedPacketInfo&  cluster_info)  {

  rtc::CritScope cs(&modules_crit_);

  // With the new pacer code path, transport sequence numbers are only set here,

  // on the pacer thread. Therefore we don't need atomics/synchronization.

  // 如果当前RTP包注册了TransportSequenceNumber扩展

  if  (packet->HasExtension<TransportSequenceNumber>())  {

    packet->SetExtension<TransportSequenceNumber>((++transport_seq_)  &  0xFFFF);

  }

}

TransportFeedback RTCP

报文格式

Transport-cc中,收流客户端通过TransportFeedback RTCP向发送端反馈收到的各个RTP包的到达时间信息。首先我们看下TransportFeedback包格式定义:


 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 

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    |V=2|P|  FMT=15  |    PT=205 | length              |

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   | SSRC of packet sender |

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |                      SSRC of media source |

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   |      base sequence number |      packet status count      |

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  | reference time                |  fb pkt.  count  |

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  |          packet chunk | packet chunk          |

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    . .

    . .

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    | packet chunk          |  recv delta |  recv delta |

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    . .

    . .

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    | recv delta          |  recv delta |  zero padding  |

    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

  • base sequence number:2字节,TransportFeedback包中记录的第一个RTP包的transport sequence number,在反馈的各个TransportFeedback RTCP包中,这个字段不一定是递增的,也有可能比之前的RTCP包小
  • packet status count:2字节,表示这个TransportFeedback包记录了多少个RTP包信息,这些RTP的transport sequence numberbase sequence number为基准

,比如记录的第一个RTP包的transport sequence numberbase sequence number,那么记录的第二个RTP包transport sequence numberbase 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: 8bits,对于"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

这里先来了解下Run length(行程长度)编码。Run length编码是一种简单的数据压缩算法,其基本思想是将重复且连续出现多次的字符使用“连续出现次数+字符”来描述,例如:aaabbbcdddd通过Run length编码就可以压缩为3a3bc4d。Run length chunk中就使用了Run length编码标识连续多个相同状态的包。

Run length chunk第一bit为0,后面跟着packet status以及run length。格式如下:


 0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5

      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

      |T|  S  | Run Length        |

      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

chunk type (T):1 bit,值为0
packet status symbol (S):2 bits,标识包状态
run length (L):13 bits,行程长度,标识有多少个连续包为相同状态

下面举例子说明下。


 0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5

      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

      |0|0  0|0  0  0  0  0  1  1  0  1  1  1  0  1|

      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

packet status为00,由前面包状态可知为"Packet not received"状态,run lengh为221(11011101),说明连续有221个包为"Packet not received"状态。

Status Vector Chunk

第一bit为1,后面跟着symbol size以及symbol list。格式如下:


 0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5

 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 |T|S| symbol list |

 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • 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个包的状态

下面举例子说明下。

例子1:


 0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5

 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 |1|0|0  1  1  1  1  1  0  0  0  1  1  1  0  0|

 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

symbol size为0,这样能标识14个包的状态。第一个包状态为"packet not received"(0),接着后面5个包状态为"packet received"(1),再接着三个包状态为"packet not received",再接着三个包状态为"packet received",最后两个包状态为"packet not received"。

例子2:


 0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5

 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 |1|1|0  0  1  1  0  1  0  1  0  1  0  0  0  0|

 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

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报文中。我们看下主要处理代码:


  if  (header.extension.hasTransportSequenceNumber)  {

    seq  =  unwrapper_.Unwrap(header.extension.transportSequenceNumber);

    if  (send_periodic_feedback_)  {

      if  (periodic_window_start_seq_  &&

          packet_arrival_times_.lower_bound(*periodic_window_start_seq_)  ==

              packet_arrival_times_.end())  {

        // Start new feedback packet, cull old packets.

        for  (auto it  =  packet_arrival_times_.begin();

 it  !=  packet_arrival_times_.end()  &&  it->first  <  seq  &&

 arrival_time_ms  -  it->second  >=  send_config_.back_window->ms();)  {

          it  =  packet_arrival_times_.erase(it);

        }

      }

      if  (!periodic_window_start_seq_  ||  seq  <  *periodic_window_start_seq_)  {

        periodic_window_start_seq_  =  seq;

      }

    }

    // We are only interested in the first time a packet is received.

    if  (packet_arrival_times_.find(seq)  !=  packet_arrival_times_.end())

      return;

    packet_arrival_times_[seq]  =  arrival_time_ms;

  }

RTCPReceiver中处理收到的Transport-cc报文,然后送给TransportFeedbackObserver处理。大概函数调用流程如下


RTCPReceiver::IncomingPacket

              ↓

RTCPReceiver::TriggerCallbacksFromRtcpPacket

              ↓

TransportFeedbackObserver::OnTransportFeedback

webrtc::rtcp::TransportFeedback中负责TransportFeedback包的解析以及构造。

总结

本文介绍了WebRTC带宽估计用到的TransportSequenceNumber RTP报头扩展以及TransportFeedback RTCP,并分析了相关代码调用,熟悉这些有助于更好理解WebRTC中的带宽估计机制。

参考

[1] RTP Extensions for Transport-wide Congestion Control draft-holmer-rmcat-transport-wide-cc-extensions-01.https://tools.ietf.org/html/draft-holmer-rmcat-transport-wide-cc-extensions-01.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350