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);
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容