目录

注:可在阅读器侧面大纲快速跳转

📖 基础篇

  1. 引言:来自一线的卡顿血案
  2. 整体架构:七大模块协作图
  3. 三种速率控制模型对比

🔍 算法篇

  1. 吞吐量估计 BitrateEstimator 逐行代码走读
  2. 探测器 BitrateProber:如何高效"探路"?
  3. 丢包估计 V1:经典 EWMA 策略
  4. 丢包估计 V2:牛顿迭代 + 多候选
  5. 时延估计 DelayBasedBWE:趋势线守门员

⚙️ 系统篇

  1. RTT Backoff & 链路容量追踪
  2. 发送端融合器 SendSideBandwidthEstimation
  3. 平滑发送核心 PacingController
  4. 代码穿行:一次 ProcessPackets() 的生命旅程

🛠️ 实战篇

  1. 实战案例:视频直播带宽抖动
  2. 参数调优:Checklist
  3. 常见问题 FAQ

📚 总结篇

  1. 总结:平滑之道,止于至善
  2. 参考资料

引言:来自一线的卡顿血案

“为什么我只切了一次 PPT 页面,800 ms 的 RTT 突然飙到 4 s,连麦直接黑屏?”

真实线上事故:某双师课堂场景,老师操作共享屏幕时,编码器产生 2 MB 的关键帧。没有 Pacer 的旧版本客户端一次性将所有 RTP 包泼向 1 Mbps 的家庭宽带 —— 网口被写满 + AP Buffer 过载 + 丢包重传雪崩,最终雪崩蔓延至音频流。

结论:再强大的 BWE(带宽估计)也抵不过"洪水式"突发,需要 Pacer 对发送进行平滑化处理。

问题分析图

PPT切换
编码器产生2MB关键帧
无Pacer直接发送
网口Buffer满
AP Buffer过载
丢包重传
音频流受影响
连麦黑屏

整体架构:七大模块协作图

Transport-CC反馈
RTCP丢包信息
RTCP丢包信息
RTT信息
容量信息
时延信息
目标速率
填充速率
探测速率
音频编码器
AudioEncoder
视频编码器
VideoEncoder
RTP发送器
RtpSender
任务队列
TaskQueuePacedSender
平滑控制器
PacingController
网络发送
Network
吞吐量估计
BitrateEstimator
丢包估计V1
LossBasedBWE v1
丢包估计V2
LossBasedBWE v2
RTT回退
RTT Backoff
链路容量追踪
LinkCapacityTracker
时延估计
DelayBasedBWE
带宽探测器
BitrateProber
发送端带宽估计
SendSideBWE
优先级队列
PacketQueue
债务预算
DebtBudget

图 1:WebRTC 发送端平滑发送与带宽评估协作图

数据流向时序图

MediaEncoder RtpSender TaskQueuePacedSender PacingController BitrateProber SendSideBWE Network 编码完成 EnqueuePacket() ProcessPackets() 获取当前速率 pacing_rate_ 检查探测时机 探测包或正常包 平滑发送 Transport-CC反馈 RTCP丢包信息 RTT信息 更新各算法估计 新目标速率 探测配置 MediaEncoder RtpSender TaskQueuePacedSender PacingController BitrateProber SendSideBWE Network

八大核心模块:

  1. BitrateEstimator(BE) —— 基于 Transport-CC 的吞吐量窗口统计。
  2. LossBasedBWE v1/v2 —— 两代基于 RTCP 丢包反馈的估计算法。
  3. DelayBasedBWE(DB) —— 基于单向时延趋势的拥塞检测。
  4. RTT Backoff —— 时延超标快速回退保护机制。
  5. LinkCapacityTracker —— 链路可承载能力平滑追踪。
  6. BitrateProber(BP) —— 主动带宽探测与验证。
  7. SendSideBWE(SSE) —— 多源融合决策,输出 target_rate
  8. 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
TokenBucket
+tokens
+rate
+consume(size)
LeakyBucket
+queue
+avg_rate
+drain()
DebtBudget
-media_debt_
-padding_debt_
+UpdateBudget(dt)
+OnPacketSent(size)

图 2:三种模型类结构对比

债务预算算法详解

时间流逝
减少债务
发送数据
增加债务
预算更新
债务>0?
等待
继续发送

债务预算公式

[\text{debt}_{t+\Delta t}=\max(\text{debt}_t- r \cdot \Delta t, 0)+s]

