<!-- title: 慢半拍的智慧：抖动缓冲原理 -->

# 慢半拍的智慧：抖动缓冲原理

## 一个反直觉的问题：打电话为什么不重传？

你下载一个文件时丢了一个包，网卡会安安静静把它重传回来，文件最终完好无损。可你打电话时丢了一个包，对方听到的却是"滋——"的一声破音，然后这事就过去了，没人补传。

这不是历史遗留问题，而是一个精心设计的选择。实时语音走的是 UDP，不是 TCP；丢了的语音帧，宁可不要，也不补传。原因算一笔账就清楚了：语音编码器每 **20 毫秒**产出一帧，而网络往返时延（RTT）在现网轻松就有几十到两百毫秒。也就是说，等一个丢掉的包重传回来，说话人已经又说完了 3 到 10 帧内容——重传到的那个音再插回去，就像你听人讲话时，中途倒带重放半秒钟，对话节奏全毁。更糟的是 TCP 的队头阻塞：一个包丢了，排在后面的所有包都得等它，语音会变成一段一段的顿挫。

那不重传，丢包导致的空洞怎么办？答案是两条腿走路：**用"慢半拍"换取确定性，用"编瞎话"填补空洞**。这两条腿，就是本文的主角——抖动缓冲（Jitter Buffer）和丢包隐藏（PLC，Packet Loss Concealment）。它们是语音质量链路上最不显眼、却决定用户"感受"的两块装置。

## 抖动：网络真正送来的"噪声"

物理教材里的抖动（Jitter）指相位抖动，而 VoIP 语境里的抖动是一个统计概念：**数据包到达间隔的波动**。

理想网络像地铁：每 20 毫秒一帧，风雨无阻。真实网络像早高峰的公交：这一班 5 毫秒就到了，下一班堵了 80 毫秒。这种"忽快忽慢"就是抖动。注意，抖动不等于时延——时延是你等多久，抖动是"你等多久"这件事本身忽大忽小。

抖动从哪来？排队。每个路由器、每个基站调度器、每个防火墙的端口里都有队列，负载一重，排队时延就会剧烈波动。此外还有包乱序、路由切换导致的路径变化，都会表现为到达间隔的抖动。

对文件下载，抖动无所谓，TCP 的接收窗口会兜底。对实时语音，抖动是致命的：解码器必须以**恒定的 20ms 节奏**吃帧，快一帧没有内容可解，慢一帧缓冲耗尽——两种情况都会产生人耳极易察觉的断续。

## 缓冲：把"随机"整流成"恒定"

解决思路朴素得像生活经验：要赶一场重要的会议，你会**提前一班车出门，宁可在楼下等三分钟**。等待的三分钟，就是缓冲。

接收端收到第一个包时不立即播放，而是先攒一小段，然后设定一个确定性的播放时刻表：

$$T_{play}(i) = T_{first} + \hat{d} + k \cdot \hat{J}$$

其中 $T_{first}$ 是本次话突（talkspurt，一段连续说话）首包的到达时刻，$\hat{d}$ 是估计的平均网络时延，$\hat{J}$ 是抖动估计值，$k$ 是一个 1 到 4 之间的保险系数。$k$ 越大越保险，但延迟也越大——这就是语音工程里永恒的天平。

整流的原理可以这样理解：**缓冲把"到达时间的方差"吃进了自己肚子里**。包早到就在队列里多排一会儿，包晚到就消耗队列的库存。只要网络时延的波动不超过缓冲深度，解码器看到的就是一个完美的恒定节奏。

但缓冲有两个失效模式，值得每个工程师背下来：

| 状态 | 表现 | 听感 | 根因 |
|------|------|------|------|
| 缓冲过浅（欠载） | 解码器断粮 | 顿挫、机器音、断续 | 目标水位低于实际抖动 |
| 缓冲过深（过载） | 时延膨胀 | 对讲机感、"隔空喊话"感、抢话 | 水位目标过于保守 |

还有第三个更隐蔽的失效模式——**时钟频差**。发送端和接收端的采样时钟是两颗物理晶振，永远存在百万分之几十的频差。哪怕网络完美无抖动，缓冲水位也会被这颗"看不见的手"缓慢推向枯竭或溢出。这决定了缓冲器**必须是自适应的**，静态配置永远只是权宜之计。

## 自适应：什么时候动、怎么动

自适应抖动缓冲的核心是两个循环。

