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