这篇写什么?
Soundbar / 带麦设备上的「OK Google」一类远场唤醒,表面上是 Assistant 在听,底下往往是:
麦阵 + 回采 Ref
→ HAL 里做 AEC/NS(可选)
→ 唤醒引擎出事件
→ 再拉 AudioRecord 给云端 ASR
本文按 代码调用链 把 audio_preprocess_* 从开机打开到读数、再到关闭捋清楚,并对比 谷歌原生架构 与 HAL 内优化架构。
一句话:
adev_open → preprocess_open(建 speech_info / 麦配置)
Assistant AudioRecord → open_input → preprocess_start(选库、起 AEC、起麦)
回调写队列 → preprocess_read 读出
standby/close → stop / close / release

相关:Google 远场与 HAL AEC、Mic + Ref、多路唤醒。
一、架构:原生 vs 优化后
1)谷歌原生(示意)
常见路径更「分层清晰」:
- 热词 / DSP 或专用通路做唤醒
- Assistant 再通过 Framework 录音做识别
- HAL 主要负责采数,重算法不一定落在 primary HAL 里
2)优化后:在 Audio HAL 里做 AEC + 唤醒

| 层 | 优化架构里多了什么 |
|---|---|
| Client | YouTube 等走 AudioTrack;Assistant 走 AudioRecord + 云端 ASR |
| Audio Server | Flinger / Policy 照常 Binder 上下 |
| Audio HAL | AEC+NS → 唤醒引擎;命中后抛 Wake-up event 通知 Assistant |
| Kernel / ALSA | PDM 麦、TDM Out / Loopback Ref、EQ/DRC 等 |
对比一句话:
原生:唤醒多在专用/系统通路,HAL 偏「采数」
优化:Mic+Ref 在 HAL 里先 AEC/NS 再唤醒,事件回调上层,再 AudioRecord 拉流做 ASR
示意对比:

