车载音频插件服务

Android 14 中新增的汽车原始设备制造商 (OEM) 插件服务支持配置某些汽车组件。特别是在音频方面,我们引入了三项新的插件服务,使 OEM 能够在 AAOS 设备上灵活配置音频管理:

  • 音频焦点控件
  • 音频音量和静音控件
  • 音频闪避控件

汽车插件服务架构

下图概述了汽车服务及其与 OEM 汽车服务的关系。与应用进程和汽车服务进程类似,OEM 汽车服务进程也会占用自己的进程空间。

图像

汽车服务通过查找 config_oemCarService 中定义的组件来启动 OEM 汽车服务。如果 config 为空,则表示 OEM 服务不存在,且不会启动任何服务。该组件必须扩展 OemCarService。车载音频服务必须覆盖用于获取车载音频 OEM 服务的 API:

public final class OemCarServiceImp extends OemCarService {
    @Override
    public OemCarAudioFocusService getOemAudioFocusService();

    @Override
    public OemCarAudioDuckingService getOemAudioDuckingService();

    @Override
    public OemCarAudioVolumeService getOemAudioVolumeService();
}

如需查看示例,请参阅 packages/services/Car/tests/OemCarServiceTestApp 中定义的参考测试应用。

虽然该服务由汽车服务启动,但它不会自动继承车载音频服务可用的权限。因此,OEM 服务所需的任何权限都应通过适当的机制获取。如需查看示例,请参阅 packages/services/Car/data/etc/com.android.car.oemcarservice.testapp.xml

采用 OEM 服务架构的车载音频服务

在 AAOS 中,车载音频服务管理以下操作:

  • 音频路由
  • 音频焦点
  • 音频闪避
  • 音量和静音

在 Android 14 之前,这种行为基本上是静态的,只能通过设置进行修改(尽管是在极少数情况下)。Android 14 引入了一种车载音频服务机制,可以与 OEM 定义的组件进行通信,而该组件负责管理以下各项:

  • 音频焦点
  • 音频闪避
  • 音量和静音

下图显示了车载音频服务和汽车 OEM 服务的简化架构。车载音频服务定义了不同的钩子,可以调用车载 OEM 音频服务来管理音频行为。只有在定义了相应的 OEM 车载音频服务组件时,才会发生后者。否则,车载音频服务将使用默认行为。

图像

为了确保车载音频服务和车载 OEM 音频服务始终保持同步,对于每次调用,车载音频服务都会将音频堆栈的当前状态所需的部分传递给车载 OEM 音频服务。例如,当车载音频服务拦截到评估音频焦点的请求时,会将堆栈的当前状态传递给车载 OEM 音频服务。当前状态包括当前焦点持有者和当前焦点丢失者。焦点丢失者是指仍属于堆栈但暂时失去焦点的焦点请求。

车载音频服务必须管理车载设备中的所有音频活动。如果车载音频服务不管理音频行为的某些部分,则提供给车载 OEM 音频服务的信息是不完整的。例如,如果 OEM 通过注册自己的音频焦点政策来覆盖汽车服务中的音频焦点处理,那么车载音频服务将无法向车载 OEM 音频服务提供完整信息。这可能会影响车载 OEM 音频服务做出决策的能力,因为它可能缺少车载音频服务看不到的信息。

为了执行操作,车载音频服务会调用 OEM 汽车服务。这些调用是跨进程进行的,需要进行进程间通信 (IPC)。IPC 会增加每次调用的延迟时间。请务必尽量缩短 OEM 服务的延迟时间。

由于车载音频服务对 OEM 服务的调用是阻塞性的,因此 OEM 服务不应在直接 API 评估时调用车载音频服务。相反,车载音频服务会提供必要的信息,因此两个进程之间的调用只需单向进行。

OEM 车载音频服务定义

OEM 车载音频焦点服务

车载音频服务通过注册音频政策焦点监听器来管理来自应用的音频焦点请求。车载音频服务具有一种基于静态交互矩阵管理焦点行为的机制。该矩阵定义了三种不同的交互方式:

  • 并发交互。焦点持有者可以同时保持焦点。

  • 独占交互。传入的焦点请求会从当前焦点持有者处获取焦点。

  • 拒绝交互。根据当前焦点持有者的情况,拒绝接受传入的焦点请求。

虽然这对某些汽车用例来说已经足够,但它并不能满足由于 OEM 要求而可能有所不同的所有交互需求。为此,我们引入了 OemCarAudioFocusService

public interface OEmCarAudioFocusService {
    OemCarAuddioFocusResults evaluateAudioFocusRequest(
        OemCarAudioFocusEvaluationRequest request);
    
    void notifyAudioFocusChange(
        List<AudioFocusEntry> holder,
        List<AudioFocusEntry> losers, int zoneId);
}

每当有音频焦点请求需要评估时,系统都会从车载音频服务中调用 API evaluateAudioFocusRequest(一种会阻止返回结果的双向 API)。该请求包含有关音频堆栈当前状态的信息:

