android音频系统(4):AudioService之音量管理

前言:AudioService这个系统服务包含或者使用了几乎所有与音频有关的内容,AudioService是音频系统在java层的大本营;

android音频系统,分为两个部分:数据流和策略;

数据流:描述了音频数据从数据源流向目的地的流程,之前我们分析的AudioTrack,AudioFlinger就是数据流;

策略:管理及控制数据流的路径与呈现方式,之前我们分析的AudioPolicyService,以及等会我们要分析的AudioService,它们都是策略得范畴;

 

AudioService:

①音频系统在java层中基本上不参与数据流的,AudioService这个系统服务包含或者使用了几乎所有与音频有关的内容,所以说AudioService是音频系统在java层的大本营;

②AudioManager拥有AudioService的Bp端,是AudioService在客户端的一个代理,几乎所有客户端对AudioManager进行的请求,最终都会交由AudioService实现;

③AudioService的功能实现依赖于AudioSystem类,AudioSystem无法实例化,它是java层到native层的代理,AudioService通过它与AudioPolicyService以及AudioFlinger进行通信;

 

今天我们分析的是,AudioService中的音量管理;

调整音量有好几种方式,我们慢慢看常见的两种:

1.音量管理:按下音量键

音量键被按下后,按键事件会一路派发给Acitivity,如果无人拦截并处理,承载当前Activity的显示PhoneWindow类的onKeyDown()以及onKeyUp()函数将会被处理,从而开始通过音量键调整音量的处理流程;

按照输入事件的派发策略,Window对象在事件的派发队列中位于Acitivity的后面,所以应用程序可以重写自己的Activity.onKeyDown()函数以截获音量键的消息,并将其用作其他的功能。比如说,在一个相机应用中,按下音量键所执行的动作是拍照而不是调节音量;

PhoneWindow.java
protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) {
    final KeyEvent.DispatcherState dispatcher =
            mDecor != null ? mDecor.getKeyDispatcherState() : null;

    switch (keyCode) {
        case KeyEvent.KEYCODE_VOLUME_UP:
        case KeyEvent.KEYCODE_VOLUME_DOWN:
        case KeyEvent.KEYCODE_VOLUME_MUTE: {
            //MediaController与视频播放器有关,使用VideoView+MediaController可实现视频播放器
            if (mMediaController != null) {
                int direction = 0;
                switch (keyCode) {
                    case KeyEvent.KEYCODE_VOLUME_UP:
                        direction = AudioManager.ADJUST_RAISE;
                        break;
                    case KeyEvent.KEYCODE_VOLUME_DOWN:
                        direction = AudioManager.ADJUST_LOWER;
                        break;
                    case KeyEvent.KEYCODE_VOLUME_MUTE:
                        direction = AudioManager.ADJUST_TOGGLE_MUTE;
                        break;
                }
                mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
            } else {
                //我们分析的是这句代码,后面有关于mVolumeControlStreamType的解析
                MediaSessionLegacyHelper.getHelper(getContext()).sendVolumeKeyEvent(
                        event, mVolumeControlStreamType, false);
            }
            return true;
        }
}

点评:将音量键输入按键事件传递给视频播放器或者MediaSessionLegacyHelper,我们这里分析的是MediaSessionLegacyHelper;

继续看代码:

public void sendVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly) {
    if (keyEvent == null) {
        return;
    }
    mSessionManager.dispatchVolumeKeyEvent(keyEvent, stream, musicOnly);
}

关于这个mSessionManager:mSessionManager = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);

我们来到MediaSessionManager.java,继续看:

MediaSessionManager.java
public void dispatchVolumeKeyEvent(@NonNull KeyEvent keyEvent, int stream, boolean musicOnly) {
    try {
        mService.dispatchVolumeKeyEvent(keyEvent, stream, musicOnly);
    } catch (RemoteException e) {
        Log.e(TAG, "Failed to send volume key event.", e);
    }
}

