Android 9 Audio系统笔记:声音焦点AudioFocusRequest说明

前言

最近发现很多小伙伴对于申请声音焦点构造AudioFocusRequest.Builde对象时传入的AUDIOFOCUS类型参数很迷惑,各种参数也不知道是用来干什么的,于是乎觉得有必要写一篇来说明一下。
声音焦点是谷歌提供的一套机制,这个机制是用来使应用间的播放与暂停等有序进行,而不至于系统声音状态混乱(比如打电话的时候又播放了音乐,导航播放的时候音乐声音没有降低),是一个十分有效的机制,各个应用遵循该机制时,才使系统声音正常有序播放。

申请声音焦点示例

申请声音焦点参数设置说明:

//1、创建声音焦点 requestBuilder
AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
attributesBuilder.setUsage(AudioAttributes.USAGE_MEDIA) // 表明是那种音源类型:这里是语音识别类型
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC);//上下文,一般用不上
//2、这个参数 AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)下面会进行详细说明
AudioFocusRequest.Builder requestBuilder = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN);//申请声音焦点类型
requestBuilder.setAudioAttributes(attributesBuilder.build())
.setAcceptsDelayedFocusGain(false) //是否接受延迟获取声音焦点
//设置对声音焦点监听
.setOnAudioFocusChangeListener(new AudioManager.OnAudioFocusChangeListener() {
   
   
@Override
public void onAudioFocusChange(int focusChange) {
   
   
   //3、对声音焦点状态变化进行处理
   //focusChange这个回调的值会在下面进行详细说明
    Log.d(TAG, "voice focus change : " + focusChange);
}
});
audioFocusVoice = requestBuilder.build();
//4、向AudioManager发起声音焦点申请。
int voiceFocus = mAudioManager.requestAudioFocus(audioFocusVoice);

2、Builder参数AUDIOFOCUS_GAIN 说明

这里直接对Builder参数进行说明,Builder的构造参数有四个:

AUDIOFOCUS_GAIN = 1;//想要长期占有焦点,失去焦点者stop播放和释放(原有声音焦点者失去声音焦点)
AUDIOFOCUS_GAIN_TRANSIENT = 2;//想要短暂占有焦点,失去焦点者pause播放(原有声音焦点者继续持有音焦点),比如语音、比如电话
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3;//想要短暂占有焦点,失去焦点者可以继续播放但是音量需要调
低,比如导航
AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4;//想要短暂占有焦点,但希望失去焦点者不要有声音播放,比如电话

可以参考官方文档给出的说明,官方给出的说明无外乎跟上面备注的一样。
那么Builder传入的参数是什么意思呢?来,这个参数实际的意思是:
**我需要申请这个类型的声音焦点,系统你告诉大家我申请什么类型的声音焦点。
我需要申请这个类型的声音焦点,系统你告诉大家我申请什么类型的声音焦点。
我需要申请这个类型的声音焦点,系统你告诉大家我申请什么类型的声音焦点。 **
重要的事情默念三遍!
有点拗口,下面来尝试解释一下。
因为声音焦点是个声音栈,系统会记录每个应用发出声音焦点申请。这个栈正常情况下是遵循先进后出的原则。
我们分别来说明一下:







1、当申请者是AUDIOFOCUS_GAIN 类型时

它告诉系统,我申请的是长期占用焦点类型AUDIOFOCUS_GAIN ,其他的应该失去声音焦点。所以当它申请成功的时,会通知栈的下面一个声音焦点失去声音焦点,并回调-1给到申请该申请者,并从声音栈中将其移除出去。

2、当申请者是AUDIOFOCUS_GAIN_TRANSIENT 类型时

它告诉系统,我申请的是短期期占用焦点类型AUDIOFOCUS_GAIN_TRANSIENT ,声音栈里其他的应该不会失去声音焦点。但是当它申请成功的时,系统会通知栈的下面一个声音焦点回调-2给到申请该申请者,这个-2意味着当前有别的应用申请了短暂声音焦点,此时收到-2后的应用应该暂停多媒体等的播放。当申请者释放掉声音焦点后,系统会将其移除出声音栈,并将它底下的移到栈顶位置,并通知其获取生音焦点(系统回调1给到该应用)。

3、当申请者是AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 类型时

它告诉系统,我申请的是短期期占用焦点类型并且希望其他应用压低声音AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK ,声音栈里其他的应该不会失去声音焦点。但是当它申请成功的时,系统会通知栈的下面一个声音焦点回调-3给到申请该申请者,这个-3 意味着当前有别的应用申请了短暂声音焦点并希望能降低音量,此时收到-3后的应用应该压低播放的音量继续播放。当申请者释放掉声音焦点后,系统会将其移除出声音栈,并将它底下的移到栈顶位置,并通知其获取生音焦点(系统回调1给到该应用)。

