Android平台音量调节(一)音量键调节音量

Android平台音量调节

本文基于Android 8.0讲述Android平台原生音量控制功能。

流的定义

Android中,音量都是分开控制,各种流定义各种流的音量。在Android8.0中,定义了11种流类型。对每种流类型都定义了最大音量(MAX_STREAM_VOLUME),默认音量(DEFAULT_STREAM_VOLUME)和最小音量(MIN_STREAM_VOLUME)。一个流也可以用另外一个流的音量设置,所以需要一个别名映射。

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

    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

    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
        11, // STREAM_MUSIC
        6,  // STREAM_ALARM
        5,  // STREAM_NOTIFICATION
        7,  // STREAM_BLUETOOTH_SCO
        7,  // STREAM_SYSTEM_ENFORCED
        11, // STREAM_DTMF
        11, // STREAM_TTS
        11, // STREAM_ACCESSIBILITY
    };
  • 别名映射
    Android中各种设备的映射不尽相同,Android中定义了3中设备DEFAULT,VOICE,TELEVISION对应的别名映射。(STREAM_VOLUME_ALIAS_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
    };

STREAM_VOLUME_ALIAS_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
    };

STREAM_VOLUME_ALIAS_DEFAULT其实和Voice映射是一样的,对应其他的设备,比如平板等。

    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
    };

映射关系如下:

流代号 流类型 默认音量 最大音量 最小音量 DEFAULT/Voice映射 TELEVISION映射
0 STREAM_VOICE_CALL 4 5 1 STREAM_VOICE_CALL STREAM_MUSIC
1 STREAM_SYSTEM 5 7 0 STREAM_RING STREAM_MUSIC
2 STREAM_RING 5 7 0 STREAM_RING STREAM_MUSIC
5 STREAM_NOTIFICATION 5 7 0 STREAM_RING STREAM_MUSIC
7 STREAM_SYSTEM_ENFORCED 7 7 0 STREAM_RING STREAM_MUSIC
8 STREAM_DTMF 11 15 0 STREAM_RING STREAM_MUSIC
3 STREAM_MUSIC 11 15 0 STREAM_MUSIC STREAM_MUSIC
9 STREAM_TTS 11 15 0 STREAM_MUSIC STREAM_MUSIC
10 STREAM_ACCESSIBILITY 11 15 0 STREAM_MUSIC STREAM_MUSIC
4 STREAM_ALARM 6 7 0 STREAM_ALARM STREAM_MUSIC
6 STREAM_BLUETOOTH_SCO 7 15 0 STREAM_BLUETOOTH_SCO STREAM_MUSIC

所以在手机设备中,从上表来看,我们能调节的其实就5个音量。当你想调节STREAM_SYSTEM,STREAM_NOTIFICATION等流类型的音量时,实际上是调节了STREAM_RING的音量。当前可控的流类型可以通过下表更直观地显示:

流类型 默认音量 最大音量 意义
STREAM_VOICE_CALL 4 5 通话音量
STREAM_RING 5 7 响铃,通知,系统默认音等
STREAM_MUSIC 11 15 多媒体音量
STREAM_ALARM 6 7 闹钟音量
STREAM_BLUETOOTH_SCO 7 15 蓝牙音量

音量键的处理流程

音量键是常用的调节音量的方式,调节音量时,是调节音量节,比如媒体音STREAM_MUSIC的最大音量为15,其实是15节,你也可以理解为16节,因为还有0.

音量键的处理

Android设置了3个音量处理键,音量加,音量减和静音。3个音量键通过PhoneWindowManager进行处理。

* 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;
        }
    }

音量的加减都是一节一节的调节的,比如最大音量为 7,表示有7节,加上0,就是8节。

3音量按键对应如下:

按键 AudioService 对应的操作 实际意义
KEYCODE_VOLUME_UP ADJUST_RAISE 音量加
KEYCODE_VOLUME_DOWN ADJUST_LOWER 音量减
KEYCODE_VOLUME_MUTE ADJUST_TOGGLE_MUTE 静音按钮

ADJUST_TOGGLE_MUTE是静音按钮,如果现在是静音的,那么将切为非静音;如果是非静音的,那么将设置为静音。相关的还有ADJUST_MUTE和ADJUST_UNMUTE。

此外,AudioService相关的还有ADJUST_SAME,这个只是弹出调节音量的框,不去修改音量。

adjustSuggestedStreamVolume通过Binder调到AudioService中。

