1.音频焦点
1.1 为什么会有音频焦点机制?
在车辆环境中,可能存在多个应用或者服务需要同时或者交替播放音频,如导航、音乐、语音助手等。音频焦点机制允许系统协调这些音频流,确保在某一时刻只有一个或几个应用能够播放音频,从而避免声音混杂和冲突。
1.2 音频焦点的基本规则和逻辑
1.2.1 当某个应用或者服务需要播放声音的时候,它首先需要向系统请求音频焦点。系统会根据当前焦点的持有情况和应用请求的焦点类型,来决定是否授予音频焦点。如果应用获取到焦点,就可以开始播放音频内容;如果请求焦点被拒绝,则不能播放。
1.2.2 有音频播放需求的应用或者服务,在发起音频请求焦点前,应该去注册监听系统的音频焦点变化。在收到系统的系统监听变化的回调后,做出相应的动作,如开始播放、暂停播放或者释放焦点。
1.2.3 当音频播放完成后,应用需要去释放之前已经申请到的音频焦点,并注销音频变化监听器。
1.2.4 音频焦点机制是一套协调各个声源应用发声的规则,但是并不能强制去控制各个应用是否发声。因此,如果某些声源应用不遵守焦点规则,就会出现播放混乱的情况。比如在播放音乐的时候触发语音,正常情况需要暂停或者压低媒体音量,如果语音服务或者多媒体应用没有遵守焦点机制,就会出现两个声音混合听不清楚。
1.3 音频焦点请求的类型
-
AUDIOFOCUS_GAIN:请求独占的长时间的音频焦点,例如音乐播放的焦点请求。
-
AUDIOFOCUS_GAIN_TRANSIENT:请求短暂的独占的音频焦点,比如短暂的语音交互。
-
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:请求短暂的播报,但是允许其他音源压低并继续播放,比如播放视频的时候有新消息提示或者地图播报。
-
AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:请求短暂的独占的音频焦点,且不允许其他音源有任何声音,与 GAIN_TRANSIENT 类似,不常用。
1.4 音频焦点变化回调的类型
焦点变化回调的类型包括:
- AUDIOFOCUS_GAIN :获得音频焦点,可以播放音源。
- AUDIOFOCUS_LOSS:长时间失去音频焦点,此时应该停止播放,并释放资源。
- AUDIOFOCUS_LOSS_TRANSIENT:短暂失去焦点,需要暂停播放,但不必释放资源,很快会得到焦点。
- AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:短暂失去焦点,但不必暂停播放,需要把自己的声音压低。比如音乐播放的时候,有导航播报,音乐声音压低。
1.5 车机系统中音频焦点的三种交互类型
- 独占
这是 Android 中最常用的交互模型。在独占交互中,一次只允许一个应用持有焦点。因此,在传入的焦点请求被授予焦点的同时,现有的焦点持有者会失去焦点。 - 拒绝
在拒绝交互中,传入的请求一律会遭到拒绝。例如,在通话过程中尝试播放音乐。在这种情况下,如果拨号器为某个通话持有音频焦点,而第二个应用请求获得焦点以播放音乐,则音乐应用发出的请求会收到 AUDIOFOCUS_REQUEST_FAILED 响应。由于焦点请求遭拒,因此系统不会向当前焦点持有者分派任何焦点丢失事件。 - 并发
并发交互是 AAOS (Android 车载系统)独有的。要实现并发交互,传入的焦点请求类型必须是 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,当前焦点持有者的焦点不会丢失焦点,而新请求的应用也可以获取到焦点,允许同时发声。
处理并发的声音流通常有两种方式,一种是做音量压低处理(闪避),另一种是把不同的声音路由到不同的音响设备。
例如,如果同时提供导航提示音和媒体播放声音,媒体声音流的增益会暂时降低(闪避),以便用户能更清楚地听到导航提示。或者,导航声音流会被路由到驾驶员身旁的音响设备,媒体则在驾驶舱的其余音响设备中继续播放。
2.音频路由
音频路由指的是系统如何将音频流从一个或多个音频源(如音乐播放器、视频播放器、导航应用等)发送到哪些音频输出设备(如扬声器、耳机、蓝牙设备等)的过程。
大致的流程如下:
首先是各种音源应用产生逻辑音频流,然后在Audio Flinger中根据音频路由策略去做声音的混合,得到物理音频流,并通过HAL层输出到Android系统外部。在Android系统之外,会有一个外部混音器,用于接收Android的物理音频流,并以合适的方式将这些音频流和车辆外部的声音做混合,并将混音结果路由到合适的音响设备。
总体的结构图如下:
3.音量管理
Android系统中去设置音量值的时候,会传入一个音频流类型的参数,以便对不同的类型做的音量的控制和记忆。音频流类型定义如下:
public static final String[] STREAM_NAMES = new String[] {"STREAM_VOICE_CALL", //电话"STREAM_SYSTEM", //系统"STREAM_RING", //铃声"STREAM_MUSIC", //音乐"STREAM_ALARM", //闹钟"STREAM_NOTIFICATION",//通知"STREAM_BLUETOOTH_SCO",//蓝牙"STREAM_SYSTEM_ENFORCED",//系统强制"STREAM_DTMF", //双音多频(拨号音)"STREAM_TTS", //语音播报
};
但在底层的音量控制中,会对以上的音频类型做一次映射,根据系统指定的需求,把这些音频类型做一次分类,有些音频流类型复用同一个存储值。
通常的映射定义如下:
private final int[] STREAM_VOLUME_ALIAS_VOICE = new int[] {AudioSystem.STREAM_VOICE_CALL, // STREAM_VOICE_CALLAudioSystem.STREAM_RING, // STREAM_SYSTEMAudioSystem.STREAM_RING, // STREAM_RINGAudioSystem.STREAM_MUSIC, // STREAM_MUSICAudioSystem.STREAM_ALARM, // STREAM_ALARMAudioSystem.STREAM_RING, // STREAM_NOTIFICATIONAudioSystem.STREAM_BLUETOOTH_SCO, // STREAM_BLUETOOTH_SCO
AudioSystem.STREAM_RING, // STREAM_SYSTEM_ENFORCEDAudioSystem.STREAM_RING, // STREAM_DTMFAudioSystem.STREAM_MUSIC // STREAM_TTS
};
可以看到系统声音、铃声、提示音、系统强制音、拨号音都共用一个类型 STREAM_RING 的音量值 ;音乐媒体和语音公用一个类型 STREAM_MUSIC 的音量值 。比如当调节了音乐媒体的音量值之后,语音播报的声音也会变大。当然,这种映射关系可以根据项目的需求修改,如果语音播报的声音不想跟着媒体音一起调节,那么也可以做单独的定义。