CarAudio(四)系统音频焦点

概念

在手机的场景中,我们会看到,当使用一个音乐播放器播放音乐时,再打开另一个音乐播放器播放音乐,这时候,上一个应用的音乐会暂停,这就是音频焦点器的作用。应用可以通过调用AudioManager的requestAudioFocus方法进行焦点的请求,并注册OnAudioFocusChangeListener监听得知焦点的变化。从而控制音频的播放,暂停,资源释放等逻辑。

1.应用请求焦点方法

音频焦点为了解决同时处理多个音频播放,音频混乱播放问题制定的逻辑。我们应用可以通过获取AudioManager服务,调用requestAudioFocus方法进行调用。下面为一个音乐播放器请求焦点示例:

1).获取AudioManager服务。

mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);

2).设置音频属性。设置音频的音频类型及使用场景,在CarAudio中可通过设置的使用场景分配焦点。

mAudioAttributes = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build();

3).创建一个用来播放音频的MediaPlayer,将音频属性AudioAttributes传入MediaPlayer中。

if (mediaPlayer == null){
            mediaPlayer = new MediaPlayer();
        }
        mediaPlayer.setAudioAttributes(mAudioAttributes);

4).实现OnAudioFocusChangeListener监听,用来监听焦点变化

 AudioManager.OnAudioFocusChangeListener changeListener = focusChange -> {
        switch (focusChange){
            case AudioManager.AUDIOFOCUS_LOSS://长期失去焦点,停止播放,可进行音频资源释放操作
                closeMedia();
                break;
            case AudioManager.AUDIOFOCUS_GAIN://获取到了当前焦点,继续播放音频
                playMedia();
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT://暂时失去焦点,暂停播放
                stopMedia();
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK://暂时失去焦点,可播放声音,但声音需减低
                break;
        }
    };

5).构造焦点请求,传入我们申请的焦点类型FocusGain,音频属性和焦点监听器。

 mFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
                .setAudioAttributes(mAudioAttributes)
                .setAcceptsDelayedFocusGain(true)
                .setOnAudioFocusChangeListener(changeListener)
                .build();

6).调用requestAudioFocus请求焦点,根据请求到的结果,进行音频播放

 int res =  mAudioManager.requestAudioFocus(mFocusRequest);
        switch (res){
            case AudioManager.AUDIOFOCUS_REQUEST_FAILED://申请焦点失败,不执行播放
                break;
            case AudioManager.AUDIOFOCUS_REQUEST_GRANTED://申请焦点成功,执行播放
                isPlayDelayed = false;
                playMedia();
                break;
            case AudioManager.AUDIOFOCUS_REQUEST_DELAYED://申请焦点延时,不执行播放,我们播放音乐时会有这种状态,
            //如果返回值为delay时,后续的播放可在收到对应的焦点变化监听OnAudioFocusChangeListener中执行。
                isPlayDelayed = true;
                break;
        }

音频进行焦点的申请,大致就分为这几步,我们需要在焦点申请成功失败后,及焦点丢失和重新获取时对音频进行播放暂停处理。系统的音频焦点属于弱管理,并不是一定得申请焦点后,才能进行播放,只是作为建议,建议应用做这样操作,从而获得更好的用户体验。

2.AudioFocusRequest介绍

申请焦点最重要的是构建一个焦点请求对象AudioFocusRequest,AudioFocusRequest封装了申请音频焦点信息,通过Builder的方式进行创建, AudioFocusRequest. Builder提供了以下一些方法。

1.Builder(int focusGain):Builder构造器创建的时候必须先传入一个focusGain必须先传入申请焦点的类型,表示应用需要请求一个什么类型的焦点focusGain类型分为以下几种:

  • AUDIOFOCUS_GAIN:长时间获取焦点,一般用于音视频。
  • AUDIOFOCUS_GAIN_TRANSIENT:短暂获得,一般用于电话,语音助理等
  • AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:混音,一般用于导航
  • AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:与AUDIOFOCUS_GAIN_TRANSIENT类似,表示一个短暂的获取焦点,一般用于语音识别什么的,很少用。

2.setFocusGain(int focusGain):一样的是传入focusGain申请的焦点类型

