AudioManager setMode机制

2022-10-25 16:54:28 浏览数 (2)

本篇介绍

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

setMode实现

先看代码实现:

代码语言:javascript复制
   /**
     * 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:

代码语言:javascript复制
    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的实现:

代码语言:javascript复制
  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 的逻辑:

代码语言:javascript复制
 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的应用,可以看下面逻辑:

代码语言:javascript复制
    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就是检查是否有活动的采集或播放:

代码语言:javascript复制
        /**
         * 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

代码语言:javascript复制
 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通知的:

代码语言:javascript复制
 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

代码语言:javascript复制
    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

代码语言:javascript复制
  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接口如何感知到呢?这儿还有一个逻辑:

代码语言:javascript复制
void initMonitor() {
        AudioSystem.setRecordingCallback(this);
}

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

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

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

0 人点赞