剑痴乎

剑痴乎
代码为剑,如痴如醉
  1. 首页
  2. WebRTC
  3. 正文

WebRTC研究:包组时间差计算-InterArrival

2019年11月12日 10188点热度 6人点赞 21条评论

在新版GCC(Google Congestion Control)也就是Sendside BWE中,包含两种拥塞控制模型。一种是基于丢包的,一种是基于时延的,全部在发送端计算。Sendside BWE最后综合这两种估计值取小的那个作为目标码率。

基于时延的拥塞控制算法主要由四部分组成:预处理(pre-filtering), 到达时间滤波器(arrival-time filter), 过载检测器(over-use detector),码率控制器(and a rate controller)。本文主要介绍其中的到达时间滤波器。

到达时间模型

在参考[1]中定义了一种到达时间模型(Arrival-time model),在这里我们先介绍几个概念。

两个包组(包组概念下一小节介绍,包组属于预处理部分的内容)的到达时间差:inter-arrival时间:

C++
1
t(i) - t(i-1)

两个包组离开(发送)时间差:inter-departure时间:

C++
1
T(i) - T(i-1)

包组i与包组i-1的时延变化:

C++
1
d(i) = t(i) - t(i-1) - (T(i) - T(i-1))

这个时延变化将在后续评估网络时延增长趋势的滤波器中用到,用于判断网络拥塞状况,这里就先不展开讨论,本文主要介绍这些时间差如何计算。

包组

在WebRTC中计算时延不是一个个包计算的,而是通过将包分组,然后计算这些包组间的时延,这样做目的主要是为了提高带宽估计准确性,避免无线网络环境下突发数据的影响,具体影响后面写篇专门文章分析。WebRTC中这些包组称作TimestampGroup。这里我们看下WebRTC中TimestampGroup的结构:

1
2
3
4
5
6
7
  struct TimestampGroup {
    TimestampGroup()
        : size(0), // 包组内包含的各个RTP包大小之和
          first_timestamp(0),
          timestamp(0),
          first_arrival_ms(-1),
          complete_time_ms(-1) {}

那么如何对包进行分组呢?

WebRTC是通过计算发送时间差值来分组。在包组中,除了第一个包外,后面的包距离包组第一个包的发送时间差小于5ms。假设每个包都有一个发送时间t,第一个包的发送时间为t0,如果后续的包发送时间与t0时间差△t = t - to <= 5ms,那么这些包都可以与t0发送时间的包归为一组,如果某个包得到的△t > 5ms,那么该包就作为下一个包组的第一个包,接着后面的包就跟该包继续比较时间差,判断能否归为一组。下面我们举个例子说明下。

上图中有两个包组TG1和TG2,其中序列号为1的包与4的时间差小于5毫秒,那么序列号1~4的包被划到一个包组TG1。序列号为5的包与1之间的时间间隔超过5毫秒,那么5就是TG2的第一个包,它与序列号6、7、8划到一个包组TG2。

在后续评估时延增长趋势的滤波器需要三个主要参数:发送时刻差值(timestamp_delta)、到达时刻差值(arrival_time_delta)和包组数据大小差值(packet_size_delta)。由上图可知:

C++
1
2
3
timestamp_delta = TG2:timestamp - TG1:timestamp;
arrival_time_delta = TG2:complete_time_ms - TG1:complete_time_ms;
packet_size_delta = TG2:size - TG1:size;

代码导读

ComputeDeltas函数

前面说到的时间差值计算以及包组判断代码主要由InterArrival类实现。InterArrival类主要就一个接口:ComputeDeltas。传入每个RTP包时间、大小等参数,计算包组时间间隔,包组大小差值,得到新的包组相关差值后返回true。

C++
1
2
3
4
5
6
7
bool InterArrival::ComputeDeltas(uint32_t timestamp,
                                 int64_t arrival_time_ms,
                                 int64_t system_time_ms,
                                 size_t packet_size,
                                 uint32_t* timestamp_delta,
                                 int64_t* arrival_time_delta_ms,
                                 int* packet_size_delta)

下面解释下各个参数:

  • timestamp:RTP包发送时间
  • arrival_time_ms:RTP包到达时间
  • system_time_ms:当前时间
  • timestamp_delta(output):包组发送时间差
  • arrival_time_delta_ms(output): 包组到达时间差
  • packet_size_delta(output) :包组大小差值

接下来看下相关代码流程,看timestamp_delta、arrival_time_delta_ms如何计算:

C++
1
2
3
4
5
6
7
8
9
10
11
12
// 若当前包属于新的包组
if (NewTimestampGroup(arrival_time_ms, timestamp)) {
  // 前一个包组有数据
  if (prev_timestamp_group_.complete_time_ms >= 0) {
    // 包组发送时间差:当前包组最后一个包发送时间减去前一个包组最后一个包发送时间
    *timestamp_delta =
        current_timestamp_group_.timestamp - prev_timestamp_group_.timestamp;
    // 包组到达时间差:当前包组最后一个包到达时间减去前一个包组最后一个包到达时间
    *arrival_time_delta_ms = current_timestamp_group_.complete_time_ms -
                             prev_timestamp_group_.complete_time_ms;
  }
}

packet_size_delta计算如下:

C++
1
2
3
// 包组大小差值:当前包组大小减去前一个包组大小
*packet_size_delta = static_cast<int>(current_timestamp_group_.size) -
                           static_cast<int>(prev_timestamp_group_.size);

NewTimestampGroup函数

WebRTC新包组判断代码:

C++
1
2
3
4
5
6
7
8
9
10
11
12
bool InterArrival::NewTimestampGroup(int64_t arrival_time_ms,
                                     uint32_t timestamp) const {
  if (current_timestamp_group_.IsFirstPacket()) {
    return false;
  } else if (BelongsToBurst(arrival_time_ms, timestamp)) {
    return false;
  } else {
    uint32_t timestamp_diff =
        timestamp - current_timestamp_group_.first_timestamp;
    return timestamp_diff > kTimestampGroupLengthTicks;
  }
}

  • 若是第一个包,得作为后续包对比基准,不认为是新包组
  • 若是突发数据(burst),不认为是新包组
  • 与第一个包发送时间间隔大于kMaxBurstDurationMs(值为5),认为该包属于新包组

BelongsToBurst函数

这里我们也介绍下上一小节说到的突发数据。有些客户端发送数据时,没用使用pacing平滑发送模块,没有控制发送速率,这样容易导致突发流量,尤其是发送关键帧数据时,或者在无线网络环境下,也容易出现突发流量。我们看下WebRTC中如何判断突发数据的。

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
bool InterArrival::BelongsToBurst(int64_t arrival_time_ms,
                                  uint32_t timestamp) const {
  if (!burst_grouping_) {
    return false;
  }
  assert(current_timestamp_group_.complete_time_ms >= 0);
  // 与当前包组最后一个包的到达时间差
  int64_t arrival_time_delta_ms =
      arrival_time_ms - current_timestamp_group_.complete_time_ms;
  // 与当前包组最后一个包的发送时间差
  uint32_t timestamp_diff = timestamp - current_timestamp_group_.timestamp;
  int64_t ts_delta_ms = timestamp_to_ms_coeff_ * timestamp_diff + 0.5;
  if (ts_delta_ms == 0)
    return true;
  // 传输时延
  int propagation_delta_ms = arrival_time_delta_ms - ts_delta_ms;
  // 传输时延变化小于0
  // 与当前包组最后一个包的到达时间差不大于kBurstDeltaThresholdMs(5ms)
  // 与当前包组第一个包的到达时间差小于kMaxBurstDurationMs(100ms)
  if (propagation_delta_ms < 0 &&
      arrival_time_delta_ms <= kBurstDeltaThresholdMs &&
      arrival_time_ms - current_timestamp_group_.first_arrival_ms <
          kMaxBurstDurationMs)
    return true;
  return false;
}

总结

本篇文章主要讲了到达时间模型以及在WebRTC中的源码实现:InterArrival。后面文章我们将研究下trendline滤波器。前面计算得到的相关差值我们将传递给trendline滤波器,进行网络拥塞情况判断。

WebRTC研究:Trendline滤波器-TrendlineEstimator

参考

[1] A Google Congestion Control Algorithm for Real-Time Communication.https://tools.ietf.org/html/draft-ietf-rmcat-gcc-02.

本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可
标签: WebRTC
最后更新:2022年3月27日

Jeff

管理员——代码为剑,如痴如醉

打赏 点赞
< 上一篇
下一篇 >

文章评论

  • holden

    请问这个值是怎么得出的,为什么不是直接写5ms,而是用了下面这个公式
    (kTimestampGroupLengthMs << 26) / 1000

    2019年12月12日
    回复
    • Jeff

      @holden 这个是内部不同时间单位的转换,kTimestampGroupLengthMs经过这个公式转换后就变为kTimestampGroupLengthTicks,你看下Ms与Ticks后缀,就明白具体单位含义了,一个是以毫秒为单位,一个是以采样频率为单位。

      2019年12月16日
      回复
      • Holden

        @Jeff 再请教一下,我再win10上编译好了webRTC的文件,该怎么调式goog_cc的那个模块,我试着启了example中的peerconnection.exe,但是好像没调到congestion controller 模块

        2019年12月20日
        回复
        • Jeff

          @Holden WebRTC本身是P2P传输,所以你还得跑P2P服务器,example也带有stun,turn服务器,因为是P2P,所以得跑两个peerconnection.exe。嫌麻烦,可以调试自带的gcc单元测试代码。

          2019年12月20日
          回复
          • Holden

            @Jeff 请问怎么才能调试自带的gcc测试代码。。。我现在只能调试example里面的代码,其他路径下的文件都没有设为启动项这个选项

            2019年12月23日
            回复
            • Jeff

              @Holden 我是自己在WebRTC里新建一个工程,把单元测试代码拷贝过来,然后调试

              2019年12月23日
              回复
  • Holden

    大佬可不可以解释下这个公式:
    uint32_t send_time_24bits =
    static_cast(
    ((static_cast(info.send_time_ms) << kAbsSendTimeFraction) +
    500) /
    1000) &
    0x00FFFFFF;
    --------------------------------
    我看文档上NTP转ABS的公式是:abs_send_time_24 = (ntp_timestamp_64 » 14) & 0x00ffffff
    那开头的那个公式是怎么得出来的?
    而且后18位是数据为,左移18是做什么用的?
    +500/1000是在做向上圆整吗?
    最新的算法不是不适用abs时间了吗,为啥代码中还有使用?

    问题比较多,望大佬答疑解惑!

    2019年12月25日
    回复
    • Jeff

      @Holden 抱歉,这个新转换公式我也无法解释,查了下,没看到相关文档说明。

      2019年12月30日
      回复
    • Jeff

      @Holden 参考这个:https://blog.jianchihu.net/webrtc-research-dbb-abstime-trans.html

      2020年5月3日
      回复
  • nativertc

    根据发送时间来分组,如果没有remb
    不是就没有abs time吗?那怎么根据发送时间来分组

    2020年11月7日
    回复
    • Jeff

      @nativertc 建议先好好了解下WebRTC现在代码在哪一端计算这个。

      2020年11月8日
      回复
      • nativertc

        @Jeff 我应该找到了,rfc5450, toffset,我想要的应该是这个。虽然还没有看的太明白
        不是接收端计算到达包组的到达时间差?

        2020年11月9日
        回复
        • Jeff

          @nativertc 计算都在发送端

          2020年11月9日
          回复
  • nativertc

    终于搞懂了,看了下源码,和toffset这个没有关系。是通过rtcp反馈然后在发送端计算的,abs-send-time是记录在history里面的

    2020年11月24日
    回复
  • Jeffrey

    另外请教一下
    看了一下该部分的代码,arrival_time_ms在发送端计算的,而且是以第一次收到的transport feedback的包的接收时间做为基准,所以这里的arrival_time_ms是不是代表的是transport feedback的接收时间而并非rtp包接收端的接收时间呀

    2021年6月30日
    回复
    • Jeff

      @Jeffrey 这里RTP包接收时间计数方式按前面文章说的是以reference time为基准,WebRTC里这个reference time是以第一个收到的feedback包的接收时间为基准,后面其他feecback包reference time会基于这个时间加上delta(各个feedback包reference time间隔)。所以你看到的RTP包接收时间是以第一次收到的transport feedback的包接收时间为基准。无论以哪个为基准都不影响计算,我们关注的是各个包的接收时间间隔。

      2021年7月5日
      回复
  • iubrutally

    请教一下,在判断突发流量时候,不是应该取发送时间的绝对值么,这地方怎么处理乱序到达的突发包呢?

    2021年8月16日
    回复
  • Jeffrey

    double send_delta_ms = (1000.0 * timestamp_delta) / (1 << kInterArrivalShift);
    这句有是啥意思呀

    2021年9月14日
    回复
    • Jeffrey

      @Jeffrey 前面这个乘以1000 是转换成ms ,后面除以这个是啥来着?

      2021年9月14日
      回复
  • razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
    取消回复

    这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理。

    版权声明

    为支持原创,创作更好的文章,未经许可,禁止任何形式的转载与抄袭,如需转载请邮件私信!本人保留所有法定权利。违者必究!

    文章目录
    • 到达时间模型
    • 包组
    • 代码导读
      • ComputeDeltas函数
      • NewTimestampGroup函数
      • BelongsToBurst函数
    • 总结
    • 参考
    最近评论
    ztt 发布于 3 周前(04月05日) 你好,想看里面的视频和图片为什么没有显示呢?需要下flash吗还是什么。
    huowa222 发布于 4 周前(03月26日) 同问
    邱国禄 发布于 2 个月前(02月17日) Receive Delta以0.25ms为单位,reference time以64ms为单位,kDe...
    啊非 发布于 4 个月前(12月30日) 大神,请教一个问题: constexpr int kBaseScaleFactor = Tran...
    啊非 发布于 4 个月前(12月30日) reference time:3字节,表示参考时间,以64ms为单位,但是 代码里面是 Trans...
    相关文章
    • WebRTC资讯:H265支持进展
    • WebRTC研究:Audio level
    • Mac平台WebRTC编译
    • WebRTC研究:RTP时间戳的产生
    • WebRTC研究:统计参数之丢包率

    COPYRIGHT © 2024 jianchihu.net. ALL RIGHTS RESERVED.

    Theme Kratos Made By Seaton Jiang