WEBRTC浅析(六)拥塞控制GCC的简介以及WEBRTC中的实现
导读
本文分为两部分:
第一部分介绍了谷歌的GCC的文档。其中主要介绍了GCC的各个模块,以及具体算法实现。
第二部分,结合了webrtc来看GCC各个模块的具体实现。
一:GCC(Congestion Control Algorithm)文档介绍
原文:Congestion Control Algorithm
-
两种拥塞控制方法
- 基于 RTCWEB的拥塞控制
- 一个基于延迟和损失
-
拥塞控制是对应用程序的所有网络资源的调控。
-
实时流媒体的拥塞控制策略面临的几大挑战:
- 媒体的编码经常无法快速的适应带宽的变化。
- 与会人可能会对如何响应有特殊的需求,比如在发现拥塞后不想降低带宽。
- 编码对丢包非常敏感,因为对实时性的要求限制了通过重传去修复传输中丢失的媒体包。
反馈和扩展
-
the controllers are running at the send-side
-
a delay-based controller at the receive-side
发送Engine
- Pace send 模块用来控制实际发送的码率。
- burst_time 是 5 ms
基于延迟的拥塞控制(The delay-based control)
可以分为四个部分:a pre-filtering, an arrival-time filter, an over-use detector,and a rate controller.
-
an adaptive filter::持续的更新网络带宽,根据收到包的时间。
-
内部到达时间(inter-arrival time) : t(i)
- 内部到达时间之差(the difference inarrival time) : t(i) - t(i-1)
-
内部离开时间(inter-departure time) : T(i)
- 内部离开时间之差(the difference in departure-time) : T(i) - T(i-1)
-
内部延迟(the inter-group delay) : d(i)
- d(i) = t(i) - t(i-1) - (T(i) - T(i-1))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P4fs5Alk-1625995097095)(./1.png)] - Any packets received out of order are ignored by the arrival-time model
- d(i) = t(i) - t(i-1) - (T(i) - T(i-1))
-
组内延迟公式(inter-group delay):
- d(i) = w(i)
- If weare over-using the channel we expect the mean of w(i) to increase,
- if a queue on the network path is being emptied, the mean of w(i)
will decrease; - otherwise the mean of w(i) will be zero
- d(i) = w(i)
-
把w(i)用 m(i) 和 v(i)拆分
- d(i) = m(i) + v(i)
- 网络排队延迟 : m(i)
- 零均值噪声(noise term) : v(i)
- 代表网络抖动和其他m(i)之外的抖动。(represents network jitter and other delay effects not captured by the model)
- d(i) = m(i) + v(i)
-
-
预过滤(The pre-filtering)
- 预过滤模块的作用是用来处理由信道突变导致的瞬时延迟。
- 划分到同一个组内的条件:
- 在burst_time间隔内,发送的一系列数据包。webrtc里是5ms。
- inter-arrival time 小于 burst_time,或者 d(i)(inter-group delay) 小于 0。
-
到达时间滤波器(Arrival-time Filter)
-
d(i): 当前测量值。这个可以通过收到的packet来计算出来。
-
m(i): 当前估计值。这个值可以用来检测网络是否过载(over-used)。
-
下面让我们来计算一下 i 时刻的 估计值 m(i)
1. m(i+1) = m(i) + u(i) 2. m_hat(i) = m_hat(i-1) + z(i) * k(i) 3. z(i) = d(i) - m_hat(i-1) e(i-1) + q(i) 4. k(i) = ---------------------------------------- var_v_hat(i) + (e(i-1) + q(i)) 5. e(i) = (1 - k(i)) * (e(i-1) + q(i)) 6. q(i) = E{u(i)^2} q(i) is RECOMMENDED equal to 10^-3
-
-
过载检测(over-use detector)
-
我们可以设置一个阈值(del_var_th)。通过检测这个del_var_th的值来判断是否over-use。这个del_var_th的值应该是动态的,以适应各个环境。
-
检测逻辑:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zbgAbh9q-1625995097097)(2.png)]
-
del_var_th 的计算公式
del_var_th(i) = del_var_th(i-1) + (t(i)-t(i-1)) * K(i) * (|m(i)|-del_var_th(i-1))
del_var_th(i) 的值建议在[6, 600]之间,因为太小的值会让检测结果变得太敏感。
-
-
速率控制器(rate control)
-
速率控制器可以分为两部分:
- 基于延迟的带宽估计
- 基于丢包的带宽估计
-
速率控制系统有三种状态: Increase, Decrease and Hold
+----+--------+-----------+------------+--------+ | \ State | Hold | Increase |Decrease| | \ | | | | | Signal\ | | | | +--------+----+-----------+------------+--------+ | Over-use | Decrease | Decrease | | +-------------+-----------+------------+--------+ | Normal | Increase | | Hold | +-------------+-----------+------------+--------+ | Under-use | | Hold | Hold | +-------------+-----------+------------+--------+
-
基于丢包的拥塞控制
前面介绍基于时延的控制是有一个假设前提,即传输通道的缓冲足够大。
当传输通道的缓冲很小时,通过时延是观测不到过载状态的,这时需要丢包率来表示过载
-
As_hat : 基于丢包的计算出的带宽(loss-based controller)
- rtt: round-trip time
- 丢包率: packet loss
- A_hat : 基于延迟计算出的码率(available bandwidth estimates
received from the delay-based controller) - 在信道容量大的时候,基于延迟的计算是有效的。但是小信道容量小的时候,基于丢包的估计更加准确。
-
计算 As_hat(i)
p :丢包率
- 丢包率2-10% , As_hat(i)保持不变。
- 丢包率大于10% , As_hat(i) = As_hat(i-1)(1-0.5p)
- 丢白率小于1% , As_hat(i) = 1.05(As_hat(i-1))
最终发送码率:
sending rate = min(As_hat(i), A_hat(i)) (取两者的最小值)
二: WebRTC 代码对照
基于延迟的带宽估计
数据流程图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5WbpG3hS-1625995097098)(flow.png)]
-
模块:DelayBasedBwe
-
作用:计算基于延迟的带宽
-
预过滤(The pre-filtering)
-
函数:InterArrival::ComputeDeltas
-
作用:过滤
-
输入:
uint32_t timestamp, int64_t arrival_time_ms, size_t packet_size
-
输出:
uint32_t* timestamp_delta, int64_t* arrival_time_delta_ms, int* packet_size_delta
-
code:
// 如果检测到是一个新的group if (NewTimestampGroup(arrival_time_ms, timestamp)) { // First packet of a later frame, the previous frame sample is ready. if (prev_timestamp_group_.complete_time_ms >= 0) { *timestamp_delta = current_timestamp_group_.timestamp - prev_timestamp_group_.timestamp; *arrival_time_delta_ms = current_timestamp_group_.complete_time_ms - prev_timestamp_group_.complete_time_ms; ....... *packet_size_delta = static_cast<int>(current_timestamp_group_.size) - static_cast<int>(prev_timestamp_group_.size); calculated_deltas = true; }
-
-
到达时间滤波器(Arrival-time Filter)
- 模块:TrendlineEstimator::Update
- 作用:在webrtc中,发送端没有使用卡尔曼滤波器,取而代之的是一个简单的过载滤波。
- 入参:滤波器需要的三个参数:发送时刻差值(delta_timestamp)、到达时刻差值(delta_arrival)和包组数据大小差值(delta_size)
- 输出:斜率
-
首先计算单个包组传输增长的延迟:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-16W9gSbo-1625995097100)(9.png)]
const double delta_ms = recv_delta_ms - send_delta_ms;
-
每个包组的叠加延迟
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dLcL2L3n-1625995097101)(02.png)]
// 累计延迟 accumulated_delay_ += delta_ms;
-
通过累积延迟计算一个均衡平滑延迟值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-osN9vg0S-1625995097103)(3.png)]smoothed_delay_ = smoothing_coef_ * smoothed_delay_ + (1 - smoothing_coef_) * accumulated_delay_;
-
将第i个包组的传输持续时间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l7Ek3TeC-1625995097104)(5.png)]
// trans = arrival_time_ms - first_arrival_time_ms // Simple linear regression. delay_hist_.push_back(std::make_pair( static_cast<double>(arrival_time_ms - first_arrival_time_ms), smoothed_delay_));
-
对累计延迟和均衡平滑延迟再求平均
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-luTmwrjq-1625995097105)(4.png)]
。。。。 for (const auto& point : delay_hist_) { sum_x += point.first; sum_y += point.second; } // x_avg 是平均累计延迟 double x_avg = sum_x / points.size(); // y_avg 是均衡平滑延迟值 double y_avg = sum_y / points.size();
-
趋势斜率分子值和分母值
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SsQKWXOp-1625995097106)(6.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NHds2jis-1625995097107)(7.png)]for (const auto& point : delay_hist_) { // 分子 numerator += (point.first - x_avg) * (point.second - y_avg); // 分母 denominator += (point.first - x_avg) * (point.first - x_avg); }
-
趋势值为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mzqFVqW7-1625995097108)(8.png)]
- Trendline filter通过到达时间差、发送时间差和数据大小来得到一个趋势增长值,如果这个值越大说明网络延迟越来越严重,如果这个值越小,说明延迟逐步下降.
-
过载检测(over-use detector)
-
函数:OveruseDetector::Detect
-
作用:估计出当前的延迟,计算当前带宽是否过载(over-use)。trendline乘以周期包组个数就是m_i
-
输入:
// offset = trendline_ * threshold_gain_ double offset // send 平均发送延迟增益 double timestamp_delta, // num_of_deltas_ int num_of_deltas,
-
输出:
enum class BandwidthUsage { kBwNormal = 0, kBwUnderusing = 1, kBwOverusing = 2, };
-
代码:
// 计算 m(i) double T = std::min(num_of_deltas, kMinNumDeltas) * offset; // 具体逻辑可以参考上文的 over-user检测逻辑 if (T >= threshold/* || (loss_rate >= 0.17f && T > threshold_in_loss)*/) { if (time_over_using_ == -1) { // Initialize the timer. Assume that we've been // over-using half of the time since the previous // sample. time_over_using_ = ts_delta / 2; } else { // Increment timer time_over_using_ += ts_delta; } overuse_counter_++; if ((time_over_using_ > overusing_time_threshold/* || (loss_rate >= 0.17f && time_over_using_ > overusing_time_threshold_in_loss)*/) /*&& overuse_counter_ > 1*/) { if (offset >= prev_offset_) { time_over_using_ = 0; overuse_counter_ = 0; hypothesis_ = BandwidthUsage::kBwOverusing; } } } else if (T < -threshold) { time_over_using_ = -1; overuse_counter_ = 0; hypothesis_ = BandwidthUsage::kBwUnderusing; } else { time_over_using_ = -1; overuse_counter_ = 0; hypothesis_ = BandwidthUsage::kBwNormal; }
-
函数:OveruseDetector::UpdateThreshold
-
作用:更新del_var_th(i) 的值,并限制在[6, 600]之间,因为太小的值会让检测结果变得太敏感
// 更新阈值(del_var_th) threshold_ += k * (fabs(modified_offset) - threshold_) * time_delta_ms; // 把阈值(del_var_th)限制在 [6, 600]之间 threshold_ = rtc::SafeClamp(threshold_, 6.f, 600.f);
-
-
速率控制器(rate control)
- 函数:AimdRateControl::ChangeBitrate
- 作用:根据不同的状态,采取不用的码率调节侧率
-
基于丢包的带宽估计
- 模块:SendSideBandwidthEstimation::UpdateEstimate
- 作用:根据丢包率,计算基于丢包的可用带宽。
-
丢包率2-10% , As_hat(i)保持不变。
-
丢包率大于10% , As_hat(i) = As_hat(i-1)(1-0.5p)
new_bitrate = static_cast<uint32_t>( (current_bitrate_bps_ * static_cast<double>(512 - last_fraction_loss_)) /512.0);
-
丢白率小于1% , As_hat(i) = 1.05(As_hat(i-1))
// webrtc中丢包的实现和Gcc文档里描述的机制还会略有出入 float beta = 1.08f; uint32_t increase_bps = std::max<uint32_t>(min_bitrate_history_.front().second*(beta - 1), kMinIncreaseBps); new_bitrate = static_cast<uint32_t>(min_bitrate_history_.front().second) + increase_bps + 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). new_bitrate += 1000;
-
最终发送码率的计算:
-
函数:SendSideBandwidthEstimation::CapBitrateToThresholds
-
公式:sending rate = min(As_hat(i), A_hat(i)) (取两者的最小值)
//如果当前bitrate(基于丢包的带宽)大于delay_based_bitrate_bps_(基于延时的带宽)时 if (delay_based_bitrate_bps_ > 0 && bitrate_bps > delay_based_bitrate_bps_) { bitrate_bps = delay_based_bitrate_bps_; } 。。。 // 把比较后的码率赋值给当前码率 current_bitrate_bps_ = bitrate_bps;