当前位置: 首页 > news >正文

WebRTC研究:丢包与抖动

1、累计收包、重传包等信息,并计算抖动:

void StreamStatisticianImpl::UpdateCounters(const RTPHeader& header,
                                            size_t packet_length,
                                            bool retransmitted) 
{
  rtc::CritScope cs(&stream_lock_);

  /*
  判断包是否有序
  判断标准:当前收包seq位于区间:[received_seq_max_ - max_reordering_threshold, received_seq_max_] 之外
  max_reordering_threshold,默认为50
  */
  bool in_order = InOrderPacketInternal(header.sequenceNumber);
  ssrc_ = header.ssrc;

  // 累计所收到的包数、字节数
  incoming_bitrate_.Update(packet_length);

  // 累计所收到的包数、header/padding/payload字节数(packet_length是包总长度)
  receive_counters_.transmitted.AddPacket(packet_length, header);

  // 累计所收到的无序重传包数、字节数
  if (!in_order && retransmitted) 
  {
    receive_counters_.retransmitted.AddPacket(packet_length, header);
  }

  // 记录所收到的第一个包的seq、当前时间
  if (receive_counters_.transmitted.packets == 1) 
  {
    received_seq_first_ = header.sequenceNumber;
    receive_counters_.first_packet_time_ms = clock_->TimeInMilliseconds();
  }

  // 收到有序包
  // Count only the new packets received. That is, if packets 1, 2, 3, 5, 4, 6
  // are received, 4 will be ignored.
  if (in_order) 
  {
    // Current time in samples.
    NtpTime receive_time(*clock_);

    // Wrong if we use RetransmitOfOldPacket.

	// 回绕
    if (receive_counters_.transmitted.packets > 1 && received_seq_max_ > header.sequenceNumber) 
	{
      // Wrap around detected.
      received_seq_wraps_++;
    }

    // 记录最新 seq
    received_seq_max_ = header.sequenceNumber;

    // 计算最新抖动
    if (header.timestamp != last_received_timestamp_ &&
        (receive_counters_.transmitted.packets - receive_counters_.retransmitted.packets) > 1) 
	{
      UpdateJitter(header, receive_time);
    }

	// 记录最近 所收到的最新包头携带的时间戳、收到最新包时的本地NTP时间与当前时间
    last_received_timestamp_ = header.timestamp;
    last_receive_time_ntp_ = receive_time;
    last_receive_time_ms_ = clock_->TimeInMilliseconds();
  }

  size_t packet_oh = header.headerLength + header.paddingLength;

  // Our measured overhead. Filter from RFC 5104 4.2.1.2:
  // avg_OH (new) = 15/16*avg_OH (old) + 1/16*pckt_OH,
  received_packet_overhead_ = (15 * received_packet_overhead_ + packet_oh) >> 4;
}

判断包是否有序:

bool StreamStatisticianImpl::InOrderPacketInternal(uint16_t sequence_number) const 
{
  // First packet is always in order.
  if (last_receive_time_ms_ == 0)
    return true;

  if (IsNewerSequenceNumber(sequence_number, received_seq_max_)) 
  {
    return true;
  } 
  else 
  {
    // If we have a restart of the remote side this packet is still in order.
    return !IsNewerSequenceNumber(sequence_number, received_seq_max_ - max_reordering_threshold_);
  }
}

计算抖动:

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

void StreamStatisticianImpl::UpdateJitter(const RTPHeader& header, NtpTime receive_time) 
{
  // 将NTP时间戳:receive_time(本地当前NTP时间)转变成RTP时间戳
  uint32_t receive_time_rtp = NtpToRtp(receive_time, header.payload_type_frequency);
  // 将NTP时间戳:last_receive_time_ntp_(最近收到最新包时的本地NTP时间)转变成RTP时间戳
  uint32_t last_receive_time_rtp = NtpToRtp(last_receive_time_ntp_, header.payload_type_frequency);

  // 第一种计算抖动的方法:jitter_q4_
  /*
  计算(最近两次收到最新包时的本地NTP时间差)与(最近两次收到最新包头携带的时间戳)之差
  即:计算抖动值,理想情况下,抖动值为0
  */
  int32_t time_diff_samples = (receive_time_rtp - last_receive_time_rtp) -
      (header.timestamp - last_received_timestamp_);

  time_diff_samples = std::abs(time_diff_samples);

  // lib_jingle sometimes deliver crazy jumps in TS for the same stream.
  // If this happens, don't update jitter value. Use 5 secs video frequency
  // as the threshold.

  // 更新 jitter_q4_
  if (time_diff_samples < 450000) 
  {
    // Note we calculate in Q4 to avoid using float.
    int32_t jitter_diff_q4 = (time_diff_samples << 4) - jitter_q4_;
    jitter_q4_ += ((jitter_diff_q4 + 8) >> 4);
  }

  // 第二种计算抖动的方法:jitter_q4_transmission_time_offset_
  // transmission_time_offset:一段时间间隔,代表属于同一帧的RTP的实际发送时间距离帧的capture time的偏移量
  // 即计算(最近两次收到最新包时的本地NTP时间差)与(最近两次收到最新包实际发送到网络的时间戳)之差
  int32_t time_diff_samples_ext = (receive_time_rtp - last_receive_time_rtp) -
    ((header.timestamp + header.extension.transmissionTimeOffset) -
     (last_received_timestamp_ + last_received_transmission_time_offset_));

  time_diff_samples_ext = std::abs(time_diff_samples_ext);

  // 计算 jitter_q4_transmission_time_offset_
  if (time_diff_samples_ext < 450000) 
  {
    int32_t jitter_diffQ4TransmissionTimeOffset = (time_diff_samples_ext << 4) - jitter_q4_transmission_time_offset_;
    jitter_q4_transmission_time_offset_ += ((jitter_diffQ4TransmissionTimeOffset + 8) >> 4);
  }
}

