webrtc 源码-发送端上行带宽调整

webrtc 版本:2021-04-23 master

1 主要内容

发送端带宽调整主要是根据 rtt、丢包率、带宽估计算法反馈的预估带宽 (TWCC、GCC) 、媒体协商设定的码率,来综合调整发送端带宽。
实际上,webrtc 一般有 3 种方法来调整发送端上行带宽:

  • 发送端带宽调整逻辑
  • Sendside-BWE by TWCC,即发送端基于延迟的带宽估计
  • REMB,即接收端发送的上行码率(可能来自 GCC 算法,也可能来自其它本地策略)

其中,发送端带宽调整依赖于后两种,而后两种也能够直接应用于发送带宽的改变。
应当注意的是,对于某个客户端来说,网络可以分为上下行链路,这里得到的带宽是上行链路的带宽。
发送端带宽调整得到的带宽值在 webrtc 中也被称作 stable-link-capacity,稳定估计值。对应 delay-based-bwe 得到的带宽一般是真实的 link-capacity。下面我们将会看到,无论是码率大小值限定,还是带宽调整策略,都能体现出稳定估计值的含义。

2 带宽调整

SendSideBandwidthEstimation 类是执行发送端带宽调整的主要类。

2.1 带宽初始化

初始化带宽目前发现有 4 种方式:

  • 默认初始化带宽,min: 5kpbs,start: 300kpbs,max: nan
  • 对端 sdp 中指定的 x-google-min-bitrate、x-google-start-bitrate、x-google-max-bitrate
  • 对端 sdp 中指定的 'b=AS',设定了最大码率,其他值默认
  • APP 层通过 PeerConnection->SetBitrate() 接口函数设置的码率

---> /modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc::SetBitrates()

void SendSideBandwidthEstimation::SetBitrates(
    absl::optional<DataRate> send_bitrate,
    DataRate min_bitrate,
    DataRate max_bitrate,
    Timestamp at_time) {
  // 设置码率上下限
  SetMinMaxBitrate(min_bitrate, max_bitrate);
  if (send_bitrate) {
    link_capacity_.OnStartingRate(*send_bitrate);
    // 设置初始码率
    SetSendBitrate(*send_bitrate, at_time);
  }
}

带宽调整将以 send_bitrate 为起始码率开始调整,码率调整最大值不会超过 max_bitrate,最小值不会超过 min_bitrate。

2.2 更新丢包率

每当收到对端反馈的 RR 包时,都会更新丢包率等参数:
---> /modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc::UpdatePacketsLost()

//
// /modules/congestion_controller/goog_cc/goog_cc_network_controld.cc::OnTransportLossReport() ---> this
// 每当收到对端的RR包时, 会触发调用此函数
//
void SendSideBandwidthEstimation::UpdatePacketsLost(int packets_lost,
                                                    int number_of_packets,
                                                    Timestamp at_time) {
  // 更新丢包反馈时间
  last_loss_feedback_ = at_time;
  // |first_report_time_|在|SendSideBandwidthEstimation|类构造函数中初始化为无限大
  // 第一次收到丢包反馈时, 这里设置为当前时间
  if (first_report_time_.IsInfinite())
    first_report_time_ = at_time;
 
  // Check sequence number diff and weight loss report
  // 如果两个RR包间对端应该收到的RTP包的数量大于0
  // 即两个RR包间本端有发送RTP包过去且对端有接收到至少一个包
  if (number_of_packets > 0) {
    // Accumulate reports.
    lost_packets_since_last_loss_update_ += packets_lost;
    expected_packets_since_last_loss_update_ += number_of_packets;
 
    // Don't generate a loss rate until it can be based on enough packets.
    // 本端起码发送过|kLimitNumPackets|(20)个RTP包
    if (expected_packets_since_last_loss_update_ < kLimitNumPackets)
      return;
 
    // 更新设置 上次因为丢包反馈而降低码率 为false
    has_decreased_since_last_fraction_loss_ = false;
    // 这里左移8bit(*256)是为了下面计算丢包率时从小数转化为整数
    int64_t lost_q8 = lost_packets_since_last_loss_update_ << 8;
    int64_t expected = expected_packets_since_last_loss_update_;
    // 计算丢包率(注意这里|last_fraction_loss_|变量为uint8类型的, 同时最大限制到255)
    last_fraction_loss_ = std::min<int>(lost_q8 / expected, 255);
 
    // Reset accumulators.
    // 累计变量清零
    lost_packets_since_last_loss_update_ = 0;
    expected_packets_since_last_loss_update_ = 0;
    // 更新报告丢包率的时间
    last_loss_packet_report_ = at_time;
    // 执行发送端带宽调整
    UpdateEstimate(at_time);
  }
  UpdateUmaStatsPacketsLost(at_time, packets_lost);
}

