KunSpace
返回博客

Android AudioService 音量调节:按键、静音、蓝牙、CEC 与全音量设备

2026年8月1日
1 次阅读

读完了?别空手走啊 😏

这行字出现时,你的大拇指应该已经 itch 了

写评论

读完了不评论,作者会以为你在沉思

这篇写什么?

上一篇 音量设置流程 讲的是 index → Policy 算 dB → Flinger 软音量 → HAL
本文补全 按键按下之后、进 Native 之前:应用 / 遥控器如何进到 AudioService,以及途中的静音、蓝牙绝对音量、CEC、透传、无障碍、全音量设备等分支。

音量「听感不对」类问题见 音量问题排查

一句话:

音量键 → MediaSession → AudioManager
  → AudioService.adjustStreamVolume / setStreamVolume
       → 静音 or adjustIndex
       → MSG_SET_DEVICE_VOLUME → AudioSystem.setStreamVolumeIndexAS
       →(旁路)AVRCP / HearingAid / HDMI-CEC / passthrough 提示
       → sendVolumeUpdate → 音量条 UI

〇、HAL 侧落点(盒子 vs Soundbar)

Framework 最终会把增益策略落到 HAL,产品形态不同,入口也不同:

产品 常见 HAL 接口
盒子(STB) out_set_volume
Soundbar adev_set_audio_port_config(port gain)

本文主线停在 AudioService;HAL 之后如何乘增益,仍看 音量设置流程 · HAL 章节


一、应用层入口

应用通过 AudioManager 调节:

// 相对调节:加 / 减 / 静音方向
AudioManager.adjustStreamVolume(int streamType, int direction, int flags);

// 绝对设置:直接指定 index
AudioManager.setStreamVolume(int streamType, int index, int flags);

adjustStreamVolume 会经 Binder 调到 IAudioService

public void adjustStreamVolume(int streamType, int direction, int flags) {
    final IAudioService service = getService();
    service.adjustStreamVolume(streamType, direction, flags,
            getContext().getOpPackageName());
}
参数 含义
streamType 音频流类型(MUSIC / RING / ACCESSIBILITY…)
direction ADJUST_RAISE / LOWER / MUTE / UNMUTE / TOGGLE_MUTE / SAME
flags 是否显示 UI、是否震动等标志位

真正干活在 AudioService.adjustStreamVolume(...)


二、音量键:从 MediaSession 进到 AudioService

遥控器 / 系统音量键往往不直接调 App 的 AudioManager,而是走 MediaSession 再落到 AudioService:

音量键到 AudioService 调用链

简化链路:

MediaSessionManager
  dispatchVolumeKeyEvent… / …ToSessionAsSystemService
    → MediaSessionService
         handleVolumeKeyEventLocked → dispatchAdjustVolumeLocked
           → AudioManager.adjustSuggestedStreamVolumeForUid
             → AudioService.adjustSuggestedStreamVolume
               → adjustStreamVolume

要点:

  • Suggested stream:先猜该调哪路流(活跃会话 / 用户锁定的 volControlStream)
  • TV 上多数流常 alias 到 MUSIC,所以 log 里常见 stream=3
  • 真正改 index / 静音后,再走 Handler 与 UI

三、AudioService 内部:静音 / 调 index / 刷 UI

AudioService 音量内部流程

两条常用入口最后都会汇到 AudioHandler + VolumeStreamState

入口 路径
绝对设置 setStreamVolumeonSetStreamVolumesetStreamVolumeInt
相对调节 adjustSuggestedStreamVolumeadjustStreamVolume

3.1 静音分支

isMuteAdjust 为真,且设备不在 mFullVolumeDevices(全音量设备往往不允许本地静音)时:

  1. ADJUST_TOGGLE_MUTE:翻转 streamState.mIsMuted
  2. ADJUST_MUTE / UNMUTE:按方向设静音状态
  3. MUSIC 时常同步 setSystemAudioMute(CEC System Audio 等场景)
  4. 对同一 alias 下的流调用 VolumeStreamState.mute(state)
  5. 强制相机音效流(STREAM_SYSTEM_ENFORCED + camera sound forced)可跳过

随后常发 MSG_SET_ALL_VOLUMESapplyAllVolumes()

3.2 正常加减(非静音)