3.setOnAudioFocusChangeListener(@NonNull OnAudioFocusChangeListener listener):设置焦点变化的监听器OnAudioFocusChangeListener

4.setOnAudioFocusChangeListenerInt(OnAudioFocusChangeListener listener, Handler handler):设置焦点变化的监听器,同时传入一个Handler,指定listenter回调到handler线程。代表可以将焦点变化的监听回调到handler中执行。而没有handler只能默认回调到与我们初始化AudioManager的那个线程,容易导致listener的回调卡顿,这样造成的后果就是我们要停止的声音没有及时停止而造成短暂的混音。

5.setWillPauseWhenDucked(boolean pauseOnDuck):当有一个音频申请的焦点类型为AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK时,之前栈顶的音频焦点的音频,系统会自动进行降音处理,而这个方法设为true时就表示,取消系统的降音处理,自己通过焦点监听器变AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK状态时,应用自行进行降音处理。

6.setAcceptsDelayedFocusGain(boolean acceptsDelayedFocusGain):设置是否延迟请求焦点。只有设置为truerequestAudioFocus才能返回AUDIOFOCUS_REQUEST_DELAYED延时请求,之后才可在焦点监听器中进行处理。否则需要进行延时处理时,焦点将直接申请失败。

7.setLocksFocus(boolean focusLocked):设置是否锁定焦点,需要设置为true时,需要给AudioManager传入一个AudioPolicy才能使用。普通应用不可使用。

8.setForceDucking(boolean forceDucking):当使用场景是USAGE_ASSISTANCE_ACCESSIBILITY且申请的是AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK焦点类型是,会强制降低其他音源。主要用于无障碍服务中。

9.setAudioAttributes(@NonNull AudioAttributes attributes):设置音频属性AudioAttributesAudioAttributes音频属性定义音频系统如何处理指定源的路由、音量和焦点决策的属性。系统Audio中很多地方都使用了这个音频属性。AudioArrtibutes是同过也是通过Builder构造器进行参数设置的。 AudioAttributes主要的属性为usagecontentType

3 AudioAttributes介绍

代码路径:/frameworks/base/media/java/android/media/AudioAttributes.java

用来定义音频系统如何处理指定源的路由、音量和焦点决策的属性。系统Audio中很多地方都使用了这个音频属性。

AudioArrtibutes是同过Builder方法进行构造的,AudioArrtibutes.Builder类内进行如下方法构建。