这个mService是啥?

ISessionManager mService;
IBinder b = ServiceManager.getService(Context.MEDIA_SESSION_SERVICE);
mService = ISessionManager.Stub.asInterface(b);

通过分析我们知道mService是MediaSessionService;

继续看看MediaSessionService.dispatchVolumeKeyEvent()方法:

MediaSessionService.java
public void dispatchVolumeKeyEvent(KeyEvent keyEvent, int stream, boolean musicOnly) {
    /*这个方法是用来分析并处理音量键的长按和短按操作,如果有音量键的长按监听器,那么在音量键长按后,那么当前操作就不是调整音量,而是将事件派发给音量键的长按监听器*/
    if (keyEvent == null ||
            (keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_UP
                     && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_DOWN
                     && keyEvent.getKeyCode() != KeyEvent.KEYCODE_VOLUME_MUTE)) {
        Log.w(TAG, "Attempted to dispatch null or non-volume key event.");
        return;
    }

    final int pid = Binder.getCallingPid();
    final int uid = Binder.getCallingUid();
    final long token = Binder.clearCallingIdentity();

    if (DEBUG_KEY_EVENT) {
        Log.d(TAG, "dispatchVolumeKeyEvent, pid=" + pid + ", uid=" + uid + ", event="
                + keyEvent);
    }

    try {
        synchronized (mLock) {
            if (isGlobalPriorityActiveLocked()
                    || mCurrentFullUserRecord.mOnVolumeKeyLongPressListener == null) {
                //有活动的全局优先级会话或者没有音量键的长按监听,那就处理音量键的短按事件
                dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);
            } else {
                // TODO: Consider the case when both volume up and down keys are pressed
                //       at the same time.
                if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
                    //第一次短按,在一段时间的延迟之后如果手指没有抬起,那就触发长按事件
                    if (keyEvent.getRepeatCount() == 0) {
                        //保存传递进来的参数副本
                        mCurrentFullUserRecord.mInitialDownVolumeKeyEvent =
                                KeyEvent.obtain(keyEvent);
                        mCurrentFullUserRecord.mInitialDownVolumeStream = stream;
                        mCurrentFullUserRecord.mInitialDownMusicOnly = musicOnly;
                        //一段延迟后触发长按事件,也就是触发onVolumeKeyLongPress方法,我们这里只分析短按事件,这个就不仔细剖析了
                        mHandler.sendMessageDelayed(
                                mHandler.obtainMessage(
                                        MessageHandler.MSG_VOLUME_INITIAL_DOWN,
                                        mCurrentFullUserRecord.mFullUserId, 0),
                                mLongPressTimeout);
                    }
                    //多次短按,或者本身就是一个长按事件时,直接触发长按事件
                    if (keyEvent.getRepeatCount() > 0 || keyEvent.isLongPress()) {
                        mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
                        if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null) {
                            dispatchVolumeKeyLongPressLocked(
                                    mCurrentFullUserRecord.mInitialDownVolumeKeyEvent);
                            // Mark that the key is already handled.
                            mCurrentFullUserRecord.mInitialDownVolumeKeyEvent = null;
                        }
                        dispatchVolumeKeyLongPressLocked(keyEvent);
                    }
                } else { // if up
                    mHandler.removeMessages(MessageHandler.MSG_VOLUME_INITIAL_DOWN);
                    if (mCurrentFullUserRecord.mInitialDownVolumeKeyEvent != null
                            && mCurrentFullUserRecord.mInitialDownVolumeKeyEvent
                                    .getDownTime() == keyEvent.getDownTime()) {
                        // Short-press. Should change volume.
                        dispatchVolumeKeyEventLocked(
                                mCurrentFullUserRecord.mInitialDownVolumeKeyEvent,
                                mCurrentFullUserRecord.mInitialDownVolumeStream,
                                mCurrentFullUserRecord.mInitialDownMusicOnly);
                        dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);
                    } else {
                        dispatchVolumeKeyLongPressLocked(keyEvent);
                    }
                }
            }
        }
    } finally {
        Binder.restoreCallingIdentity(token);
    }
}

