Android音量调节(一)音量键的处理流程

csdn同步更新:https://blog.csdn.net/qq_33668392/article/details/85265271

1.Android音量键调节音量流程

整个流程分析是基于Android 8.0来进行讲解的

2.Android流定义

在Android系统中,音量都是分开控制的,比如媒体音量,铃声音量,闹钟音量,蓝牙音量,通话音量都是分开控制的。各种音量都是通过音频流来区别不同音量类型的。在Android 8.0里面,一共定义了11种流的类型,每种流类型都定义了最大音量(MAX_STREAM_VOLUME),最小音量(MIN_STREAM_VOLUME),以及默认音量(DEFAULT_STREAM_VOLUME),细心的童鞋可能会发现,我们能调节的音量类型,也就5种。所以还存在一个流的映射关系,然后这个流就可以使用另外一个流的音量了。

  • 最大音量
-frameworks/base/services/core/java/com/android/server/audio/AudioService.java

   /** Maximum volume index values for audio streams */
    private static int[] MAX_STREAM_VOLUME = new int[] {
        5,  // STREAM_VOICE_CALL
        7,  // STREAM_SYSTEM
        7,  // STREAM_RING
        15, // STREAM_MUSIC
        7,  // STREAM_ALARM
        7,  // STREAM_NOTIFICATION
        15, // STREAM_BLUETOOTH_SCO
        7,  // STREAM_SYSTEM_ENFORCED
        15, // STREAM_DTMF
        15, // STREAM_TTS
        15  // STREAM_ACCESSIBILITY
    };
  • 最小音量
-frameworks/base/services/core/java/com/android/server/audio/AudioService.java

    /** Minimum volume index values for audio streams */
    private static int[] MIN_STREAM_VOLUME = new int[] {
        1, // STREAM_VOICE_CALL
        0, // STREAM_SYSTEM
        0, // STREAM_RING
        0, // STREAM_MUSIC
        0, // STREAM_ALARM
        0, // STREAM_NOTIFICATION
        0, // STREAM_BLUETOOTH_SCO
        0, // STREAM_SYSTEM_ENFORCED
        0, // STREAM_DTMF
        0, // STREAM_TTS
        0  // STREAM_ACCESSIBILITY
    };
  • 默认音量
