Android13音频子系统分析(四)---座舱的多音区框架

     

目录

一、AAOS架构

二、CarAudioService初始化

2.1 car_audio_configuration.xml配置文件

2.2 AudioPolicy的数据结构

2.3 CarAudioService.java->init()函数

三、多音区的路由策略

四、多音区的音量控制

4.1 默认物理音量按键调节

4.2 各个乘员区的音量按键调节

4.3 通过CarAudioManager调节


         Google在Android核心系统的基础上,为车机系统扩展了一套针对智能座舱场景下的多音区框架,多音区是指在汽车中,不同的用户可以单独使用多媒体功能。比如:司机可以在驾驶舱中播放音乐,后排的乘客可以用耳机连上后座显示屏,观看视频,互相独立不受干扰。

在介绍多音区框架之前,需要先简单介绍一下AAOS(Android Automotive)架构。读者也可以在AOSP官网中查看AAOS的相关文章:Automotive  |  Android 开源项目  |  Android Open Source Project

一、AAOS架构

        AAOS(Android Automotive)是专门针对车载场景的车机系统。它不是Android的分支,而是在Android核心系统基础上扩展的,跟随Android大版本一起更新。

以上是Google官方的AAOS架构图,从图中可以看出,在Android核心系统基础上,AAOS主要扩展了三部分内容:

  • Car API:它是提供给3rd APP调用的各个模块的接口,比如音频的API接口CarAudioManager。CarAudioManager和AudioManager类似,是CarAudioService的代理,与CarAudoService通过Binder进行跨进程通信,CarAudioManager运行在调用方的进程中。源码位置:/packages/services/Car/car-lib/
  • CarService:针对车机系统扩展的各种框架服务层,比如音频的扩展服务CarAudioService、Activity的扩展服务CarActivityService等。所有的这些扩展服务CarXXXService都是由CarService负责启动和管理生命周期。CarService会被打包进包名为"com.android.car"CarService.apk中。它是一个System APP,SystemServer进程在启动时,会启动这个CarService。所以,所有的CarXXXService都是运行在"com.android.car"进程中的。源码位置:/packages/services/Car/service-builtin//packages/services/Car/service/
  • 车载HAL:需要OEM厂商实现的各种HAL层接口。源码位置:/hardware/interfaces/automotive/

上面是车机核心框架层CarService的类图。下面我们一起来看下CarService的启动过程:

SystemServer.java->main()
    SystemServer.java->run()
        SystemServer.java->startOtherServices()
            new CarServiceHelperService()
                new CarServiceHelperServiceUpdatableImpl()
            
            CarServiceHelperService.java->onStart()
                CarServiceHelperServiceUpdatableImpl.java->onStart()
                    Context.bindService()//Action:"android.car.ICar"  package: "com.android.car"

SystemServer在启动时,会创建并初始化CarServiceHelperService对象,CarServiceHelperService为了能够实现动态更新车机框架,又创建并初始化了CarServiceHelperServiceUpdatableImpl对象,在CarServiceHelperServiceUpdatableImpl的初始化函数onStart()中,会通过Context.bindService()接口,绑定一个packageName为"com.android.car"、Action为"android.car.ICar"的普通Service。

/packages/services/Car/service-builtin/AndroidManifest.xml文件中可以看到,申明Action为"android.car.ICar"的是CarService.java。所以,此时"com.android.car"进程被启动,CarService开始初始化。

从上面的类图可以看出,CarService继承于ServiceProxy,ServiceProxy在其onCreate()函数中,会调用自己的init()函数,在init()函数中会创建代理Service:CarServiceImpl。CarServiceImpl在其onCreate()函数中,会创建ICarImpl对象,并调用它的ICarImpl.init()函数进行初始化。

在ICarImpl的构造函数中,它会创建所有为车机系统扩展的框架服务对象,比如音频相关的CarAudioService、座舱区域相关的CarOccupantZoneService等。 ICarImpl会将这些框架服务对象全部保存到CarLocalServices类的静态Map集合中,key为各个框架服务的class,value为框架服务的对象。这样,因为所有框架服务对象都运行在同一进程中,就可以直接通过CarLocalServices类相互获取到对方的服务对象,直接进行交互。

在ICarImpl.init()函数中,它会遍历调用所有框架服务的init()函数。所以,每个框架服务对象的初始化函数入口有两个:一个是其构造函数、一个是init()函数。

那么,我们如何通过Car API来和各个CarService进行交互呢?可以通过android.car.Car.java这个接口来实现。它是Car API层的总入口。

通过调用Car.createCar(Context context)静态函数,我们可以创建一个Car对象,然后调用其Car.getCarManager(String serviceName)接口,就可以获取到各个CarService的代理类CarXXXManager对象了。下面以音频举例:

Car myCar = Car.createCar(context);
CarAudioManager myCarAudioManager = (CarAudioManager) myCar.getCarManager(Car.AUDIO_SERVICE);

二、CarAudioService初始化

        AAOS为了实现多音区,在CarAudioService中扩展了三大块功能:路由策略、音量控制和焦点管理。下面我先分析一下CarAudioService的初始化流程,再逐个分析。

前面我们提到过,所有的CarXXXService的初始化入口都是两个函数:构造函数和init()函数。

CarAudioService在它的构造函数中没有干太多的事,只是创建了一些自己的成员变量。主要的初始化工作是在CarAudioService.init()函数中完成。在init()函数中,它会先解析car_audio_configuration.xml文件,然后将解析出来的配置信息通过AudioManager注册到AudioService中。

2.1 car_audio_configuration.xml配置文件

car_audio_configuration.xml配置文件中,包含三部分节点<zones><oemContexts><mirroringDevices>。以下是car_audio_configuration.xml配置文件的示例。

<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2020 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<!--
  Defines the audio configuration in a car, including
    - Audio zones
    - Context to audio bus mappings
    - Volume groups
  in the car environment.
-->
<carAudioConfiguration version="3">
    <mirroringDevices>
        <mirroringDevice address="bus1000_mirror_device"/>
    </mirroringDevices>
    <zones>
        <zone name="primary zone" isPrimary="true" occupantZoneId="0">
            <zoneConfigs>
                <zoneConfig name="primary zone config" isDefault="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>
                        </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>
                            <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>
                    </volumeGroups>
                </zoneConfig>
            </zoneConfigs>
            <inputDevices>
                <inputDevice address="input_bus_tone_zone_0"/>
                <inputDevice address="tuner0"/>
            </inputDevices>
        </zone>
        <zone name="front passenger zone 1" audioZoneId="1" occupantZoneId="1">
            <zoneConfigs>
                <zoneConfig name="front passenger zone 1 config 0" isDefault="true">
                    <volumeGroups>
                        <group>
                            <device address="bus100_audio_zone_1">
                                <context context="music"/>
                            </device>
                        </group>
                        <group>
                            <device address="bus101_audio_zone_1">
                                <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>
                </zoneConfig>
                <zoneConfig name="front passenger zone 1 config 1">
                    <volumeGroups>
                        <group>
                            <device address="bus110_audio_zone_1">
                                <context context="music"/>
                            </device>
                        </group>
                        <group>
                            <device address="bus111_audio_zone_1">
                                <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>
                </zoneConfig>
            </zoneConfigs>
            <inputDevices>
                <inputDevice address="input_bus_tone_zone_1"/>
            </inputDevices>
        </zone>
        <zone name="rear seat zone 2"  audioZoneId="2"  occupantZoneId="2">
            <zoneConfigs>
                <zoneConfig name="rear seat zone 2 config 0" isDefault="true">
                    <volumeGroups>
                        <group>
                            <device address="bus200_audio_zone_2">
                                <context context="music"/>
                            </device>
                        </group>
                        <group>
                            <device address="bus201_audio_zone_2">
                                <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>
                </zoneConfig>
                <zoneConfig name="rear seat zone 2 config 1">
                    <volumeGroups>
                        <group>
                            <device address="bus210_audio_zone_2">
                                <context context="music"/>
                            </device>
                        </group>
                        <group>
                            <device address="bus211_audio_zone_2">
                                <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>
                </zoneConfig>
            </zoneConfigs>
        </zone>
    </zones>
</carAudioConfiguration>
  • <zones>节点:

        用于配置各个不同的音区。其中只能有一个音区可以配置成主音区,通过<zone>节点的isPrimary属性配置。<zone>节点对应的是CarAudioZone类

每个音区包括多个配置信息<zoneConfig>节点,<zoneConfig>节点对应的是CarAudioZoneConfig类

每个音区配置信息可以配置多个音量组<group>节点,<group>节点对应的是CarVolumeGroup类。CarVolumeGroup有两个子类,CarAudioVolumeGroup类和CoreAudioVolumeGroup类,分别代表是两种设置音量的方法。通过config.xml文件中的<audioUseCoreVolume>节点进行配置,配置为false时(默认配置),使用CarAudioVolumeGroup类。配置为true时,使用CoreAudioVolumeGroup类。默认使用的CarAudioVolumeGroup类,在进行音量设置时,会通过AudioManager.setAudioPortGain()接口直接将音量增益值设置到AudioHAL层。而CoreAudioVolumeGroup类在进行音量设置时,会通过AudioManager.setVolumeIndexForAttributes()接口,走音频子系统原来的streamType关联音量设置那一套逻辑。也就是说,CarAudioVolumeGroup类是硬件音量调节、CoreAudioVolumeGroup类是软件音量调节。如果要使用CarAudioVolumeGroup类进行硬件音量调节,就必须在audio_policy_configuration.xml配置文件的<devicePort>节点中,通过<gain>子节点配置该设备的最小增益值、最大增益值、默认增益值和步进。

在每个音量组下面,可以配置多个输出设备<device>节点,这些输出设备必须是在audio_policy_configuration.xml文件中配置过的,在车机系统中,所有输出设备的类型都是AUDIO_DEVICE_OUT_BUS,系统会根据它们的address来进行区分。<device>节点对应的是CarAudioDeviceInfo类

在每个<device>节点下面,可以配置多个<context>,<context>节点对应的是CarAudioContextInfo类CarAudioContextInfo会包含多个AudioAttributes,这样音频框架AudioPolicyManager在进行播放路由选择时,就可以根据AudioTrack指定的AudioAttributes,找到合适的Device。

在<zone>节点下,除了<zoneConfigs>子节点,还有一个<inputDevices>子节点,它用于配置当前这个音区的可用录音设备。这些设备也必须在audio_policy_configuration.xml文件中配置过。

  • <oemContexts>节点:

        用于配置AudioAttributes信息。<oemContext>节点的子节点<audioAttributes>中,可以配置多个<usage>节点和<audioAttribute>节点。<usage>节点表示的是AudioAttributes对象的usage属性。目前多音区的路由策略主要是通过usage属性来进行选择。AudioAttributes的其它属性如ContentType等可以不配置。

在AOSP的示例配置文件car_audio_configuration.xml中,默认没有<oemContexts>节点的配置。因为目前CarAudioContext的配置信息,是通过硬编码的方式在CarAudioContext.java->getAllContextsInfo()函数中写死的。我们如果自己在car_audio_configuration.xml中配置了<oemContexts>节点,记得将car_audio_configuration.xml的版本(<carAudioConfiguration>节点的version属性)定义为大于等于3,这样CarAudioService就不会再使用默认的硬编码配置信息了。

  • <mirroringDevices>节点:

        <mirroringDevices>节点用于配置多个镜像播放设备,镜像播放设备的作用是让两个或多个不同音区的播放设备可以同时输出同一个AudioTrack的声音。镜像播放只支持USAGE_MEDIA类型的声音输出,也就是支持音乐、视频、游戏场景下的镜像播放。镜像播放不能将主音区关联到其它音区进行播放。每个镜像播放设备只能被关联使用一次,比如副驾驶音区和后排音区1通过<mirroringDevice>1进行关联播放后,如果还想让后排音区2和后排音区3进行关联播放,就必须再重新定义一个<mirroringDevice>2使用。

