Android R- CarAudioService之car_audio_configuration.xml解析

11 篇文章 27 订阅
6 篇文章 15 订阅

前言

关于car_audio_configuration.xml的解析这部分在Android R上还是有一点变化的。具体我们一步一步来分析下其解析原理和过程。

car_audio_configuration.xml

首先关于这个xml配置文件的位置,aosp的源码位置为/device/generic/car/emulator/audio/car_emulator_audio.mk,当然也可具体根据自己的情况来定,反正最后放到车机的路径是固定的,具体如下

     device/generic/car/emulator/audio/car_audio_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/car_audio_configuration.xml 

然后看下这个配置文件里面的内容

<!--
  Defines the audio configuration in a car, including
    - Audio zones
    - Context to audio bus mappings
    - Volume groups
  in the car environment.
-->
<carAudioConfiguration version="2">
    <zones>
        <zone name="primary zone" isPrimary="true" occupantZoneId="0">
            <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>
        <zone name="rear seat zone 2"  audioZoneId="2">
            <volumeGroups>
                <group>
                    <device address="bus200_audio_zone_2">
                        <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>

从xml的注释就看出来来了,这个配置文件主要有

  • Audio zones
  • Context to audio bus mappings
  • Volume groups
    这三个功能,首先说下Audio Zones,通过xml可以看到有primary zone和"rear seat zone 1和"rear seat zone 2一共三个Zone。首先看下Zone的加载时序
    在这里插入图片描述
    在CaeAudioService启动的时候,会调用 setupDynamicRoutingLocked进行动态路由的注册,注册前首先会解析这个xml,即调用 loadCarAudioZonesLocked,我们看下源码
    private void loadCarAudioZonesLocked() {
        //主要获取Device是AUDIO_DEVICE_OUT_BUS的device,然后再过滤掉输入的Device,剩下就是用于输出的Device,
        //最后将Device转化成CarAudioDeviceInfo
        List<CarAudioDeviceInfo> carAudioDeviceInfos = generateCarAudioDeviceInfos();
		
        mCarAudioConfigurationPath = getAudioConfigurationPath();
        if (mCarAudioConfigurationPath != null) {
        	// 解析的xml主要存在vendor/etc下,如果vendor下不存在,则会去system/etc下去寻找。
            mCarAudioZones = loadCarAudioConfigurationLocked(carAudioDeviceInfos);
        } else {
            mCarAudioZones = loadVolumeGroupConfigurationWithAudioControlLocked(
                    carAudioDeviceInfos);
        }

        CarAudioZonesValidator.validate(mCarAudioZones);
    }

具体关于CarAudioDeviceInfo都有哪些,这里不细说了,可以参照之前的分析。其实这些Devices的信息都是来自audio_policy_configuration.xml中的devicePort下type为AUDIO_DEVICE_OUT_BUS的Device,这个Device的type全部为AUDIO_DEVICE_OUT_BUS,而且address都是以bus+数字开头的。
接下来具体看下CarAudioZone是如何解析的,继续看loadCarAudioConfigurationLocked()

    private SparseArray<CarAudioZone> loadCarAudioConfigurationLocked(
            List<CarAudioDeviceInfo> carAudioDeviceInfos) {
         // 获取输入的device设备信息
        AudioDeviceInfo[] inputDevices = getAllInputDevices();
        try (InputStream inputStream = new FileInputStream(mCarAudioConfigurationPath)) {
            //CarAudioZonesHelper 来具体解析car_audio_configuration.xml
            CarAudioZonesHelper zonesHelper = new CarAudioZonesHelper(mCarAudioSettings,
                    inputStream, carAudioDeviceInfos, inputDevices);
            //一个zoneID和occupantZoneId对应的集合
            mAudioZoneIdToOccupantZoneIdMapping =
                    zonesHelper.getCarAudioZoneIdToOccupantZoneIdMapping();
             // 加载xml
            return zonesHelper.loadAudioZones();
        } catch (IOException | XmlPullParserException e) {
            throw new RuntimeException("Failed to parse audio zone configuration", e);
        }
    }

这里说下inputDevices,之前的carAudioDeviceInfos找到主要是输出设备即device的role = sink,而inputDevices 则找到主要是输入设备即device的role = source,对应的type = AUDIO_DEVICE_IN_BUS。
拿到输出和输入设备以及xml的path,把这些给到CarAudioZonesHelper来做解析和初始化。
略过CarAudioZonesHelper的构造函数,直接进入loadAudioZones()。

    SparseArray<CarAudioZone> loadAudioZones() throws IOException, XmlPullParserException {
        //mInputStream即CarAudioService中传入的用于解析xml的FileInputStream
        return parseCarAudioZones(mInputStream);
    }