二、丢包统计

每发送一次RTP包,就会进行一次丢包统计(即丢包统计的周期:5000ms)。

1、调用流程

void ModuleRtpRtcpImpl::Process()

int32_t RTCPSender::SendRTCP(…)

int32_t RTCPSender::SendCompoundRTCP(…)

void RTCPSender::PrepareReport(…)

void RTCPSender::PrepareReport(const std::set<RTCPPacketType>& packetTypes,
                               const FeedbackState& feedback_state) 
{
  ...
  ...
  ...
   
   if (receive_statistics_) 
   {
      StatisticianMap statisticians = receive_statistics_->GetActiveStatisticians();

      RTC_DCHECK(report_blocks_.empty());
      for (auto& it : statisticians) 
	  {
        AddReportBlock(feedback_state, it.first, it.second);
      }
  }
}
bool RTCPSender::AddReportBlock(const FeedbackState& feedback_state,
                                uint32_t ssrc,
                                StreamStatistician* statistician) 
{
  RtcpStatistics stats;
  if (!statistician->GetStatistics(&stats, true))
    return false;
    
  ...
  ...
  ...
}
bool StreamStatisticianImpl::GetStatistics(RtcpStatistics* statistics, bool reset) 
{
  {
    rtc::CritScope cs(&stream_lock_);
	/*
	received_seq_first_:所收到的第一个包的seq
	payload_bytes:所收到的包中payload部分字节总数
	*/
    if (received_seq_first_ == 0 && receive_counters_.transmitted.payload_bytes == 0) 
	{
      // We have not received anything.
      return false;
    }

	/* 不重新进行丢包统计 */
    if (!reset) 
	{
	  /* 收到的包总数 与 重传报数 之差 */
      if (last_report_inorder_packets_ == 0) 
	  {
        // No report.
        return false;
      }

      // 直接使用上一次的report
      *statistics = last_reported_statistics_;
      return true;
    }

	// 丢包统计
    *statistics = CalculateRtcpStatistics();
  }

  NotifyRtcpCallback();

  return true;
}

2、丢包统计

丢包率 = 255 * 丢包数 / 预期收到的包总数

丢包数:期望收到的包总数 - 实际收到的包总数

期望收到的包总数 = 当前收到的最新包seq - 截止到上一次report时收到的最新包seq

实际收到的包总数 = 正常包总数 + 重传包总数

正常包总数(不包括重传包)= 截止到目前收到的包总数与重传包总数之差 - 截止到上一次report时收到的包总数与重传包总数之差

重传包数 = 截止到目前收到的重传包总数 - 截止到上一次report时收到的重传包总数