* 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调同名函数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;
            ... ...
        }

        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;
        }

        if (mVolumeController.suppressAdjustment(resolvedStream, flags, isMute)) {
            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 表示强制控制某一种流类型,这里应该很少走
  • getActiveStreamType获取要控制的流类型,从Binder那边传过来的 值为USE_DEFAULT_STREAM_TYPE,getActiveStreamType大概流程如下:
![获取Stream的类型](https://www.icode9.com/i/?i=20180420195631415?)
  • 获取映射别名 resolvedStream
  • 对于通知音或铃声STREAM_RING,先显示调节音量的对话框,这个在AudioService的内部类VolumeController中处理。
  • 最后,通过adjustStreamVolume函数设置音量

adjustStreamVolume函数

adjustStreamVolume函数比较长,我们分段来看

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

    private void adjustStreamVolume(int streamType, int direction, int flags,
            String callingPackage, String caller, int uid) {
        ... ...

        ensureValidDirection(direction);
        ensureValidStreamType(streamType);

        boolean isMuteAdjust = isMuteAdjust(direction);

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

        int streamTypeAlias = mStreamVolumeAlias[streamType];

        VolumeStreamState streamState = mStreamStates[streamTypeAlias];
  • 获取需要设置音量类型的映射别名streamTypeAlias
  • 获取音量类型对应的音量信息状态 streamState,VolumeStreamState 音量控制的核心,映射后的每种音量都有各自的VolumeStreamState,它保存了一个流类型所有的音量信息。
* frameworks/base/services/core/java/com/android/server/audio/AudioService.java

    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;
  • 获取流类型对应的设备
    流各自的VolumeStreamState通过observeDevicesForStream_syncVSS去获取对应的device,observeDevicesForStream_syncVSS的获取到的是一个device列表,最终是通过AudioSystem去native获取的。

再从获取的device列表中进行二次选择device:

    private int getDeviceForStream(int stream) {
        int device = getDevicesForStream(stream);
        if ((device & (device - 1)) != 0) {
            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;
    }

继续看adjustStreamVolume,这部分我们以注释的形式加在代码中

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

    private void adjustStreamVolume(int streamType, int direction, int flags,
            String callingPackage, String caller, int uid) {
        ... ...
        // 绝对音量控制
        if ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) == 0 &&
            (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) != 0) {
            return;
        }

        // user处理
        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;
        }

        // 清掉待处理的音量处理命令
        synchronized (mSafeMediaVolumeState) {
            mPendingVolumeCommand = null;
        }

        // 处理是否混音,获取step
        flags &= ~AudioManager.FLAG_FIXED_VOLUME;
        if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) &&
               ((device & mFixedVolumeDevices) != 0)) {
            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 = mSafeMediaVolumeIndex;
            } 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 (((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;
            }
        }
        // 如果不设置音量adjustVolume
        if (!volumeAdjustmentAllowedByDnd(streamTypeAlias, flags)) {
            adjustVolume = false;
        }
        int oldIndex = mStreamStates[streamType].getIndex(device);

        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))) {
                            mStreamStates[stream].mute(state);
                        }
                    }
                }
            } else if ((direction == AudioManager.ADJUST_RAISE) &&
                    !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
                //安全模式,提示音量过高
                mVolumeController.postDisplaySafeVolumeWarning(flags);
            } else if (streamState.adjustIndex(direction * step, device, caller)
                    || streamState.mIsMuted) {
                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);
            }

            // 是否需要设置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);
    }

在上面这段代码中,需要注意下面几个关键的流程:

  • 计算step
    如果不是音乐类型,且固定音量,那么音量调节将转换,转换的算法为:
    private int rescaleIndex(int index, int srcStream, int dstStream) {
        return (index * mStreamStates[dstStream].getMaxIndex() + mStreamStates[srcStream].getMaxIndex() / 2) / mStreamStates[srcStream].getMaxIndex();
    }

index为10,所以STREAM_NOTIFICATION的step为10,STREAM_DTMF的step为(10x15 + 15/2) / 15 = 5

  • 处理RingMode
    Android定义了如下的模式:
    public static final int RINGER_MODE_SILENT = 0;
    public static final int RINGER_MODE_VIBRATE = 1;
    public static final int RINGER_MODE_NORMAL = 2;
    public static final int RINGER_MODE_MAX = RINGER_MODE_NORMAL;
  • 设置音量
    首先处理静音,处理安全音量,最后再处理音量。

音量首先保存在每个流类型的VolumeStreamState中:

        public boolean adjustIndex(int deltaIndex, int device, String caller) {
            return setIndex(getIndex(device) + deltaIndex, device, caller);
        }

在通过 MSG_SET_DEVICE_VOLUME 消息设置到底层。AudioHandler通过setDeviceVolume处理MSG_SET_DEVICE_VOLUME消息。最后是通过AudioSystem的native接口设置到native。

  • 通知音量更改
    最后,通过sendVolumeUpdate广播整个系统,通知整个系统音量修改。

看完代码,我们来看看Java层的时序吧~

![AudioService设置按键音量的过程](https://www.icode9.com/i/?i=20180420200124304?)
上一篇:博客园markdown语法扩展


下一篇:QQ音乐的爬取