1.1 nack是什么
丢包重传(NACK)是抵抗网络错误的重要手段。NACK在接收端检测到数据丢包后,发送NACK报文到发送端;发送端根据NACK报文中的序列号,在发送缓冲区找到对应的数据包,重新发送到接收端。NACK需要发送端,发送缓冲区的支持。
1.2 nack流程
发送端发送rtp,到达接收端时,发现丢包,接收端发送nack请求,发送端会从历史队列中取出数据重发。
2.1 rfc协议
在rfc4585协议中定义可重传未到达数据的类型有二种:
1)RTPFB:rtp报文丢失重传(nack)。
2)PSFB:指定净荷重传,指定净荷重传里面又分如下三种(关键帧请求):
1、PLI (Picture Loss Indication) 视频帧丢失重传。
2、SLI (Slice Loss Indication) slice丢失重转。
3、RPSI (Reference Picture Selection Indication)参考帧丢失重传。
在创建视频连接的SDP协议里面,会协商以上述哪种类型进行NACK重转。以webrtc为例,会协商两种NACK,一个rtp报文丢包重传的nack(nack后面不带参数,默认RTPFB)、PLI 视频帧丢失重传的nack。

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

nack rtcp报文格式如上图所示,pt=205。Packet identifier(PID) 为丢包起始参考值,Bitmap of Lost Packets(BLP)为16位的bitmap,对应为1的为表示丢包数据,具体如下抓包分析:
Packet identifier(PID)为176。Bitmap of Lost Packets(BLP):0x6ae1。解析的时候需要按照小模式解析,0x6ae1对应二进制:110101011100001倒过来看1000 0111 0101 0110。按照1bit是丢包,0bit是没有丢包解析,丢失报文序列号分别是:176 177 182 183 184 186 188 190 191与wireshark解析一致,当然pid和blp可以有多个。
以视频为例
3.2 调用堆栈
- H264EncoderImpl::Encode
- VideoStreamEncoder::OnEncodedImage
- VideoSendStreamImpl::OnEncodedImage
- RtpVideoSender::OnEncodedImage
- RTPSenderVideo::SendEncodedImage
- RTPSenderVideo::SendVideo
- RTPSenderVideo::LogAndSendToNetwork
- RTPSender::EnqueuePackets
- pacer //
- RtpSenderEgress::SendPacket
- //放入队列
- RtpPacketHistory::PutRtpPacket
3.3 RtpPacketHistory
RtpPacketHistory 负责缓存历史数据,有nack请求时,从此队列发送
4.1 rtp接收数据流程