我们可以通过CarAudioManager.java->enableMirrorForAudioZones(List<Integer> audioZoneIds)接口进行设置。我会在"多音区的路由策略"章节中进一步分析镜像播放的流程。

以上是car_audio_configuration.xml配置文件被解析后的类图。其中CarAudioZonesHelper.java是解析配置文件的工具类,里面也保存了所有解析后的配置信息。CarAudioDeviceInfo(<device>节点)和CarAudioContextInfo(<context>节点)的关联关系是1对多,它们通过CarVolumeGroup的成员变量mContextToAddress集合进行关联。

2.2 AudioPolicy的数据结构

        当CarAudioService在初始化时解析完car_audio_configuration.xml配置文件,会将CarAudioZonesHelper中保存的多音区配置信息,转换成AudioPolicy对象,然后通过AudioManager.registerAudioPolicy()接口,注册到AudioService中。AudioService又会将AudioPolicy的配置信息注册到AudioPolicyService中,供AudioPolicyService进行播放设备路由选择时使用。

AudioPolicy是播放设备路由策略的扩展接口,当有人创建了AudioPolicy对象,并注册给AudioPolicyService时,AudioPolicyService就会优先使用这个路由策略,而不会使用默认的硬编码策略。AudioPolicy相关的源码是在/frameworks/base/media/java/android/media/audiopolicy/目录中。

上面是AudioPolicy在Java层中的数据结构类图。所有的数据结构都保存在AudioPolicyConfig对象中,一个AudioPolicyConfig对象代表一个播放路由定制策略。

在AudioPolicyConfig对象中,会包含多个AudioMix对象。每个AudioMix对象代表的是一个播放设备的选择策略。所以,每个AudioMix对象只包含一个DeviceAddress的字符串。

每个播放Device对应一个匹配策略对象--AudioMixingRule,在AudioMixingRule中,可以包含多条匹配规则--AudioMixingRule.AudioMixMatchCriterion对象。目前支持的匹配规则有6种:

  • RULE_MATCH_ATTRIBUTE_USAGE:通过AudioAttributes的Usage进行匹配。比如当一个AudioTrack在播放时,指定的Usage是USAGE_MEDIA类型,而Device1也定义了USAGE_MEDIA类型的匹配规则时,AudioPolicyManager就会选择Device1来播放这个AudioTrack的声音。
  • RULE_EXCLUDE_ATTRIBUTE_USAGE:通过AudioAttributes的Usage进行排除。排除的意思是指Device1不支持USAGE_MEDIA类型的声音播放。
  • RULE_MATCH_UID:通过APP的UID进行匹配。在Android中,每个3rd APP的UID都不一样。即当指定UID的APP进行声音播放时,AudioPolicyManager就会选择Device1来输出。
  • RULE_EXCLUDE_UID:通过APP的UID进行排除。意思是Device1不能播放指定UID的APP下发的声音。
  • RULE_MATCH_USERID:通过UserId进行匹配。上面提到的镜像播放(mirroringDevice)就是通过这个规则来实现的。意思是当指定UserId中的任何APP播放声音时,AudioPolicyManager都会选择Device1来输出。
  • RULE_EXCLUDE_USERID:通过UserId进行排除。意思是Device1不能播放指定UserId下的所有APP下发的声音。

上面是AudioPolicy在C++层中的数据结构类图。AudioService通过AudioSystem向AudioPolicyManager中注册AudioPolicy数据时,AudioSystem就会将Java层的数据结构转换成C++层的数据结构。它们的对应关系是:

  • C++层的AudioMix对应的是Java层的AudioMix.java。
  • C++层的AudioMixMatchCriterion对应的是Java层的AudioMixingRule.AudioMixMatchCriterion。
  • C++层的mDeviceAddress字符串对应的是Java层的mDeviceAddress字符串。

AudioPolicyManager为了将播放Device与outputStream进行关联,定义了AudioPolicyMix类来继承扩展AudioMix类。在AudioPolicyMix类定义了成员变量SwAudioOutputDescriptor mOutput,用于表示此Device关联的outputStream。同时,定义了AudioPolicyMixCollection集合来保存所有的AudioPolicyMix对象。

        下面我来介绍一下car_audio_configuration.xml配置文件和AudioPolicy数据结构的对应关系:

<zoneConfig>中的定义每个<device>节点,都会被创建一个AudioMix对象,并将此Device的address设置到AudioMix.mDeviceAddress中保存。

<device>节点中的每个<context>节点,都会被转换成多个AudioAttributes对象(根据<oemContext>节点中的配置进行转换),每个AudioAttributes对象对应一个AudioMixMatchCriterion对象,此时AudioMixMatchCriterion对象中的匹配规则是RULE_MATCH_ATTRIBUTE_USAGE。所有AudioMixMatchCriterion对象会被保存到Device对应的AudioMix对象中。

2.3 CarAudioService.java->init()函数

        介绍完car_audio_configuration.xml配置文件和AudioPolicy的数据结构,我们再来看CarAudioService的初始化代码流程,就比较简单了。

