15、HDMI音频路由:HDMI音频输出策略、EDID信息解析与路由决策、HDMI音频格式协商
HDMI音频路由,说白了就是解决一个问题:Android设备怎么知道该通过HDMI输出什么格式的音频?
我刚开始接触这个模块时,觉得不就是把音频信号塞进HDMI线里吗?后来踩了坑才明白,这里面涉及EDID解析、格式协商、策略决策三个环环相扣的步骤。任何一个环节出问题,用户听到的就是无声、杂音,或者干脆没有声音输出。
15.1 HDMI音频输出策略:谁来决定走HDMI?
Android的音频路由策略,核心在AudioPolicyManager里。系统会根据当前连接的设备类型,决定音频走哪条路。
举个例子,你插上HDMI线,系统会检测到DEVICE_OUT_HDMI或DEVICE_OUT_HDMI_ARC。这时候策略引擎会问:当前播放的音频,适合走HDMI吗?
判断依据主要有三点:
- 设备优先级:HDMI通常优先级高于扬声器,但低于有线耳机
- 音频格式兼容性:比如AC3、DTS这些压缩格式,HDMI支持,但扬声器不支持
- 用户设置:有些设备允许强制HDMI输出,或者仅输出多声道
核心逻辑:AudioPolicyManager会遍历所有可用输出设备,按优先级排序,然后选择第一个支持当前音频格式的设备。如果HDMI排在前面,且支持当前格式,音频就走HDMI。
我在项目中遇到过一个问题:某款电视盒子的HDMI输出总是被扬声器抢走优先级。查了半天,发现是audio_policy_configuration.xml里把扬声器的优先级写高了。改过来就好了。嗯,这种配置问题其实挺常见的。
15.2 EDID信息解析:HDMI的“自我介绍”
EDID(Extended Display Identification Data)是显示设备发给源端的一串数据。你可以把它理解成显示器的“身份证”。里面写了:我能支持什么分辨率、什么刷新率、什么音频格式。
Android系统在检测到HDMI连接后,会通过DisplayManager或AudioManager读取EDID。音频相关的信息藏在EDID的CEA-861扩展块里。
EDID音频信息的结构大致如下:
EDID CEA-861 Extension Block
├── Short Audio Descriptor (SAD) #1
│ ├── Audio Format Code (1: LPCM, 2: AC3, 3: DTS, ...)
│ ├── Max Channels (2, 6, 8)
│ ├── Sample Rates (32kHz, 44.1kHz, 48kHz, ...)
│ └── Bit Depth (16bit, 20bit, 24bit)
├── SAD #2
│ └── ...
└── Speaker Allocation Data (可选)
每个SAD描述了一种音频格式。比如:
- LPCM:2声道,48kHz,16bit
- AC3:6声道,48kHz,640kbps
- DTS:6声道,48kHz,1.5Mbps
Android的AudioService会解析这些SAD,然后构建一个音频格式能力表。这个表决定了后续的格式协商。
避坑指南:我曾经遇到一台显示器,EDID里写了支持AC3,但实际解码器只支持LPCM。结果系统协商出AC3格式,播放时全是噪音。后来我们在驱动层加了一个EDID校验逻辑,把不靠谱的格式过滤掉。所以,不要完全相信EDID,它有时候会撒谎。
15.3 路由决策:EDID信息如何影响音频路由?
解析完EDID后,系统会做两件事:
- 更新可用设备列表:把HDMI设备标记为“支持多声道”或“仅支持立体声”
- 触发路由重评估:如果当前音频格式不被HDMI支持,系统会尝试降级或切换设备
举个例子:
- 你播放一个5.1声道的电影,音频格式是AC3
- 系统检查HDMI的EDID,发现它支持AC3,6声道
- 路由决策:走HDMI,输出AC3
- 如果EDID只支持LPCM 2声道,系统会尝试:
→ 把AC3解码成LPCM 2声道?
→ 或者直接切换到扬声器输出?
这个决策逻辑在AudioPolicyManager::getOutputForDevice()里实现。我建议你去看一下源码,里面有一个checkOutputForStrategy()函数,专门处理这种格式兼容性问题。
注意:有些HDMI设备(比如Soundbar)会报告支持所有格式,但实际上只解码部分格式。这种情况下,Android的格式协商会失败,导致无声。解决办法是在AudioPolicyManager里加一个格式回退策略:如果协商失败,自动降级到LPCM。
15.4 HDMI音频格式协商:握手与妥协
格式协商,说白了就是源端和显示端之间的“握手”。源端说:“我支持这些格式”,显示端说:“我只能解这些格式”,然后双方找一个共同点。
协商流程如下:
- 源端发送音频格式列表:比如LPCM、AC3、DTS、EAC3
- 显示端回复EDID:告诉源端我能解哪些格式
- 源端选择最佳格式:取交集,按优先级排序
- 开始传输:如果协商失败,回退到LPCM 2声道
Android里负责协商的是AudioFlinger和HAL层。具体来说:
AudioFlinger调用HAL::open_output_stream(),传入格式参数- HAL层与HDMI驱动交互,读取EDID,返回支持的格式列表
AudioFlinger选择最佳格式,创建音频流
下面是一个简化的协商流程图:
你想想看,如果EDID里没有LPCM,而源端只支持LPCM,那协商就失败了。这时候系统会怎么做?
嗯,大多数Android设备会直接静音,或者切换到其他输出设备。我见过一个奇葩案例:某款投影仪的EDID里只写了AC3,没有LPCM。结果手机播放普通音乐(LPCM)时,投影仪完全没声音。最后我们在HAL层加了一个虚拟LPCM支持,强制把LPCM伪装成AC3输出,才解决了问题。当然,这不是标准做法,但有时候你得灵活处理。
15.5 实战中的常见问题与排查思路
我在调试HDMI音频时,总结了一套排查流程,分享给你:
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| HDMI无声 | EDID解析失败,或格式协商失败 | 用dumpsys audio查看设备列表和格式信息 |
| 只有立体声,没有多声道 | EDID里没有多声道SAD,或系统策略限制 | 检查EDID原始数据,确认SAD内容 |
| 播放AC3时噪音 | 显示端不支持AC3,但EDID谎报支持 | 用cat /sys/class/drm/card0-HDMI-A-1/edid查看原始EDID |
| HDMI和扬声器同时出声 | 路由策略配置错误,或设备未正确断开 | 检查audio_policy_configuration.xml中的设备映射 |
调试小技巧:我习惯在AudioPolicyManager里加日志,打印每次路由决策的输入和输出。比如:
// 伪代码
log("Device: %s, Format: %s, Channels: %d, Samplerate: %d",
deviceName, format, channels, samplerate);
log("Decision: route to %s with format %s",
selectedDevice, selectedFormat);
这样一看日志,就知道问题出在EDID解析还是格式协商了。
15.6 总结
HDMI音频路由,本质上是一个信息收集 → 策略匹配 → 格式协商的过程。EDID是信息源,AudioPolicyManager是决策者,HAL层是执行者。任何一个环节出问题,都会导致音频输出异常。
我个人觉得,理解这个流程的关键在于:不要假设EDID是可靠的。你永远不知道显示设备会报出什么奇怪的数据。所以,在代码里加校验、加回退、加日志,是保证稳定性的不二法门。
好了,关于HDMI音频路由就聊到这里。如果你在实际项目中遇到相关问题,欢迎随时交流。