这篇写什么?
电视切到 HDMI-IN(外接机顶盒 / 游戏机等)时,声音往往不是「普通 App 播一段 AudioTrack」,而是 HAL 里建一条 Audio Patch:一边从 HDMI 输入读 PCM,一边写到本机输出。链路长、还要和 Dolby MS12、原有 mixer 线程打交道,log 一长串容易晕。
本文按调用顺序把这条路径捋直,方便对照 log 与源码。
一句话:
adev_create_audio_patch
→ create_patch / create_patch_l
→(如需)清理 MS12 连续模式、usecase 校验
→ 启动 input 线程 + output 线程
→ ALSA 读入 → aml_ringbuffer → out_write_new → mixer / MS12 → 喇叭

DTV 一类实现结构类似(输入/输出线程 + 中间缓冲),可对照:

一、入口:adev_create_audio_patch
作用:创建音频补丁,把「音频源」接到「输出设备」。
大致步骤:
- 初始化、检查设备类型与参数是否合法
- 把 Android 音频设备类型转成 HAL 内部类型
- 调用
create_patch真正建 patch,并配好路由 - 相关格式按 PCM 等约定落下
可以把它理解成:「告诉 HAL——从现在起,HDMI-IN 的数据要送到本机出声口。」
二、create_patch → create_patch_l
| 函数 | 做什么 |
|---|---|
create_patch |
加互斥锁,保护共享状态,再调 create_patch_l |
create_patch_l |
分配并初始化 aml_audio_patch,按设备/格式填参数 |
参数配好后,拉起两条线程(任一步失败要释放资源):
audio_patch_input_threadloop:输入audio_patch_output_threadloop:输出
中间用 aml_ringbuffer 解耦:输入只管往里写,输出只管往外读。
三、切 HDMI-IN 前:MS12 连续模式要先收一收
若当前在用 Dolby MS12,且处于 continuous(连续)音频模式,建 patch 前通常要:
get_dolby_ms12_cleanup:把 MS12 设成 非 continuous,并清理(停相关线程、重置格式、释放资源,continuous_audio_mode = 0)- 更新设备状态,记录退出 MS12 的时间点
usecase_change_validate_l:校验/更新输出流用例;常会把当前 mixer 线程相关路径置到合适状态(如 standby),避免和 HDMI-IN 新路径抢写
流程图里常强调:clean MS12 要发生在 output 侧某些写入之前,顺序错了容易卡音或状态错乱。
同时系统里往往还有一条 一直在跑的 mixer 线程(按键音等)。HDMI-IN patch 起来后,可能出现 两条输出路径并存,后面会靠 main / aux mixer 写入函数分工。
四、输入线程:audio_patch_input_threadloop
职责:从 HDMI-IN(ALSA)把数据读进环形缓冲。
adev_open_input_stream
→ while (!input_thread_exit)
start_input_stream(按需)
aml_alsa_input_read
· 信号不稳 → ring_buffer_clear,等待
· 有数据 → ring_buffer_write → aml_ringbuffer
→ adev_close_input_stream
要点:
- 用
input_thread_exit控制退出 - 不稳定时先清缓冲,避免脏数据把输出线程拖垮
- 输入线程 不直接碰喇叭,只负责「采进来」
五、输出线程:audio_patch_output_threadloop
职责:从环形缓冲取数,写到输出通路。
5.1 启动
- 若有活跃 direct 流 →
do_output_standby_l;若仍挂着 MS12 → 再清理 adev_open_output_stream_new打开输出流,分配 buffer- 设线程名、优先级,必要时绑核
5.2 循环主体
while (!output_thread_exit)
根据格式 / 游戏低延迟模式决定 write_bytes
若 ringbuffer 数据不够 → 按格式等待
若够 → ring_buffer_read
→(可选)格式转换等
→ out_write_new(...)
→ do_output_standby_l
→ adev_close_output_stream_new
5.3 out_write_new 里常看的几步
| 调用 | 作用 |
|---|---|
get_sink_format / MS12 相关查询 |
看当前 sink、MS12 状态 |
usecase_change_validate_l |
用例是否变化,要不要换写入函数 |
write_func_p(...) |
真正写入(指向不同 mixer/process 函数) |
update_audio_format |
更新内部格式(常用于信息条显示等) |
六、usecase_change_validate_l:谁来写硬件?
用例(usecase)一变,写入函数可能切换,例如:
| 写入函数 | 典型场景 |
|---|---|
mixer_main_buffer_write |
主缓冲路径 |
mixer_aux_buffer_write |
辅助缓冲(多路并存时常见) |
process_buffer_write |
其它处理路径 |
待机时会重置用例掩码,并给 MS12 发相应消息。
没有用例变化时,则沿用已选好的 write 函数,保证行为稳定。
HDMI-IN 起来后若与原 mixer 并存,log 里常能看到 双 output 线程 / aux 写入 相关痕迹——对照本节看会清晰很多。
七、mixer_main_buffer_write 与 config_output
主缓冲写入时大致会:
- 有数据 → 写硬件、恢复定时器等;无数据 → 按空转策略处理
- 若是 HDMI / SPDIF 且 输入格式变化(例如编码类型从一种变成另一种)→ 重新配输出,必要时 重建 MS12(create ms12)
- 特殊格式(如 DTS CD)走专用分支
- 按是否 MS12 库选择渲染/同步路径
config_output() 是重配核心,尤其判断是否 eDolbyMS12Lib:
- 不支持当前压缩格式 → 关旧解码器 → 按新配置再 init
- 设置 continuous / OTT 输入等 MS12 参数
- 非 MS12 也有类似「重置 + 初始化」,只是没有 MS12 专有项
- 需要时做 AV 同步相关处理
因此:切 HDMI-IN 后若中途片源格式变了,log 里出现 recreate MS12,往往是预期行为,不是偶然报错。
八、和 DTV Patch 对照(同一套骨架)
DTV 流程(create_dtv_patch_l 等)同样是:
主控线程
├─ 输入线程:取 ES → 打包 → 写入 Audio Data 缓冲
└─ 输出线程:取出 → 解码 → out_write_new → HAL out
和 HDMI-IN 的「input loop / output loop / 中间 buffer」是同一类设计;差别主要在 数据从哪来(HDMI ALSA PCM vs 数字电视 ES 包)以及解码落在哪一层。
九、对照 log 时怎么抓重点
建议按时间线搜这些关键字(名称因分支略有差异):
create_audio_patch/create_patchms12+cleanup/continuoususecase_change_validateinput_thread/output_thread/ring_bufferout_write_new/mixer_main/mixer_aux/config_output/create.*ms12
异常时可快速判断卡在哪一段:
| 现象 | 优先怀疑 |
|---|---|
| 有 patch 无声音 | 输出线程没起来、ringbuffer 一直空、write 函数选错 |
| 一切换就卡死/爆音 | MS12 continuous 未清干净,或与 mixer 双写冲突 |
| 中途格式变后无声 | config_output / 重建 MS12 失败 |
| 只有按键音没有 HDMI | HDMI 路径走了 standby,或 aux/main 混错 |
总结
TV 切到 HDMI-IN 的音频核心,不是单函数「打开设备」,而是:
- 建 patch(源 → 宿)
- 必要时拆掉 MS12 连续模式,usecase 让路
- 双线程 + 环形缓冲 搬数
- out_write_new → usecase → mixer/MS12 → config_output 真正出声
把这条链记熟,再去啃上千行 HDMI-IN log,会顺很多。
相关阅读
评论
还没有评论,来做第一个吧
有想法?直接在这说,不用跑留言板