CarAudioService.java->init()
    CarAudioService.java->setupDynamicRoutingLocked()
        CarAudioService.java->loadCarAudioZonesLocked()
            CarAudioService.java->generateCarAudioDeviceInfos()//从AudioPolicyManager中获取所有类型为TYPE_BUS的输出Device。
            CarAudioService.java->loadCarAudioConfigurationLocked()
                CarAudioZonesHelper.java->loadAudioZones()//解析car_audio_configuration.xml文件
            CarAudioZonesValidator.java->validate(mCarAudioZones)//检查配置文件的有效性

        CarAudioZone.java->init()//设置每个Device的当前音量增益值到AudioHAL中,并保存到SettingProvider数据库中。
        CarAudioService.java->setupMirrorDevicePolicyLocked()//为MirrorDevice创建一个USAGE_MEDIA匹配规则的对象AudioMix,并添加到AudioPolicy中。
        CarAudioDynamicRouting.java->setupAudioDynamicRouting()//将CarAudioZone数据结构转换成AudioPolicyConfig,并添加到AudioPolicy中。
        CarAudioPolicyVolumeCallback.addVolumeCallbackToPolicy(builder,mCarAudioPolicyVolumeCallback)//向AudioPolicy中注册一个音量控制callback
        AudioPolicy.Builder.setAudioPolicyFocusListener(mFocusHandler)//向AudioPolicy中注册一个焦点管理Listener
        AudioPolicy.Builder.setIsAudioFocusPolicy(true)
        
        AudioManager.java->registerAudioPolicy(mAudioPolicy)//向AudioService注册此AudioPolicy。


    CarAudioService.java->setupHalAudioFocusListenerLocked()//向IAudioControl.aidl注册焦点请求Listener
    CarAudioService.java->setupHalAudioGainCallbackLocked()//向IAudioControl.aidl注册音量变化callback
    CarAudioService.java->setupHalAudioModuleChangeCallbackLocked()//向IAudioControl.aidl注册设备配置信息变化callback,当底层硬件设置了播放Device的最小增益、最大增益、默认增益、步进这些配置信息时,会调用此callback。
    CarAudioService.java->setupAudioConfigurationCallbackLocked()//向AudioService注册PlaybackCallback
    CarAudioService.java->setupPowerPolicyListener()
    AudioManager.java->setSupportedSystemUsages(CarAudioContext.getSystemUsages())//设置当前支持的system级AudioAttributes.usage,注册到AudioPolicyService中

从上面的代码调用流程可以看出,初始化的核心是在setupDynamicRoutingLocked()函数中。它会先从AudioPolicyManager中获取所有类型为TYPE_BUS的输出Device,封装成CarAudioDeviceInfo对象。也就是从audio_policy_configuration.xml配置文件中获取,这个CarAudioDeviceInfo对象中包含了输出设备的音量配置信息(最小音量增益、最大音量增益、默认音量增益和步进)。

然后通过CarAudioZonesHelper解析car_audio_configuration.xml配置文件,封装到CarAudioZone数据结构中。同时会通过CarAudioZonesValidator检查配置文件的有效性。

接着会通过CarAudioZone.init()函数设置每个播放设备的当前音量,当前音量等级会被保存到SettingsProvider数据库中,当前音量增益值会被设置到AudioHAL层。

然后会根据CarAudioZone数据结构,构建AudioPolicy对象,在构建AudioPolicy中的AudioMix对象时,会为每个MirrorDevice都单独创建一个USAGE_MEDIA匹配规则的AudioMix对象。AudioPolicy中包含三部分信息:播放设备路由策略、音量控制监听者、焦点管理监听者。

最后,通过AudioManager.registerAudioPolicy()接口,将AudioPolicy的配置信息注册到AudioService中。

下面我重点分析一下CarAudioZonesValidator.validate()函数,这个函数里面定义了一些car_audio_configuration.xml配置文件需要遵守的规则:

  1. 至少需要配置一个<zone>音区。
  2. 每个<zone>音区中,都需要配置一个默认的<zoneConfig>,通过<zoneConfig>节点的isDefault属性进行配置。并且一个<zone>音区中只能配置一个默认<zoneConfig>。
  3. 在整个配置文件中,每个<device>只能出现一次,不能重复配置,即使是在不同的<zone>音区中。
  4. 在每个<zoneConfig>中,所有已定义的<context>都必须出现。默认的定义是在CarAudioContext.java->getAllContextsInfo()函数中。也就是说每个<zoneConfig>都必须配置完整的<context>规则。
  5. 在一个<zoneConfig>中,每种<context>只能出现一次。即:一种<context>不能同时配置到两个不同的<device>中。
  6. 在同一个<group>中的所有Device,它们的步进必须相同。因为CarAudioService在针对每个volumeGroup设置音量时,会同时设置这个<group>下的所有Device。
  7. 主音区中必须配置一个TYPE_BUILTIN_MIC类型的麦克风设备。主音区是通过<zone>节点的isPrimary属性配置。当然,如果你没有在配置文件中配置主音区的麦克风设备,CarAudioZonesHelper在解析完配置文件后,也会通过CarAudioZonesHelper.addRemainingMicrophonesToPrimaryZone()函数自动帮你添加上。

下面我们再来看一下AudioPolicy的注册流程。

AudioManager.java->registerAudioPolicy(mAudioPolicy)//向AudioService注册此AudioPolicy。
    AudioService.java->registerAudioPolicy(AudioPolicyConfig)
        new AudioPolicyProxy(AudioPolicyConfig)
            MediaFocusControl.java->setFocusPolicy()//注册焦点管理callback
            AudioService.java->setExtVolumeController()//注册音量控制callback
            
            AudioService.java->AudioPolicyProxy.connectMixes()//注册播放设备路由策略
                AudioSystem.java->registerPolicyMixes(mMixes, true)
                    android_media_AudioSystem.cpp->android_media_AudioSystem_registerPolicyMixes()//限制了AudioMix和AudioMixMatchCriterion的数量!AudioPolicy.h中定义了AudioMix和CRITERIA的最多数量:MAX_MIXES_PER_POLICY 10,MAX_CRITERIA_PER_MIX 20
                        AudioSystem.cpp->registerPolicyMixes()//限制了AudioMix的数量!
                            AudioPolicyIntefaceImpl.cpp->AudioPolicyService::registerPolicyMixes(mMixes,true)//限制了MIX的数量!
                                AudioPolicyManager.cpp->registerPolicyMixes()//循环遍历所有AudioMix。先从mHwModules中找到此AudioMix对应的Device,再找到此Device对应的outputStream。
                                    AudioPolicyMix.cpp->AudioPolicyMixCollection::registerMix()

