Android汽车服务篇(四) CarAudioService

一.简介

        本文将基于CarService中另一个重要的服务CarAudioService以及其对应的CarAudioManager介绍汽车音频的相关内容.

        在车载上,音频设备的数量还是使用场景都和手机有很大的不同,紧靠Android原有的音频服务是无法满足在车内的使用需求的.

        因此AAOS对Android原有的音频机制进行了扩充. 在CarService中加入了CarAudioService.对音频设备进行更加细致的管理,以满足车上的使用场景.

二. 音量控制        

2.1  音量组

        汽车音频中新增了音量组的概念. 所谓音量组,就是将不同用途的声音进行归纳,按组对相关的音量进行控制.

        在进一步解释音量组的概念之前,先了解一下音频上下文(Audio Context)和音频属性(Audio Attribute)两个概念.

        Android中的每段声音都由相应的应用和声音生成的原因来识别, 然后Android设备会使用这些信息来确认如何呈现声音. 

音频属性与音频上下文的具体对应关系如表:

音频上下文 音频属性对象
MUSIC MEDIA
VOICE_COMMAND USAGE_ASSISTANT
NAVIGATION ASSISTANCE_NAVIGATION_GUIDEANCE
CALL VOICE_COMMUNICATION
RINGTONE NOTIFICATION_RINGTONE
NOTIFICATION NOTIFICATION
ALARM ALARM
SYSTEM_SOUND ASSISTANCE_SONIFICATION
UNKNOWN UNKNOWN

也就是说, 应用在使用音频设备时, 需要指定相应的音频属性,而音频属性对应着相关的音频上下文. 音量组就是对音频上下文进行了分组,在同一组内的上下文,音量会同步变化.

而如何对音量组进行分类,则由制造商自己定义,如果制造商没有定义,则使用AAOS的默认分组(Android10中首选以car_audio_configuration.xml文件读取音频配置).

2.2 音量组的定义与加载

        音量组通过car_volume_groups.xml文件进行定义,默认的配置文件的源码路径为:

packages/services/Car/service/res/xml/car_volume_groups.xml   

<volumeGroups xmlns:car="http://schemas.android.com/apk/res-auto"
        car:isDeprecated="true">
    <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>

从XML文件中的内容可以发现CarAudioService默认的音量组分为4组

其中 music, call_ring, notification,system_sound为一组;  navigation, voice_command组成另一组; 而call 和 alarm单独为一组.

在同一组中的音频虽然属于不同的上下文,但是它们的音量变化是联动的.  当然,各制造商可以覆盖以上默认定义,对音频上下文进行不同的组合,或者增加分组的数量.  但是需要满足以下条件:

1.  一个音频上下文只能归属于一个组

2. 除去UNKNOWN, 共有8个有效的音量上下文类型,这8个类型都需要进行分组,不能缺省.

3.  上下文对应的底层的总线设备不应该出现在两个组中

4. 在同一组的音频上下文的单次调节的步长度应该一致.

在Android10之后,为了更好地支持多音区音频的特性,修改了汽车音频配置文件的格式.同时文件也不再以Android资源的形式进行配置,而是通过系统构建时复制到指定的路径下进行读取.

PRODUCT_COPY_FILES += \
    vendor/custom/car_audio_configuration.xml: $(TARGET_COPY_OUT_VENDRO)/etc/car_audio_configuration.xml

2.3 音量控制接口

有了音量组,就要通过音量组对音量大小进行控制, 这点和手机不一样, 手机上面使用AudiaManager的setStreamVolume方法进行音量调节,但是在AAOS中该方法很可能是无效的(取决于制造商的配置).

原因是AAOS上建议的是使用车上的硬件放大器完成对音量大小的调节,而非软件混音器.  系统音量的调节需要通过CarAudioManager中提供的接口进行控制.  其中相关的接口都是SystemAPI, 即系统级别的接口,所以普通第三方应用是无法使用的. 也就是说在实际驾驶过程中,第三方应用的音量调节主要依赖系统设置或者相关控制按键(如音量旋钮, 方向盘音量按键)实现.

CarAudioManager实例的获取方法和CarPropertyManager的获取类似,也是通过

        Car car = Car.createCar(this);
        CarAudioManager carAudioManager = (CarAudioManager)car.getCarManager(Car.AUDIO_SERVICE);

获取到CarAudioManager的实例之后,就可以使用它对音量进行调节了. 以增加某一Group的音量为例子:

        int volume = carAudioManager.getGroupVolume(groupID);
        carAudioManager.setGroupVolume(groupID, ++volume,
                AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_PLAY_SOUND);

通过getGroupVolume可以获取当前组的音量值,在通过setGroupVolume设置新的值.

那么这里有个疑问?如何知道想要调节的音频属性所对应的groupID呢? 因为制造商是可以重新定义分组的,不推荐开发者使用"硬编码"的方式, 为了使应用有较好的通用性,应用态获取音频属性所属的分组.  这里分为两个步骤实现:

1. 通过getVolumeGroupIdForUsage获取该Usage对应的groupId

2. 在利用获取的groupId设置音量

int groupID = carAudioManager.getVolumeGroupIdForUsage(AudioAttributes.USAGE_MEDIA);

调节音量需要申请对应的权限,因此不要忘记在清单文件中增加声明:

<uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"/>

同样的,该权限只有系统特权应用可以使用.

