Audio AudioFocus流程

     AudioFocus是Android引入的一个Audio协调机制,当多方需要使用Audio资源时,可以通过AudioFocus机制来协调配合,提高用户的体验。    该机制需要开发者主动去遵守,比如A应用没遵守该机制,则其它遵守了该机制的应用是完全没办法影响A应用的。    试想下后台在播放着音乐的时候你点开了某个视频,使得后台的音乐和视频的声音一起播放,毫无关联的声音一同播放会给用户带来极差的体验,此时我们就可以通过AudioFocus机制来解决这样的问题。

       使用AudioFocus机制主要是通过android.media.AudioManager这个类来进行的 public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) 方法请求获取焦点,

如果获取成功,会返回int值AudioManager.AUDIOFOCUS_REQUEST_GRANTED,

失败则返回AudioManager.AUDIOFOCUS_REQUEST_FAILED。

通过abandonAudioFocus(OnAudioFocusChangeListener l)方法放弃焦点。 由于音频焦点是唯一的,所以可以在需要播放音乐时去申请音频焦点,如果获取到了则播放,同时正在播放的音频在失去音频焦点时停止播放或者调低音量,从而达到音频播放间的相互协调。

       对requestAudioFocus方法的参数进行解析:  OnAudioFocusChangeListener是一个接口,在这个接口里面只有一个方法需要实现 public void onAudioFocusChange(int focusChange);

该方法会在焦点状态变化的时候被调用 参数focusChange代表变化后当前的状态,一共有以下四个值:

AUDIOFOCUS_GAIN 重新获取到音频焦点时触发的状态。

AUDIOFOCUS_LOSS 失去音频焦点时触发的状态,且该状态应该会长期保持,此时应当暂停音频并释放音频相关的资源。

AUDIOFOCUS_LOSS_TRANSIENT 失去音频焦点时触发的状态,但是该状态不会长时间保持,此时应该暂停音频,且当重新获取音频焦点的时候继续播放。

AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 失去音频焦点时触发的状态,在该状态的时候不需要暂停音频,但是应该降低音频的声音。

streamType(STREAM_RING,STREAM_MUSIC,STREAM_ALARM,STREAM_NOTIFICATION,STREAM_BLUETOOTH_SCO,STREAM_DTMF,STREAM_TTS)

durationHint用来表示获取焦点的时长,同时通知其它获取了音频焦点的OnAudioFocusChangeListener该如何相互配合,有以下几个值: AUDIOFOCUS_GAIN 代表此次申请的音频焦点需要长时间持有,原本获取了音频焦点的OnAudioFocusChangeListener 接口将会回调onAudioFocusChange(int focusChange) 方法,传入的参数为AUDIOFOCUS_LOSS。

AUDIOFOCUS_GAIN_TRANSIENT 代表此次申请的音频焦点只需短暂持有,原本获取了音频焦点的OnAudioFocusChangeListener 接口将会回调onAudioFocusChange(int focusChange) 方法,传入的参数为AUDIOFOCUS_LOSS_TRANSIENT。按照官方注释:适用于短暂的音频,在接收到事件通知等情景时可使用该durationHint。

AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 代表此次申请的音频焦点只需短暂持有,原本获取了音频焦点的OnAudioFocusChangeListener 接口将会回调onAudioFocusChange(int focusChange) 方法,传入的参数为AUDIOFOCUS_LOSS_TRANSIENT。按照官方注释:在需要录音或语音识别等情景时可使用该durationHint。

AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 代表此次申请不需要暂停其它申请的音频播放,应用跟其他应用共用焦点但播放的时候其他音频会降低音量。原本获取了音频焦点的OnAudioFocusChangeListener 接口将会回调onAudioFocusChange(int focusChange) 方法,传入的参数为AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK。

 

在AudioManager的requestAudioFocus调用内部重载后的方法,根据传进来的streamType,构造了一个AudioAttributes对象向下传递,这个AudioAttributes主要是存储了一些音频流信息的属性. 这里面对falgs进行了与操作,由于之前传进来的是0,所以转换后的结果还是0.

继续AudioManager.requestAudioFocus中 public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) 里面首先在registerAudioFocusRequest中注册所有的

AudioFocusRequest private final ConcurrentHashMap<String, FocusRequestInfo> mAudioFocusIdListenerMap =         new ConcurrentHashMap<String, FocusRequestInfo>();        

 final FocusRequestInfo fri = new FocusRequestInfo(afr, (h == null) ? null :new ServiceEventHandlerDelegate(h).getHandler());