RtcpStatistics StreamStatisticianImpl::CalculateRtcpStatistics() 
{
  RtcpStatistics stats;

  // 第一次进行统计
  if (last_report_inorder_packets_ == 0) 
  {
	// 设置截止到上一次report时,收到的最新包
    // First time we send a report.
    last_report_seq_max_ = received_seq_first_ - 1;
  }

  /*
  计算从上一次report到当前这段时间内,预期收到的包总数
  期望收到的包总数 = 当前收到的最新包seq - 截止到上一次report时收到的最新包seq
  */
  uint16_t exp_since_last = (received_seq_max_ - last_report_seq_max_);

  // 遇到seq回绕
  if (last_report_seq_max_ > received_seq_max_) 
  {
    exp_since_last = 0;
  }

  /*
  计算从上一次report到当前这段时间内,所收到的包数(不包括重传包)
  包数(不包括重传包)= 截止到目前收到的包总数与重传包总数之差 - 截止到上一次report时收到的包总数与重传包总数之差
  */
  uint32_t rec_since_last =
      (receive_counters_.transmitted.packets -
       receive_counters_.retransmitted.packets) - last_report_inorder_packets_;

  // With NACK we don't know the expected retransmissions during the last
  // second. We know how many "old" packets we have received. We just count
  // the number of old received to estimate the loss, but it still does not
  // guarantee an exact number since we run this based on time triggered by
  // sending of an RTP packet. This should have a minimum effect.

  // With NACK we don't count old packets as received since they are
  // re-transmitted. We use RTT to decide if a packet is re-ordered or
  // re-transmitted.

  /*
  计算从上一次report到当前这段时间内,收到的重传包总数
  重传包数 = 截止到目前收到的重传包总数 - 截止到上一次report时收到的重传包总数
  */
  uint32_t retransmitted_packets = receive_counters_.retransmitted.packets - last_report_old_packets_;

  /*
  计算从上一次report到当前这段时间内,实际收到的包总数
  实际收到的包总数 = 正常包总数 + 重传包总数
  */
  rec_since_last += retransmitted_packets;

  // 计算丢包数:期望收到的包总数 - 实际收到的包总数
  int32_t missing = 0;
  if (exp_since_last > rec_since_last) 
  {
    missing = (exp_since_last - rec_since_last);
  }

  /*
  计算丢包率
  丢包率 = 255 * 丢包数 / 预期收到的包总数
  255表示100%丢包
  */
  uint8_t local_fraction_lost = 0;
  if (exp_since_last) 
  {
    local_fraction_lost = static_cast<uint8_t>(255 * missing / exp_since_last);
  }
  stats.fraction_lost = local_fraction_lost;

  // 累计到目前为止的丢包总数
  cumulative_loss_ += missing;
  stats.cumulative_lost = cumulative_loss_;

  /*
  扩展最近收到的最新包seq
  received_seq_wraps_:发生seq回绕的次数
  */
  stats.extended_max_sequence_number = (received_seq_wraps_ << 16) + received_seq_max_;

  // 抖动
  // Note: internal jitter value is in Q4 and needs to be scaled by 1/16.
  stats.jitter = jitter_q4_ >> 4;

  // Store this report.
  last_reported_statistics_ = stats;

  // 截止到目前为止,收到的包总数与重传包总数之差
  last_report_inorder_packets_ = receive_counters_.transmitted.packets - receive_counters_.retransmitted.packets;

  // 记录本次report时的重传包总数、最新包seq
  last_report_old_packets_ = receive_counters_.retransmitted.packets;
  last_report_seq_max_ = received_seq_max_;

  return stats;
}

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

相关文章:

  • freeswitch的3XX重定向
  • jsp+sql毕业生招聘系统免费系统+论文
  • Java毕业设计-火车订票管理系统
  • SwiftUI 界面状态 成员变量 @State @Buiding immutable
  • Java 进阶集合和数据结构
  • RabbitMQ、RocketMQ、Kafka常见消息队列不得不知道的事
  • 简单工厂模式、工厂模式、抽象工厂模式(含C++代码)
  • 自动化测试之路 —— Appium输入及模拟手势
  • 使用聚类(K-means)分析方法对骑手进行分类标签定义
  • Z-Score模型的进阶版:Zeta模型
  • 从零开始配置vim(20)——模糊查询
  • 【CSAPP】现代操作系统前几章
  • React全家桶
  • 聊一聊密钥交换
  • 安装 ZooKeeper 并配置服务
  • php的引用
  • 【399天】跃迁之路——程序员高效学习方法论探索系列(实验阶段156-2018.03.11)...
  • 【前端学习】-粗谈选择器
  • HTML中设置input等文本框为不可操作
  • IE报vuex requires a Promise polyfill in this browser问题解决
  • JavaScript对象详解
  • JavaScript设计模式与开发实践系列之策略模式
  • js面向对象
  • Laravel深入学习6 - 应用体系结构:解耦事件处理器
  • markdown编辑器简评
  • tab.js分享及浏览器兼容性问题汇总
  • thinkphp5.1 easywechat4 微信第三方开放平台
  • Vue ES6 Jade Scss Webpack Gulp
  • 后端_ThinkPHP5
  • 基于web的全景—— Pannellum小试
  • 如何在GitHub上创建个人博客
  • 算法系列——算法入门之递归分而治之思想的实现
  • 通过获取异步加载JS文件进度实现一个canvas环形loading图
  • 通过来模仿稀土掘金个人页面的布局来学习使用CoordinatorLayout
  • 小试R空间处理新库sf
  • 新书推荐|Windows黑客编程技术详解
  • - 转 Ext2.0 form使用实例
  • nb
  • Prometheus VS InfluxDB
  • 交换综合实验一
  • ​如何在iOS手机上查看应用日志
  • ​学习一下,什么是预包装食品?​
  • #APPINVENTOR学习记录
  • #Linux(帮助手册)
  • #单片机(TB6600驱动42步进电机)
  • (十一)c52学习之旅-动态数码管
  • (学习日记)2024.04.04:UCOSIII第三十二节:计数信号量实验
  • (转)利用PHP的debug_backtrace函数,实现PHP文件权限管理、动态加载 【反射】...
  • (转载)CentOS查看系统信息|CentOS查看命令
  • .net core 客户端缓存、服务器端响应缓存、服务器内存缓存
  • .NET DevOps 接入指南 | 1. GitLab 安装
  • .Net Web窗口页属性
  • .NET 材料检测系统崩溃分析
  • .NET/C# 使用 ConditionalWeakTable 附加字段(CLR 版本的附加属性,也可用用来当作弱引用字典 WeakDictionary)
  • .net快速开发框架源码分享