6、过载检测器(Overuse Detector):阈值设计、过载信号判断、自适应阈值、GCC v1与v2的差异

过载检测器,说白了就是WebRTC拥塞控制的“神经末梢”。它负责判断当前网络是不是已经“撑不住了”。我刚开始接触GCC源码时,觉得这模块不就是比个斜率吗?后来踩了坑才发现,阈值怎么设、信号怎么判、阈值要不要自适应——这里面的门道,深着呢。

6.1 过载检测器的核心任务

它的输入是什么?是到达时间差(Inter-arrival Time)的增量。输出呢?是一个三态信号:OveruseUnderuseNormal

你想想看,如果发送端以恒定速率发包,接收端发现包间隔越来越大,说明什么?说明网络在变堵。反之,间隔越来越小,说明网络在变空。过载检测器就是干这个的——它用一个线性回归模型,去拟合到达时间差的趋势斜率。

核心公式(简化版):

m(t) = 斜率(趋势) + 噪声
过载信号 = m(t) - 阈值

m(t) > 阈值 持续一段时间,就判定为 Overuse。

6.2 阈值设计:固定阈值 vs 自适应阈值

GCC v1 用的是固定阈值。我记得早期版本里,阈值默认是 12.5ms。这个值怎么来的?经验值。我在项目中遇到过,如果网络抖动特别大(比如4G弱信号场景),固定阈值很容易误判——明明只是正常抖动,却被当成过载,导致码率被过度压低。

GCC v2 引入了自适应阈值。它的思路是:阈值应该随着网络抖动的大小动态调整。抖动大时,阈值放宽;抖动小时,阈值收紧。

特性 GCC v1(固定阈值) GCC v2(自适应阈值)
阈值更新方式 常数,不变化 根据抖动统计动态调整
对抖动的鲁棒性 较差 较好
收敛速度 快,但容易误判 稍慢,但更稳定
典型场景 有线网络、低抖动环境 无线网络、高抖动环境

避坑指南:我曾经在一个Wi-Fi穿墙场景里,用GCC v1的固定阈值,结果视频一卡一卡的。后来换成v2的自适应阈值,码率曲线平滑了很多。说白了,阈值不是越大越好,也不是越小越好,而是要“跟着感觉走”——这个感觉就是网络抖动的标准差。

6.3 过载信号判断:三态机与状态切换

过载检测器内部其实是一个有限状态机。它有三个状态:

  • Normal:斜率在阈值范围内,网络正常。
  • Overuse:斜率持续超过阈值,网络过载。
  • Underuse:斜率持续低于负阈值,网络空闲。

状态切换不是瞬间的。GCC v1 里,只要一次斜率超阈值就切换,太敏感了。v2 里加了一个持续计数器——必须连续 N 次超阈值,才真正切换状态。这个 N 值,我建议根据帧率来调,比如 30fps 时 N=3,15fps 时 N=2。

伪代码示例:

if (m(t) > threshold) {
    overuse_counter++;
    if (overuse_counter >= N) {
        state = Overuse;
    }
} else if (m(t) < -threshold) {
    underuse_counter++;
    if (underuse_counter >= N) {
        state = Underuse;
    }
} else {
    overuse_counter = 0;
    underuse_counter = 0;
    state = Normal;
}

6.4 自适应阈值的数学原理

GCC v2 的自适应阈值,核心是维护一个抖动估计值 var。它用指数移动平均(EMA)来更新:

var = (1 - beta) * var + beta * |m(t) - m_avg|

然后阈值 gamma 就设为 K * sqrt(var)。K 是一个缩放因子,我一般取 2~3。为什么开根号?因为抖动方差是二次的,开根号后回到一次量纲,跟斜率单位一致。

注意:自适应阈值不是万能的。如果网络突然剧烈抖动(比如丢包率从0%跳到20%),阈值还没来得及跟上,还是会误判。我建议在自适应阈值的基础上,再加一个上限保护——比如阈值最大不超过 50ms,防止阈值被“撑爆”。

6.5 GCC v1 与 v2 的差异总结

除了阈值设计,v1 和 v2 在过载检测上还有几个关键差异:

  1. 趋势估计器:v1 用简单的线性回归,v2 用了卡尔曼滤波(虽然简化版),更平滑。
  2. 状态切换逻辑:v1 是“一次触发”,v2 是“多次确认”。
  3. 与码率控制器的交互:v1 直接输出三态信号给码率控制器,v2 还额外输出一个“置信度”指标。

我个人习惯是:如果网络环境可控(比如企业内网),用 v1 的固定阈值,响应快。如果网络环境复杂(比如移动网络、跨国链路),必须上 v2 的自适应阈值。

6.6 知识体系流程图

下面这张图,把过载检测器的核心逻辑串起来了。你可以看到输入、处理、输出,以及 v1 和 v2 的分叉点。

到达时间差增量 线性回归求斜率 m(t) GCC v1: 固定阈值 阈值 = 12.5ms (常数) GCC v2: 自适应阈值 阈值 = K * sqrt(var) 三态状态机 Normal → Overuse → Underuse 过载信号输出 Overuse / Underuse / Normal GCC v1 路径 GCC v2 路径

6.7 实战中的调参建议

如果你在实际项目中需要调过载检测器,我建议你关注这几个参数:

  • 阈值初始值:v1 用 12.5ms,v2 用 10ms 起步,让自适应慢慢调整。
  • 持续计数器 N:帧率越高,N 可以越大。我一般用 N = max(2, floor(fps / 10))
  • EMA 的 beta:v2 里抖动估计的平滑系数,0.1~0.3 之间。beta 越大,对抖动响应越快,但也越容易误判。

一个小技巧:我曾经在调试时,把过载检测器的日志打印出来,画成曲线图。你会发现,很多“过载”其实是假阳性——网络只是抖了一下,不是真的堵了。这时候调大 N 或者放宽阈值,效果立竿见影。

嗯,过载检测器就讲到这里。它虽然只是 GCC 里的一小块,但却是整个拥塞控制的“眼睛”。眼睛看错了,后面再怎么调码率都是白搭。

公众号:蓝海资料掘金营,微信deep3321