方法 描述
setUsage(@AttributeSdkUsage int usage) 设置音频使用场景,用来描述为什么要播放这个音频,是用来导航/闹钟/播放媒体?
setSystemUsage(@AttributeSystemUsage int systemUsage) 设置为系统使用的音频
setContentType(@AttributeContentType int contentType) 设置音频内容,用来描述这个音频的内容是什么,是语音/音乐....
setFlags(int flags) 设置标志的组合,Flag用来描述这个音频的输出标识,比如:低时延输出、强制可听……
setHotwordModeEnabled(boolean enable) 是否在hotword模式下请求捕获。实际上是添加一个FLAG_HW_HOTWORDflag
setAllowedCapturePolicy(@CapturePolicy int capturePolicy) 指定音频是否可以被其他应用程序或系统捕获
addBundle(@NonNull Bundle bundle) 携带Bundle数据
addTag(String tag) 添加标志
setLegacyStreamType(int streamType) 使用旧的streamType的类型,对应usage,和setUsage不可同时使用
setCapturePreset(int preset) 设置捕获预设。在AudioRecord录音时使用
setInternalCapturePreset(int preset) hotword模式下进行捕获预设
setHapticChannelsMuted(boolean muted) 设置播放音频时,是否可震动
  • setUsage:音频使用场景,用来描述为什么要播放这个音频,是用来导航/闹钟/播放媒体?

             *     {@link AttributeSdkUsage#USAGE_MEDIA},
             *     {@link AttributeSdkUsage#USAGE_VOICE_COMMUNICATION},
             *     {@link AttributeSdkUsage#USAGE_VOICE_COMMUNICATION_SIGNALLING},
             *     {@link AttributeSdkUsage#USAGE_ALARM}, {@link AudioAttributes#USAGE_NOTIFICATION},
             *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_RINGTONE},
             *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_COMMUNICATION_REQUEST},
             *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_COMMUNICATION_INSTANT},
             *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_COMMUNICATION_DELAYED},
             *     {@link AttributeSdkUsage#USAGE_NOTIFICATION_EVENT},
             *     {@link AttributeSdkUsage#USAGE_ASSISTANT},
             *     {@link AttributeSdkUsage#USAGE_ASSISTANCE_ACCESSIBILITY},
             *     {@link AttributeSdkUsage#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE},
             *     {@link AttributeSdkUsage#USAGE_ASSISTANCE_SONIFICATION},
             *     {@link AttributeSdkUsage#USAGE_GAME}.
    
  • setContentType:用来描述这个音频的内容是什么,是语音/音乐....

             *     {@link AudioAttributes#CONTENT_TYPE_MOVIE},
             *     {@link AudioAttributes#CONTENT_TYPE_MUSIC},
             *     {@link AudioAttributes#CONTENT_TYPE_SONIFICATION},
             *     {@link AudioAttributes#CONTENT_TYPE_SPEECH},
             *     {@link AudioAttributes#CONTENT_TYPE_UNKNOWN}.
    
  • setAllowedCapturePolicy:指定音频是否可以被其他应用程序或系统捕获。定义了如下三个值:

    public static final int ALLOW_CAPTURE_BY_ALL = 1;//可被任何应用截取
    public static final int ALLOW_CAPTURE_BY_SYSTEM = 2;//只能被系统应用截取
    public static final int ALLOW_CAPTURE_BY_NONE = 3;//表示任何应用都不能进行截取
    

4.应用申请音频焦点的代码流程

焦点的申请经过AudioManager.java-->AudioService.java --> MediaFocusControl.java的流程来申请的。现在我们看下每个类里面都进行了哪些操作。先看AudioManager.java中做了什么。

1.AudioManager.requestAudioFocus

代码路径:/frameworks/base/media/java/android/media/AuidoManager.java

    public int requestAudioFocus(@NonNull AudioFocusRequest focusRequest) {
        return requestAudioFocus(focusRequest, null /* no AudioPolicy*/);
    }
@SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) {
         ...
        //1.进行locksFocus检测,在Builder时设置了setLocksFocus(true)但AudioPolicy为null,则不能进行焦点申请。
        if (afr.locksFocus() && ap == null) {
            throw new IllegalArgumentException(
                    "Illegal null audio policy when locking audio focus");
        }
        //2.注册监听,构造FocusRequestInfo,将焦点变化监听器的哈希值做为key,如果没有,则直接以当前AudioManager的哈希值作为key,
        //FocusRequestInfo作为value,添加到mAudioFocusIdListenerMap监听列表里
        registerAudioFocusRequest(afr);
        
        //3.获取AudioService,sdk,clientId(与mAudioFocusIdListenerMap中的key对应)
        final IAudioService service = getService();
        final int status;
        int sdk;...
        final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
        final BlockingFocusResultReceiver focusReceiver;
        synchronized (mFocusRequestsLock) {
            try {
                //4.调用到AudioService的requestAudioFocus方法,传入mAudioFocusDispatcher和clientId,
                //mAudioFocusDispatcher作为AudioManager中的焦点监听,进行应用焦点监听分配回调
                status = service.requestAudioFocus(afr.getAudioAttributes(),
                        afr.getFocusGain(), mICallBack,mAudioFocusDispatcher,clientId,
                        getContext().getOpPackageName() /* package name */, afr.getFlags(),
                        ap != null ? ap.cb() : null,sdk);
            ...
            //5.返回的状态不为AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY时,正常返回
            if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {
                // default path with no external focus policy
                return status;
            }
            //6.status为AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY时,
            //将通过BlockingFocusResultReceiver等待调用setFocusRequestResult之后拿到结果返回
            //该情况用于车载的CarAudio焦点分配。需要等待时创建一个BlockingFocusResultReceiver等待线程,
            //focusReceiver为等待列表,以clientId为key,等待200ms后如果焦点状态还未在BlockingFocusResultReceiver
            //中赋值,则超时报错,否则正常返回申请结果。
            if (mFocusRequestsAwaitingResult == null) {
                mFocusRequestsAwaitingResult =
                        new HashMap<String, BlockingFocusResultReceiver>(1);
            }
            focusReceiver = new BlockingFocusResultReceiver(clientId);
            mFocusRequestsAwaitingResult.put(clientId, focusReceiver);
        }
        focusReceiver.waitForResult(EXT_FOCUS_POLICY_TIMEOUT_MS);
        if (DEBUG && !focusReceiver.receivedResult()) {
            Log.e(TAG, "requestAudio response from ext policy timed out, denying request");
        }
        synchronized (mFocusRequestsLock) {
            mFocusRequestsAwaitingResult.remove(clientId);
        }
        return focusReceiver.requestResult();
    }
//2.注册监听,构造FocusRequestInfo,将焦点变化监听器的哈希值做为key,如果没有,则直接以当前AudioManager的哈希值作为key,
        //FocusRequestInfo作为value,添加到mAudioFocusIdListenerMap监听列表里    
public void registerAudioFocusRequest(@NonNull AudioFocusRequest afr) {
        final Handler h = afr.getOnAudioFocusChangeListenerHandler();
        final FocusRequestInfo fri = new FocusRequestInfo(afr, (h == null) ? null :
            new ServiceEventHandlerDelegate(h).getHandler());
        final String key = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());
        mAudioFocusIdListenerMap.put(key, fri);
    }

总结,在AuidoManager中存在一个mAudioFocusDispatcher进行焦点变化监听,一个mAudioFocusIdListenerMap集合管理所有的焦点请求FocusRequestInfo,并通过OnAudioFocusChangeListener的哈希值作为key进行关联。具体请求结果再调用到AudioService中,我们在看下AudioService.java中的代码。

2.AudioService.requestAudioFocus

frameworks/base/services/core/java/com/android/server/audio/AuidoService.java

    public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
            IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
            IAudioPolicyCallback pcb, int sdk) {
        final int uid = Binder.getCallingUid();
        ...
        //做权限检测,clientId等非空判断,提前返回焦点申请失败
        ...
        return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
                clientId, callingPackageName, flags, sdk,
                forceFocusDuckingForAccessibility(aa, durationHint, uid), -1 /*testUid, ignored*/);
    }

在AudioService中并没有做过多的操作,只是进行了一些参数检测,权限检测,提前返回焦点申请失败,具体的逻辑是调用MediaFocusControl中的requestAudioFocus,将其返回的status直接返回给AudioManager。

3.MediaFocusControl.requestAudioFocus

MediaFocusControl为焦点请求的关键类。MediaFocusControl为焦点申请的关键类,其中包含焦点请求堆栈mFocusStack

protected int requestAudioFocus(@NonNull AudioAttributes aa, int focusChangeHint, IBinder cb,
            IAudioFocusDispatcher fd, @NonNull String clientId, @NonNull String callingPackageName,
            int flags, int sdk, boolean forceDuck, int testUid) {
       ...
        synchronized(mAudioFocusLock) {
           //1.判断焦点申请个数是否已满。mFocusStack为焦点<FocusRequester>的堆栈,栈顶则为当前占用的焦点。
           //最大个数为100个。超过100个返回焦点请求失败
            if (mFocusStack.size() > MAX_STACK_SIZE) {
                Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");
                return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
            }
            //2.判断是否是系统手机电话音频。设置mRingOrCallActive标志为true
            //AudioSystem.IN_VOICE_COMM_FOCUS_ID这个类型是只用于AudioManager.requestAudioFocusForCall专门进行电话音频焦点申请。
            boolean enteringRingOrCall = !mRingOrCallActive
                    & (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
            if (enteringRingOrCall) { mRingOrCallActive = true; }
            
            //3.重点,这里会根据mFocusPolicy是否为null,也就是IAudioPolicyCallback是否为null,构造一个AudioFocusInfo,
            //用于比如车载的音频焦点,在CarAudioService创建时就会IAudioPolicyCallback传入,mFocusPolicy就为CarAudioService中的AudioPolicy。
            //在mFocusPolicy不为null的情况下,将会返回AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY标志,在AudioManager中等待CarAudio中
            //进行完焦点分配后,setFocusRequestResult返回给AudioManager,最后返回给应用。
            final AudioFocusInfo afiForExtPolicy;
           
            if (mFocusPolicy != null) {
                // construct AudioFocusInfo as it will be communicated to audio focus policy
                afiForExtPolicy = new AudioFocusInfo(aa, uid,
                        clientId, callingPackageName, focusChangeHint, 0 /*lossReceived*/,
                        flags, sdk);
            } else {
                afiForExtPolicy = null;
            }
            
            //4.判断申请结果是否需要delay,如果当前音频焦点被通话或手机铃声占用时且请求的焦点设置了可延时获取,
            //则返回AUDIOFOCUS_REQUEST_DELAYED告知焦点请求dealy,否则返回焦点申请失败。
            boolean focusGrantDelayed = false;
            if (!canReassignAudioFocus()) {
                if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                } else {
                    focusGrantDelayed = true;
                }
            }
            // 接着3的流程,mFocusPolicy不为null时,调用notifyExtFocusPolicyFocusRequest_syncAf方法。
            if (mFocusPolicy != null) {
                if (notifyExtFocusPolicyFocusRequest_syncAf(afiForExtPolicy, fd, cb)) {
                    return AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY;
                } else {
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                }
            }
            //5. 判断请求焦点的客户端是否已经死亡,如果已经死亡,则返回焦点请求失败
            AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb);
            try {
                cb.linkToDeath(afdh, 0);
            } catch (RemoteException e) {
                // client has already died!
                Log.w(TAG, "AudioFocus  requestAudioFocus() could not link to "+cb+" binder death");
                return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
            }
            //6.排重,如果请求的焦点和当前正在占用焦点的clientId是一样的,那么不进行焦点的修改。直接返回焦点申请成功。
            if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
                final FocusRequester fr = mFocusStack.peek();
                if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) {
                    cb.unlinkToDeath(afdh, 0);
                    notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(),
                            AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
                    return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
                }
                if (!focusGrantDelayed) {
                    mFocusStack.pop();
                    fr.release();
                }
            }
            //7.该方法从mFocusStack栈内查找是否存在这个clientId的焦点请求,有的话进行移除,并释放资源。
            removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
            
            //8.新建一个FocusRequester,用于成功后push到栈顶
            final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
                    clientId, afdh, callingPackageName, uid, this, sdk);
                    
            //9.这段代码的逻辑为当mMultiAudioFocusEnabled这个变量为true,将并且申请的焦点类型为一个长期占用的焦点类型时,
            //则通过mMultiAudioFocusList列表进行焦点管理。当前申请焦点用于call通话时,直接将前面列表中的所有焦点都变为LOSS状态,否则
            //直接返回焦点申请成功,并添加到mMultiAudioFocusList列表。该模式下,都进行混音
            //mMultiAudioFocusEnabled可通过Settings.System.putIntForUser(cr,
            //    Settings.System.MULTI_AUDIO_FOCUS_ENABLED, enabled ? 1 : 0, cr.getUserId())进行配置。
            if (mMultiAudioFocusEnabled
                    && (focusChangeHint == AudioManager.AUDIOFOCUS_GAIN)) {
                if (enteringRingOrCall) {
                    if (!mMultiAudioFocusList.isEmpty()) {
                        for (FocusRequester multifr : mMultiAudioFocusList) {
                            multifr.handleFocusLossFromGain(focusChangeHint, nfr, forceDuck);
                        }
                    }
                } else {
                    boolean needAdd = true;
                    if (!mMultiAudioFocusList.isEmpty()) {
                        for (FocusRequester multifr : mMultiAudioFocusList) {
                            if (multifr.getClientUid() == Binder.getCallingUid()) {
                                needAdd = false;
                                break;
                            }
                        }
                    }
                    if (needAdd) {
                        mMultiAudioFocusList.add(nfr);
                    }
                    nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
                    notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
                            AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
                    return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
                }
            }
            if (focusGrantDelayed) {
            //10.如果是焦点申请需要延时,在pushBelowLockedFocusOwners方法中判断焦点堆栈中如果有被锁定的焦点
            //(setLocksFocus(true)或 通话和铃声焦点)且 在栈顶,则焦点可直接申请成功,否则返回焦点申请需Delay。
                final int requestResult = pushBelowLockedFocusOwners(nfr);
                if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
                    notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);
                }
                return requestResult;
            } else {
                //11.返回焦点申请成功,通知之前的焦点FocusRequester失去焦点,将当前焦点push到mFocusStack栈顶
                propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck);
                mFocusStack.push(nfr);
                nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
            }
            // 12. 给AudioPolicy更新焦点申请成功回调
            notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
                    AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
            //13.ENFORCE_MUTING_FOR_RING_OR_CALL设置为true时,当前申请的焦点为通话时,强制其他所有音频静音,直到通话结束后恢复       
            if (ENFORCE_MUTING_FOR_RING_OR_CALL & enteringRingOrCall) {
                runAudioCheckerForRingOrCallAsync(true/*enteringRingOrCall*/);
            }
        }//synchronized(mAudioFocusLock)

        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    }

MediaFocusControlrequestAudioFocus流程中我们注意到,在焦点申请成功的时候,会执行propagateFocusLossFromGain_syncAfq方法去通知当前的焦点持有者丢失焦点。代码如下:

 @GuardedBy("mAudioFocusLock")
    private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr,
                                                   boolean forceDuck) {
        final List<String> clientsToRemove = new LinkedList<String>();
        if (!mFocusStack.empty()) {
            for (FocusRequester focusLoser : mFocusStack) {
                final boolean isDefinitiveLoss =
                        focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
                if (isDefinitiveLoss) {
                    clientsToRemove.add(focusLoser.getClientId());
                }
            }
        }
        if (mMultiAudioFocusEnabled && !mMultiAudioFocusList.isEmpty()) {
            for (FocusRequester multifocusLoser : mMultiAudioFocusList) {
                final boolean isDefinitiveLoss =
                        multifocusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
                if (isDefinitiveLoss) {
                    clientsToRemove.add(multifocusLoser.getClientId());
                }
            }
        }
        for (String clientToRemove : clientsToRemove) {
            removeFocusStackEntry(clientToRemove, false /*signal*/,
                    true /*notifyFocusFollowers*/);
        }
    }

