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

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

作者 David更新于 第 69 篇(共 112 篇)

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

六种操作里最神奇的,是 Expand(丢包隐藏,PLC):包丢了、声卡却每 10ms 准时要音频,NetEq 凭空编造一段填上,还要以假乱真。这是音频独有的招数(视频编不出、只能花屏)。这一章看它怎么“变”出音频。

为什么语音能编

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

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

这也是音频和视频的根本差异:语音波形短时可预测(能续),而视频丢帧没有这种简单的可外推性(续出来就是花屏)。所以音频能玩丢包隐藏,视频只能等重传 / 关键帧。

Expand 怎么编

Expand(modules/audio_coding/neteq/expand.cc)编造填充,大致分几步:

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

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

Merge:编造和真实的缝合

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

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

一次丢包的完整应对

图表加载中…

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

小结与下一章

丢包隐藏(Expand) 凭空编造填充:利用语音的短时准周期性(基音重复),分析最近波形、按基音周期外推续接;丢包持续则逐渐淡出(硬续会成刺耳嗡音)。这是音频独有、视频做不到的招。包回来后,Merge 用交叉淡化把编造和真实音频无缝缝合(避免咔哒声)。二者配合,让丢包听感无痕。

另一类见功力的操作,是加速 / 减速的时间伸缩——怎么改变播放速度却不变调?下一章。👉 NetEq 时间伸缩。

参考资料

  • [1] libwebrtc 源码.modules/audio_coding/neteq/expand.cc、merge.cc、g3doc/index.md。
  • [2] libwebrtc 源码.modules/audio_coding/neteq/background_noise.h(淡出用的背景噪声)。
  • [3] 关联视频丢包恢复——对比音视频的不同应对。

本页目录