音频焦点

在启动逻辑声音流之前,应用会使用与逻辑声音流相同的音频属性来请求音频焦点。应用必须尊重焦点损失,以便在汽车用例中按预期运行。

虽然我们建议发送焦点请求,但系统不会强制要求发送。因此,请将焦点视为间接控制和避免播放期间发生冲突的手段,而不是主要的音频控制机制。车辆不应依赖焦点系统来操作音频子系统。

焦点交互

为了支持 AAOS,系统会根据请求的 CarAudioContext 和当前焦点持有者的 CarAudioContext 之间的预定义交互来处理音频焦点请求。交互分为三种类型:

  • 独占
  • 拒绝
  • 并发

独占交互

这是 Android 中最常用的交互模型。

在独占交互中,一次只允许一个应用持有焦点。因此,在传入的焦点请求被授予焦点的同时,现有的焦点持有者会失去焦点。由于两个应用都播放媒体,因此仅允许一个应用持有焦点。结果就是,新启动的应用发出的焦点请求将返回 AUDIOFOCUS_REQUEST_GRANTED;而当前播放音乐的应用将收到焦点更改事件,该事件中的丢失状态与所发请求的类型相对应。

拒绝交互

在拒绝交互中,传入的请求一律会遭到拒绝。例如,在通话过程中尝试播放音乐。在这种情况下,如果拨号器为某个通话持有音频焦点,而第二个应用请求获得焦点以播放音乐,则音乐应用发出的请求会收到 AUDIOFOCUS_REQUEST_FAILED 响应。由于焦点请求遭拒,因此系统不会向当前焦点持有者分派任何焦点丢失事件。

并发交互

并发交互是 AAOS 独有的。在这种交互模式下,请求音频焦点的车载应用可与其他应用同时持有焦点。若要实现并发交互,必须满足以下条件。即:

如果满足上述条件,焦点请求将返回 AUDIOFOCUS_REQUEST_GRANTED,而当前焦点持有者的焦点不会发生任何变化。不过,如果当前焦点持有者选择接收闪避事件或在闪避时暂停,则会失去焦点,就像独占交互一样。

处理并发声音流

虽然并发交互有很多用途,但是在硬件级别跨输出设备进行混音和降低音量时要小心。我们强烈建议将允许同时播放的 CarAudioContext 路由到不同的输出设备。

通过为并发声音流提供单独的输出设备,HAL 便可在混音之前对其中一个声音流进行闪避;或者将物理声音流路由到车辆中的其他音响设备。如果在 Android 中对逻辑声音流进行混音,则增益将保持不变,并作为同一物理声音流的一部分进行传递。

例如,如果同时提供导航提示音和媒体播放声音,媒体声音流的增益会暂时降低(或闪避),以便用户能更清楚地听到导航提示。或者,导航声音流会被路由到驾驶员身旁的音响设备,媒体则在驾驶舱的其余音响设备中继续播放。

交互矩阵

下表显示了由 CarAudioService 定义的交互矩阵。每行都代表当前焦点持有者的 CarAudioContext,每列都代表传入请求的 CarAudioContext。

例如,如果导航应用请求获得焦点时音乐媒体应用持有焦点,此矩阵会指示这两种交互可以并行播放(假设满足并发交互的其他条件)。

由于并发交互的缘故,可能会存在多个焦点持有者。在这种情况下,系统会将传入的焦点请求与当前的各个焦点持有者进行比较,然后决定应用哪种交互。此时,最保守的交互会胜出。先是拒绝交互,然后是独占交互,最后是并发交互。

图 1. 音频焦点交互矩阵。

Android 11 中引入了一项新的用户设置,可让用户更改导航与通话之间的交互行为。设置后,android.car.KEY_AUDIO_FOCUS_NAVIGATION_REJECTED_DURING_CALL 会将传入的焦点请求 NAVIGATION 与当前焦点持有者 CALL 之间的交互从“并发”变为“拒绝”。如果用户不希望导航指示中断通话,则可以启用此设置。系统会为用户保留此设置,但可以动态设置,以便后续焦点请求遵循新设置。

可延迟的音频焦点

在 Android 11 中,AAOS 开始支持请求获得可延迟的音频焦点。这样一来,当非瞬态焦点请求与当前焦点持有者交互通常会遭到拒绝时,前者可以延迟。一旦焦点的变化导致延迟的请求可以获得焦点,系统就会将焦点授予该请求。

延迟音频焦点请求的规则

  • 仅限非瞬态请求。只能针对非瞬态来源发出延迟请求,以免在不相关时刻之后很长时间还播放瞬态声音。

  • 一次只能延迟一项请求。如果在已有一项延迟请求的情况下,又发出另一项延迟请求,则最初发送的延迟请求将收到 AUDIOFOCUS_LOSS 更改事件,新发送的请求将收到 AUDIOFOCUS_REQUEST_DELAYED 的同步响应。

  • 可延迟的请求必须具有 OnAudioFocusChangeListener。请求延迟后,监听器将用于在请求最终获得焦点 (AUDIOFOCUS_GAIN) 或稍后被拒绝 (AUDIOFOCUS_LOSS) 时通知请求者。

请求可延迟的焦点

