车载音频(CarAudio)

1.概览

  Android Automotive OS (AAOS) 是在核心 Android 音频堆栈的基础之上打造而成,以支持用作车辆信息娱乐系统的用例。AAOS 负责实现信息娱乐声音(即媒体、导航和通讯声音),但不直接负责具有严格可用性和计时要求的铃声和警告。虽然 AAOS 提供了信号和机制来帮助车辆管理音频,但最终还是由车辆来决定应为驾驶员和乘客播放什么声音,从而确保对保障安全至关重要的声音和监管声音能被确切听到,而不会中断。

  当 Android 管理车辆的媒体体验时,应通过应用来代表外部媒体来源(例如电台调谐器),这类应用可以处理该来源的音频焦点和媒体键事件。

1.1 Android 声音和声音流

汽车音频系统可以处理以下声音和声音流:

image.png

  Android 管理来自 Android 应用的声音,同时控制这些应用,并根据其声音类型将声音路由到 HAL 中的输出设备:

  • 逻辑声音流:在核心音频命名法中称为“声源”,使用音频属性进行标记。
  • 物理声音流:在核心音频命名法中称为“设备”,在混音后没有上下文信息。

  为了确保可靠性,外部声音(来自独立声源,例如安全带警告铃声)在 Android 外部(HAL 下方,甚至是在单独的硬件中)进行管理。系统实现者必须提供一个混音器,用于接受来自 Android 的一个或多个声音输入流,然后以合适的方式将这些声音流与车辆所需的外部声源组合起来。

  HAL 实现和外部混音器负责确保对保障安全至关重要的外部声音能够被用户听到,而且负责在 Android 提供的声音流中进行混音,并将混音结果路由到合适的音响设备。

1.1.1 Android 声音

  应用可以有一个或多个通过标准 Android API(如用于控制焦点的 AudioManager 或用于在线播放的 MediaPlayer)交互的播放器,以便发出一个或多个音频数据逻辑声音流。这些数据可能是单声道声音,也可能是 7.1 环绕声,但都会作为单个声源进行路由和处理。应用声音流与 AudioAttributes(可向系统提供有关应如何表达音频的提示)相关联。

  逻辑声音流通过 AudioService 发送,并路由到一个(并且只有一个)可用的物理输出声音流,其中每个声音流都是混音器在 AudioFlinger 内的输出。音频属性在混合到物理声音流后将不再可用。

  接下来,每个物理声音流都会传输到音频 HAL,以在硬件上呈现。在汽车应用中,呈现硬件可能是本地编解码器(类似于移动设备),也可能是车辆物理网络中的远程处理器。无论是哪种情况,音频 HAL 实现都需要提供实际样本数据并使其能被用户听见。

1.1.2 外部声音流.

  如果声音流因认证或计时原因而不应经由 Android,则可以直接发送到外部混音器。从 Android 11 开始,HAL 现在能够针对这些外部声音请求焦点,以通知 Android,使其能够采取适当措施(例如暂停媒体或阻止其他人获得焦点)。

  如果外部声音流是应与 Android 正在生成的声音环境交互的媒体源(例如,当外部调谐器处于开启状态时,停止 MP3 播放),则那些外部声音流应由 Android 应用表示。此类应用将代表媒体来源(而非 HAL)请求音频焦点,并根据需要通过启动/停止外部声音源来响应焦点通知,以符合 Android 音频焦点政策规定。应用还负责处理媒体键事件,例如播放/暂停。如需控制此类外部设备,建议使用的一种机制是 HwAudioSource。

1.2 输出设备

  在音频 HAL 级别,设备类型 AUDIO_DEVICE_OUT_BUS 提供用于车载音频系统的通用输出设备。总线设备支持可寻址端口(其中每个端口都是一个物理声音流的端点),并且应该是车辆内唯一受支持的输出设备类型。

  系统实现可以针对所有 Android 声音使用一个总线端口,在这种情况下,Android 会将所有声音混合在一起,并将混音结果作为一个声音流进行传输。此外,HAL 可以分别为每个 CarAudioContext 提供一个总线端口,以允许并发传输任何声音类型。这样一来,HAL 实现就可以根据需要混合和闪避不同的声音。

  音频上下文到输出设备的分配是通过 car_audio_configuration.xml 完成的。