**第一个循环：估计。** 持续估计网络时延的均值和波动。RTP 协议在包头里带了序号和时间戳，接收端用到达时间和时间戳之差的变化量来估计抖动。RFC 3550 给出的经典指数滑动平均是：

$$J \leftarrow J + \frac{|D_i| - J}{16}$$

其中 $D_i$ 是相邻两包"到达时间之差"与"时间戳之差"的偏离量。除以 16 意味着这是约 1/16 权重的低通滤波——单个包的异常不会惊动估计值，趋势变化又能较快跟上。

**第二个循环：调整。** 有了估计值就动态升降目标水位。这里藏着整个设计中最精妙的一个洞察：

> **播放时刻表只能在说话停顿期间大幅调整。**

为什么？说话期间每一帧都被解码器"实时"吃掉，此刻提前播放等于跳过内容，推后播放等于重复内容——都会产生可闻的失真。而静音期没人说话，删一段静音或插一段静音，人耳毫无知觉。所以经典算法（如 Moon、Kurose 与 Towsley 在上世纪九十年代末的经典文献）在通话期使用接近 1 的极慢调整系数（约 0.998 量级），在静音期使用快得多的系数（约 0.99 量级）——**通话期稳如老狗，静音期偷偷挪表**。

```mermaid
flowchart TD
    A["检测到说话停顿"] --> B{"缓冲水位偏满<br/>还是偏空"}
    B -->|偏满| C["删掉一段静音<br/>播放时刻表提前"]
    B -->|偏空| D["补插一段静音<br/>播放时刻表推后"]
    C --> E["水位回落到目标区"]
    D --> E
    A2["正在说话中"] --> F["播放时刻表锁死<br/>不许动"]
    F --> G["最多做细微变速<br/>让人耳察觉不到"]
```

如果静音期等不到，水位在说话中途就必须纠正呢？现代实现不再粗暴丢帧或重复帧，而是**变速不变调**：把一小段已解码音频做时间上的拉伸或压缩（WSOLA 类时域波形相似叠加算法），语速变化百分之几，人耳几乎无感，缓冲水位却被悄悄扳了回来。WebRTC 的 NetEQ 模块把这条路走到了极致——它干脆不设传统的"缓冲队列"，而是由一个决策器在每一帧上选择正常播放、加速、减速、扩展静音或拼接过渡，缓冲深度变成了一个连续可调的连续量。

## 丢包隐藏：补上的帧是"编"出来的

缓冲能治抖动，治不了真丢包——超过了最深缓冲的等待极限，包就彻底迟到了。这时候轮到 PLC 上场：**解码器凭空合成一段"像样"的语音**，把空洞填上。

接收端 PLC 的手段按缺口大小分档：

```mermaid
flowchart TD
    A["该来的一帧没有来"] --> B{"缺口有多大"}
    B -->|只缺一帧<br/>约20ms| C["波形外推<br/>把尾音渐弱着重复"]
    B -->|缺几帧| D["合成舒适噪声<br/>模拟说话间隙"]
    B -->|缺一大段| E["按通话中断处理<br/>不再硬撑"]
```

单帧缺失的掩盖效果最好——20 毫秒在人耳的"听觉暂留"之内，外推一段逐渐衰减的波形，多数场景完全无感。缺口越长，合成内容越假，所以发送端配合很重要：

| 手段 | 在哪一端 | 原理 | 代价 |
|------|----------|------|------|
| 波形外推 / 参数外推 | 接收端 | 用前几帧的波形或线性预测系数顺势外推 | 无带宽代价，长缺口穿帮 |
| 舒适噪声 | 接收端 | 缺口落在静音段时填环境噪声 | 几乎零代价 |
| 冗余打包 | 发送端 | 一包捎带前一帧的压缩副本 | 带宽翻倍量级 |
| 前向纠错 FEC | 发送端 | 用异或校验包重建缺失帧 | 带宽增加，成块丢失时失效 |
| 交织 | 发送端 | 打散突发错误，化整为零交给 PLC | 引入整卷时延，实时语音慎用 |

你可能会问：VoLTE 空口不是有 HARQ 和非确认模式的无线链路控制层兜底吗？没错，但那套机制只覆盖**基站到手机**这一段。语音的 RTP 包从媒体网关出发，穿越 IP 承载网、防火墙、SBC，中间任何一个路由器队列抖动、任何一段链路丢包，链路层的重传都无能为力——这段"无人管"的长途，正是端到端 PLC 存在的理由。