源码逻辑如下:

//frameworks/base/services/core/java/com/android/server/audio/FocusRequester.java
    /**
     * For a given audio focus gain request, return the audio focus loss type that will result
     * from it, taking into account any previous focus loss.
     * @param gainRequest
     * @return the audio focus loss type that matches the gain request
     */
    private int focusLossForGainRequest(int gainRequest) {
   
   //失去声音焦点处理策略
        switch(gainRequest) {
   
   
            case AudioManager.AUDIOFOCUS_GAIN:
                switch(mFocusLossReceived) {
   
   
					/*
					mFocusLossReceived 这个变量以下这方法会改变它的值:
					1、int dispatchFocusChange(int focusChange)
					2、void handleFocusLoss(int focusLoss, @Nullable final FocusRequester frWinner, boolean forceDuck)
					3、void handleFocusGain(int focusGain) AudioManager.AUDIOFOCUS_NONE
					4、初始化时会设置为AudioManager.AUDIOFOCUS_NONE,不失去声音焦点控制
					*/
                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                    case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                    case AudioManager.AUDIOFOCUS_LOSS:
                    case AudioManager.AUDIOFOCUS_NONE:
                        return AudioManager.AUDIOFOCUS_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;
                }
            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;
        }
    }

如下图,以申请AUDIOFOCUS_GAIN为例说明系统声音栈处理过程:
AUDIOFOCUS_GAIN
小结:
1、这个参数的作用就是告诉系统申请者想要申请了什么样的类型。
2、同时会根据当前声音栈状态给系统决定是否申请成功等处理。
3、同时还告诉系统,希望系统通知其他应用做相应的声音焦点变化处理,比如该暂停的暂停,该降音的降音等等




其他特殊情况

当声音栈中的栈顶者申请的是lock时(Android系统中仅有系统APP可以设置,不过定制的ROM时可以自己修改开放权限),表示我当前想持有该声音焦点,此时其他申请无法立即申请到声音焦点
这个时候setAcceptsDelayedFocusGain 就起作用了,这个函数什么意思呢?就是当处于声音栈顶有强制占有声音焦点无法立即获取到声音焦点的时候是否接受延迟获取到声音焦点。

如果setAcceptsDelayedFocusGain (true)

表示可以接受延迟获取声音焦点,此时系统会将其压入声音栈顶的底下,此时申请者未获取到声音焦点,也就是没申请到声音焦点。然而当强制占有声音焦点者释放声音焦点后,系统会将强制占有声音焦点弹出声音栈,另外则会通知这个申请者获取到声音焦点,这个时候刚才申请的会变成声音栈顶。那么这个时候表示就可以正常播放了。

如果setAcceptsDelayedFocusGain (false)

表示不接受接受延迟获取声音焦点,此时当声音栈顶有强制占有声音焦点的时候,系统直接返回申请失败其不将其压入声音栈内,申请处理流程结束。
当然,系统声音焦点处理非常复杂(想深入了解系统对声音栈处理请看Android MediaFocusControl等部分源码),这个仅仅是通常情况下的情况,但是基本满足绝大部分使用场景了,当app开发能够遵循谷歌的声音焦点规范时,就会减少大量不必要的规避措施了,程序将会更健壮。

总结

如何申请:

1、当应用想长期占用声音焦点时,并且当被其他声音焦点占用释放后再想获取声音焦点的时候使用:AUDIOFOCUS_GAIN
2、当应用想要短暂占有焦点,失去焦点者pause播放时候使用:AUDIOFOCUS_GAIN_TRANSIENT
3、当应用想要短暂占有焦点,失去焦点者可以继续播放但是音量需要调低时候,使用:AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
4、当应用想要短暂占有焦点,但希望失去焦点者不要有声音播放时候,使用:AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE


如何处理声音状态回调:

public void onAudioFocusChange(int focusChange) {
   
   
    
}

当focusChange = -1时:表示,系统通知你失去声音焦点,此时你应该停止播放
当focusChange = -2时:表示,系统通知你短暂失去声音焦点,此时你应该暂停播放
当focusChange = -3时:表示,系统通知你需要压低播放的声音,此时你应该压低音量继续播放
当focusChange = 1时:表示,系统通知你获取到声音焦点了,你可以继续播放了


好了到这里就结束了,有对Android系统的声音相关疑问可以留言,我会尽力进行解答,希望能有所收获。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章