点评:这段代码是用来处理音量键的长按与短按的,我们这里只分析短按事件,继续看:

MediaSessionService.java
dispatchVolumeKeyEventLocked(keyEvent, stream, musicOnly);
private void dispatchVolumeKeyEventLocked(
        KeyEvent keyEvent, int stream, boolean musicOnly) {
    //注意我们传进来的参数,keyEvent为down事件,stream是音频类型,musicOnly为false
    boolean down = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
    boolean up = keyEvent.getAction() == KeyEvent.ACTION_UP;
    int direction = 0;
    boolean isMute = false;
    //音量调整的方向
    switch (keyEvent.getKeyCode()) {
        case KeyEvent.KEYCODE_VOLUME_UP:
            direction = AudioManager.ADJUST_RAISE;
            break;
        case KeyEvent.KEYCODE_VOLUME_DOWN:
            //direction为音量调整的方向,1为增大,-1为减小
            direction = AudioManager.ADJUST_LOWER;
            break;
        case KeyEvent.KEYCODE_VOLUME_MUTE:
            isMute = true;
            break;
    }
    if (down || up) {
        //设置标签
        int flags = AudioManager.FLAG_FROM_KEY;
        if (musicOnly) {
            // This flag is used when the screen is off to only affect active media.
            flags |= AudioManager.FLAG_ACTIVE_MEDIA_ONLY;
        } else {
            // These flags are consistent with the home screen
            if (up) {
                //按下音量键后手指抬起,PLAY_SOUND,即播放按键音并且震动
                flags |= AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE;
            } else {
                //手指刚按下音量键,SHOW_UI,即显示UI并且震动,这个UI指的是音量调节框,后面来看到
                flags |= AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE;
            }
        }
        if (direction != 0) {
            //因为down事件已经处理了音量的调整,up事件不需要再做什么,播放个按键音声音就好
            if (up) {
                direction = 0;
            }
            //要开始调节音量了
            dispatchAdjustVolumeLocked(stream, direction, flags);
        } else if (isMute) {
            //按下了静音键
            if (down && keyEvent.getRepeatCount() == 0) {
                dispatchAdjustVolumeLocked(stream, AudioManager.ADJUST_TOGGLE_MUTE, flags);
            }
        }
    }
}

点评:这段代码的主要工作是,确定音量调整的方向以及设置按下音量键时所携带的动作;

重点来了:dispatchAdjustVolumeLocked();

MediaSessionService.java
private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags) {
    MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
            : mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();

    boolean preferSuggestedStream = false;
    if (isValidLocalStreamType(suggestedStream)
            && AudioSystem.isStreamActive(suggestedStream, 0)) {
        preferSuggestedStream = true;
    }
    if (DEBUG_KEY_EVENT) {
        Log.d(TAG, "Adjusting " + session + " by " + direction + ". flags="
                + flags + ", suggestedStream=" + suggestedStream
                + ", preferSuggestedStream=" + preferSuggestedStream);
    }
    if (session == null || preferSuggestedStream) {
        if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0
                && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
            if (DEBUG) {
                Log.d(TAG, "No active session to adjust, skipping media only volume event");
            }
            return;
        }

        // Execute mAudioService.adjustSuggestedStreamVolume() on
        // handler thread of MediaSessionService.
        // This will release the MediaSessionService.mLock sooner and avoid
        // a potential deadlock between MediaSessionService.mLock and
        // ActivityManagerService lock.
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    String packageName = getContext().getOpPackageName();
                    //重点来了,调用AudioService的adjustSuggestedStreamVolume()方法
                    mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
                            flags, packageName, TAG);
                } catch (RemoteException e) {
                    Log.e(TAG, "Error adjusting default volume.", e);
                } catch (IllegalArgumentException e) {
                    Log.e(TAG, "Cannot adjust volume: direction=" + direction
                            + ", suggestedStream=" + suggestedStream + ", flags=" + flags,
                            e);
                }
            }
        });
    } else {
        session.adjustVolume(direction, flags, getContext().getPackageName(),
                Process.SYSTEM_UID, true);
    }
}

