rtcp中的持续性丢包统计

文章目录

  • 简介

  • 起因

  • licode中的实现

  • chrome中的实现

  • rfc中的定义

简介

在rtcp的rr(receiver report)包中有两个字段fraction lost 和cumulative number of packets lost 用来表明丢包率和总共的丢包个数。这个文章会介绍这个关于这两个值的定义以及chrome内核中的实现。

起因

事情起因是看到一个关于丢包个数统计的方式。大概的统计方式如下,拿new time(最新收到的包的时间)对应的seq number + 1减去new time - windows time(windows tiem是统计窗口时间)的seq number,计算出期望收到的包个数 expected number(期望收到的个数)。在统计new time到new time - window time这段时间内接收到包的个数计为received count(实际收到的个数)。最后将expect number - receive count 计算出丢失包的个数loss count。大概是这个样子:

下面是sequence number对应接收时间的变化。为了方便统计,时间就没有标注单位。

-> sequence number
1 2 3 4 5 [6 7 9 10 11] seq number
1 2 3 4 5 [6 7 8 9  10] 时间
-> time

假定window time是5
new time 的seq number:11
new time - windows time的seq number:6
expected number: 11 + 1 - 6 = 6, [6, 7, 8, 9, 10, 11]
receive count:5
losst count:6 - 5 = 1
到这边看起来的确是没有问题,但是一旦发生乱序就会有问题,乱序在udp包中还是很常见的事。如果乱序会有什么问题,大概是这个样子:

1 2 3 4 5 [6 7 9 11 10] seq number
1 2 3 4 5 [6 7 8 9  10] 时间

假定window time是5

new time 的seq number:11

new time - windows time的seq number:6

expected number: 10 + 1 - 6 = 5, [6, 7, 8, 9, 10]

receive count:5

loss count:5 - 5 = 0

这个时候会计算出一个错误的值。

其实如果仔细思考,这个不单单是乱序的时候有问题,在重复收到序号相同或者是序号错误的包(序号值跳变非常大)。这个时候我就思考,标准的webrtc是怎么实现的?

本文福利, C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击领取↓↓

licode中的实现

首先我去看了licode实现。为什么我会先去看licode的实现?这个就别管了,反正是有原因的,哈哈。licode中关于丢包个数的统计如下:

//接收到一个rtp包之后处理
bool RtcpRrGenerator::handleRtpPacket(std::shared_ptr<DataPacket> packet) {
  /* ......
    省略了非常多不相关的代码
    ......*/
  uint16_t seq_num = head->getSeqNumber();
  rr_info_.packets_received++; //接收包个数++,切记
  if (rr_info_.base_seq == -1) {
    rr_info_.base_seq = head->getSeqNumber(); //第一次收到rtp包,切记
  }
  if (rr_info_.max_seq == -1) { //第一次收到rtp包
    rr_info_.max_seq = seq_num; 
  } else if (!RtpUtils::sequenceNumberLessThan(seq_num, rr_info_.max_seq)) { 
    //判断条件中已经做回环处理,会用最终的seq_num和max_seq比较,进入这边就是seq_num > max_seq
    if (seq_num < rr_info_.max_seq) { //回环判断
      rr_info_.cycle++; //如果发生了回环,回环数++
    }
    rr_info_.max_seq = seq_num; //所以rr_info_.max_seq中一直记录的是最大的seq number
  }
  //因为回环的存在,计算出最终的最大的seq number,切记
  rr_info_.extended_seq = (rr_info_.cycle << 16) | rr_info_.max_seq;
  
  return false;
}

