前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >WebRTC系列分享 第四期 | WebRTC QoS方法之视频接收端NACK实现

WebRTC系列分享 第四期 | WebRTC QoS方法之视频接收端NACK实现

作者头像
腾讯云音视频
发布2022-05-09 12:41:02
8100
发布2022-05-09 12:41:02
举报
文章被收录于专栏:音视频咖音视频咖

导语 | 上一篇文章我们详解了WebRTC中视频接收端NACK的实现,本文将为大家进一步详细解读WebRTC中视频接收端NACK的实现。文章中引用的WebRTC代码基于master,commit:f412945f05ce1ac372a7dad77d85498d23deaae源码分析。

概述

WebRTC接收端触发发送NACK报文有两处:

  1. 接收RTP报文,对序列号进行检测,发现有丢包,立即触发发送NACK报文;
  2. 定时检查nack_list_队列,发现丢包满足申请重传条件,立即触发发送NACK报文。

函数实现

1. 检测丢包触发

核心函数是NackModule2::OnReceivedPacket。

NackModule2::OnReceivedPacket函数在整个网络报文接收线程的调用栈的位置:

RtpVideoStreamReceiver::OnReceivedPayloadData调用NackModule2::OnReceivedPacket:

2. 定时检查触发

kTimeOnly模式默认周期调度时间是20ms。

代码语言:javascript
复制
static constexpr TimeDelta kUpdateInterval = TimeDelta::Millis(20);

3. 核心函数思想

NackModule2::AddPacketsToNack和NackModule2::GetNackBatch是NACK核心函数。

NackModule2::AddPacketsToNack:决定是否将该报文放入NACK队列。