-frameworks/base/media/java/android/media/AudioSystem.java

    public static int[] DEFAULT_STREAM_VOLUME = new int[] {
        4, // STREAM_VOICE_CALL
        7, // STREAM_SYSTEM
        5, // STREAM_RING
        5, // STREAM_MUSIC
        6, // STREAM_ALARM
        5, // STREAM_NOTIFICATION
        7, // STREAM_BLUETOOTH_SCO
        7, // STREAM_SYSTEM_ENFORCED
        5, // STREAM_DTMF
        5, // STREAM_TTS
        5  // STREAM_ACCESSIBILITY
    };
  • 音频流映射
    在Android系统中,不同设备的映射不尽相同,一共定义了三种设备的音频流的映射,分别是VOICE(STREAM_VOLUME_ALIAS_VOICE),TELEVISION(STREAM_VOLUME_ALIAS_TELEVISION),DEFAULT(STREAM_VOLUME_ALIAS_DEFAULT)。

    • VOICE(STREAM_VOLUME_ALIAS_VOICE),voice表示具有语音功能的设备,比如电话
    -frameworks/base/services/core/java/com/android/server/audio/AudioService.java
    
        private final int[] STREAM_VOLUME_ALIAS_VOICE = new int[] {
            AudioSystem.STREAM_VOICE_CALL,       // STREAM_VOICE_CALL
            AudioSystem.STREAM_RING,             // STREAM_SYSTEM
            AudioSystem.STREAM_RING,             // STREAM_RING
            AudioSystem.STREAM_MUSIC,            // STREAM_MUSIC
            AudioSystem.STREAM_ALARM,            // STREAM_ALARM
            AudioSystem.STREAM_RING,             // STREAM_NOTIFICATION
            AudioSystem.STREAM_BLUETOOTH_SCO,    // STREAM_BLUETOOTH_SCO
            AudioSystem.STREAM_RING,             // STREAM_SYSTEM_ENFORCED
            AudioSystem.STREAM_RING,             // STREAM_DTMF
            AudioSystem.STREAM_MUSIC,            // STREAM_TTS
            AudioSystem.STREAM_MUSIC             // STREAM_ACCESSIBILITY
        };
    
    • TELEVISION(STREAM_VOLUME_ALIAS_TELEVISION),television对应电视或者机顶盒
    -frameworks/base/services/core/java/com/android/server/audio/AudioService.java
    
        private final int[] STREAM_VOLUME_ALIAS_TELEVISION = new int[] {
            AudioSystem.STREAM_MUSIC,      // STREAM_VOICE_CALL
            AudioSystem.STREAM_MUSIC,      // STREAM_SYSTEM
            AudioSystem.STREAM_MUSIC,      // STREAM_RING
            AudioSystem.STREAM_MUSIC,      // STREAM_MUSIC
            AudioSystem.STREAM_MUSIC,      // STREAM_ALARM
            AudioSystem.STREAM_MUSIC,      // STREAM_NOTIFICATION
            AudioSystem.STREAM_MUSIC,      // STREAM_BLUETOOTH_SCO
            AudioSystem.STREAM_MUSIC,      // STREAM_SYSTEM_ENFORCED
            AudioSystem.STREAM_MUSIC,      // STREAM_DTMF
            AudioSystem.STREAM_MUSIC,      // STREAM_TTS
            AudioSystem.STREAM_MUSIC       // STREAM_ACCESSIBILITY
        };
    
    • DEFAULT(STREAM_VOLUME_ALIAS_DEFAULT),default表示平板之类的设备
    -frameworks/base/services/core/java/com/android/server/audio/AudioService.java
    
        private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] {
            AudioSystem.STREAM_VOICE_CALL,      // STREAM_VOICE_CALL
            AudioSystem.STREAM_RING,            // STREAM_SYSTEM
            AudioSystem.STREAM_RING,            // STREAM_RING
            AudioSystem.STREAM_MUSIC,           // STREAM_MUSIC
            AudioSystem.STREAM_ALARM,           // STREAM_ALARM
            AudioSystem.STREAM_RING,            // STREAM_NOTIFICATION
            AudioSystem.STREAM_BLUETOOTH_SCO,   // STREAM_BLUETOOTH_SCO
            AudioSystem.STREAM_RING,            // STREAM_SYSTEM_ENFORCED
            AudioSystem.STREAM_RING,            // STREAM_DTMF
            AudioSystem.STREAM_MUSIC,           // STREAM_TTS
            AudioSystem.STREAM_MUSIC            // STREAM_ACCESSIBILITY
        };
    
    • 映射关系如下
    流序号 流类型 最大音量 最小音量 默认音量 VOICE/DEFAULT TELEVISION
    0 STREAM_VOICE_CALL 5 1 4 STREAM_VOICE_CALL STREAM_MUSIC
    1 STREAM_SYSTEM 7 0 7 STREAM_RING STREAM_MUSIC
    2 STREAM_RING 7 0 5 STREAM_RING STREAM_MUSIC
    3 STREAM_MUSIC 15 0 5 STREAM_MUSIC STREAM_MUSIC
    4 STREAM_ALARM 7 0 6 STREAM_ALARM STREAM_MUSIC
    5 STREAM_NOTIFICATION 7 0 5 STREAM_RING STREAM_MUSIC
    6 STREAM_BLUETOOTH_SCO 15 0 7 STREAM_BLUETOOTH_SCO STREAM_MUSIC
    7 STREAM_SYSTEM_ENFORCED 7 0 7 STREAM_RING STREAM_MUSIC
    8 STREAM_DTMF 15 0 5 STREAM_RING STREAM_MUSIC
    9 STREAM_TTS 15 0 5 STREAM_MUSIC STREAM_MUSIC
    10 STREAM_ACCESSIBILITY 15 0 5 STREAM_MUSIC STREAM_MUSIC

    通过上表,可以很明显的看到,在手机和平板上面,我们实际上能调节的也就是上面所讲述的5个音量,在TV和机顶盒之类的设备上,我们能调节的也就一种音量。整理一下上表可直接调节的流类型,如下表所示:

    流类型 最大音量 最小音量 默认音量 含义
    STREAM_VOICE_CALL 5 1 4 通话音量
    STREAM_RING 7 0 5 铃声,通知音量等
    STREAM_MUSIC 15 0 5 多媒体音量
    STREAM_ALARM 7 0 6 闹钟音量
    STREAM_BLUETOOTH_SCO 15 0 7 蓝牙音量

