• WebRTC源码分析 nack详解


    1、Nack过程

    1.1 nack是什么

    丢包重传(NACK)是抵抗网络错误的重要手段。NACK在接收端检测到数据丢包后,发送NACK报文到发送端;发送端根据NACK报文中的序列号,在发送缓冲区找到对应的数据包,重新发送到接收端。NACK需要发送端,发送缓冲区的支持。

    1.2 nack流程

    发送端发送rtp,到达接收端时,发现丢包,接收端发送nack请求,发送端会从历史队列中取出数据重发。

    2、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、发送流程

    3.1 数据流

    以视频为例

    3.2 调用堆栈 

    1. H264EncoderImpl::Encode
    2. VideoStreamEncoder::OnEncodedImage
    3. VideoSendStreamImpl::OnEncodedImage
    4. RtpVideoSender::OnEncodedImage
    5. RTPSenderVideo::SendEncodedImage
    6. RTPSenderVideo::SendVideo
    7. RTPSenderVideo::LogAndSendToNetwork
    8. RTPSender::EnqueuePackets
    9. pacer //
    10. RtpSenderEgress::SendPacket
    11. //放入队列
    12. RtpPacketHistory::PutRtpPacket

    3.3 RtpPacketHistory

    RtpPacketHistory 负责缓存历史数据,有nack请求时,从此队列发送

    4、视频Nack过程

    4.1 rtp接收数据流程

     

    4.2 nack对报序号的管理

    接收到rtp数据包后,会调用nack模块对报序号进行记录,同时检测是否需要nack请求
    看代码

    1. int NackRequester::OnReceivedPacket(uint16_t seq_num,
    2. bool is_keyframe,
    3. bool is_recovered) {
    4. RTC_DCHECK_RUN_ON(worker_thread_);
    5. bool is_retransmitted = true;
    6. if (!initialized_) {
    7. newest_seq_num_ = seq_num;
    8. if (is_keyframe)
    9. keyframe_list_.insert(seq_num);
    10. initialized_ = true;
    11. return 0;
    12. }
    13. // Since the `newest_seq_num_` is a packet we have actually received we know
    14. // that packet has never been Nacked.
    15. if (seq_num == newest_seq_num_)
    16. return 0;
    17. //如果接收的报序号小于之前接收到的,可能是乱序的包,可能是重传包
    18. //如果nack列表有,则清除
    19. if (AheadOf(newest_seq_num_, seq_num)) {
    20. // An out of order packet has been received.
    21. auto nack_list_it = nack_list_.find(seq_num);
    22. int nacks_sent_for_packet = 0;
    23. if (nack_list_it != nack_list_.end()) {
    24. nacks_sent_for_packet = nack_list_it->second.retries;
    25. nack_list_.erase(nack_list_it);
    26. }
    27. if (!is_retransmitted)
    28. UpdateReorderingStatistics(seq_num);
    29. return nacks_sent_for_packet;
    30. }
    31. // Keep track of new keyframes.
    32. //保留最新的关键帧包
    33. if (is_keyframe)
    34. keyframe_list_.insert(seq_num);
    35. // And remove old ones so we don't accumulate keyframes.
    36. auto it = keyframe_list_.lower_bound(seq_num - kMaxPacketAge);
    37. if (it != keyframe_list_.begin())
    38. keyframe_list_.erase(keyframe_list_.begin(), it);
    39. if (is_recovered) {
    40. recovered_list_.insert(seq_num);
    41. // Remove old ones so we don't accumulate recovered packets.
    42. auto it = recovered_list_.lower_bound(seq_num - kMaxPacketAge);
    43. if (it != recovered_list_.begin())
    44. recovered_list_.erase(recovered_list_.begin(), it);
    45. // Do not send nack for packets recovered by FEC or RTX.
    46. return 0;
    47. }
    48. AddPacketsToNack(newest_seq_num_ + 1, seq_num);
    49. newest_seq_num_ = seq_num;
    50. // Are there any nacks that are waiting for this seq_num.
    51. //获取nack序号,如果有则触发nack
    52. std::vector<uint16_t> nack_batch = GetNackBatch(kSeqNumOnly);
    53. if (!nack_batch.empty()) {
    54. // This batch of NACKs is triggered externally; the initiator can
    55. // batch them with other feedback messages.
    56. nack_sender_->SendNack(nack_batch, /*buffering_allowed=*/true);
    57. }
    58. return 0;
    59. }

    这部分逻辑主要是收到包,查一下是不是乱序的,可能是网络造成乱序,也可能是重发过来的,收到了就把nack list里面的记录删掉

    1. void NackRequester::AddPacketsToNack(uint16_t seq_num_start,
    2. uint16_t seq_num_end) {
    3. // Called on worker_thread_.
    4. // Remove old packets.
    5. //kMaxPacketAge=1000,删除超出队列数量,删除最老的
    6. auto it = nack_list_.lower_bound(seq_num_end - kMaxPacketAge);
    7. nack_list_.erase(nack_list_.begin(), it);
    8. // If the nack list is too large, remove packets from the nack list until
    9. // the latest first packet of a keyframe. If the list is still too large,
    10. // clear it and request a keyframe.
    11. //nack_list 的最大容量为 kMaxNackPackets = 1000,
    12. //如果满了会删除最后一个 KeyFrame 之前的所有nacked 序号,
    13. //如果删除之后还是满的那么清空 nack_list 并请求KeyFrame
    14. uint16_t num_new_nacks = ForwardDiff(seq_num_start, seq_num_end);
    15. if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {
    16. while (RemovePacketsUntilKeyFrame() &&
    17. nack_list_.size() + num_new_nacks > kMaxNackPackets) {
    18. }
    19. if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {
    20. nack_list_.clear();
    21. RTC_LOG(LS_WARNING) << "NACK list full, clearing NACK"
    22. " list and requesting keyframe.";
    23. keyframe_request_sender_->RequestKeyFrame();
    24. return;
    25. }
    26. }
    27. for (uint16_t seq_num = seq_num_start; seq_num != seq_num_end; ++seq_num) {
    28. // Do not send nack for packets that are already recovered by FEC or RTX
    29. if (recovered_list_.find(seq_num) != recovered_list_.end())
    30. continue;
    31. NackInfo nack_info(seq_num, seq_num + WaitNumberOfPackets(0.5),
    32. clock_->TimeInMilliseconds());
    33. RTC_DCHECK(nack_list_.find(seq_num) == nack_list_.end());
    34. nack_list_[seq_num] = nack_info;
    35. }
    36. }

    我们可以看到AddPacketsToNack()函数主要实现了:

    nack_list 的最大容量为 kMaxNackPackets = 1000, 如果满了会删除最后一个 KeyFrame 之前的所有nacked 序号, 如果删除之后还是满的那么清空 nack_list 并请求KeyFrame。

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

    获取需要nack的

    1. std::vector NackRequester::GetNackBatch(NackFilterOptions options) {
    2. // Called on worker_thread_.
    3. //只考虑根据序列号获取nacklist
    4. bool consider_seq_num = options != kTimeOnly;
    5. //只考虑根据时间戳获取nacklist
    6. bool consider_timestamp = options != kSeqNumOnly;
    7. //当前时间
    8. Timestamp now = clock_->CurrentTime();
    9. std::vector nack_batch;
    10. auto it = nack_list_.begin();
    11. //遍历nack
    12. while (it != nack_list_.end()) {
    13. //初始化rtt为重发延时间隔
    14. TimeDelta resend_delay = TimeDelta::Millis(rtt_ms_);
    15. //如果使用了nack的配置
    16. if (backoff_settings_) {
    17. //设置最大的重发延时间隔
    18. resend_delay =
    19. std::max(resend_delay, backoff_settings_->min_retry_interval);
    20. // 如果重试次数超过了1次,则重新计算幂指后的重发延迟间隔(避免重试频繁)
    21. //每次延时增大25%,1.25的n次幂
    22. if (it->second.retries > 1) {
    23. TimeDelta exponential_backoff =
    24. std::min(TimeDelta::Millis(rtt_ms_), backoff_settings_->max_rtt) *
    25. std::pow(backoff_settings_->base, it->second.retries - 1);
    26. resend_delay = std::max(resend_delay, exponential_backoff);
    27. }
    28. }
    29. // 判断当前包seq_num是否该发送了(即超过了最大发送延迟时间)
    30. bool delay_timed_out =
    31. now.ms() - it->second.created_at_time >= send_nack_delay_ms_;
    32. // 判断基于rtt延迟时间时是否该发送了(即超过了重发延迟时间)
    33. bool nack_on_rtt_passed =
    34. now.ms() - it->second.sent_at_time >= resend_delay.ms();
    35. // 判断基于序列号时是否该发送了(即超过了重发延迟时间)
    36. bool nack_on_seq_num_passed =
    37. // 初次发送时有效,避免重复重发
    38. it->second.sent_at_time == -1 &&
    39. // 当前包序列号比较老
    40. AheadOrAt(newest_seq_num_, it->second.send_at_seq_num);
    41. // 如果该发送了
    42. if (delay_timed_out && ((consider_seq_num && nack_on_seq_num_passed) ||
    43. (consider_timestamp && nack_on_rtt_passed))) {
    44. // 当前包seq_num加入到nack list
    45. nack_batch.emplace_back(it->second.seq_num);
    46. ++it->second.retries; // 累积重试次数
    47. // 设置发送时间
    48. it->second.sent_at_time = now.ms();
    49. // 超过最大重试次数了则从nack_list_移除
    50. if (it->second.retries >= kMaxNackRetries) {
    51. RTC_LOG(LS_WARNING) << "Sequence number " << it->second.seq_num
    52. << " removed from NACK list due to max retries.";
    53. it = nack_list_.erase(it);
    54. } else {
    55. ++it;
    56. }
    57. continue;
    58. }
    59. ++it;
    60. }
    61. return nack_batch;
    62. }

    1、delay_timed_out :加入nacklist的时间大于要发送nack的延时

    2、nack_on_rtt_passed :该序号上次发送NACK的时间到当前时间要超过前面计算出来的延时。

    3:nack_on_seq_num_passed :确定有最新的包序号比这个大,是被丢失的

    4.3 nack的触发时机

    逻辑图

    有两个地方触发nack,用红方框框出来了

    • 1 当收到rtp数据,nack模块会记录包序号,包类型
    • 2 线程定期检测是否存在丢包,需要nack请求

    5、nack响应

    5.1 rtcp数据流

    5.2 源码

    1. void ModuleRtpRtcpImpl2::OnReceivedNack(
    2. const std::vector& nack_sequence_numbers) {
    3. if (!rtp_sender_)
    4. return;
    5. if (!StorePackets() || nack_sequence_numbers.empty()) {
    6. return;
    7. }
    8. // Use RTT from RtcpRttStats class if provided.
    9. int64_t rtt = rtt_ms();
    10. if (rtt == 0) {
    11. rtcp_receiver_.RTT(rtcp_receiver_.RemoteSSRC(), NULL, &rtt, NULL, NULL);
    12. }
    13. //取得rtt,把请求和rtt时间调用rtp补包
    14. rtp_sender_->packet_generator.OnReceivedNack(nack_sequence_numbers, rtt);
    15. }
    1. void RTPSender::OnReceivedNack(
    2. const std::vector<uint16_t>& nack_sequence_numbers,
    3. int64_t avg_rtt) {
    4. //设置历史队列rtt,取包时根据rtt计算
    5. packet_history_->SetRtt(5 + avg_rtt);
    6. for (uint16_t seq_no : nack_sequence_numbers) {
    7. const int32_t bytes_sent = ReSendPacket(seq_no);
    8. if (bytes_sent < 0) {
    9. // Failed to send one Sequence number. Give up the rest in this nack.
    10. RTC_LOG(LS_WARNING) << "Failed resending RTP packet " << seq_no
    11. << ", Discard rest of packets.";
    12. break;
    13. }
    14. }
    15. }
    1. int32_t RTPSender::ReSendPacket(uint16_t packet_id) {
    2. // Try to find packet in RTP packet history. Also verify RTT here, so that we
    3. // don't retransmit too often.
    4. absl::optional stored_packet =
    5. packet_history_->GetPacketState(packet_id);
    6. if (!stored_packet || stored_packet->pending_transmission) {
    7. // Packet not found or already queued for retransmission, ignore.
    8. return 0;
    9. }
    10. const int32_t packet_size = static_cast(stored_packet->packet_size);
    11. const bool rtx = (RtxStatus() & kRtxRetransmitted) > 0;
    12. std::unique_ptr packet =
    13. packet_history_->GetPacketAndMarkAsPending(
    14. packet_id, [&](const RtpPacketToSend& stored_packet) {
    15. // Check if we're overusing retransmission bitrate.
    16. // TODO(sprang): Add histograms for nack success or failure
    17. // reasons.
    18. std::unique_ptr retransmit_packet;
    19. if (retransmission_rate_limiter_ &&
    20. !retransmission_rate_limiter_->TryUseRate(packet_size)) {
    21. return retransmit_packet;
    22. }
    23. if (rtx) {
    24. retransmit_packet = BuildRtxPacket(stored_packet);
    25. } else {
    26. retransmit_packet =
    27. std::make_unique(stored_packet);
    28. }
    29. if (retransmit_packet) {
    30. retransmit_packet->set_retransmitted_sequence_number(
    31. stored_packet.SequenceNumber());
    32. }
    33. return retransmit_packet;
    34. });
    35. if (!packet) {
    36. return -1;
    37. }
    38. packet->set_packet_type(RtpPacketMediaType::kRetransmission);
    39. packet->set_fec_protect_packet(false);
    40. std::vector> packets;
    41. packets.emplace_back(std::move(packet));
    42. paced_sender_->EnqueuePackets(std::move(packets));
    43. return packet_size;
    44. }

    主要看一下RtpPacketHistory::GetPacketAndMarkAsPending 函数
    有两个调用:

    1. GetStoredPacket //按照序列号拿到packet
    2. VerifyRtt //距离上次发送,超过rtt时间才能再次发送
    1. bool RtpPacketHistory::VerifyRtt(const RtpPacketHistory::StoredPacket& packet,
    2. int64_t now_ms) const {
    3. if (packet.send_time_ms_) {
    4. // Send-time already set, this check must be for a retransmission.
    5. if (packet.times_retransmitted() > 0 &&
    6. now_ms < *packet.send_time_ms_ + rtt_ms_) {
    7. // This packet has already been retransmitted once, and the time since
    8. // that even is lower than on RTT. Ignore request as this packet is
    9. // likely already in the network pipe.
    10. return false;
    11. }
    12. }
    13. return true;
    14. }

    6、注意

    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↓↓↓↓↓↓见下面↓↓文章底部点击领取↓↓

  • 相关阅读:
    七牛云创建存储空间并绑定自定义域名-https协议
    线性表
    计算机网络-网络层 (IPV6,IPV4与IPV6对比,IPV6地址类型)
    温习JAVA
    golang slice/array无重复取随机内容
    在Maven中配置代理服务器的详细教程
    【VS Code】推荐一套我非常喜欢的主题和字体样式
    CSS网格布局
    蓝桥杯每日一题2023.11.15
    mysql数据库,sql语句中连接查询,连表查询,内连接,外连接,左外连接,右外连接,inner join、left join、right join,全连接
  • 原文地址:https://blog.csdn.net/m0_60259116/article/details/126269270