WebRtc音视频实时通信--发送端拥塞控制之PacedSender代码走读

PacedSender(步长发送器)简介:

因为视频是按帧采集的,一帧视频数据量在比较大的时候需要拆分成多个RTP包进行发送,如I帧,如此便会造成各RTP包的发送间隔不规律,属于一帧的各RTP包可能在很短暂的时间间隔内发送出去了,如1ms内,然后等待了几十ms之后才开始发送第二帧的第一个RTP包,这样各RTP的发送间隔不规律会造成瞬间的发送码率过大,可能会因此丢包等。加入一个PacedSender可尽量平均各RTP包(帧内或帧间)的发送间隔时间。

如图:


webrtc中代码位置:

webrtc/modules/pacing/paced_sender.cc


PacedSender基本工作原理:

通过Process方法的不断被调用来SendPacket

通过media_budget模块来计算本次理应发送的字节数,即预算,数据包发送后根据本次实际发送的字节数和理应发送的字节数来判断下次是否应该允许继续发送?

通过BitrateProbe模块来探测每次SendPacket后要wait多久?

核心代码走读:

// 用于获取下次Process方法被调用的时间间隔,以毫秒计。其内部调用BitrateProber的
// TimeUntilNextProbe方法来计算。
int64_t PacedSender::TimeUntilNextProcess() {
  rtc::CritScope cs(&critsect_);
  //当前时间减去上次Process调用时间点来计算出elapsed_time_us,单位为微秒
  int64_t elapsed_time_us =
      clock_->TimeInMicroseconds() - time_last_process_us_;
  int64_t elapsed_time_ms = (elapsed_time_us + 500) / 1000; //求整
  // When paused we wake up every 500 ms to send a padding packet to ensure
  // we won't get stuck in the paused state due to no feedback being received.
  if (paused_)
    return std::max(kPausedProcessIntervalMs - elapsed_time_ms, 0);

  if (prober_->IsProbing()) {
    // 通过BitrateProber获得下次执行Process的时机,详见BitrateProbe
    int64_t ret = prober_->TimeUntilNextProbe(clock_->TimeInMilliseconds());
    if (ret > 0 || (ret == 0 && !probing_send_failure_))
      return ret; //返回下次执行Process的需等待时间间隔
  }
  // 若当前没有开启探测,则使用默认值,最小等待间隔kMinPacketLimitMs减去已消逝
  // 时间间隔。
  return std::max(kMinPacketLimitMs - elapsed_time_ms, 0);
}

/**
 * Process方法是PacedSender的主发送线程,在Process方法内没有while循环和wait机制
 * ,其是由其他模块在外部驱动的。具体可参考call/rtp_transport_controller_send.cc
 * 中有关PacedSender是如何作为一个module注册到process thread中的,以及是如何在
 * ProcessThread中对各module的Process方法进行驱动的。因为PacedSender继承于Pacer,
 * 而Pacer类又继承于module.
 * 调用Process的模块每次都会等待一个时间间隔,此时间间隔是由BitrateProbe模块来
 * 探测出来的。具体可参考BitrateProbe代码走读部分.
 * Process方法的主要功能就是SendPacket及更新预算。具体在下面代码中讲解。
 */
