CarAudio(五) 车载音频焦点

1.焦点矩阵

CarAudio中的音频焦点是根据音频分区来的,每个音频分区分别进行焦点分配,分区内焦点分配通过一个焦点交互矩阵进行优先级判断。该矩阵可在收到焦点请求时根据新的请求来源及当前焦点持有者的用途捕获所需行为。交互类型分以下三种:

独占交互(EXC)

一次只允许一个应用持有焦点。如果两个媒体播放器应用都在播放媒体,则只有其中一个可以持有焦点。这是大多数焦点持有者和焦点请求者都请求 AudioManager.AUDIOFOCUS_GAIN 时的默认交互方式。在这种情况下,当焦点请求时系统会将AudioManager.AUDIOFOCUS_LOSS分派给当前的焦点持有者。

拒绝交互(REJ)

焦点请求者发出的请求会一直遭拒。尝试从通知转换为闹铃就是拒绝用途交互的一个示例。在本例中,如果播放通知铃声的应用正持有音频焦点,而另一个应用要请求焦点以播放闹铃,则闹钟应用发出的焦点请求会被拒。由于焦点请求遭拒,因此系统不会向当前焦点持有者分派任何类型的焦点丢失事件。

并发交互(CON)

在这种交互模式下,请求音频焦点的车载应用可与其他应用同时播放音频。要实现并发交互,焦点请求者必须请求 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCKsetPauseWhenDucked(true)在与AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK结合使用时会正常运行

如下是一个交互矩阵的表格:在该表中,行内容表示当前正持有焦点的应用,列内容表示传入的焦点请求者。

0000.png

如果某个音乐媒体应用正持有音频焦点,某导航应用请求获得焦点,则该矩阵表示这两个交互可以同时进行。如果传入的导航应用请求的焦点为AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,则该应用的焦点请求会获批,且系统不会向媒体应用发送焦点丢失事件。该矩阵允许同时存在多个焦点持有者。在这种情况下,系统会将传入的焦点请求者与当前的各个焦点持有者进行比较,然后决定进行哪种转换。

2.交互矩阵在代码中的具体定义:

1.在CarAudioContext.java类中定义类型常量,注意必须从0开始递增。

/packages/services/Car/service/src/com/android/car/audio/CarAudioContext.java
static final int INVALID = 0;
    /*
     * Music playback
     * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.INVALID implicitly + 1
     */
    static final int MUSIC = 1;
    /*
     * Navigation directions
     * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.MUSIC implicitly + 1
     */
    static final int NAVIGATION = 2;
    /*
     * Voice command session
     * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.NAVIGATION implicitly + 1
     */
    static final int VOICE_COMMAND = 3;
    /*
     * Voice call ringing
     * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber
     *     .VOICE_COMMAND implicitly + 1
     */
    static final int CALL_RING = 4;
    /*
     * Voice call
     * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.CALL_RING implicitly + 1
     */
    static final int CALL = 5;
    /*
     * Alarm sound from Android
     * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.CALL implicitly + 1
     */
    static final int ALARM = 6;
    /*
     * Notifications
     * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber.ALARM implicitly + 1
     */
    static final int NOTIFICATION = 7;
    /*
     * System sounds
     * ::android::hardware::automotive::audiocontrol::V1_0::ContextNumber
     *     .NOTIFICATION implicitly + 1
     */
    static final int SYSTEM_SOUND = 8;
    /*
     * Emergency related sounds such as collision warnings
     */
    static final int EMERGENCY = 9;
    /*
     * Safety sounds such as obstacle detection when backing up or when changing lanes
     */
    static final int SAFETY = 10;
    /*
     * Vehicle Status related sounds such as check engine light or seat belt chimes
     */
    static final int VEHICLE_STATUS = 11;
    /*
     * Announcement such as traffic announcements
     */
    static final int ANNOUNCEMENT = 12;

2.在FocusInteraction.java类中定义一个二维矩阵sInteractionMatrix。

如:Int k = sInteractionMatrix [i] [j]

i 代表当前焦点占用类型 j 代表焦点请求者的焦点类型 k 代表焦点该如何转换,分别为:

拒绝 INTERACTION_REJECT = 0

独占 INTERACTION_EXCLUSIVE = 1

并发 INTERACTION_CONCURRENT = 2

packages/services/Car/service/src/com/android/car/audio/FocusInteraction.java
    private static final int[][] sInteractionMatrix = {
            // Each Row represents CarAudioContext of current focus holder
            // Each Column represents CarAudioContext of incoming request (labels along the right)
            // Cell value is one of INTERACTION_REJECT, INTERACTION_EXCLUSIVE,
            // or INTERACTION_CONCURRENT

            // Focus holder: INVALID
            {
                    INTERACTION_REJECT, // INVALID
                    INTERACTION_REJECT, // MUSIC
                    INTERACTION_REJECT, // NAVIGATION
                    INTERACTION_REJECT, // VOICE_COMMAND
                    INTERACTION_REJECT, // CALL_RING
                    INTERACTION_REJECT, // CALL
                    INTERACTION_REJECT, // ALARM
                    INTERACTION_REJECT, // NOTIFICATION
                    INTERACTION_REJECT, // SYSTEM_SOUND,
                    INTERACTION_EXCLUSIVE, // EMERGENCY
                    INTERACTION_EXCLUSIVE, // SAFETY
                    INTERACTION_REJECT, // VEHICLE_STATUS
                    INTERACTION_REJECT, // ANNOUNCEMENT
            },
            // Focus holder: MUSIC
            {
                    INTERACTION_REJECT, // INVALID
                    INTERACTION_EXCLUSIVE, // MUSIC
                    INTERACTION_CONCURRENT, // NAVIGATION
                    INTERACTION_EXCLUSIVE, // VOICE_COMMAND
                    INTERACTION_EXCLUSIVE, // CALL_RING
                    INTERACTION_EXCLUSIVE, // CALL
                    INTERACTION_EXCLUSIVE, // ALARM
                    INTERACTION_CONCURRENT, // NOTIFICATION
                    INTERACTION_CONCURRENT, // SYSTEM_SOUND
                    INTERACTION_EXCLUSIVE, // EMERGENCY
                    INTERACTION_CONCURRENT, // SAFETY
                    INTERACTION_CONCURRENT, // VEHICLE_STATUS
                    INTERACTION_EXCLUSIVE, // ANNOUNCEMENT
            },
            ...
  }