1.3 麦克风输入

  在捕获音频时,音频 HAL 会收到 openInputStream 调用,其中包含指示应如何处理麦克风输入的 AudioSource 参数。

  VOICE_RECOGNITION 源(尤其是 Google 助理)需要一个符合以下条件的立体声麦克风流:具有回声消除效果(如果有),但不应用任何其他处理。波束成形应由 Google 助理来完成。

1.3.1 多声道麦克风输入

  若要从具有两个以上声道(立体声)的设备捕获音频,请使用声道索引掩码,而不是定位索引掩码(例如 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 设置的值(最多两个声道)。

1.3.2 并发捕获

  从 Android 10 开始,Android 框架支持并发捕获输入,但具有保护用户隐私的限制。作为这些限制的一部分,AUDIO_SOURCE_FM_TUNER 等虚拟来源会被忽略,因此可以与常规输入(例如麦克风)同时捕获。HwAudioSources 也不会被纳入并发捕获限制。

  旨在与 AUDIO_DEVICE_IN_BUS 设备或辅助 AUDIO_DEVICE_IN_FM_TUNER 设备结合使用的应用必须依赖于以下功能:明确识别这些设备,以及使用 AudioRecord.setPreferredDevice() 绕过 Android 默认声源选择逻辑。

1.4 音频用法

  AAOS 主要使用 AudioAttributes.AttributeUsages 进行路由、音量调整和焦点管理。用法用于表示播放声音流的“原因”。因此,所有声音流和音频焦点请求都应为其音频播放指定用法。如果在构建 AudioAttributes 对象时未明确设置,则用法将默认为 USAGE_UNKOWN。虽然目前会对此用法采取与 USAGE_MEDIA 一样的处理,但不应依赖此行为进行媒体播放。

1.4.1 系统用法

  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。这将返回关联的用法或系统用法。

1.4.2 音频上下文

  为了简化 AAOS 音频的配置,类似用法均已归入 CarAudioContext。这些音频上下文会在整个 CarAudioService 中使用,以定义路由、音量组和音频焦点管理。

Android 11 中的音频上下文包括:

image.png

  音频上下文和用法之间的映射关系。突出显示的行用于新的系统用法

1.5 多区音频

  在汽车领域,围绕多个用户同时与平台互动并且每个用户都希望使用单独媒体的需求,出现了一系列新的用例。例如,后座上的乘客在后座显示屏上观看 YouTube 视频时,司机可以在驾驶舱中播放音乐。多区音频通过允许不同的音频源在车辆的不同音频区同时进行播放来实现此目的。

  从 Android 10 开始提供的多区音频让原始设备制造商 (OEM) 能够将音频配置到单独的音频区。每个音频区由车辆内的一组设备组成,并且有各自的音量组、用于上下文的路由配置以及焦点管理。通过这种方式,可以将主驾驶舱配置为一个音频区,而将后座显示屏的耳机插孔配置为第二个音频区。

  这些音频区被定义为 car_audio_configuration.xml 的一部分。然后,CarAudioService 读取该配置,并帮助 AudioService 根据关联的音频区路由音频流。每个音频区仍会根据上下文和应用 UID 定义路由规则。创建播放器时,CarAudioService 会确定播放器与哪个音频区相关联,然后根据用法确定 AudioFlinger 应将音频路由到哪个设备。

  每个音频区的焦点也是单独维护的。这使得不同音频区中的应用可以单独生成音频,而不会彼此干扰,同时让应用保持关注其所在音频区内焦点的变化。CarAudioService 内中的 CarZonesAudioFocus 负责管理每个音频区的焦点。

image.png

1.6 音频 HAL

车载音频实现依赖标准 Android 音频 HAL,其中包括以下内容:

  • IDevice.hal:负责创建输入声音流和输出声音流、处理主音量和静音操作,以及使用:
  • createAudioPatch:在设备之间创建外部-外部音频通路。
  • IDevice.setAudioPortConfig():为各个物理声音流提供音量。
  • IStream.hal:连同输入变体和输出变体一起管理进出硬件的样本音频流。

1.6.1 车载设备类型

以下设备类型与车载平台相关:

image.png

1.6.2 配置音频设备

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)。连同输入变体和输出变体一起管理进出硬件的样本音频流。

