Android音频之车载音频配置

 

Android音频之车载音频配置

车载音频是 Android Automotive OS(AAOS)中的一个核心功能模块,用于管理汽车内部复杂的音频路由、音量控制和区域化播放。通过灵活的音频配置文件,开发者可以定制音频系统以适应不同的硬件和使用场景。

官方网站 https://source.android.com/docs/automotive/audio/audio-policy-configuration

音频配置概述

在 AAOS 中,音频配置主要通过以下两个文件完成:

  • audio_policy_configuration.xml:定义音频设备、音量策略和路由规则,是系统层的主要配置文件。
  • car_audio_configuration.xml:针对汽车的音频拓扑配置,定义音频区域、设备组和音频流的映射。

两者共同作用,确保系统能够根据硬件能力和车辆需求灵活处理音频流。架构图如下:

架构图


配置文件详解

audio_policy_configuration.xml

该文件定义了系统的音频硬件拓扑及相关策略,包括以下部分:

  • 音频设备: 列出系统中可用的音频设备,例如多媒体、导航等。
  • 音频流: 列出系统中可用的音频流,例如多媒体、导航等。
  • 路由策略: 指定音频流的默认音频设备

完整示例如下:

    <audioPolicyConfiguration version="1.0" xmlns:xi="http://www.w3.org/2001/XInclude">
    <modules>
        <!-- Primary Audio HAL -->
        <module name="primary" halVersion="3.0">
            <attachedDevices>
                <item>bus0_media_out</item>
            </attachedDevices>
            <defaultOutputDevice>bus0_media_out</defaultOutputDevice>
            <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>
            </mixPorts>
            <devicePorts>
                <devicePort tagName="bus0_media_out" role="sink" type="AUDIO_DEVICE_OUT_BUS"
                    address="bus0_media_out">
                <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                        samplingRates="48000" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
                <gains>
                    <gain name="" mode="AUDIO_GAIN_MODE_JOINT"
                            minValueMB="-3200" maxValueMB="600" defaultValueMB="0" stepValueMB="100"/>
                </gains>
            </devicePort>
            </devicePorts>
            <routes>
                <route type="mix" sink="bus0_media_out" sources="mixport_bus0_media_out"/>
            </routes>
        </module>
        <!-- A2DP Input Audio HAL -->
        <xi:include href="a2dp_audio_policy_configuration.xml"/>

        <!-- Usb Audio HAL -->
        <xi:include href="usb_audio_policy_configuration.xml"/>

        <!-- Remote Submix Audio HAL -->
        <xi:include href="r_submix_audio_policy_configuration.xml"/>
    </modules>
    <!-- Volume section -->
    <xi:include href="audio_policy_volumes.xml"/>
    <xi:include href="default_volume_tables.xml"/>
    </audioPolicyConfiguration>

car_audio_configuration.xml

Android 10 中,car_audio_configuration.xml 取代了 car_volumes_groups.xmlIAudioControl.getBusForContext。音频政策文件通常包含在 vendor 分区中,表示主板的音频硬件配置。car_audio_configuration.xml 中引用的所有设备都必须在 audio_policy_configuration.xml 中进行定义。

将车载音频配置文件放在设备的 vendor\etc\system\etc\ 中,车载音频服务会首先在 vendor\etc\ 中搜索该文件。车载音频服务会读取 car_audio_configuration.xml 来确定音频配置。

车载音频区:

  • 每个音频区均包含唯一的音频区 ID。
  • 每个音频区都可以映射到乘员区。
  • 每个音频区中的音频操作彼此独立:

    • 音频焦点
    • 音频路由
    • 降低其他应用音量
  • 车载音量组:

  • 包含音量组的所有音频设备都通过相同的增益变化统一控制。组内所有设备的音频增益配置应相同。

  • 音频上下文与音频设备的映射。使用此功能可构建音频混音,将音频用法映射到输出设备。

  • 所有音频上下文都应在一个音频区内表示。这样可以为所有音频属性用法准确设置音频路由。