点评:上面的代码只有一个核心:

mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,flags, packageName, TAG);

分析下方法中参数的含义:

direction:表示了音量的调整方向,1位增大,-1为减小,0就是保持原音量;

suggestedStream:操作的音频类型,后面会分析到;

flags:音量键动作所携带的消息,比如播放按键音,显示音量调节框等;

packageName:应用包名;

 

mAudioService就是AudioService,来到AudioService.java,继续往下看:

AudioService.java
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
        String callingPackage, String caller, int uid) {
    final int streamType;
    //从这段代码可以看出,在AudioService中还有地方可以强行改变音量键控制的流类型,
    //后面有关于mUserSelectedVolumeControlStream的解析
    if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1
        streamType = mVolumeControlStream;
    } else {
        //通过getActiveStreamType函数获取要控制的流类型,这里根据建议的流类型与AudioService的实际情况,返回一个值
        final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType);
        final boolean activeForReal;
        if (maybeActiveStreamType == AudioSystem.STREAM_MUSIC) {
            activeForReal = isAfMusicActiveRecently(0);
        } else {
            activeForReal = AudioSystem.isStreamActive(maybeActiveStreamType, 0);
        }
        if (activeForReal || mVolumeControlStream == -1) {
            streamType = maybeActiveStreamType;
        } else {
            streamType = mVolumeControlStream;
        }
    }

    final boolean isMute = isMuteAdjust(direction);

    ensureValidStreamType(streamType);

    //这句代码等会过来分析
    final int resolvedStream = mStreamVolumeAlias[streamType];

    //只能在STREAM_RING音频类型下才能播放声音
    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
    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()
    adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid);
}

点评:我们先把流程走完,有些细节后面一起分析;

继续看adjustStreamVolume()方法:

AudioService.java
private void adjustStreamVolume(int streamType, int direction, int flags,
        String callingPackage, String caller, int uid) {
    if (mUseFixedVolume) {
        return;
    }
    if (DEBUG_VOL) Log.d(TAG, "adjustStreamVolume() stream=" + streamType + ", dir=" + direction
            + ", flags=" + flags + ", caller=" + caller);

    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)
    //获取streamType映射到的流类型
    int streamTypeAlias = mStreamVolumeAlias[streamType];

    //注意VolumeStreamState类
    VolumeStreamState streamState = mStreamStates[streamTypeAlias];

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

    // reset any pending volume command
    synchronized (mSafeMediaVolumeState) {
        mPendingVolumeCommand = null;
    }

    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 = 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
        //由于不同的流类型的音量调节范围不同,rescaleIndex用于将音量值的变化量从源流类型变换到目标流类型下
        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表示是否有必要继续设置音量值,因为在某些情况下,音量键是改变情景模式,而不是设置音量的,比如音量为0时,继续减小音量,此时就是改变情景模式为静音
        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;
    }

    //取出调整前的音量值,这个值稍后被用在sendVolumeUpdate()方法中
    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)) {
            Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex);
            mVolumeController.postDisplaySafeVolumeWarning(flags);
        } else if (streamState.adjustIndex(direction * step, device, caller)
                || streamState.mIsMuted) {
            //上面的判断中调用了streamState.adjustIndex()方法,表示如果音量值在调整之后没有发生变化,比如说达到了最大值,就不需要继续后面的操作了
            // 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);
                    }
                }
            }
            //发送信息给mAudioHandler,这个消息将把音量设置到底层去,并将其存储到SettingsProvider中,后面的分析会看到
            sendMsg(mAudioHandler,
                    MSG_SET_DEVICE_VOLUME,
                    SENDMSG_QUEUE,
                    device,
                    0,
                    streamState,
                    0);
        }

        // Check if volume update should be sent to Hdmi system audio.
        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,通知外界音量值发生了变化
    sendVolumeUpdate(streamType, oldIndex, index, flags);
}