从上面的代码流程可以看出,当CarAudioService通过AudioManager.registerAudioPolicy()接口向AudioService注册AudioPolicy后,AudioService干了3件事:

  1. 向自己的音频焦点控制子模块MediaFocusControl中注册CarAudioService的焦点管理callback。
  2. 注册CarAudioService的音量控制callback。只有在config.xml配置文件中将config_handleVolumeKeysInWindowManager配置为true,才会生效。因为只有配置为了true,input子系统才会将音量按键事件直接传递给PhoneWindowManager,PhoneWindowManager才能传递给AudioService,AudioService才有机会传递给CarAudioService的音量控制callback处理。
  3. 通过AudioSystem接口向AudioPolicyManager中注册播放设备路由策略,也就是AudioMix列表。AudioPolicyManager最终会将它们保存到自己的成员变量mPolicyMixes中,即AudioPolicyMixCollection集合中。

需要注意的是,在AudioSystem的JNI层、C++层,以及AudioPolicyService中,都对注册的AudioMix个数做了限制!一次只能注册10个AudioMix,每个AudioMix中,只能定义20条匹配规则,即20个AudioMixMatchCriterion对象。当超过时,会只取前10个AudioMix对象,以及每个AudioMix中的前20条匹配规则,注册到AudioPolicyManager中。限制的个数定义在AudioPolicy.h文件中的MAX_MIXES_PER_POLICY宏和MAX_CRITERIA_PER_MIX宏。我估计Android做这个数量限制,是为了防止在进行路由选择时,循环遍历AudioMix对象次数过多,影响系统性能吧。

从上面介绍的CarAudioZone和AudioPolicy数据结构的映射关系中我们知道,car_audio_configuration.xml中配置的每个<device>都对应一个AudioMix对象。所以,如果我们配置的device数量超过10个,就需要修改AudioPolicy.h文件中的MAX_MIXES_PER_POLICY宏。以免超过10个之后的Device没有对应的路由选择策略可用。

三、多音区的路由策略

        下面我来介绍一下当一个AudioTrack播放声音时,AudioPolicyManager是如何通过AudioMix来进行路由选择的。

当一个AudioTrack对象被APP创建时,它会在其构造函数中调用native_setup()方法,最终会调用到AudioFlinger.cpp->createTrack()接口,来通知AudioFlinger为其创建一个Track对象。AudioFlinger在创建Track对象时,需要先找AudioPolicyManager询问此Track应该在那个OutputStream中播放合适,也就是播放路由的选择。以下是代码调用流程:

AudioFlinger.cpp->createTrack()
    AudioSystem.cpp->getOutputForAttr(vector<audio_io_handle_t>* secondaryOutputs)
        AudioPolicyInterfaceImpl.cpp->AudioPolicyService::getOutputForAttr()
            AudioPolicyManager.cpp->getOutputForAttr(vector<audio_io_handle_t> *secondaryOutputs)
                AudioPolicyManager.cpp->getOutputForAttrInt(vector<sp<AudioPolicyMix>> *secondaryMixes)
                    AudioPolicyMix.cpp->AudioPolicyMixCollection::getOutputForAttr(sp<AudioPolicyMix> &primaryMix,vector<sp<AudioPolicyMix>> *secondaryMixes)//只有routeFlags被定义为MIX_ROUTE_FLAG_LOOP_BACK_AND_RENDER的AudioMix,才有可能被当做secondaryMixes。
                        AudioPolicyMix.cpp->AudioPolicyMixCollection::mixMatch()//优先根据AudioAttributes.tags与Device address匹配。目前CarAudioService没有使用此方式。

从上面的代码流程中可以看出,车机系统选择播放路由的核心逻辑是在AudioPolicyMix.cpp->AudioPolicyMixCollection::getOutputForAttr()函数中。它会遍历CarAudioService注册的所有AudioMix,找到第一个与该AudioTrack的AudioAttributes.usage相匹配的AudioMix,找到了AudioMix,也就找到了对应的Device,以及Device对应的outputStream。因为AudioPolicyManager在接收到AudioMix的注册请求时,会根据这个AudioMix的Device对象找到其对应的outputStream,查找方法是通过audio_policy_configuration.xml文件中的<routes>节点。

第一个匹配的Device,一定会是主音区的Device。因为CarAudioService不光在初始化时注册了包含AudioAttributes.usage匹配规则的AudioMix对象。并且为每个AudioMix对象,根据其Device所在的音区不同,设置了不同的userId匹配规则。每个音区的userId是从CarOccupantZoneService(乘员区管理服务)中获取到的。也就是说,每个音区对应的userId是不同的,而APP在创建AudioTrack对象时,如果没有明确指定,默认使用的是主音区对应的userId。所以第一个匹配的Device是主音区的Device。也就是说APP默认在主音区播放声音。以下是CarAudioService设置userId匹配规则的代码流程:

CarAudioService.java->init()
	CarAudioService.java->setupDynamicRoutingLocked()
		CarAudioService.java->setupOccupantZoneInfoLocked()
			CarOccupantZoneService.java->setAudioZoneIdsForOccupantZoneIds(audioZoneIdToOccupantZoneMapping)//音区(AudioZoneId)和乘员区(occupantZoneId)的映射关系是在car_audio_configuration.xml文件中的<zone>节点配置的。通过<zone>节点的audioZoneId属性和occupantZoneId属性。
			CarOccupantZoneService.java->registerCallback(mOccupantZoneCallback)


