美文网首页
AudioManager setMode机制

AudioManager setMode机制

作者: android小奉先 | 来源:发表于2022-08-29 18:54 被阅读0次

    本篇介绍

    在开发Android Audio的时候,免不了需要修改音量类型,可是setMode真的可以每次都能生效吗?本篇就从源码层面回答下这个问题。

    setMode实现

    先看代码实现:

       /**
         * Sets the audio mode.
         * <p>
         * The audio mode encompasses audio routing AND the behavior of
         * the telephony layer. Therefore this method should only be used by applications that
         * replace the platform-wide management of audio settings or the main telephony application.
         * In particular, the {@link #MODE_IN_CALL} mode should only be used by the telephony
         * application when it places a phone call, as it will cause signals from the radio layer
         * to feed the platform mixer.
         *
         * @param mode  the requested audio mode.
         *              Informs the HAL about the current audio state so that
         *              it can route the audio appropriately.
         */
        public void setMode(@AudioMode int mode) {
            final IAudioService service = getService();
            try {
                service.setMode(mode, mICallBack, mApplicationContext.getOpPackageName());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    

    正如注释所介绍,setMode会修改音频路由等行为,而且并不是可以随意任何mode,一般外部设置的就是MODE_NORMAL和MODE_IN_COMMUNICATION:

        public static final int MODE_INVALID            = -2;
        /** @hide */
        public static final int MODE_CURRENT            = -1;
        /** @hide */
        public static final int MODE_NORMAL             = 0;
        /** @hide */
        public static final int MODE_RINGTONE           = 1;
        /** @hide */
        public static final int MODE_IN_CALL            = 2;
        /** @hide */
        public static final int MODE_IN_COMMUNICATION   = 3;
        /** @hide */
        public static final int MODE_CALL_SCREENING     = 4;
        /** @hide */
        public static final int MODE_CALL_REDIRECT     = 5;
        /** @hide */
        public static final int MODE_COMMUNICATION_REDIRECT  = 6;
        /** @hide */
        public static final int NUM_MODES               = 7;
    

    接下来看下AudioService的实现:

      public void setMode(int mode, IBinder cb, String callingPackage) {
            int pid = Binder.getCallingPid();
            int uid = Binder.getCallingUid();
            if (DEBUG_MODE) {
                Log.v(TAG, "setMode(mode=" + mode + ", pid=" + pid
                        + ", uid=" + uid + ", caller=" + callingPackage + ")");
            }
            if (!checkAudioSettingsPermission("setMode()")) {.   // 权限检查
                return;
            }
            if (cb == null) {
                Log.e(TAG, "setMode() called with null binder");
                return;
            }
            if (mode < AudioSystem.MODE_CURRENT || mode >= AudioSystem.NUM_MODES) {
                Log.w(TAG, "setMode() invalid mode: " + mode);
                return;
            }
    
            if (mode == AudioSystem.MODE_CURRENT) {
                mode = getMode();
            }
    
            if (mode == AudioSystem.MODE_CALL_SCREENING && !mIsCallScreeningModeSupported) {
                Log.w(TAG, "setMode(MODE_CALL_SCREENING) not permitted "
                        + "when call screening is not supported");
                return;
            }
    
            final boolean hasModifyPhoneStatePermission = mContext.checkCallingOrSelfPermission(
                    android.Manifest.permission.MODIFY_PHONE_STATE)
                    == PackageManager.PERMISSION_GRANTED;
            if ((mode == AudioSystem.MODE_IN_CALL
                    || mode == AudioSystem.MODE_CALL_REDIRECT
                    || mode == AudioSystem.MODE_COMMUNICATION_REDIRECT)
                    && !hasModifyPhoneStatePermission) {
                Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: setMode("
                        + AudioSystem.modeToString(mode) + ") from pid=" + pid
                        + ", uid=" + Binder.getCallingUid());
                return;
            }
    
            SetModeDeathHandler currentModeHandler = null;
            synchronized (mDeviceBroker.mSetModeLock) {
                for (SetModeDeathHandler h : mSetModeDeathHandlers) {  // 关键逻辑
                    if (h.getPid() == pid) {
                        currentModeHandler = h;
                        break;
                    }
                }
    
                if (mode == AudioSystem.MODE_NORMAL) { // 如果是媒体音量,那么就清理currentModeHandler, 也就是设置媒体音量不会成为modeOwner
                    if (currentModeHandler != null) {
                        if (!currentModeHandler.isPrivileged()
                                && currentModeHandler.getMode() == AudioSystem.MODE_IN_COMMUNICATION) {
                            mAudioHandler.removeEqualMessages(
                                    MSG_CHECK_MODE_FOR_UID, currentModeHandler);
                        }
                        mSetModeDeathHandlers.remove(currentModeHandler);  // 通过是设置媒体音量 ,清理mode owner
                        if (DEBUG_MODE) {
                            Log.v(TAG, "setMode(" + mode + ") removing hldr for pid: " + pid);
                        }
                        try {
                            currentModeHandler.getBinder().unlinkToDeath(currentModeHandler, 0);
                        } catch (NoSuchElementException e) {
                            Log.w(TAG, "setMode link does not exist ...");
                        }
                    }
                } else {
                    if (currentModeHandler != null) {
                        currentModeHandler.setMode(mode);  // 设置通话音量会保留mode owner
                        if (DEBUG_MODE) {
                            Log.v(TAG, "setMode(" + mode + ") updating hldr for pid: " + pid);
                        }
                    } else {
                        currentModeHandler = new SetModeDeathHandler(cb, pid, uid,
                                hasModifyPhoneStatePermission, callingPackage, mode);
                        // Register for client death notification
                        try {
                            cb.linkToDeath(currentModeHandler, 0);
                        } catch (RemoteException e) {
                            // Client has died!
                            Log.w(TAG, "setMode() could not link to " + cb + " binder death");
                            return;
                        }
                        mSetModeDeathHandlers.add(currentModeHandler);
                        if (DEBUG_MODE) {
                            Log.v(TAG, "setMode(" + mode + ") adding handler for pid=" + pid);
                        }
                    }
                    if (mode == AudioSystem.MODE_IN_COMMUNICATION) {
                        // Force active state when entering/updating the stack to avoid glitches when
                        // an app starts playing/recording after settng the audio mode,
                        // and send a reminder to check activity after a grace period.
                        if (!currentModeHandler.isPrivileged()) {     // 这儿是为了 保证每次掉用setMode设置通话音量 都可以立马生效,但是 6s 后会检查,如果还是没有启动采集或播放,那么就会回收 通话音量,重置为媒体音量
                            currentModeHandler.setPlaybackActive(true);
                            currentModeHandler.setRecordingActive(true);
                            sendMsg(mAudioHandler,
                                    MSG_CHECK_MODE_FOR_UID,
                                    SENDMSG_QUEUE,
                                    0,
                                    0,
                                    currentModeHandler,
                                    CHECK_MODE_FOR_UID_PERIOD_MS);
                        }
                    }
                }
    
                sendMsg(mAudioHandler,
                        MSG_UPDATE_AUDIO_MODE,
                        SENDMSG_REPLACE,
                        mode,
                        pid,
                        callingPackage,
                        0);
            }
        }
    

    从这块逻辑可以看出以下几点:

    1. 设置媒体音量不会关联 mode owner,因此不保证可以设置成功
    2. 设置通话音量会关联mode owner,这样可以保证设置成功,不过也需要采集或播放才能长久保持住通话音量,6s后会进行检查,如果还是没有采集或播放,那么就会重新设置回媒体音量

    接下来继续看下MSG_UPDATE_AUDIO_MODE 的逻辑:

     void onUpdateAudioMode(int requestedMode, int requesterPid, String requesterPackage,
                               boolean force) {
            if (requestedMode == AudioSystem.MODE_CURRENT) {
                requestedMode = getMode();
            }
            int mode = AudioSystem.MODE_NORMAL;
            int uid = 0;
            int pid = 0;
            SetModeDeathHandler currentModeHandler = getAudioModeOwnerHandler(); // 获取当前的mode owner,也就是每次setMode其实是以mode owner 为准的,并不一定是当前应用
            if (currentModeHandler != null) {
                mode = currentModeHandler.getMode();
                uid = currentModeHandler.getUid();
                pid = currentModeHandler.getPid();
            }
            if (DEBUG_MODE) {
                Log.v(TAG, "onUpdateAudioMode() new mode: " + mode + ", current mode: "
                        + mMode.get() + " requested mode: " + requestedMode);
            }
            if (mode != mMode.get() || force) {
                final long identity = Binder.clearCallingIdentity();
                int status = mAudioSystem.setPhoneState(mode, uid); // 这儿设置下去可以保证一定可以设置成功,会通过audioflinger设置到hal 层。
                Binder.restoreCallingIdentity(identity);
                if (status == AudioSystem.AUDIO_STATUS_OK) {
                    if (DEBUG_MODE) {
                        Log.v(TAG, "onUpdateAudioMode: mode successfully set to " + mode);
                    }
                    sendMsg(mAudioHandler, MSG_DISPATCH_AUDIO_MODE, SENDMSG_REPLACE, mode, 0,
                            /*obj*/ null, /*delay*/ 0);
                    int previousMode = mMode.getAndSet(mode);
                    // Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL
                    mModeLogger.log(new PhoneStateEvent(requesterPackage, requesterPid,
                            requestedMode, pid, mode)); // 记录dumpsys audio 日志
    
                    int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
                    int device = getDeviceForStream(streamType);
                    int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device);
                    setStreamVolumeInt(mStreamVolumeAlias[streamType], index, device, true,
                            requesterPackage, true /*hasModifyAudioSettings*/); //更新音量值
    
                    updateStreamVolumeAlias(true /*updateVolumes*/, requesterPackage);
    
                    // change of mode may require volume to be re-applied on some devices
                    updateAbsVolumeMultiModeDevices(previousMode, mode);
    
                    // Forcefully set LE audio volume as a workaround, since the value of 'device'
                    // is not DEVICE_OUT_BLE_* even when BLE is connected.
                    setLeAudioVolumeOnModeUpdate(mode);
    
                    // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
                    // connections not started by the application changing the mode when pid changes
                    mDeviceBroker.postSetModeOwnerPid(pid, mode);
                } else {
                    Log.w(TAG, "onUpdateAudioMode: failed to set audio mode to: " + mode);
                }
            }
        }
    
    

    这儿关键逻辑就是 查找mode owner,mode owner并不一定是当前掉用setMode的应用,可以看下面逻辑:

        private SetModeDeathHandler getAudioModeOwnerHandler() {
            // The Audio mode owner is:
            // 1) the most recent privileged app in the stack
            // 2) the most recent active app in the tack
            SetModeDeathHandler modeOwner = null;
            SetModeDeathHandler privilegedModeOwner = null;
            for (SetModeDeathHandler h : mSetModeDeathHandlers) {
                if (h.isActive()) {
                    // privileged apps are always active
                    if (h.isPrivileged()) {
                        if (privilegedModeOwner == null
                                || h.getUpdateTime() > privilegedModeOwner.getUpdateTime()) {
                            privilegedModeOwner = h;
                        }
                    } else {
                        if (modeOwner == null
                                || h.getUpdateTime() > modeOwner.getUpdateTime()) {
                            modeOwner = h;
                        }
                    }
                }
            }
            return privilegedModeOwner != null ? privilegedModeOwner :  modeOwner;
        }
    

    这儿有两个信息,一个是isActive,一个是UpdataTime,isActive就是检查是否有活动的采集或播放:

            /**
             * An app is considered active if:
             * - It is privileged (has MODIFY_PHONE_STATE permission)
             *  or
             * - It requests mode MODE_IN_COMMUNICATION, and it is either playing
             * or recording for VOICE_COMMUNICATION.
             *   or
             * - It requests a mode different from MODE_IN_COMMUNICATION or MODE_NORMAL
             * Note: only privileged apps can request MODE_IN_CALL, MODE_CALL_REDIRECT
             * or MODE_COMMUNICATION_REDIRECT.
             */
            public boolean isActive() {
                return mIsPrivileged
                        || ((mMode == AudioSystem.MODE_IN_COMMUNICATION)
                            && (mRecordingActive || mPlaybackActive))
                        || mMode == AudioSystem.MODE_RINGTONE
                        || mMode == AudioSystem.MODE_CALL_SCREENING;
            }
    

    而UpdataTime 是在setMode的时候会更新。
    这儿还有一个疑问,那播放和采集状态如何更新的呢?
    这儿有一个回调 onPlaybackConfigChange

     private void onPlaybackConfigChange(List<AudioPlaybackConfiguration> configs) {
            boolean voiceActive = false;
            for (AudioPlaybackConfiguration config : configs) {
                final int usage = config.getAudioAttributes().getUsage();
                if ((usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
                        || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
                        && config.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
                    voiceActive = true;
                    break;
                }
            }
            if (mVoicePlaybackActive.getAndSet(voiceActive) != voiceActive) {
                updateHearingAidVolumeOnVoiceActivityUpdate();
            }
    
            // Update playback active state for all apps in audio mode stack.
            // When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE
            // and request an audio mode update immediately. Upon any other change, queue the message
            // and request an audio mode update after a grace period.
            synchronized (mDeviceBroker.mSetModeLock) {
                boolean updateAudioMode = false;
                int existingMsgPolicy = SENDMSG_QUEUE;
                int delay = CHECK_MODE_FOR_UID_PERIOD_MS;
                for (SetModeDeathHandler h : mSetModeDeathHandlers) {
                    boolean wasActive = h.isActive();
                    h.setPlaybackActive(false);
                    for (AudioPlaybackConfiguration config : configs) {
                        final int usage = config.getAudioAttributes().getUsage();
                        if (config.getClientUid() == h.getUid()
                                && (usage == AudioAttributes.USAGE_VOICE_COMMUNICATION
                                    || usage == AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING)
                                && config.getPlayerState()
                                    == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
                            h.setPlaybackActive(true);
                            break;
                        }
                    }
                    if (wasActive != h.isActive()) { // 如果状态不一样,那么就会触发更新
                        updateAudioMode = true;
                        if (h.isActive() && h == getAudioModeOwnerHandler()) {
                            existingMsgPolicy = SENDMSG_REPLACE;
                            delay = 0;
                        }
                    }
                }
                if (updateAudioMode) { 这时候就会重新查找mode owner并设置音量类型了,如果没有mode owner,那么就设置成媒体音量
                    sendMsg(mAudioHandler,
                            MSG_UPDATE_AUDIO_MODE,
                            existingMsgPolicy,
                            AudioSystem.MODE_CURRENT,
                            android.os.Process.myPid(),
                            mContext.getPackageName(),
                            delay);
                }
            }
        }
    

    同样也有一个 onRecordingConfigChange,也是同样逻辑,就不重复了。
    就以onPlaybackConfigChange 为线索继续看,这个是以回调形式注册到PlaybackActivityMonitor中了。而PlaybackActivityMonitor也是由AudioService通知的:

     public void playerEvent(int piid, int event, int deviceId) {
            mPlaybackMonitor.playerEvent(piid, event, deviceId, Binder.getCallingUid());
        }
    

    那再看下playerEvent的源头,这时候会发现是在frameworks/base/media/java/android/media/PlayerBase.java

        private void updateState(int state, int deviceId) {
            final int piid;
            synchronized (mLock) {
                mState = state;
                piid = mPlayerIId;
                mDeviceId = deviceId;
            }
            try {
                getService().playerEvent(piid, state, deviceId);
            } catch (RemoteException e) {
                Log.e(TAG, "Error talking to audio service, "
                        + AudioPlaybackConfiguration.toLogFriendlyPlayerState(state)
                        + " state will not be tracked for piid=" + piid, e);
            }
        }
    
    

    会发现在baseStart,baseStop,basePause中会掉用, 而PlayerBase本身又被AudioTrack继承,在AudioTrack中调用父类方法就可以了:
    frameworks/base/media/java/android/media/AudioTrack.java

      private void startImpl() {
            synchronized (mRoutingChangeListeners) {
                if (!mEnableSelfRoutingMonitor) {
                    mEnableSelfRoutingMonitor = testEnableNativeRoutingCallbacksLocked();
                }
            }
            synchronized(mPlayStateLock) {
                baseStart(0); // unknown device at this point
                native_start();
                // FIXME see b/179218630
                //baseStart(native_getRoutedDeviceId());
                if (mPlayState == PLAYSTATE_PAUSED_STOPPING) {
                    mPlayState = PLAYSTATE_STOPPING;
                } else {
                    mPlayState = PLAYSTATE_PLAYING;
                    mOffloadEosPending = false;
                }
            }
        }
    

    对于Native接口如何感知到呢?这儿还有一个逻辑:

    void initMonitor() {
            AudioSystem.setRecordingCallback(this);
    }
    

    这儿会注册Native的通知,Native感知到采集和播放变化后,就会通知上来,具体流程本篇先略过。

    这时候就基本理清楚了setMode 的mode owner 机制了。可以总结成如下:

    1. setMode 设置媒体音量不一定能成功,因为如果有其他应用是通话音量的mode owner,并且有活动的采集或播放,或者是系统应用,那么就还是会继续设置通话音量
    2. setMode设置通话音量一定可以成功,同时自己也会成为mode owner,但是如果不启动 采集或播放,通话音量也不会一直生效,过上一会儿(最新代码是6s)后就会刷新一次,被重置成不生效,这时候也就不会被认为是mode owner了,如果没有其他通话音量的mode owner,那么就会被设置成媒体音量

    相关文章

      网友评论

          本文标题:AudioManager setMode机制

          本文链接:https://www.haomeiwen.com/subject/lcncnrtx.html