# 延迟based 带宽估计 · trendline 与过载检测

> GoogCC 的主控制器，靠「延迟趋势」在丢包前就嗅到拥塞。这一章读 goog_cc 的 DelayBasedBwe 与 TrendlineEstimator：怎么从包的到达时间算出延迟变化、用线性回归判断趋势、over-use detector 如何输出 kNormal/kOveruse/kUnderusing，以及 AIMD 如何据此加减码率。

- 作者：David（道雾轩）
- 专栏：音视频与 WebRTC 详解（https://daiw.org/manual/webrtc.md）
- 最后更新：2026-07-14
- 原文：https://daiw.org/manual/webrtc/delay-based-bwe
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 延迟based 带宽估计 · trendline 与过载检测

[GoogCC 的主控制器](https://daiw.org/manual/webrtc/congestion-control-overview)是延迟based 的。它有个了不起的本事：**在丢包真正发生之前，就从延迟的细微变化里嗅到拥塞将至。** 这一章拆解它怎么做到——`DelayBasedBwe` 与它的核心 `TrendlineEstimator`。

## 核心信号：延迟在涨吗

原理起点很直觉：

<Callout type="info">
  **网络快堵时，最早的征兆是「延迟开始涨」——因为路由器的缓冲区开始排队。** 想象一条路快堵车了：车还没完全停（还没[丢包](https://daiw.org/manual/webrtc/loss-based-bwe)），但每辆车通过的时间**变长了**（排队）。网络也一样：你发得比链路容量快时，超出的包在路由器**缓冲区排队**，于是它们的**传输延迟逐渐增大**——这比「缓冲区满了开始[丢包](https://daiw.org/manual/webrtc/loss-based-bwe)」**早得多**。所以盯住「延迟趋势」，就能**提前**发现拥塞。

  数据从哪来？[Transport-CC 反馈](https://daiw.org/manual/webrtc/transport-cc)——它告诉发送端每个[包何时发、何时到](https://daiw.org/manual/webrtc/transport-cc)。比较「发送间隔」和「到达间隔」的差异，就得到**延迟的变化**：如果一组包的到达比发送**越来越晚**，延迟在涨，拥塞在逼近。
</Callout>

## TrendlineEstimator：用回归看趋势

单看一两个包的延迟会有噪声（网络本就抖动）。要看**趋势**，`TrendlineEstimator`（`goog_cc/trendline_estimator.h`）用**线性回归**：

```cpp
// goog_cc/trendline_estimator.h(节选)
class TrendlineEstimator : public DelayIncreaseDetectorInterface {
  BandwidthUsage State() const override;   // 当前判断:正常/过载/欠载
  BandwidthUsage hypothesis_;
};
```

它把最近一批包**累积的延迟变化**拟合成一条直线，看这条线的**斜率（trend）**：斜率明显为正 = 延迟在稳定上升 = 拥塞；斜率约零 = 平稳；斜率为负 = 延迟在降（网络在疏通）。用回归而非单点，是为了**从抖动的噪声里提取出真实趋势**——这是信号处理的常规智慧。

## 过载检测：三种状态

趋势斜率和一个**动态阈值**比较，`TrendlineEstimator` 输出三种 `BandwidthUsage` 状态：

<Callout type="info">
  **`BandwidthUsage` 三态，是延迟based 控制的判决：**

  - **`kNormal`（正常）** —— 延迟趋势平稳，网络没堵，可以维持甚至[试探提速](https://daiw.org/manual/webrtc/probing)。
  - **`kOveruse`（过载）** —— 延迟趋势明显上涨，**发过量了**，必须**降码率**。这是最关键的信号。
  - **`kUnderusing`（欠载）** —— 延迟在下降，说明之前降过头 / 网络刚疏通，可以恢复。

  关键设计：那个判决阈值是**动态自适应**的——不是写死一个「延迟涨多少算过载」。因为不同网络的基础抖动差异极大（光纤 vs 4G），固定阈值要么太敏感（误报）、要么太迟钝（漏报）。动态阈值会根据观测到的延迟波动**自我调整**，在各种网络下都能恰当地区分「正常抖动」和「真的在过载」。这种自适应，是 GoogCC 鲁棒性的来源之一。
</Callout>

## AIMD：加性增，乘性减

拿到过载检测的三态，怎么调码率？用经典的 **AIMD（Additive Increase, Multiplicative Decrease，加性增、乘性减）**——和 TCP 拥塞控制同源的思想：

- **`kNormal` → 加性增（小步慢涨）** —— 没堵，就**缓慢线性地**试探性提高码率，一点点往上探，找网络的上限。
- **`kOveruse` → 乘性减（大步快降）** —— 一过载，就**成比例地大幅**降码率（比如乘 0.85），快速让出拥塞。

<Callout type="info">
  **为什么增要慢、减要快？这是拥塞控制的黄金法则，背后是「代价不对称」。** 涨过头的代价（拥塞、[延迟飙升](https://daiw.org/manual/webrtc/transport-cc)、卡顿）远大于保守一点的代价（画质略低）。所以**探测上限要谨慎**（加性小步，万一涨过头损失有限）、**发现过载要果断**（乘性大步，快速脱离拥塞、把排队的延迟泄掉）。慢涨快降，让系统**稳定地收敛**到网络容量附近来回小幅摆动，而不会剧烈震荡。这个「慢涨快降」的智慧，几乎所有拥塞控制（TCP、QUIC、WebRTC）都遵循。WebRTC 里由 `AimdRateControl` 实现这套加减逻辑。
</Callout>

## 一张估计流程图

```mermaid
flowchart TD
    FB["Transport-CC 反馈(每包发送/到达时间)"] --> DV["算延迟变化"]
    DV --> TL["TrendlineEstimator:线性回归看斜率"]
    TL --> OD{"和动态阈值比较"}
    OD -->|"斜率平稳"| N["kNormal → AIMD 加性增(慢涨)"]
    OD -->|"斜率上涨"| O["kOveruse → AIMD 乘性减(快降)"]
    OD -->|"斜率下降"| U["kUnderusing → 恢复"]
    N --> RATE["延迟based 目标码率"]
    O --> RATE
    U --> RATE
```

## 小结与下一章

**延迟based 估计**在[丢包](https://daiw.org/manual/webrtc/loss-based-bwe)前就嗅到拥塞：网络快堵时**延迟先涨**（缓冲区排队），靠 [Transport-CC](https://daiw.org/manual/webrtc/transport-cc) 算出延迟变化。`TrendlineEstimator` 用**线性回归**从噪声里提取趋势斜率，过载检测用**动态阈值**输出 `BandwidthUsage` 三态（`kNormal`/`kOveruse`/`kUnderusing`）。据此跑 **AIMD**：正常**加性增（慢涨）**、过载**乘性减（快降）**——慢涨快降因代价不对称，让码率稳定收敛。

延迟based 是主控，但它有盲区（比如链路本身随机丢包时）。**丢包based** 控制器作为兜底补上。下一章。👉 [丢包based 带宽估计](https://daiw.org/manual/webrtc/loss-based-bwe)。

## 参考资料

- [1] libwebrtc 源码.`modules/congestion_controller/goog_cc/trendline_estimator.h`、`delay_based_bwe.h`、`aimd_rate_control.h`。
- [2] Holmer et al.「A Google Congestion Control Algorithm...」—— trendline 与 over-use detector。
- [3] `BandwidthUsage` 枚举（`api/transport/`）。