2.1 车载设备类型

以下设备类型与车载平台相关:


image.png

2.2 路由音频源

  应使用 AudioRecord 或相关 Android 机制捕获大多数音频源。接下来,可为数据分配 AudioAttributes 并通过 AndroidTrack 播放数据,只需依赖默认的 Android 路由逻辑或通过对 AudioRecord 或 AudioTrack 对象显式调用 setPreferredDevice() 即可。

  对于与外部混音器之间有专用硬件连接的来源或具有极为严苛的延迟要求的来源,您可以使用 createAudioPatch() 和 releaseAudioPatch() 来启用和停用外部设备之间的路由(在样本传输过程中无需使用 AudioFlinger)。

2.3 配置音频设备

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.4 指定 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.多区

3.1 整体介绍

  在汽车领域,围绕多个用户同时与平台互动并且每个用户都希望使用单独媒体的需求,出现了一系列新的用例。例如,后座上的乘客在后座显示屏上观看 YouTube 视频时,司机可以在驾驶舱中播放音乐。多可用区音频允许不同的音频源通过车辆的不同可用区同时进行播放,从而实现此功能。

  Android 10 中的多可用区音频使原始设备制造商 (OEM) 能够将音频配置为不同的可用区。每个可用区由车辆内的一组设备组成,并且有各自的音量组、用于上下文的路由配置以及焦点管理。这样,主驾驶舱可配置为一个音频可用区,而后座显示屏的耳机插孔可配置为第二个可用区。

  可用区定义为 car_audio_configuration.xml 的一部分。然后,CarAudioService 会读取该配置,并帮助 AudioService 根据关联可用区路由音频流。每个可用区仍会根据上下文和应用 UID 定义路由规则。创建播放器时,CarAudioService 会确定播放器与哪个可用区相关联,然后根据使用情况,确定 AudioFlinger 应将音频路由到哪个设备。

  系统为每个音频可用区单独维护焦点。这使得不同可用区中的应用可以单独生成音频,而不会彼此干扰,同时让应用保持关注其所在可用区内焦点的变化。CarAudioService 内的 CarZonesAudioFocus 负责管理每个可用区的焦点。

image.png

3.1.2 配置多个可用区

  在 Android 10 中,car_audio_configuration.xml 取代了 car_volumes_groups.xml 和 IAudioControl.getBusForContext。新的配置文件中定义了可用区列表。每个可用区都有一个或多个音量组及其关联设备,而每台设备都有其应在该可用区内进行路由的上下文。所有上下文都必须在各个可用区进行表示。

  audio_policy 通常位于 vendor 分区中,表示主板的音频硬件配置。car_audio_configuration.xml 中引用的所有设备都将在 audio_policy_configuration.xml 中进行定义。

  1. 启用多可用区支持

如果存在多可用区音频,则必须将 audioUseDynamicRouting 标志设置为 true:

<resources>
    <bool name="audioUseDynamicRouting">true</bool>
</resources>

如果设置为 false,CarAudioService 将回退为使用 car_volumes_groups.xml 和 IAudioControl.getBusForContext。虽然 HAL 层不需要进行任何更改,但 car_audio_configuration.xml 文件定义了设备与上下文的关系,因此在 Android 10 中已弃用 IAudioControl.getBusForContext。

  1. 主可用区audioUseDynamicRouting

  主可用区是系统默认向其路由所有音频的位置。只能有一个主可用区,该可用区在配置中通过属性 isPrimary="true" 进行指示。如果未指定主可用区,则默认情况下,系统会推荐使用列表中的第一个可用区。

  1. 示例配置

  对于之前定义的示例用例,车辆需要两个可用区,即一个主可用区和一个后座娱乐系统可用区。对于该配置,可能的 car_audio_configuration.xml 的定义如下:

<audioZoneConfiguration version="1.0">
       <zone name="primary zone" isPrimary="true">
           <volumeGroups>
               <group>
                   <device address="bus0_media_out">
                       <context context="music"/>
                   </device>
                   <device address="bus3_call_ring_out">
                       <context context="call_ring"/>
                   </device>
                   <device address="bus6_notification_out">
                       <context context="notification"/>
                   </device>
                   <device address="bus7_system_sound_out">
                       <context context="system_sound"/>
                   </device>
               </group>
               <group>
                   <device address="bus1_navigation_out">
                       <context context="navigation"/>
                   </device>
                   <device address="bus2_voice_command_out">
                       <context context="voice_command"/>
                   </device>
               </group>
               <group>
                   <device address="bus4_call_out">
                       <context context="call"/>
                   </device>
               </group>
               <group>
                   <device address="bus5_alarm_out">
                       <context context="alarm"/>
                   </device>
               </group>
           </volumeGroups>
           <displays>
                <display port="0"/>
           </displays>
       </zone>
        <zone name="rear seat zone">
           <volumeGroups>
               <group>
                   <device address="bus100_rear_seat">
                       <context context="music"/>
                       <context context="navigation"/>
                       <context context="voice_command"/>
                       <context context="call_ring"/>
                       <context context="call"/>
                       <context context="alarm"/>
                       <context context="notification"/>
                       <context context="system_sound"/>
                   </device>
               </group>
           </volumeGroups>
           <displays>
               <display port="1"/>
           </displays>
    </zones>
</audioZoneConfiguration>

  在这里,主可用区将上下文划分至各个设备。这样一来,HAL 便可使用车辆的硬件,在各个设备输出中应用不同的后处理效果和混音。设备已划分为四个音量组:媒体、导航、通话和闹钟。如果系统配置为 useFixedVolume,则每个组的音量级别都将传递到 HAL,以应用于这些设备的输出。

  对于次要可用区,应通过单个耳机插孔进行输出。在此示例中,所有使用请求都将路由到单台设备和音量组,以简化操作。

  对于每个可用区,都可以添加可选的显示屏列表,以及与每个显示屏关联的实体显示屏端口。这使某些应用能够查询与其想要定位的特定显示屏相关联的可用区(见下文)。

3.1.3 新的隐藏 API(仅限 OEM 和系统应用)

在 Android 10 中,向 CarAudioManager 引入了一系列隐藏 API,使应用可以查询并设置音频可用区和焦点。

int[] getAudioZoneIds();
int getZoneIdForUid(int uid);
boolean setZoneIdForUid(int zoneId, int uid);
boolean clearZoneIdForUid(int uid);
int getZoneIdForDisplay(Display display);

更改应用的可用区

默认情况下,所有音频将路由到主可用区。如需更新应用以路由到其他可用区,请使用 setZoneIdForUid:

int zoneIdForDisplayId =
mCarAudioManager.getZoneIdForDisplay(selectedDisplay);

int uid = mContext.getPackageManager()
        .getApplicationInfo(mContext.getPackageName(), 0)
        .uid;

if (mCarAudioManager.setZoneIdForUid(zoneId, info.uid)) {
    Log.d(TAG, "Zone successfully updated");
} else {
    Log.d(TAG, "Failed to change zone");
}

注意:数据流无法动态地切换可用区。因此,必须停止播放才能更改可用区。

3.2 音频焦点

  通常情况下,任何时候都只能由一个应用在全局范围内持有音频焦点。其他应用请求获得焦点时,前一个应用会收到焦点丢失事件,新的应用则会获得焦点。但也有一些例外情况,例如,当通话应用持有音频焦点时,新的焦点请求会被拒。

3.2.1 新的上下文交互定义

  汽车音频焦点的管理方式从 Android 9 开始有所变化。CarAudioFocus 引入了一个交互矩阵,该矩阵可在收到焦点请求时根据新的请求来源及当前焦点持有者的用途捕获所需行为。交互类型分以下三种:

  1. 独占交互

  在独占应用上下文交互中,一次只允许一个应用持有焦点。如果两个媒体播放器应用都在播放媒体,则只有其中一个可以持有焦点。这是大多数焦点持有者和焦点请求者都请求 AudioManager.AUDIOFOCUS_GAIN 时的默认交互方式。在这种情况下,当焦点请求获批时,系统会将 AudioManager.AUDIOFOCUS_LOSS 分派给当前的焦点持有者。除了 AudioManager.AUDIOFOCUS_GAIN 以外,独占交互可能还会请求其他音频焦点。交互仍是独占模式,且系统会分派相应类型的焦点丢失事件。

  1. 拒绝交互

  在拒绝上下文交互中,焦点请求者发出的请求会一直遭拒。尝试从通知转换为闹铃就是拒绝用途交互的一个示例。在本例中,如果播放通知铃声的应用正持有音频焦点,而另一个应用要请求焦点以播放闹铃,则闹钟应用发出的焦点请求会被拒。由于焦点请求遭拒,因此系统不会向当前焦点持有者分派任何类型的焦点丢失事件。

  1. 并发交互

  车载音频用户最感兴趣的一个方面就是并发上下文交互。在这种交互模式下,请求音频焦点的车载应用可与其他应用同时播放音频。除了能够进行车载音频路由之外,原始设备制造商 (OEM) 还可以将音频路由到汽车的其他部分。要实现并发交互,焦点请求者必须请求 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK。setPauseWhenDucked(true) 在与 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 结合使用时会正常运行。系统将为并发焦点请求发送焦点丢失事件,使应用暂停播放。后者通常用于有声图书应用,可防止用户错过音频。

  虽然并发交互适用于许多实用应用,但原始设备制造商 (OEM) 必须在硬件级别跨输出设备实现混音和降低音量。例如,同时提供导航和媒体播放服务时,原始设备制造商 (OEM) 可以暂时将驾驶员侧播放媒体的音响静音(降低媒体音量),改为播放导航提示。如果汽车的配置是将媒体和导航音频传送至不同的设备,则可通过在将数据传送至导航设备时降低媒体的音量来实现这一点。在这种情况下,原始设备制造商 (OEM) 可在驾驶员侧播放导航提示,在车厢的其余位置继续播放媒体。但是,如果将两个来源传送至同一输出设备,则不会降低音量。因此,建议您将同时播放的所有内容路由到不同的设备,以便系统适当地降低音量。

  1. 交互矩阵

  下方的表 1 展示了 CarAudioFocus 中定义的交互矩阵。在该表中,行内容表示当前正持有焦点的应用,列内容表示传入的焦点请求者。

  如果某个音乐媒体应用正持有音频焦点,某导航应用请求获得焦点,则该矩阵表示这两个交互可以同时进行。如果传入的导航应用请求的焦点为 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,则该应用的焦点请求会获批,且系统不会向媒体应用发送焦点丢失事件。该矩阵允许同时存在多个焦点持有者。在这种情况下,系统会将传入的焦点请求者与当前的各个焦点持有者进行比较,然后决定进行哪种转换。

表 1. 汽车音频焦点用途交互矩阵。

传入焦点请求者的上下文:

image.png

说明:

  • REJ:拒绝
  • EXC:独占
  • CON:并发

如需了解详情,请参阅 packages/services/Car/service/src/com/android/car/audio/CarAudioFocus.java

3.2.2 多音频区焦点管理

  Android 10 保留了 Android 9 中的应用用途交互规则,但对每个音频区进行了进一步划分。因此,主车厢与后座娱乐系统的焦点可分开管理,以免某个音频区的焦点发生变化时另一个音频区的播放中断。

  所有应用的焦点均由 CarAudioService 自动管理且通过

  CarAudioManager.setZoneIdForUid API 进行设置。将 UID 映射到特定音频区后,相应音频区会自动请求焦点。以下是焦点请求示例:

//Create focus
ret = mAudioManager
.requestAudioFocus(mFocusListener,
mMusicAudioAttrib,AudioManager.AUDIOFOCUS_GAIN, 0);

if (ret == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.i(TAG, "Got focus for usage " + mMusicAudioAttrib.getUsage());
           start();
} else {
           Log.i(TAG, "MediaPlayer denied focus for usage "
+ mMusicAudioAttrib.getUsage());
}

  该示例与第三方应用请求焦点的方式没有任何区别,因此第三方应用仍可以使用音频区功能,但请注意,音频区必须由原始设备制造商 (OEM) 的应用启动器或其他管理应用进行管理。

同时流式传输到多个音频区

  如果将应用流式传输到单个音频区,从应用的角度来看,音频焦点运作方式没有发生变化,且可通过使用 CarAudioManager.setZoneIdForUid 对音频区的管理进行设置。但是,如果应用需要同时在多个音频区播放音频,则必须在软件包中包含 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,
               zoneIdForDisplayId);

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

//Create focus

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

推荐阅读更多精彩内容