继续看parseCarAudioZones(),即开始真正的解析。

    private SparseArray<CarAudioZone> parseCarAudioZones(InputStream stream)
            throws XmlPullParserException, IOException {
          ........
		 /* 略掉无用的部分 直奔主题*/
        // Get all zones configured under <zones> tag
        while (parser.next() != XmlPullParser.END_TAG) {
            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
            //开始真正的解析,首先找到 <zones>标签
            if (TAG_AUDIO_ZONES.equals(parser.getName())) {
                // parser即XmlPullParser
                return parseAudioZones(parser);
            } else {
                skip(parser);
            }
        }
        throw new RuntimeException(TAG_AUDIO_ZONES + " is missing from configuration");
    }

通过xml可以看到只有一组 zones标签,即while只执行一次parseAudioZones

    private SparseArray<CarAudioZone> parseAudioZones(XmlPullParser parser)
            throws XmlPullParserException, IOException {
         // 创建carAudioZones,最终返给CarAudioService的mCarAudioZones 
        SparseArray<CarAudioZone> carAudioZones = new SparseArray<>();
		//固定的解析模板,开始while循环
        while (parser.next() != XmlPullParser.END_TAG) {
            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
            // 开始解析 zone标签
            if (TAG_AUDIO_ZONE.equals(parser.getName())) {
            	// 一层一层向内部解析,继续解析 zone标签内部的元素
                CarAudioZone zone = parseAudioZone(parser);
                //check 检查是已经存在有primary zone,如果存在而且多个便会抛出异常,
                // 因为有primary zone必须要有且只能有一个
                verifyOnlyOnePrimaryZone(zone, carAudioZones);
                
                carAudioZones.put(zone.getId(), zone);
            } else {
                skip(parser);
            }
        }
		//check 检查是否有primary zone,因为一定要有primary zone 否则会抛异常
        verifyPrimaryZonePresent(carAudioZones);
        //返回carAudioZones,最终给到CarAudioService
        return carAudioZones;
    }

这里开始解析zone标签,通过xml可以看到这里有三组zone分别是primary zone 、rear seat zone 1和rear seat zone 2,
要调用三次parseAudioZone(parser)即创建三个CarAudioZone。继续看parseAudioZone()

    private CarAudioZone parseAudioZone(XmlPullParser parser)
            throws XmlPullParserException, IOException {
        // 解析isPrimary属性,这里primary zone 是ture
        final boolean isPrimary = Boolean.parseBoolean(
                parser.getAttributeValue(NAMESPACE, ATTR_IS_PRIMARY));
         // 解析xml得到 zoneName
        final String zoneName = parser.getAttributeValue(NAMESPACE, ATTR_ZONE_NAME);
        // 解析xml得到 zoneId
        final int audioZoneId = getZoneId(isPrimary, parser);
        // check Occupant zone Id
        parseOccupantZoneId(audioZoneId, parser);
        // 构建 CarAudioZone 
        final CarAudioZone zone = new CarAudioZone(audioZoneId, zoneName);
        while (parser.next() != XmlPullParser.END_TAG) {
            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
            // Expect one <volumeGroups> in one audio zone
            if (TAG_VOLUME_GROUPS.equals(parser.getName())) {
               // 解析 volume group
                parseVolumeGroups(parser, zone);
            } else if (TAG_INPUT_DEVICES.equals(parser.getName())) {
                 //解析 inputdevices
                parseInputAudioDevices(parser, zone);
            } else {
                skip(parser);
            }
        }
        return zone;
    }

zoneName 的获取直接通过解析对应的属性就拿到了,简单说下audioZoneId,我们看到了不是直接去解析xml而是调用了getZoneId(isPrimary, parser)函数,其实这个函数对primary zone做了特殊处理。如果我们传入的isPrimary是true,那么它的audioZoneId就是0即PRIMARY_AUDIO_ZONE。因此这个xml里对于primary zone也就未定义audioZoneId。这里还对primary zone的 audioZoneId做了强制要求就是如果我们定义了audioZoneId,它的值必须必须是0,而且其他zoneId不可以定义0.
再说下parseOccupantZoneId(audioZoneId, parser),这个Android R新增的。这里是解析occupantZoneId然后把它和audioZoneId一起放到map中,即 mZoneIdToOccupantZoneIdMapping.put(audioZoneId, occupantZoneId),occupantZoneId不能重复,但还没看到是否可以定义多个,以及occupantZoneId是否也是必须是从0开始。但是一般只定义一个,如 occupantZoneId="0"这样。
最后关于这个函数再看下CarAudioZone的定义

    CarAudioZone(int id, String name) {
        mId = id;// audioZoneId
        mName = name;// zoneName 
        mVolumeGroups = new ArrayList<>(); // VolumeGroup list
        mInputAudioDevice = new ArrayList<>();// InputAudioDevice list
    }