2.3 更新 rtt

在本端计算出 rtt 后,会应用设置给多个模块,其中就包括 SendSideBandwidthEstimation 模块:
---> /modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc::UpdateRtt()

void SendSideBandwidthEstimation::UpdateRtt(TimeDelta rtt, Timestamp at_time) {
  // Update RTT if we were able to compute an RTT based on this RTCP.
  // FlexFEC doesn't send RTCP SR, which means we won't be able to compute RTT.
  if (rtt > TimeDelta::Zero())
    last_round_trip_time_ = rtt;
 
  // ...
}

注意到这里并没有触发执行发送端带宽调整的主逻辑 UpdateEstimate() 函数。

2.4 根据带宽估计算法调整带宽上限

前面 SetBitrates() 初始化函数已经设置了最大码率,这里会再次根据带宽估计算法来调整码率上限,但是调整的码率永远不会大于初始化函数设定的码率。
Sendside-BWE 带宽估计算法计算出预估带宽后,会更新设置到本模块:
---> /modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc::UpdateDelayBasedEstimate()

void SendSideBandwidthEstimation::UpdateDelayBasedEstimate(Timestamp at_time,
                                                           DataRate bitrate) {
  // delay_based 算法预估码率, 作为潜在的发送端带宽调整的上限值
  delay_based_limit_ = bitrate.IsZero() ? DataRate::PlusInfinity() : bitrate;
}

可以看到,Sendside-BWE 在 webrtc 中也称为 DelayBased 算法。delay_based_limit_ 变量保存了 Sendside-BWE 算法预估的带宽值,此值会作为发送端带宽调整的上限值之一,即发送端带宽调整不会超过此值。
同样的,接收端也会通过 REMB 包反馈预估带宽:
---> /modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc::UpdateReceiverEstimate()

void SendSideBandwidthEstimation::UpdateReceiverEstimate(Timestamp at_time,
                                                         DataRate bandwidth) {
  // REMB包设置的预估码率, 作为潜在的发送端带宽调整的上限值
  receiver_limit_ = bandwidth.IsZero() ? DataRate::PlusInfinity() : bandwidth;
}

带宽上限调整:
---> /modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc::GetUpperLimit()

DataRate SendSideBandwidthEstimation::GetUpperLimit() const {
  DataRate upper_limit = std::min(delay_based_limit_, receiver_limit_);
  upper_limit = std::min(upper_limit, max_bitrate_configured_);
  // 默认不使能
  if (loss_based_bandwidth_estimation_.Enabled() &&
      loss_based_bandwidth_estimation_.GetEstimate() > DataRate::Zero()) {
    upper_limit =
        std::min(upper_limit, loss_based_bandwidth_estimation_.GetEstimate());
  }
  return upper_limit;
}

max_bitrate_configured_ 变量即 SetBitrates() 初始化函数设置的最大码率,不能超过它。
在后面 UpdateTargetBitrate() 函数中,所有需要被调整的带宽都不会超过 GetUpperLimit() 函数返回的值。

2.5 发送端带宽调整

执行发送端带宽调整的主要是 UpdateEstimate() 函数,会有两种情况触发此函数:

  • 收到 RR 包,且 RR 包大概 1 秒发送一次
  • 定时,25 ms 触发一次

2.5.1 历史最小码率表

在介绍 UpdateEstimate() 函数之前,需要先介绍一下 SendSideBandwidthEstimation 类维护的历史最小码率表 min_bitrate_history_:
---> /modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.h

std::deque<std::pair<Timestamp, DataRate> > min_bitrate_history_;

