图 3 - 音频数据流图
PCM 怎么从麦克风走到云端、Opus 怎么从云端走到喇叭?整个音频链路的全貌。
如果上方图无法显示,点这里看 PNG 版本

上行链(用户说话)
麦克风 → input_pcm_queue → AFE → encode_pcm_queue → Opus Encoder → send_opus_queue → Protocol
每一站的作用:
| 站点 | 作用 |
|---|---|
| I²S 麦克风 | 硬件采集 16 kHz 16-bit PCM |
| input_pcm_queue | 麦克风快、AFE 慢,用队列解耦 |
| AFE 前端 | AEC(消回声)+ VAD(检测人声)+ NS(降噪) |
| encode_pcm_queue | AFE 输出再排队,让 Opus 编码独立线程消费 |
| Opus Encoder | 60ms 帧、16 kbps 压缩,输出二进制 Opus |
| send_opus_queue | 网络发包独立,背压时不阻塞前面 |
| Protocol.SendAudio | WebSocket 帧或 UDP+AES 包 |
下行链(云端说话)
Protocol → decode_opus_queue → Opus Decoder → Opus Resampler → output_pcm_queue → 喇叭
| 站点 | 作用 |
|---|---|
| Protocol.OnIncomingAudio | 网络收到云端 Opus 包 |
| decode_opus_queue | 让 OpusCodecTask 独立消费 |
| Opus Decoder | 解码出 24 kHz PCM(TTS 默认采样率) |
| Opus Resampler | 把 24 kHz 适配到喇叭实际采样率 |
| output_pcm_queue | 让 AudioOutputTask 取出来播 |
| I²S 喇叭 | 硬件输出 |
5 个队列的核心价值
为什么这么多队列?两个原因:
- 解耦速度:麦克风采样率固定,但网络速度波动;硬件采集硬性 16 kHz,编码任务可能因为系统忙慢一点——用队列吸收抖动。
- 任务隔离:3 个 FreeRTOS 任务(input / output / codec)通过队列通信,零锁,无死锁风险。
AEC 反馈环
注意图最下面:喇叭播放的 PCM 会反馈给 AFE 作为参考信号。这是 AEC 的核心——通过对比"喇叭在播什么"和"麦克风听到什么",可以减掉对方的回声,避免设备自己说话被自己当成用户输入。
一句话讲清
"麦克风原始声音先过 AFE 清洗(消回声去噪),再压成 Opus 发上云;云端回的 Opus 解出来重采样后塞给喇叭。中间 5 个队列把不同速度的环节解耦,让所有任务都能各跑各的不互相卡。"
关联章节
/04-audio整章