此信息可用于评估 newFocusRequest(与 focusHolders 中的当前焦点持有者以及 focusLosers 中的当前焦点丢失者相比)。API 将返回结果:

class OemCarAudioFocusResult {
    int audioZoneId;
    int audioFocusEvaluationResults;
    AudioFocusEntry focusResult;
    List<AudioFocusEntry> newLosers;
    List<AudioFocusEntry> newlyBlocked;
}

该结果包含 audioFocusEvaluationResults 中实际评估结果的相关信息,用于指明当前请求是已获准、延迟还是失败。对当前焦点堆栈所做的任何更改都应在 newLosersnewlyBlocked 条目中设置,具体取决于堆栈更改的性质。

其中,newLosers 包含之前持有焦点但现在应永久或暂时失去焦点的条目。系统会进一步从音频焦点堆栈中移除永久性焦点丢失者,并将暂时性焦点丢失者移至当前焦点丢失者堆栈,直到它们重新获得焦点或被原始焦点请求者放弃为止。无论如何,请求的焦点监听器都会收到相应的焦点丢失信息。

newlyBlocked 列表包含以前位于焦点丢失者列表中但现在被新条目阻塞的条目。阻塞可能是永久性的,也可能是暂时的;对于永久性焦点阻塞,相应条目将从堆栈中移除,并且焦点丢失信息将发送给焦点监听器。对于暂时性焦点丢失,相应条目会保留在焦点丢失者堆栈中,但系统会将一个新的焦点阻塞者添加到其阻塞者列表中,不会发送焦点丢失信息,因为在第一次阻塞时已经发送了一次。当所有当前阻塞者被移除时,请求最终会被解除阻塞;如果焦点被放弃,请求将从堆栈中移除。

第二个 API notifyAudioFocusChange 是一种单向 API,在每次请求或放弃音频焦点时都会被调用。该 API 主要用于通知 OEM 服务有关焦点变更的信息,这可能会影响 OEM 车载音频服务的行为。

焦点评估准则

在 AAOS 中,音频焦点用于管理音频播放,以及确定哪个应用应坚持为用户提供最佳体验。因此,在管理音频焦点请求时,OEM 插件服务应考虑以下因素:

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

  • 当媒体焦点处于活跃状态时,应用请求:

    • Call 使用焦点,应该能够同时或独占地接收焦点。

    • Navigation 使用焦点,应该能够同时或独占地接收焦点。

    • Assistant 使用焦点,应该能够同时或独占地接收焦点。

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

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

OEM 车载音量服务

车载音频服务通过监听音频系统的音量调整或直接从汽车输入服务监听音量键事件来管理音量键事件。在每种情况下,车载音频服务的默认行为都是根据处于活跃状态的音频播放器和音频上下文优先级列表确定要更改的音量组。

我们提供了两个音量优先级列表。第一个列表会按此顺序考虑所有音频上下文。该列表按降序排列,优先级最高的显示在顶部,优先级最低的显示在底部。例如,如果导航音频和音乐音频同时处于活跃状态,则在音量键事件期间,会更改导航音量。

  1. 导航
  2. 呼叫
  3. 音乐
  4. 公告
  5. 语音命令
  6. 来电响铃
  7. 系统声音
  8. 安全
  9. 闹钟
  10. 通知
  11. 车辆状态
  12. 紧急

为了降低音量键事件管理的复杂性,车载音频服务还提供了第二个音频上下文优先级列表:

  1. 呼叫
  2. 媒体
  3. 公告
  4. 语音命令

此列表也是按降序排列。第二个列表的目的是允许通过按键事件更改更常见的声音。不常见的声音(可能是持续时间较短的声音)只能通过音频设置界面进行管理。

实际版本的音量可通过 audioVolumeAdjustmentContextsVersion 配置来设置。该配置可以设置为 122 为默认值)。

为了提高音量管理灵活性,Android 14 中引入了 OemCarAudioVolumeService

public interface OemCarAudioVolumeService {
    OemCarvolumeChangeInfo getSuggestedGroupForVolumeChange(
OemCarAudioVolumeRequest request, int volumeAdjustment);
}

OEM 车载音频音量服务采用了一种方法,该方法接受 volumeAdjustmentOemCarAudioVolumeRequest

class OemCarAudioVolumeRequest {
    int audioZoneId;
    int callState;
    List<AudioAttributes> activePlaybackAttributes;
    List<AudioAttributes> duckedAttributes;
    List<CarVolumeGroupInfo> volumeGroupState;
}

上述请求的 activePlaybackAttributes 包含有效的音频属性。duckedAttributes 是当前闪避的所有音频属性。volumeGroupState 表示音量组的当前状态。该请求表示音频堆栈的当前状态,可用于确定应更改的音量组。结果应该在 OemCarVolumeChangeInfo 中返回:

class OemCarVolumeChangeInfo {
    boolean change;
    CarVolumeGroupInfo volumeGroupChanged;
}