## 端到端时延预算：每一毫秒都是借来的

国际电联 G.114 建议给实时语音划了条红线：**单向时延 150 毫秒以内，大多数应用感觉自然；150 到 400 毫秒勉强可用；超过 400 毫秒不可接受**。这条预算要摊给全链路，缓冲深度是其中最大的一块弹性开支：

| 环节 | 典型值 | 说明 |
|------|--------|------|
| 声码器算法时延 | 20ms 上下 | 帧长加前视窗，编码器固有的 |
| 打包延迟 | 20ms | 一帧一包，攒帧的时间 |
| 网络 + 空口 | 40~80ms | 随负载波动，抖动的源头 |
| **抖动缓冲深度** | **30~100ms** | **最大弹性项，直接可调** |
| 解码 + PLC | <10ms | 可忽略 |
| **合计** | **≈150ms** | 撞到 G.114 红线 |

缓冲每加深 10 毫秒，用户端"抢话"和"对讲机感"就重一分；而且时延还会连累回声——端到端时延越大，自己的声音绕一圈回来的重叠感越明显，回声消除器的搜窗压力也越大。时延、抖动、回声这三件事在语音质量里是连坐的。

为什么时延的杀伤力这么大？国际电联 E 模型给出过被广泛引用的量化。单向时延 $d$（毫秒）带来的损伤项（Cole–Rosenbluth 简化式）：

$$I_d = 0.024\,d + 0.11\,(d - 177.3)\,H(d - 177.3)$$

其中 $H$ 是阶跃函数——**177 毫秒是一个拐点，过了这个拐点时延惩罚开始陡增**。损伤折算成质量分：

$$R = 93.2 - I_d - I_e, \qquad MOS = 1 + 0.035R + 7\times10^{-6}R(R-60)(100-R)$$

$I_e$ 是编码器与丢包的联合损伤。这套公式的工程含义直白：缓冲深度不是"越大越保险"的技术参数，而是直接从 MOS 分里扣钱的成本项。

## 现网实战：语音质量投诉怎么查

理解了原理，排查思路就清晰了。用户投诉"电话卡"，第一步永远是**分诊**，区分三种病：

**第一步，看 RTCP 接收报告。** RTP 的伴生协议 RTCP 周期性携带两个关键字段：丢包率（fraction lost）和抖动（interarrival jitter，就是上文那个 1/16 滤波后的估计值）。丢包率高 → 病在网络丢包，往承载网和空口查；丢包率低但抖动大 → 病在排队，往拥塞节点和传输质量查。

**第二步，区分"真丢"与"迟丢"。** 严密的实现会把统计分开记：一种是包彻底没到（lost），一种是包到了但晚于播放时刻被接收端主动丢弃（discarded）。后者在包计数上"丢包率为零"，用户却在骂街——这种案子里真凶是抖动或缓冲配置，不是丢包。只看丢包率会把排查引向死胡同。

**第三步，警惕时钟漂移。** 如果投诉模式是"通话时间越长越卡"，高度怀疑两端采样钟频差：缓冲被缓慢抽干或撑爆，自适应机制被迫频繁变速。抓包验证方法：对比 RTP 时间戳的增长速率与抓包时钟的流逝速率，两者应当一致，偏差就是频差。

两个常见调试陷阱。其一，现网部署了 SRTP 加密后，抓包看不到语音载荷，只能依赖包头信息和包间隔统计——别指望还原音频听一听了事。其二，MOS 打点测试仪（基于 PESQ/POLQA 算法）自身的缓冲配置会影响得分，两台不同配置的探针对同一段网络打出不同 MOS 是常有的事，横向对比前先对齐探针配置。

最后留一个观念纠偏：抖动缓冲**治不了回声**。回声是声学与线路反射问题，缓冲是时延整流问题，两者症状都是"通话难受"，但病灶不同——把回声投诉归因于"缓冲太大"，是新手最常见的方向性错误。

## 小结

实时语音的传输哲学可以压缩成三句话：**可靠性靠预测（缓冲）而不是重传，连续性靠掩盖（PLC）而不是补发，自适应靠静音期悄悄挪表而不是随时乱动。**这三大设计与 TCP 世界"错了就重来"的逻辑彻底分道扬镳，根源只有一个——在实时会话里，晚到的好消息就是坏消息。

