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