Skip to content

嵌入式音频链路:PCM / I2S / Opus / 多麦

熟悉嵌入式音频与通信链路,具备音频采集、播放、编解码、网络传输、缓冲管理和异常恢复经验。
实现音频采集与播放链路,完成麦克风 PCM 采集、I2S 音频播放、播放缓冲、音量控制和播放异常处理。
接入 Opus 音频编解码,降低音频数据传输带宽。

音频链路能体现实时性、数据流、缓冲、外设和网络传输综合能力。面试官会看你是否理解 PCM、码率、I2S、编解码、播放断续和多麦价值。

短答:

PCM 是最基础的数字音频表示方式,用固定采样率、位深和声道数记录声音波形。

展开回答:

PCM 关键参数:

sample rate: 每秒采样次数
bit depth: 每个样本位数
channels: 声道数

例如 pcm_s16le/16k/mono 表示 16kHz、16bit、小端、单声道。

结合我的项目:

设备侧采集和播放最终都要落到 PCM。Opus 只是网络传输中的压缩格式,播放前仍要解码回 PCM 给 I2S/Player。

继续追问:

PCM 不是压缩格式,所以数据量稳定但带宽压力大。

Q2:16kHz / 16bit / mono 为什么是 32KB/s

Section titled “Q2:16kHz / 16bit / mono 为什么是 32KB/s?”

短答:

码率 = 采样率 × 位深 × 声道数。16000 × 16 × 1 = 256000 bit/s = 32000 byte/s

展开回答:

也就是:

16k samples/s * 2 bytes/sample = 32KB/s
20ms PCM = 16000 * 0.02 * 2 = 640B

结合我的项目:

上行裸 PCM 每秒约 32KB,对公网 WebSocket 和弱网链路压力较大,所以我接入 Opus,把传输码率降到约 20kbps 量级。

继续追问:

音频时长可以通过 PCM 字节数反推:duration_ms = bytes / 32,仅针对 16k/16bit/mono。

短答:

上游准备 PCM 数据,写入播放缓冲,播放器按水位启动,通过 I2S DMA 连续送到音频 Codec 或功放。

展开回答:

典型链路:

Decoded PCM -> PCM ringbuf -> Player -> I2S driver/DMA -> Codec/Amplifier -> Speaker

播放器不能收到一点播一点,需要一定水位抵抗网络抖动和解码抖动。

结合我的项目:

我通过 TTSPlayer、PCM ringbuf、播放水位和 underrun/rebuffer 日志定位下行播放断续问题。

继续追问:

如果 I2S write 返回 short_write,要看 DMA、驱动状态、任务调度和上游数据供给。

Q4:播放为什么会断续?怎么排查?

Section titled “Q4:播放为什么会断续?怎么排查?”

短答:

播放断续通常是上游供给不连续、解码跟不上、PCM buffer 欠载、I2S 写入异常或任务调度被阻塞。

展开回答:

排查顺序:

网络是否连续收到 packet
packet 是否丢失
解码是否失败或耗时过长
PCM ringbuf 是否为空
播放器是否 rebuffer/underrun
I2S write 是否 short_write

结合我的项目:

我不会一上来只调播放器水位,而是看 rx_gapdecode_failPCM depthrebuffershort_write 等分层指标。

继续追问:

如果网络突发发送,队列可能溢出;如果发送太慢,播放器可能欠载。两者要通过缓冲和背压平衡。

Q5:Opus 为什么能降低网络传输压力?

Section titled “Q5:Opus 为什么能降低网络传输压力?”

短答:

Opus 是语音/音频压缩编码,可以把 PCM 从固定高码率压缩到较低码率,减少网络传输字节数。

展开回答:

16k/16bit/mono PCM 是 256kbps,而语音场景 Opus 可以用 16~24kbps 量级。代价是增加编码/解码耗时和 packet 化处理。

结合我的项目:

我把上行音频从 PCM 改为 Opus packet,通过 WebSocket binary frame 发送,降低公网链路传输压力。

继续追问:

Opus packet 必须保留边界,不能像 PCM 一样任意字节流切割,否则解码端无法正确按 packet 解码。