音频上下文

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

下表介绍了音频上下文和用法之间的对应关系。突出显示的行供新的系统使用。

注意:系统上下文仅在 Android 11 及更高版本中可用。

CarAudioContext AttributeUsages
MUSIC USAGE_UNKNOWN
USAGE_GAME
USAGE_MEDIA
NAVIGATION USAGE_ASSISTANCE_NAVIGATION_GUIDANCE
VOICE_COMMAND USAGE_ASSISTANT
CALL_RING USAGE_NOTIFICATION_RINGTONE
CALL USAGE_VOICE_COMMUNICATION
ALARM USAGE_ALARM
NOTIFICATION USAGE_NOTIFICATION
SYSTEM_SOUND USAGE_ASSISTANCE_SONIFICATION
EMERGENCY USAGE_EMERGENCY
SAFETY USAGE_SAFETY
VEHICLE_STATUS USAGE_VEHICLE_STATUS
ANNOUNCEMENT USAGE_ANNOUNCEMENT

启用 AAOS 路由

如需使用基于 AAOS 的路由,您必须将 audioUseDynamicRouting 标志设为 true

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

如果设为 false,路由和大部分 CarAudioService 将被停用,并且 AAOS 会回退到 AudioService 的默认行为。

主音频区

默认情况下,所有音频都会路由到主音频区。只有一个主音频区,该音频区在配置中通过属性 isPrimary="true" 进行指示。系统会自动为主音频区分配 Audiomanager.PRIMARY_AUDIO_ZONE

示例配置(版本2)

<?xml version="1.0" encoding="utf-8"?>
<carAudioConfiguration version="2">
    <zones>
        <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"/>
                        <context context="emergency"/>
                        <context context="safety"/>
                        <context context="vehicle_status"/>
                        <context context="announcement"/>
                    </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>
        </zone>
        <zone name="rear seat zone 1" audioZoneId="1">
            <volumeGroups>
                <group>
                    <device address="bus100_audio_zone_1">
                        <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"/>
                        <context context="emergency"/>
                        <context context="safety"/>
                        <context context="vehicle_status"/>
                        <context context="announcement"/>
                    </device>
                </group>
            </volumeGroups>
        </zone>
    </zones>
</carAudioConfiguration>

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

乘员区的音频配置

Android 11 中,car_audio_configuration.xml 引入了 audioZoneIdoccupantZoneId 这两个新字段。您可以使用 audioZoneId 来控制音频区管理,并可以使用 occupantZoneId 根据用户 ID 配置路由。

注意:这些新字段在 car_audio_configuration.xml 版本 2 中是必填字段。

如果想重新访问上述音频配置,但利用该新字段进行乘员区 ID 和音频区 ID 之间的映射,则可按如下所示设置不含音量组定义的新配置。

<audioZoneConfiguration version="2.0">
       <zone name="primary zone" isPrimary="true" occupantZoneId="0">
         ...
       </zone>
       <zone name="rear seat zone" audioZoneId="1" occupantZoneId="1">
         ...
       </zone>
    </zones>
</audioZoneConfiguration>

上面的配置定义了主音频区到乘员区 0 以及 audioZoneId 1 到 occupantZoneId 1 的映射。一般来说,可以配置乘员区和音频区之间的任何映射。但是,这种映射必须是一对一的。下面列出了定义这两个新字段的规则。

  • 主音频区的 audioZoneId 始终为 PRIMARY_AUDIO_ZONE ID。如果定义了 isPrimary=”true”,则无需 audioZoneId。

  • audioZoneId 和 occupantZoneId 的编号不能重复。

  • audioZoneId 和 occupantZoneId 之间只能是一对一的映射。

Android 14 车载音频配置

Android 14 中,AAOS 引入了原始设备制造商(OEM)插件服务,可让您更主动地管理由车载音频服务监督的音频行为。除了新的插件服务之外,车载音频配置文件中还添加了以下更改:

  • OEM 定义的车载音频上下文
  • 非主音频区动态配置