其主要逻辑就是迭代焦点栈,执行handleFocusLossFromGain把状态变为LOSS的焦点对象移除栈内。

而在焦点申请需要Delay时,则是去执行pushBelowLockedFocusOwners方法直接将当前焦点对象插入栈顶后一个位置,这样当当前的焦点持有者释放焦点后,焦点申请者能被push到栈顶,获取到焦点,进行音频播放。代码如下:

        private int pushBelowLockedFocusOwners(FocusRequester nfr) {
        int lastLockedFocusOwnerIndex = mFocusStack.size();
        for (int index = mFocusStack.size()-1; index >= 0; index--) {
            if (isLockedFocusOwner(mFocusStack.elementAt(index))) {
                lastLockedFocusOwnerIndex = index;
            }
        }
        if (lastLockedFocusOwnerIndex == mFocusStack.size()) {
            // this should not happen, but handle it and log an error
            Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()",
                    new Exception());
            propagateFocusLossFromGain_syncAf(nfr.getGainRequest(), nfr, false /*forceDuck*/);
            mFocusStack.push(nfr);
            return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
        } else {
            mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex);
            return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
        }
    }

其主要逻辑为:先迭代焦点栈,如果焦点栈内存在被锁定的焦点,则将当前焦点插入到锁定了焦点者后面,等被锁定的焦点释放了焦点后才能获取焦点。如果不存在,就直接返回申请成功。这一部在我看来其实是一个再次检测的过程,因为我们在执行这个方法之前是已经检查过当前焦点持有者是否被锁定,是的话才走到这。所以大部分的还是会执行insertElementAt这个方法插入到栈内,等待上一个焦点释放。

