音频管线总览 · 比视频更精细的实时

音频数据量小,但对延迟和瑕疵更敏感——丢一小段、飘一下都刺耳。这一章画出音频的发送/接收管线:采集→APM 处理→编码→传输→NetEq→解码→播放,介绍 AudioSendStream/ReceiveStream、ACM,以及 WebRTC 音频的两颗明珠 NetEq 和 AEC3。

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

音频管线总览 · 比视频更精细的实时

视频讲完,进入音频。别以为音频比视频简单——它数据量小,但对延迟和瑕疵的敏感度更高:画面卡一下能忍,声音断一下、飘一下、带回声,立刻刺耳。第 7 部分(modules/audio_coding/、modules/audio_processing/)拆解音频管线,它藏着 WebRTC 最精妙的两个模块:NetEq 和 AEC3。

两条管线

音频也有发送和接收两条管线:

图表加载中…
  • 发送:麦克风采集 → APM(音频处理:回声消除、降噪、增益——让你的声音干净)→ 编码(主用 Opus)→ 打包传输。
  • 接收:收包 → NetEq(自适应抖动缓冲 + 丢包隐藏)→ 解码 → 播放。

核心对象:AudioSendStream / AudioReceiveStream

管线的编排者,是 audio/ 下的两个 stream 对象:

  • AudioSendStream(audio/audio_send_stream.cc)—— 发送侧编排,持有 ChannelSend。
  • AudioReceiveStream —— 接收侧编排,持有 ChannelReceive。

它们和视频的 VideoStreamEncoder/VideoReceiveStream2 平行。中间的音频编解码由 ACM(Audio Coding Module,modules/audio_coding/) 统管——它像视频的编解码器抽象层,把 Opus 等编解码器接进来。

音频为什么“另有一套”

音频和视频面对同一类问题(丢包、抖动),但解法迥异,值得先点明:

音频和视频对“丢包 / 抖动”的应对完全不同,因为两者的“瑕疵”性质不同。

  • 视频:丢包 → 花屏 / 冻结,靠重传 / FEC / 关键帧恢复。视频帧大、有依赖链。
  • 音频:丢包 → 断音 / 卡顿。音频包小、密(每 20ms 一个)、帧间依赖弱。这让音频有独特的招数——丢包隐藏(PLC):丢了一小段,可以凭前后的声音“编造”出一段以假乱真的填充(语音有很强的短时可预测性),听感上几乎无缝。视频没法这么“编”(编出来就是花屏)。

这就是为什么音频接收侧有一个专门的、极其精妙的模块——NetEq,它把“抖动缓冲 + 丢包隐藏 + 时间伸缩”融为一体,是视频抖动缓冲的音频对应物,但精细得多。而发送侧,音频有一个视频完全没有的难题——回声(扬声器的声音被麦克风录回去),由 AEC3 消除。NetEq 和 AEC3,是本部分的两座高峰。

本部分路线

第 7 部分分三段走:

  1. 编解码:Opus 与编解码器抽象 → ACM。
  2. 接收侧的 NetEq:总览 → 缓冲与延迟 → 六种操作 → 丢包隐藏 → 时间伸缩。
  3. 发送侧的 APM:总览 → 回声问题 → AEC3 内核 → 增益 AGC → 降噪与 VAD → 设备与混音。

小结与下一章

音频有发送(采集→APM 处理→编码)和接收(NetEq→解码→播放)两条管线,由 AudioSendStream/AudioReceiveStream 编排,ACM 统管编解码。音频对延迟和瑕疵更敏感,但因音频包小密、帧间依赖弱,有独特招数——丢包隐藏(凭上下文编造填充)。两座高峰:接收侧的 NetEq(抖动缓冲 + PLC + 时间伸缩)、发送侧的 AEC3(回声消除)。

先从“声音怎么被压缩”讲起——Opus 与音频编解码器抽象。👉 音频编解码器。

参考资料

  • [1] libwebrtc 源码.audio/audio_send_stream.cc、audio_receive_stream.cc、channel_send.cc、channel_receive.cc。
  • [2] libwebrtc 源码.modules/audio_coding/(ACM/NetEq)、modules/audio_processing/(APM)。
  • [3] libwebrtc 源码.modules/audio_coding/neteq/g3doc/index.md。

本页目录