OEM 定义的车载音频上下文

为了实现灵活的音频配置,在 Android 14 中,车载音频服务允许以不同于上面定义的静态音频上下文的方式对音频用法进行分组。这个 OEM 定义的上下文可以在 car_audio_configuration.xml 版本 3 文件中定义。

注意:OEM 上下文为可选设置,可以跳过。

动态音频区配置

在 Android 14 中,为了适应动态音频区配置,用于定义音频区的车载音频配置架构也更新到了版本 3。新架构需要为每个音频区设置配置。

<carAudioConfiguration version="3">
    <!-- optional OEM context -->
    <oemContexts>
      <oemContext name="media">
        <audioAttributes>
          <usage value="AUDIO_USAGE_MEDIA" />
          <usage value="AUDIO_USAGE_UNKNOWN"/>
        </audioAttributes>
      </oemContext>
      <oemContext name="game">
        <audioAttributes>
          <usage value="AUDIO_USAGE_GAME" />
        </audioAttributes>
      </oemContext>
...
    </oemContexts>
  <zones>
    <zone name="primary zone" isPrimary="true" occupantZoneId="0">
      <zoneConfigs>
        <zoneConfig name="primary zone config 0" isDefault="true">
          <volumeGroups>
            <group>
              <device address="bus0_media_out">
                <context context="media"/>
                <context context="game"/>
                <context context="announcement"/>
              </device>
              <device address="bus6_notification_out">
                <context context="notification"/>
              </device>
            </group>
  ...
      </zoneConfigs>
    </zone>
  </zones>

如需了解详情,请参阅 device/generic/car/emulator/audio/car_audio_configuration.xml 中定义的版本 3 文件。从 Android 14 开始,主音频区只能有一 (1) 项配置。非主音频区可以有多项配置。以下规则适用于车载音频配置:

  • 主音频区只能有一项配置。

  • 非主音频区可以有多项配置。

  • 每个音频区和音频区配置的名称必须是唯一的。

  • 在一个音频区中,音频配置可能有所不同:

    • 音量组的设置不必相同。
    • 音频上下文分配不必相同。
  • 音频输出设备名称在各个音频区或配置中应该是唯一的。在音频配置或音频区中,设备名称应仅出现一次。

  • 属于同一音量组的音频设备应具有相同的音频增益配置。

  • 必须为每项音频配置分配所有音频上下文(OEM 或静态)。

注意:上面的车载音频配置示例使用的是同一文件中定义的 OEM 定义的上下文。

Android 10 以前

CarAudioService获取BusForContext并注册路由