好的继续看VolumeGroups的解析,这个算是重点了。

    private void parseVolumeGroups(XmlPullParser parser, CarAudioZone zone)
            throws XmlPullParserException, IOException {
        int groupId = 0;
        while (parser.next() != XmlPullParser.END_TAG) {
            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
            if (TAG_VOLUME_GROUP.equals(parser.getName())) {
                 //继续解析 group 标签
                zone.addVolumeGroup(parseVolumeGroup(parser, zone.getId(), groupId));
                 // groupId 从0开始 累加
                groupId++;
            } else {
                skip(parser);
            }
        }
    

这里分析下primary zone的group解析,其他zone相同,primary zone里共有四组group,我们先看第一组group的解析parseVolumeGroup(parser, zone.getId(), groupId)

    private CarVolumeGroup parseVolumeGroup(XmlPullParser parser, int zoneId, int groupId)
            throws XmlPullParserException, IOException {
        // 创建volumeGroup 对象
        CarVolumeGroup group = new CarVolumeGroup(mCarAudioSettings, zoneId, groupId);
        while (parser.next() != XmlPullParser.END_TAG) {
            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
            // 解析device标签
            if (TAG_AUDIO_DEVICE.equals(parser.getName())) {
                 // 获取device标签下的 address属性
                String address = parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_ADDRESS);
                // check address有效性
                validateOutputDeviceExist(address);
                // 主要处理声音分组
                parseVolumeGroupContexts(parser, group, address);
            } else {
                skip(parser);
            }
        }
        return group;
    }

这里开始解析volumeGroup了,这块先看下时序图
在这里插入图片描述
时序画的简单些了,具体看下每个过程,主要这个函数parseVolumeGroupContexts(),我们知道car_audio_configuration.xml这个xml做了三件事,其中这个就是Volume groups。我们再具体看下这个函数:

    private void parseVolumeGroupContexts(
            XmlPullParser parser, CarVolumeGroup group, String address)
            throws XmlPullParserException, IOException {
        while (parser.next() != XmlPullParser.END_TAG) {
            if (parser.getEventType() != XmlPullParser.START_TAG) continue;
            if (TAG_CONTEXT.equals(parser.getName())) {
            	// 解析context标签 
                @AudioContext int carAudioContext = parseCarAudioContext(
                        parser.getAttributeValue(NAMESPACE, ATTR_CONTEXT_NAME));
                // check carAudioContext 的正确性
                validateCarAudioContextSupport(carAudioContext);
                // 根据address得到CarAudioDeviceInfo 
                CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address);
                // 将caraudiodeviceinfo 和carAudioContext bind到一起
                group.bind(carAudioContext, info);

                // If V1, default new contexts to same device as DEFAULT_AUDIO_USAGE
                // 这里走不到 用的都是version 2
                if (isVersionOne() && carAudioContext == CarAudioService.DEFAULT_AUDIO_CONTEXT) {
                    bindNonLegacyContexts(group, info);
                }
            }
            // Always skip to upper level since we're at the lowest.
            skip(parser);
        }
    }

首先说下carAudioContext是怎么取到的,因为xml给的是一个string,这里是int,肯定是在哪里做了映射,映射的地方如下

    static {
        CONTEXT_NAME_MAP = new HashMap<>(CarAudioContext.CONTEXTS.length);
        CONTEXT_NAME_MAP.put("music", CarAudioContext.MUSIC);
        CONTEXT_NAME_MAP.put("navigation", CarAudioContext.NAVIGATION);
        CONTEXT_NAME_MAP.put("voice_command", CarAudioContext.VOICE_COMMAND);
        CONTEXT_NAME_MAP.put("call_ring", CarAudioContext.CALL_RING);
        CONTEXT_NAME_MAP.put("call", CarAudioContext.CALL);
        CONTEXT_NAME_MAP.put("alarm", CarAudioContext.ALARM);
        CONTEXT_NAME_MAP.put("notification", CarAudioContext.NOTIFICATION);
        CONTEXT_NAME_MAP.put("system_sound", CarAudioContext.SYSTEM_SOUND);
        CONTEXT_NAME_MAP.put("emergency", CarAudioContext.EMERGENCY);
        CONTEXT_NAME_MAP.put("safety", CarAudioContext.SAFETY);
        CONTEXT_NAME_MAP.put("vehicle_status", CarAudioContext.VEHICLE_STATUS);
        CONTEXT_NAME_MAP.put("announcement", CarAudioContext.ANNOUNCEMENT);

        SUPPORTED_VERSIONS = new SparseIntArray(2);
        SUPPORTED_VERSIONS.put(SUPPORTED_VERSION_1, SUPPORTED_VERSION_1);
        SUPPORTED_VERSIONS.put(SUPPORTED_VERSION_2, SUPPORTED_VERSION_2);
    }

