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);
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM