理解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音频焦点的主要内容,如果未能解决你的问题,请参考以下文章

Android音频焦点申请处理

Android 音频焦点管理

Android音频焦点及混音策略

音频焦点系列:手写一个demo理解音频焦点与AudioMananger

片段变化后失去焦点(ANDROID TV)

Android TV - 在细节片段中失去焦点