这样我们就拿到了carAudioContext,完事CarAudioDeviceInfo的获取就不说了(上面说过了)拿到info和contex后一起传给了CarVolumeGroup的bind

    void bind(int carAudioContext, CarAudioDeviceInfo info) {
        Preconditions.checkArgument(mContextToAddress.get(carAudioContext) == null,
                String.format("Context %s has already been bound to %s",
                        CarAudioContext.toString(carAudioContext),
                        mContextToAddress.get(carAudioContext)));

        synchronized (mLock) {
             // 第一次mAddressToCarAudioDeviceInfo的size是0的,
             // 存在一个device标签下有多个context的情况,要bind 多次
             // 只有第一次拿info的step
            if (mAddressToCarAudioDeviceInfo.size() == 0) {
               // 这个值最早来自audio_policy_configuration.xml下的stepValueMB
                mStepSize = info.getStepValue();
            } else {
              // 如果不是第一次调用group的bind ,需要check每个device的StepValue是否相同,
              // 即同一个group下不同device的step必须是一样的,因为同一个group的音量是一起调节的
                Preconditions.checkArgument(
                        info.getStepValue() == mStepSize,
                        "Gain controls within one group must have same step value");
            }
			// 存入mAddressToCarAudioDeviceInfo
            mAddressToCarAudioDeviceInfo.put(info.getAddress(), info);
            // 存入mContextToAddress
            mContextToAddress.put(carAudioContext, info.getAddress());
			// 更新DefaultGain,同一个group里deviceinfo里最大的DefaultGain
            if (info.getDefaultGain() > mDefaultGain) {
                // We're arbitrarily selecting the highest
                // device default gain as the group's default.
                mDefaultGain = info.getDefaultGain();
            }
            // 更新mMaxGain,同一个group里deviceinfo里最大的MaxGain
            if (info.getMaxGain() > mMaxGain) {
                mMaxGain = info.getMaxGain();
            }
            // // 更新mMinGain ,同一个group里deviceinfo里最小的MinGain
            if (info.getMinGain() < mMinGain) {
                mMinGain = info.getMinGain();
            }
            // 更新当前音量,有存储的音量,用存储的音量,没有存储的音量用(default - min)/setp
            updateCurrentGainIndexLocked();
        }
    }

bind是一个很重要的函数,主要是给音量分组,更新当前组音量的最大/小值,默认值以及当前音量值。并且维护了两个很重要的的map是mAddressToCarAudioDeviceInfo(address为key,CarAudioDeviceInfo 为value)和mContextToAddress(context是key,address是value),其实有了这两个map我们就可以根据context找到address,根据address找到CarAudioDeviceInfo 。把context和CarAudioDeviceInfo 路由在了一起。
我们继续再回头看下 CarVolumeGroup里还有什么, mZoneId 和mId(groupId),这样一个CarVolumeGroup对应的zoneId以及自己的Id和内部维护的context以及CarAudioDeviceInfo 就全部关联上了。

总结

其实到这里整个xml基本就要解析完成了,回顾下这三步:

  • Audio zones
    有三个zone,一个primary zone也是occupant Zone
  • Context to audio bus mappings
    在解析VolumeGroup时将context和CarAudioDeviceInfo mapping到一起了,具体就是bind中做的
  • Volume groups
    每个zone都至少有个volumeGroups,同一个volumeGroups里有几个group就有几组音量。同一组的音量相同。
    这个car_audio_configuration.xml看似很简单,但是如果我们在项目中需要定制,需要修改某一部分一定要多注意,因为每个标签的属性都是有一定的关联和限定的。
    下一次继续分析解析car_audio_configuration.xml后做的动态路由,以及多音区的焦点管理。
  • 0
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

轻量级LZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值