NACK pour la lecture du code source webrtc

Le mécanisme NACK (Negative Acknowledgment) est un mécanisme important pour gérer la perte de paquets dans WebRTC. Utilisé pour avertir l'expéditeur de renvoyer les paquets perdus lorsque des paquets sont perdus. Lorsque le récepteur détecte une perte de paquet, il envoie un message NACK à l'expéditeur, demandant de renvoyer le paquet perdu. Ce mécanisme peut aider à améliorer la qualité et la fiabilité des communications, en particulier dans les environnements de réseau non fiables.
Cet article analysera l'implémentation et la version de nack à partir du code source de webrtc m98.

1. NackRequester ::OnReceivedPacket

int NackRequester::OnReceivedPacket(uint16_t seq_num,
                                    bool is_keyframe,
                                    bool is_recovered) {
    
    
  RTC_DCHECK_RUN_ON(worker_thread_);
  // TODO(philipel): When the packet includes information whether it is
  //                 retransmitted or not, use that value instead. For
  //                 now set it to true, which will cause the reordering
  //                 statistics to never be updated.
  bool is_retransmitted = true;

  if (!initialized_) {
    
      //如果没有初始化,就初始化,设置newest_seq_num_
    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_) //当前帧等于最新帧,说明无需更新nack模块
    return 0;

  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.
  //清除掉太老的关键帧,最多10000包
  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 说明是fec包恢复的,则更新recovered_list_
    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;
  }

  //将newest_seq_num_ + 1 至 seq_num的所有包添加至Nack队列
  AddPacketsToNack(newest_seq_num_ + 1, seq_num);
  newest_seq_num_ = seq_num;

  // Are there any nacks that are waiting for this seq_num.
  //获取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信息
    nack_sender_->SendNack(nack_batch, /*buffering_allowed=*/true);
  }

  return 0;
}

Une fois que webrtc aura reçu les données rtp, il appellera NackRequester::OnReceivedPacketpour mettre à jour et traiter les informations liées au nack. Dans NackRequester::OnReceivedPacket, jugez d'abord s'il s'agit d'un paquet en panne ou retransmis, et mettez à jour la file d'attente de perte de paquets, sinon, mettez à jour le NackRequester::AddPacketsToNacknouveau paquet perdu dans la file d'attente de perte de paquets, et enfin NackRequester::GetNackBatchenvoyez-le via NackSender après avoir obtenu le groupe de paquets perdus .

二、NackRequester ::AddPacketsToNack

void NackRequester::AddPacketsToNack(uint16_t seq_num_start,
                                     uint16_t seq_num_end) {
    
    
  // Called on worker_thread_.
  // Remove old packets.
  //清除太旧的Nack包
  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);
  //如果要插入的nack包数目+当前数目超过最大包数目,就按关键帧队列,清除关键帧之前的nac包
  if (nack_list_.size() + num_new_nacks > kMaxNackPackets) {
    
    
    while (RemovePacketsUntilKeyFrame() &&
           nack_list_.size() + num_new_nacks > kMaxNackPackets) {
    
    
    }

    //清除以后还是空间还是不够,就清空nack队列,请求关键帧
    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;
    }
  }

  //依次把待插入的nack包插入nack队列
  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;
  }
}

La logique d'insertion de paquets nack est relativement simple. Il s'agit de porter un jugement sur le nombre maximum de paquets. S'il dépasse, la file d'attente nack sera effacée en fonction du numéro d'image clé. Si elle dépasse toujours la limite après le nettoyage, le La file d'attente nack sera effacée et une image clé sera demandée à l'expéditeur.

3. NackRequester ::GetNackBatch

std::vector<uint16_t> NackRequester::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_);
    //根据配置选项,计算重传补偿延迟值,不配置的话就是一个rtt
    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_;  //可以通过配置nack发送延迟来设置,0-20ms,默认是0;
    bool nack_on_rtt_passed =
        now.ms() - it->second.sent_at_time >= resend_delay.ms(); //是否超过了nack重传延迟
    bool nack_on_seq_num_passed =
        it->second.sent_at_time == -1 &&
        AheadOrAt(newest_seq_num_, it->second.send_at_seq_num);  //是否是第一次发送nack,且序列号小于最新包
    if (delay_timed_out && ((consider_seq_num && nack_on_seq_num_passed) ||
                            (consider_timestamp && nack_on_rtt_passed))) {
    
     //满足条件,将nack序列放入nack_batch
      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;
}

GetNackBatch a deux modes, à savoir basé sur le mode numéro de série et basé sur le mode horodatage. Selon le mode numéro de série, après avoir reçu rtp et confirmé la perte de paquets pour la première fois, une demande de perte de paquets est envoyée selon le mode numéro de série ; selon le mode horodatage, la demande de perte de paquets est envoyée périodiquement à un certain moment intervalle.
Paramètres importants :

  • resend_delay : intervalle de retransmission de perte de paquets, la valeur par défaut est un rtt. Il peut être modifié en configurant backoff_settings_, par exemple, min_retry_interval peut être configuré pour définir l'intervalle de retransmission minimum afin d'empêcher les demandes de retransmission fréquentes lorsque le rtt est petit.
  • send_nack_delay_ms_ : le premier délai de retransmission de perte de paquets. Lorsque des paquets hors de l'ordre normal sont reçus, le mécanisme natif renverra NACK directement par défaut.En contrôlant l'intervalle de temps d'envoi retardé de NACK, les demandes de retransmission inutiles sous des réseaux à délai fixe peuvent être évitées. Par exemple, si kDefaultSendNackDelayMs=20ms, si certains paquets de données sont retardés de 10ms en raison du retard inhérent au réseau, et qu'il n'y a pas de mécanisme d'envoi de retard NACK à ce moment, ces paquets seront considérés comme perdus, et ces paquets seront demandés à retransmettre. Cependant, s'il y a un délai NACK de 20 ms, ces paquets ne seront pas comptés comme perdus, évitant ainsi les demandes de retransmission inutiles et le gaspillage de ressources.

L'analyse logique de l'envoi NACK est terminée, et le traitement spécifique de l'envoi et de la réception des emballages ne sera pas analysé dans cet article.

Je suppose que tu aimes

Origine blog.csdn.net/qq_36383272/article/details/131761392
conseillé
Classement