Q6:编解码耗时和播放实时性怎么平衡?

Section titled “Q6:编解码耗时和播放实时性怎么平衡?”

短答:

编解码耗时必须小于音频 frame 时长,并且最好放到独立 task,避免阻塞采集或播放。

展开回答:

如果 20ms 音频帧的解码耗时接近或超过 20ms,就会积压。即使平均耗时低,p95/max 过高也可能造成短时卡顿。

结合我的项目:

我把 Opus 编码和解码拆成独立 worker task,通过 queue/ringbuf 连接主链路,并观察 stack high-water mark 和 encode/decode p95。

继续追问:

不要只看平均值,实时链路更关注尾延迟。

Q7:为什么使用多通道麦克风,而不是单通道?

Section titled “Q7:为什么使用多通道麦克风,而不是单通道?”

短答:

单麦只有一个位置的声音波形,多麦可以获得时间差、相位差和能量差,从而提供声音空间信息,支持波束形成、声源定位、降噪和远场拾音。

展开回答:

多麦价值:

波束形成: 对准说话人方向
声源定位: 判断声音方向
降噪增强: 抑制非目标方向噪声
远场拾音: 用户不用贴近设备
唤醒稳定: 降低误唤醒和漏唤醒

结合我的项目:

桌面语音设备和车载/会议场景都可能有播放回声和环境噪声。多麦配合 AFE 可以提升 WakeNet、VAD 和 ASR 的输入质量。

继续追问:

多麦会增加成本、PCB 空间、I2S/TDM/PDM 通道、DMA 带宽、算法算力和标定复杂度。近场低成本设备可以用单麦。

Q8:WakeNet、AFE、VAD 怎么按工程角度解释?

Section titled “Q8:WakeNet、AFE、VAD 怎么按工程角度解释?”

短答:

WakeNet 做唤醒词检测,AFE 做音频前端增强,VAD 判断是否有人声活动。

展开回答:

工程链路可以理解为:

Mic PCM -> AFE -> WakeNet/VAD -> audio publish -> encoder/network

AFE 可能包含降噪、回声抑制、自动增益、多麦处理等。VAD 用于判断一段语音何时开始和结束。

结合我的项目:

本地仍用 PCM 给 WakeNet/AFE/VAD,上传前再编码为 Opus。这样不破坏本地语音前端,只优化网络传输。

继续追问:

不要把 WakeNet、VAD、ASR 混为一谈。WakeNet 判断唤醒词,VAD 判断语音活动,ASR 才是识别文本。

Q9:如何把麦克风采集、编码、WebSocket、解码、播放串起来?

Section titled “Q9:如何把麦克风采集、编码、WebSocket、解码、播放串起来?”

短答:

用清晰的数据流和缓冲边界串联,每个模块只处理自己的输入输出。

展开回答:

Uplink:
Mic PCM -> AFE/VAD -> Opus Encoder -> WebSocket binary
Downlink:
WebSocket binary -> Opus Decoder -> PCM ringbuf -> TTSPlayer -> I2S

控制消息走 JSON,音频数据走 binary,避免互相干扰。

结合我的项目:

我的项目里 WebSocket 不理解音频业务,播放器不理解网络协议,Session 负责状态。这样问题定位时能明确是哪一层异常。

继续追问:

如果某层处理不过来,要么增加缓冲,要么形成背压,不能无限丢包或无限分配内存。

  • 只说“Opus 压缩率高”,不讲 packet 边界和编解码耗时。
  • 不会计算 PCM 码率。
  • 播放断续只会调水位,不会分层定位。
  • 把 WakeNet、VAD、ASR 混成一个概念。
  • 说多麦“只是音质更好”,不讲空间信息。
我理解嵌入式音频链路的核心是 PCM 数据流、I2S 实时播放、编解码耗时和缓冲连续性。项目里我把本地语音前端继续使用 PCM,网络传输改为 Opus 降低带宽,下行再解码成 PCM 交给播放器。遇到断续问题时,我会按网络、解码、PCM buffer、播放器、I2S 分层定位。