change 布尔值表示是否有任何音量发生了变化,true 表示有变化,应该更新音量组。volumeGroupChanged 是应更改的实际音量组。该音量组应根据传递给 API 的原始 volumeAdjustment 参数进行更改。例如,如果结果表明导航音量组应设为静音,则布尔值为 true,并且返回的音量组应该是用于导航的音量组。

OEM 车载音频闪避服务

车载音频服务会监控音频焦点变化并向 AudioControl HAL 发送信号告知要闪避的音频设备,来管理音频闪避。当焦点发生变化时,系统会评估所有处于活跃状态的焦点持有者,以根据下面这组静态音频闪避规则来确定应该闪避的焦点持有者:

  • EMERGENCY:闪避一切声音,但来电声音除外
  • SAFETY:闪避一切声音,但紧急声音除外
  • NAVIGATION:闪避一切声音,但用于保障安全的声音和紧急声音除外
  • CALL:闪避一切声音,但用于保障安全的声音、紧急声音和导航声音除外
  • VOICE:闪避来电铃声
  • MUSIC 和 ANNOUNCEMENT:应闪避一切声音

这些规则并不详尽,OEM 仍有责任根据这些准则确定应如何闪避声音。OEM 可以根据可用要求更积极地控制这些建议。Android 14 中引入了 OemCarDuckingService

class OemCarAudioDuckingService {
List<AudioAttributes>   evaluateAttributesToDuck(
        OemCarAudioVolumeRequest request);
}

当音频焦点发生变化时,车载音频服务会调用此 API。它重复使用了 OEM 车载音量服务中引入的 OemCarAudioVolumeRequest,并包含相关信息来决定要闪避哪些属性。系统会将 API 中要闪避的音频属性的列表与当前音频状态进行比较:

  • 当前闪避的音频属性:

    • 如果在列表中,则继续被闪避
    • 如果不在列表中,则停用闪避功能
  • 当前未闪避的音频属性:

    • 如果在列表中,则进行闪避
    • 如果不在列表中,则停用闪避功能

然后,车载音频服务会确定音频属性属于哪些音频输出设备,并将这些设备分别添加到已闪避音频输出设备的列表或未闪避音频设备的列表中。该信息最终会发送到 AudioControl HAL,以在硬件级别执行所需的闪避。

下图显示了使用 OEM 闪避服务时,对焦点请求进行音频闪避控制的简化序列图:

图像

当应用通过公共音频管理器 API 请求管理音频焦点时,该序列就开始了。该请求会转发给车载音频服务以确定结果。确定音频焦点后,调用 OemCarAudioDuckingService 的车载音频服务会评估音频闪避,以确定应闪避哪些音频属性。一旦从 evaluateAttributesToDuck API 返回结果,系统就会计算要闪避的音频设备,最后将该信息发送到 AudioControl,以对音频硬件应用闪避。

OEM 车载音频服务参考实现

AAOS 在 packages/services/Car/tests/OemCarServiceTestApp 中提供了 OEM 汽车服务的参考实现,它实现了 OemCarService 以及 OemCarAudioFocusServiceOemCarAudioDuckingServiceOemCarAudioVolumeService。对于后者,每项服务都使用 XML 文件加载静态行为。例如,OemCarAudioFocusServiceImp 会加载 oem_focus_config.xml,其中包含交互矩阵。在调用 evaluateAudioFocusRequest 时,该矩阵用于评估焦点请求。

参考测试应用调试

OEM 汽车服务测试应用是 AOSP 源代码的一部分。OEM 可以根据自己的需求进行更改。如需进行调试,请使用 config_oemCarService 配置启用测试应用。

<!-- This is the component name for the OEM customization service. OEM can choose to implement
this service to customize car service behavior for different policies. If OEMs choose to
implement it, they have to implement a service extending OemCarService exposed by car-lib,
and implement the required component services.
If the component name is invalid, CarService would not connect to any OEM service.
Component name can not be a third party package. It should be pre-installed -->
<string name="config_oemCarService" translatable="false">
com.android.car.oemcarservice.testapp/.OemCarServiceImpl
</string>

如需验证 OEM 汽车服务,请对 OEM 服务使用汽车服务 dump 命令:

adb shell dumpsys car_service --oem-service

结果可能类似于以下输出:

***CarOemProxyService dump***
  mIsFeatureEnabled: true
  mIsOemServiceBound: true
  mIsOemServiceReady: true
  mIsOemServiceConnected: true
  mInitComplete: true
  OEM_CAR_SERVICE_CONNECTED_TIMEOUT_MS: 5000
  OEM_CAR_SERVICE_READY_TIMEOUT_MS: 5000
  mComponentName: com.android.car.oemcarservice.testapp/.OemCarServiceImpl

每批 dump 信息中的每个布尔值都决定了功能和服务的状态。例如,转储信息 mIsOemServiceReady 指明服务是否可供使用,其中 true 表示服务已准备就绪,false 表示服务尚未准备就绪。