理解Android音频焦点
Posted zhanghui_cuc
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了理解Android音频焦点相关的知识,希望对你有一定的参考价值。
在android系统中可能会有多个应用程序会播放音频,所以需要考虑他们之间该如何交互,为了避免多个应用程序同时播放音乐,Android 系统使用音频焦点来进行统一管理,即只有获得了音频焦点的应用程序才可以播放音乐。
我们的应用在开始播放音频文件前,首先应该请求获得音频焦点,并且应该同时注册监听音频焦点的丢失通知,即如果音频焦点被系统或其他的应用程序抢占时,应该做出什么响应。
“音频焦点”这个翻译可能有点难以理解。其实audio focus的本意是说用户在同一时刻只能聚焦(focus on)在一个音频流的播放上,不可能同时听两个播放的音频流,由此演化出了audio focus的概念。
Android平台上的音频焦点分为以下四类
AudioManager#AUDIOFOCUS_GAIN 永久性的音频焦点。
AudioManager#AUDIOFOCUS_GAIN_TRANSIENT 暂时性的音频焦点。
AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 请求附带“降低音量”的暂时性焦点,表示您只希望在短时间内播放音频,并允许前一个持有焦点的应用在降低其音量输出的情况下继续播放,也就是允许两个音频流同时出声。特别适合于语音导航、语音助手的场景使用。
AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 请求暂时性的音频焦点,同时不希望系统播放任何其他音频,常用于录音或者需要做语音识别的场景。
因为每个焦点类型都是以整型定义的,所以可以通过抓日志adb logcat | egrep -i focus来从日志判断某个应用申请的是什么类型的音频焦点。比如我就发现google的语音助手申请的是AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK类型的焦点,而国内某语音助手申请的就是AUDIOFOCUS_GAIN_TRANSIENT类型的焦点。
关于音频焦点的申请,在API 26之后有了变化。可以直接做一个工具类,专门处理音频焦点的申请和释放,基本代码如下
private int requestFocus26() {
if (focusRequest == null) {
AudioFocusRequest.Builder builder = new AudioFocusRequest.Builder(focusType);
focusRequest= builder.setAudioAttributes(attributes)
.setWillPauseWhenDucked(true)//看函数名字貌似是在其他应用申请AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK类型的焦点时,决定当前应用是否要暂停音频播放。但其实这里有一个小坑,就是这个方法名字很有迷惑性,它其实是告诉系统使用你定义的回调来处理duck事件,而不是执行自动降低音量,也不是直接执行暂停。
.setOnAudioFocusChangeListener(focusListener)
.build();
}
return audioManager.requestAudioFocus(focusRequest);
}
private void abandonFocus26() {
if (focusRequest != null) {
audioManager.abandonAudioFocusRequest(focusRequest);
}
}
public void requestFocus() {
if (!hasFocus) {//音频焦点千万不要重复申请
int ret;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ret = requestFocus26();
} else {
ret = audioManager.requestAudioFocus(
focusListener,
AudioManager.STREAM_MUSIC,
focusType);
}
hasFocus = ret == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
}
public void abandonFocus() {
if (!hasFocus) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
abandonFocus26();
} else {
audioManager.abandonAudioFocus(focusListener);
}
hasFocus = false;
}
与四种音频焦点对应的是四种音频焦点通知事件
AUDIOFOCUS_LOSS 永远失去焦点,此时应该abandonFocus。
AUDIOFOCUS_LOSS_TRANSIENT 暂时失去焦点,不用abandonFocus。
对于以上两种事件,都应该暂停当前应用的音频播放。
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 暂时失去焦点,但是可以不用暂停播放,只用降低音量即可。
AUDIOFOCUS_GAIN 获得音频焦点。如果之前因为失去焦点而暂停播放,此时可以恢复播放。
最后说一下audio focus的实现原理:它利用了栈的数据结构。
栈顶的使用者拥有AudioFocus,在申请AudioFocus成功时,申请者被放置在栈顶,同时,通知之前在栈顶的使用者,告诉它新的申请者抢走了AudioFocus。当释放AudioFocus时,使用者将从栈中被移除。如果这个使用者位于栈顶,则表明释放前它拥有AudioFocus,因此AudioFocus将被返还给新的栈顶。
相关的代码可以参考frameworks/base/services/core/java/com/android/server/audio/MediaFocusControl.java
代码分析可以参考这里。
以上是关于理解Android音频焦点的主要内容,如果未能解决你的问题,请参考以下文章