点评:上述代码的逻辑很多,我们逐一分析:

①在这个函数中,我们首先看到了这两句代码:

int streamTypeAlias = mStreamVolumeAlias[streamType];

VolumeStreamState streamState = mStreamStates[streamTypeAlias];

VolumeStreamState是什么?

我们知道,android的音量是依赖于某种流类型的,如果Android定义了N个流类型,AudioService就需要维护N个音量值与之相对应,所以AudioService提供了VolumeStreamState,为每一种流类型都分配了一个VolumeStreamState对象,VolumeStreamState保存与一个流类型所有音量相关的信息。并且以流类型的的值为索引,将它保存在一个名为mStreamStates的数组中;

②streamState.adjustIndex

streamState也就是VolumeStreamState,VolumeStreamState.adjustIndex()就是改变这个对象存储的音量值,不过它仅仅是改变了它的存储值,并没有把这个变化设置到底层;

我们来看下这个方法:

public boolean adjustIndex(int deltaIndex, int device, String caller) {
    //将现有的音量值加上变化量,然后调用setIndex进行设置
    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;
            }
        }
        //保存设置的音量值,使用了Map
        mIndexMap.put(device, index);

        changed = oldIndex != index;
        //同时设置所有映射到当前流类型的其他流的音量
        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) {
        //加5是为了四舍五入,除10,因为10的整数容易操作
        oldIndex = (oldIndex + 5) / 10;
        index = (index + 5) / 10;
        //发送广播
        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;
}

点评:这个函数有如下工作:

(1)首先是保存设置的音量值,音量值与设备相关联,对同一种流类型来说,在不同的音频设备下将会有不同的音量值;

(2)再就是对流映射的处理。既然A→B,那么在设置B的音量的同时要改变A的音量;

可以看出,VolumeStreamState.adjustIndex()除了更新自己所保存的音量值外,没有做其他的事情;

 

再来看sendMsg(),发送MSG_SET_DEVICE_VOLUME消息;

这个消息是发送给mAudioHandler,mAudioHandler是运行在AudioService主线程上的Handler,我们看下mAudioHandler处理这个消息的setDeviceVolume函数:

private void setDeviceVolume(VolumeStreamState streamState, int device) {

    synchronized (VolumeStreamState.class) {
        //这个函数就是调用AudioSystem.setStreamVolumeIndex,将音量设置到底层的AudioFlinger
        streamState.applyDeviceVolume_syncVSS(device);

        // Apply change to all streams using this one as alias
        //处理流映射的情况
        int numStreamTypes = AudioSystem.getNumStreamTypes();
        for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
            if (streamType != streamState.mStreamType &&
                    mStreamVolumeAlias[streamType] == streamState.mStreamType) {
                int streamDevice = getDeviceForStream(streamType);
                if ((device != streamDevice) && mAvrcpAbsVolSupported &&
                        ((device & AudioSystem.DEVICE_OUT_ALL_A2DP) != 0)) {
                    mStreamStates[streamType].applyDeviceVolume_syncVSS(device);
                }
                mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
            }
        }
    }
    /*发送消息给mAudioHandler,其处理函数将会调用persitVolume()函数,这将会把音量的设置信息存储到SettingsProvider中;
    AudioService在初始化时,将会从SettingsProvider中将音量设置读取出来并进行设置 */
    sendMsg(mAudioHandler,
            MSG_PERSIST_VOLUME,
            SENDMSG_QUEUE,
            device,
            0,
            streamState,
            PERSIST_DELAY);
}