最后焦点申请者申请成功,焦点持有者丢失焦点,会分别执行FocusRequester对象的 handleFocusGainFromRequesthandleFocusLossFromGain方法。

  • handleFocusGainFromRequest方法比较简单,就是执行restoreVShapedPlayers方法,通知底层可进行音频播放。

        @GuardedBy("MediaFocusControl.mAudioFocusLock")
        void handleFocusGainFromRequest(int focusRequestResult) {
            if (focusRequestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
                mFocusController.restoreVShapedPlayers(this);
            }
        }
    
  • handleFocusLossFromGain方法中调用focusLossForGainRequest方法,根据上一次焦点的状态,和当前焦点申请者的请求的焦点类型,返回焦点变化状态,在handleFocusLoss方法中执行IAudioFocusDispatcher(AudioManager中传入的监听器)的dispatchAudioFocusChange方法,最终将焦点状态变化回调到应用的监听器中。

      @GuardedBy("MediaFocusControl.mAudioFocusLock")
        boolean handleFocusLossFromGain(int focusGain, final FocusRequester frWinner, boolean forceDuck)
        {
            final int focusLoss = focusLossForGainRequest(focusGain);
            handleFocusLoss(focusLoss, frWinner, forceDuck);
            return (focusLoss == AudioManager.AUDIOFOCUS_LOSS);
        }
        //mFocusLossReceived为当前焦点的上一次变化记录
      private int focusLossForGainRequest(int gainRequest) {
            switch(gainRequest) {
              //请求的焦点类型为长期占用时,焦点状态变为丢失LOSS
                case AudioManager.AUDIOFOCUS_GAIN:
                    switch(mFocusLossReceived) {
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                        case AudioManager.AUDIOFOCUS_LOSS:
                        case AudioManager.AUDIOFOCUS_NONE:
                            return AudioManager.AUDIOFOCUS_LOSS;
                    }
                //请求的焦点类型为短期占用,
              //上次焦点状态为短暂丢失或混音时,则焦点状态变为LOSS_TRANSIENT短暂丢失。    
              //上次焦点状态为丢失时,则焦点状态变为LOSS丢失          
                case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
                case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
                    switch(mFocusLossReceived) {
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                        case AudioManager.AUDIOFOCUS_NONE:
                            return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
                        case AudioManager.AUDIOFOCUS_LOSS:
                            return AudioManager.AUDIOFOCUS_LOSS;
                    }
              //请求的焦点类型为混音
              //上次焦点状态为混音时,焦点状态变为CAN_DUCK可混音状态
              //上次焦点状态为短暂丢失时,焦点状态保持LOSS_TRANSIENT
              //上次焦点状态为丢失时,焦点状态保持LOSS
                case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
                    switch(mFocusLossReceived) {
                        case AudioManager.AUDIOFOCUS_NONE:
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                            return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                            return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
                        case AudioManager.AUDIOFOCUS_LOSS:
                            return AudioManager.AUDIOFOCUS_LOSS;
                    }
                default:
                    Log.e(TAG, "focusLossForGainRequest() for invalid focus request "+ gainRequest);
                            return AudioManager.AUDIOFOCUS_NONE;
            }
        }
    

