AAOS Audio学习记录

Posted by Bill on March 27, 2024

1. 背景

默认的Android Audio在Framework层进行混音后输出到音频设备,而在车载场景,往往需要将不同类型的声音,如系统音,导航音,音乐等通过配置在不同设备中分别输出,这个需求与以往Android Audio的实现有所区别,为了研究AAOS是如何实现这个功能,又是如何对接到AudioFlinger/AudioPolicyService,现对AAOS Audio进行通路学习。

官网的框架图如下所示:

image

从上图的框架,将从CarAudioManager,CarAudioService,AudioFlinger,AudioControl HAL这四个方面着手分析。

2. CarAudioManager

CarAudioManager的代码位于packages/services/Car/car-lib/src/android/car/media/,官网的接口说明链接为https://developer.android.com/reference/android/car/media/CarAudioManager.

CarAudioManager可以理解为专门用于车载的音频APIS,如果在config.xml打开了属性audioUseDynamicRouting,那么就开启了动态路由(dynamic routing)的开关, 即所有的音频设备都被划分成为以“Zone”为单位,其中至少有一个primary zone,且音频设备也会根据“Group”组为单位进行音量的控制。如果动态路由关闭,Audio就会根据AudioAttribue去设置音频设备了。

2.1 API介绍

内部类: CarAudioManager.CarVolumeCallback

这个回调类专门用于接收音量变化的事件,其中包括接口:

  • onGroupMuteChanged: 当Group静音状态发生变化
  • onGroupVolumeChanged: 当Group的音量大小发生变化
  • onMasterMuteChanged: 当全局的静音状态发生变化

原理分析: 回调类用于跨进程的通信,应用首先会在应用层创建一个回调对象,实现上述接口来监听静音,音量变化。并通过carAudioManager的registerCarVolumeCallback将回调方法注册进去。CarAudioManager本身会定义一个CopyOnWriteArrayList类型的mCarVolumeCallback对象,用于管理应用传入的回调对象,即registerCarVolumeCallback的时候add进去,unregisterCarVolumeCallback时remove。(CopyOnWriterArrayList是Java的一个提供线程安全的ArrayList,用途是在多线程的环境下提供高效读操作的方式,其特点是在读时可以随意读,写的时候会先创建一个副本,并在副本上修改,完成后再更新为副本对象,因此写操作开销大。)carAudioManager的registerCarVolumeCallback除了将应用传入的回调对象进行本地保存,还会将一个ICarVolumeCallback的Stub实现传给CarAudioService中。当CarAudioService得知发生了音量的变化时,便可以通过Binder跨进程通信到CarAudioManager,并通过发送Handler发送消息到应用的Looper线程,最终通过mCarVolumeCallback实现应用回调方法的调用。

接口:

  • isAudioFeatureEnabled: 这个接口用于判断audio特性是否支持,通过CarAudioService获取。
  • registerCarVolumeCallback: 注册音量回调方法。
  • unregisterCarVolumeCallback: 注销音量回调。

总结:CarAudioManager为应用提供了音量回调的接口,并且能够查询Audio的特性。从实现上来看,CarAudioManager除了有AudioManager的属性,还直接和CarAudioService进行交互,包括回调的调用,设置和获取Group的音量等,最终都需要调用CarAudioService,可在后续章节继续分析。从CarAudioManager的设计而言,其继承了CarManagerBase。基类的特点是都维护了一个Car的对象,并且提供了一些公共的处理异常,dump的接口。CarAudioManager可以通过如下形式进行获取:

1
mCarAudioManager = (CarAudioManager) car.getCarManager(Car.AUDIO_SERVICE); 

3. CarAudioService

在学习CarAudioService前,先了解下车载相关的配置。

1.config.xml:

在packages/services/Car/service/res/values/config.xml记录了有关车载的特性,包括官网上提到的:

  • audioUseDynamicRouting:开启动态路由,此时可以启用AAOS的路由策略。
  • audioUseCoreVolume:使用核心的音量API
  • audioUseCoreRouting:使用核心的音频路由API,可以和audioUseDynamicRouting共用。
  • audioUseCarVolumeGroupMuting:启动后能够根据Volume Grou进行独立的静音动作。
  • audioUseHalDuckingSignals:打开后,当需要对音频进行闪躲(duck)时,上层就能够通过IAudioControl#onDevicesToDuckChange通知到HAL层。

CarAudioService的获取类似如下代码,用户可以在方案上通过overlay的方式对这些值进行赋值,也可以使用官网提到的在运行时改动应用资源https://source.android.com/docs/core/runtime/rros?hl=zh-cn

1
mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting);
  1. car_audio_configuration.xml

为了实现开始的框架所示的音频分区的功能,AAOS新增了car_audio_configuration.xml, 其结构和audio_policy_configuration.xml大不相同,包括:

  • 可以定义多个Zone音频区,至少有一个primary Zone。
  • Zone内可以定义zoneConfig,zoneConfig可以定义VolumeGroups
  • VolumeGroups中可以定义多个group,每个group可以绑定多个device,并且指定device的context上下文,如是播放音乐的,还是导航的。
  • device会定义address,这里又和audio_policy_configuration.xml中的address中一一对应。

如官网展示的示例,分为了两个音频区,第一个区包括两个音量组,分别以bus_1,bus_2作为地址,与audio_policy_configuration.xml的devicePorts对应。前者负责音频,后者负责导航。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<carAudioConfiguration version="3">
    <zones>
        <zone name="Zone0" audioZneId="0" occupantZoneI="0">
            <zoneConfigs>
                <zoneConfig name="config0" isDefault="true">
                   <volumeGoups>
                        <group>
                            <device address="bus_1">
                                <context context="music"/>
                            </device>
                        </group>
                        <group>
                            <device address="bus_2">
                                <context context="navigation"/>
                            </device>
                        </group>
                        ...
                    </volumeGroups>
                </zoneConfig>
            </zoneConfigs>
        </zone>
        <zone name="Zone1" audioZoneId="1" occupantZoneId="1">
            <zoneConfigs>
                <zoneConfig name="config0" isDefault="true">
                    <volumeGroups>
                        <group>
                            <device address="bus_6">
                                <context context="music"/>
                            </device>
                        </group>
                        <group>
                            <device address="bus_7">
                                <context context="navigation"/>
                           </device>
                       </group>
                       ...
                    </volumeGroups>
                </zoneConfig>
            </zoneConfigs>
        </zone>
        ...
    ...
   </zones>
</carAudioConfiguration>