这是一个双向队列,里面存储了 [time, bps] 键值对,且按照 bps 从小到大排序 (对应表头到表尾)。在 UpdateMinHistory() 函数内更新:
---> /modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc::UpdateMinHistory()

 void SendSideBandwidthEstimation::UpdateMinHistory(Timestamp at_time) {
  // Remove old data points from history.
  // Since history precision is in ms, add one so it is able to increase
  // bitrate if it is off by as little as 0.5ms.
  // 去除最小码率表中过期的元素, 过期时间默认1秒
  while (!min_bitrate_history_.empty() &&
         at_time - min_bitrate_history_.front().first + TimeDelta::Millis(1) >
             kBweIncreaseInterval) {
    // 弹出队列头部元素
    min_bitrate_history_.pop_front();
  }
 
  // Typical minimum sliding-window algorithm: Pop values higher than current
  // bitrate before pushing it.
  // 去除最小码率表中大于当前码率的元素(保证了按照bps从小到大排序)
  while (!min_bitrate_history_.empty() &&
         current_target_ <= min_bitrate_history_.back().second) {
    // 弹出队列尾部元素
    min_bitrate_history_.pop_back();
  }
 
  // [at_time, current_target_]入|min_bitrate_history_|队列
  min_bitrate_history_.push_back(std::make_pair(at_time, current_target_));
}

其中,current_target_ 变量即为当前系统实际使用的预估码率,这个码率可能是 REMB、Sendside-BWE或发送端带宽调整逻辑提供的,三种方式都能设置此值。
min_bitrate_history_ 队列的主要作用是保存1秒内的 current_target_ 值,从小到大排序,当需要增加码率时,取出最小值,在最小值的基础上增加码率。且这个最小值只有在1秒后才会被过期删除,所以一秒内取 min_bitrate_history_ 队列的最小值都是同一个值。

2.5.2 执行最终的码率调整

下面开始看 UpdateEstimate() 函数:
---> /modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc::UpdateEstimate()