CarAudioService.java->mOccupantZoneCallback.onOccupantZoneConfigChanged()
	CarAudioService.java->handleOccupantZoneUserChanged()
		CarAudioService.java->updateUserForOccupantZoneLocked()
			CarAudioService.java->setUserIdDeviceAffinitiesLocked()//为每个音区下的所有Device设置userId匹配规则。
				AudioPolicy.java->setUserIdDeviceAffinity(int userId,List<AudioDeviceInfo> devices)
					AudioService.java->setUserIdDeviceAffinity()
						AudioService.java->AudioPolicyProxy.hasMixRoutedToDevices()//设置的Device,必须已配置了AudioMix
						AudioService.java->AudioPolicyProxy.setUserIdDeviceAffinities()
							AudioService.java->AudioPolicyProxy.setUserIdDeviceAffinitiesOnSystem()
								AudioSystem.java->setUserIdDeviceAffinities()
									AudioPolicyIntefaceImpl.cpp->setUserIdDeviceAffinities()
										AudioPolicyManager.cpp->setUserIdDeviceAffinities()
											AudioPolicyMix.cpp->AudioPolicyMixCollection::setUserIdDeviceAffinities()

通过上面的代码流程可以看出,CarAudioService在初始化时,建立了音区和乘员区的映射关系,映射关系是在car_audio_configuration.xml文件中的<zone>节点配置的。通过<zone>节点的audioZoneId属性和occupantZoneId属性进行配置。然后将映射关系注册进了CarOccupantZoneService中,并向CarOccupantZoneService注册了一个callback:ICarOccupantZoneCallback。当有乘员区可用时,CarOccupantZoneService就会通过这个callback通知到CarAudioService。CarAudioService会根据每个乘员区的userId,来设置对应音区的userId匹配规则。

userId匹配规则最终设置的地方是在AudioPolicyMix.cpp->AudioPolicyMixCollection::setUserIdDeviceAffinities()函数中。它的逻辑是将本音区之外的所有Device,都设置一条RULE_EXCLUDE_USERID规则。比如:主音区对应的userId是0、左后排音区对应的userId是1、右后排音区对应的userId是2。那么设置的规则就是:

userId 匹配规则
MirrorDevice 不能播放userId0、userId1和userId2中的APP声音
主音区的设备 userId0 不能播放userId1和userId2中的APP声音
左后排音区的设备 userId1 不能播放userId0和userId2中的APP声音
右后排音区的设备 userId2 不能播放userId0和userId1中的APP声音

APP也可以通过以下三种方式来指定播放声音时的音区:指定UID关联音区、指定UserId关联音区、通过AudioTrack.setPreferredDevice()接口指定播放设备。详细方法可以参见AOSP官网中的介绍:音频路由  |  Android 开源项目  |  Android Open Source Project

下面我介绍一下AOSP官网中没有提及的镜像播放(MirrorDevice)。

我们可以通过调用CarAudioManager.java->enableMirrorForAudioZones(List<Integer> audioZoneIds)接口,来指定两个或多个音区进行共享播放。audioZoneIds列表至少需要指定2个音区,并且不能指定主音区。以下是代码调用流程:

CarAudioService.java->enableMirrorForAudioZones()
	CarAudioService.java->verifyCanMirrorToAudioZones()
	CarAudioMirrorRequestHandler.java->getUniqueRequestIdAndAssignMirrorDevice()//找一个还没有用过的mirror device返回
	CarAudioService.java->handleEnableAudioMirrorForZones()
		CarAudioService.java->setupAudioRoutingForUserInMirrorDeviceLocked()
			CarAudioService.java->getUserIdForZone(int audioZoneId)//通过CarOccupantZoneService找到每个音区对应的UserId。
			CarAudioService.java->setupMirrorDeviceForUserIdLocked(int userId, CarAudioZone audioZone,AudioDeviceInfo mirrorDevice)
				CarAudioService.java->setUserIdDeviceAffinityLocked()//将此音区中的所有Device加上mirrorDevice,一起设置userId关联。
					AudioPolicy.java->setUserIdDeviceAffinity(int userId,List<AudioDeviceInfo> devices)
						AudioService.java->setUserIdDeviceAffinity()
							AudioService.java->AudioPolicyProxy.hasMixRoutedToDevices()//设置的Device,必须配置了AudioMix
							AudioService.java->AudioPolicyProxy.setUserIdDeviceAffinities()
								AudioService.java->AudioPolicyProxy.setUserIdDeviceAffinitiesOnSystem()
									AudioSystem.java->setUserIdDeviceAffinities()
										AudioPolicyIntefaceImpl.cpp->setUserIdDeviceAffinities()
											AudioPolicyManager.cpp->setUserIdDeviceAffinities()
												AudioPolicyMix.cpp->AudioPolicyMixCollection::setUserIdDeviceAffinities()
    
        CarAudioService.java->sendMirrorInfoToAudioHal()
            AudioManager.java->setParameters(String keyValuePairs)//mirroring_src=DevAddress;mirroring_dst=address,address;

从上面的代码调用流程中可以发现,两个音区的关联方法也是通过设置userId匹配规则来实现的。它设置的匹配规则逻辑是:取消MirrorDeviceRULE_EXCLUDE_USERID规则。比如有三个音区:主音区(对应userId0)、左后排音区(对应userId1)、右后排音区(对应userId2),现在将左后排音区和右后排音区进行关联播放。设置后的匹配规则如下:

userId 匹配规则
MirrorDevice 不能播放userId0中的APP声音
主音区的设备 userId0 不能播放userId1和userId2中的APP声音
左后排音区的设备 userId1 不能播放userId0和userId2中的APP声音
右后排音区的设备 userId2 不能播放userId0和userId1中的APP声音

从上面的匹配规则表可以看出,MirrorDevice变成了可以播放userId1和userId2中的APP声音。所以,此时如果userId1的APP播放USAGE_MEDIA类型声音时,MirrorDevice和左后排音区的设备都可以被匹配到。userId2的APP播放时也一样。
CarAudioService在注册AudioPolicy时,会为每个MirrorDevice创建一个AudioMix,它只有一个匹配规则:AudioAttributes.usage定义为USAGE_MEDIA类型。并且MirrorDevice的AudioMix对象,会最先放到AudioMix列表中,之后才是音区Device的AudioMix对象。而AudioPolicyMix.cpp->AudioPolicyMixCollection::mixMatch()函数中实现的匹配逻辑是:按从前到后的顺序遍历AudioMix列表,找到第一个匹配成功的AudioMix中的Device。所以,MirrorDevice是最先被匹配到的。