代码语言:javascript
复制
void NackModule2::AddPacketsToNack(uint16_t seq_num_start,                                   uint16_t seq_num_end) {  // Called on worker_thread_.  // Remove old packets.  auto it = nack_list_.lower_bound(seq_num_end - kMaxPacketAge);  nack_list_.erase(nack_list_.begin(), it);
  // If the nack list is too large, remove packets from the nack list until  // the latest first packet of a keyframe. If the list is still too large,  // clear it and request a keyframe.  uint16_t num_new_nacks = ForwardDiff(seq_num_start, seq_num_end);  if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {    while (RemovePacketsUntilKeyFrame() &&           nack_list_.size() + num_new_nacks > kMaxNackPackets) {    }    if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {      nack_list_.clear();      RTC_LOG(LS_WARNING) << "NACK list full, clearing NACK"                             " list and requesting keyframe.";      keyframe_request_sender_->RequestKeyFrame();      return;    }  }  for (uint16_t seq_num = seq_num_start; seq_num != seq_num_end; ++seq_num) {    // Do not send nack for packets that are already recovered by FEC or RTX    if (recovered_list_.find(seq_num) != recovered_list_.end())      continue;    NackInfo nack_info(seq_num, seq_num + WaitNumberOfPackets(0.5),                       clock_->TimeInMilliseconds());    RTC_DCHECK(nack_list_.find(seq_num) == nack_list_.end());    nack_list_[seq_num] = nack_info;  }}

该函数的中心思想是:

  • nack_list的最大长度为kMaxNackPackets,即本次发送的nack包至多可以对kMaxNackPackets个丢失的包进行重传请求。如果丢失的包数量超过kMaxNackPackets,会循环清空nack_list中关键帧之前的包,直到其长度小于kMaxNackPackets。也就是说,放弃对关键帧首包之前的包的重传请求,直接而快速的以关键帧首包之后的包号作为重传请求的开始;
  • nack_list中包号的距离不能超过kMaxPacketAge个包号。即nack_list中的包号始终保持 [cur_seq_num - kMaxPacketAge, cur_seq_num] 这样的跨度,以保证nack请求列表中不会有太老旧的包号。

NackModule2::GetNackBatch:决定是否发送NACK请求重传该报文,两种触发方式都是调用这个函数。

代码语言:javascript
复制
std::vector<uint16_t> NackModule2::GetNackBatch(NackFilterOptions options) {  // Called on worker_thread_.  bool consider_seq_num = options != kTimeOnly;  bool consider_timestamp = options != kSeqNumOnly;  Timestamp now = clock_->CurrentTime();  std::vector<uint16_t> nack_batch;  auto it = nack_list_.begin();  while (it != nack_list_.end()) {    TimeDelta resend_delay = TimeDelta::Millis(rtt_ms_);    if (backoff_settings_) {      resend_delay =          std::max(resend_delay, backoff_settings_->min_retry_interval);      if (it->second.retries > 1) {        TimeDelta exponential_backoff =            std::min(TimeDelta::Millis(rtt_ms_), backoff_settings_->max_rtt) *            std::pow(backoff_settings_->base, it->second.retries - 1);        resend_delay = std::max(resend_delay, exponential_backoff);      }    }    bool delay_timed_out =        now.ms() - it->second.created_at_time >= send_nack_delay_ms_;    bool nack_on_rtt_passed =        now.ms() - it->second.sent_at_time >= resend_delay.ms();    bool nack_on_seq_num_passed =        it->second.sent_at_time == -1 &&        AheadOrAt(newest_seq_num_, it->second.send_at_seq_num);    if (delay_timed_out && ((consider_seq_num && nack_on_seq_num_passed) ||                            (consider_timestamp && nack_on_rtt_passed))) {      nack_batch.emplace_back(it->second.seq_num);      ++it->second.retries;      it->second.sent_at_time = now.ms();      if (it->second.retries >= kMaxNackRetries) {        RTC_LOG(LS_WARNING) << "Sequence number " << it->second.seq_num                            << " removed from NACK list due to max retries.";        it = nack_list_.erase(it);      } else {        ++it;      }      continue;    }    ++it;  }  return nack_batch;}

该函数的中心思想是:

  • 因为报文有可能出现乱序抖动情况,不能说检测出丢包就立即重传,需要等待send_nack_delay_ms_,当等待时间大于send_nack_delay_ms_,申请重传。send_nack_delay_ms_是系统初始化时,在GetSendNackDelay()配置。根据实际场景配置合理值。比方说可以牺牲一定带宽保证实时性要求比较高场景,send_nack_delay_ms_可以配置成0;
  • 因为NACK产生的延时主要在RTT环路延时上,所以再次重传的时间一定要大于rtt_ms_,当两次发送NACK重传请求时间大于rtt_ms_时,才会申请再次重传;
  • 视频会议场景对实时性要求很高,当报文一直处于丢包状态,不能持续申请重传,最大重传次数为kMaxNackRetries,超过最大重传次数,放弃该报文。不再重传。使用其他QoS手段进行恢复。

接收端NACK参数汇总

关于云架构平台部

云架构平台部是腾讯规模最大的技术部门之一,长期深耕音视频、存储、接入和计算服务等技术领域,通过海量的存储和数据库平台,世界级的CDN&音视频服务,先进的操作系统和视频编解码技术,助力腾讯云以技术的力量持续赋能客户,帮他们提升效率,降低成本。

WebRTC相关资源汇总

为了方便广大开发者快速了解上手WebRTC,我们对WebRTC相关的开源项目、工作招聘、测试工具以及行业内的RTC厂商资源进行了汇总。感兴趣的同学可以点击「阅读原文」前往 https://github.com/webrtcwork/webrtcwork 全面了解WebRTC相关内容

腾讯云音视频在音视频领域已有超过21年的技术积累,持续支持国内90%的音视频客户实现云上创新,独家具备 RT-ONE™ 全球网络,在此基础上,构建了业界最完整的 PaaS 产品家族,并通过腾讯云视立方 RT-Cube™ 提供All in One 的终端SDK,助力客户一键获取众多腾讯云音视频能力。腾讯云音视频为全真互联时代,提供坚实的数字化助力。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-05-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯云音视频 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 检测丢包触发
  • 2. 定时检查触发
  • 3. 核心函数思想
相关产品与服务
内容分发网络 CDN
内容分发网络(Content Delivery Network,CDN)通过将站点内容发布至遍布全球的海量加速节点,使其用户可就近获取所需内容,避免因网络拥堵、跨运营商、跨地域、跨境等因素带来的网络不稳定、访问延迟高等问题,有效提升下载速度、降低响应时间,提供流畅的用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档