这样改的动机通常是:产品麦阵/功放拓扑固定,把 回声消除与唤醒 和 产品类型、通道数 绑在同一套 HAL 配置里,便于量产换型。
二、总流程分五色(对应实现阶段)
| 阶段 | 代表 API | 做什么 |
|---|---|---|
| 系统初始化 | audio_preprocess_open |
建 speech_info、麦基础配置 |
| 配置请求 | audio_init_req_config |
按每路录音请求建路由 / 队列 |
| 核心处理 | audio_preprocess_start |
选产品库、起 AEC、起麦设备 |
| 音频处理 | audio_preprocess_read + 回调 |
队列读写、AEC 后数据给上层 |
| 清理释放 | stop / close / release_* |
停麦、拆插件、释放结构 |
下面按调用顺序展开。
三、系统初始化(开机 / adev_open)
系统启动
→ adev_open
→ audio_preprocess_open
→ init_speech_info
→ audio_init_mic_config
| 函数 | 作用 |
|---|---|
audio_preprocess_open |
preprocess 主入口,创建/挂接 speech_info |
init_speech_info |
初始化基础数据结构 |
audio_init_mic_config |
配置麦克风基础参数(通道、周期等,与 ALSA period 同类概念) |
此时往往 还没开始真正录音,只是 HAL 打开时把语音预处理模块备好。
四、配置请求(打开输入流)
当 Framework 打开录音输入时:
AudioFlinger::openInputStream / InHWHal::openInputStream
→ adev_open_input_stream
→ audio_init_req_config // 为该路请求建配置与 byte queue
→ in_get_buffer_size / audio_mic_get_period
Assistant(如 Katniss)侧典型触发:
AudioRecord::set
→ AudioFlinger::openInput_l
→ adev_open_input_stream
| 函数 | 作用 |
|---|---|
audio_init_req_config |
为 每一路 AudioRecord/输入流准备录音路由与缓冲队列 |
set_spk_product_type(若有) |
设置产品类型 / 配置文件路径,供后面选库 |
五、核心处理:audio_preprocess_start(最关键)
真正「选哪套算法库、起麦、起 AEC」多在 start:
audio_preprocess_start
→ initialize_aec_and_mic_devices
├─ 产品/库映射(见下)
├─ init_aec_wakeup_config → createAECPlugin → 注册唤醒事件
└─ audio_start_mic_device → 注册回调 audioMicDevCallback
5.1 产品类型 → 动态库名
parse_spk_type_to_library
→ getLibraryMappingFromConfig / getProductType(读系统属性)
→ 决定 searchKey
→ 在 config_lib_map 里查找
→ 生成库名并加载
searchKey 怎么选:
| 条件 | searchKey |
|---|---|
configName 为空 |
productType(如属性里的 "b") |
configName 非空 |
配置文件名(如 A600BAR_EX51.cfg) |
库名常见模式(示意):
libElevocVoice_{productType}_{mic_ch}mic_{spk_type}_{ref_ch}ref
含义拆开:
| 字段 | 含义 |
|---|---|
productType |
产品线 / 机型档 |
mic_ch |
麦克风通道数 |
spk_type |
喇叭/箱体类型(影响 AEC 模型) |
ref_ch |
回采 Ref 通道数 |
同时会落下 mic_ch / ref_ch / spk_type / spk_ch 等运行参数。
换机型却唤醒变差,优先查:属性 productType、cfg 名、通道数是否和库后缀一致。
5.2 AEC 与唤醒
init_aec_wakeup_config
→ createAECPlugin
→ 注册 wakeup 回调 / 事件
命中热词后,HAL 侧抛 Wake-up event,上层 Assistant 进入会话,再持续 AudioRecord 读识别流(可上云 ASR)。
5.3 启动麦设备与回调
audio_start_mic_device
→ 注册 handler
→ audioMicDevCallback
→ ev_byte_queue_write // 处理后的数据写入队列
采数与算法在回调线程跑;读路径与写路径用 byte queue 解耦,避免阻塞 ALSA 周期。
六、音频处理:audio_preprocess_read
上层(经 Flinger)读输入流时:
audio_preprocess_read
→(必要时再确认)initialize_aec_and_mic_devices
→ ev_byte_queue_read
数据路径示意:
ALSA Mic(+Ref)
→ 回调里 AEC/NS / 唤醒分析
→ queue_write
→ preprocess_read → queue_read → 交给 AudioRecord
唤醒事件是 控制面;preprocess_read 是 数据面。两者配合:先事件叫醒,再拉流识别。
七、清理释放
| 入口 | 调用 | 释放什么 |
|---|---|---|
in_standby 等 |
audio_preprocess_stop |
停处理、释放麦设备(release_mic_devices) |
adev_close_input_stream |
audio_close_req_config |
该路请求的路由/队列 |
adev_close |
audio_preprocess_close |
release_mic_devices + release_aec_info,整模块收干净 |
原则:开流配对关流,open 配对 close,避免 AEC 插件或麦设备泄漏导致第二次唤醒失败。
八、和 Framework 的衔接(便于联调)
Google Assistant / Katniss
→ AudioRecord::set / start
→ AudioFlinger openInput
→ HAL adev_open_input_stream
→ audio_init_req_config
→ audio_preprocess_start(选库、起麦、AEC)
→ 回调写队列;read 出数
→ 唤醒事件回上层 → 云端 ASR(可选)
播放侧(YouTube 等)仍走 AudioTrack → Flinger → 功放;AEC 用 TDM Loopback / Ref 对齐喇叭声,才能在大声播放时还听得清唤醒词——见 Mic + Ref。
九、排查清单
-
productType/ cfg 名是否指向正确库后缀 -
mic_ch/ref_ch与硬件、ALSA 配置一致 - start 是否走到
audio_start_mic_device,回调是否在写队列 - read 是否在读同一队列(空队列会像「录到全 0」)
- 唤醒事件是否注册、上层是否收到
- stop/close 是否成对,避免二次 open 失败
十、小结
| 要点 | 内容 |
|---|---|
| 架构差异 | 优化方案把 AEC/NS + 唤醒 放进 HAL,用事件叫醒 Assistant |
| 生命周期 | open → init_req → start(选库+起麦) → read/回调 → stop/close |
| 选库 | configName 空用 productType,否则用 cfg 名;库名编码通道与箱体类型 |
| 数据 | 回调写队列,read 读队列,和唤醒事件分工明确 |
把 audio_preprocess_start 里的 选库菱形 看懂,换机型、换麦数、换 Ref 时就不容易「库加载成功但唤醒奇差」。
相关阅读
评论
还没有评论,来做第一个吧
赞同、吐槽、提问都行,评论区开放