条件大致是:不是 full volume 设备,且 adjustIndex(...) 成功(或当前处于静音态需要配合处理):

  • ADJUST_RAISE:若原先静音,立即 mute(false)(Amlogic 等平台会对 alias 组批量 unmute)
  • ADJUST_LOWER:在 single volume 等场景可能 延迟 unmuteMSG_UNMUTE_STREAM

然后:

sendMsg(MSG_SET_DEVICE_VOLUME) → setDeviceVolume
  → VolumeStreamState.setStreamVolumeIndex
  → AudioSystem.setStreamVolumeIndexAS   // 进入 Native Policy

handleMessage 里典型分支:

msg 动作
MSG_SET_DEVICE_VOLUME setDeviceVolume
MSG_SET_ALL_VOLUMES setAllVolumes
MSG_UNMUTE_STREAM 延迟取消静音

3.3 刷音量条 UI

sendVolumeUpdateVolumeController.postVolumeChanged → SystemUI VolumeDialogControllerImplVolumeDialogImpl 画条。
改 HAL 增益刷 UI 是两条线:可以只改数不显示条,也可以只显示条(视 flags)。


四、旁路同步:蓝牙、助听器、CEC、透传

4.1 蓝牙 AVRCP 绝对音量

adjustStreamVolume 时若当前设备在 A2DP 集合,且流 alias 为 MUSIC:

静音 → newIndex = 0
否则 → 用当前 index
mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex / 10)  // 刻度换算视平台

遥控器调音量 → 同步到蓝牙音箱(示意 log):

AudioService: adjustStreamVolume: postSetAvrcpAbsoluteVolumeIndex index=50 stream=3
AS.BtHelper: setAvrcpAbsoluteVolumeIndex index=5
BluetoothA2dp: setAvrcpAbsoluteVolume
AvrcpVolumeManager: sendVolumeChanged ... deviceVolume=5

蓝牙音箱端调音量 → 回写系统(示意 log):

AvrcpTargetJni: setVolume
AvrcpVolumeManager: setVolume ... avrcpVolume=55 deviceVolume=6
AudioService: setStreamVolume(stream=3, index=6, calling=com.android.bluetooth)

两边是「系统调 → AVRCP 下发」与「音箱调 → setStreamVolume 回写」对称关系。

4.2 助听器

device == DEVICE_OUT_HEARING_AID
  && streamType == getHearingAidStreamType()
  → mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType)

4.3 HDMI-CEC 音量

当同时满足大致条件:

  • mHdmiPlaybackClient != null
  • mHdmiCecVolumeControlEnabled
  • stream alias 为 MUSIC
  • 设备在 mFullVolumeDevices

则本地不算「可变软音量」,而是把方向映射成按键发给对端:

direction keyCode
RAISE KEYCODE_VOLUME_UP
LOWER KEYCODE_VOLUME_DOWN
MUTE / UNMUTE / TOGGLE KEYCODE_VOLUME_MUTE

调用 mHdmiPlaybackClient.sendVolumeKeyEvent(...),由电视 / 功放真正改音量。

4.4 Passthrough:本地调不动

透传时本地往往无法有效调节 PCM 增益,应对用户提示,由后端设备控制:

STREAM_MUSIC && isVolumePassthrough() → showPassthroughWarning()

与「全音量设备隐藏音量条」常一起出现(见下一节)。


五、无障碍(TalkBack)音量

5.1 独立音量 vs 跟 MUSIC

updateStreamVolumeAlias 里:

a11yStreamAlias = sIndependentA11yVolume
    ? STREAM_ACCESSIBILITY
    : STREAM_MUSIC

sIndependentA11yVolume == true

  • STREAM_ACCESSIBILITY 用独立 index,并从 Settings readSettings() 恢复
  • 遥控器按音量键常调的是 MUSIC调不动 TalkBack——这是预期行为

TalkBack 语音大小请到:

Settings → Device Preferences → Accessibility → TalkBack
  → Configuration → Speech volume

若产品要求「遥控器也能调无障碍音量」,常见改法是把对应 alias 改回 STREAM_MUSIC(需评估与独立响度策略的冲突)。

5.2 静音键调不了无障碍流

if (isMuteAdjust && !isStreamAffectedByMute(streamType)) {
    return; // 直接返回,不静音
}

isStreamAffectedByMute 看位掩码 mMuteAffectedStreams

return (mMuteAffectedStreams & (1 << streamType)) != 0;

