导语 | 上一篇文章我们详解了WebRTC中视频接收端NACK的实现,本文将为大家进一步详细解读WebRTC中视频接收端NACK的实现。文章中引用的WebRTC代码基于master,commit:f412945f05ce1ac372a7dad77d85498d23deaae源码分析。
概述
WebRTC接收端触发发送NACK报文有两处:
函数实现
核心函数是NackModule2::OnReceivedPacket。
NackModule2::OnReceivedPacket函数在整个网络报文接收线程的调用栈的位置:
RtpVideoStreamReceiver::OnReceivedPayloadData调用NackModule2::OnReceivedPacket:
kTimeOnly模式默认周期调度时间是20ms。
static constexpr TimeDelta kUpdateInterval = TimeDelta::Millis(20);
NackModule2::AddPacketsToNack和NackModule2::GetNackBatch是NACK核心函数。
NackModule2::AddPacketsToNack:决定是否将该报文放入NACK队列。
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; }}
该函数的中心思想是:
NackModule2::GetNackBatch:决定是否发送NACK请求重传该报文,两种触发方式都是调用这个函数。
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;}
该函数的中心思想是:
接收端NACK参数汇总
关于云架构平台部
云架构平台部是腾讯规模最大的技术部门之一,长期深耕音视频、存储、接入和计算服务等技术领域,通过海量的存储和数据库平台,世界级的CDN&音视频服务,先进的操作系统和视频编解码技术,助力腾讯云以技术的力量持续赋能客户,帮他们提升效率,降低成本。
WebRTC相关资源汇总
为了方便广大开发者快速了解上手WebRTC,我们对WebRTC相关的开源项目、工作招聘、测试工具以及行业内的RTC厂商资源进行了汇总。感兴趣的同学可以点击「阅读原文」前往 https://github.com/webrtcwork/webrtcwork 全面了解WebRTC相关内容
腾讯云音视频在音视频领域已有超过21年的技术积累,持续支持国内90%的音视频客户实现云上创新,独家具备 RT-ONE™ 全球网络,在此基础上,构建了业界最完整的 PaaS 产品家族,并通过腾讯云视立方 RT-Cube™ 提供All in One 的终端SDK,助力客户一键获取众多腾讯云音视频能力。腾讯云音视频为全真互联时代,提供坚实的数字化助力。