3.按键的处理流程

-frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java

    private void dispatchDirectAudioEvent(KeyEvent event) {
        if (event.getAction() != KeyEvent.ACTION_DOWN) {
            return;
        }
        int keyCode = event.getKeyCode();
        int flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND
                | AudioManager.FLAG_FROM_KEY;
        String pkgName = mContext.getOpPackageName();
        switch (keyCode) {
            case KeyEvent.KEYCODE_VOLUME_UP:
                try {
                    getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE,
                            AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
                } catch (Exception e) {
                    Log.e(TAG, "Error dispatching volume up in dispatchTvAudioEvent.", e);
                }
                break;
            case KeyEvent.KEYCODE_VOLUME_DOWN:
                try {
                    getAudioService().adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER,
                            AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
                } catch (Exception e) {
                    Log.e(TAG, "Error dispatching volume down in dispatchTvAudioEvent.", e);
                }
                break;
            case KeyEvent.KEYCODE_VOLUME_MUTE:
                try {
                    if (event.getRepeatCount() == 0) {
                        getAudioService().adjustSuggestedStreamVolume(
                                AudioManager.ADJUST_TOGGLE_MUTE,
                                AudioManager.USE_DEFAULT_STREAM_TYPE, flags, pkgName, TAG);
                    }
                } catch (Exception e) {
                    Log.e(TAG, "Error dispatching mute in dispatchTvAudioEvent.", e);
                }
                break;
        }
    }
    
    ......
    
    // 这里通过AIDL获取IAudioService的实例,不深究,有需要了解AIDL的请找度娘哈
    static IAudioService getAudioService() {
        IAudioService audioService = IAudioService.Stub.asInterface(
                ServiceManager.checkService(Context.AUDIO_SERVICE));
        if (audioService == null) {
            Log.w(TAG, "Unable to find IAudioService interface.");
        }
        return audioService;
    }

这里是直接执行了音频键的操作,通过Binder获取到了AudioService的实例,去调用了adjustSuggestedStreamVolume方法,参数含义如下:

按键类型 Audio Service操作类型 含义
KEYCODE_VOLUME_UP AudioManager.ADJUST_RAISE 音量减
KEYCODE_VOLUME_DOWN AudioManager.ADJUST_LOWER 音量加
KEYCODE_VOLUME_MUTE AudioManager.ADJUST_TOGGLE_MUTE 改变静音状态

在按键的处理过程中,并没有将相应的code传递给AudioService,而是使用了相关的定义,将KEYCODE_VOLUME_UP等操作转化为了ADJUST_RAISE等。而flag存储了一些对音量的要求或者信息吧,这个也很重要。

-frameworks/base/media/java/android/media/AudioManager.java

    /**
     * Increase the ringer volume.
     */
    public static final int ADJUST_RAISE = 1;

    /**
     * Decrease the ringer volume.
     */
    public static final int ADJUST_LOWER = -1;

    /**
     * Maintain the previous ringer volume. This may be useful when needing to
     * show the volume toast without actually modifying the volume.
     *
     */
    public static final int ADJUST_SAME = 0;

    /**
     * Mute the volume. Has no effect if the stream is already muted.
     */
    public static final int ADJUST_MUTE = -100;

    /**
     * Unmute the volume. Has no effect if the stream is not muted.
     */
    public static final int ADJUST_UNMUTE = 100;

    /**
     * Toggle the mute state. If muted the stream will be unmuted. If not muted
     * the stream will be muted.
     */
    public static final int ADJUST_TOGGLE_MUTE = 101;