接收到rtp数据包后,会调用nack模块对报序号进行记录,同时检测是否需要nack请求
看代码
- int NackRequester::OnReceivedPacket(uint16_t seq_num,
- bool is_keyframe,
- bool is_recovered) {
- RTC_DCHECK_RUN_ON(worker_thread_);
- bool is_retransmitted = true;
-
- if (!initialized_) {
- newest_seq_num_ = seq_num;
- if (is_keyframe)
- keyframe_list_.insert(seq_num);
- initialized_ = true;
- return 0;
- }
-
- // Since the `newest_seq_num_` is a packet we have actually received we know
- // that packet has never been Nacked.
- if (seq_num == newest_seq_num_)
- return 0;
- //如果接收的报序号小于之前接收到的,可能是乱序的包,可能是重传包
- //如果nack列表有,则清除
- if (AheadOf(newest_seq_num_, seq_num)) {
- // An out of order packet has been received.
- auto nack_list_it = nack_list_.find(seq_num);
- int nacks_sent_for_packet = 0;
- if (nack_list_it != nack_list_.end()) {
- nacks_sent_for_packet = nack_list_it->second.retries;
- nack_list_.erase(nack_list_it);
- }
- if (!is_retransmitted)
- UpdateReorderingStatistics(seq_num);
- return nacks_sent_for_packet;
- }
-
- // Keep track of new keyframes.
- //保留最新的关键帧包
- if (is_keyframe)
- keyframe_list_.insert(seq_num);
-
- // And remove old ones so we don't accumulate keyframes.
- auto it = keyframe_list_.lower_bound(seq_num - kMaxPacketAge);
- if (it != keyframe_list_.begin())
- keyframe_list_.erase(keyframe_list_.begin(), it);
-
- if (is_recovered) {
- recovered_list_.insert(seq_num);
-
- // Remove old ones so we don't accumulate recovered packets.
- auto it = recovered_list_.lower_bound(seq_num - kMaxPacketAge);
- if (it != recovered_list_.begin())
- recovered_list_.erase(recovered_list_.begin(), it);
-
- // Do not send nack for packets recovered by FEC or RTX.
- return 0;
- }
-
- AddPacketsToNack(newest_seq_num_ + 1, seq_num);
- newest_seq_num_ = seq_num;
-
- // Are there any nacks that are waiting for this seq_num.
- //获取nack序号,如果有则触发nack
- std::vector<uint16_t> nack_batch = GetNackBatch(kSeqNumOnly);
- if (!nack_batch.empty()) {
- // This batch of NACKs is triggered externally; the initiator can
- // batch them with other feedback messages.
- nack_sender_->SendNack(nack_batch, /*buffering_allowed=*/true);
- }
-
- return 0;
- }
这部分逻辑主要是收到包,查一下是不是乱序的,可能是网络造成乱序,也可能是重发过来的,收到了就把nack list里面的记录删掉
- void NackRequester::AddPacketsToNack(uint16_t seq_num_start,
- uint16_t seq_num_end) {
- // Called on worker_thread_.
- // Remove old packets.
- //kMaxPacketAge=1000,删除超出队列数量,删除最老的
- 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.
- //nack_list 的最大容量为 kMaxNackPackets = 1000,
- //如果满了会删除最后一个 KeyFrame 之前的所有nacked 序号,
- //如果删除之后还是满的那么清空 nack_list 并请求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;
- }
- }
我们可以看到AddPacketsToNack()函数主要实现了:
nack_list 的最大容量为 kMaxNackPackets = 1000, 如果满了会删除最后一个 KeyFrame 之前的所有nacked 序号, 如果删除之后还是满的那么清空 nack_list 并请求KeyFrame。
本文福利, C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击领取↓↓
获取需要nack的
- std::vector
NackRequester::GetNackBatch(NackFilterOptions options) { - // Called on worker_thread_.
- //只考虑根据序列号获取nacklist
- bool consider_seq_num = options != kTimeOnly;
- //只考虑根据时间戳获取nacklist
- bool consider_timestamp = options != kSeqNumOnly;
- //当前时间
- Timestamp now = clock_->CurrentTime();
- std::vector
nack_batch; - auto it = nack_list_.begin();
- //遍历nack
- while (it != nack_list_.end()) {
- //初始化rtt为重发延时间隔
- TimeDelta resend_delay = TimeDelta::Millis(rtt_ms_);
- //如果使用了nack的配置
- if (backoff_settings_) {
- //设置最大的重发延时间隔
- resend_delay =
- std::max(resend_delay, backoff_settings_->min_retry_interval);
- // 如果重试次数超过了1次,则重新计算幂指后的重发延迟间隔(避免重试频繁)
- //每次延时增大25%,1.25的n次幂
- 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);
- }
- }
- // 判断当前包seq_num是否该发送了(即超过了最大发送延迟时间)
- bool delay_timed_out =
- now.ms() - it->second.created_at_time >= send_nack_delay_ms_;
- // 判断基于rtt延迟时间时是否该发送了(即超过了重发延迟时间)
- 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))) {
- // 当前包seq_num加入到nack list
- nack_batch.emplace_back(it->second.seq_num);
- ++it->second.retries; // 累积重试次数
- // 设置发送时间
- it->second.sent_at_time = now.ms();
- // 超过最大重试次数了则从nack_list_移除
- 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;
- }
1、delay_timed_out :加入nacklist的时间大于要发送nack的延时
2、nack_on_rtt_passed :该序号上次发送NACK的时间到当前时间要超过前面计算出来的延时。
3:nack_on_seq_num_passed :确定有最新的包序号比这个大,是被丢失的
4.3 nack的触发时机
逻辑图

有两个地方触发nack,用红方框框出来了
5.1 rtcp数据流

