应用或者按键调整音量
→
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);
此函数比较复杂,分别设置了不同类型的音量,具体代码如下:
java
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
分类设置不同类型的音量