void PacedSender::Process() {
  int64_t now_us = clock_->TimeInMicroseconds();
  rtc::CritScope cs(&critsect_);
  time_last_process_us_ = now_us;
  // last_send_time_us_是上次Process方法被执行时的时间点,elapsed_time_ms是上次
  // Process方法调用与本次调用的时间间隔,以毫秒计。
  int64_t elapsed_time_ms = (now_us - last_send_time_us_ + 500) / 1000;
  if (elapsed_time_ms > kMaxElapsedTimeMs) {
    RTC_LOG(LS_WARNING) << "Elapsed time (" << elapsed_time_ms
                        << " ms) longer than expected, limiting to "
                        << kMaxElapsedTimeMs << " ms";
    elapsed_time_ms = kMaxElapsedTimeMs;// 最大不能超过2秒,否则作修正。
  }
  // When congested we send a padding packet every 500 ms to ensure we won't get
  // stuck in the congested state due to no feedback being received.
  // TODO(srte): Stop sending packet in paused state when pause is no longer
  // used for congestion windows.
  // 先忽略。。
  if (paused_ || Congested()) {
    // We can not send padding unless a normal packet has first been sent. If we
    // do, timestamps get messed up.
    if (elapsed_time_ms >= kCongestedPacketIntervalMs && packet_counter_ > 0) {
      PacedPacketInfo pacing_info;
      size_t bytes_sent = SendPadding(1, pacing_info);
      alr_detector_->OnBytesSent(bytes_sent, elapsed_time_ms);
      last_send_time_us_ = clock_->TimeInMicroseconds();
    }
    return;
  }

  int target_bitrate_kbps = pacing_bitrate_kbps_;
  if (elapsed_time_ms > 0) {
    size_t queue_size_bytes = packets_->SizeInBytes();
    if (queue_size_bytes > 0) { // 有数据包待发送
      // Assuming equal size packets and input/output rate, the average packet
      // has avg_time_left_ms left to get queue_size_bytes out of the queue, if
      // time constraint shall be met. Determine bitrate needed for that.
      packets_->UpdateQueueTime(clock_->TimeInMilliseconds());
      int64_t avg_time_left_ms = std::max(
          1, queue_time_limit - packets_->AverageQueueTimeMs());
      int min_bitrate_needed_kbps =
          static_cast(queue_size_bytes * 8 / avg_time_left_ms);
      if (min_bitrate_needed_kbps > target_bitrate_kbps)
        target_bitrate_kbps = min_bitrate_needed_kbps;
    }
    // 更新media_budget的目标带宽
    media_budget_->set_target_rate_kbps(target_bitrate_kbps);
    // 更新预算
    UpdateBudgetWithElapsedTime(elapsed_time_ms);
  }

  last_send_time_us_ = clock_->TimeInMicroseconds();

  bool is_probing = prober_->IsProbing();
  PacedPacketInfo pacing_info;
  size_t bytes_sent = 0;
  size_t recommended_probe_size = 0;
  if (is_probing) {
    // 若当前间隔探测器有效,获取第一个cluster的pacing_info信息,每一个带宽值
    // 的探测对应一个Cluster,详见BitrateProber相关代码走读。
    pacing_info = prober_->CurrentCluster();
    // 推荐的最小探测间隔是2ms, recommended_probe_size是2ms的依当前带宽的数据量
    // 用法见下面while循环
    recommended_probe_size = prober_->RecommendedMinProbeSize();
  }
  // The paused state is checked in the loop since SendPacket leaves the
  // critical section allowing the paused state to be changed from other code.
  // 循环发送packets_队列中的RTP包,直到探测结束或bytes_sent >
  // recommended_probe_size,
  // 即本轮发送的字节总数超过了推荐探测间隔对应的字节数,因recommended_probe_size
  // 值代表2ms的数据量,此值比较小,所以一般while循环内只发送一次packet就会停止。
  while (!packets_->Empty() && !paused_ && !Congested()) {
    // Since we need to release the lock in order to send, we first pop the
    // element from the priority queue but keep it in storage, so that we can
    // reinsert it if send fails.
    // 从packets_队列中取出一个待发送RTP包(此时仅取出但不从队列中移除)
    const PacketQueueInterface::Packet& packet = packets_->BeginPop();
    // 发送pop出来的RTP数据包,其内由media_budget模块的bytes_remaining变量值来
    // 决定是否应该发送此RTP数据包,bytes_remaining用于表示本次剩余或理应发送的
    // 数据量。详见IntervalBudget相关代码
    if (SendPacket(packet, pacing_info)) {
      bytes_sent += packet.bytes;
      // Send succeeded, remove it from the queue.
      // 数据真正发送成功后,才将其从队列中移出。
      packets_->FinalizePop(packet);
      if (is_probing && bytes_sent > recommended_probe_size)
        break;
    } else {
      // Send failed, put it back into the queue.
      // 当发送数据失败,如上次发送数据量过多,本轮不应发送,则取消从队列中pop
      packets_->CancelPop(packet);
      break;
    }
  }

  // 依需要发送填充包,暂时忽略。。。
  if (packets_->Empty() && !Congested()) {
    // We can not send padding unless a normal packet has first been sent. If we
    // do, timestamps get messed up.
    if (packet_counter_ > 0) {
      int padding_needed =
          static_cast(is_probing ? (recommended_probe_size - bytes_sent)
                                      : padding_budget_->bytes_remaining());
      if (padding_needed > 0) {
        bytes_sent += SendPadding(padding_needed, pacing_info);
      }
    }
  }
  // 通过已发送数据字节数bytes_sent和当前时间来更新bitrate probe以计算出下次
  // Process方法调用时间点
  if (is_probing) {
    probing_send_failure_ = bytes_sent == 0;
    if (!probing_send_failure_)
      // 详见BitrateProbe中的ProbeSent代码走读,主要用于探测下次Process方法的调
      // 用时间点
      prober_->ProbeSent(clock_->TimeInMilliseconds(), bytes_sent);
  }
  // 忽略...
  alr_detector_->OnBytesSent(bytes_sent, elapsed_time_ms);
}

总结:

    在PacedSender.cc中最重要的就是Process和TimeUntilNextProcess方法,Process是主发送线程,其由外部其他逻辑驱动进行定时调用,每次调用Process的时间间隔是从TimeUntilNextProcess方法中获取的。此间隔值依带宽变化和本轮探测中已发送数据包的字节数来估算。

    在Process内部分2部分,发送数据前和发送数据后,发送前要依当前带宽和本轮Process和上次Process的时间间隔值来更新media_budget预算,以决定是否允许本轮发送数据。发送成功后要调用BitrateProber的ProbeSent方法来更新下次Process调用等待时间间隔。

    需进一步查看IntervalBudget和BitrateProber模块。

你可能感兴趣的:(webrtc)