前言
- WebRTC GCC-基于丟包的碼率估計原理一文發(fā)布已有三年之久腰耙,隨著webrtc 代碼的不斷更新办绝,擁塞控制部分谷歌也一直在更新著觉。
- 當(dāng)前基于丟包的碼率估計部分已經(jīng)額外新拓展了兩個分支,總共已有三套算法著角。
- 其中最原始的算法基于靜態(tài)丟包閾值進行計算敲长,本文分析分析
LossBasedBandwidthEstimation
簡稱為V1版本,從代碼來看主要是對丟包閾值進行了動態(tài)化處理。 - 本文首先簡單總結(jié)基于Base版本基于丟包的碼率估計原理堕花。
Base版本基于丟包的碼率估計原理總結(jié)
-
在分析之前先簡單回顧最原始的版本的核心原理
001.png - 在
Gcc-analysis
論文中有定義如上策略。 - 當(dāng)丟包率小于
%2
的時候碼率按照1.05
倍遞增,其中代碼實現(xiàn)中的遞增系數(shù)為1.08
,代碼實現(xiàn)中基準(zhǔn)值是在一秒內(nèi)的最小碼率的基礎(chǔ)上進行遞增粥鞋。 - 而當(dāng)丟包率大于
%10
的時候按照newRate = rate * (1 - 0.5*lossRate)
進行衰減,代碼實現(xiàn)中需要考慮每次遞減的時間間隔300ms+RTT
- 當(dāng)丟包率在
%2~10%
之間則維持不變缘挽。 - 此類策略在高碼率、且低延遲的場景中呻粹,存在十分大的缺陷壕曼,比如說
20Mbps
的實時碼率,假設(shè)丟包率達到5%
等浊,并且RTT
超過30Ms
以上腮郊,壓根無法保證低延遲,類似云游戲場景筹燕,對丟包的容忍度十分低轧飞。
LossBasedBandwidthEstimation 丟包率和Ack碼率更新
002.png
- 通過
tcc feedback
報文對LossBasedBandwidthEstimation
模塊中的average_loss_(平均丟包率)、average_loss_max_(平均最大丟包率)撒踪、以及acknowledged_bitrate_max_(最大應(yīng)答)碼率
進行實時更新过咬。
LossBasedBandwidthEstimation 平均丟包率更新
void LossBasedBandwidthEstimation::UpdateLossStatistics(
const std::vector<PacketResult>& packet_results,
Timestamp at_time) {
// 無反饋直接返回
if (packet_results.empty()) {
RTC_DCHECK_NOTREACHED();
return;
}
int loss_count = 0;
for (const auto& pkt : packet_results) {
loss_count += !pkt.IsReceived() ? 1 : 0;
}
// 計算丟包率(當(dāng)前丟包個數(shù)/當(dāng)前反饋總個數(shù))
last_loss_ratio_ = static_cast<double>(loss_count) / packet_results.size();
// 計算距離上次tcc反饋所流逝的時間間隔
const TimeDelta time_passed = last_loss_packet_report_.IsFinite()
? at_time - last_loss_packet_report_
: TimeDelta::Seconds(1);
// 更新上次丟包反饋時間為當(dāng)前tcc反饋時間
last_loss_packet_report_ = at_time;
has_decreased_since_last_loss_report_ = false;
// 對丟包率進行指數(shù)平滑,默認(rèn)平滑窗口為800ms,其中time_passed(兩次tcc feedback的間隔越大)
// 則當(dāng)前的平均丟包率越逼近與當(dāng)前的丟包率
average_loss_ += ExponentialUpdate(config_.loss_window, time_passed) *
(last_loss_ratio_ - average_loss_);
if (average_loss_ > average_loss_max_) {
average_loss_max_ = average_loss_;
} else {
// 對最大平均丟包率進行指數(shù)平滑,同理兩次tcc feedback的間隔越大,平均最大丟包率越逼近于當(dāng)前的平均丟包率
average_loss_max_ +=
ExponentialUpdate(config_.loss_max_window, time_passed) *
(average_loss_ - average_loss_max_);
}
}
- 其中
ExponentialUpdate()
函數(shù)的實現(xiàn)如下:
/**
*參數(shù)interval:為兩次tcc feedback的時間間隔
*參數(shù)window:默認(rèn)800ms
*/
double ExponentialUpdate(TimeDelta window, TimeDelta interval) {
// Use the convention that exponential window length (which is really
// infinite) is the time it takes to dampen to 1/e.
if (window <= TimeDelta::Zero()) {
return 1.0f;
}
return 1.0f - exp(interval / window * -1.0);
}
- 首先回顧已
e^x
次方的函數(shù)圖像:
003.png - 很明顯
ExponentialUpdate
函數(shù)為一個指數(shù)遞減函數(shù),當(dāng)interval
越大(表示兩次tcc feedback
的間隔越大)制妄,則該函數(shù)的返回值會越大掸绞,則平均丟包率越逼近于本次tcc feedback
計算出來的丟包率。
LossBasedBandwidthEstimation Ack碼率更新
void LossBasedBandwidthEstimation::UpdateAcknowledgedBitrate(
DataRate acknowledged_bitrate,
Timestamp at_time) {
const TimeDelta time_passed =
acknowledged_bitrate_last_update_.IsFinite()
? at_time - acknowledged_bitrate_last_update_
: TimeDelta::Seconds(1);
acknowledged_bitrate_last_update_ = at_time;
// 更新最大ack碼率
if (acknowledged_bitrate > acknowledged_bitrate_max_) {
acknowledged_bitrate_max_ = acknowledged_bitrate;
} else {
// 同理當(dāng)time_passed越大的時候這個ack碼率的最大值會越逼近當(dāng)前tcc feedback的碼率值
acknowledged_bitrate_max_ -=
ExponentialUpdate(config_.acknowledged_rate_max_window, time_passed) *
(acknowledged_bitrate_max_ - acknowledged_bitrate);
}
}
LossBasedBandwidthEstimation 計算基于丟包的碼率
- 如果把該模塊當(dāng)初一個小黑盒,那么基于
Ack
碼率,調(diào)用Update()
函數(shù)最終會輸出一個lost
丟包率的碼率耕捞。
004.png
void SendSideBandwidthEstimation::UpdateEstimate(Timestamp at_time) {
...
if (LossBasedBandwidthEstimatorV1ReadyForUse()) {
DataRate new_bitrate = loss_based_bandwidth_estimator_v1_.Update(
at_time, min_bitrate_history_.front().second, delay_based_limit_,
last_round_trip_time_);
UpdateTargetBitrate(new_bitrate, at_time);
return;
}
...
}
/**
* min_bitrate:為1秒內(nèi)最小碼率
* wanted_bitrate: 為基于延遲delay_based算出來的碼率信息(也是基于twcc+aimd)模塊估算出來的
*/
DataRate LossBasedBandwidthEstimation::Update(Timestamp at_time,
DataRate min_bitrate,
DataRate wanted_bitrate,
TimeDelta last_round_trip_time) {
// 這里應(yīng)該為初始狀態(tài),未收到feedback之前
if (loss_based_bitrate_.IsZero()) {
loss_based_bitrate_ = wanted_bitrate;
}
// Only increase if loss has been low for some time.
// 是否增加帶寬使用平均最大丟包率和閾值進行比較
const double loss_estimate_for_increase = average_loss_max_;
// Avoid multiple decreases from averaging over one loss spike.
// 降碼率的條件取當(dāng)前丟包率和平均碼率的最小值
const double loss_estimate_for_decrease =
std::min(average_loss_, last_loss_ratio_);
// 允許降低碼率的條件為首先:上一次feedback未降低碼率衔掸、其次:兩次feedback之間的間隔為當(dāng)前rtt + 300ms
// 這個300毫秒個人覺得對于高碼率的應(yīng)用場景有點太高了
const bool allow_decrease =
!has_decreased_since_last_loss_report_ &&
(at_time - time_last_decrease_ >=
last_round_trip_time + config_.decrease_interval);
// If packet lost reports are too old, dont increase bitrate.
// 兩次twcc feedback的反饋間隔在6秒內(nèi),則認(rèn)為這個lost_report是有效的(6秒內(nèi)對于高碼率場景是不是太久了點?)
const bool loss_report_valid =
at_time - last_loss_packet_report_ < 1.2 * kMaxRtcpFeedbackInterval;
// 1) 平均丟包率的最大值比reset閾值要小,則認(rèn)為網(wǎng)絡(luò)可能不擁塞了,這里直接取delay_based的碼率
if (loss_report_valid && config_.allow_resets &&
if (loss_report_valid && config_.allow_resets &&
loss_estimate_for_increase < loss_reset_threshold()) {
loss_based_bitrate_ = wanted_bitrate;
} else if (loss_report_valid &&
loss_estimate_for_increase < loss_increase_threshold()) {
// Increase bitrate by RTT-adaptive ratio.
//2)平均丟包率最大值比loss_increase_threshold閾值小則增加碼率,以GetIncreaseFactor()作為系數(shù)
// 當(dāng)前1秒內(nèi)最小碼率作為base進行遞增,并且遞增規(guī)則是和RTT相關(guān)的,其中這個GetIncreaseFactor(config_, last_round_trip_time)
// 的返回值在[1.02,1.08]之間砸脊,當(dāng)RTT越大,這個因子越逼近1.02,也就是緩慢增加,當(dāng)RTT越小則越逼近1.08,也就是快速增加
// 而config_.increase_offset為1kbps纬霞,是一個補償
DataRate new_increased_bitrate =
min_bitrate * GetIncreaseFactor(config_, last_round_trip_time) +
config_.increase_offset;
// The bitrate that would make the loss "just high enough".
// 確保遞增的碼率在預(yù)設(shè)的范圍內(nèi)new_increased_bitrate_cap = 0.5kbps * (1/average_loss_max_)^2
// 這個丟包率越小凌埂,可遞增到的碼率值會越大,假設(shè)0.001的丟包率,那么能增加到的碼率值為500Mbps...
const DataRate new_increased_bitrate_cap = BitrateFromLoss(
loss_estimate_for_increase, config_.loss_bandwidth_balance_increase/*0.5kbps*/,
config_.loss_bandwidth_balance_exponent/*0.5*/);
// 所以這里會限制最大能增加的范圍,也就是說丟包率越低诗芜,會越接近于GetIncreaseFactor計算出來的結(jié)果
new_increased_bitrate =
std::min(new_increased_bitrate, new_increased_bitrate_cap);
loss_based_bitrate_ = std::max(new_increased_bitrate, loss_based_bitrate_);
} else if (loss_estimate_for_decrease > loss_decrease_threshold() &&
allow_decrease) {
// The bitrate that would make the loss "just acceptable".
//3)當(dāng)前最小丟包值比loss_decrease_threshold閾值大則進行帶寬遞減
// new_decreased_bitrate_floor = 4kbps * (1/loss_estimate_for_decrease)^2
// 假設(shè)10%的丟包率,那么最低能降到400Kbps,丟包率越大能降低到的程度就會越大瞳抓,最終new_decreased_bitrate_floor就會越小
const DataRate new_decreased_bitrate_floor = BitrateFromLoss(
loss_estimate_for_decrease, config_.loss_bandwidth_balance_decrease/*4kbps*/,
config_.loss_bandwidth_balance_exponent/*0.5*/);
// decreased_bitrate()為0.99倍的ack最大碼率,這里是取0.99 * ack_max和new_decreased_bitrate_floor的最大值
DataRate new_decreased_bitrate =
std::max(decreased_bitrate(), new_decreased_bitrate_floor);
// 如果新遞減后的碼率比loss_based_bitrate_要小,設(shè)置loss_based_bitrate_為最小值
if (new_decreased_bitrate < loss_based_bitrate_) {
time_last_decrease_ = at_time;
has_decreased_since_last_loss_report_ = true;
loss_based_bitrate_ = new_decreased_bitrate;
}
}
return loss_based_bitrate_;
}
- 原理上事實上和
Base
版本基本一致。 - 當(dāng)平均丟包的最大值小于
loss_increase_threshold()
的時候進行碼率遞增伏恐。 - 當(dāng)平均丟包的最大值小于
loss_reset_threshold()
的時候碼率維持不變孩哑。 - 當(dāng)平均最小丟包率大于
loss_decrease_threshold()
的時候進行碼率遞減。
GetIncreaseFactor遞增因子計算原理
// Increase slower when RTT is high.
double GetIncreaseFactor(const LossBasedControlConfig& config, TimeDelta rtt) {
// Clamp the RTT
// 如果當(dāng)前rtt小于200ms翠桦,則取rtt為200Ms
if (rtt < config.increase_low_rtt) {
rtt = config.increase_low_rtt;
} else if (rtt > config.increase_high_rtt) {//800ms
// 如果當(dāng)前rtt大于200ms横蜒,則取rtt為800ms
rtt = config.increase_high_rtt;
}
// 這里其實就是限制rtt的范圍為[increase_low_rtt, increase_high_rtt]
// 默認(rèn)實現(xiàn)不成立,假設(shè)強制設(shè)置不成立,則返回config.min_increase_factor胳蛮,默認(rèn)為1.02
auto rtt_range = config.increase_high_rtt.Get() - config.increase_low_rtt;
if (rtt_range <= TimeDelta::Zero()) {
RTC_DCHECK_NOTREACHED(); // Only on misconfiguration.
return config.min_increase_factor;
}
// modify rtt - 200
auto rtt_offset = rtt - config.increase_low_rtt;
// relative_offset限制在[0,1.0]之間
auto relative_offset = std::max(0.0, std::min(rtt_offset / rtt_range, 1.0));
// 1.08 - 1.02 = 0.06
auto factor_range = config.max_increase_factor - config.min_increase_factor;
// 1.02 + 0.06 * (1 - relative_offset) ,其中relative_offset為rtt_offset / rtt_range小于1
return config.min_increase_factor + (1 - relative_offset) * factor_range;
}
-
config.min_increase_factor
默認(rèn)為1.02
,config.max_increase_factor
默認(rèn)為1.08
丛晌。 - 從上述實現(xiàn)來看仅炊,遞增的規(guī)則為,最小系數(shù)為
1.02
澎蛛,最大為1.08
,當(dāng)RTT
越小這個增加因子會越逼近于1,08
也就是碼率增加得越快抚垄。 - 當(dāng)
RTT
越大則越逼近1.02
,也就是碼率增加得相對越緩慢一些谋逻。
BitrateFromLoss帶寬增加或減少閾值計算原理
DataRate BitrateFromLoss(double loss,
DataRate loss_bandwidth_balance,
double exponent) {
if (exponent <= 0) {
RTC_DCHECK_NOTREACHED();
return DataRate::Infinity();
}
// 這里注意,如果丟包率小于十萬分之1,那么返回正無窮呆馁,這樣每次帶寬增加會按照[1.02,1.08]*(一秒內(nèi)最小碼率)遞增
if (loss < 1e-5)
return DataRate::Infinity();
return loss_bandwidth_balance * pow(loss, -1.0 / exponent);
}
-
loss
為丟包率。 -
loss_bandwidth_balance
為因丟包導(dǎo)致的帶寬損耗毁兆,舉個例子假設(shè)loss
為0.05
浙滤,當(dāng)前帶寬為bitrate
,那么重傳導(dǎo)致的帶寬損耗為bitrate * 0.05
荧恍。 - 這里的思想就是每次重傳引入的帶寬損耗為
loss_bandwidth_balance = bitate * loss
瓷叫。 - 那么
N
次重傳引入的帶寬損耗為loss_bandwidth_balance = bitate * loss^N
次方。 - 有了上述的思想送巡,再來反推摹菠,已知
loss(丟包率)
、loss_bandwidth_balance(帶寬損耗)
骗爆、exponent(重傳次數(shù)的倒數(shù))
來求當(dāng)前的bitrate(當(dāng)前帶寬信息)
次氨。 - 反推公式就為
bitate = loss_bandwidth_balance * pow(loss, -1.0 / exponent) = loss_bandwidth_balance * (1/loss)^(1/exponent)
。 - 有了如上的推導(dǎo)和思路的理解后再回過頭分析摘投,代碼增加和降低的邏輯就不難分析了煮寡。
LossBasedBandwidthEstimation 動態(tài)丟包率閾值的計算原理
double LossFromBitrate(DataRate bitrate,
DataRate loss_bandwidth_balance,
double exponent) {
if (loss_bandwidth_balance >= bitrate)
return 1.0;
return pow(loss_bandwidth_balance / bitrate, exponent);
}
- 已知
bitrate(目標(biāo)碼率)
、loss_bandwidth_balance(帶寬損耗碼率)
犀呼、exponent損耗次數(shù)的倒數(shù)
幸撕,求丟包率 - 上節(jié)提到
N
次重傳引入的帶寬損耗為loss_bandwidth_balance = bitate * loss^N
次方。 - 反過來
loss = (loss_bandwidth_balance / bitate)^(1/N) = std::pow(loss_bandwidth_balance / bitrate, 1/N)
外臂,其中1/N = exponent
坐儿。
double LossBasedBandwidthEstimation::loss_reset_threshold() const {
// (0.1 / bitrate)^(1/2),表示的是在目標(biāo)碼率為loss_based_bitrate_,兩次損耗帶寬為0.1kbps情況下的損耗率(丟包率)
return LossFromBitrate(loss_based_bitrate_,
config_.loss_bandwidth_balance_reset,
config_.loss_bandwidth_balance_exponent);
}
- 假設(shè)按照
30fps
,每幀一個包,每個包的大小1000
字節(jié)宋光,也就是8000bit
來算貌矿,也就是目標(biāo)碼率為8000 * 30 = 240kbps
,這樣算出來的丟包率大約為0.02041241452
也就是2%
的丟包率。意思是當(dāng)平均最大丟包率小于這個值的時候罪佳,保持碼率不變逛漫。 - 而從上述公式來看,當(dāng)實時碼率也就是
loss_based_bitrate_
越大赘艳,這個丟包率的閾值越小酌毡,對丟包率的容忍度越低克握,這個看上起是符合預(yù)期的
double LossBasedBandwidthEstimation::loss_increase_threshold() const {
//(0.5 / bitrate)^(1/2),表示的是在目標(biāo)碼率為loss_based_bitrate_,兩次損耗帶寬為0.5kbps情況下的損耗率(丟包率)
return LossFromBitrate(loss_based_bitrate_,
config_.loss_bandwidth_balance_increase,
config_.loss_bandwidth_balance_exponent);
}
- 對于碼率增加的動態(tài)丟包閾值,也是一樣的阔馋,當(dāng)實時碼率越大玛荞,那么要想增加碼率,則期望的丟包率越小越有可能呕寝,同樣是碼率越大勋眯,丟包的容忍度越低。
double LossBasedBandwidthEstimation::loss_decrease_threshold() const {
//(4 / bitrate)^(1/2),表示的是在目標(biāo)碼率為loss_based_bitrate_,兩次損耗帶寬為4kbps情況下的損耗率(丟包率)
return LossFromBitrate(loss_based_bitrate_,
config_.loss_bandwidth_balance_decrease,
config_.loss_bandwidth_balance_exponent);
}
- 而對于遞減邏輯來看下梢,實時碼率越大客蹋,算出來的丟包率閾值也同樣是越小,也就是說當(dāng)碼率越高孽江,那丟包率稍微上去就有可能觸發(fā)帶寬遞減邏輯讶坯,同樣是碼率越大,丟包容忍度越低岗屏。
總結(jié)
- 從
LossBasedBandwidthEstimation
模塊實現(xiàn)來看辆琅,其根本的碼率升降和維持邏輯和原始Base
版本是差不多的。 - 不同點在于在丟包率閾值的決策上使用了動態(tài)計算來進行處理这刷,同時這個動態(tài)丟包率閾值的計算借助了每次重傳帶寬損失的思想婉烟。
- 在目標(biāo)碼率為
bitrate
的情況下,假設(shè)丟包率為lost
暇屋,那么第一次重傳的帶寬損失為bitrate * lost
似袁,而第二次重傳是基于上次丟失包的情況下進行重傳,所以第N
次的重傳帶寬損失為bitrate * lost^N
咐刨。 - 了解重傳對帶寬損失的思想后再去分析該模塊相對就容易理解昙衅。