前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >AudioManager setMode机制

AudioManager setMode机制

作者头像
一只小虾米
发布2022-10-25 16:54:28
1.5K0
发布2022-10-25 16:54:28
举报
文章被收录于专栏:Android点滴分享Android点滴分享

本篇介绍

在开发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,那么就会被设置成媒体音量
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-09-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 本篇介绍
  • setMode实现
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档