这篇写什么?
现象很吓人,也很容易复现:
- 电视先调到 较低音量
- 用 HDMI ARC 接上 Soundbar,播 Spotify 等音乐
- 拔掉 Soundbar 的 HDMI
- 声音切回电视喇叭时,突然变成 最大音量
排查顺序是:先排除「双屏 / 面板 Gain」误判 → 用 log 证明下层只是在执行 index=100 → 再追到 Java 层谁把 100 塞进去的。
一句话:
不是 HAL 自己拉满,也不是面板 Gain=1
→ AudioPolicy 只是收到 volumeIndex=100
→ 根因:AudioService.isFullVolumeDevice
误把 mHdmiSystemAudioSupported OR 进来
→ 拔 ARC 后喇叭被当成 full-volume 设备

相关背景:Android 音量设置流程。
一、现象与复现
| 步骤 | 操作 |
|---|---|
| 1 | 系统音量调低 |
| 2 | 连接 Soundbar(HDMI ARC),播放音乐 |
| 3 | 拔掉 Soundbar HDMI |
| 4 | 音频路由回本机喇叭 → 音量跳到最大 |
关键点:没有去操作什么「大屏小屏切换播放」,只是断开 ARC,上层就会把音量写成最大。
二、先排除:不是「面板 Gain=1 = 最大音量」
一开始容易怀疑:客户大屏 / 小屏面板音量控制是不是把音量写成满了。
日志里能看到类似:
hal_param_app_panel_id_and_volume=0,1.0000
hal_param_app_panel_id_and_volume=1,1.0000

和客户对齐后确认:这里的 1.0000 是增益倍数(×1),意思是「按系统当前音量原样输出」,不是把音量 index 设成 100%。
结论:
- 与大屏 / 小屏面板音量无关
- 断开 Soundbar 时,是 上层主动把系统音量 index 推到最大
三、日志:谁把 volumeIndex 写成了 100?
继续抓 log,在 Audio Policy 侧能看到:
checkAndSetVolume ... volumeIndex: 100
volumeMinIndex: 0 volumeMaxIndex: 100
curDevice: 0x2 // 常见对应 SPEAKER
随后 HAL 走 adev_set_audio_port_config,各 outport gain 多为 1.0,活跃通路在切换瞬间仍可能和 HDMI_ARC / SPEAKER 纠缠在一起——但 「100」这个数字已经在 Policy 层定了。

查看 AudioPolicyManager.cpp 里相关逻辑:厂商侧 未改,是原生路径——根据上层下发的 index,换算 dB,再 updateGain / setVolumeCurveIndex。

也就是说:Native Policy 是「忠实执行者」,真正要查的是谁调用了 setStreamVolumeIndex / setVolumeCurveIndex,并且传入了 100。

setVolumeIndexForAttributes 里最终落到:
status = setVolumeCurveIndex(index, device, curves);

到这里可以下半个结论:100 来自更上层的 AudioService,不是 APM 自己发明的。
四、复现时建议怎么抓证据
开机后立刻开始存 log,复现后再 dump 音频状态,方便和「拔 ARC 瞬间」对齐:
# 开机后持续抓 log
adb logcat > /data/logcat_arc_volume.log
# 复现「拔 ARC → 音量飙最大」之后
adb shell "dumpsys media.audio_flinger > /data/dump_audio.txt"
adb shell "dumpsys media.audio_policy >> /data/dump_audio.txt"
adb shell "dumpsys audio >> /data/dump_audio.txt"
adb pull /data/logcat_arc_volume.log .
adb pull /data/dump_audio.txt .
在 log 里重点搜:
setVolumeCurveIndex+index 100applyAllVolumes/isFullVolumeDevice- 拔 ARC 前后的设备切换、
MUTE_CHANGED一类事件
五、根因:isFullVolumeDevice 误判
最终在 AudioService 日志里钉死:
AudioService: applyAllVolumes isFullVolumeDevice device:2, index:100
APM_AudioPolicyManager: setVolumeCurveIndex device 00000002, index 100

对应代码类似(示意):
private boolean isFullVolumeDevice(int deviceType) {
// ...
return mFullVolumeDevices.contains(deviceType)
|| mHdmiSystemAudioSupported; // ← 问题在这里
}

为什么拔 ARC 会中招?
| 概念 | 含义 |
|---|---|
| Full volume device | 被策略当成「应由外部设备控音量 / 本机应按满刻度处理」一类设备;applyAllVolumes 时可能直接把 index 写成 max(如 100) |
mFullVolumeDevices |
真正登记过的 full-volume 设备集合(按 deviceType 判断) |
mHdmiSystemAudioSupported |
HDMI CEC / System Audio 能力相关标志,表示「系统音频经 HDMI 通路协同」一类状态 |
错误写法把 全局标志 mHdmiSystemAudioSupported 用 || 并进「某个 deviceType 是不是 full-volume」:
- 只要该标志仍为
true - 任意 传入的
deviceType(包括本机SPEAKER)都可能被当成 full-volume - 拔掉 ARC、路由回到喇叭时,
applyAllVolumes就会给喇叭写下 index=100
这和「音量键怎么发」不是一回事:isFullVolumeDevice 并不是直接绑在音量键发送上,但会在 应用全部音量 / 设备切换刷新 时把设备按满音量处理。
修复
去掉多余条件即可:
private boolean isFullVolumeDevice(int deviceType) {
// ...
return mFullVolumeDevices.contains(deviceType);
}
只让 真正登记在 mFullVolumeDevices 里的设备 走 full-volume 逻辑;HDMI System Audio 支持与否,不应「污染」对本机喇叭的判断。
六、排查清单(同类问题可复用)
- 复现:低音量 → ARC 外放 → 拔线 → 是否瞬间最大
- 排除面板 / 应用 Gain:确认是「×1」还是「写成 max」
- log 搜
volumeIndex: 100/setVolumeCurveIndex ... index 100 - 确认 APM 是否原样下发(多数情况只是执行者)
- 搜
isFullVolumeDevice/applyAllVolumes - 检查是否把 全局 HDMI 标志 错误 OR 进了 按设备类型 的判断
七、小结
| 层级 | 角色 | 本次结论 |
|---|---|---|
| 面板 / 客户 APK Gain | 易被误判 | 1.0 = 乘 1,不是最大音量 |
| AudioPolicy / HAL | 执行 index→增益 | 原样执行 100,非根因 |
| AudioService | 决定 index | isFullVolumeDevice 误 OR mHdmiSystemAudioSupported |
拔 ARC 后音量飙最大,本质是:设备切换刷新音量时,把本机喇叭错当成 full-volume 设备,写下了最大 index。
修法很克制:让 isFullVolumeDevice 只看设备集合,不要用 HDMI 全局标志「一票通过」。
相关阅读
评论
还没有评论,来做第一个吧
读完了不评论,作者会以为你在沉思