2.4 制造商实现与相关配置

        前文提到可以通过overlay机制覆盖CarAudioService默认的音量组实现音量组的自定义. 当然制造商可以做的和需要完成的事情不仅于此. 本节内容就基于CarAudioService的相关实现,对制造商的音频实现和相关配置进行一些补充.

        在AAOS中推荐使用硬件放大器来控制音量, 而在Android手机中,支持软件混音器调节音量,这也是Android框架中的默认配置. 因此,如果要改用硬件放大器调节音量, 首先需要覆盖config_useFixedVolume属性.

<bool name="config_useFixedVolume">false</bool>

/frameworks/base/core/res/res/values/config.xml 默认值为false, 该情况下,应用就可以调用AudioManager.setStreamVolume方法调节不同音频属性的音量. 

在车载设备上, 我们一般设置为true, 这样子AudioManager.setStreamVolume就不生效了.

继续看一下 CarAudioService的源码(Android10)

  public void init() {
        synchronized (mImplLock) {
            //注释1
            if (mUseDynamicRouting) {
                // Enumerate all output bus device ports
                AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(
                        AudioManager.GET_DEVICES_OUTPUTS);
                if (deviceInfos.length == 0) {
                    Log.e(CarLog.TAG_AUDIO, "No output device available, ignore");
                    return;
                }
                SparseArray<CarAudioDeviceInfo> busToCarAudioDeviceInfo = new SparseArray<>();
                for (AudioDeviceInfo info : deviceInfos) {
                    Log.v(CarLog.TAG_AUDIO, String.format("output id=%d address=%s type=%s",
                            info.getId(), info.getAddress(), info.getType()));
                    if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
                        final CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(info);
                        // See also the audio_policy_configuration.xml,
                        // the bus number should be no less than zero.
                        if (carInfo.getBusNumber() >= 0) {
                            busToCarAudioDeviceInfo.put(carInfo.getBusNumber(), carInfo);
                            Log.i(CarLog.TAG_AUDIO, "Valid bus found " + carInfo);
                        }
                    }
                }
                setupDynamicRouting(busToCarAudioDeviceInfo);
            } else {
                Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not enabled, run in legacy mode");
                setupLegacyVolumeChangedListener();
            }

            // Restore master mute state if applicable
            if (mPersistMasterMuteState) {
                boolean storedMasterMute = Settings.Global.getInt(mContext.getContentResolver(),
                        VOLUME_SETTINGS_KEY_MASTER_MUTE, 0) != 0;
                setMasterMute(storedMasterMute, 0);
            }
        }
    }

注释1处的 mUseDynamicRouting 这个boolean值非常重要,   实际上无论是硬件放大器调节音量还是音量组的划分, 都有一个先决条件, 那就是mUseDynamicRouting的值为true. 该值是在

/frameworks/base/core/res/res/values/config.xml中配置

因为它的默认值为false,所以需要制造商覆盖设置为true. 这一点非常重要,否则汽车音频相关特性都不会被使能.

三. 音频焦点

        在汽车音频中,音频焦点(Audio Focus)的处理和手机有所不同, 在具体介绍 AAOS中汽车音频焦点的实现之前, 需要了解一下Android的音频焦点机制

        Android 引入音频焦点机制的主要目的是协调多个应用同时播放音频时产生的声音竞争的问题, 从而避免系统中有多个应用同时发声, 而导致声音混杂,影响用户体验.

        每个应用在播放音频时需要申请音频焦点,当获得音频焦点成功或音频焦点被抢占后,应用应当根据相关的规则,暂停/继续播放音频.

          因此在音频焦点的基础上,为了保证车类音频体验,制造商可以通过在HAL层对不同的音频上下文采用强制性的策略.

        在AAOS中, 通过CarAudioFocus,汽车音频定义了属于自己的音频焦点规则.   见下表

       竖列代表获得焦点并正在播放中的音频上下文

       横行代表申请焦点的音频上下文.

       0: 代表焦点获取被拒绝

       1: 代表焦点申请成功, 且原持有者失去焦点

       2: 代表申请焦点成功,且原焦点保持

Context Music Nav Voice Ring Call Alarm Notification System
Music 1 2 1 1 1 1 2 2
Nav 2 2 1 2 1 2 2 2
Voice 2 0 2 1 1 0 0 0
Ring 0 2 2 2 2 0 0 2
Call 0 2 0 2 2 2 2 0
Alarm 2 2 1 1 1 2 2 2
Notification 2 2 1 1 1 2 2 2
System 2 2 1 1 1 2 2 2

举个例子: 

如果一个应用正在使用导航(Nav)上下文播放音频, 并获取了音频焦点,这时有一个应用以音乐(Music)上下文申请焦点, 那么申请结果为2  表示的意思: 焦点申请成功, 且导航应用保持焦点.

也就是说: 虽然音乐音频申请焦点成功了,导航音频却不会收到音频焦点丢失的通知  

这与手机不同, 手机系统中同时只有一个应用维持音频焦点, 有其他用应用获取了焦点,那么意味着之前的应用失去了焦点.

但是CarAudioFocus中却不是这样, 使用导航上下文和音乐上下文的应用可以同时维持着音频焦点. 这样的设计更符合在汽车上的使用场景, 因为汽车上往往不同音频对应不同的扬声器设备, 汽车本身已经有很好的车内音响环境, 有的时候不同的声音可以同时播放并提供给用户出色的体验感.       

猜你喜欢

转载自blog.csdn.net/u012514113/article/details/129855250