WebRTC 平滑发送与带宽评估全解析(万字长文)
目录
注:可在阅读器侧面大纲快速跳转
📖 基础篇
🔍 算法篇
- 吞吐量估计
BitrateEstimator逐行代码走读 - 探测器
BitrateProber:如何高效"探路"? - 丢包估计 V1:经典 EWMA 策略
- 丢包估计 V2:牛顿迭代 + 多候选
- 时延估计
DelayBasedBWE:趋势线守门员
⚙️ 系统篇
- RTT Backoff & 链路容量追踪
- 发送端融合器
SendSideBandwidthEstimation - 平滑发送核心
PacingController - 代码穿行:一次
ProcessPackets()的生命旅程
🛠️ 实战篇
📚 总结篇
引言:来自一线的卡顿血案
“为什么我只切了一次 PPT 页面,800 ms 的 RTT 突然飙到 4 s,连麦直接黑屏?”
真实线上事故:某双师课堂场景,老师操作共享屏幕时,编码器产生 2 MB 的关键帧。没有 Pacer 的旧版本客户端一次性将所有 RTP 包泼向 1 Mbps 的家庭宽带 —— 网口被写满 + AP Buffer 过载 + 丢包重传雪崩,最终雪崩蔓延至音频流。
结论:再强大的 BWE(带宽估计)也抵不过"洪水式"突发,需要 Pacer 对发送进行平滑化处理。
问题分析图
整体架构:七大模块协作图
图 1:WebRTC 发送端平滑发送与带宽评估协作图
数据流向时序图
八大核心模块:
- BitrateEstimator(BE) —— 基于 Transport-CC 的吞吐量窗口统计。
- LossBasedBWE v1/v2 —— 两代基于 RTCP 丢包反馈的估计算法。
- DelayBasedBWE(DB) —— 基于单向时延趋势的拥塞检测。
- RTT Backoff —— 时延超标快速回退保护机制。
- LinkCapacityTracker —— 链路可承载能力平滑追踪。
- BitrateProber(BP) —— 主动带宽探测与验证。
- SendSideBWE(SSE) —— 多源融合决策,输出 target_rate。
- PacingController(PC) —— 债务预算 + 优先级调度 + 平滑发送。
核心原则:先评估(BWE),后平滑(PC)。BWE 给速率,PC 负责节拍。
模块职责详解
| 模块 | 输入 | 输出 | 核心算法 |
|---|---|---|---|
| BitrateEstimator | Transport-CC 反馈 | 吞吐量估计 | 滑动窗口 + 贝叶斯融合 |
| LossBasedBWE v1 | RTCP 丢包率 | 丢包驱动带宽 | EWMA + 双阈值 |
| LossBasedBWE v2 | RTCP 丢包率 | 丢包驱动带宽 | 牛顿迭代 + 多候选 |
| DelayBasedBWE | 单向时延变化 | 拥塞状态 | 趋势线回归 |
| RTT Backoff | RTT 测量值 | 保护性降速 | 阈值触发 |
| LinkCapacityTracker | ACK 速率 | 容量上限 | EWMA 平滑 |
| BitrateProber | 探测配置 | 探测包生成 | 状态机调度 |
| SendSideBWE | 多源估计 | 目标速率 | 最小值融合 |
| PacingController | 目标速率 | 发送调度 | 债务预算 |
三种速率控制模型对比
| 维度 | Token Bucket | Leaky Bucket | Debt-Budget (WebRTC) |
|---|---|---|---|
| 思想 | 令牌==带宽,以秒为单位放入、使用 | 水出=平均带宽,水越多=超速 | 预算可为负 → 允许透支,随后"还债" |
| 透支 | 否 | 否 | 是 |
| 是否适合关键帧 | 不适合(会阻塞) | 部分适合 | 最适合 |
| 典型应用 | 防火墙限流 | Linux qdisc | WebRTC / QUIC Pacer |
图 2:三种模型类结构对比
债务预算算法详解
债务预算公式:
[\text{debt}_{t+\Delta t}=\max(\text{debt}_t- r \cdot \Delta t, 0)+s]
其中 r 是发送速率上限(bps),s 是发送数据大小(bit)。允许 debt 先负后正,给突发帧开绿灯。
债务变化曲线
吞吐量估计 BitrateEstimator 逐行代码走读
文件:
modules/congestion_controller/goog_cc/bitrate_estimator.cc
4.1 构造函数 & 字段
4.2 Update() 主流程
// 丢包率补偿
if(current_lost_packet_rate_ < 10) {
if(bitrate_sample_kbps < bitrate_estimate_kbps_ *
(100 - current_lost_packet_rate_ * 3/4) / 100) {
bitrate_sample_kbps = bitrate_estimate_kbps_ *
(100 - current_lost_packet_rate_ * 3/4) / 100;
}
} else {
if(bitrate_sample_kbps < bitrate_estimate_kbps_ * 90 / 100) {
bitrate_sample_kbps = bitrate_estimate_kbps_ * 90 / 100;
}
}
// 最小估计阈值保护
if (bitrate_sample_kbps < bitrate_estimate_kbps_ * kMinEstimateRateThreshold) {
bitrate_sample_kbps = bitrate_estimate_kbps_ * kMinEstimateRateThreshold;
}
// 选择不确定度因子
float scale = uncertainty_scale_;
if (is_small_sample && bitrate_sample_kbps < bitrate_estimate_kbps_) {
scale = small_sample_uncertainty_scale_;
} else if (in_alr && bitrate_sample_kbps < bitrate_estimate_kbps_) {
scale = uncertainty_scale_in_alr_;
}
// 计算样本不确定度
float sample_uncertainty = scale * std::abs(bitrate_estimate_kbps_ - bitrate_sample_kbps) /
(bitrate_estimate_kbps_ + std::min(bitrate_sample_kbps,
uncertainty_symmetry_cap_.Get().kbps<float>()));
float sample_var = sample_uncertainty * sample_uncertainty;
// 贝叶斯融合更新
float pred_bitrate_estimate_var = bitrate_estimate_var_ + 5.f;
bitrate_estimate_kbps_ = (sample_var * bitrate_estimate_kbps_ +
pred_bitrate_estimate_var * bitrate_sample_kbps) /
(sample_var + pred_bitrate_estimate_var);
// 确保不低于下限
bitrate_estimate_kbps_ = std::max(bitrate_estimate_kbps_,
estimate_floor_.Get().kbps<float>());
// 更新方差
bitrate_estimate_var_ = sample_var * pred_bitrate_estimate_var /
(sample_var + pred_bitrate_estimate_var);
}
4.3 UpdateWindow() 滑动窗口实现
float BitrateEstimator::UpdateWindow(int64_t now_ms, int bytes,
int rate_window_ms, bool* is_small_sample) {
RTC_DCHECK(is_small_sample != nullptr);
// 时间回退保护
if (now_ms < prev_time_ms_) {
prev_time_ms_ = -1;
sum_ = 0;
current_window_ms_ = 0;
}
if (prev_time_ms_ >= 0) {
current_window_ms_ += now_ms - prev_time_ms_;
// 重置:如果间隔超过窗口大小
if (now_ms - prev_time_ms_ > rate_window_ms) {
sum_ = 0;
current_window_ms_ %= rate_window_ms;
}
}
prev_time_ms_ = now_ms;
float bitrate_sample = -1.0f;
if (current_window_ms_ >= rate_window_ms) {
*is_small_sample = sum_ < small_sample_threshold_->bytes();
// 计算吞吐量:Byte/ms -> kbit/s
bitrate_sample = 8.0f * sum_ / static_cast<float>(rate_window_ms);
current_window_ms_ -= rate_window_ms;
sum_ = 0;
}
sum_ += bytes;
return bitrate_sample;
}
4.4 算法流程图
结论:吞吐量估计适合应对 ACK 丰富、丢包低的网络,如企业 WLAN;在移动网络需与 Loss 模型联合。
探测器 BitrateProber:如何高效"探路"?
文件:
modules/pacing/bitrate_prober.cc
探测器负责生成 Probe Cluster,一般在:
- 会话开始 → Initial Probe。
- BWE 上调 → ALR Probe。
5.1 探测状态机
5.2 探测时序图
5.3 关键代码实现
void BitrateProber::OnIncomingPacket(DataSize packet_size) {
if (!enabled_ || probing_state_ == ProbingState::kDisabled) {
return;
}
if (probing_state_ == ProbingState::kInactive) {
probing_state_ = ProbingState::kActive;
time_of_last_probe_ = clock_->CurrentTime();
ready_to_probe_ = true;
}
if (ready_to_probe_) {
time_of_last_probe_ = clock_->CurrentTime();
ready_to_probe_ = false;
}
// 累积探测包大小
cluster_bytes_sent_ += packet_size;
if (cluster_bytes_sent_ >= recommended_probe_size_) {
// 探测完成
probing_state_ = ProbingState::kInactive;
cluster_bytes_sent_ = DataSize::Zero();
}
}
Timestamp BitrateProber::NextProbeTime(Timestamp now) const {
if (probing_state_ != ProbingState::kActive) {
return Timestamp::PlusInfinity();
}
// 计算下次探测时间
TimeDelta time_since_last_probe = now - time_of_last_probe_;
if (time_since_last_probe >= min_probe_delta_) {
return now;
}
return time_of_last_probe_ + min_probe_delta_;
}
- 节拍:允许 1 ms 提前执行 (
kMaxEarlyProbeProcessing) 避免 miss。 - 退出条件:当
cluster_bytes_sent ≥ recommended_probe_size,停止。
5.4 探测策略对比
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Initial Probe | 快速获取初始带宽 | 可能过激 | 会话开始 |
| ALR Probe | 检测带宽恢复 | 需要ALR检测 | 应用限速后 |
| Periodic Probe | 持续监控 | 开销较大 | 稳定网络 |
丢包估计 V1:经典 EWMA 策略
文件:
modules/congestion_controller/goog_cc/loss_based_bandwidth_estimation.cc
6.1 核心公式
[\text{avgLoss}{n} = (1-\alpha)\cdot\text{avgLoss}{n-1} + \alpha \cdot \text{instLoss}]
其中
α = 1 – exp(–Δt / window),window=800 ms。- 叠加
average_loss_max_捕捉 峰值,防抖。
6.2 关键代码片段
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;
}
last_loss_ratio_ = static_cast<double>(loss_count) / packet_results.size();
const TimeDelta time_passed = last_loss_packet_report_.IsFinite()
? at_time - last_loss_packet_report_
: TimeDelta::Seconds(1);
last_loss_packet_report_ = at_time;
has_decreased_since_last_loss_report_ = false;
// EWMA 更新平均丢包率
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 {
average_loss_max_ +=
ExponentialUpdate(config_.loss_max_window, time_passed) *
(average_loss_ - average_loss_max_);
}
}
6.3 速率调整逻辑
DataRate LossBasedBandwidthEstimation::Update(Timestamp at_time,
DataRate min_bitrate,
DataRate wanted_bitrate,
TimeDelta last_round_trip_time) {
if (loss_based_bitrate_.IsZero()) {
loss_based_bitrate_ = wanted_bitrate;
}
// 只使用峰值丢包率进行增速判断
const double loss_estimate_for_increase = average_loss_max_;
// 使用平均丢包率进行减速判断,避免多次减速
const double loss_estimate_for_decrease =
std::min(average_loss_, last_loss_ratio_);
const bool allow_decrease =
!has_decreased_since_last_loss_report_ &&
(at_time - time_last_decrease_ >=
last_round_trip_time + config_.decrease_interval);
// 如果丢包反馈过旧,禁止加速
const bool loss_report_valid =
at_time - last_loss_packet_report_ < 1.2 * kMaxRtcpFeedbackInterval;
if (loss_report_valid && config_.allow_resets &&
loss_estimate_for_increase < loss_reset_threshold()) {
// 重置:完全信任 wanted_bitrate
loss_based_bitrate_ = wanted_bitrate;
} else if (loss_report_valid &&
loss_estimate_for_increase < loss_increase_threshold()) {
// 增速:RTT 自适应 + balance cap
DataRate new_increased_bitrate =
min_bitrate * GetIncreaseFactor(config_, last_round_trip_time) +
config_.increase_offset;
const DataRate new_increased_bitrate_cap = BitrateFromLoss(
loss_estimate_for_increase, config_.loss_bandwidth_balance_increase,
config_.loss_bandwidth_balance_exponent);
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) {
// 减速:单次减速 & floor cap
const DataRate new_decreased_bitrate_floor = BitrateFromLoss(
loss_estimate_for_decrease, config_.loss_bandwidth_balance_decrease,
config_.loss_bandwidth_balance_exponent);
DataRate new_decreased_bitrate =
std::max(decreased_bitrate(), new_decreased_bitrate_floor);
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_;
}
核心逻辑拆解
- Dual-threshold:
loss_estimate_for_increase与loss_estimate_for_decrease分别负责「加速」与「减速」决策。 - Guard
allow_decrease:避免在一个 RTT 窗内多次减速,确保带宽收敛平滑。 - Balance Cap:
BitrateFromLoss()通过反函数求得“理想带宽”,防止过冲。 - RTT-Adaptive Increase:
GetIncreaseFactor()在 200-800 ms RTT 范围内插值,RTT 越大加速越保守。 - Reset Path:丢包持续低于
loss_reset_threshold()且allow_resets开启时,直接重置到wanted_bitrate。
Tip:Loss V1 算法对反馈时延较为敏感。当 RTCP 反馈周期 > 1 s 时,应调大
loss_window以避免误判。
6.4 小结:V1 的优缺点
| 维度 | 优点 | 局限 |
|---|---|---|
| 实现复杂度 | 数据结构简单,EWMA 易于实现 | 仅考虑丢包率,无视 Delay/TCP-Friendly |
| 收敛速度 | 峰值跟踪 + RTT 自适配,收敛较快 | 对突发丢包容忍度低,易反复减速 |
| 应用场景 | 移动 4G/5G、Wi-Fi 场景 | 高丢包 + 高 RTT 卫星链路 |
丢包估计 V2:牛顿迭代 + 多候选
文件:
modules/congestion_controller/goog_cc/loss_based_bwe_v2.cc
7.1 动机
V1 的线性加/减速策略在高波动环境下表现欠佳,V2 引入了 目标函数优化 与 多路候选(ChannelParameters),通过牛顿迭代在实时更新的损耗曲面上寻找最优带宽。
接下来我们将从「状态机、候选生成、目标函数、迭代求解」四个角度展开分析。
7.2 状态机概览
- Delay:完全沿用 Delay-Based (OWD) 结果。
- Increasing / IncreasingWithPadding:带宽向上攀升,后者允许通过 Pad 包探测。
- LossLimited / Decreasing:被丢包限制或主动减速。
- Holding:观察期,冻结 BWE 至 recent ACK 值,等待网络恢复。
Why Padding? 进入
kIncreaseUsingPadding时会强制 PacedSender 以pace_at_loss_based_estimate的速率注入填充包,借此快速验证上行容量。
7.3 关键数据结构
struct Observation {
int64_t id;
int num_packets;
int num_lost_packets;
int num_received_packets;
DataRate sending_rate;
DataSize size;
DataSize lost_size;
};
struct ChannelParameters {
DataRate loss_limited_bandwidth;
double inherent_loss; // 固有丢包下限
};
- Observation:基于 发送端时间戳 的滑窗统计,默认
window=50。 - ChannelParameters:一组候选带宽 + 固定损耗假设,每轮迭代会被喂入目标函数。
7.4 候选生成逻辑 GetCandidates(in_alr)
- 以当前
best_estimate为中心,构造 ±N% 的扩展候选。 - 若处于 ALR,额外加入
delay_based_estimate作为 aggressive candidate。 - 最多 8 个候选,保证计算量可控。
for (ChannelParameters cand : GetCandidates(in_alr)) {
NewtonsMethodUpdate(cand);
double obj = GetObjective(cand);
best = obj > best_obj ? cand : best;
}
7.5 目标函数 & 牛顿迭代
目标函数本质为 逻辑似然最大化:
[\max_{B,\epsilon}; \sum_{i} w_i \big( k_i \ln p + (n_i-k_i) \ln(1-p) \big) + \beta B]
其中
- (k_i):第 i 个 Observation 的丢包数。
- (n_i):发送包数。
- (p = \epsilon + f(B)):总丢包率,含固有损耗 (\epsilon)。
- (\beta B):高带宽偏好项,鼓励寻找更高速率。
const double objective = temporal_weight *
((obs.num_lost_packets * std::log(loss_prob)) +
(obs.num_received_packets * std::log(1.0 - loss_prob))) +
temporal_weight * high_bandwidth_bias * obs.num_packets;
NewtonsMethodUpdate() 对 inherent_loss 进行多次一维牛顿迭代:
for (int i = 0; i < newton_iterations; ++i) {
Derivatives d = GetDerivatives(param);
param.inherent_loss -= step_size * d.first / d.second;
param.inherent_loss = GetFeasibleInherentLoss(param);
}
- 一阶导:目标函数对 (\epsilon) 的梯度。
- 二阶导:用于自适应步长,防止发散。
- Feasible 区间:
[0, 0.3],超界立即裁剪。
典型配置:
newton_iterations=3,step_size=0.5,单轮成本 < 0.1 ms。
7.6 结果分类与输出
- 若
bounded_estimate < delay_based_estimate& 在上升区间 →kIncreasing/kIncreaseUsingPadding。 - 若
average_loss > threshold→kDecreasing并启动 HOLD 计时器。 - 其余情况 →
kLossLimited,随后与 ACK rate/Min-Max/InstantBound 共同裁剪。
输出结构体:
struct Result {
DataRate bandwidth_estimate;
LossBasedState state;
};
SendSideBWE 随后根据 state 决定是否 pacing at loss based estimate,并更新 LinkCapacityTracker。
时延估计 DelayBasedBWE:趋势线守门员
文件:
modules/congestion_controller/goog_cc/delay_based_bwe.cc
7.7 原理速览
- TrendlineEstimator:对 单向时延 (OWD) 做线性回归,观察斜率。
• 斜率 > 阈值 →Overuse→ 降速
• 斜率 < –阈值 →Underuse→ 升速
• 其余 →Normal→ 保持 - RateControl:把
BandwidthUsage+acked_bitrate融合为具体比特率,RTT 越高,加速越保守。 - 守门员角色:最终限速取
min(delay_based, receiver_limit, loss_based_limit),因此 Delay-based 更像“上限裁剪器”。
const RateControlInput input(detector_state, acked_bitrate);
*target_rate = rate_control_.Update(input, at_time);
7.8 核心代码解析
7.8.1 DelayBasedBwe 主流程
DelayBasedBwe::Result DelayBasedBwe::IncomingPacketFeedbackVector(
const TransportPacketsFeedback& msg,
std::optional<DataRate> acked_bitrate,
std::optional<DataRate> probe_bitrate,
std::optional<NetworkStateEstimate> network_estimate,
bool in_alr,
void* internal_peer_connection) {
auto packet_feedback_vector = msg.SortedByReceiveTime();
for (const auto& packet_feedback : packet_feedback_vector) {
IncomingPacketFeedback(packet_feedback, msg.feedback_time);
}
return MaybeUpdateEstimate(acked_bitrate, probe_bitrate,
std::move(network_estimate),
recovered_from_overuse, in_alr, msg.feedback_time);
}
- 输入:Transport-CC 反馈包,按接收时间排序。
- 处理:逐个调用
IncomingPacketFeedback()更新时延统计。 - 输出:
Result包含target_bitrate和delay_detector_state。
7.8.2 时延计算与状态检测
void DelayBasedBwe::IncomingPacketFeedback(const PacketResult& packet_feedback,
Timestamp at_time) {
// 计算包间时延变化
TimeDelta send_delta = TimeDelta::Zero();
TimeDelta recv_delta = TimeDelta::Zero();
int size_delta = 0;
bool calculated_deltas = inter_arrival_for_packet->ComputeDeltas(
packet_feedback.sent_packet.send_time, packet_feedback.receive_time,
at_time, packet_size.bytes(), &send_delta, &recv_delta, &size_delta);
// 更新趋势线估计器
delay_detector_for_packet->Update(recv_delta.ms<double>(),
send_delta.ms<double>(),
packet_feedback.sent_packet.send_time.ms(),
packet_feedback.receive_time.ms(),
packet_size.bytes(), calculated_deltas);
}
- InterArrivalDelta:计算相邻包的发送间隔、接收间隔、大小差异。
- OWD 计算:
delta_ms = recv_delta_ms - send_delta_ms,正值表示排队延迟增加。
7.8.3 TrendlineEstimator 线性回归
void TrendlineEstimator::UpdateTrendline(double recv_delta_ms,
double send_delta_ms,
int64_t send_time_ms,
int64_t arrival_time_ms,
size_t packet_size) {
double delta_ms = recv_delta_ms - send_delta_ms;
// 指数平滑滤波
accumulated_delay_ += delta_ms;
smoothed_delay_ = smoothing_coef_ * smoothed_delay_ +
(1 - smoothing_coef_) * accumulated_delay_;
// 维护滑动窗口
delay_hist_.emplace_back(arrival_time_ms - first_arrival_time_ms_,
smoothed_delay_, accumulated_delay_);
while (delay_hist_.size() > settings_.window_size)
delay_hist_.pop_front();
// 线性回归计算斜率
double trend = prev_trend_;
if (delay_hist_.size() == settings_.window_size) {
trend = LinearFitSlope(delay_hist_).value_or(trend);
}
Detect(trend, send_delta_ms, recv_delta_ms, arrival_time_ms, packet_size);
}
- 平滑滤波:
smoothing_coef_默认 0.78,减少噪声影响。 - 线性回归:对 20 个样本点做最小二乘拟合,斜率反映排队趋势。
7.8.4 状态检测逻辑
void TrendlineEstimator::Detect(double trend,
double ts_delta,
double receive_delta,
int64_t now_ms,
size_t packet_size) {
const double modified_trend =
std::min(num_of_deltas_, kMinNumDeltas) * trend * threshold_gain_;
if (modified_trend > threshold_) {
// Overuse 检测:需要连续超标一段时间
if (time_over_using_ == -1) {
time_over_using_ = ts_delta / 2;
} else {
time_over_using_ += ts_delta;
}
overuse_counter_++;
if (time_over_using_ > overusing_time_threshold_ && overuse_counter_ > 1) {
hypothesis_ = BandwidthUsage::kBwOverusing;
}
} else if (modified_trend < -threshold_) {
// Underuse 检测:立即响应
hypothesis_ = BandwidthUsage::kBwUnderusing;
} else {
hypothesis_ = BandwidthUsage::kBwNormal;
}
}
- 阈值调整:
threshold_gain_默认 4.0,放大趋势信号。 - Overuse 保护:需要连续超标 10ms 且计数器 > 1,避免误判。
- Underuse 敏感:负斜率立即触发,快速恢复。
7.8.5 速率控制融合
DelayBasedBwe::Result DelayBasedBwe::MaybeUpdateEstimate(
std::optional<DataRate> acked_bitrate,
std::optional<DataRate> probe_bitrate,
std::optional<NetworkStateEstimate> network_estimate,
bool recovered_from_overuse,
bool in_alr,
Timestamp at_time) {
Result result;
if (active_delay_detector_->State() == BandwidthUsage::kBwOverusing) {
// Overuse:降速
if (acked_bitrate && rate_control_.TimeToReduceFurther(at_time, *acked_bitrate)) {
result.updated = UpdateEstimate(at_time, acked_bitrate, &result.target_bitrate);
}
} else {
// Normal/Underuse:升速或保持
if (probe_bitrate) {
result.probe = true;
rate_control_.SetEstimate(*probe_bitrate, at_time);
result.target_bitrate = rate_control_.LatestEstimate();
} else {
result.updated = UpdateEstimate(at_time, acked_bitrate, &result.target_bitrate);
}
}
return result;
}
- Overuse 降速:基于
acked_bitrate和 RTT 计算降幅。 - Probe 升速:探测结果直接设置新速率。
- 正常升速:
RateControl根据acked_bitrate和 RTT 自适应调整。
7.9 与 Pacer/Loss 协同
- DelayBased:防 RTT 暴涨,提供 cap;
- LossBased:丢包驱动,加/减速;
- Pacer:真正执行节拍。
7.9 调参要点
| 参数 | 说明 | 默认 | 建议范围 |
|---|---|---|---|
kTrendlineWindow |
线性回归窗口 | 20 pkt | 15–30 |
kOveruseThreshold |
斜率阈值 | 12.5 ms/s | 10–15 |
kTrendlineGain |
速率增益 | 0.65 | 0.5–0.8 |
Tip:在高抖动无线环境(4G/5G)可适当放宽
OveruseThreshold,减少误杀。
7.10 调试利器
# Chrome
chrome://webrtc-internals # 关注 googReceiveSideDelay 曲线
# WebRTC Native
rtc::LogToStderr(rtc::LS_VERBOSE);
现在,Delay-based、Loss-based、RTT Backoff 三驾马车共同决定 Paced 速率,至此发送端 BWE 体系图谱完整。
RTT Backoff & 链路容量追踪
关键文件:
send_side_bandwidth_estimation.cc
8.1 RTT Backoff 算法
if (rtt_backoff_.IsRttAboveLimit()) {
if (at_time - time_last_decrease_ >= rtt_backoff_.drop_interval_ &&
current_target_ > rtt_backoff_.bandwidth_floor_) {
DataRate new_bitrate = std::max(current_target_ *
rtt_backoff_.drop_fraction_,
rtt_backoff_.bandwidth_floor_.Get());
UpdateTargetBitrate(new_bitrate, at_time);
}
return; // RTT 超标,短路其余逻辑
}
- 阈值:
configured_limit_默认为 300 ms,可通过 FieldTrial 调整。 - 策略:当 RTT > limit 连续触发,按照
drop_fraction_乘法衰减,最低不小于bandwidth_floor_。
Why:在移动弱网中 RTT 暴涨时,丢包往往滞后。提前降速可避免“排队延迟雪崩”。
8.2 LinkCapacityTracker
void LinkCapacityTracker::OnRateUpdate(std::optional<DataRate> acked,
DataRate target,
Timestamp at_time) {
// 以 ACK Rate 为上限,用 alpha=0.05 的 EWMA 平滑
capacity_estimate_bps_ = 0.95 * capacity_estimate_bps_ +
0.05 * target.bps();
}
- 持久保存最近 “可持续” 速率,用于
stable_target_rate输出。 - 在评价“可用链路容量”时,
SendSideBWE会取min(delay_based, loss_based, capacity_estimate)。
发送端融合器 SendSideBandwidthEstimation
9.1 五路输入
- DelayBased —— OWD 估计 (必选)
- LossBased v1/v2 —— 丢包估计 (互斥)
- Receiver REMB/TMMBR —— 防御端限速
- RTT Backoff —— 延迟保护
- Probe Bitrate —— 探测结果(可选)
9.2 限速优先级
upper_limit = min(delay_based, receiver_limit, loss_based_limit)
低于 min_bitrate_configured_ 将被硬拉升;高于 max_bitrate_configured_ 将被裁剪。
平滑发送核心 PacingController
文件:
modules/pacing/pacing_controller.cc
10.1 债务与预算
media_debt_:媒体包欠债padding_debt_:填充包欠债
TimeDelta delta = elapsed_time;
media_debt_ -= pacing_rates_.media_rate * delta;
padding_debt_-= pacing_rates_.padding_rate * delta;
media_debt_ = std::max(media_debt_, DataSize::Zero());
padding_debt_ = std::max(padding_debt_, DataSize::Zero());
10.2 发送决策
- 当负债<=0 允许立刻发送;否则等待下一 Tick。
- 若媒体队列空且
padding_rate>0,生成 Padding RTP 包维持探测。
代码穿行:一次 ProcessPackets() 的生命旅程
调用链:
RtpVideoSender::SendVideo→PacketRouter::EnqueuePacket→TaskQueuePacedSender::EnqueuePacket→ProcessPackets()。
关键里程碑:
- 排队 — 加入媒体优先队列
- 借债更新 — 依据
elapsed_time调整 debt - 出队 — 媒体 > 高优先 > FEC > Retrans > Padding
- 生成 Probe/Padding —
BitrateProber&MaybeSendPadding - 调速 —
UpdatePacingRates(target_rate)
实战案例:视频直播带宽抖动
| 时间线 | 事件 | 现象 | 关键指标 |
|---|---|---|---|
| T0 | 观众激增 | 端到端延迟升高 | RTT↑、丢包↓、ACK rate↑ |
| T1 | RTT Backoff 触发 | 码率骤降 | target_rate↓、stable_rate↓ |
| T2 | ALR 探测 | 码率缓慢上升 | Probe cluster 50 pkt |
| T3 | 网络恢复 | 进入 LossLimited | loss_based_state=kLossLimited |
调优:适当调大
bandwidth_floor_,避免过度回退;缩短delayed_increase_window加快恢复。
参数调优:Checklist
- kMinEstimateRateThreshold(吞吐量估计下限)
- loss_window / loss_max_window(Loss V1 滑窗)
- newton_iterations / step_size(Loss V2)
- rtt_backoff_.drop_fraction_(RTT 回退幅度)
- pacing_factor (默认 2.5) — 发送高峰拆分粒度
- ALR probe period — 建议 5–6 s
常见问题 FAQ
- Q:为何 ACK 速率高于编码码率仍会卡顿?
A:可能是 Pacer 债务累积导致突发;检查media_debt_曲线。 - Q:丢包 2% 是否一定减速?
A:Loss V1 仅在loss_estimate_for_decrease > threshold且allow_decrease时触发,具体阈值与 RTT 相关。 - Q:Padding 包会不会占用真正视频带宽?
A:Padding 仅在媒体队列空闲时发送,且速率受padding_rate控制,不会抢占关键帧。
总结:平滑之道,止于至善
- 估计先行,平滑护航:任何高级 BWE 都离不开稳定的 Pacer。
- 多信源融合:吞吐量、丢包、RTT、接收端信令共同决定带宽。
- 容错与恢复并重:RTT Backoff 勇于退让,Probe&Pacing 敢于探路。
面向未来,WebRTC 将持续在 QUIC、多路并行传输上演进,但「平滑发送 + 自适应带宽」的核心哲学不会改变。
参考资料
- Google I/O – WebRTC: Real-time communication for the open web
- RFC 8835 - RTP Congestion Control Requirements
- WebRTC 源码 (branch m123) modules/congestion_controller
- Yang, Xu et al. “DeepCC: A Packet Loss Resilient Congestion Control.” SIGCOMM 2018
- Blog – Understanding Google Congestion Control (GCC)
- WebRTC 官方文档 - Congestion Control
- RFC 3550 - RTP: A Transport Protocol for Real-Time Applications
- RFC 4585 - Extended RTP Profile for Real-time Transport Control Protocol (RTCP)-Based Feedback
- Google Congestion Control (GCC) 实现分析
更多推荐

所有评论(0)