Android Audio分区——音频分区加载流程(三)

2024-09-02 11:52

本文主要是介绍Android Audio分区——音频分区加载流程(三),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

        前面文章介绍了车载多区音频基础,并且介绍了音频分区相关类及对应功能,这里我们就来看一下音频分区的解析过程。

一、音频分区加载

        音频分区的加载是在 CarAudioService 的初始化函数 init() 流程中进行的。

1、CarAudioService.java

源码位置:/packages/services/Car/service/src/com/android/car/audio/CarAudioService.java

init

@Override
public void init() {synchronized (mImplLock) {// 获取CarOccupantZoneService服务,该服务在车辆网中介绍过mOccupantZoneService = CarLocalServices.getService(CarOccupantZoneService.class);Car car = new Car(mContext, /* service= */null, /* handler= */ null);mOccupantZoneManager = new CarOccupantZoneManager(car, mOccupantZoneService);if (mUseDynamicRouting) {// 设置动态音频路由setupDynamicRoutingLocked();// 设置HAL音频焦点监听器setupHalAudioFocusListenerLocked();} else {// 动态路由为其用,设置传统模式下的音量变化监听器setupLegacyVolumeChangedListener();}// 恢复主静音状态if (mPersistMasterMuteState) {// 获取存储的主静音状态boolean storedMasterMute = mCarAudioSettings.getMasterMute();// 设置主静音状态setMasterMute(storedMasterMute, 0);}// 设置支持的系统用途mAudioManager.setSupportedSystemUsages(SYSTEM_USAGES);}
}

        这里主要是根据动态音频路由是否启用来进行不同的配置,确保了系统能够正确地处理音频输出和音频焦点的变化。 

setupDynamicRoutingLocked

private void setupDynamicRoutingLocked() {// 创建音频策略构建器final AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);builder.setLooper(Looper.getMainLooper());// 加载音频区域loadCarAudioZonesLocked();// 同步音频区域增益索引for (CarAudioZone zone : mCarAudioZones) {// 确保HAL获得初始值zone.synchronizeCurrentGainIndex();}// 设置动态路由规则final CarAudioDynamicRouting dynamicRouting = new CarAudioDynamicRouting(mCarAudioZones);dynamicRouting.setupAudioDynamicRouting(builder);// 设置音频策略音量回调builder.setAudioPolicyVolumeCallback(mAudioPolicyVolumeCallback);// 配置音频焦点处理if (sUseCarAudioFocus) {mFocusHandler = new CarZonesAudioFocus(mAudioManager, mContext.getPackageManager(),mCarAudioZones, mCarAudioSettings, ENABLE_DELAYED_AUDIO_FOCUS);builder.setAudioPolicyFocusListener(mFocusHandler);builder.setIsAudioFocusPolicy(true);}mAudioPolicy = builder.build();if (sUseCarAudioFocus) {// 连接音频策略和焦点监听器mFocusHandler.setOwningPolicy(this, mAudioPolicy);}// 注册音频策略int r = mAudioManager.registerAudioPolicy(mAudioPolicy);if (r != AudioManager.SUCCESS) {throw new RuntimeException("registerAudioPolicy failed " + r);}// 设置乘员区域信息setupOccupantZoneInfo();
}

        该方法用于设置动态音频路由,包括配置动态路由规则、设置音频策略、处理音频焦点事件等。这里我们主要看一下音频区的加载。

loadCarAudioZonesLocked

private CarAudioZone[] mCarAudioZones;
private String mCarAudioConfigurationPath;private void loadCarAudioZonesLocked() {// 生成音频设备信息List<CarAudioDeviceInfo> carAudioDeviceInfos = generateCarAudioDeviceInfos();// 获取音频配置路径mCarAudioConfigurationPath = getAudioConfigurationPath();if (mCarAudioConfigurationPath != null) {// 从音频配置文件中加载音频区域mCarAudioZones = loadCarAudioConfigurationLocked(carAudioDeviceInfos);} else {// 从音量组配置中加载音频区域mCarAudioZones = loadVolumeGroupConfigurationWithAudioControlLocked(carAudioDeviceInfos);}// 验证加载的音频区域是否有效CarAudioZonesValidator.validate(mCarAudioZones);
}

        该方法用于加载所有可用的音频区域(CarAudioZone)。它首先生成音频设备信息,然后根据是否存在特定的音频配置路径来决定如何加载音频区域。这里我们先来看一下获取音频配置路径,然后再看音频区的加载流程。

getAudioConfigurationPath

private static final String[] AUDIO_CONFIGURATION_PATHS = new String[] {"/vendor/etc/car_audio_configuration.xml","/system/etc/car_audio_configuration.xml"
};private String getAudioConfigurationPath() {for (String path : AUDIO_CONFIGURATION_PATHS) {File configuration = new File(path);// 检查文件是否存在if (configuration.exists()) {return path;}}return null;
}

        可以看到这里查找流程,先找到 /vendor/etc/ 路径下是否存在 car_audio_configuration.xml 文件,如果存在直接返回,不存在则查找 /system/etc/ 下的对应文件。vendor 和 system 代表厂商定制和系统配置,而 car_audio_configuration.xml 文件正是我们前面介绍的车载音频配置文件。

loadCarAudioConfigurationLocked

private CarAudioZone[] loadCarAudioConfigurationLocked(List<CarAudioDeviceInfo> carAudioDeviceInfos) {// 获取所有输入设备AudioDeviceInfo[] inputDevices = getAllInputDevices();// 打开音频配置文件try (InputStream inputStream = new FileInputStream(mCarAudioConfigurationPath)) {// 创建CarAudioZonesHelper实例CarAudioZonesHelper zonesHelper = new CarAudioZonesHelper(mCarAudioSettings,inputStream, carAudioDeviceInfos, inputDevices);// 加载音频区域映射,获取音频区域ID到乘员区域ID的映射。mAudioZoneIdToOccupantZoneIdMapping = zonesHelper.getCarAudioZoneIdToOccupantZoneIdMapping();// 加载音频区域return zonesHelper.loadAudioZones();} catch (IOException | XmlPullParserException e) {throw new RuntimeException("Failed to parse audio zone configuration", e);}
}

        该方法用于从音频配置文件中加载所有可用的音频区域(CarAudioZone)。它首先读取音频配置文件的内容,然后使用 CarAudioZonesHelper 类的 loadAudioZones() 函数来解析配置文件并加载音频区域。

2、CarAudioZonesHelper

源码位置:/packages/services/Car/service/src/com/android/car/audio/CarAudioZonesHelper.java

loadAudioZones

CarAudioZone[] loadAudioZones() throws IOException, XmlPullParserException {List<CarAudioZone> carAudioZones = new ArrayList<>();// 解析音频区域parseCarAudioZones(carAudioZones, mInputStream);return carAudioZones.toArray(new CarAudioZone[0]);
}

        这里首先创建一个 ArrayList 来存储解析得到的音频区域,然后调用 parseCarAudioZones() 方法来解析输入流中的数据,并将结果转换为 CarAudioZone 数组返回。 

parseCarAudioZones

private static final String TAG_ROOT = "carAudioConfiguration";
private static final String TAG_AUDIO_ZONES = "zones";
private static final int INVALID_VERSION = -1;
private static final int SUPPORTED_VERSION_2 = 2;private void parseCarAudioZones(List<CarAudioZone> carAudioZones, InputStream stream)throws XmlPullParserException, IOException {// 创建XML解析器final XmlPullParser parser = Xml.newPullParser();parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, NAMESPACE != null);parser.setInput(stream, null);// 跳过文档中的注释或空白parser.nextTag();// 确保当前元素是<carAudioConfiguration>parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_ROOT);// 获取XML文档中的版本号final int versionNumber = Integer.parseInt(parser.getAttributeValue(NAMESPACE, ATTR_VERSION));if (SUPPORTED_VERSIONS.get(versionNumber, INVALID_VERSION) == INVALID_VERSION) {throw new IllegalArgumentException("Latest Supported version:" + SUPPORTED_VERSION_2 + " , got version:" + versionNumber);}mCurrentVersion = versionNumber;// 遍历XML文档直到遇到根元素的结束标签while (parser.next() != XmlPullParser.END_TAG) {if (parser.getEventType() != XmlPullParser.START_TAG) continue;// 如果当前元素是<audioZones>,则调用parseAudioZones()方法解析音频区域。if (TAG_AUDIO_ZONES.equals(parser.getName())) {// 解析音频区域parseAudioZones(parser, carAudioZones);} else {// 跳过该元素及其子元素skip(parser);}}
}

        这里首先确保 XML 文档的根元素是 <carAudioConfiguration>,然后检查版本号是否支持,接着解析所有的音频区域配置。 

parseAudioZones

private static final String TAG_AUDIO_ZONE = "zone";private void parseAudioZones(XmlPullParser parser, List<CarAudioZone> carAudioZones)throws XmlPullParserException, IOException {while (parser.next() != XmlPullParser.END_TAG) {if (parser.getEventType() != XmlPullParser.START_TAG) continue;if (TAG_AUDIO_ZONE.equals(parser.getName())) {carAudioZones.add(parseAudioZone(parser));} else {skip(parser);}}Preconditions.checkArgument(mHasPrimaryZone, "Requires one primary zone");carAudioZones.sort(Comparator.comparing(CarAudioZone::getId));
}

       与上面的解析流程基本相同,只是解析的标签不同,这里通过解析 <zone>  标签并将数据保存到 carAudioZones 中。

二、车载音频分区配置

        对于前面文章中的 xml 文件解析,是否注意到了对应的文件路径 "/vendor/etc/" 和 "/system/etc/",在源码中并未找到该路径,是不是有些疑惑。其实这是设备的内存路径,对应的源码文件并不在次,而是通过代码拷贝过去的。

1、system文件

源码位置:/device/google/trout/hal/audio/6.0/car_audio_configuration.xml

<!--定义汽车中的音频配置,包括- 音频区- 上下文到音频总线的映射- 音量组在汽车环境中。
-->
<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>

文件拷贝

 源码位置:/device/google/trout/aosp_trout_common.mk

LOCAL_AUDIO_PRODUCT_COPY_FILES ?= \device/google/trout/hal/audio/6.0/car_audio_configuration.xml:$(TARGET_COPY_OUT_VENDOR)/etc/car_audio_configuration.xml \

2、vendor文件

        对于 vendor 下的配置文件都是车企定制的配置,根据需要去设置分组及音频流等信息,并且同样需要在 mk 文件中进行拷贝操作,这里就不再展示代码了。

源码位置:device/{硬件平台}/audio/{产品}/config/audio/car_audio_configuration.xml

这篇关于Android Audio分区——音频分区加载流程(三)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1129880

相关文章

2.1/5.1和7.1声道系统有什么区别? 音频声道的专业知识科普

《2.1/5.1和7.1声道系统有什么区别?音频声道的专业知识科普》当设置环绕声系统时,会遇到2.1、5.1、7.1、7.1.2、9.1等数字,当一遍又一遍地看到它们时,可能想知道它们是什... 想要把智能电视自带的音响升级成专业级的家庭影院系统吗?那么你将面临一个重要的选择——使用 2.1、5.1 还是

SpringBoot项目启动后自动加载系统配置的多种实现方式

《SpringBoot项目启动后自动加载系统配置的多种实现方式》:本文主要介绍SpringBoot项目启动后自动加载系统配置的多种实现方式,并通过代码示例讲解的非常详细,对大家的学习或工作有一定的... 目录1. 使用 CommandLineRunner实现方式:2. 使用 ApplicationRunne

Python实现NLP的完整流程介绍

《Python实现NLP的完整流程介绍》这篇文章主要为大家详细介绍了Python实现NLP的完整流程,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 编程安装和导入必要的库2. 文本数据准备3. 文本预处理3.1 小写化3.2 分词(Tokenizatio

SpringBoot项目删除Bean或者不加载Bean的问题解决

《SpringBoot项目删除Bean或者不加载Bean的问题解决》文章介绍了在SpringBoot项目中如何使用@ComponentScan注解和自定义过滤器实现不加载某些Bean的方法,本文通过实... 使用@ComponentScan注解中的@ComponentScan.Filter标记不加载。@C

springboot 加载本地jar到maven的实现方法

《springboot加载本地jar到maven的实现方法》如何在SpringBoot项目中加载本地jar到Maven本地仓库,使用Maven的install-file目标来实现,本文结合实例代码给... 在Spring Boothttp://www.chinasem.cn项目中,如果你想要加载一个本地的ja

最好用的WPF加载动画功能

《最好用的WPF加载动画功能》当开发应用程序时,提供良好的用户体验(UX)是至关重要的,加载动画作为一种有效的沟通工具,它不仅能告知用户系统正在工作,还能够通过视觉上的吸引力来增强整体用户体验,本文给... 目录前言需求分析高级用法综合案例总结最后前言当开发应用程序时,提供良好的用户体验(UX)是至关重要

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

SpringBoot使用minio进行文件管理的流程步骤

《SpringBoot使用minio进行文件管理的流程步骤》MinIO是一个高性能的对象存储系统,兼容AmazonS3API,该软件设计用于处理非结构化数据,如图片、视频、日志文件以及备份数据等,本文... 目录一、拉取minio镜像二、创建配置文件和上传文件的目录三、启动容器四、浏览器登录 minio五、

MyBatis延迟加载的处理方案

《MyBatis延迟加载的处理方案》MyBatis支持延迟加载(LazyLoading),允许在需要数据时才从数据库加载,而不是在查询结果第一次返回时就立即加载所有数据,延迟加载的核心思想是,将关联对... 目录MyBATis如何处理延迟加载?延迟加载的原理1. 开启延迟加载2. 延迟加载的配置2.1 使用

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超