应用或者按键调整音量
→
frameworks/base/media/java/android/media/AudioManager.java
setStreamVolume
→
frameworks/base/services/core/java/com/android/server/audio/AudioService.java
setStreamVolumeWithAttribution
→
setStreamVolumeWithAttributionInt
→
setStreamVolume(streamType, index, flags, ada,
callingPackage, callingPackage, attributionTag,
Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission(),
canChangeMuteAndUpdateController);
此函数比较复杂,分别设置了不同类型的音量,具体代码如下:
private void setStreamVolume(int streamType, int index, int flags, @Nullable AudioDeviceAttributes ada, String callingPackage, String caller, String attributionTag, int uid, boolean hasModifyAudioSettings, boolean canChangeMuteAndUpdateController) { if (DEBUG_VOL) { Log.d(TAG, "setStreamVolume(stream=" + streamType+", index=" + index + ", dev=" + ada + ", calling=" + callingPackage + ")"); } if (mUseFixedVolume) { return; } streamType = replaceBtScoStreamWithVoiceCall(streamType, "setStreamVolume"); ensureValidStreamType(streamType); int streamTypeAlias = sStreamVolumeAlias.get(streamType, /*valueIfKeyNotFound*/-1); if (streamTypeAlias == -1) { Log.e(TAG, "setStreamVolume: no stream vol alias for stream type " + streamType); return; } final VolumeStreamState streamState = getVssForStreamOrDefault(streamTypeAlias); if (!replaceStreamBtSco() && (streamType == AudioManager.STREAM_VOICE_CALL) && isInCommunication() && mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) { Log.i(TAG, "setStreamVolume for STREAM_VOICE_CALL, switching to STREAM_BLUETOOTH_SCO"); streamType = AudioManager.STREAM_BLUETOOTH_SCO; } final int device = (ada == null) ? getDeviceForStream(streamType) : ada.getInternalType(); int oldIndex; // skip a2dp absolute volume control request when the device // is neither an a2dp device nor BLE device if ((!AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device) && !AudioSystem.DEVICE_OUT_ALL_BLE_SET.contains(device)) && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) { return; } // If we are being called by the system (e.g. hardware keys) check for current user // so we handle user restrictions correctly. if (uid == android.os.Process.SYSTEM_UID) { uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid)); } if (!checkNoteAppOp( STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage, attributionTag)) { return; } if (isAndroidNPlus(callingPackage) && wouldToggleZenMode(getNewRingerMode(streamTypeAlias, index, flags)) && !mNm.isNotificationPolicyAccessGrantedForPackage(callingPackage)) { throw new SecurityException("Not allowed to change Do Not Disturb state"); } if (!volumeAdjustmentAllowedByDnd(streamTypeAlias, flags)) { return; } mSoundDoseHelper.invalidatePendingVolumeCommand(); oldIndex = streamState.getIndex(device); index = rescaleIndex(index * 10, streamType, streamTypeAlias); if (setStreamVolumeOrder()) { flags &= ~AudioManager.FLAG_FIXED_VOLUME; if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) { flags |= AudioManager.FLAG_FIXED_VOLUME; // volume is either 0 or max allowed for fixed volume devices if (index != 0) { index = mSoundDoseHelper.getSafeMediaVolumeIndex(device); if (index < 0) { index = streamState.getMaxIndex(); } } } if (!mSoundDoseHelper.willDisplayWarningAfterCheckVolume(streamType, index, device, flags)) { onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings, // ada is non-null when called from setDeviceVolume, // which shouldn't update the mute state canChangeMuteAndUpdateController /*canChangeMute*/); index = getVssForStreamOrDefault(streamType).getIndex(device); } } int streamToDriveAbsVol = absVolumeIndexFix() ? getBluetoothContextualVolumeStream() : AudioSystem.STREAM_MUSIC; if (streamTypeAlias == streamToDriveAbsVol && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device) && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { if (DEBUG_VOL) { Log.d(TAG, "setStreamVolume postSetAvrcpAbsoluteVolumeIndex index=" + index + "stream=" + streamType); } mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10); } else if (isAbsoluteVolumeDevice(device) && ((flags & AudioManager.FLAG_ABSOLUTE_VOLUME) == 0)) { final AbsoluteVolumeDeviceInfo info = getAbsoluteVolumeDeviceInfo(device); dispatchAbsoluteVolumeChanged(streamType, info, index); } if (AudioSystem.isLeAudioDeviceType(device) && streamType == getBluetoothContextualVolumeStream() && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) { if (DEBUG_VOL) { Log.d(TAG, "setStreamVolume postSetLeAudioVolumeIndex index=" + index + " stream=" + streamType); } mDeviceBroker.postSetLeAudioVolumeIndex(index, getVssForStreamOrDefault(streamType).getMaxIndex(), streamType); } if (device == AudioSystem.DEVICE_OUT_HEARING_AID && streamType == getBluetoothContextualVolumeStream()) { Log.i(TAG, "setStreamVolume postSetHearingAidVolumeIndex index=" + index + " stream=" + streamType); mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType); } if (!setStreamVolumeOrder()) { flags &= ~AudioManager.FLAG_FIXED_VOLUME; if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) { flags |= AudioManager.FLAG_FIXED_VOLUME; // volume is either 0 or max allowed for fixed volume devices if (index != 0) { index = mSoundDoseHelper.getSafeMediaVolumeIndex(device); if (index < 0) { index = streamState.getMaxIndex(); } } } if (!mSoundDoseHelper.willDisplayWarningAfterCheckVolume(streamType, index, device, flags)) { onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings, // ada is non-null when called from setDeviceVolume, // which shouldn't update the mute state canChangeMuteAndUpdateController /*canChangeMute*/); index = getVssForStreamOrDefault(streamType).getIndex(device); } } synchronized (mHdmiClientLock) { if (streamTypeAlias == AudioSystem.STREAM_MUSIC && (oldIndex != index)) { maybeSendSystemAudioStatusCommand(false); } } if (canChangeMuteAndUpdateController) { // only non-null when coming here from setDeviceVolume // TODO change test to check early if device is current device or not sendVolumeUpdate(streamType, oldIndex, index, flags, device); } }豆包对此函数的分析如下:
代码深度解析:Android 音量设置核心方法
这是Android 系统服务
AudioService中最核心、最复杂的音量设置方法:setStreamVolume()一句话总结:它是 Android 系统处理所有音量调节请求的总入口,负责权限校验、设备适配、音量计算、策略控制、硬件同步,最终完成音量设置。我会用最清晰、最容易理解的方式,把这段几百行代码彻底讲透。
一、整体定位
- 所在类:
AudioService.java(系统核心服务)- 作用:处理所有音量调节请求包括:按键调音量、设置页拉音量条、App 调节音量、蓝牙设备同步音量
- 调用链:
App / 系统UI → AudioManager → AudioService.setStreamVolume二、核心流程(按执行顺序)
我把这段代码拆成10 个关键步骤,你一眼就能看懂:
1. 调试日志 + 固定音量设备直接返回
if (mUseFixedVolume) { return; }电视、机顶盒等音量固定的设备,直接不处理。
2. 音频流类型替换(蓝牙 SCO 特殊处理)
streamType = replaceBtScoStreamWithVoiceCall(streamType);把蓝牙通话流统一转为通话流处理。
3. 校验流类型 + 获取流别名(音量共享)
ensureValidStreamType(streamType); int streamTypeAlias = sStreamVolumeAlias.get(streamType);Android 中多个流共用同一个音量(比如系统音、通知音共用)。
4. 获取音量状态对象
final VolumeStreamState streamState = getVssForStreamOrDefault(streamTypeAlias);
VolumeStreamState保存每个流的当前音量、最大音量、设备音量。5. 权限检查(非常严格)
checkNoteAppOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage);
- 检查 App 是否有修改音频设置权限
- 检查用户是否限制音量
- 无权限直接返回
6. 勿扰模式(DND)检查
if (wouldToggleZenMode(...) && !isNotificationPolicyAccessGranted...) { throw new SecurityException("Not allowed to change DND state"); }没权限的 App不能通过调节音量触发勿扰模式。
7. 音量指数重计算(关键)
index = rescaleIndex(index * 10, streamType, streamTypeAlias);把 0~100 的音量重映射到硬件支持的范围。
8. 安全音量提醒(Sound Dose)
if (!mSoundDoseHelper.willDisplayWarningAfterCheckVolume(...))欧盟强制要求:大音量提醒,超过安全音量会弹窗。
9. 真正设置音量
onSetStreamVolume(streamType, index, flags, device, ...);最终调用到JNI → AudioSystem → AudioPolicyService。
10. 同步蓝牙 / HDMI/LE Audio 等外部设备音量
mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10); mDeviceBroker.postSetLeAudioVolumeIndex(...); mDeviceBroker.postSetHearingAidVolumeIndex(...);
- 蓝牙耳机 AVRCP 绝对音量
- LE Audio 蓝牙音量
- 助听设备音量
- HDMI 音量
11. 发送音量更新广播
sendVolumeUpdate(streamType, oldIndex, index, flags, device);通知系统 UI 更新音量条、通知 App 音量变化。
三、这段代码里的 “黑科技” 细节
1. 音量别名机制
int streamTypeAlias = sStreamVolumeAlias.get(streamType);Android 多个流共用一套音量:
- 铃声 + 系统 + 通知 → 共用铃声音量
- 蓝牙通话 → 共用通话音量
2. 蓝牙设备特殊处理
- A2DP 蓝牙耳机音乐音量
- BLE 音频(LE Audio)
- 蓝牙助听设备
- SCO 通话音量
3. 固定音量设备处理
if (isFixedVolumeDevice(device)) { flags |= AudioManager.FLAG_FIXED_VOLUME; index = streamState.getMaxIndex(); }有些设备音量不可调,只能最大或静音。
4. 勿扰模式拦截
if (!volumeAdjustmentAllowedByDnd(streamTypeAlias, flags)) { return; }勿扰模式下,部分音量不能调节。
四、和你上一轮 C++ 代码的关系
Java 层(AudioService)
onSetStreamVolume(...)JNI 层(AudioSystem)
AudioSystem::setStreamVolumeIndex(...)底层服务(AudioPolicyService)
AudioPolicyService::setStreamVolumeIndex(...)完整调用链
AudioService.setStreamVolume ↓ AudioSystem.setStreamVolumeIndex (JNI) ↓ AudioPolicyService.setStreamVolumeIndex (C++ IPC) ↓ AudioFlinger → 硬件音量设置你之前看到的
onTransact安全校验,就在这里发挥作用。五、最关键的总结(必看)
- 这是 Android 音量设置的总入口
- 负责:权限、设备、策略、安全、蓝牙同步
- 所有音量调节最终都会走到这个函数
- 最终通过 JNI 调用到底层 C++ 代码
- 是 Android 音频系统最核心、最复杂的方法之一
超级精简总结
setStreamVolume=Android 音量总管- 做权限校验、设备适配、音量计算
- 调用底层
AudioSystem/AudioPolicyService- 同步蓝牙、HDMI、助听设备等外部音量
在此函数中调用了以下函数,进行设置音量:
→
onSetStreamVolume
→
setStreamVolumeInt
通过 MSG_SET_DEVICE_VOLUME 消息调用
→
setDeviceVolume
→
setStreamVolumeIndex
→
frameworks/base/media/java/android/media/AudioSystem.java
setStreamVolumeIndexAS
→
android_media_AudioSystem_setStreamVolumeIndex
→
AudioSystem::setStreamVolumeIndex
→
frameworks/av/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
AudioPolicyService::setStreamVolumeIndex
→
AudioPolicyManager::setStreamVolumeIndex
→
AudioPolicyManager::setVolumeIndexForAttributes
分类设置不同类型的音量