如需构建可以延迟的请求,请:

  1. 使用 AudioFocusRequest.Builder#setAcceptsDelayedFocusGain

    mMediaWithDelayedFocusListener = new MediaWithDelayedFocusListener();
    
    mDelayedFocusRequest = new AudioFocusRequest
         .Builder(AudioManager.AUDIOFOCUS_GAIN)
         .setAudioAttributes(mMusicAudioAttrib)
         .setOnAudioFocusChangeListener(mMediaWithDelayedFocusListener)
         .setForceDucking(false)
         .setWillPauseWhenDucked(false)
         .setAcceptsDelayedFocusGain(true)
         .build();
    
  2. 在发出请求时,处理 AUDIOFOCUS_REQUEST_DELAYED 响应:

    int delayedFocusRequestResults = mAudioManager.requestAudioFocus(mDelayedFocusRequest);
    if (delayedFocusRequestResults == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
        // start audio playback
        return;
    }
    if (delayedFocusRequestResults == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
         // audio playback delayed to audio focus listener
         return;
    }
    
  3. 在请求获得延迟后,焦点监听器负责处理焦点变更:

    private final class MediaWithDelayedFocusListener implements
    OnAudioFocusChangeListener {
           @Override
           public void onAudioFocusChange(int focusChange) {
               synchronized (mLock) {
                   switch (focusChange) {
                       case AudioManager.AUDIOFOCUS_GAIN:
                           … // Start focus playback
                       case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                           … // Pause media transiently
                       case AudioManager.AUDIOFOCUS_LOSS:
                           … // Stop media
    

多音频区焦点管理

对于具有多个音频区的车辆,系统会为每个音频区单独管理音频焦点。因此,对一个音频区的请求不会考虑其他音频区中的焦点持有者,也不会导致其他音频区中的焦点持有者失去焦点。因此,主驾驶舱与后座娱乐系统的焦点可分开管理,从而避免某个音频区的焦点变更时另一个音频区的播放也随之中断。

对于所有应用,CarAudioService 都会自动管理焦点。焦点请求的音频区由其关联的 UserIdUID 确定(如需了解详情,请参阅多区音频路由)。

同时从多个音频区请求音频

如果应用需要同时在多个音频区播放音频,就必须在软件包中包含 AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID,以针对各个音频区请求焦点:

//Create attribute with bundle and AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID
Bundle bundle = new Bundle();
bundle.putInt(CarAudioManager.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID,
               zoneId);

AudioAttributes attributesWithZone = new AudioAttributes.Builder()
     .setUsage(AudioAttributes.USAGE_MEDIA)
     .addBundle(bundle)
     .build();

//Create focus request using built attributesWithZone

此软件包参数允许请求者替换自动音频区映射,以改用指定的音频区 ID。因此,应用可针对不同的音频区发出单独的请求。

HAL 音频焦点

从 Android 11 开始,HAL 可以代表外部声音流请求焦点。虽然这些 API 是可选的,但我们强烈建议您使用,以便启用外部声音,从而成为 Android 生态系统的最佳参与者,并提供无缝的用户体验。

关于应优先使用哪些声音,最终由 HAL 决定。因此,无论 HAL 是否被授予音频焦点,都应播放紧急声音或对保障安全至关重要的声音,即使 HAL 失去音频焦点,也应继续在适当的情况下播放这些声音。此规则也适用于政府法规要求的任何声音。

为了确保人们能够清楚地听到,HAL 应在播放紧急声音或对保障安全至关重要的声音时,适时主动将 Android 声音流静音。

2.0 版 AudioControl

2.0 版 AudioControl HAL 引入了以下新 API:

API 用途
IAudioControl#registerFocusListener 向 AudioControl HAL 注册 IFocusListener 的实例。此监听器可让 HAL 请求和放弃音频焦点。HAl 提供了一个 ICloseHandle 实例,供 Android 用来取消注册此监听器。
IAudioControl#onAudioFocusChange 向 HAL 发送通知,告知由 HAL 通过 IFocusListener 发出的焦点请求发生状态变化,包括对初始焦点请求的响应。
IFocusListener#requestAudioFocus 代表 HAL 针对指定的用法、音频区 ID 和焦点增益类型请求焦点。
IFocusListener#abandonAudioFocus 针对指定的使用行为和音频区 ID,放弃现有的 HAL 焦点请求。

HAL 可以同时拥有多个焦点请求,但每个用法与音频区 ID 的配对仅限一个请求。Android 会假定,HAL 将在请求发出后针对某个使用行为立即开始播放声音,并在放弃焦点之前继续执行同样的操作。

registerFocusListener 之外,这些请求为 oneway,以确保 Android 在处理焦点请求时不会延迟 HAL。在播放对保障安全至关重要的声音前,HAL 不应等待获得焦点。HAL 通过 IAudioControl#onAudioFocusChange 监听和响应音频焦点的变化是可选操作。

OEM 车载音频焦点服务

在 Android 14 中,AAOS 引入了汽车 OEM 插件服务,以便某些汽车组件具有可配置性。对于车载音频插件服务,OEM 可以通过该插件服务管理由车载音频服务拦截的焦点请求。这样一来,OEM 可以根据规则和法规的要求更灵活地管理焦点。因此,音频焦点交互可能因制造商和地区而异。音频焦点的基本前提仍然适用,即应用仍应请求焦点以更好地管理音频,从而增强用户体验。一般来说,某些规则仍适用于应用的音频焦点请求:

  • 在没有任何高优先级音频焦点(包括通话、紧急警报或安全通知)的情况下,应用应能暂时或永久获得音频焦点。

  • 当媒体焦点处于活跃状态时:

    • 请求通话使用焦点的应用应能够同时或独占地接收通话。

    • 请求导航使用焦点的应用应能够同时或独占地接收导航焦点。

    • 请求 Google 助理使用焦点的应用应能够同时或独占地接收使用焦点。

  • 当高优先级音频焦点(包括通话、紧急警报或安全通知)的应用处于活跃状态时,应根据需要批准或延迟传入的延迟音频焦点请求。

虽然上述建议并非详尽无遗,但有助于请求焦点的应用在没有处于活跃状态的高优先级声音时能够获得焦点。即使高优先级声音处于活跃状态,也应遵从延迟的焦点请求,并应能在高优先级声音停止后获得焦点。