其中 r 是发送速率上限(bps),s 是发送数据大小(bit)。允许 debt 先负后正,给突发帧开绿灯。

债务变化曲线

0ms: 0KB
10ms: -5KB
20ms: -3KB
30ms: 0KB
40ms: 2KB
50ms: 4KB
60ms: 2KB
70ms: 0KB
80ms: -1KB
90ms: 0KB

吞吐量估计 BitrateEstimator 逐行代码走读

文件:modules/congestion_controller/goog_cc/bitrate_estimator.cc

4.1 构造函数 & 字段

BitrateEstimator 初始化
配置参数解析
窗口配置
initial_window_ms_: 500ms
冷启动窗口
noninitial_window_ms_: 200ms
稳态窗口
不确定度因子
uncertainty_scale_: 10.0
标准不确定度
uncertainty_scale_in_alr_: 10.0
ALR模式不确定度
small_sample_uncertainty_scale_: 10.0
小样本不确定度
阈值配置
estimate_floor_: 450 kbps
最小估计下限
small_sample_threshold_: 0 bytes
小样本阈值
状态变量
sum_: 0
窗口内字节累计
bitrate_estimate_kbps_: -1.0
当前估计值
bitrate_estimate_var_: 50.0
估计方差

4.2 Update() 主流程

Update 开始
是否第一次估计?
使用 initial_window_ms_
500ms 冷启动窗口
使用 noninitial_window_ms_
200ms 稳态窗口
调用 UpdateWindow
样本有效?
bitrate_sample_kbps >= 0
返回 - 未到完整窗口
是否第一次估计?
直接使用样本
bitrate_estimate_kbps_ = sample
进入贝叶斯融合流程
返回
丢包率补偿
最小阈值保护
选择不确定度因子
计算样本方差
贝叶斯融合更新
确保不低于下限
更新估计方差
返回
  // 丢包率补偿
  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
是否第一次?
直接使用样本
选择窗口大小
计算吞吐量样本
样本有效?
返回
丢包率补偿
最小阈值保护
选择不确定度因子
计算样本方差
贝叶斯融合
更新估计

结论:吞吐量估计适合应对 ACK 丰富、丢包低的网络,如企业 WLAN;在移动网络需与 Loss 模型联合。


探测器 BitrateProber:如何高效"探路"?

文件:modules/pacing/bitrate_prober.cc

探测器负责生成 Probe Cluster,一般在:

  1. 会话开始 → Initial Probe
  2. BWE 上调 → ALR Probe

5.1 探测状态机

Enable()
ProbeSent()
TimeToProbe()
Disable()
Disable()
Disabled
Probing
Waiting

5.2 探测时序图

PacingController BitrateProber Network BandwidthEstimator Cluster = N 个包, 总大小 S byte OnIncomingPacket(size) NextProbeTime() Send Probe Packet Transport-CC Feedback ProbeSent(...bytes) Update Probe State PacingController BitrateProber Network BandwidthEstimator

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_increaseloss_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),通过牛顿迭代在实时更新的损耗曲面上寻找最优带宽。

LossStats
样本队列
CandidatePool
候选池
ObjectiveFn
目标函数
BestBandwidth
最优带宽

接下来我们将从「状态机、候选生成、目标函数、迭代求解」四个角度展开分析。

7.2 状态机概览

触发填充
达到上限
Loss 缓解
Loss 严重
进入 HOLD
ACK rate 恢复
再次丢包
Delay
Increasing
IncreasingWithPadding
LossLimited
Decreasing
Holding
  • 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)

  1. 以当前 best_estimate 为中心,构造 ±N% 的扩展候选。
  2. 若处于 ALR,额外加入 delay_based_estimate 作为 aggressive candidate。
  3. 最多 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=3step_size=0.5,单轮成本 < 0.1 ms。


7.6 结果分类与输出

  1. bounded_estimate < delay_based_estimate & 在上升区间 → kIncreasing / kIncreaseUsingPadding
  2. average_loss > thresholdkDecreasing 并启动 HOLD 计时器。
  3. 其余情况 → kLossLimited,随后与 ACK rate/Min-Max/InstantBound 共同裁剪。
Increase
Decrease
Hold
LossLimited
BestCandidate
BoundedByInstantLower/Upper
loss_based_state
Increasing/Padding
Decreasing
Hold
LossLimited