```mermaid
flowchart TD
    A["声码器每20ms出帧"] --> B["RTP打包<br/>载荷约30字节"]
    B --> C["头开销40字节<br/>比载荷还大"]
    C --> D["IP网络<br/>排队产生抖动"]
    D --> E{"按播放时刻表<br/>检查到达"}
    E -->|早到| F["进缓冲排队<br/>等待整流"]
    E -->|迟到| G["判定丢弃<br/>交PLC合成"]
    F --> H["恒定节奏解码"]
    G --> H
    H --> I["扬声器平滑播放"]
```

## 常见问题

**Q: 抖动缓冲和 TCP 重传为什么不能都要？**
A: 两者服务的是不同的时间观。TCP 重传保证"数据最终都到"，但不管什么时候到；实时语音需要"按时到"，迟到的语音帧没有任何价值。重传一个 20ms 的语音帧要付出的等待（半个 RTT 起步）远超它的播放窗口，还会阻塞后续帧，所以实时语音从协议选型（UDP/RTP）到接收端设计（缓冲+PLC）整体绕开了重传。

**Q: 抖动和丢包怎么区分？**
A: 抖动是到达间隔的波动，丢包是包根本没到。抓包看序号连续性：序号断档是真丢；序号连续但到达间隔忽大忽小是抖动。RTCP 的 fraction lost 对应前者，interarrival jitter 对应后者。两者对听感的破坏路径不同——抖动过深会转化为接收端"迟丢"，这也是很多"零丢包却卡顿"案例的真相。

**Q: 为什么视频会议画面卡成幻灯片还能忍，声音一卡就烦？**
A: 人耳对人声断续和时延极其敏感，因为人类对话依赖"轮替节奏"——你说完我接话，超过几百毫秒的错位就会让双方开始互相抢话。视觉有显著性补偿，掉几帧画面大脑自动脑补；而语音的每一帧都携带时间结构，空洞直接可闻。这就是为什么语音系统的时延预算（150ms 级）比视频（数百毫秒到秒级缓冲）苛刻得多。

**Q: WebRTC 的 NetEQ 和传统抖动缓冲的区别是什么？**
A: 传统缓冲是一个"队列 + 固定目标水位"，调整靠静音期删除/插入静音，粒度粗。NetEQ 取消了刚性队列，用逐帧决策替代：每 10ms 决定正常播放、加速、减速、扩展或拼接，用 WSOLA 变速实现水位微调，让缓冲深度变成连续控制的量。思想一脉相承——都是"估计时延、维护播放时刻表、在感知不敏感处调整"——只是控制粒度从"帧级开关"进化到了"每帧连续微操"。

**Q: 空口有 HARQ 和 RLC，为什么端到端还要 PLC？**
A: HARQ 与无线链路层重传只覆盖手机到基站这一跳，且为了时延通常采用非确认模式、重传次数极少。RTP 媒体流出基站后还要穿越承载网、防火墙、SBC、媒体锚点，这些段落在链路层之上、没有逐包重传，它们产生的丢包和抖动只能由接收端的缓冲与 PLC 兜底。一套完整的语音质量方案，是逐跳机制与端到端机制的分工协作。

## 参考来源

1. [RFC 3550: RTP: A Transport Protocol for Real-Time Applications](https://datatracker.ietf.org/doc/html/rfc3550) — RTP/RTCP 协议本体，含帧间到达抖动估计公式与接收报告字段定义
2. [ITU-T G.114: One-way transmission time](https://www.itu.int/rec/T-REC-G.114/en) — 单向时延 150/400ms 分级的出处
3. [Jitter buffer - Wikipedia](https://en.wikipedia.org/wiki/Jitter_buffer) — 抖动缓冲的定义、固定式与自适应式对比
4. [Packet loss concealment - Wikipedia](https://en.wikipedia.org/wiki/Packet_loss_concealment) — 接收端与发送端丢包隐藏技术分类
5. S. Moon, J. Kurose, D. Towsley《Packet audio playout delay adjustment for performance improvement of voice over IP》 — 说话期/静音期双系数自适应播放调度的经典文献；WebRTC 的 NetEQ 实现可在其官方源码仓（webrtc.googlesource.com）modules/audio_coding/neteq 目录下研读

---
> 本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵**爱卫生**（微信ID: gprshome201101）维护，聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。
> 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