//生成rr包
std::shared_ptr<DataPacket> RtcpRrGenerator::generateReceiverReport() {
  /* ......
    省略了非常多不相关的代码
    ......*/    
  uint64_t now = ClockUtils::timePointToMs(clock_->now());//当前时间
  //期望收到的包 = 最大的seq num - 初使seq num
  uint32_t expected = rr_info_.extended_seq - rr_info_.base_seq + 1;  
  
  //这次统计期望的包 = 这次统计总共期望收到的包个数 - e
  uint32_t expected_interval = expected - rr_info_.expected_prior;
  //更新上次统计总共期望收到 = 这次统计总共期望收到
  rr_info_.expected_prior = expected; 
  //这次统计收到的包 = 这次统计总共收到的包个数 - 上次统计总共收到的包个数
  uint32_t received_interval = rr_info_.packets_received - rr_info_.received_prior; 
  //更新上次统计总共收到的包的个数 = 这次统计总共收到的包的个数
  rr_info_.received_prior = rr_info_.packets_received;
  //这次统计丢包的个数 = 这次统计期望的包 - 这次统计收到的包
  int64_t lost_interval = static_cast<int64_t>(expected_interval) - received_interval;

  // TODO(pedro): We're getting closer to packet loss without retransmissions by ignoring negative
  // lost in the interval. This is not perfect but will provide a more "monotonically increasing" behavior
  if (lost_interval > 0) {//做了一个非负数的判断,上面这段看法是作者自己的注释
    //lost加上每一次的统计丢包的个数,也就是所有丢包的个数
    rr_info_.lost += lost_interval;
  }
  //赋值给rtcp,总共的丢包个数
  rtcp_head.setLostPackets(rr_info_.lost);
  
  return (std::make_shared<DataPacket>(0, reinterpret_cast<char*>(&packet_), length, type_));
}

认真看完上一段代码和注释就可以明显的看出,在licode中每次统计,并不会用最新收到包的seq number,他会拿当前收到最大的seq number减去第一个收到的seq number得到expected number。received number就是所有收到的包。所以在licode中是有解决乱序包的问题的。但是我还有一个疑问,它并没有解决“在重复收到序号相同或者是序号错误的包”这个问题。那我们看一下chrome中是怎么做的。

chrome中的实现

//在接收到rtp包后处理
void StreamStatisticianImpl::UpdateCounters(const RtpPacketReceived& packet) {
  /* ......
    省略了非常多不相关的代码
    ......*/    
  int64_t now_ms = clock_->TimeInMilliseconds();
  //持续丢包的个数--
  --cumulative_loss_;

  //因为回环问题,计算出最终的sequence_number
  int64_t sequence_number =
      seq_unwrapper_.UnwrapWithoutUpdate(packet.SequenceNumber());

  if (!ReceivedRtpPacket()) { //如果是第一次接收到rtp包
    //记录第一个接受rtp包的值,切记
    received_seq_first_ = sequence_number;

    last_report_seq_max_ = sequence_number - 1; 
    //接收到最大的包seq number
    received_seq_max_ = sequence_number - 1; 
    receive_counters_.first_packet_time_ms = now_ms;
  } else if (UpdateOutOfOrder(packet, sequence_number, now_ms)) {
    //这边很关键,检测当前收到的包是不是一个乱序的包,具体实现在后面,如果是乱序包直接return
    return;
  }
  //顺序的包处理逻辑


  //统计丢包的个数
  //假设:sequence_number = 3, received_seq_max_ = 2, cumulative_loss_ += 1
  //因为一开始的时候cumulative_loss_--,所以抵消了。只有当真正发生丢包的时候才会加上一个值
  cumulative_loss_ += sequence_number - received_seq_max_;
  //received_seq_max_赋予成seq number最大值
  received_seq_max_ = sequence_number;
}