也就是说,userId1和userId2中的APP播放时,都会选择MirrorDevice。而通过上面代码流程可以看到,CarAudioService会通过AudioManager.setParameters(String keyValuePairs)接口,将以下参数配置信息传递给AudioHAL层:"mirroring_src=MirrorDevice的address;mirroring_dst=左后排音区中匹配USAGE_MEDIA规则的DeviceAddress,右后排音区中匹配USAGE_MEDIA规则的DeviceAddress;"

从这里可以看出,MirrorDevice其实是一个虚拟设备,当音频框架指定MirrorDevice进行播放时,需要AudioHAL层实现将传输过来的声音数据同时拷贝到左后排音区设备中和右后排音区设备中。这样就实现了两个音区的关联播放。多个音区关联时,也是同样的逻辑。

四、多音区的音量控制

        车机系统多音区的音量控制是在CarAudioService中实现的。CarAudioService在设置音量时,会直接将每个Device的增益值设置到AudioHAL层中,即:硬件音量调节。

我们需要在/frameworks/base/core/res/res/values/config.xml配置文件中,将config_useFixedVolume配置为true。这样APP通过AudioManager.setStreamVolume()接口设置音量时,就不会生效。因为AudioManager.setStreamVolume()接口实现的音量设置逻辑是通过AudioFlinger进行调节,也就是软件音量调节。

同时,我们还需要将config.xml配置文件中的config_handleVolumeKeysInWindowManager配置为true。这样物理音量按键的输入事件就会直接传递给PhoneWindowManager,而不是传递给APP自己处理。因为默认APP自己处理音量按键事件的方式是调用AudioManager.setStreamVolume()接口。

CarAudioService支持三种方式的音量调节:默认物理音量按键调节、各个乘员区的音量按键调节、通过CarAudioManager调节。

4.1 默认物理音量按键调节

当用户在汽车中,按下默认的音量+、音量-按键时,input子系统会将输入事件传递给PhoneWindowManager,PhoneWindowManager又会将输入事件传递给AudioService,AudioService会将输入事件传递给CarAudioService在初始化时注册的callback。最终交由CarAudioService进行处理。

PhoneWindowManager.java->interceptKeyBeforeDispatching()
    PhoneWindowManager.java->dispatchDirectAudioEvent()
        AudioService.java->handleVolumeKey()
            AudioService.java->adjustSuggestedStreamVolume()
                AudioService.java->notifyExternalVolumeController()
                    AudioPolicy.java->IAudioPolicyCallback.notifyVolumeAdjust()
                        CarAudioPolicyVolumeCallback.java->onVolumeAdjustment(int adjustment)
                            CarAudioPolicyVolumeCallback.java->onVolumeAdjustment(int adjustment, PRIMARY_AUDIO_ZONE)
                                CarAudioPolicyVolumeCallback.java->evaluateVolumeAdjustmentInternal()

以上是代码调用逻辑,从中可以看出,最终处理音量按键的是CarAudioPolicyVolumeCallback.java->onVolumeAdjustment(int adjustment, PRIMARY_AUDIO_ZONE)函数。因为默认音量按键没有关联到任何音区中,所以调节的是主音区。

调节音区音量的逻辑分为三个步骤:找到合适的VolumeGroup、获取该VolumeGroup的音量等级、设置该VolumeGroup下的所有Device音量。

1、找到合适的VolumeGroup。

CarAudioPolicyVolumeCallback.java->onVolumeAdjustment(int adjustment, PRIMARY_AUDIO_ZONE)
    CarAudioPolicyVolumeCallback.java->evaluateVolumeAdjustmentInternal()
        CarVolumeInfoWrapper.java->getVolumeGroupIdForAudioZone()//找到需要调节的VolumeGroup
            
            CarAudioService.java->getSuggestedAudioContextForZone(zoneId)//根据当前音区下,所有正在播放的AudioTrack的AudioAttributes,找到其对应的CarAudioContext,再根据优先级找到最优先设置的CarAudioContext
                CarAudioService.java->getAllActiveAttributesForZone()//找到此音区下,所有正在播放的Track对应的AudioAttributes
                    CarAudioPlaybackCallback.java->getAllActiveAudioAttributesForZone(int audioZone)
                        ZoneAudioPlaybackCallback.java->getAllActiveAudioAttributes()
                            ZoneAudioPlaybackCallback.java->getCurrentlyActiveAttributesLocked()
                                CarAudioZone.java->findActiveAudioAttributesFromPlaybackConfigurations()
                CarVolume.java->getSuggestedAudioContextAndSaveIfFound()
                    CarVolume.java->findActiveContextWithHighestPriority()//按照CarVolume.AUDIO_ATTRIBUTE_VOLUME_PRIORITY_V2常量列表中的定义顺序,找到最优先的CarAudioContext
            
            CarAudioService.java->getVolumeGroupIdForAudioContext(int zoneId, int suggestedContext)//在当前音区的default <zoneConfig>中,或者CarAudioManager.java.switchZoneToConfig()设置的<zoneConfig>中,找到与audioContext匹配的VolumeGroup。

找VolumeGroup的逻辑是:

先找到当前音区下所有正在播放声音的AudioTrack的AudioAttributes。CarAudioService在初始化时,会向AudioService注册一个PlaybackCallback,这样当有任何应用播放声音时,CarAudioService都能被通知到,并且会告知该声音的AudioAttributes,以及对应的播放Device。也就知道了目前哪些Device正在播放声音,CarAudioService就能通过car_audio_configuration.xml配置文件知道Device对应的音区是哪个了。