公版默认受静音影响的流常见包括 MUSIC / RING / NOTIFICATION / SYSTEM / VOICE_CALL / BLUETOOTH_SCO 等,往往不含 STREAM_ACCESSIBILITY
例:streamType = 10(ACCESSIBILITY)时,1 << 10 与默认掩码相与为 0 → 返回 false → 静音键无效。

若产品要求静音键也静掉 TalkBack,需把 ACCESSIBILITY 位加入 DEFAULT_MUTE_STREAMS_AFFECTED / mMuteAffectedStreams(注意无障碍可达性合规)。


六、全音量设备(Full Volume)与隐藏音量条

「全音量」意味着:本地不做可变 index 衰减,音量交给对端(TV / 功放)或透传链路

6.1 两种常见把 HDMI OUT 设成全音量的情况

(1)打开 CEC,且打开 CEC Volume Control

hdmiCecSink && mHdmiCecVolumeControlEnabled
  → addAudioSystemDeviceOutToFullVolumeDevices(DEVICE_OUT_HDMI)

(2)编码 / 透传输出(非 PCM)

例如非 MS12,或 MS12 但 digital_audio_format == BYPASS 时,看 SPDIF/HDMI 格式是否为 AML_STEREO_PCM

hal_control_vol_en = (format == AML_STEREO_PCM) ? true : false;

非 PCM(DTS / Dolby 透传等)时本地 HAL 音量控制关闭,行为上接近「调不动 / 交给后级」。

6.2 UI:全音量时隐藏音量条

sendVolumeUpdate 中对 MUSIC:

if (isFullVolumeDevice(device)) {
    flags &= ~FLAG_SHOW_UI;  // 隐藏音量条
    // log: Hide volume bar for TV's available or in audio passthrough mode.
}

典型 log:

AudioService: adjustStreamVolume() stream=3, dir=1, ...
AudioService: Hide volume bar for TV's available or in audio passthrough mode.

6.3 setDeviceVolumeBehaviorInternal

behavior 效果
VARIABLE 移出 full / fixed,本地可调
FIXED 固定音量设备
FULL 加入 full volume(HDMI 时往往还要求 CEC sink + CEC volume 可用,否则可能「完全没法调」)
ABSOLUTE 当前路径常直接抛不支持

七、排查时怎么对号入座

现象 先看
按键有 UI、Native 无增益变化 MSG_SET_DEVICE_VOLUME 之后 Policy/HAL
有按键 log、无音量条 FLAG_SHOW_UI 被清掉?full volume / passthrough?
蓝牙不同步 AVRCP absolute volume 路径 / setAvrcpAbsoluteVolumeIndex
CEC 下本地调不动 是否 full volume + sendVolumeKeyEvent
透传调不动 isVolumePassthrough / 非 PCM 格式
TalkBack 遥控器调不动 sIndependentA11yVolume,去 Speeech volume 设置
TalkBack 静音键无效 isStreamAffectedByMute / mMuteAffectedStreams
DVB 后再播 OTT 变小 音量问题排查 · AD/continuous
按键进 MediaSession 了吗?
  → adjustStreamVolume 走到静音还是 adjustIndex?
  → 是否 full volume / passthrough / a11y 提前 return?
  → MSG_SET_DEVICE_VOLUME → setStreamVolumeIndexAS?
  → 旁路:AVRCP / CEC / HearingAid 是否该同步却没同步?
  → UI flags 是否被去掉 SHOW_UI?

八、小结

  • 入口:App 用 adjustStreamVolume / setStreamVolume;按键多经 MediaSession → adjustSuggestedStreamVolume
  • 主干AudioService 里分 静音adjustIndex,再经 MSG_SET_DEVICE_VOLUME 下到 AudioSystem
  • 旁路:A2DP 绝对音量、助听器、HDMI-CEC 按键、passthrough 提示。
  • 无障碍:独立 volume 时遥控器调 MUSIC 正常调不动 TalkBack;静音掩码常不含 ACCESSIBILITY。
  • 全音量:CEC volume control 或 非 PCM 透传 会让 HDMI 变为 full volume,并 隐藏音量条
  • HAL:盒子常见 out_set_volume,Soundbar 常见 adev_set_audio_port_config

把「Java 分支」和「Native 曲线 / HAL」分开看,音量键类问题会清晰很多。

相关阅读

评论

还没有评论,来做第一个吧

读完了不评论,作者会以为你在沉思