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 的核心功能包括:

  1. 吸收到达时间变化:缓冲数据包,将不规则的到达转化为规则的播放

  2. 数据包重排序:将乱序到达的包按序列号重新排列

  3. 流同步:延迟一个流以匹配另一个流的播放(如音视频唇同步)

  4. 延迟首包解码:等待足够的缓冲后再开始播放

  5. 判定丢包:确定哪些包已丢失,触发丢包隐藏(PLC)

  6. 去重:丢弃重复到达的数据包

固定抖动缓冲区 vs 自适应抖动缓冲区

固定抖动缓冲区(Fixed Jitter Buffer)

固定抖动缓冲区使用预设的固定延迟值:

  • 延迟固定不变

  • 不需要计算抖动统计信息

  • 实现简单

  • 当抖动变化较大时,语音质量会受到影响

示例配置:

缓冲区最大容量 = 100ms
播放延迟 = 40ms(缓冲区中至少有 40ms 数据时开始播放)

自适应抖动缓冲区(Adaptive Jitter Buffer)

自适应抖动缓冲区根据实时网络状况动态调整延迟:

  • 延迟根据抖动实时变化动态调整

  • 关键技术:时间缩放(Time Scaling)——在不影响语音质量的前提下调整播放时间

    • 对语音包进行缩放(加速/减速播放)

    • 对静音包进行缩放(拉伸/压缩静音段)

  • 实现复杂度较高

  • 使用良好的时间缩放算法,语音质量不会受到大幅抖动变化的影响

固定 vs 自适应 Jitter Buffer 对比

特性

固定 Jitter Buffer

自适应 Jitter Buffer

延迟

固定,可能偏高

动态调整,通常更低

实现复杂度

低

高

网络适应性

差

好

语音质量

稳定网络下好

各种网络下都较好

适用场景

网络稳定的局域网

互联网、移动网络

自适应抖动缓冲区算法

自适应 Jitter Buffer 的核心是 目标延迟估计 (Target Delay Estimation),即动态计算最优的缓冲延迟。

直方图法(Histogram-based)

WebRTC 的 NetEQ 使用直方图法来估计目标延迟:

  1. 维护一个到达间隔时间(IAT)的直方图

  2. 每收到一个包,更新直方图

  3. 根据直方图的某个百分位(如 95th percentile)确定目标延迟

  4. 目标延迟 = 使得 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 类),需要特殊处理:

  1. 检测到达间隔的突变(超过阈值的跳变)

  2. 区分是持续性变化还是瞬态峰值

  3. 对瞬态峰值使用更快的衰减,避免不必要的延迟增加

WebRTC NetEQ 中的 Jitter Buffer

NetEQ 是 WebRTC 中负责音频接收端处理的核心模块,其中的 Jitter Buffer 实现是业界最成熟的自适应方案之一。

核心组件

NetEQ 核心类

类名

职责

PacketBuffer

存储接收到的 RTP 包,按时间戳排序

DelayManager

管理目标延迟的计算,维护 IAT 直方图

DecisionLogic

根据缓冲区状态决定播放策略(正常/加速/减速/丢包隐藏)

Accelerate

加速播放算法,通过丢弃部分样本缩短音频

PreemptiveExpand

减速播放算法,通过插入样本拉长音频

Expand

丢包隐藏(PLC),生成替代音频

Merge

将 PLC 生成的音频与真实音频平滑过渡

Normal

正常播放模式

播放决策逻辑

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 的关键是找到波形相似的位置进行拼接,避免产生可听的失真:

原始波形:  ~~~∧~~~∨~~~∧~~~∨~~~∧~~~∨~~~
加速后:    ~~~∧~~~∨~~~~~~~∨~~~∧~~~∨~~~  (去掉一个周期)
减速后:    ~~~∧~~~∨~~~∧~~~∧~~~∨~~~∧~~~∨~~~  (重复一个周期)

关键参数与调优

Jitter Buffer 关键参数

参数

典型值

说明

min_delay

0 ms

最小缓冲延迟,设为 0 表示由算法自动决定

max_delay

500 ms

最大缓冲延迟上限,防止延迟无限增长

target_delay

动态

由 DelayManager 根据网络状况动态计算

histogram_quantile

0.95

IAT 直方图的百分位,用于计算目标延迟

max_packets_in_buffer

200

缓冲区最大包数,防止内存溢出

accelerate_threshold

2 帧

缓冲区水位超过目标延迟多少帧时触发加速

preemptive_expand_threshold

1 帧

缓冲区水位低于目标延迟多少帧时触发减速

peak_detection

开启

是否启用峰值检测以处理突发抖动

调优建议

  • 低延迟场景 (如游戏语音):降低 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;
};

常见问题与解决方案

常见问题排查

问题

可能原因

解决方案

延迟过高

目标延迟设置过大;网络抖动持续偏高

降低 max_delay;检查网络质量;降低 histogram_quantile

音频断续/卡顿

缓冲区过小;丢包率过高

增大 max_delay;检查网络丢包;启用 FEC

咔嗒声/爆音

PLC 算法质量差;加速/减速拼接不平滑

使用 WSOLA 算法;增加交叉淡入淡出

单向音频

缓冲区溢出;时间戳不连续

检查 RTP 时间戳;增大缓冲区容量

音调变化

时间缩放算法不当

使用基于基频的 WSOLA;避免简单的样本丢弃/重复

缓冲区持续增长

发送端时钟漂移;采样率不匹配

检查时钟同步;确认编解码器采样率一致

实现 Jitter Buffer 的步骤

总结实现一个音频 Jitter Buffer 的关键步骤:

  1. 理解抖动概念:抖动是由于网络拥塞、丢包或其他因素导致的音频包到达时间的变化。

  2. 设计缓冲区结构:通常使用按时间戳排序的优先队列或有序映射。

  3. 设置缓冲区大小:根据需求确定合适的缓冲区大小,较大的缓冲区可以处理更大的抖动变化,但会引入额外延迟。

  4. 接收和存储包:按序列号或时间戳存储到缓冲区,正确处理乱序包。

  5. 估计播放时间:使用接收包的时间戳估计每个包的播放时间。

  6. 调度包播放:根据估计的播放时间启动播放定时器。

  7. 处理迟到或丢失的包:使用插值、外推或丢包隐藏技术。

  8. 动态调整缓冲区大小:监控网络状况,动态调整缓冲区大小。

  9. 持续更新缓冲区:定期移除已播放的包,添加新到达的包。

  10. 实现错误恢复机制:考虑 FEC 或重传等额外的错误恢复机制。

  11. 测试和优化:在各种网络条件下测试,调优缓冲区大小、播放时间和错误恢复参数。

参考资料

  • 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.cc

  • WebRTC DecisionLogic: modules/audio_coding/neteq/decision_logic.cc

  • Adaptive Playout Mechanisms for VoIP

  • The Effect of Jitter Buffer Algorithms on VoIP Quality