其中mAudioFocusIdListenerMap根据AudioFocusRequest中OnAudioFocusChangeListener对象的来生成一个key,value则为根据afr生成的FocusRequestInfo ,存储在了mAudioFocusIdListenerMap对象中。 而之前所述的abandonAudioFocus中有unregisterAudioFocusRequest做的操作就是remove掉mAudioFocusIdListenerMap中的OnAudioFocusChangeListener 

在AudioManager中还有一个电话相关的调用:

public void requestAudioFocusForCall(int streamType, int durationHint)

 这个调用的也是AudioService中的requestAudioFocus并且往其中设置了一个clientId为AudioSystem.IN_VOICE_COMM_FOCUS_ID的状态。 继续往下就是进入到AudioService.requestAudioFocus中首先会进行权限检查,这里面就用到了AudioSystem.IN_VOICE_COMM_FOCUS_ID 也就是说如果clientId等于AudioSystem.IN_VOICE_COMM_FOCUS_ID,且要申请到MODIFY_PHONE_STATE的权限,否则会申请焦点失败。

AudioService也只是做了中转,并没有做实际的操作,具体实现都是在MediaFocusControl.requestAudioFocus中大致过程如下:

private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>(); 最主要的是对mFocusStack栈的操作,用来维护各client的申请和释放。

1.判断mFocusStack.size() 不能超过100

2.检查当前栈顶的元素是否是Phone应用占用,如果Phone处于占用状态,那么focusGrantDelayed = true。

3. 压栈之前,需要检查当前栈中是否已经有这个应用的记录,如果有的话就删除掉。  

如果mFocusStack不为空,并且栈顶的clientId与要申请焦点的clientId相同,得到栈顶元素即FocusRequester对象。如果申请的时长和flags都相同,则表示重复申请,直接返回成功,如果如果申请的时长和flags有一个不相同,则认为需要重新申请,此时如果focusGrantDelayed = false则需要将栈顶的元素出栈并将其释放

4. // focus requester might already be somewhere below in the stack, remove it          

  removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);    

移除可能在栈中(栈顶或者栈中)其他位置存在着相同clientId的元素

a:如果要释放的应用是在栈顶,则释放之后,还需要通知先在栈顶应用,其获得了audiofocus;

b:如果要释放的应用不是在栈顶,则只是移除这个记录,不需要更改当前audiofocus的占有情况。

5.创建FocusRequester实例将请求包含的各种信息传入

AudioAttributes aa, int focusChangeHint, IBinder cb,  IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags, int sdk

6.如果focusGrantDelayed = true,那么就会延迟申请,并把此次请求FocusRequester实例入栈,但是此时记录不是被压在栈顶,而是放在lastLockedFocusOwnerIndex这个位置,也就是打电话这个记录的后面;如果focusGrantDelayed = false即不需要延迟获得焦点,同样创建FocusRequester实例,但是先要通知栈里其他记录失去焦点,然后压入栈顶,最后通知自己获得焦点成功

遍历mFocusStack,调用FocusRequester对象的handleExternalFocusGain方法 通知栈中其他元素丢失焦点流程. stackIterator.next()得到的是FocusRequester对象,因此查看FocusRequester中handleExternalFocusGain的代码

主要关注两个变量gainRequest和mFocusLossReceived, mFocusLossReceived这个值在handleFocusLoss中进行赋值的,默认值是AudioManager.AUDIOFOCUS_NONE。

 * gainRequest这个是传进来的,例如

AudioManager.AUDIOFOCUS_GAIN

 * return AudioManager.AUDIOFOCUS_LOSS

 * 若传进来的参数是 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT

 * 则return AudioManager.AUDIOFOCUS_LOSS_TRANSIENT

在handleFocusLoss中

fd.dispatchAudioFocusChange(mFocusLossReceived, mClientId);

通过mFocusDispatcher对象调用了dispatchAudioFocusChange方法,将mFocusLossReceived和mClientId传了进去。

3.1 FocusRequester构造方法的第四个参数IAudioFocusDispatcher afl

3.2 MediaFocusControl的requestAudioFocus方法的第四个参数IAudioFocusDispatcher fd。

3.3 AudioService的requestAudioFocus方法的第四个参数IAudioFocusDispatcher fd 最终在AudioManager中实现。IAudioFocusDispatcher 的回调dispatchAudioFocusChange方法。

在dispatchAudioFocusChange中通过Handler发送MSSG_FOCUS_CHANGE

在ServiceEventHandlerDelegate 的创建handleMessage在其中根据MSSG_FOCUS_CHANGE来回调,而msg.arg1为focusChange即mFocusLossReceived

listener.onAudioFocusChange(msg.arg1);

最终app中实现onAudioFocusChange根据焦点的切换做相应的控制。

  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值