3.1 adjustSuggestedStreamVolume

接下来就到了AudioService的adjustSuggestedStreamVolume方法里面了

-frameworks/base/services/core/java/com/android/server/audio/AudioService.java

    public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
            String callingPackage, String caller) {
        adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
                caller, Binder.getCallingUid());
    }

调用重载的adjustSuggestedStreamVolume方法

    private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
            String callingPackage, String caller, int uid) {
        final int streamType;
        if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1
            streamType = mVolumeControlStream;
        } else {
            // 这里获取到,可能是活动状态的音频流,但是不确定,还有待进一步确认
            final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType);  
            final boolean activeForReal;
            if (maybeActiveStreamType == AudioSystem.STREAM_MUSIC) {
                // 这里如果STREAM_MUSIC现在在AudioFlinger处理的流中或在最后的0ms中处于活动状态,则为true。
                activeForReal = isAfMusicActiveRecently(0);
            } else {
                // 会调用native方法,不深究
                activeForReal = AudioSystem.isStreamActive(maybeActiveStreamType, 0);
            }
            // 如果activeForReal为true或者mVolumeControlStream为-1
            // 那么确定要操作修改的流类型为maybeActiveStreamType对应的流类型
            if (activeForReal || mVolumeControlStream == -1) {
                streamType = maybeActiveStreamType;
            } else {
                // activeForReal为false并且mVolumeControlStream不为-1
                // 表示用户点击了音量进度条,这时候要操作修改的流类型为mVolumeControlStream对应的流类型
                streamType = mVolumeControlStream;
            }
        }

        final boolean isMute = isMuteAdjust(direction);
        // 确保我们获取到的流类型是有效的
        ensureValidStreamType(streamType);
        // 将我们获取到的流,进行流映射,拿到最终需要操作的流类型
        final int resolvedStream = mStreamVolumeAlias[streamType];

        // Play sounds on STREAM_RING only.
        if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 &&
                resolvedStream != AudioSystem.STREAM_RING) {
            flags &= ~AudioManager.FLAG_PLAY_SOUND;
        }

        // For notifications/ring, show the ui before making any adjustments
        // Don't suppress mute/unmute requests
        // 通知和响铃,调整音量之前先显示UI。
        if (mVolumeController.suppressAdjustment(resolvedStream, flags, isMute)) {
            // 将操作置为ADJUST_SAME(ADJUST_SAME = 0);
            direction = 0;
            flags &= ~AudioManager.FLAG_PLAY_SOUND;
            flags &= ~AudioManager.FLAG_VIBRATE;
            if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");
        }
        // 这里设置音量
        adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid);
    }

这里需要注意的有以下几点:

  • mUserSelectedVolumeControlStream:这个属性表示,用户是否已通过单击音量进度条选择音量流来更改由音量键控制的音量,如果mVolumeControlStream为-1,那么mUserSelectedVolumeControlStream 为false。说简单点,当用户点击了某个音量条,这时再去按下音量加减,这个时候调节的是你点击的那个流类型。
  • getActiveStreamType:获取我们要控制的流的类型,当然,只是可能需要控制的流类型,还要进一步确认。getActiveStreamType获取流类型流程如下图所示。
  • mStreamVolumeAlias[streamType]:进行流映射,获取最终需要调整的流类型
  • suppressAdjustment:字面意思为抑制调整,为什么抑制调整呢,说白了,当我们没有显示音量的UI进度条的时候,不管我们是加音量还是减音量(注意:静音和解静音除外),这个时候都是先显示音量条,而不去改变音量的大小。所以当这个方法返回true的时候, direction = 0,这里direction为0就表示我们的操作为ADJUST_SAME,大家可以在AudioManager里面查看ADJUST_SAME的注释就知道这个操作表示只弹出UI但是不调整音量大小。
  • adjustStreamVolume:进行音量的调整