5.2 源码
- void ModuleRtpRtcpImpl2::OnReceivedNack(
- const std::vector
& nack_sequence_numbers) { - if (!rtp_sender_)
- return;
-
- if (!StorePackets() || nack_sequence_numbers.empty()) {
- return;
- }
- // Use RTT from RtcpRttStats class if provided.
- int64_t rtt = rtt_ms();
- if (rtt == 0) {
- rtcp_receiver_.RTT(rtcp_receiver_.RemoteSSRC(), NULL, &rtt, NULL, NULL);
- }
- //取得rtt,把请求和rtt时间调用rtp补包
- rtp_sender_->packet_generator.OnReceivedNack(nack_sequence_numbers, rtt);
- }
- void RTPSender::OnReceivedNack(
- const std::vector<uint16_t>& nack_sequence_numbers,
- int64_t avg_rtt) {
- //设置历史队列rtt,取包时根据rtt计算
- packet_history_->SetRtt(5 + avg_rtt);
- for (uint16_t seq_no : nack_sequence_numbers) {
- const int32_t bytes_sent = ReSendPacket(seq_no);
- if (bytes_sent < 0) {
- // Failed to send one Sequence number. Give up the rest in this nack.
- RTC_LOG(LS_WARNING) << "Failed resending RTP packet " << seq_no
- << ", Discard rest of packets.";
- break;
- }
- }
- }
- int32_t RTPSender::ReSendPacket(uint16_t packet_id) {
- // Try to find packet in RTP packet history. Also verify RTT here, so that we
- // don't retransmit too often.
- absl::optional
stored_packet = - packet_history_->GetPacketState(packet_id);
- if (!stored_packet || stored_packet->pending_transmission) {
- // Packet not found or already queued for retransmission, ignore.
- return 0;
- }
-
- const int32_t packet_size = static_cast
(stored_packet->packet_size); - const bool rtx = (RtxStatus() & kRtxRetransmitted) > 0;
-
- std::unique_ptr
packet = - packet_history_->GetPacketAndMarkAsPending(
- packet_id, [&](const RtpPacketToSend& stored_packet) {
- // Check if we're overusing retransmission bitrate.
- // TODO(sprang): Add histograms for nack success or failure
- // reasons.
- std::unique_ptr
retransmit_packet; - if (retransmission_rate_limiter_ &&
- !retransmission_rate_limiter_->TryUseRate(packet_size)) {
- return retransmit_packet;
- }
- if (rtx) {
- retransmit_packet = BuildRtxPacket(stored_packet);
- } else {
- retransmit_packet =
- std::make_unique
(stored_packet); - }
- if (retransmit_packet) {
- retransmit_packet->set_retransmitted_sequence_number(
- stored_packet.SequenceNumber());
- }
- return retransmit_packet;
- });
- if (!packet) {
- return -1;
- }
- packet->set_packet_type(RtpPacketMediaType::kRetransmission);
- packet->set_fec_protect_packet(false);
- std::vector
> packets; - packets.emplace_back(std::move(packet));
- paced_sender_->EnqueuePackets(std::move(packets));
-
- return packet_size;
- }
主要看一下RtpPacketHistory::GetPacketAndMarkAsPending 函数
有两个调用:
- GetStoredPacket //按照序列号拿到packet
- VerifyRtt //距离上次发送,超过rtt时间才能再次发送
- bool RtpPacketHistory::VerifyRtt(const RtpPacketHistory::StoredPacket& packet,
- int64_t now_ms) const {
- if (packet.send_time_ms_) {
- // Send-time already set, this check must be for a retransmission.
- if (packet.times_retransmitted() > 0 &&
- now_ms < *packet.send_time_ms_ + rtt_ms_) {
- // This packet has already been retransmitted once, and the time since
- // that even is lower than on RTT. Ignore request as this packet is
- // likely already in the network pipe.
- return false;
- }
- }
-
- return true;
- }
nack请求次数限制,kMaxNackRetries ,不会一直请求,超过10次就不在请求
nack间隔越来越大, 如果重试次数超过了1次,则重新计算幂指后的重发延迟间隔(避免重试频繁),每次延时增大25%,1.25的n次幂
nack最大数量是1000,大于的不会重传 kMaxPacketAge
nack线程会间隔20ms检测一次, kProcessIntervalMs 默认为20ms,
发送端收到nack请求后,检测距离上次时间超过rtt才能再次发送
本文福利, C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击领取↓↓