输出结构体:

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 原理速览

  1. TrendlineEstimator:对 单向时延 (OWD) 做线性回归,观察斜率。
    • 斜率 > 阈值 → Overuse降速
    • 斜率 < –阈值 → Underuse升速
    • 其余 → Normal → 保持
  2. RateControl:把 BandwidthUsage + acked_bitrate 融合为具体比特率,RTT 越高,加速越保守。
  3. 守门员角色:最终限速取 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_bitratedelay_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 协同

upper_limit
DelayBased Limit
Limiter
LossBased Limit
Receiver REMB
SendSideBWE
Pacer
  • 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 五路输入

  1. DelayBased —— OWD 估计 (必选)
  2. LossBased v1/v2 —— 丢包估计 (互斥)
  3. Receiver REMB/TMMBR —— 防御端限速
  4. RTT Backoff —— 延迟保护
  5. Probe Bitrate —— 探测结果(可选)
target_rate_
Delay
Mixer
LossV1
LossV2
REMB
RTTBackoff
Probe
Pacer

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 发送决策

Tick 定时器触发
UpdateDebt
更新债务预算
债务 <= 0?
DequeueMedia
出队媒体包
Wait
等待下次Tick
MaybePadding
可能发送填充包
下次Tick
  • 当负债<=0 允许立刻发送;否则等待下一 Tick。
  • 若媒体队列空且 padding_rate>0,生成 Padding RTP 包维持探测。

代码穿行:一次 ProcessPackets() 的生命旅程

Encoder RtpSender TaskQueue PacingController Network GCC 产生 RTP 包 EnqueuePacket() ProcessPackets() Pace & Send Feedback (TCC/RTCP) Update rates Encoder RtpSender TaskQueue PacingController Network GCC

调用链:RtpVideoSender::SendVideoPacketRouter::EnqueuePacketTaskQueuePacedSender::EnqueuePacketProcessPackets()

关键里程碑:

  1. 排队 — 加入媒体优先队列
  2. 借债更新 — 依据 elapsed_time 调整 debt
  3. 出队 — 媒体 > 高优先 > FEC > Retrans > Padding
  4. 生成 Probe/PaddingBitrateProber & MaybeSendPadding
  5. 调速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

  1. kMinEstimateRateThreshold(吞吐量估计下限)
  2. loss_window / loss_max_window(Loss V1 滑窗)
  3. newton_iterations / step_size(Loss V2)
  4. rtt_backoff_.drop_fraction_(RTT 回退幅度)
  5. pacing_factor (默认 2.5) — 发送高峰拆分粒度
  6. ALR probe period — 建议 5–6 s

常见问题 FAQ

  1. Q:为何 ACK 速率高于编码码率仍会卡顿?
    A:可能是 Pacer 债务累积导致突发;检查 media_debt_ 曲线。
  2. Q:丢包 2% 是否一定减速?
    A:Loss V1 仅在 loss_estimate_for_decrease > thresholdallow_decrease 时触发,具体阈值与 RTT 相关。
  3. Q:Padding 包会不会占用真正视频带宽?
    A:Padding 仅在媒体队列空闲时发送,且速率受 padding_rate 控制,不会抢占关键帧。

总结:平滑之道,止于至善

  • 估计先行,平滑护航:任何高级 BWE 都离不开稳定的 Pacer。
  • 多信源融合:吞吐量、丢包、RTT、接收端信令共同决定带宽。
  • 容错与恢复并重:RTT Backoff 勇于退让,Probe&Pacing 敢于探路。

面向未来,WebRTC 将持续在 QUIC、多路并行传输上演进,但「平滑发送 + 自适应带宽」的核心哲学不会改变。


参考资料

  1. Google I/O – WebRTC: Real-time communication for the open web
  2. RFC 8835 - RTP Congestion Control Requirements
  3. WebRTC 源码 (branch m123) modules/congestion_controller
  4. Yang, Xu et al. “DeepCC: A Packet Loss Resilient Congestion Control.” SIGCOMM 2018
  5. Blog – Understanding Google Congestion Control (GCC)
  6. WebRTC 官方文档 - Congestion Control
  7. RFC 3550 - RTP: A Transport Protocol for Real-Time Applications
  8. RFC 4585 - Extended RTP Profile for Real-time Transport Control Protocol (RTCP)-Based Feedback
  9. Google Congestion Control (GCC) 实现分析
Logo

网易智企-云信开发者社区是面向全网开发者的技术交流与服务平台,依托近 29 年 IM、音视频技术积累,提供 IM、RTC、实时对话智能体、云原生、短信等全场景开发资源。

更多推荐