# NetEq 丢包隐藏 · 凭空编造以假乱真的音频

> 音频包丢了、声卡却要音频，NetEq 靠 Expand 操作「编造」一段填充——分析最近的波形、找出基音周期、外推续上、再逐渐淡出。这一章讲清丢包隐藏的原理、为什么语音能这么编，以及 Merge 如何把编造的填充和真实音频无缝拼接。

- 作者：David（道雾轩）
- 专栏：音视频与 WebRTC 详解（https://daiw.org/manual/webrtc.md）
- 最后更新：2026-07-14
- 原文：https://daiw.org/manual/webrtc/neteq-plc
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# NetEq 丢包隐藏 · 凭空编造以假乱真的音频

[六种操作](https://daiw.org/manual/webrtc/neteq-operations)里最神奇的，是 **Expand（丢包隐藏，PLC）**：包丢了、[声卡却每 10ms 准时要音频](https://daiw.org/manual/webrtc/neteq-overview)，NetEq **凭空编造**一段填上，还要以假乱真。这是[音频独有的招数](https://daiw.org/manual/webrtc/audio-codecs)（[视频编不出、只能花屏](https://daiw.org/manual/webrtc/video-decode-recovery)）。这一章看它怎么“变”出音频。

## 为什么语音能编

先解释这为什么可行——它建立在语音信号的一个特性上：

<Callout type="info">
  **语音在短时间内是高度“可预测”的——它有明显的**基音周期（pitch）**，即声带振动的重复模式。丢失的这 20ms，和它前面的 20ms 极为相似，所以可以“接着前面的波形续下去”。** 想想元音“啊——”的发声：那是一段近乎周期性重复的波形。如果丢了中间一小段，把前面一个基音周期的波形**复制、续接**上去，听起来几乎一样。这就是丢包隐藏的物理基础：**语音的短时准周期性，让“续写”成为可能**。

  这也是[音频和视频的根本差异](https://daiw.org/manual/webrtc/audio-pipeline-overview)：语音波形短时可预测（能续），而[视频丢帧](https://daiw.org/manual/webrtc/video-decode-recovery)没有这种简单的可外推性（续出来就是花屏）。所以音频能玩丢包隐藏，视频只能[等重传 / 关键帧](https://daiw.org/manual/webrtc/video-decode-recovery)。
</Callout>

## Expand 怎么编

`Expand`（`modules/audio_coding/neteq/expand.cc`）编造填充，大致分几步：

1. **分析最近的音频** —— 从[已解码的历史](https://daiw.org/manual/webrtc/neteq-buffer-delay)里，分析出**基音周期**（声音重复的节奏）和频谱特性。
2. **外推续接** —— 按基音周期，把最近的波形**重复 / 延展**下去，生成填充这段丢失的音频。它不是简单复制，而是考虑了周期、能量、随机性，让续出来的自然。
3. **逐渐淡出** —— 关键的一步：如果丢包**持续**（连丢好几个），不能一直重复同一段（会变成刺耳的机械嗡鸣），所以 Expand 会**逐渐降低能量、增加噪声成分、最终淡到静音**。

<Callout type="info">
  **丢包隐藏的智慧，在于“能续多久续多久，续不下去就体面地淡出”。** 丢一两个包（20-40ms），外推得很像，几乎听不出；但丢包若持续几百毫秒，再怎么外推也会失真——这时**硬续会变成难听的电子嗡音**，不如**逐渐淡出到[舒适噪声](https://daiw.org/manual/webrtc/neteq-operations)或静音**，让听者感觉“声音渐弱了”而非“卡了个怪响”。这个“短丢无缝、长丢淡出”的策略，是[实时媒体“优雅降级”](https://daiw.org/manual/webrtc/video-decode-recovery)的又一典范：救得回就无痕救，救不回就选个**最不刺耳**的降级方式。所以你打电话遇到弱网，听到的是“对方声音渐渐模糊 / 消失”而非“刺啦一声”——那就是 Expand 在体面地淡出。
</Callout>

## Merge：编造和真实的缝合

丢包隐藏之后，如果那个包（或后续包）**又来了**，NetEq 要从“编造的填充”切回“真实解码的音频”。但两者**接不上**——编造的波形和真实波形在切换点会**不连续**，硬切会“咔”一声。**Merge** 操作专门解决这个：

<Callout type="info">
  **Merge 把[编造的填充](https://daiw.org/manual/webrtc/neteq-plc)和随后到达的真实音频**平滑地缝合**在一起——通过交叉淡化（crossfade）让两段音频在切换点无缝过渡，而非生硬拼接。** 具体是在重叠的一小段里，让编造的音频**渐弱**、真实的音频**渐强**，叠加过渡。这样即使两段波形本身对不齐，听感上也是“平滑地接上了”，没有咔哒声。**丢包隐藏负责“变出”音频，Merge 负责“接回”真实**——两者配合，才能让一次丢包在听感上真正无缝。这种对“拼接处”的精细处理，正是 NetEq 被誉为精妙的原因：魔鬼在细节，而它把每个细节都磨平了。
</Callout>

## 一次丢包的完整应对

```mermaid
flowchart LR
    OK["正常播放(Normal)"] -->|"下个包没到"| EXP["Expand:分析基音→外推续接"]
    EXP -->|"还没来,继续"| EXP2["Expand:逐渐淡出"]
    EXP -->|"包来了"| MRG["Merge:交叉淡化缝合"]
    MRG --> OK
    EXP2 -->|"一直没来"| CNG["淡入舒适噪声/静音"]
```

**丢一小段** → Expand 无缝续 → 包来了 → Merge 缝回，听者毫无察觉；**丢一长段** → Expand 淡出，听者觉得声音渐弱——两种情形 NetEq 都处理得体面。

## 小结与下一章

**丢包隐藏（Expand）** 凭空编造填充：利用**语音的短时准周期性**（[基音重复](https://daiw.org/manual/webrtc/neteq-plc)），分析最近波形、按[基音周期](https://daiw.org/manual/webrtc/neteq-plc)外推续接；丢包持续则**逐渐淡出**（硬续会成刺耳嗡音）。这是[音频独有](https://daiw.org/manual/webrtc/audio-codecs)、[视频做不到](https://daiw.org/manual/webrtc/video-decode-recovery)的招。包回来后，**Merge** 用**交叉淡化**把编造和真实音频**无缝缝合**（避免咔哒声）。二者配合，让丢包听感无痕。

另一类见功力的操作，是[加速 / 减速的时间伸缩](https://daiw.org/manual/webrtc/neteq-operations)——怎么改变播放速度却不变调？下一章。👉 [NetEq 时间伸缩](https://daiw.org/manual/webrtc/neteq-timestretch)。

## 参考资料

- [1] libwebrtc 源码.`modules/audio_coding/neteq/expand.cc`、`merge.cc`、`g3doc/index.md`。
- [2] libwebrtc 源码.`modules/audio_coding/neteq/background_noise.h`（淡出用的背景噪声）。
- [3] 关联[视频丢包恢复](https://daiw.org/manual/webrtc/video-decode-recovery)——对比音视频的不同应对。