void SendSideBandwidthEstimation::UpdateEstimate(Timestamp at_time) {
  // |rtt_backoff_.rtt_limit_|默认3秒,
  // rtt_backoff_.CorrectedRtt() 返回距离上次更新rtt的时间间隔
  // 如果距离上次更新rtt的时间间隔大于3秒, 则说明可能网络拥塞很严重
  if (rtt_backoff_.CorrectedRtt(at_time) > rtt_backoff_.rtt_limit_) {
    // 距离上次降低码率的时间间隔大于等于阈值
    // 当前码率大于最低值
    if (at_time - time_last_decrease_ >= rtt_backoff_.drop_interval_ &&
        current_target_ > rtt_backoff_.bandwidth_floor_) {
      // 更新码率降低的时刻
      time_last_decrease_ = at_time;
      // 降低码率(默认降低20%), 最低为|rtt_backoff_.bandwidth_floor_|(默认5kbps)
      DataRate new_bitrate =
          std::max(current_target_ * rtt_backoff_.drop_fraction_,
                   rtt_backoff_.bandwidth_floor_.Get());
      link_capacity_.OnRttBackoff(new_bitrate, at_time);
      // 更新目标(实际为输出编码器)码率
      UpdateTargetBitrate(new_bitrate, at_time);
      return;
    }
    // TODO(srte): This is likely redundant in most cases.
    ApplyTargetLimits(at_time);
    return;
  }
 
  // We trust the REMB and/or delay-based estimate during the first 2 seconds if
  // we haven't had any packet loss reported, to allow startup bitrate probing.
  /**
   * 1. 没有收到对端RR包的丢包统计(丢包率), 或没有发生丢包
   * 2. 在开始的2秒钟内
   */
  if (last_fraction_loss_ == 0 && IsInStartPhase(at_time)) {
    // 目标(实际为输出编码器)码率|current_target_|变量在类构造函数中初始化为0
    // 在SetBitrates()初始化函数中被初始化(默认300k)
    DataRate new_bitrate = current_target_;
    // TODO(srte): We should not allow the new_bitrate to be larger than the
    // receiver limit here.
    /**
     * |receiver_limit_|即依赖REMB接收端预估的带宽估计值
     * |delay_based_limit_|即依赖Transport-CC发送端预估的带宽估计值
     * 三者取最大值作为new_bitrate的值
     */
    if (receiver_limit_.IsFinite())
      new_bitrate = std::max(receiver_limit_, new_bitrate);
    if (delay_based_limit_.IsFinite())
      new_bitrate = std::max(delay_based_limit_, new_bitrate);
    // 默认不使能
    if (loss_based_bandwidth_estimation_.Enabled()) {
      loss_based_bandwidth_estimation_.SetInitialBitrate(new_bitrate);
    }
 
    if (new_bitrate != current_target_) {
      min_bitrate_history_.clear();
      if (loss_based_bandwidth_estimation_.Enabled()) {
        min_bitrate_history_.push_back(std::make_pair(at_time, new_bitrate));
      } else {
        min_bitrate_history_.push_back(
            std::make_pair(at_time, current_target_));
      }
      // 更新目标(实际为输出编码器)码率
      UpdateTargetBitrate(new_bitrate, at_time);
      return;
    }
  }
  // 更新最小码率表, 去掉过期的元素(超过1s), 并删除掉所有比当前码率current_bitrate_大的元素, current_bitrate_保存在表尾
  // 所以历史最小码率表就是这样一张表: 保存1s内系统当前的预估码率, 有序, 表尾最大值是最后一次预估的码率
  UpdateMinHistory(at_time);
  // 如果还没有收到过RR包
  if (last_loss_packet_report_.IsInfinite()) {
    // No feedback received.
    // TODO(srte): This is likely redundant in most cases.
    ApplyTargetLimits(at_time);
    return;
  }
 
  // 默认不使能
  if (loss_based_bandwidth_estimation_.Enabled()) {
    loss_based_bandwidth_estimation_.Update(
        at_time, min_bitrate_history_.front().second, last_round_trip_time_);
    DataRate new_bitrate = MaybeRampupOrBackoff(current_target_, at_time);
    UpdateTargetBitrate(new_bitrate, at_time);
    return;
  }
 
  // 当前距上次收到RR包的时间
  // 如果是收到RR包才触发的此函数, 则此变量为0
  // 如果是定时触发, 则可能为任意大于0的值
  TimeDelta time_since_loss_packet_report = at_time - last_loss_packet_report_;
  // 如果time_since_loss_packet_report < 1.2*5=6秒, 则执行带宽调整, 否则不调整
  if (time_since_loss_packet_report < 1.2 * kMaxRtcpFeedbackInterval) {
    // We only care about loss above a given bitrate threshold.
    // 将丢包率转换为小数
    float loss = last_fraction_loss_ / 256.0f;
    // We only make decisions based on loss when the bitrate is above a
    // threshold. This is a crude way of handling loss which is uncorrelated
    // to congestion.
    /**
     * 默认|bitrate_threshold_|为0, |low_loss_threshold_|为0.02
     * 所以这里主要是检查丢包率是否小于0.02, 如果小于, 则认为网络很好,
     * 可以取历史码率表中的第一个元素, 也就是记录的码率最小值(按码率从小到大排序, 所以是历史最小码率),
     * 在最小码率的基础上增加8%的码率
     */
    if (current_target_ < bitrate_threshold_ || loss <= low_loss_threshold_) {
      // Loss < 2%: Increase rate by 8% of the min bitrate in the last
      // kBweIncreaseInterval.
      // Note that by remembering the bitrate over the last second one can
      // rampup up one second faster than if only allowed to start ramping
      // at 8% per second rate now. E.g.:
      //   If sending a constant 100kbps it can rampup immediately to 108kbps
      //   whenever a receiver report is received with lower packet loss.
      //   If instead one would do: current_bitrate_ *= 1.08^(delta time),
      //   it would take over one second since the lower packet loss to achieve
      //   108kbps.
      // 因为历史最小码率表只会删除保存时间大于kBweIncreaseInterval即1秒的码率记录,
      // 所以如果考虑几秒内码率都在上升, 则定时触发下, 1秒内min_bitrate_history_.front()都是返回的同一个值
      // 所以码率增加的逻辑是1秒内在上一秒的基础上增加8%的码率
      // 实际上这个增加速度并不快, 增加预估码率最快的是Sendside-BWE算法和REMB包通知
      DataRate new_bitrate = DataRate::BitsPerSec(
          min_bitrate_history_.front().second.bps() * 1.08 + 0.5);
 
      // Add 1 kbps extra, just to make sure that we do not get stuck
      // (gives a little extra increase at low rates, negligible at higher
      // rates).
      // 额外增加1kbps
      new_bitrate += DataRate::BitsPerSec(1000);
      // 更新设置目标(输出编码器)码率
      UpdateTargetBitrate(new_bitrate, at_time);
      return;
    } else if (current_target_ > bitrate_threshold_) { // 如果丢包率大于2%
      // 但是小于|high_loss_threshold_|(10%), 不做任何调整, 维持当前码率
      if (loss <= high_loss_threshold_) {
        // Loss between 2% - 10%: Do nothing.
      } else { // 丢包率大于10%, 需要降低码率
        // Loss > 10%: Limit the rate decreases to once a kBweDecreaseInterval
        // + rtt.
        // 以(kBweDecreaseInterval(300ms) + last_round_trip_time_(rtt))为最小时间间隔来降低码率
        // 只有收到RR包, |has_decreased_since_last_fraction_loss_|才会被设置为false
        // 定时触发逻辑每隔25ms调用一次本函数, 不可能每次定时触发都降低码率
        if (!has_decreased_since_last_fraction_loss_ &&
            (at_time - time_last_decrease_) >=
                (kBweDecreaseInterval + last_round_trip_time_)) {
          time_last_decrease_ = at_time;   // 更新本次降低码率的时刻
 
          // Reduce rate:
          //   newRate = rate * (1 - 0.5*lossRate);
          //   where packetLoss = 256*lossRate;
          // 降低幅度为50%的丢包率, 所以一次最小降低50%*10%=5%
          DataRate new_bitrate = DataRate::BitsPerSec(
              (current_target_.bps() *
               static_cast<double>(512 - last_fraction_loss_)) /
              512.0);
          // 更新设置|has_decreased_since_last_fraction_loss_|为true,
          // 在UpdatePacketsLost()函数即收到RR包时设置为false
          has_decreased_since_last_fraction_loss_ = true;
          // 更新设置目标(输出编码器)码率
          UpdateTargetBitrate(new_bitrate, at_time);
          return;
        }
      }
    }
  }
  // TODO(srte): This is likely redundant in most cases.
  ApplyTargetLimits(at_time);
}