3.2 adjustStreamVolume

进入到了adjustStreamVolume函数,该函数比较长,分开讲,最后再来总体分析一下

    private void adjustStreamVolume(int streamType, int direction, int flags,
            String callingPackage, String caller, int uid) {
        if (mUseFixedVolume) {
            return;
        }

        ensureValidDirection(direction);
        ensureValidStreamType(streamType);

        boolean isMuteAdjust = isMuteAdjust(direction);

        if (isMuteAdjust && !isStreamAffectedByMute(streamType)) {
            return;
        }

        // use stream type alias here so that streams with same alias have the same behavior,
        // including with regard to silent mode control (e.g the use of STREAM_RING below and in
        // checkForRingerModeChange() in place of STREAM_RING or STREAM_NOTIFICATION)
        int streamTypeAlias = mStreamVolumeAlias[streamType];

        VolumeStreamState streamState = mStreamStates[streamTypeAlias];
        ......
}
  • mUseFixedVolume:表示使用固定音量,我们无法修改音量
  • mStreamVolumeAlias[streamType]:进行音频流的映射,拿到映射后的音频流
  • mStreamStates[streamTypeAlias]:mStreamStates是一个存储VolumeStreamState类型的数组,保存着每个音频流的状态。VolumeStreamState是AudioService的一个内部类,里面保存单个音频流的所有信息,比如流类型,音量大小,mute状态等。并且相同的流类型,在不同的设备,大小也是不一样的(比如耳机和扬声器,媒体音量大小是不一样的),这也是在VolumeStreamState里面去维护的。这个类也比较重要,后面有机会去分析一波。
    private void adjustStreamVolume(int streamType, int direction, int flags,
        String callingPackage, String caller, int uid) {
        ......
        final int device = getDeviceForStream(streamTypeAlias);

        int aliasIndex = streamState.getIndex(device);
        boolean adjustVolume = true;
        int step;

        // skip a2dp absolute volume control request when the device
        // is not an a2dp device
        // 如果不是蓝牙设备,则跳过音量绝对控制请求 
        if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) == 0 &&
            (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.
        // uid判断,方便去做用户权限处理
        if (uid == android.os.Process.SYSTEM_UID) {
            uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid));
        }
        // 权限处理
        if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)
                != AppOpsManager.MODE_ALLOWED) {
            return;
        }
        .......
    }
  • getDeviceForStream:通过流类型去获取设备类型
    这里去获取设备类型,通过流类型获取到了对应的VolumeStreamState的实例,然后调用了其observeDevicesForStream_syncVSS方法去获取devices,在observeDevicesForStream_syncVSS方法里面,又会去调用AudioSystem的getDevicesForStream去获取设备,这是个native方法。
    private int getDeviceForStream(int stream) {
        //这里最终会调用VolumeStreamState的observeDevicesForStream_syncVSS方法
        int device = getDevicesForStream(stream);
        if ((device & (device - 1)) != 0) {
            // Multiple device selection is either:
            // - speaker + one other device: give priority to speaker in this case.
            // - one A2DP device + another device: happens with duplicated output. In this case
            // retain the device on the A2DP output as the other must not correspond to an active
            // selection if not the speaker.
            // - HDMI-CEC system audio mode only output: give priority to available item in order.
            if ((device & AudioSystem.DEVICE_OUT_SPEAKER) != 0) {
                device = AudioSystem.DEVICE_OUT_SPEAKER;
            } else if ((device & AudioSystem.DEVICE_OUT_HDMI_ARC) != 0) {
                device = AudioSystem.DEVICE_OUT_HDMI_ARC;
            } else if ((device & AudioSystem.DEVICE_OUT_SPDIF) != 0) {
                device = AudioSystem.DEVICE_OUT_SPDIF;
            } else if ((device & AudioSystem.DEVICE_OUT_AUX_LINE) != 0) {
                device = AudioSystem.DEVICE_OUT_AUX_LINE;
            } else {
                device &= AudioSystem.DEVICE_OUT_ALL_A2DP;
            }
        }
        return device;
    }

    public int observeDevicesForStream_syncVSS(boolean checkOthers) {
        //调用到了AudioSystem里面,里面是一个native方法,这里就先不分析了
        final int devices = AudioSystem.getDevicesForStream(mStreamType);
        if (devices == mObservedDevices) {
            return devices;
        }
        final int prevDevices = mObservedDevices;
        mObservedDevices = devices;
        if (checkOthers) {
            // one stream's devices have changed, check the others
            observeDevicesForStreams(mStreamType);
        }
        // log base stream changes to the event log
        if (mStreamVolumeAlias[mStreamType] == mStreamType) {
            EventLogTags.writeStreamDevicesChanged(mStreamType, prevDevices, devices);
        }
        sendBroadcastToAll(mStreamDevicesChanged
                .putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_DEVICES, prevDevices)
                .putExtra(AudioManager.EXTRA_VOLUME_STREAM_DEVICES, devices));
        return devices;
    }

