基于原生Android 16设置音量调用流程

应用或者按键调整音量

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 安全校验,就在这里发挥作用。


五、最关键的总结(必看)

  1. 这是 Android 音量设置的总入口
  2. 负责:权限、设备、策略、安全、蓝牙同步
  3. 所有音量调节最终都会走到这个函数
  4. 最终通过 JNI 调用到底层 C++ 代码
  5. 是 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

分类设置不同类型的音量

相关推荐
zh_xuan19 分钟前
Android Hilt实现依赖注入
android·hilt
freshman_y1 小时前
Qtcreator怎么新建安卓项目?编写一个五子棋游戏APP?
android·qt
时寒的笔记1 小时前
js逆向7_案例惠nong网
android·开发语言·javascript
肯多洛夫斯基2 小时前
安卓工控屏静默连WiFi全攻略
android
极梦网络无忧3 小时前
Android无障碍服务实现抖音直播间界面监控(场控助手核心原理)
android
call me by ur name4 小时前
ERNIE 5.0 Technical Report论文解读
android·开发语言·人工智能·机器学习·ai·kotlin
kerli4 小时前
Compose 组件:Box 核心参数及其 Bias 算法
android·前端
BLUcoding4 小时前
Android 常用控件及核心属性
android
遥不可及zzz4 小时前
[特殊字符] Android AAB 一键安装工具配置指南
android·macos
私人珍藏库5 小时前
【Android】一键硬核锁手机
android·智能手机·app·工具·软件