我们在申请焦点的时候,并不会直接传入一个CarAudioContext类型,而是根据我们在设置音频属性AudioAttributes的时候,会设置一个音频使用场景Usage,再在CarAudio将Usage于CarAudioContext进行关联,代码如下:

/packages/services/Car/service/src/com/android/car/audio/CarAudioContext.java
    
private static final SparseArray<int[]> CONTEXT_TO_USAGES = new SparseArray<>();

static {
        CONTEXT_TO_USAGES.put(MUSIC,
                new int[]{
                        AudioAttributes.USAGE_UNKNOWN,
                        AudioAttributes.USAGE_GAME,
                        AudioAttributes.USAGE_MEDIA
                });

        CONTEXT_TO_USAGES.put(NAVIGATION,
                new int[]{
                        AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE
                });
...
    
    private static final SparseIntArray USAGE_TO_CONTEXT = new SparseIntArray();

    static {
        for (int i = 0; i < CONTEXT_TO_USAGES.size(); i++) {
            @AudioContext int audioContext = CONTEXT_TO_USAGES.keyAt(i);
            @AttributeUsage int[] usages = CONTEXT_TO_USAGES.valueAt(i);
            for (@AttributeUsage int usage : usages) {
                USAGE_TO_CONTEXT.put(usage, audioContext);
            }
        }
    }

CONTEXT_TO_USAGES :CarAudioContext为key,Usage列表为value。

USAGE_TO_CONTEXT :Usage为key, CarAudioContext列表为value。且通过CONTEXT_TO_USAGES转换而来,所以我们要修改两者之间的关联的话,只需要修改CONTEXT_TO_USAGES 就行。

3.CarAudioFocus焦点申请流程

在进行系统的焦点分配时,会根据mFocusPolicy不为null时,交由自定义的AudioPolicy去进行分配。

在CarAudio中,其实就是在CarAudioService启动之后,创建 AudioPolicy和音频分区的焦点管理器CarZonesAudioFocus,

实现AudioPolicy中的焦点变化监听器。注册到AudioManager中,赋值给mFocusPolicy:

011.png

接着我们看在CarAudio中焦点的分配的流程。关键流程是

从FocusInteraction中匹配焦点矩阵,返回结果interactionResult结果,将结果通过AudioManager的requestAudioFocus返回,其他的焦点变化通过AudioManager的dispatchAudioFocusChange返回到应用监听器内:

022.png

重点看下FocusInteraction中的evaluateRequest方法:

/packages/services/Car/service/src/com/android/car/audio/FocusInteraction.java
public @FocusRequestResult int evaluateRequest(@AudioContext int requestedContext,
            FocusEntry focusHolder, List<FocusEntry> focusLosers, boolean allowDucking,
            boolean allowsDelayedFocus) {
        @AudioContext int holderContext = focusHolder.getAudioContext();
        synchronized (mLock) {
            //从焦点矩阵中匹配到对应的状态
            int[] holderRow = mInteractionMatrix[holderContext];
            switch (holderRow[requestedContext]) {
                case INTERACTION_REJECT:
                //表示当前焦点优先级更低,请求被拒绝时,如果设置了支持延时获取,则返回申请delay,否则返回申请失败
                    if (allowsDelayedFocus) {
                        return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
                    }
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                case INTERACTION_EXCLUSIVE:
                //表示当前焦点优先级更高,返回焦点请求成功,并将当前焦点持有者添加到focusLosers队列中。
                    focusLosers.add(focusHolder);
                    return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
                case INTERACTION_CONCURRENT:
                    //表示当前申请的音频可以和当前正在占用焦点的音频可混音播放,返回焦点申请成功。
                    //当前焦点持有者不支持系统自动降低音量,添加focusLosers队列。
                    if (!allowDucking
                            || focusHolder.wantsPauseInsteadOfDucking()
                            || focusHolder.receivesDuckEvents()) {
                        focusLosers.add(focusHolder);
                    }
                    return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
                default:
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
            }
        }
    }

针对添加进focusLosers队列中的焦点,会进行遍历,根据当前焦点请求的焦点类型,往AudioManager服务中更新焦点状态变化,最终调用到应用的焦点监听器中。

            final boolean permanent =
                (afi.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN);
            int lossType;
            if (permanent) {
                //当前焦点请求类型为长期焦点时,失去焦点
                lossType = AudioManager.AUDIOFOCUS_LOSS;
            } else if (allowDucking && entry.receivesDuckEvents()) {
                //如果设置了自动降音,变为混音
                lossType = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
                entry.setDucked(true);
            } else {
                //除此之外,变为暂时失去焦点
                lossType = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
            }
            //该方法内调用mAudioManager.dispatchAudioFocusChange更新焦点
            sendFocusLossLocked(entry.getAudioFocusInfo(), lossType);
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容