可以看到:

  • 在开始2秒钟内,一般直接使用 sendside-bwe 算法和 REMB 包设置的码率
  • 后面,以丢包率为依据来决定码率的升降
  • 丢包率小于2%,每1秒升8%的码率
  • 大于2%但小于10%,不调整
  • 大于10%,每 rtt+300ms 的时间降低丢包率的 50%
  • 带宽调整的最终值不超过 sendside-bwe 算法和 REMB 包设置的码率,以及不过超过 SetBitrates() 初始化函数设置的范围

2.5.3 应用调整后的码率

SendSideBandwidthEstimation::current_target 变量记录了最终的码率值,然后设置到 LinkCapacityTracker 模块,等待上层 GoogCcNetworkController 类调用:
---> /modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc::UpdateTargetBitrate()

void SendSideBandwidthEstimation::UpdateTargetBitrate(DataRate new_bitrate,
                                                      Timestamp at_time) {
  // GetUpperLimit()得到delay_based_limit_和receiver_limit_设置的上限值
  // 新的发送端带宽调整值不能超过上限值
  new_bitrate = std::min(new_bitrate, GetUpperLimit());
  if (new_bitrate < min_bitrate_configured_) {
    MaybeLogLowBitrateWarning(new_bitrate, at_time);
    new_bitrate = min_bitrate_configured_;
  }
  // 更新|current_target_|
  current_target_ = new_bitrate;
  MaybeLogLossBasedEvent(at_time);
  // ---> LinkCapacityTracker::OnRateUpdate()
  link_capacity_.OnRateUpdate(acknowledged_rate_, current_target_, at_time);
}
 
void SendSideBandwidthEstimation::ApplyTargetLimits(Timestamp at_time) {
  UpdateTargetBitrate(current_target_, at_time);
}
posted @ 2022-02-17 11:17  小夕nike  阅读(1291)  评论(0编辑  收藏  举报