5.释放焦点abandonAudioFocusRequest

看完焦点的申请流程,在看下焦点的释放流程,在申请成功焦点播放音频后,需要将焦点再次释放,否则焦点不会自动释放。焦点释放调用AudioManagerabandonAudioFocusRequest方法,将申请焦点时构造的AudioFocusRequest传入。和申请焦点的流程类似,最后也是在MediaFocusControl.java中执行核心代码。

protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa,
            String callingPackageName) {
       ...
        try {
            synchronized(mAudioFocusLock) {
                if (mFocusPolicy != null) {
                    //mFocusPolicy不为null时,同样的将焦点释放给到AudioPolicy中释放
                    final AudioFocusInfo afi = new AudioFocusInfo(aa, Binder.getCallingUid(),
                            clientId, callingPackageName, 0 /*gainRequest*/, 0 /*lossReceived*/,
                            0 /*flags*/, 0 /* sdk n/a here*/);
                    if (notifyExtFocusPolicyFocusAbandon_syncAf(afi)) {
                        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
                    }
                }
                ...
                // 移除焦点
                removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/);
                //通话焦点则恢复之前的音频
                if (ENFORCE_MUTING_FOR_RING_OR_CALL & exitingRingOrCall) {
                    runAudioCheckerForRingOrCallAsync(false/*enteringRingOrCall*/);
                }
            }
        } catch (java.util.ConcurrentModificationException cme) {
            Log.e(TAG, "FATAL EXCEPTION AudioFocus  abandonAudioFocus() caused " + cme);
            cme.printStackTrace();
        }
        return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
    }

