网络抖动:成因与测量
概述
网络抖动(Jitter)是实时通信的头号敌人之一。它指的是数据包到达时间间隔的**不规则变化**——发送端以固定间隔发包(如每 20ms 一个),但接收端收到的间隔可能是 15ms、25ms、10ms、35ms……
对于语音和视频通信,抖动会导致:
音频断续、卡顿
视频帧不均匀、画面跳跃
唇音不同步
播放缓冲区溢出或欠载
抖动的成因
flowchart LR
A[发送端<br/>均匀发包] --> B[网络路径]
B --> C[接收端<br/>不均匀到达]
subgraph 网络路径
B1[路由器排队]
B2[链路竞争]
B3[路由变化]
B4[WiFi 重传]
B5[缓冲膨胀]
end
路由器排队延迟
路由器的输出队列是抖动的主要来源。当多个流竞争同一出口时,包在队列中等待的时间不确定。
链路层竞争
WiFi 的 CSMA/CA 机制导致发送时机不确定;蜂窝网络的调度也引入变化。
路由变化
BGP 路由收敛期间,连续的包可能走不同路径,延迟差异可达数十毫秒。
缓冲膨胀(Bufferbloat)
网络设备中过大的缓冲区会吸收突发流量,但代价是增加延迟和抖动。
操作系统调度
发送端和接收端的 OS 调度延迟也会贡献抖动,特别是在非实时操作系统上。
抖动的测量
RFC 3550 定义
RTP 协议(RFC 3550)定义了 interarrival jitter 的计算方法,这是最广泛使用的抖动度量:
对于每对连续的 RTP 包 i 和 i-1:
D(i, i-1) = (R_i - R_{i-1}) - (S_i - S_{i-1})
其中:
S_i = 包 i 的 RTP 时间戳(发送时间)
R_i = 包 i 的到达时间(接收端本地时钟)
D 就是"传输延迟的变化量"
抖动用指数加权移动平均(EWMA)平滑:
J(i) = J(i-1) + (|D(i, i-1)| - J(i-1)) / 16
这个公式的含义:
D > 0:包 i 比预期晚到(延迟增加)D < 0:包 i 比预期早到(延迟减少)D = 0:完美,无抖动除以 16 是一个低通滤波器,平滑瞬时波动
RTCP SR/RR 中的抖动字段就是用这个公式计算的。
代码实现
type JitterEstimator struct {
jitter float64
lastTransit int64
firstPacket bool
}
func (j *JitterEstimator) Update(rtpTimestamp uint32, arrivalTime int64, clockRate uint32) {
// 将 RTP 时间戳转换为与到达时间相同的单位(微秒)
sendTime := int64(rtpTimestamp) * 1000000 / int64(clockRate)
transit := arrivalTime - sendTime
if j.firstPacket {
j.lastTransit = transit
j.firstPacket = false
return
}
d := transit - j.lastTransit
j.lastTransit = transit
if d < 0 {
d = -d
}
// EWMA: J = J + (|D| - J) / 16
j.jitter += (float64(d) - j.jitter) / 16.0
}
其他抖动度量
度量 |
定义 |
适用场景 |
|---|---|---|
RFC 3550 Jitter |
EWMA 平滑的传输延迟变化 |
RTCP 报告,通用 |
P95/P99 Jitter |
第 95/99 百分位的延迟变化 |
网络质量评估 |
Max Jitter |
观测窗口内最大延迟变化 |
Jitter Buffer 设计 |
Mean Jitter |
平均延迟变化 |
长期趋势分析 |
Jitter Range |
max(delay) - min(delay) |
缓冲区大小估算 |
抖动与 Jitter Buffer
Jitter Buffer 是对抗抖动的核心机制——在接收端引入一个缓冲区,将不均匀到达的包重新排列为均匀的播放流:
flowchart LR
A[网络<br/>不均匀到达] --> B[Jitter Buffer<br/>缓冲 + 重排]
B --> C[播放器<br/>均匀播放]
style B fill:#f9f,stroke:#333
缓冲区大小的权衡:
太小:无法吸收抖动,导致欠载(underrun),播放卡顿
太大:增加端到端延迟,影响交互体验
理想的缓冲区大小应该刚好覆盖网络抖动的范围。详见 Jitter Buffer 章节。
抖动对不同媒体的影响
音频
人耳对音频中断极其敏感,10ms 的间断就能感知
20ms 帧长的语音流,抖动超过 20ms 就会导致帧丢失
Jitter Buffer 通常设置 40-200ms
视频
视频帧间隔较大(33ms @30fps),对抖动的容忍度稍高
但视频帧通常跨多个 RTP 包,需要所有包到齐才能解码
关键帧(I 帧)丢失影响更大(后续 P/B 帧都无法解码)
实际网络中的抖动数据
典型网络环境的抖动参考值:
网络类型 |
平均抖动 |
P95 抖动 |
说明 |
|---|---|---|---|
有线局域网 |
< 1 ms |
< 2 ms |
几乎无抖动 |
企业 WiFi |
2-10 ms |
20-50 ms |
取决于负载 |
家庭 WiFi |
5-20 ms |
50-100 ms |
干扰较多 |
4G LTE |
10-30 ms |
50-150 ms |
调度引入抖动 |
5G NR |
5-15 ms |
20-50 ms |
改善明显 |
跨国互联网 |
10-50 ms |
100-300 ms |
路由变化影响大 |
卫星链路 |
20-100 ms |
200-500 ms |
高延迟 + 高抖动 |
监控与调试
Chrome webrtc-internals
在 Chrome 中访问 chrome://webrtc-internals,可以看到:
jitterBufferDelay:Jitter Buffer 引入的延迟jitter:RFC 3550 计算的抖动值packetsLost:丢包数(高抖动常伴随丢包)
getStats() API
const stats = await peerConnection.getStats();
stats.forEach(report => {
if (report.type === 'inbound-rtp' && report.kind === 'audio') {
console.log('Jitter:', report.jitter);
console.log('JB Delay:', report.jitterBufferDelay);
console.log('JB Emitted:', report.jitterBufferEmittedCount);
// 平均 JB 延迟 = jitterBufferDelay / jitterBufferEmittedCount
}
});
小结
抖动是实时通信中不可避免的网络现象。理解它的成因和测量方法,是设计 Jitter Buffer、选择 FEC 策略、调优拥塞控制算法的基础。
后续章节将深入讲解音频和视频 Jitter Buffer 的设计,以及 WebRTC NetEQ 模块的实现。