可以看到获取设备类型的代码最终是在native层做的,上层只是做了一些判断。并且Android系统对Audio的设备也有一系列的定义,这个我们就不讨论这个了,有需要的可以去网上找一找。

我们接着adjustStreamVolume方法往下看。

    private void adjustStreamVolume(int streamType, int direction, int flags,
        String callingPackage, String caller, int uid) {
        ......
        // 清除掉任何待处理的音量命令
        synchronized (mSafeMediaVolumeState) {
            mPendingVolumeCommand = null;
        }
        
        // 表示不是固定音量 
        flags &= ~AudioManager.FLAG_FIXED_VOLUME;
        // 如果是多媒体音量,并且是使用固定音量的设备
        if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) &&
               ((device & mFixedVolumeDevices) != 0)) {
            // 加上表示固定音量的flag
            flags |= AudioManager.FLAG_FIXED_VOLUME;

            // Always toggle between max safe volume and 0 for fixed volume devices where safe
            // volume is enforced, and max and 0 for the others.
            // This is simulated by stepping by the full allowed volume range
            if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&
                    (device & mSafeMediaVolumeDevices) != 0) {
                step = safeMediaVolumeIndex(device);
            } else {
                step = streamState.getMaxIndex();
            }
            if (aliasIndex != 0) {
                aliasIndex = step;
            }
        } else {
            // convert one UI step (+/-1) into a number of internal units on the stream alias
            // 如果不是多媒体音量,或者是多媒体音量但是不是固定音量的设备时
            // 将音量值的步进量从源流类型变换到目标流类型下,由于不同的流类型的音量调节范围不同,所以这个转换是必需的
            step = rescaleIndex(10, streamType, streamTypeAlias);
        }

        // 情景模式的处理
        // If either the client forces allowing ringer modes for this adjustment,
        // or the stream type is one that is affected by ringer modes
        if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
                (streamTypeAlias == getUiSoundsStreamType())) {
            int ringerMode = getRingerModeInternal();
            // do not vibrate if already in vibrate mode
            // 如果已经是震动模式,则不进行震动
            if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
                flags &= ~AudioManager.FLAG_VIBRATE;
            }
            // Check if the ringer mode handles this adjustment. If it does we don't
            // need to adjust the volume further.
            // 根据我们的操作来检查是否需要切换情景模式
            final int result = checkForRingerModeChange(aliasIndex, direction, step,
                    streamState.mIsMuted, callingPackage, flags);
            adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0;
            // If suppressing a volume adjustment in silent mode, display the UI hint
            if ((result & AudioManager.FLAG_SHOW_SILENT_HINT) != 0) {
                flags |= AudioManager.FLAG_SHOW_SILENT_HINT;
            }
            // If suppressing a volume down adjustment in vibrate mode, display the UI hint
            if ((result & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0) {
                flags |= AudioManager.FLAG_SHOW_VIBRATE_HINT;
            }
        }

        // If the ringermode is suppressing media, prevent changes
        // 勿扰模式
        if (!volumeAdjustmentAllowedByDnd(streamTypeAlias, flags)) {
            adjustVolume = false;
        }

        // 获取旧的音量大小
        int oldIndex = mStreamStates[streamType].getIndex(device);
        ......
    }
  • 固定音量设备的处理
  • 音量步进的处理:rescaleIndex方法,将音量值的步进量从源流类型变换到目标流类型下,由于不同的流类型的音量调节范围不同,所以这个转换是必需的。计算按下音量键的音量步进值。这个步进值是10而不是1。在VolumeStreamState中保存的音量值是其实际值的10倍,这是为了在不同流类型之间进行音量转化时能够保证一定精度的一种实现。可以理解为在转化过程中保留了小数点后一位的精度。
  • 情景模式处理:情景模式的处理就涉及到了音量的调整,以及情景模式的切换,在切换情景模式(震动到响铃除外)的时候,是没有去调整音量的。通过adjustVolume = (result & FLAG_ADJUST_VOLUME) != 0这一句体现出来的。
  • 勿扰模式处理
    private void adjustStreamVolume(int streamType, int direction, int flags,
        String callingPackage, String caller, int uid) {
        ......
        // 如果是调整音量大小
        if (adjustVolume && (direction != AudioManager.ADJUST_SAME)) {
            mAudioHandler.removeMessages(MSG_UNMUTE_STREAM);

            // Check if volume update should be send to AVRCP
            if (streamTypeAlias == AudioSystem.STREAM_MUSIC &&
                (device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0 &&
                (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
                synchronized (mA2dpAvrcpLock) {
                    if (mA2dp != null && mAvrcpAbsVolSupported) {
                        mA2dp.adjustAvrcpAbsoluteVolume(direction);
                    }
                }
            }

            //先处理静音调整
            if (isMuteAdjust) {
                boolean state;
                if (direction == AudioManager.ADJUST_TOGGLE_MUTE) {
                    state = !streamState.mIsMuted;
                } else {
                    state = direction == AudioManager.ADJUST_MUTE;
                }
                if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
                    setSystemAudioMute(state);
                } 
                for (int stream = 0; stream < mStreamStates.length; stream++) {
                    if (streamTypeAlias == mStreamVolumeAlias[stream]) {
                        if (!(readCameraSoundForced()
                                    && (mStreamStates[stream].getStreamType()
                                        == AudioSystem.STREAM_SYSTEM_ENFORCED))) {
                            // 这里获取当前流对应的VolumeStreamState实例,然后去调用mute方法
                            // 最终也会到AudioSystem去调用native方法
                            mStreamStates[stream].mute(state);
                        }
                    }
                }
            } else if ((direction == AudioManager.ADJUST_RAISE) &&
                    !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
                // 安全音量提示,音量增加的时候才会去检测
                Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex);
                mVolumeController.postDisplaySafeVolumeWarning(flags);
            } else if (streamState.adjustIndex(direction * step, device, caller)
                    || streamState.mIsMuted) {
                // Post message to set system volume (it in turn will post a
                // message to persist).
                if (streamState.mIsMuted) {
                    // Unmute the stream if it was previously muted
                    if (direction == AudioManager.ADJUST_RAISE) {
                        // unmute immediately for volume up
                        streamState.mute(false);
                    } else if (direction == AudioManager.ADJUST_LOWER) {
                        if (mIsSingleVolume) {
                            sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE,
                                    streamTypeAlias, flags, null, UNMUTE_STREAM_DELAY);
                        }
                    }
                }
                // 设置音量到底层
                sendMsg(mAudioHandler,
                        MSG_SET_DEVICE_VOLUME,
                        SENDMSG_QUEUE,
                        device,
                        0,
                        streamState,
                        0);
            }

            // Check if volume update should be sent to Hdmi system audio.
            // hdmi音量更新,这个不过多讲解
            int newIndex = mStreamStates[streamType].getIndex(device);
            if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
                setSystemAudioVolume(oldIndex, newIndex, getStreamMaxVolume(streamType), flags);
            }
            if (mHdmiManager != null) {
                synchronized (mHdmiManager) {
                    // mHdmiCecSink true => mHdmiPlaybackClient != null
                    if (mHdmiCecSink &&
                            streamTypeAlias == AudioSystem.STREAM_MUSIC &&
                            oldIndex != newIndex) {
                        synchronized (mHdmiPlaybackClient) {
                            int keyCode = (direction == -1) ? KeyEvent.KEYCODE_VOLUME_DOWN :
                                    KeyEvent.KEYCODE_VOLUME_UP;
                            final long ident = Binder.clearCallingIdentity();
                            try {
                                mHdmiPlaybackClient.sendKeyEvent(keyCode, true);
                                mHdmiPlaybackClient.sendKeyEvent(keyCode, false);
                            } finally {
                                Binder.restoreCallingIdentity(ident);
                            }
                        }
                    }
                }
            }
        }
        int index = mStreamStates[streamType].getIndex(device);
        // //通知外界音量发生变化
        sendVolumeUpdate(streamType, oldIndex, index, flags);
        ......
    }