//检测当前收到的包是不是乱序包
bool StreamStatisticianImpl::UpdateOutOfOrder(const RtpPacketReceived& packet,
                                              int64_t sequence_number,
                                              int64_t now_ms) {
    /* ......
    省略了一点点不相关的代码
    ......*/  
  // received_seq_out_of_order_ 这个变量做了两件事:
  //1. 如果有值,标记上个包是相对于上上个包的seq number别大的包。(我就先称包叫“seq跳变包”,)
  //2. 有值,值表示上个包的seq number
  //ps:什么时候会有seq跳变包,我从注释中观察到是当stream restart的时候。
  if (received_seq_out_of_order_) {
    //持续丢包的个数--。后面逻辑可以看到,如果seq跳变包出现,暂时不会做--cumulative_loss_,会在seq跳变包后一个包做--cumulative_loss_
    --cumulative_loss_;
    //预估这个包的seq number
    uint16_t expected_sequence_number = *received_seq_out_of_order_ + 1;
    //清空这个数据
    received_seq_out_of_order_ = absl::nullopt;
    //如果这个包是seq跳变包的下一个包,确定流发生了变化,需要重新评估整个seq number了
    if (packet.SequenceNumber() == expected_sequence_number) {
      // 这段英文注释是源代码的注释
      // Ignore sequence number gap caused by stream restart for packet loss
      // calculation, by setting received_seq_max_ to the sequence number just
      // before the out-of-order seqno. This gives a net zero change of
      // `cumulative_loss_`, for the two packets interpreted as a stream reset.
      //
      // Fraction loss for the next report may get a bit off, since we don't
      // update last_report_seq_max_ and last_report_cumulative_loss_ in a
      // consistent way.

      //将sequence_number设置为跳变包前一个包
      //这样做是为了让cumulative_loss_两次 -- 操作加回去,不明白可以结合解释一起看
      received_seq_max_ = sequence_number - 2;
      //返回false,也就意味着会继续走顺序包的逻辑
      return false;
    }
  }

  //检测这个包和上一个包的seq num是否差值过大。这边就是判断这个包是否是跳变包。
  if (std::abs(sequence_number - received_seq_max_) >
      max_reordering_threshold_) {
    // Sequence number gap looks too large, wait until next packet to check
    // for a stream restart.
    // 如果差值过大,标记,并且赋值。
    received_seq_out_of_order_ = packet.SequenceNumber();
    // Postpone counting this as a received packet until we know how to update
    // `received_seq_max_`, otherwise we temporarily decrement
    // `cumulative_loss_`. The
    // ReceiveStatisticsTest.StreamRestartDoesntCountAsLoss test expects
    // `cumulative_loss_` to be unchanged by the reception of the first packet
    // after stream reset.
    //把当前包的统计恢复回去,因为在一开始的时候做了--,注释说是为了延迟统计,我也不太清楚为什么需要延迟统计
    ++cumulative_loss_;
    //返回true,不会执行后面顺序包的逻辑
    return true;
  }
  //是一个正常递增的包,直接返回false
  if (sequence_number > received_seq_max_)
    return false;
  //如果到这边了,就是一个乱序包
  return true;
}


chrome中的源码还是稍微比licode难理解一点的。

chrome中的丢包计算的逻辑是:

我收到一个包,先把cumulative_loss_减1

判断这个包是否是有序的,如果是无序(seq < max seq),那么返回。如果是有序的,继续执行

把丢包个数补上。如果是递增1,那么和刚刚的减1抵消了

所以说,chrome对乱序包还是有处理的。

对seq跳变包处理逻辑时候(如果加上cumulative_loss_会变得很混乱,所以不加cumulative_loss_的修改了):

当前包是seq跳变包,记录seq跳变包的seq number(我们定义为unoreder seq)。

接受下一个包。判断当前的包seq number是否和unoreder seq + 1相等。如果是,表示当前seq number真的产生跳变,需要修改received_seq_max_的值,用于后续正确统计丢包个数。

如果seq number和unoreder seq + 1不相等,那么就代表seq跳变包是一个有问题的包,不会修改当前received_seq_max_值,继续以当前seq number做统计。

这块有点绕,如果不理解,可以多看两遍我上面的代码注释或者源码。

其实chrome中的实现非常严谨,但是还是没有处理rr中重复包的现象,和错误的seq跳变包的统计。是chrome写的代码有问题吗?于是我就去翻了一下rfc中关于累计丢包cumulative number of packets lost的描述。

rfc中的定义

rfc中描述如下

cumulative number of packets lost: 24 bits
      The total number of RTP data packets from source SSRC_n that have
      been lost since the beginning of reception.  This number is
      defined to be the number of packets expected less the number of
      packets actually received, where the number of packets received
      includes any which are late or duplicates.  Thus, packets that
      arrive late are not counted as lost, and the loss may be negative
      if there are duplicates.  The number of packets expected is
      defined to be the extended last sequence number received, as
      defined next, less the initial sequence number received.  This may
      be calculated as shown in Appendix A.3.