找到当前音区下的这些AudioAttributes后,就可以找到它们所对应的CarAudioContext,当有多个CarAudioContext处于正在播放状态时,会按照CarVolume..java->AUDIO_ATTRIBUTE_VOLUME_PRIORITY_V2常量中定义的优先级,找到最优先设置的那一个。AUDIO_ATTRIBUTE_VOLUME_PRIORITY_V2常量定义的优先级如下:USAGE_VOICE_COMMUNICATION、USAGE_MEDIA、USAGE_ANNOUNCEMENT、USAGE_ASSISTANT。也就是说,当通话和音乐播放同时进行时,会优先设置通话的音量。

找到最优先的CarAudioContext后,就可以从<zoneConfig>中找到其对应的volumeGroup了。

2、获取该VolumeGroup的音量等级。

CarAudioPolicyVolumeCallback.java->onVolumeAdjustment(int adjustment, PRIMARY_AUDIO_ZONE)
    CarAudioPolicyVolumeCallback.java->evaluateVolumeAdjustmentInternal()
        CarVolumeInfoWrapper.java->getGroupVolume(zoneId, groupId)//获取当前音量等级

            CarAudioService.java->getGroupVolume()
                CarAudioService.java->getCarVolumeGroupLocked()
                    CarAudioZone.java->getCurrentVolumeGroup(groupId)
                        CarAudioZoneConfig.java->getVolumeGroup(groupId)
                CarVolumeGroup.java->getCurrentGainIndex()//默认的音量Gain值是根据audio_policy_configuration.xml中,每个Device的配置得来。mDefaultGain等于所有Device定义中最大的。mMinGain等于所有Device定义中最小的。mMaxGain等于所有Device定义中最大的。            

当车机系统第一次开机时,VolumeGroup的默认音量等级是根据它下面配置的所有Device得来的。每个Device的默认音量都需要在audio_policy_configuration.xml文件中的<gain>节点中进行配置。<gain>节点是<devicePort>的子节点。VolumeGroup会取所有Device中最大的默认音量等级作为自己的默认音量等级。

3、设置该VolumeGroup下的所有Device音量。

CarAudioPolicyVolumeCallback.java->onVolumeAdjustment(int adjustment, PRIMARY_AUDIO_ZONE)
    CarAudioPolicyVolumeCallback.java->evaluateVolumeAdjustmentInternal()
        CarAudioService.java->AudioPolicyVolumeCallbackInternal.onGroupVolumeChange()

            CarAudioService.java->setGroupVolume()
                CarVolumeGroup.java->setCurrentGainIndex(index)
                    CarAudioVolumeGroup.java->setCurrentGainIndexLocked()//设置当前Group对应的所有Device的音量
                        CarAudioDeviceInfo.java->setCurrentGain()
                            AudioManagerHelper.java->setAudioDeviceGain()
                                AudioManager.java->setAudioPortGain()
                                    AudioSystem.java->setAudioPortConfig()
                                        AudioPolicyIntefaceImpl.cpp->AudioPolicyService::setAudioPortConfig()
                                            AudioPolicyManager.cpp->setAudioPortConfig()
                                                DeviceDescriptor.cpp->applyAudioPortConfig()
                                                AudioPolicyClientImpl.cpp->AudioPolicyService::AudioPolicyClient::setAudioPortConfig()
                                                    AudioPolicyService.cpp->clientSetAudioPortConfig()
                                                        AudioFlinger.cpp->setAudioPortConfig()
                                                            DeviceHalHidl.cpp->setAudioPortConfig()
                                                                Device.cpp->setAudioPortConfig()
                                                                    audio_hw_device_t->set_audio_port_config()

                    CarVolumeGroup.java->setCurrentGainIndexLocked()//保存到SettingProvider数据库中

CarVolumeGroup会将当前group音量等级的增益值设置到名下所有的Device中。每个CarAudioDeviceInfo对象都会通过AudioManager.setAudioPortGain()接口,将自己的音量增益值经过AudioSystem-->AudioPolicyManager-->AudioFlinger-->DeviceHal的层层调用,最终传递到AudioHAL层的audio_hw_device_t.set_audio_port_config()接口中。

同时,CarVolumeGroup还会将当前音量等级保存到SettingProvider数据库中,确保车机系统重启后,仍然有效。

4.2 各个乘员区的音量按键调节

        CarAudioService在初始化时,会向CarInputService注册一个KeyEventListener。当每个乘员区的独立音量按键按下时,CarInputService就可以通知到CarAudioService,并且会告知是哪个成员区的按键。这样CarAudioService就可以通过OccupantZoneService找到乘员区对应的音区,然后对这个音区进行音量调节。调节方法与上面主音区的调节方法一样,都是通过CarAudioPolicyVolumeCallback.java->onVolumeAdjustment(adjustment, audioZoneId)函数进行。

CarAudioService.java->init()
    CarInputService.java->registerKeyEventListener(mCarKeyEventListener,KEYCODES_OF_INTEREST)


CarAudioService.java->KeyEventListener.onKeyEvent(KeyEvent event, int displayType, int seat)
    CarAudioPolicyVolumeCallback.java->onVolumeAdjustment(adjustment, audioZoneId)

4.3 通过CarAudioManager调节

        APP可以先通过CarAudioManger.java.getVolumeGroupIdForUsage(int zoneId, @AttributeUsage int usage)接口,来查找指定usage在某个音区下对应的VolumeGroup,然后通过CarAudioManger.setGroupVolume(int zoneId, int groupId, int index, int flags)接口对该VolumeGroup进行音量调节,比如系统设置APP。音量设置方法也是一样的,最终会通过调用CarVolumeGroup.java->setCurrentGainIndex()接口来设置该VolumeGroup下所有Device的音量。

CarAudioManger.java->getVolumeGroupIdForUsage(int zoneId, @AttributeUsage int usage)

CarAudioManger.java->setGroupVolume(int zoneId, int groupId, int index, int flags)
	CarAudioService.java->setGroupVolume(int zoneId, int groupId, int index, int flags)
		CarAudioService.java->getCarVolumeGroupLocked(zoneId, groupId)
		CarVolumeGroup.java->setCurrentGainIndex(index)

猜你喜欢

转载自blog.csdn.net/weixin_41004543/article/details/133931665