再看下mAudioHandler处理MSG_PERSIST_VOLUME消息的persistVolume函数:

private void persistVolume(VolumeStreamState streamState, int device) {
    if (mUseFixedVolume) {
        return;
    }
    if (mIsSingleVolume && (streamState.mStreamType != AudioSystem.STREAM_MUSIC)) {
        return;
    }
    if (streamState.hasValidSettingsName()) {
        //将音量值写进SettingProvider
        System.putIntForUser(mContentResolver,
                streamState.getSettingNameForDevice(device),
                (streamState.getIndex(device) + 5)/ 10,
                UserHandle.USER_CURRENT);
    }
}

点评:sendMsg是个异步操作,消息是串行的,由于是先返回值会操作,就意味着执行结果可能会有延迟,即获取的音量值没问题,但是手机发出的声音音量值大小并没有变化;

 

③sendVolumeUpdate()分析

sendVolumeUpdate()的工作内容,就是通知外界音量值发生了变化,其中有一项就是通知音量调节通知框,因为6.0以后的代码逻辑改变比较大,我们后面再分析这个;

 

这样,音量调节的流程就结束了,接下来做个总结:

①音量键处理流程的发起者是PhoneWindow;

②MediaSessionService仅仅起到代理的作用;

③AudioService接受MediaSessionService的调用请求,操作VolumeStreamState的实例进行音量的设置;

④VolumeStreamState负责保存音量设置,并且提供了将音量设置到底层的方法;

⑤AudioService负责将设置结果以广播的形式通知外界;

 

OK!音量调节的流程说完了,接下来要说遗留的细节代码了!

①在PhoneWindow.java的onKeyDown方法中,有这么一句代码:

MediaSessionLegacyHelper.getHelper(getContext()).sendVolumeKeyEvent(event, mVolumeControlStreamType, false);

这里的mVolumeControlStreamType是什么?

mVolumeControlStreamType:它指的是要改变哪一种流类型的音量,adnroid的音量控制与流密不可分,每种流类型都独立的拥有自己的音量设置,如音乐音量,通话音量就是相互独立的。mVolumeControlStreamType它是从哪来的呢?

在Activity中有这么一个函数,通过它可以来指定显示这个Activity时音量键所控制的流类型;

Activity.java

Activity.java
public final void setVolumeControlStream(int streamType) {
    getWindow().setVolumeControlStream(streamType);
}

getWindow()就是用于显示当前Activity的PhoneWindow,再回到PhoneWindow.setVolumeControlStream()方法:

PhoneWindow.java
public void setVolumeControlStream(int streamType) {
    mVolumeControlStreamType = streamType;
}

这就是mVolumeControlStreamType的由来;

我们应该能看出,这个设置被绑定到Activity的Window上,在不同Activity之间切换时,接收按键事件的Window也会随之切换,所以应用不需要去考虑在其生命周期中音量键所控制的流类型的切换问题;

②在AudioService.adjustStreamVolume()中有这么两句代码:

int streamTypeAlias = mStreamVolumeAlias[streamType];
VolumeStreamState streamState = mStreamStates[streamTypeAlias];

我们先看mStreamVolumeAlias是什么?

找到mStreamVolumeAlias被赋值的其中一处:

mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT;
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
};

看到这里我们知道了,mStreamVolumeAlias是一个数组,数组里面依次保存着音频流类型;所以resolvedStream也只不过是从mStreamVolumeAlias数组中获取到相应的流类型而已;

mStreamStates也是用来保存VolumeStreamState的数组,streamState是从数组中获取到相应的VolumeStreamState;

初看:这种写法不是多次一举吗,直接VolumeStreamState streamState = mStreamStates[streamType]不就可以了吗?

其实不然:

这种设计是为了满足为了所谓的“将铃声音量用作音乐音量”这种需求,如果碰到这个需求,我们就可以这么处理:

