Jitter Buffer
Abstract |
Audio Jitter Buffer |
Authors |
Walter Fan |
Status |
v2 |
Updated |
2026-03-20 |
概述
在实时音视频通信中,抖动缓冲区(Jitter Buffer)是接收端最关键的组件之一。网络传输的不确定性导致 RTP 数据包到达时间不均匀,而音频播放器需要以恒定速率消费数据。Jitter Buffer 就是连接这两者的桥梁——它将网络的"不规则"转化为播放器的"规则"。
一个设计良好的 Jitter Buffer 需要在两个矛盾的目标之间取得平衡:
低延迟:缓冲越少,端到端延迟越低,对话体验越好
低丢包:缓冲越多,能容忍的网络抖动越大,丢包率越低
ITU-T G.114 建议单向延迟应低于 150ms 以获得可接受的对话体验。150ms 到 400ms 之间用户能感知到延迟但仍可接受,超过 600ms 则不可接受。
抖动的类型与度量
抖动(Jitter)是由网络路径上的排队、争用和序列化效应引起的数据包传输延迟的变化。一般而言,在慢速或严重拥塞的链路上更可能发生更高级别的抖动。
抖动分类
根据 ITU-T 的分类,抖动可分为三种类型:
(i) A 类 – 恒定抖动(Constant Jitter)
数据包到数据包通过网络传输延迟变化的大致恒定水平。这是最常见的抖动类型,通常由网络设备的固有处理延迟差异引起。
(ii) B 类 – 瞬态抖动(Transient Jitter)
以单个数据包可能引起的大量增量延迟为特征。通常由突发的网络事件引起,如路由器队列溢出。
(iii) C 类 – 短期延迟变化(Short-term Delay Variation)
特点是延迟的增加持续了一定数量的数据包,并且可能伴随着数据包到数据包延迟变化的增加。C 类抖动通常与拥塞和路由变化有关。
抖动度量
RFC 3550 定义了 RTP 抖动的计算方法——基于相邻包的到达间隔差异:
D(i,j) = (Rj - Ri) - (Sj - Si)
其中:
- Ri, Rj: 包 i 和包 j 的接收时间
- Si, Sj: 包 i 和包 j 的发送时间(RTP timestamp)
抖动的指数加权移动平均:
J(i) = J(i-1) + (|D(i-1,i)| - J(i-1)) / 16
常用的抖动度量指标包括:
指标 |
说明 |
|---|---|
Mean Jitter |
平均抖动,反映整体网络稳定性 |
Max Jitter |
最大抖动,决定缓冲区的最小容量 |
Jitter Percentile (P95/P99) |
百分位抖动,用于自适应算法的目标延迟计算 |
Inter-arrival Jitter (RFC 3550) |
RTCP SR/RR 中报告的抖动值 |
抖动缓冲区的工作原理
网络以异步方式传递 RTP 数据包,延迟各不相同。为了以合理的质量播放音频流,接收端需要将可变延迟转换为恒定延迟。
基本工作流程
发送端 网络 接收端
┌──────┐ ┌──────────────┐
│ 编码 │──→ [P1] ──→ ···延迟 20ms··· ──→ │ │
│ │──→ [P2] ──→ ···延迟 35ms··· ──→ │ Jitter │ ──→ 解码 ──→ 播放
│ │──→ [P3] ──→ ···延迟 15ms··· ──→ │ Buffer │
│ │──→ [P4] ──→ ···延迟 50ms··· ──→ │ │
└──────┘ └──────────────┘
时间轴示意(20ms 帧间隔):
发送时刻: |--P1--|--P2--|--P3--|--P4--|--P5--|--P6--|
0 20 40 60 80 100 120 ms
到达时刻: |P1| |P3| |P2| |P4| |P5||P6|
20 55 60 110 120 125 ms
(乱序、间隔不均匀)
播放时刻: |--P1--|--P2--|--P3--|--P4--|--P5--|--P6--|
80 100 120 140 160 180 200 ms
(经过 Jitter Buffer 重排后,均匀播放)
核心功能
Jitter Buffer 的核心功能包括:
吸收到达时间变化:缓冲数据包,将不规则的到达转化为规则的播放
数据包重排序:将乱序到达的包按序列号重新排列
流同步:延迟一个流以匹配另一个流的播放(如音视频唇同步)
延迟首包解码:等待足够的缓冲后再开始播放
判定丢包:确定哪些包已丢失,触发丢包隐藏(PLC)
去重:丢弃重复到达的数据包
固定抖动缓冲区 vs 自适应抖动缓冲区
固定抖动缓冲区(Fixed Jitter Buffer)
固定抖动缓冲区使用预设的固定延迟值:
延迟固定不变
不需要计算抖动统计信息
实现简单
当抖动变化较大时,语音质量会受到影响
示例配置:
缓冲区最大容量 = 100ms
播放延迟 = 40ms(缓冲区中至少有 40ms 数据时开始播放)
自适应抖动缓冲区(Adaptive Jitter Buffer)
自适应抖动缓冲区根据实时网络状况动态调整延迟:
延迟根据抖动实时变化动态调整
关键技术:时间缩放(Time Scaling)——在不影响语音质量的前提下调整播放时间
对语音包进行缩放(加速/减速播放)
对静音包进行缩放(拉伸/压缩静音段)
实现复杂度较高
使用良好的时间缩放算法,语音质量不会受到大幅抖动变化的影响
特性 |
固定 Jitter Buffer |
自适应 Jitter Buffer |
|---|---|---|
延迟 |
固定,可能偏高 |
动态调整,通常更低 |
实现复杂度 |
低 |
高 |
网络适应性 |
差 |
好 |
语音质量 |
稳定网络下好 |
各种网络下都较好 |
适用场景 |
网络稳定的局域网 |
互联网、移动网络 |
自适应抖动缓冲区算法
自适应 Jitter Buffer 的核心是 目标延迟估计 (Target Delay Estimation),即动态计算最优的缓冲延迟。
直方图法(Histogram-based)
WebRTC 的 NetEQ 使用直方图法来估计目标延迟:
维护一个到达间隔时间(IAT)的直方图
每收到一个包,更新直方图
根据直方图的某个百分位(如 95th percentile)确定目标延迟
目标延迟 = 使得 95% 的包能在播放前到达的最小缓冲时间
IAT 直方图示例:
频率
│
│ ██
│ ████
│ ██████
│ ████████
│ ██████████
│████████████ ██
└──────────────────→ 到达间隔 (ms)
15 20 25 30 35 40 45 50
P95 = 40ms → 目标延迟 = 40ms
指数加权移动平均法(EWMA)
另一种常用方法是使用 EWMA 来平滑抖动估计:
jitter_estimate = α * |actual_delay - expected_delay| + (1 - α) * jitter_estimate
target_delay = mean_delay + k * jitter_estimate
其中:
- α: 平滑因子(通常 0.01 ~ 0.1)
- k: 安全系数(通常 2 ~ 4,对应不同的丢包容忍度)
峰值检测法
对于突发抖动(B 类),需要特殊处理:
检测到达间隔的突变(超过阈值的跳变)
区分是持续性变化还是瞬态峰值
对瞬态峰值使用更快的衰减,避免不必要的延迟增加
WebRTC NetEQ 中的 Jitter Buffer
NetEQ 是 WebRTC 中负责音频接收端处理的核心模块,其中的 Jitter Buffer 实现是业界最成熟的自适应方案之一。
核心组件
类名 |
职责 |
|---|---|
|
存储接收到的 RTP 包,按时间戳排序 |
|
管理目标延迟的计算,维护 IAT 直方图 |
|
根据缓冲区状态决定播放策略(正常/加速/减速/丢包隐藏) |
|
加速播放算法,通过丢弃部分样本缩短音频 |
|
减速播放算法,通过插入样本拉长音频 |
|
丢包隐藏(PLC),生成替代音频 |
|
将 PLC 生成的音频与真实音频平滑过渡 |
|
正常播放模式 |
播放决策逻辑
NetEQ 的 DecisionLogic 在每个 10ms 帧周期做出以下决策:
if (buffer_level > target_delay + threshold):
→ Accelerate(加速播放,降低缓冲区水位)
elif (buffer_level < target_delay - threshold):
if (next_packet_available):
→ PreemptiveExpand(减速播放,等待更多包到达)
else:
→ Expand(丢包隐藏,生成替代音频)
elif (next_packet_available):
→ Normal(正常解码播放)
else:
→ Expand(丢包隐藏)
加速与减速播放
加速播放(Accelerate):当缓冲区水位过高时,通过 WSOLA(Waveform Similarity Overlap-Add)算法丢弃部分音频样本,在不改变音调的前提下缩短播放时间。
减速播放(PreemptiveExpand):当缓冲区水位过低时,通过 WSOLA 算法插入重复的音频样本,拉长播放时间以等待更多包到达。
WSOLA 的关键是找到波形相似的位置进行拼接,避免产生可听的失真:
原始波形: ~~~∧~~~∨~~~∧~~~∨~~~∧~~~∨~~~
加速后: ~~~∧~~~∨~~~~~~~∨~~~∧~~~∨~~~ (去掉一个周期)
减速后: ~~~∧~~~∨~~~∧~~~∧~~~∨~~~∧~~~∨~~~ (重复一个周期)
关键参数与调优
参数 |
典型值 |
说明 |
|---|---|---|
|
0 ms |
最小缓冲延迟,设为 0 表示由算法自动决定 |
|
500 ms |
最大缓冲延迟上限,防止延迟无限增长 |
|
动态 |
由 DelayManager 根据网络状况动态计算 |
|
0.95 |
IAT 直方图的百分位,用于计算目标延迟 |
|
200 |
缓冲区最大包数,防止内存溢出 |
|
2 帧 |
缓冲区水位超过目标延迟多少帧时触发加速 |
|
1 帧 |
缓冲区水位低于目标延迟多少帧时触发减速 |
|
开启 |
是否启用峰值检测以处理突发抖动 |
调优建议
低延迟场景 (如游戏语音):降低
histogram_quantile到 0.90,接受更高的丢包率换取更低延迟高质量场景 (如音乐直播):提高
histogram_quantile到 0.99,增加缓冲以减少丢包移动网络:适当增大
max_delay,因为移动网络抖动通常更大WiFi 网络:注意 WiFi 的突发抖动特性,确保峰值检测开启
性能指标
评估 Jitter Buffer 性能的关键指标:
指标 |
说明 |
|---|---|
Buffer Delay |
当前缓冲延迟(ms),反映实时延迟水平 |
Target Delay |
目标缓冲延迟(ms),算法计算的最优值 |
Concealment Rate |
丢包隐藏比率(%),反映丢包对音质的影响 |
Accelerate Rate |
加速播放比率(%),过高说明缓冲区经常过满 |
Decelerate Rate |
减速播放比率(%),过高说明缓冲区经常过空 |
Packet Loss Rate |
丢包率(%),包括网络丢包和缓冲区溢出丢包 |
Late Packet Rate |
迟到包比率(%),到达时已过播放时间的包 |
Reorder Rate |
乱序率(%),需要重排序的包比例 |
在 WebRTC 中,这些指标可通过 getStats() API 获取:
const stats = await peerConnection.getStats();
stats.forEach(report => {
if (report.type === 'inbound-rtp' && report.kind === 'audio') {
console.log('Jitter Buffer Delay:', report.jitterBufferDelay);
console.log('Jitter Buffer Target Delay:', report.jitterBufferTargetDelay);
console.log('Concealed Samples:', report.concealedSamples);
console.log('Total Samples Received:', report.totalSamplesReceived);
console.log('Concealment Rate:',
report.concealedSamples / report.totalSamplesReceived * 100, '%');
}
});
实现示例
以下是一个简化的自适应 Jitter Buffer 伪代码:
class AdaptiveJitterBuffer {
public:
// 插入收到的 RTP 包
void InsertPacket(const RtpPacket& packet) {
uint32_t seq = packet.sequence_number();
uint32_t ts = packet.timestamp();
// 计算到达间隔
if (last_arrival_time_ > 0) {
int64_t arrival_delta = now_ms() - last_arrival_time_;
int64_t send_delta = (ts - last_timestamp_) / sample_rate_hz_ * 1000;
int64_t iat = arrival_delta - send_delta; // 到达间隔偏差
UpdateHistogram(iat);
UpdateTargetDelay();
}
last_arrival_time_ = now_ms();
last_timestamp_ = ts;
// 按时间戳排序插入缓冲区
buffer_.Insert(packet);
}
// 获取下一帧用于播放
PlayoutDecision GetNextFrame(AudioFrame* frame) {
int buffer_level = buffer_.BufferLevel();
if (buffer_level > target_delay_ + accelerate_threshold_) {
// 缓冲区过满,加速播放
return Accelerate(frame);
} else if (buffer_level < target_delay_ - preemptive_threshold_) {
if (buffer_.HasNextPacket()) {
return PreemptiveExpand(frame); // 减速播放
} else {
return Expand(frame); // 丢包隐藏
}
} else if (buffer_.HasNextPacket()) {
return Normal(frame); // 正常播放
} else {
return Expand(frame); // 丢包隐藏
}
}
private:
void UpdateHistogram(int64_t iat) {
// 更新 IAT 直方图
int bucket = iat / bucket_size_ms_;
histogram_[bucket]++;
total_count_++;
// 老化:降低旧数据的权重
if (total_count_ > max_count_) {
for (auto& [k, v] : histogram_) {
v = v * decay_factor_;
}
total_count_ *= decay_factor_;
}
}
void UpdateTargetDelay() {
// 根据直方图的 P95 计算目标延迟
int cumulative = 0;
int threshold = total_count_ * quantile_; // quantile_ = 0.95
for (auto& [bucket, count] : histogram_) {
cumulative += count;
if (cumulative >= threshold) {
target_delay_ = bucket * bucket_size_ms_;
break;
}
}
// 限制在 [min_delay_, max_delay_] 范围内
target_delay_ = std::clamp(target_delay_, min_delay_, max_delay_);
}
PacketBuffer buffer_;
std::map<int, int> histogram_;
int target_delay_ = 60; // ms
int min_delay_ = 0; // ms
int max_delay_ = 500; // ms
int accelerate_threshold_ = 20; // ms
int preemptive_threshold_ = 10; // ms
double quantile_ = 0.95;
double decay_factor_ = 0.998;
int64_t last_arrival_time_ = 0;
uint32_t last_timestamp_ = 0;
int sample_rate_hz_ = 48000;
int total_count_ = 0;
int max_count_ = 1000;
int bucket_size_ms_ = 5;
};
常见问题与解决方案
问题 |
可能原因 |
解决方案 |
|---|---|---|
延迟过高 |
目标延迟设置过大;网络抖动持续偏高 |
降低 |
音频断续/卡顿 |
缓冲区过小;丢包率过高 |
增大 |
咔嗒声/爆音 |
PLC 算法质量差;加速/减速拼接不平滑 |
使用 WSOLA 算法;增加交叉淡入淡出 |
单向音频 |
缓冲区溢出;时间戳不连续 |
检查 RTP 时间戳;增大缓冲区容量 |
音调变化 |
时间缩放算法不当 |
使用基于基频的 WSOLA;避免简单的样本丢弃/重复 |
缓冲区持续增长 |
发送端时钟漂移;采样率不匹配 |
检查时钟同步;确认编解码器采样率一致 |
实现 Jitter Buffer 的步骤
总结实现一个音频 Jitter Buffer 的关键步骤:
理解抖动概念:抖动是由于网络拥塞、丢包或其他因素导致的音频包到达时间的变化。
设计缓冲区结构:通常使用按时间戳排序的优先队列或有序映射。
设置缓冲区大小:根据需求确定合适的缓冲区大小,较大的缓冲区可以处理更大的抖动变化,但会引入额外延迟。
接收和存储包:按序列号或时间戳存储到缓冲区,正确处理乱序包。
估计播放时间:使用接收包的时间戳估计每个包的播放时间。
调度包播放:根据估计的播放时间启动播放定时器。
处理迟到或丢失的包:使用插值、外推或丢包隐藏技术。
动态调整缓冲区大小:监控网络状况,动态调整缓冲区大小。
持续更新缓冲区:定期移除已播放的包,添加新到达的包。
实现错误恢复机制:考虑 FEC 或重传等额外的错误恢复机制。
测试和优化:在各种网络条件下测试,调优缓冲区大小、播放时间和错误恢复参数。
参考资料
RFC 3550: RTP: A Transport Protocol for Real-Time Applications
RFC 3551: RTP Profile for Audio and Video Conferences
ITU-T G.114: One-way transmission time
ITU-T G.131: Talker echo and its control
WebRTC NetEQ 源码:
modules/audio_coding/neteq/WebRTC DelayManager:
modules/audio_coding/neteq/delay_manager.ccWebRTC DecisionLogic:
modules/audio_coding/neteq/decision_logic.cc