这段话中有一句很关键:This number is defined to be the number of packets expected less the number of packets actually received, where the number of packets received includes any which are late or duplicates

中文是:这个值定义是期望收到包的个数减去实际收到包的个数,实际收到包的个数包含晚到达或者重复的包。

所以cumulative number of packets lost 并不是丢包的个数,它只是一个差值。它还有可能是负数。

本文福利, C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击领取↓↓

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: RTCP(Real-time Transport Control Protocol)是一种实时传输控制协议,它用于控制多媒体会话的质量和带宽。抖动(Jitter)是指信号传输时由于网络延迟、拥塞等原因造成的数据包时序偏移的现象,使得信号的失真度增加。 在RTCP,抖动的计算方法如下:首先,接收端收到的每个数据包的时间戳计算与前一个数据包接收的时间戳之差,得到每个数据包的延迟。然后,将所有数据包的延迟相加,并将其除以数据包数量,得到平均延迟。接下来,计算每个数据包与平均延迟之间的差值,得出每个数据包的抖动。最后,将所有数据包的抖动值相加,并将其除以数据包数量,得到平均抖动值。 RTCP所计算的抖动值可以用来评估网络的稳定性和网络资源的利用情况,以及改善多媒体传输的质量。在应用层面,可以利用抖动值来动态调整各种参数,如带宽、编码率、视频分辨率等,以优化多媒体传输的效果。 ### 回答2: RTCP是一种控制协议,主要用于实时音视频传输的流量控制和质量监测。在RTCP抖动指的是连续 RTP 包之间在接收端播放时间的差异,如果间隔时间不同,可能会引起播放的章节变化、音频或视频的卡顿,影响用户体验。 计算抖动的方法是,对于每个 RTP 包,接收端都会记录其到达时间(Timestamp),并根据前后两个 RTP 包到达时间的差异计算出每个 RTP 包的抖动值。抖动计算公式如下: J(i) = J(i-1)+( |D(i-1,i)| - J(i-1)/16) 其,i表示当前 RTP 包的序号,D(i-1,i)表示前一个RTP包和当前RTP包的时间差值,J(i)表示前i个RTP包的抖动值。 需要注意的是,抖动值的单位与 RTP包的时间戳 (Timestamp)的单位保持一致,一般为毫秒(ms)。抖动值的大小越小,说明 RTP 包之间的时间间隔越稳定,对于实时音视频的传输来说越好。抖动值的阈值通常设置为 30 ms 左右,当抖动值超过该阈值时,接收端通常会采取一些措施,如重新协商带宽,调整编码参数等,以更好地适应当前的网络传输环境。 ### 回答3: RTCP (Real-time Transport Control Protocol) 是一种用于协调和管理实时多媒体会话的协议。它提供了流量控制、网络质量反馈等重要功能。其,抖动(jitter)是一项重要的网络质量指标,衡量了包到达时间的变化。 RTCP 使用一种叫做 RTP 原始时间戳进行抖动的计算。抖动可以通过以下步骤计算: 1. 在接收者端,通过接收到的 RTP 报头的时间戳,计算出间隔时间。每次接收到新的 RTP 报文,就记录下这个报文的接收时间和 RTP 报头的时间戳。 2. 根据所记录的相邻两个 RTP 报文的间隔时间(相对时间戳)计算出相邻两个报文的抖动值。这个抖动值可以通过下列公式来计算: ``` jitter = jitter + (difference - jitter) / 16 ``` 其,difference 是两个 RTP 报文的间隔时间,jitter 表示一段时间内的累计抖动值。 3. 对于抖动值,需要进行限制和平滑处理。这个过程首先将抖动值限制在最小值和最大值之间。然后,通过加权平均的方法,对抖动值进行平滑处理,以使其更加稳定。 通过 RTCP 对抖动进行计算和反馈,可以帮助实时多媒体应用实现更好的网络质量控制和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值