mStreamVolumeAlias[AudioSystem.STREAM_MUSIC] = AudioSystem.STREAM_RING;

当我们在操作音乐类型的流时,实际上是在操作铃声类型的流;

这种设计是不是很棒,是不是很nice,其实HaspMap也可以做嘛,键是源流类型,值是目标流类型;

当真的有“将铃声音量用作音乐音量”这种奇葩需求时:

我们需要根据实际情况给mStreamVolumeAlias数组赋值,AudioService也给它提供了几种在不同情形下的不同赋值,它的赋值跟硬件平台有关;

 

③在AudioService.adjustSuggestedStreamVolume()中有这么一句:

if (mUserSelectedVolumeControlStream) { // implies mVolumeControlStream != -1
	streamType = mVolumeControlStream;
}

mUserSelectedVolumeControlStream是什么?只要它等于true,就强制调整音量的类型就是它!

看看它是在哪被赋值的?

public void forceVolumeControlStream(int streamType, IBinder cb) {
    if (DEBUG_VOL) { Log.d(TAG, String.format("forceVolumeControlStream(%d)", streamType)); }
    synchronized(mForceControlStreamLock) {
        //mVolumeControlStream != -1,便将mUserSelectedVolumeControlStream设为true
        if (mVolumeControlStream != -1 && streamType != -1) {
            mUserSelectedVolumeControlStream = true;
        }
        //给mVolumeControlStream 赋值
        mVolumeControlStream = streamType;
        if (mVolumeControlStream == -1) {
            if (mForceControlStreamClient != null) {
                mForceControlStreamClient.release();
                mForceControlStreamClient = null;
            }
            mUserSelectedVolumeControlStream = false;
        } else {
            if (mForceControlStreamClient != null) {
                mForceControlStreamClient.release();
            }
            mForceControlStreamClient = new ForceControlStreamClient(cb);
        }
    }
}

我们可以看到,mUserSelectedVolumeControlStream以及mVolumeControlStream 是在forceVolumeControlStream()方法中被赋值的,而forceVolumeControlStream()方法是由VolumePanel调用的,VolumePanel就是我们按下音量键后的那个音量调节通知框,如果我们在VolumePanel显示时改变的是铃声类型的音量,那么它会调用forceVolumeControlStream强制后续的音量键操作固定为促使它显示的那个流类型,也就是铃声类型,并在它关闭时取消这个强制设置,即设置mVolumeControlStream为-1,这个在后面分析VolumePanel时会看到;

 

2.音量管理:setStreamVolume()

还有一种直接调整音量的接口:AudioManager.setStreamVolume();

AudioManager.java
public void setStreamVolume(int streamType, int index, int flags) {
    final IAudioService service = getService();
    Log.d(TAG, "setStreamVolume: StreamType = " + streamType + ", index = " + index);
    try {
        service.setStreamVolume(streamType, index, flags, getContext().getOpPackageName());
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

我们知道,service就是AudioService,继续:;

AudioService.java
public void setStreamVolume(int streamType, int index, int flags, String callingPackage) {
    if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {
        Log.w(TAG, "Trying to call setStreamVolume() for a11y without"
                + " CHANGE_ACCESSIBILITY_VOLUME  callingPackage=" + callingPackage);
        return;
    }
    mVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
            index/*val1*/, flags/*val2*/, callingPackage));
    setStreamVolume(streamType, index, flags, callingPackage, callingPackage,
            Binder.getCallingUid());
}