// packages/services/Car/service/src/com/android/car/audio/CarAudioService.java
--> init()
----> setupDynamicRoutingLocked()
------> IAudioControl audioControl = getAudioControl()
--------> getDynamicAudioPolicy()
----------> AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext)
// 1st, enumerate all output bus device ports
----------> AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
// 2nd, map context to physical bus
----------> for (int contextNumber : CONTEXT_NUMBERS)
----------> int busNumber = audioControl.getBusForContext(contextNumber);
// hardware/interfaces/automotive/audiocontrol/1.1/default/AudioControl.cpp
// static int sContextToBusMap1[] = {
//     -1,     // INVALID
//      0,     // MUSIC_CONTEXT
//      4,     // NAVIGATION_CONTEXT
//      3,     // VOICE_COMMAND_CONTEXT
//      2,     // CALL_RING_CONTEXT
//      2,     // CALL_CONTEXT
//      5,     // ALARM_CONTEXT
//      6,     // NOTIFICATION_CONTEXT
//      1,     // SYSTEM_SOUND_CONTEXT
// };
------------> return sContextToBusMap1[contextNumber];
----------> mContextToBus.put(contextNumber, busNumber);
// 3rd, enumerate all physical buses and build the routing policy.
----------> for (int i = 0; i < mCarAudioDeviceInfos.size(); i++)
----------> mixingRuleBuilder.addRule(new AudioAttributes.Builder().setUsage(usage).build(),AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
----------> AudioMix audioMix = new AudioMix.Builder()
----------> builder.addMix(audioMix)
// 4th, attach the {@link AudioPolicyVolumeCallback}
----------> builder.setAudioPolicyVolumeCallback(mAudioPolicyVolumeCallback);
  
// 5th, add AudioPolicyFocusListener
----------> builder.setIsAudioFocusPolicy(true);
----------> builder.setAudioPolicyFocusListener(mAudioPolicyFocusListener);
----------> return builder.build()
--------> mAudioManager.registerAudioPolicy(audioPolicy);

Android 10 以后

CarAudioService解析配置文件

// packages/services/Car/service/src/com/android/car/audio/CarAudioService.java
--> init()
----> setupDynamicRoutingLocked()
------> loadCarAudioZonesLocked()
--------> generateCarAudioDeviceInfos()
--------> getAudioConfigurationPath()
--------> loadCarAudioConfigurationLocked()
----------> inputStream = new FileInputStream(mCarAudioConfigurationPath)
----------> zonesHelper = new CarAudioZonesHelper(mCarAudioSettings,inputStream, carAudioDeviceInfos, inputDevices)
----------> zonesHelper.getCarAudioZoneIdToOccupantZoneIdMapping()
----------> zonesHelper.loadAudioZones()
------------> parseCarAudioZones()
--------------> parseAudioZones(parser)
----------------> parseAudioZone(parser)
------------------> carAudioZones.put(zone.getId(), zone)

CarAudioService注册路由

// packages/services/Car/service/src/com/android/car/audio/CarAudioService.java
--> init()
----> setupDynamicRoutingLocked()
------> loadCarAudioZonesLocked()
------> CarAudioDynamicRouting.setupAudioDynamicRouting(builder, mCarAudioZones)
--------> for (int i = 0; i < carAudioZones.size(); i++)
--------> for (CarVolumeGroup group : zone.getVolumeGroups())
--------> setupAudioDynamicRoutingForGroup(group, builder)
----------> for (String address : group.getAddresses())
----------> for (int carAudioContext : group.getContextsForAddress(address))
----------> mixingRuleBuilder.addRule(attributesAudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
----------> AudioMix audioMix = new AudioMix.Builder()
----------> builder.addMix(audioMix)
------> mAudioPolicy = builder.build()
--------> return new AudioPolicy()
----------> return new AudioPolicyConfig(mMixes)
------> mAudioManager.registerAudioPolicy(mAudioPolicy)
// frameworks/base/media/java/android/media/AudioManager.java
--------> registerAudioPolicyStatic()
--------> IAudioService service = getService()
--------> service.registerAudioPolicy()
// frameworks/base/services/core/java/com/android/server/audio/AudioService.java
--------> registerAudioPolicy()
--------> AudioPolicyProxy app = new AudioPolicyProxy()
----------> mMediaFocusControl.setFocusPolicy(mPolicyCallback, mIsTestFocusPolicy)
----------> connectMixes()
------------> AudioSystem.registerPolicyMixes(mMixes, true) //native jni 接口
// frameworks/base/core/jni/android_media_AudioSystem.cpp
--------------> android_media_AudioSystem_registerPolicyMixes()
// frameworks/av/media/libaudioclient/AudioSystem.cpp
----------------> AudioSystem::registerPolicyMixes(mixes, registration)
------------------> AudioSystem::get_audio_policy_service()
------------------> aps->registerPolicyMixes(mixes, registration);
// frameworks/av/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
--------------------> AudioPolicyService::registerPolicyMixes()
----------------------> mAudioPolicyManager->registerPolicyMixes(mixes)
// frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
------------------------> AudioPolicyManager::registerPolicyMixes()
--------------------------> mPolicyMixes.registerMix(mix, 0 /*output desc*/)