这一段,如果是调整音量的大小,才会走这个if语句。先处理静音调整,然后处理安全音量提示,最后再处理音量的调整。到这里,整个adjustStreamVolume方法就讲完了,不过里面还有一些方法没有讲完,我们接着说说。

音量指数的调整是在adjustIndex这个方法里面去处理的,接下来我们看看这个方法

  • streamState.adjustIndex(direction * step, device, caller)
    public boolean adjustIndex(int deltaIndex, int device, String caller) {
        return setIndex(getIndex(device) + deltaIndex, device, caller);
    }

    public boolean setIndex(int index, int device, String caller) {
        boolean changed = false;
        int oldIndex;
        synchronized (VolumeStreamState.class) {
            // 获取到旧音量大小
            oldIndex = getIndex(device);
            index = getValidIndex(index);
            synchronized (mCameraSoundForced) {
                if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) {
                    index = mIndexMax;
                }
            }
            mIndexMap.put(device, index);
            // 如果需要调整的大小不等于旧音量大小,表示音量大小发生了改变
            changed = oldIndex != index;
            // Apply change to all streams using this one as alias if:
            // - the index actually changed OR
            // - there is no volume index stored for this device on alias stream.
            // If changing volume of current device, also change volume of current
            // device on aliased stream
            final boolean currentDevice = (device == getDeviceForStream(mStreamType));
            final int numStreamTypes = AudioSystem.getNumStreamTypes();
            for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
                final VolumeStreamState aliasStreamState = mStreamStates[streamType];
                if (streamType != mStreamType &&
                        mStreamVolumeAlias[streamType] == mStreamType &&
                        (changed || !aliasStreamState.hasIndexForDevice(device))) {
                    final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
                    aliasStreamState.setIndex(scaledIndex, device, caller);
                    if (currentDevice) {
                        aliasStreamState.setIndex(scaledIndex,
                                getDeviceForStream(streamType), caller);
                    }
                }
            }
        }
        if (changed) {
            // 因为存储的指数的大小是正常的10倍,为了得到的指数是10的倍数,这是进行了四舍五入的转化
            oldIndex = (oldIndex + 5) / 10;
            index = (index + 5) / 10;
            // log base stream changes to the event log
            if (mStreamVolumeAlias[mStreamType] == mStreamType) {
                if (caller == null) {
                    Log.w(TAG, "No caller for volume_changed event", new Throwable());
                }
                EventLogTags.writeVolumeChanged(mStreamType, oldIndex, index, mIndexMax / 10,
                        caller);
            }
            // fire changed intents for all streams
            // 发送广播 通知音量已经更改
            mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, index);
            mVolumeChanged.putExtra(AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, oldIndex);
            mVolumeChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE_ALIAS,
                    mStreamVolumeAlias[mStreamType]);
            sendBroadcastToAll(mVolumeChanged);
        }
        return changed;
    }

好了,音量键的处理暂时就写到这里吧,这篇文章主要就是讲了AudioService对音量键的一些处理。

以上如有错误欢迎指正

参考了该内容一部分https://www.jianshu.com/p/a48fc2c830da

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342