private void setStreamVolume(int streamType, int index, int flags, String callingPackage,
        String caller, int uid) {
    if (DEBUG_VOL) {
        Log.d(TAG, "setStreamVolume(stream=" + streamType+", index=" + index
                + ", calling=" + callingPackage + ")");
    }
    if (mUseFixedVolume) {
        return;
    }

    //判读流类型的有效性
    ensureValidStreamType(streamType);
    int streamTypeAlias = mStreamVolumeAlias[streamType];
    //获取相应的VolumeStreamState
    VolumeStreamState streamState = mStreamStates[streamTypeAlias];

    //获取当前流将使用哪一个音频设备进行播放
    final int device = getDeviceForStream(streamType);
    int oldIndex;

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

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

    synchronized (mSafeMediaVolumeState) {
        // reset any pending volume command
        mPendingVolumeCommand = null;

        //获取流当前的音量
        oldIndex = streamState.getIndex(device);

        //将原流类型下的音量值映射到目标流类型下的音量值;
        //因为不同流类型的音量值刻度不一样,所以需要进行转换
        index = rescaleIndex(index * 10, streamType, streamTypeAlias);

        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.setAvrcpAbsoluteVolume(index / 10);
                }
            }
        }

        if (streamTypeAlias == AudioSystem.STREAM_MUSIC) {
            setSystemAudioVolume(oldIndex, index, getStreamMaxVolume(streamType), flags);
        }

        flags &= ~AudioManager.FLAG_FIXED_VOLUME;
        if ((streamTypeAlias == AudioSystem.STREAM_MUSIC) &&
                ((device & mFixedVolumeDevices) != 0)) {
            flags |= AudioManager.FLAG_FIXED_VOLUME;

            // volume is either 0 or max allowed for fixed volume devices
            if (index != 0) {
                if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&
                        (device & mSafeMediaVolumeDevices) != 0) {
                    index = safeMediaVolumeIndex(device);
                } else {
                    index = streamState.getMaxIndex();
                }
            }
        }

        if (!checkSafeMediaVolume(streamTypeAlias, index, device)) {
            mVolumeController.postDisplaySafeVolumeWarning(flags);
            mPendingVolumeCommand = new StreamVolumeCommand(
                                                streamType, index, flags, device);
        } else {
            //调用onSetStreamVolume
            onSetStreamVolume(streamType, index, flags, device, caller);
            //获取设置的结果
            index = mStreamStates[streamType].getIndex(device);
        }
    }
    //广播通知
    sendVolumeUpdate(streamType, oldIndex, index, flags);
}

我们来看一下:onSetStreamVolume()

private void onSetStreamVolume(int streamType, int index, int flags, int device,
        String caller) {
    final int stream = mStreamVolumeAlias[streamType];
    //调用setStreamVolumeInt
    setStreamVolumeInt(stream, index, device, false, caller);
    // setting volume on ui sounds stream type also controls silent mode
    if (((flags & AudioManager.FLAG_ALLOW_RINGER_MODES) != 0) ||
            (stream == getUiSoundsStreamType())) {
        setRingerMode(getNewRingerMode(stream, index, flags),
                TAG + ".onSetStreamVolume", false /*external*/);
    }
   //如果调节后的音量为0了,那就让某种流的音量静音
    mStreamStates[stream].mute(index == 0);
}

private void setStreamVolumeInt(int streamType,
                                int index,
                                int device,
                                boolean force,
                                String caller) {
    VolumeStreamState streamState = mStreamStates[streamType];

    //调用streamState.setIndex
    if (streamState.setIndex(index, device, caller) || force) {
        //如果setIndex返回true,或者force为true,就在这里向mAudioHandler发送通知
        sendMsg(mAudioHandler,
                MSG_SET_DEVICE_VOLUME,
                SENDMSG_QUEUE,
                device,
                0,
                streamState,
                0);
    }
}

使用setStreamVolume()调整音量,跟点击音量键控制音质,其实也是差不多的,就不再过多的叙述了;

 

3.音量管理的总结

①AudioService音量管理的核心是VolumeStreamState,它保存了一个流类型所有的音量信息;

②VolumeStreamState保存了运行时的音量信息,而音量的生效则是在底层AudioFlinger完成的;

所以进行音量设置需要做两件事情:更新VolumeStreamState存储的音量值,设置音量到Audio底层系统;

你可能感兴趣的:(音频系统)