1.车载音频
Android Automotive OS (AAOS) 是在核心 Android 音频堆栈的基础之上打造而成,以支持用作车辆信息娱乐系统的用例。AAOS 负责实现信息娱乐声音(即媒体、导航和通讯声音),但不直接负责具有严格可用性和计时要求的铃声和警告。虽然 AAOS 提供了信号和机制来帮助车辆管理音频,但最终还是由车辆来决定应为驾驶员和乘客播放什么声音,从而确保对保障安全至关重要的声音和监管声音能被确切听到,而不会中断。
当 Android 管理车辆的媒体体验时,应通过应用来代表外部媒体来源(例如电台调谐器),这类应用可以处理该来源的音频焦点和媒体键事件。
对于与汽车相关的音频支持,Android 11 进行了以下更改:
- 根据关联的用户 ID 自动选择音频区
- 新的系统用法可支持特定于汽车的声音
- HAL 音频焦点支持
- 非瞬态声音流的音频焦点延迟
- 用于控制导航和通话之间交互的用户设置
Android 声音和声音流
汽车音频系统可以处理以下声音和声音流:
图 1. 以声音流为中心的架构图
Android 管理来自 Android 应用的声音,同时控制这些应用,并根据其声音类型将声音路由到 HAL 中的输出设备:
- 逻辑声音流:在核心音频命名法中称为“声源”,使用音频属性进行标记。
- 物理声音流:在核心音频命名法中称为“设备”,在混音后没有上下文信息。
为了确保可靠性,外部声音(来自独立声源,例如安全带警告铃声)在 Android 外部(HAL 下方,甚至是在单独的硬件中)进行管理。系统实现者必须提供一个混音器,用于接受来自 Android 的一个或多个声音输入流,然后以合适的方式将这些声音流与车辆所需的外部声源组合起来。
HAL 实现和外部混音器负责确保对保障安全至关重要的外部声音能够被用户听到,而且负责在 Android 提供的声音流中进行混音,并将混音结果路由到合适的音响设备。
Android 声音
应用可以有一个或多个通过标准 Android API(如用于控制焦点的 AudioManager 或用于在线播放的 MediaPlayer)交互的播放器,以便发出一个或多个音频数据逻辑声音流。这些数据可能是单声道声音,也可能是 7.1 环绕声,但都会作为单个声源进行路由和处理。应用声音流与 AudioAttributes(可向系统提供有关应如何表达音频的提示)相关联。
逻辑声音流通过 AudioService 发送,并路由到一个(并且只有一个)可用的物理输出声音流,其中每个声音流都是混音器在 AudioFlinger 内的输出。音频属性在混合到物理声音流后将不再可用。
接下来,每个物理声音流都会传输到音频 HAL,以在硬件上呈现。在汽车应用中,呈现硬件可能是本地编解码器(类似于移动设备),也可能是车辆物理网络中的远程处理器。无论是哪种情况,音频 HAL 实现都需要提供实际样本数据并使其能被用户听见。
外部声音流
如果声音流因认证或计时原因而不应经由 Android,则可以直接发送到外部混音器。从 Android 11 开始,HAL 现在能够针对这些外部声音请求焦点,以通知 Android,使其能够采取适当措施(例如暂停媒体或阻止其他人获得焦点)。
如果外部声音流是应与 Android 正在生成的声音环境交互的媒体源(例如,当外部调谐器处于开启状态时,停止 MP3 播放),则那些外部声音流应由 Android 应用表示。此类应用将代表媒体来源(而非 HAL)请求音频焦点,并根据需要通过启动/停止外部声音源来响应焦点通知,以符合 Android 音频焦点政策规定。应用还负责处理媒体键事件,例如播放/暂停。如需控制此类外部设备,建议使用的一种机制是 HwAudioSource。
输出设备
在音频 HAL 级别,设备类型 AUDIO_DEVICE_OUT_BUS
提供用于车载音频系统的通用输出设备。总线设备支持可寻址端口(其中每个端口都是一个物理声音流的端点),并且应该是车辆内唯一受支持的输出设备类型。
系统实现可以针对所有 Android 声音使用一个总线端口,在这种情况下,Android 会将所有声音混合在一起,并将混音结果作为一个声音流进行传输。此外,HAL 可以分别为每个 CarAudioContext 提供一个总线端口,以允许并发传输任何声音类型。这样一来,HAL 实现就可以根据需要混合和闪避不同的声音。
音频上下文到输出设备的分配是通过 car_audio_configuration.xml 完成的。
麦克风输入
在捕获音频时,音频 HAL 会收到 openInputStream
调用,其中包含指示应如何处理麦克风输入的 AudioSource
参数。
VOICE_RECOGNITION
源(尤其是 Google 助理)需要一个符合以下条件的立体声麦克风流:具有回声消除效果(如果有),但不应用任何其他处理。波束成形应由 Google 助理来完成。
多声道麦克风输入
若要从具有两个以上声道(立体声)的设备捕获音频,请使用声道索引掩码,而不是定位索引掩码(例如 CHANNEL_IN_LEFT
)。例如:
final AudioFormat audioFormat = new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(44100)
.setChannelIndexMask(0xf /* 4 channels, 0..3 */)
.build();
final AudioRecord audioRecord = new AudioRecord.Builder()
.setAudioFormat(audioFormat)
.build();
audioRecord.setPreferredDevice(someAudioDeviceInfo);
如果 setChannelMask
和 setChannelIndexMask
均已设置,则 AudioRecord
仅使用由 setChannelMask
设置的值(最多两个声道)。
并发捕获
从 Android 10 开始,Android 框架支持并发捕获输入,但具有保护用户隐私的限制。作为这些限制的一部分,AUDIO_SOURCE_FM_TUNER 等虚拟来源会被忽略,因此可以与常规输入(例如麦克风)同时捕获。HwAudioSources 也不会被纳入并发捕获限制。
旨在与 AUDIO_DEVICE_IN_BUS 设备或辅助 AUDIO_DEVICE_IN_FM_TUNER 设备结合使用的应用必须依赖于以下功能:明确识别这些设备,以及使用 AudioRecord.setPreferredDevice() 绕过 Android 默认声源选择逻辑。
音频用法
AAOS 主要使用 AudioAttributes.AttributeUsages 进行路由、音量调整和焦点管理。用法用于表示播放声音流的“原因”。因此,所有声音流和音频焦点请求都应为其音频播放指定用法。如果在构建 AudioAttributes 对象时未明确设置,则用法将默认为 USAGE_UNKOWN。虽然目前会对此用法采取与 USAGE_MEDIA 一样的处理,但不应依赖此行为进行媒体播放。
系统用法
Android 11 中引入了系统用法。这些用法的行为与之前确立的用法类似,不同之处在于它们需要使用系统 API 以及 android.permission.MODIFY_AUDIO_ROUTING
。新的系统用法如下:
USAGE_EMERGENCY
USAGE_SAFETY
USAGE_VEHICLE_STATUS
USAGE_ANNOUNCEMENT
若要通过系统用法构造 AudioAttributes,请使用 AudioAttributes.Builder#setSystemUsage,而不是 setUsage。如果要通过非系统用法调用此方法,就会导致系统抛出 IllegalArgumentException。此外,如果同时在构建器上设置了系统用法和非系统用法,则在构建时将会抛出 IllegalArgumentException。
如需查看与 AudioAttributes 实例关联的用法,请调用 AudioAttributes#getSystemUsage。这将返回关联的用法或系统用法。
音频上下文
为了简化 AAOS 音频的配置,类似用法均已归入 CarAudioContext
。这些音频上下文会在整个 CarAudioService
中使用,以定义路由、音量组和音频焦点管理。
Android 11 中的音频上下文包括:
CarAudioContext | 关联的 AttributeUsages |
---|---|
MUSIC |
UNKNOWN, GAME, MEDIA |
NAVIGATION |
ASSISTANCE_NAVIGATION_GUIDANCE |
VOICE_COMMAND |
ASSISTANT, ASSISTANCE_ACCESSIBILITY |
CALL_RING |
NOTIFICATION_RINGTONE |
CALL |
VOICE_COMMUNICATION, VOICE_COMMUNICATION_SIGNALING |
ALARM |
ALARM |
NOTIFICATION |
NOTIFICATION, NOTIFICATION_* |
SYSTEM_SOUND |
ASSISTANCE_SONIFICATION |
EMERGENCY |
EMERGENCY |
SAFETY |
SAFETY |
VEHICLE_STATUS |
VEHICLE_STATUS |
ANNOUNCEMENT |
ANNOUNCEMENT |
音频上下文和用法之间的映射关系。突出显示的行用于新的系统用法。
多区音频
在汽车领域,围绕多个用户同时与平台互动并且每个用户都希望使用单独媒体的需求,出现了一系列新的用例。例如,后座上的乘客在后座显示屏上观看 YouTube 视频时,司机可以在驾驶舱中播放音乐。多区音频通过允许不同的音频源在车辆的不同音频区同时进行播放来实现此目的。
从 Android 10 开始提供的多区音频让原始设备制造商 (OEM) 能够将音频配置到单独的音频区。每个音频区由车辆内的一组设备组成,并且有各自的音量组、用于上下文的路由配置以及焦点管理。通过这种方式,可以将主驾驶舱配置为一个音频区,而将后座显示屏的耳机插孔配置为第二个音频区。
这些音频区被定义为 car_audio_configuration.xml
的一部分。然后,CarAudioService
读取该配置,并帮助 AudioService 根据关联的音频区路由音频流。每个音频区仍会根据上下文和应用 UID 定义路由规则。创建播放器时,CarAudioService
会确定播放器与哪个音频区相关联,然后根据用法确定 AudioFlinger 应将音频路由到哪个设备。
每个音频区的焦点也是单独维护的。这使得不同音频区中的应用可以单独生成音频,而不会彼此干扰,同时让应用保持关注其所在音频区内焦点的变化。CarAudioService
内中的 CarZonesAudioFocus
负责管理每个音频区的焦点。
图 2. 配置多区音频
音频 HAL
车载音频实现依赖标准 Android 音频 HAL,其中包括以下内容:
-
IDevice.hal
:负责创建输入声音流和输出声音流、处理主音量和静音操作,以及使用:-
createAudioPatch
:在设备之间创建外部-外部音频通路。 -
IDevice.setAudioPortConfig()
:为各个物理声音流提供音量。
-
-
IStream.hal
:连同输入变体和输出变体一起管理进出硬件的样本音频流。
车载设备类型
以下设备类型与车载平台相关:
设备类型 | 说明 |
---|---|
AUDIO_DEVICE_OUT_BUS |
Android 的主要输出(Android 的所有音频均通过这种方式提供给车辆)。用作消除各个上下文的信息流歧义的地址。 |
AUDIO_DEVICE_OUT_TELEPHONY_TX |
用于传输路由到手机无线装置的音频。 |
AUDIO_DEVICE_IN_BUS |
用于尚未进行分类的输入。 |
AUDIO_DEVICE_IN_FM_TUNER |
仅用于广播无线装置输入。 |
AUDIO_DEVICE_IN_TV_TUNER |
用于电视设备(如果存在)。 |
AUDIO_DEVICE_IN_LINE |
用于 AUX 输入耳机插孔。 |
AUDIO_DEVICE_IN_BLUETOOTH_A2DP |
通过蓝牙接收到的音乐。 |
AUDIO_DEVICE_IN_TELEPHONY_RX |
用于从移动网络电台接收到的与通话相关联的音频。 |
配置音频设备
Android 可见的音频设备必须在 /audio_policy_configuration.xml
中进行定义,其中包括以下组件:
- 模块名称:支持“primary”(用于汽车用例)、“A2DP”、“remote_submix”和“USB”。模块名称和相应音频驱动程序应编译到
audio.primary.$(variant).so
中。 - devicePorts:包含可从此模块访问的所有输入和输出设备(包括永久连接的设备和可移除设备)的设备描述符列表。
- 对于每种输出设备,您可以定义增益控制(包含以 millibel 为单位的 min/max/default/step 值,其中 1 millibel = 1/100 dB = 1/1000 bel)。
- 即使有多个设备的设备类型为
AUDIO_DEVICE_OUT_BUS
,也可以使用 devicePort 实例上的地址属性查找设备。
- mixPorts:包含由音频 HAL 提供的所有输出声音流和输入声音流的列表。每个 mixPort 实例都可被视为传输到 Android AudioService 的物理声音流。
- routes:定义输入和输出设备之间或声音流和设备之间可能存在的连接的列表。
以下示例定义了输出设备 bus0_phone_out,其中所有 Android 音频流都通过 mixer_bus0_phone_out 完成混音。该路由会将 mixer_bus0_phone_out
的输出声音流传递到设备 bus0_phone_out
。
<audioPolicyConfiguration version="1.0" xmlns:xi="http://www.w3.org/2001/XInclude">
<modules>
<module name="primary" halVersion="3.0">
<attachedDevices>
<item>bus0_phone_out</item>
<defaultOutputDevice>bus0_phone_out</defaultOutputDevice>
<mixPorts>
<mixPort name="mixport_bus0_phone_out"
role="source"
flags="AUDIO_OUTPUT_FLAG_PRIMARY">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000"
channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
</mixPort>
</mixPorts>
<devicePorts>
<devicePort tagName="bus0_phone_out"
role="sink"
type="AUDIO_DEVICE_OUT_BUS"
address="BUS00_PHONE">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000"
channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
<gains>
<gain name="" mode="AUDIO_GAIN_MODE_JOINT"
minValueMB="-8400"
maxValueMB="4000"
defaultValueMB="0"
stepValueMB="100"/>
</gains>
</devicePort>
</devicePorts>
<routes>
<route type="mix" sink="bus0_phone_out"
sources="mixport_bus0_phone_out"/>
</routes>
</module>
</modules>
</audioPolicyConfiguration>
2.实现车载音频 HAL
车载音频实现依赖标准 Android 音频 HAL,其中包括以下内容:
-
IDevice
(hardware/interfaces/audio/2.0/IDevice.hal
)。负责创建输入流和输出流、处理主音量和静音操作,以及使用:-
createAudioPatch
在设备之间创建 external-external 补丁程序。 -
IDevice.setAudioPortConfig()
为各个物理音频流提供音量。
-
-
IStream
(hardware/interfaces/audio/2.0/IStream.hal
)。连同输入变体和输出变体一起管理进出硬件的样本音频流。
车载设备类型
以下设备类型与车载平台相关:
设备类型 | 说明 |
---|---|
AUDIO_DEVICE_OUT_BUS |
Android 的主要输出(Android 的所有音频均通过这种方式提供给车辆)。用作消除各个上下文的信息流歧义的地址。 |
AUDIO_DEVICE_OUT_TELEPHONY_TX |
用于传输路由到手机无线装置的音频。 |
AUDIO_DEVICE_IN_BUS |
用于尚未进行分类的输入。 |
AUDIO_DEVICE_IN_FM_TUNER |
仅用于广播无线装置输入。 |
AUDIO_DEVICE_IN_TV_TUNER |
用于电视设备(如果存在)。 |
AUDIO_DEVICE_IN_LINE |
用于 AUX 输入耳机插孔。 |
AUDIO_DEVICE_IN_BLUETOOTH_A2DP |
通过蓝牙接收到的音乐。 |
AUDIO_DEVICE_IN_TELEPHONY_RX |
用于从手机无线装置接收到的与通话相关联的音频。 |
路由音频源
您应使用 AudioRecord
或相关 Android 机制捕获大多数音频源。接下来,可为数据分配 AudioAttributes并通过 AndroidTrack
播放数据,只需依赖默认的 Android 路由逻辑或通过对 AudioRecord
或 AudioTrack
对象显式调用 setPreferredDevice()
即可。
对于与外部混音器之间有专用硬件连接的来源或具有极为严苛的延迟要求的来源,您可以使用 createAudioPatch()
和 releaseAudioPatch()
来启用和停用外部设备之间的路由(在样本传输过程中无需使用 AudioFlinger
)。
配置音频设备
Android 可见的音频设备必须在 /audio_policy_configuration.xml
中进行定义,其中包括以下组件:
-
module name。支持“primary”(用于汽车用例)、“A2DP”、“remote_submix”和“USB”。模块名称和相应音频驱动程序应编译到
audio.primary.$(variant).so
中。 -
devicePorts。包含可从此模块访问的所有输入和输出设备(包括永久连接的设备和可移除设备)的设备描述符列表。
- 对于每种输出设备,您可以定义增益控制(包含以 millibel 为单位的 min/max/default/step 值,其中 1 millibel = 1/100 dB = 1/1000 bel)。
-
devicePort
实例中的地址属性可用于查找设备(即使多个设备的设备类型均为AUDIO_DEVICE_OUT_BUS
)。
-
mixPorts。包含由音频 HAL 提供的所有输出流和输入流的列表。每个
mixPort
实例都可被视为传输到 AndroidAudioService
的物理音频流。 - routes。定义输入和输出设备之间或音频流和设备之间可能存在的连接的列表。
以下示例定义了输出设备 bus0_phone_out
,其中所有 Android 音频流都通过 mixer_bus0_phone_out
完成混音。路由将 mixer_bus0_phone_out
的输出流传递到 device bus0_phone_out
。
<audioPolicyConfiguration version="1.0" xmlns:xi="http://www.w3.org/2001/XInclude">
<modules>
<module name="primary" halVersion="3.0">
<attachedDevices>
<item>bus0_phone_out</item>
<defaultOutputDevice>bus0_phone_out</defaultOutputDevice>
<mixPorts>
<mixPort name="mixport_bus0_phone_out"
role="source"
flags="AUDIO_OUTPUT_FLAG_PRIMARY">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000"
channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
</mixPort>
</mixPorts>
<devicePorts>
<devicePort tagName="bus0_phone_out"
role="sink"
type="AUDIO_DEVICE_OUT_BUS"
address="BUS00_PHONE">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000"
channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
<gains>
<gain name="" mode="AUDIO_GAIN_MODE_JOINT"
minValueMB="-8400"
maxValueMB="4000"
defaultValueMB="0"
stepValueMB="100"/>
</gains>
</devicePort>
</devicePorts>
<routes>
<route type="mix" sink="bus0_phone_out"
sources="mixport_bus0_phone_out"/>
</routes>
</module>
</modules>
</audioPolicyConfiguration>
指定 devicePorts
车载平台应该为每个输入到 Android 和从 Android 输出的物理音频流指定 devicePort
实例。对于输出音频流,每个 devicePort
实例的类型都应该为 AUDIO_DEVICE_OUT_BUS
,并采用整数(即总线 0、总线 1 等)编址。mixPort
实例与 devicePort
实例的数量之比应为 1:1,并应允许指定可以路由到每条总线的数据格式。
车载实现可以使用多个输入设备类型,包括 FM_TUNER
(保留以用于广播无线装置输入)、MIC
设备(用于处理麦克风输入)和 TYPE_AUX_LINE
(用于表示模拟线路输入)。所有其他输入流都会分配到 AUDIO_DEVICE_IN_BUS
并在通过 AudioManager.getDeviceList()
调用枚举设备时被发现。各个来源可根据 AudioDeviceInfo.getProductName()
进行区分。
您还可以将外部设备定义为端口,然后通过音频 HAL 的 IDevice::createAudioPatch
方法(通过新的 CarAudioManager
入口点提供)使用这些端口与外部硬件互动。
如果存在基于总线的音频驱动程序,必须将 audioUseDynamicRouting
标志设置为 true
:
<resources>
<bool name="audioUseDynamicRouting">true</bool>
</resources>
如需了解详情,请参阅 device/generic/car/emulator/audio/overlay/packages/services/Car/service/res/values/config.xml
。
3.实现 AudioControl HAL
Android 9 弃用了之前各版车载 HAL 中的 AUDIO_*
属性,并改为使用包含显式函数调用和类型化参数列表的专用 AudioControl HAL。
这个新的 HAL 提供了 IAudioControl
作为主接口对象,该对象负责提供出于配置和音量控制目的与车辆的音频引擎进行交互所需的入口点。系统只能包含该对象的一个实例,即 CarAudioService
启动时创建的实例。该对象是传统 Android 音频 HAL 的汽车扩展;在大多数实现中,发布音频 HAL 接口的进程还应发布 IAudioControl interfaces
。
支持的接口
AudioControl
HAL 支持以下接口:
-
getBusforContext
:在启动时针对每个上下文调用一次,以获取从ContextNumber
到busAddress
的映射。用法示例:
使车辆能够针对每个上下文告诉框架将物理输出音频流路由到哪里。对于每个上下文,必须返回有效的总线编号(0 - 总线数-1)。如果遇到无法识别的getBusForContext(ContextNumber contextNumber) generates (uint32_t busNumber);
contextNumber
,则应返回 -1。对于任何上下文,如果返回无效的busNumber
,都会路由到总线 0。
任何通过此机制与同一个busNumber
相关联的并发声音都会先通过 AndroidAudioFlinger
进行混音,然后才会作为单个音频流提供给音频 HAL。这会取代车载 HAL 属性AUDIO_HW_VARIANT
和AUDIO_ROUTING_POLICY
。 -
setBalanceTowardRight
:用于控制车载音响设备的右/左平衡设置。用法示例:
将音响设备音量调向汽车右侧 (+) 或左侧 (-)。0.0 表示在中间,+1.0 表示完全调到右侧,-1.0 表示完全调到左侧,如果值未在 -1 到 1 之间,则是错误的。setBalanceTowardRight(float value);
-
setFadeTowardFront
:用于控制车载音响设备的前/后淡化设置。用法示例:
将音响设备音量调向汽车前面 (+) 或后面 (-)。0.0 表示在中间,+1.0 表示完全调到前面,-1.0 表示完全调到后面,如果值未在 -1 到 1 之间,则是错误的。setFadeTowardFront(float value);
配置音量
Android Automotive 实现应使用硬件放大器(而非软件混音器)来控制音量。为避免产生副作用,请在 device/generic/car/emulator/audio/overlay/frameworks/base/core/res/res/values/config.xml
中将 config_useFixedVolume
标记设为 true
(根据需要叠加):
<resources>
<!-- Car uses hardware amplifier for volume. -->
<bool name="config_useFixedVolume">true</bool>
</resources>
config_useFixedVolume
标记未设置或设为 false
时,应用可以调用 AudioManager.setStreamVolume()
,并在软件混音器中按音频流类型更改音量。用户可能不希望出现这种情况,因为这会对其他应用带来潜在影响,而且使用硬件放大器接收信号时,软件混音器中的音量衰减会导致信号中的可用有效位减少。
配置音量组
CarAudioService
使用 packages/services/Car/service/res/xml/car_volume_group.xml
中定义的音量组。您可以替换此文件,以便根据需要重新定义音量组。系统在运行时会按 XML 文件中的定义顺序来识别音量组。ID 介于 0 到 N-1 之间,其中 N 是音量组的数量。示例:
<volumeGroups xmlns:car="http://schemas.android.com/apk/res-auto">
<group>
<context car:context="music"/>
<context car:context="call_ring"/>
<context car:context="notification"/>
<context car:context="system_sound"/>
</group>
<group>
<context car:context="navigation"/>
<context car:context="voice_command"/>
</group>
<group>
<context car:context="call"/>
</group>
<group>
<context car:context="alarm"/>
</group>
</volumeGroups>
此配置中使用的属性在 packages/services/Car/service/res/values/attrs.xml
中定义。
处理音量键事件
Android 定义了一些用于控制音量的键码,包括 KEYCODE_VOLUME_UP
、KEYCODE_VOLUME_DOWN
和 KEYCODE_VOLUME_MUTE
。默认情况下,Android 会将音量键事件路由到应用。Automotive 实现应强制将这些键事件路由到 CarAudioService
,以便该服务可以根据情况适当地调用 setGroupVolume
或 setMasterMute
。
如要强制实现此行为,请在 device/generic/car/emulator/car/overlay/frameworks/base/core/res/res/values/config.xml
中将 config_handleVolumeKeysInWindowManager
标记设为 true
:
<resources>
<bool name="config_handleVolumeKeysInWindowManager">true</bool>
</resources>
CarAudioManager API
CarAudioManager
使用 CarAudioService
来配置和控制车载音频系统。该管理器对于系统中的大多数应用来说都是不可见的,但车辆专用组件(如音量控制器)可以使用 CarAudioManager
API 与系统交互。
下文介绍了 Android 9 对 CarAudioManager API
所做的更改。
已弃用的 API
Android 9 通过现有的 AudioManager
getDeviceList
API 处理设备枚举,因此弃用并移除了以下车辆专用函数:
String[] getSupportedExternalSourceTypes()
String[] getSupportedRadioTypes()
Android 9 使用 AudioAttributes.AttributeUsage
或基于音量组的入口点处理音量,因此移除了以下依赖于 streamType
的 API:
void setStreamVolume(int streamType, int index, int flags)
int getStreamMaxVolume(int streamType)
int getStreamMinVolume(int streamType)
void setVolumeController(IVolumeController controller)
新增 API
Android 9 添加了以下用于控制放大器硬件的新 API(明确基于音量组):
int getVolumeGroupIdForUsage(@AudioAttributes.AttributeUsage int usage)
int getVolumeGroupCount()
int getGroupVolume(int groupId)
int getGroupMaxVolume(int groupId)
int getGroupMinVolume(int groupId)
Android 9 还提供了以下全新的系统 API 供系统 GUI 使用:
void setGroupVolume(int groupId, int index, int flags)
void registerVolumeChangeObserver(@NonNull ContentObserver observer)
void unregisterVolumeChangeObserver(@NonNull ContentObserver observer)
void registerVolumeCallback(@NonNull IBinder binder)
void unregisterVolumeCallback(@NonNull IBinder binder)
void setFadeToFront(float value)
Void setBalanceToRight(float value)
此外,Android 9 还添加了用于管理外部来源的新 API。这些 API 主要用于支持根据媒体类型将音频从外部来源路由到输出总线。借助这些 API,第三方应用还能够访问外部设备。
-
String[] getExternalSources()
:返回一个地址数组,这些地址用于识别系统中AUX_LINE
、FM_TUNER
、TV_TUNER
和BUS_INPUT
类型的可用音频端口。 -
CarPatchHandle createAudioPatch(String sourceAddress, int carUsage)
:将来源地址路由到与所提供的carUsage
关联的输出BUS
。 -
int releaseAudioPatch(CarPatchHandle patch)
:移除所提供的音频通路。如果CarPatchHandle
的创建者意外终止,则由AudioPolicyService::removeNotificationClient()
自动处理。
创建音频通路
您可以在两个音频端口(混音端口或设备端口)之间创建音频通路。通常,从混音端口到设备端口的音频通路用于播放音频,而从设备端口到混音端口的音频通路则用于捕获音频。
例如,将音频样本直接从 FM_TUNER
来源路由到媒体接收器的音频通路会绕过软件混音器。因此,您必须使用硬件混音器为接收器将来自 Android 和 FM_TUNER
的音频样本进行混音。创建直接从 FM_TUNER
来源到媒体接收器的音频通路时:
- 音量控制会应用于媒体接收器,并且应该会影响 Android 音频和
FM_TUNER
音频。 - 用户应该能够通过简单的应用切换在 Android 音频和
FM_TUNER
音频之间进行切换(应该不需要必须选择显式媒体来源)。
Automotive 实现可能还需要在两个设备端口之间创建音频通路。为此,您必须先在 audio_policy_configuration.xml
中声明设备端口和可能的路由,并将混音端口与这些设备端口相关联。
配置示例
另请参阅 device/generic/car/emulator/audio/audio_policy_configuration.xml
。
<audioPolicyConfiguration>
<modules>
<module name="primary" halVersion="3.0">
<attachedDevices>
<item>bus0_media_out</item>
<item>bus1_audio_patch_test_in</item>
</attachedDevices>
<mixPorts>
<mixPort name="mixport_bus0_media_out" role="source"
flags="AUDIO_OUTPUT_FLAG_PRIMARY">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000"
channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
</mixPort>
<mixPort name="mixport_audio_patch_in" role="sink">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000"
channelMasks="AUDIO_CHANNEL_IN_STEREO"/>
</mixPort>
</mixPorts>
<devicePorts>
<devicePort tagName="bus0_media_out" role="sink" type="AUDIO_DEVICE_OUT_BUS"
address="bus0_media_out">
<profile balance="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
<gains>
<gain name="" mode="AUDIO_GAIN_MODE_JOINT"
minValueMB="-8400" maxValueMB="4000" defaultValueMB="0" stepValueMB="100"/>
</gains>
</devicePort>
<devicePort tagName="bus1_audio_patch_test_in" type="AUDIO_DEVICE_IN_BUS" role="source"
address="bus1_audio_patch_test_in">
<profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
samplingRates="48000" channelMasks="AUDIO_CHANNEL_IN_STEREO"/>
<gains>
<gain name="" mode="AUDIO_GAIN_MODE_JOINT"
minValueMB="-8400" maxValueMB="4000" defaultValueMB="0" stepValueMB="100"/>
</gains>
</devicePort>
</devicePorts>
<routes>
<route type="mix" sink="bus0_media_out" sources="mixport_bus0_media_out,bus1_audio_patch_test_in"/>
<route type="mix" sink="mixport_audio_patch_in" sources="bus1_audio_patch_test_in"/>
</routes>
</module>
</modules>
</audioPolicyConfiguration>
音频驱动程序 API
您可以使用 getExternalSources()
获取可用来源列表(按地址进行标识),然后按音频用法在这些来源和接收器端口之间创建音频通路。音频 HAL 上相应的入口点显示在 IDevice.hal
中:
Interface IDevice {
...
/
* Creates an audio patch between several source and sink ports. The handle
* is allocated by the HAL and must be unique for this audio HAL module.
*
* @param sources patch sources.
* @param sinks patch sinks.
* @return retval operation completion status.
* @return patch created patch handle.
*/
createAudioPatch(vec<AudioPortConfig> sources, vec<AudioPortConfig> sinks)
generates (Result retval, AudioPatchHandle patch);
* Release an audio patch.
*
* @param patch patch handle.
* @return retval operation completion status.
*/
releaseAudioPatch(AudioPatchHandle patch) generates (Result retval);
...
}
注意:这些 API 钩子从 AUDIO_DEVICE_API_VERSION_3_0 便已开始提供。如需了解详情,请参阅 device/generic/car/emulator/audio/driver/audio_hw.c
。
配置音量设置界面
Android 9 将音量设置界面从音量组配置(该配置可叠加,如“配置音量组”中所述)中分离出来。这种分离可确保将来音量组配置发生更改时,无需对音量设置界面进行任何更改。
在汽车设置界面中,packages/apps/Car/Settings/res/xml/car_volume_items.xml
文件包含与每个已定义的 AudioAttributes.USAGE
相关联的界面元素(标题和图标资源)。此文件通过使用与每个 VolumeGroup 中包含的首个识别出的用法相关联的资源,合理呈现已定义的 VolumeGroup。
例如,以下示例将 VolumeGroup 定义为同时包含 voice_communication
和 voice_communication_signalling
。汽车设置界面的默认实现使用与 voice_communication
相关联的资源呈现 VolumeGroup,因为它是文件中的首个匹配项。
<carVolumeItems xmlns:car="http://schemas.android.com/apk/res-auto">
<item car:usage="voice_communication"
car:title="@*android:string/volume_call"
car:icon="@*android:drawable/ic_audio_ring_notif"/>
<item car:usage="voice_communication_signalling"
car:title="@*android:string/volume_call"
car:icon="@*android:drawable/ic_audio_ring_notif"/>
<item car:usage="media"
car:title="@*android:string/volume_music"
car:icon="@*android:drawable/ic_audio_media"/>
<item car:usage="game"
car:title="@*android:string/volume_music"
car:icon="@*android:drawable/ic_audio_media"/>
<item car:usage="alarm"
car:title="@*android:string/volume_alarm"
car:icon="@*android:drawable/ic_audio_alarm"/>
<item car:usage="assistance_navigation_guidance"
car:title="@string/navi_volume_title"
car:icon="@drawable/ic_audio_navi"/>
<item car:usage="notification_ringtone"
car:title="@*android:string/volume_ringtone"
car:icon="@*android:drawable/ic_audio_ring_notif"/>
<item car:usage="assistant"
car:title="@*android:string/volume_unknown"
car:icon="@*android:drawable/ic_audio_vol"/>
<item car:usage="notification"
car:title="@*android:string/volume_notification"
car:icon="@*android:drawable/ic_audio_ring_notif"/>
<item car:usage="notification_communication_request"
car:title="@*android:string/volume_notification"
car:icon="@*android:drawable/ic_audio_ring_notif"/>
<item car:usage="notification_communication_instant"
car:title="@*android:string/volume_notification"
car:icon="@*android:drawable/ic_audio_ring_notif"/>
<item car:usage="notification_communication_delayed"
car:title="@*android:string/volume_notification"
car:icon="@*android:drawable/ic_audio_ring_notif"/>
<item car:usage="notification_event"
car:title="@*android:string/volume_notification"
car:icon="@*android:drawable/ic_audio_ring_notif"/>
<item car:usage="assistance_accessibility"
car:title="@*android:string/volume_notification"
car:icon="@*android:drawable/ic_audio_ring_notif"/>
<item car:usage="assistance_sonification"
car:title="@*android:string/volume_unknown"
car:icon="@*android:drawable/ic_audio_vol"/>
<item car:usage="unknown"
car:title="@*android:string/volume_unknown"
car:icon="@*android:drawable/ic_audio_vol"/>
</carVolumeItems>
上述配置中使用的属性和值在 packages/apps/Car/Settings/res/values/attrs.xml
中声明。音量设置界面使用以下基于 VolumeGroup
的 CarAudioManager
API:
-
getVolumeGroupCount()
:用于了解应绘制多少个控件。 -
getGroupMinVolume()
和getGroupMaxVolume()
:用于获取音量上限和下限。 -
getGroupVolume()
:用于获取当前音量。 -
registerVolumeChangeObserver()
:用于获取音量更改通知。
4.交互序列示例
在下列汽车音频示例中,车机搭载 Android 9,并安装收音机应用和导航应用。此外,车辆调谐器在外部布线,并通过扬声器播放。在实际使用场景中,采用如下做法可能会大有好处:将调谐器处理为 Android 的输入,让收音机应用从调谐器读取数据并将其写入 AudioTrack
对象。
用户启动收音机
在此交互序列中,当用户对收音机应用中的某个预设频率按播放时,车辆内不播放任何媒体内容。收音机应用必须获得焦点,然后调谐器才能通过扬声器播放声音。
图 1. 收音机获得焦点,调谐器通过扬声器播放声音
- 收音机:“调到 FM 96.5”。
- 收音机:请求焦点 GAIN。
- AudioManager:授予 GAIN。
- 收音机:
createAudioPatch()
- 收音机:“播放调谐器输出”。
- 外部布线的调谐器:混音器使调谐器音频路由到放大器。
收音机闪避导航提示
在此交互序列中,当导航应用为下一个转弯通知生成导航提示时,收音机正在播放内容。导航应用必须先从 AudioManager
获得瞬时焦点,然后才能播放导航提示。
图 2. 收音机播放闪避导航提示
- 收音机:“播放调谐器输出”。
- 外部布线的调谐器:混音器使调谐器音频路由到放大器。
- 导航:从
AudioManager
请求焦点 GAIN TRANSIENT。 - AudioManager:向导航提供 GAIN TRANSIENT。
- 导航:打开信息流,发送数据包。
- 导航:在 bus1 上路由上下文 GUIDANCE。
- 混音器:闪避调谐器,以通过扬声器播放 bus1 GUIDANCE。
- 导航:通知结束,关闭数据流。
- 导航:放弃焦点。
AudioManager
认为收音机播放可以闪避,并且通常会在不通知收音机应用的情况下对音乐流应用一个闪避因子。不过,通过叠加 framework/base/core/res/res/values/config.xml
并将 config_applyInternalDucking
设置为 false
,可以绕过框架闪避,因此,外部调谐器会继续提供声音,并且收音机应用未发现任何变化。混音器(位于 HAL 下游)负责合并这两个输入,并且可以选择是闪避收音机播放,还是将收音机播放移至后置扬声器。
导航提示播放完毕后,导航应用将释放焦点,收音机播放将恢复。
用户启动有声读物应用
在此交互序列中,用户启动有声读物应用,导致收音机播放停止(按流式传输音乐应用中的“播放”是类似的触发器)。
图 3. 有声读物从收音机播放处夺取焦点
- 有声读物:从
AudioManager
请求 GAIN 上下文 MEDIA。 - 收音机失去焦点:
- AudioManager:LOSS。
- 收音机:
releaseAudioPatch()
- 有声读物获得焦点:
- 授予 GAIN,在 bus0 上路由上下文 MEDIA
- 打开信息流,发送 MEDIA 数据包。
有声读物应用发起的焦点请求不是瞬时的,因此上一个焦点持有者(收音机应用)将收到永久性焦点丢失信号;收音机应用会断开连接到调谐器的补丁程序,以此作为响应。混音器停止接收调谐器信号,并开始处理通过音频 HAL 传输的音频(它还可以选择性地在从收音机过渡到有声读物的过程中执行交错淡出)。
导航提示获得焦点
在此交互序列中,当导航应用生成导航提示时,系统正在播放有声读物。
图 4. 导航提示从有声读物处夺取焦点
- 有声读物:正在流式传输 MEDIA 数据包,焦点未并发。
- 导航:请求 GAIN TRANSIENT。
- AudioManager:LOSS TRANSIENT。
- 有声读物:停止。
- AudioManager:授予 GAIN TRANSIENT。
- 导航:打开信息流,发送数据包。
- 导航:在 bus1 上路由上下文 GUIDANCE。
- 混音器:播放 bus1 (GUIDANCE)。
- 导航:通知结束,关闭数据流。
- 导航:放弃焦点。
- 有声读物:GAIN。
- 有声读物:重新启动。
由于有声读物应用的原始 AudioFocusRequest
(在启动 AudioTrack
时发送)包含 AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS
标志,因此 AudioManager
发现无法对有声读物应用进行闪避处理。取而代之的是,AudioManager
会向有声读物应用发送一条 AUDIOFOCUS_LOSS_TRANSIENT
消息,有声读物应用应该通过暂停播放来进行响应。
导航应用现在可以不间断地播放导航提示。导航提示播放完毕后,有声读物将重新获得焦点并恢复播放。