关键方法为removeFocusStackEntry,传入signal,notifyFocusFollowerstrue,接着看removeFocusStackEntry中的代码逻辑

@GuardedBy("mAudioFocusLock")
    private void removeFocusStackEntry(String clientToRemove, boolean signal,
            boolean notifyFocusFollowers) {
        AudioFocusInfo abandonSource = null;
        //释放的焦点正处于栈顶,直接pop出栈
        if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
        {
            FocusRequester fr = mFocusStack.pop();
            fr.maybeRelease();
            if (notifyFocusFollowers) {
                abandonSource = fr.toAudioFocusInfo();
            }
            //signal为true时,更新当前焦点出栈后的栈顶焦点为AUDIOFOCUS_GAIN
            if (signal) {
                //在该方法中,将栈内的焦点peek出来,再执行handleFocusGain方法。
                notifyTopOfAudioFocusStack();
            }
        } else {
        //释放的焦点不在栈顶时,直接从栈内移除。
         Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
         while(stackIterator.hasNext()) {
                FocusRequester fr = stackIterator.next();
                if(fr.hasSameClient(clientToRemove)) {
                    Log.i(TAG, "AudioFocus  removeFocusStackEntry(): removing entry for "
                            + clientToRemove);
                    stackIterator.remove();
                    if (notifyFocusFollowers) {
                        abandonSource = fr.toAudioFocusInfo();
                    }
                    fr.maybeRelease();
                }
           
        }
        if (abandonSource != null) {
            abandonSource.clearLossReceived();
            //通知AudioPolicy当前焦点已经被释放
            notifyExtPolicyFocusLoss_syncAf(abandonSource, false);
        }
        ...
    }

6.总结

至此,我们的audio焦点生命周期已经大概都已经知晓。对以上流程我们进行归纳总结:

  • 1.系统音频焦点在用户场景上,只做通话和非通话音频区分。
  • 2.系统音频焦点由mFocusStack堆栈进行管理。焦点申请成功,push到栈顶;焦点释放,pop出栈,焦点需延时,inster插入栈内。焦点状态变为Loss,从栈内移除。
  • 3.mFocusPolicy不为null时,系统Audio不处理焦点分配,交由自定义的AudioPolicy去进行分配。比如CarAudioFocus
  • 4.当前焦点持有者为通话时,焦点返回Delay,知道通话焦点释放,重新获取焦点。

场景一:

1.音频A申请了一个长期焦点AUDIOFOCUS_GAIN,焦点申请成功后,音频A播放。

2.音频B也申请了一个长期焦点AUDIOFOCUS_GAIN,焦点申请成功,音频B的焦点被push入栈顶。音频B播放

3.音频A的焦点被移出Stack内, A焦点监听器收到AUDIOFOCUS_LOSS 状态,再次播放,需重新申请焦点。

场景二:

1.音频A为通话音频,申请了焦点,通话中。

2.音频B也申请了一个长期焦点AUDIOFOCUS_GAIN,并设置了可延时申请,焦点申请delay,音频B被插入栈内。

3.音频A的通话结束,释放焦点,音频A被pop出栈,音频B来到栈顶。音频B监听器收到AUDIOFOCUS_GAIN状态,

可进行音频播放。

场景三:

1.音频A申请了一个长期焦点AUDIOFOCUS_GAIN,焦点申请成功后,音频A播放。

2.音频B申请了一个短期焦点AUDIOFOCUS_GAIN_TRANSIENT,焦点申请成功,音频B的焦点被push入栈顶。音频B播放

3.音频A的焦点监听器中收到AUDIOFOCUS_LOSS_TRANSIENT状态。如果音频B处于stack 0位置,音频A焦点则于Stack 1的位置,还在栈内。

4.音频B停止播放,同时释放焦点,音频B的焦点出栈。

5.音频A成为了栈顶焦点,监听器中将收到AUDIOFOCUS_GAIN状态,可继续音频播放。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容