音频管线总览 · 比视频更精细的实时
音频数据量小,但对延迟和瑕疵更敏感——丢一小段、飘一下都刺耳。这一章画出音频的发送/接收管线:采集→APM 处理→编码→传输→NetEq→解码→播放,介绍 AudioSendStream/ReceiveStream、ACM,以及 WebRTC 音频的两颗明珠 NetEq 和 AEC3。
音频管线总览 · 比视频更精细的实时
视频讲完,进入音频。别以为音频比视频简单——它数据量小,但对延迟和瑕疵的敏感度更高:画面卡一下能忍,声音断一下、飘一下、带回声,立刻刺耳。第 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 部分分三段走:
- 编解码:Opus 与编解码器抽象 → ACM。
- 接收侧的 NetEq:总览 → 缓冲与延迟 → 六种操作 → 丢包隐藏 → 时间伸缩。
- 发送侧的 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。