Android AudioFocus音频焦点机制学习和理解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yus201120/article/details/81774356

1、为什么会有音频焦点机制?
我们android系统里面会安装各种多媒体软件,如果不制定一个有效合理的规则,各个应用各自为政,那么可能就会出现各种播放器、软件的混音。音频焦点机制规定某一时刻只能有一个应用获取到声音的焦点,这个时候就可以发出声音。当然,在这个应用获取到焦点之前,需要通知其他所用的应用失去焦点。

2、使用音频焦点

//获取焦点
AudioManager mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        mAudioManager.requestAudioFocus(cl, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);

requestAudioFocus方法有三个参数
第一个参数:OnAudioFocusChangeListener ,此为一个监听控制器,通过这个监听器可以知道自己获取到焦点或者失去焦点。
第二个参数:streamType音频流类型,焦点获得之后的数据传输类型,这个参数不会影响焦点机制,不同的音频流类型同样遵守一个焦点机制。
第三个参数:durationHint,获得焦点的时间长短,定义了四种类型
a、AUDIOFOCUS_GAIN //长时间获得焦点,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS
b、AUDIOFOCUS_GAIN_TRANSIENT //短暂性获得焦点,用完应立即释放,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
c、AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK //短暂性获得焦点并降音,可混音播放,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
d、AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE //短暂性获得焦点,录音或者语音识别,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT
我们通常使用前面三种类型就可以了。

看看OnAudioFocusChangeListener 的实现:

OnAudioFocusChangeListener cl = new OnAudioFocusChangeListener() {

        @Override
        public void onAudioFocusChange(int focusChange) {
            switch(focusChange){
            case AudioManager.AUDIOFOCUS_LOSS:
                //长时间丢失焦点,这个时候需要停止播放,并释放资源。根据不同的逻辑,有时候还会释放焦点
                mAudioManager.abandonAudioFocus(cl);
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                //短暂失去焦点,这时可以暂停播放,但是不必要释放资源,因为很快又会获取到焦点
                break;
            case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                //短暂失去焦点,但是可以跟新的焦点拥有者同时播放,并做降噪处理
                break;
            case AudioManager.AUDIOFOCUS_GAIN:
                //获得了音频焦点,可以播放声音
                break;
            }
        }
    };

3、获取音频焦点机制流程分析
我们调用AudioManager请求焦点,并在重构方法里面判断参数合法值,然后注册监听,通过Binder通信,和系统服务AudioService通信。我们看到OnAudioFocusChangeListener这个回调监听并没有发送给AudioService,取而代之的是mAudioFocusDispatcher这个参数作为和跨进程回调的桥梁。

requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint)
.......
registerAudioFocusListener(l);
.......
 IAudioService service = getService();
        try {
            status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack,
                   mAudioFocusDispatcher, getIdForAudioFocusListener(l),
                   getContext().getOpPackageName() /* package name */, flags,
                   ap != null ? ap.cb() : null);
       } catch (RemoteException e) {
           throw e.rethrowFromSystemServer();
       }

IAudioFocusDispatcher 的设计很简洁,主要就是把从AudioService获取到的消息通过handler机制,交给另外的线程处理,从代码看到是交给了请求焦点的线程处理。

private final IAudioFocusDispatcher mAudioFocusDispatcher = new IAudioFocusDispatcher.Stub() {

        public void dispatchAudioFocusChange(int focusChange, String id) {
            final Message m = mServiceEventHandlerDelegate.getHandler().obtainMessage(
                    MSSG_FOCUS_CHANGE/*what*/, focusChange/*arg1*/, 0/*arg2 ignored*/, id/*obj*/);
            mServiceEventHandlerDelegate.getHandler().sendMessage(m);
        }

    };

AudioService是运行在system_server进程里面的系统服务,其中维护了一个栈:Stack mFocusStack,此为维护焦点的关键。
申请焦点主要是如下几点:
a、检查当前栈顶的元素是否是Phone应用占用,如果Phone处于占用状态,那么focusGrantDelayed = true。
b、压栈之前,需要检查当前栈中是否已经有这个应用的记录,如果有的话就删除掉。
c、如果focusGrantDelayed = true,那么就会延迟申请,并把此次请求FocusRequester实例入栈,但是此时记录不是被压在栈顶,而是放在lastLockedFocusOwnerIndex这个位置,也就是打电话这个记录的后面;如果focusGrantDelayed = false,不需要延迟获得焦点,同样创建FocusRequester实例,但是先要通知栈里其他记录失去焦点,然后压入栈顶,最后通知自己获得焦点成功。

 boolean focusGrantDelayed = false;
           if (!canReassignAudioFocus()) { //这里判断焦点是否处于电话状态
                if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                } else {
                    // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
                    // granted right now, so the requester will be inserted in the focus stack
                    // to receive focus later
                    focusGrantDelayed = true;
                }
            }          
// focus requester might already be somewhere below in the stack, remove it 此处便是移除栈里面相同clientId的记录
            removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
//创建新的FocusRequester实例,为入栈做准备
final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
                   clientId, afdh, callingPackageName, Binder.getCallingUid(), this);

 if (focusGrantDelayed) {
          // focusGrantDelayed being true implies we can't reassign focus right 
          // which implies the focus stack is not empty.延迟
            final int requestResult = pushBelowLockedFocusOwners(nfr);
           if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
               notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);
            }
           return requestResult;
   } else {
               // propagate the focus change through the stack没有延迟
               if (!mFocusStack.empty()) {
                    propagateFocusLossFromGain_syncAf(focusChangeHint);
               }

               // push focus requester at the top of the audio focus stack
               mFocusStack.push(nfr);
           }
             notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
              AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
    }

4、释放音频焦点流程
释放音频焦点会有以下两种情况:
a:如果要释放的应用是在栈顶,则释放之后,还需要通知先在栈顶应用,其获得了audiofocus;
b:如果要释放的应用不是在栈顶,则只是移除这个记录,不需要更改当前audiofocus的占有情况。

 private void removeFocusStackEntry(String clientToRemove, boolean signal,
            boolean notifyFocusFollowers) {
        // is the current top of the focus stack abandoning focus? (because of request, not death)
        if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
      { //释放焦点的应用端在栈顶
            //Log.i(TAG, "   removeFocusStackEntry() removing top of stack");
            FocusRequester fr = mFocusStack.pop();
            fr.release();
            if (notifyFocusFollowers) {
                final AudioFocusInfo afi = fr.toAudioFocusInfo();
                afi.clearLossReceived();
                notifyExtPolicyFocusLoss_syncAf(afi, false);
            }
            if (signal) {
                // notify the new top of the stack it gained focus
                notifyTopOfAudioFocusStack();
            }
        } else {
            //释放焦点的应用端不在栈顶
            // focus is abandoned by a client that's not at the top of the stack,
            // no need to update focus.
            // (using an iterator on the stack so we can safely remove an entry after having
            //  evaluated it, traversal order doesn't matter here)
            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();
                    fr.release();
                }
            }
        }
    }

猜你喜欢

转载自blog.csdn.net/yus201120/article/details/81774356