Android Audio基础——音频配置xml文件加载(七)

        通过前面的文章,我们知道在 AudioPolicyManager 初始化的时候回调用 loadConfig() 方法去加载 Audio 相关的配置信息,这里我们就来详细看一下。

一、配置文件加载

1、AudioPolicyManager

源码位置:/frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp

loadConfig

AudioPolicyManager::AudioPolicyManager(AudioPolicyClientInterface *clientInterface): AudioPolicyManager(clientInterface, false /*forTesting*/)
{loadConfig();
}void AudioPolicyManager::loadConfig() {// 处理音频配置xmlif (deserializeAudioPolicyXmlConfig(getConfig()) != NO_ERROR) {ALOGE("无法加载音频策略配置文件,设置默认值");getConfig().setDefault();}
}

deserializeAudioPolicyXmlConfig

#define AUDIO_POLICY_XML_CONFIG_FILE_PATH_MAX_LENGTH 128
#define AUDIO_POLICY_XML_CONFIG_FILE_NAME "audio_policy_configuration.xml"static status_t deserializeAudioPolicyXmlConfig(AudioPolicyConfig &config) {char audioPolicyXmlConfigFile[AUDIO_POLICY_XML_CONFIG_FILE_PATH_MAX_LENGTH];std::vector<const char*> fileNames;status_t ret;……// 保存要解析的Audio配置文件名fileNames.push_back(AUDIO_POLICY_XML_CONFIG_FILE_NAME);for (const char* fileName : fileNames) {// 获取配置文件路径for (const auto& path : audio_get_configuration_paths()) {snprintf(audioPolicyXmlConfigFile, sizeof(audioPolicyXmlConfigFile), "%s/%s", path.c_str(), fileName);// 开始解析文件ret = deserializeAudioPolicyFile(audioPolicyXmlConfigFile, &config);if (ret == NO_ERROR) {config.setSource(audioPolicyXmlConfigFile);return ret;}}}return ret;
}

      这里首先保存了要解析的 Audio 配置文件的名称,然后获取通过文件名获取文件存储路径,最后开始解析文件。我们先来看一下配置文件路径的获取 audio_get_configuration_paths() 方法。

2、audio_config.h

源码位置:/system/media/audio/include/system/audio_config.h

// 返回一个路径向量,其中音频配置文件必须按提供的顺序搜索。
static inline std::vector<std::string> audio_get_configuration_paths() {static const std::vector<std::string> paths = []() {char value[PROPERTY_VALUE_MAX] = {};// 获取属性ro.boot.product.vendor.sku的值if (property_get("ro.boot.product.vendor.sku", value, "") <= 0) { // 使用默认路径return std::vector<std::string>({"/odm/etc", "/vendor/etc", "/system/etc"});} else {// 增加了一个配置路径return std::vector<std::string>({"/odm/etc", std::string("/vendor/etc/audio/sku_") + value, "/vendor/etc", "/system/etc"});}}();return paths;
}

        这里主用通过系统属性 ro.boot.product.vendor.sku 的值判断是否定制配置文件路径。这里一般使用系统默认路径。

  • /odm/etc:这个路径通常用于 OEM(原始设备制造商)的专有或私有数据,比如那些在设备制造过程中预装的、与设备固件紧密相关的配置文件。ODM 数据通常不被用户修改,而且可能包含敏感信息。
  • /vendor/etc:这个路径是为供应商提供的配置文件,比如音频驱动或服务的特定设置。供应商可以在这一层定制硬件特定的音频配置,以适应他们的设备。这些配置文件通常随硬件驱动一起更新,且不会随着操作系统升级而改变。
  • /system/etc:这是系统级别的配置文件存放位置,包含了操作系统核心的音频设置。这些配置文件是系统的一部分,可能会随着 Android 系统的更新而改变。

        所以,对于 Audio 的相关配置,一般位于 /vendor/etc 下。下面我们继续看一下配置文件的加载。

3、Serializer.cpp

源码位置:/frameworks/av/services/audiopolicy/common/managerdefinitions/src/Serializer.cpp

deserializeAudioPolicyFile

status_t deserializeAudioPolicyFile(const char *fileName, AudioPolicyConfig *config)
{PolicySerializer serializer;return serializer.deserialize(fileName, config);
}

        这里调用了 PolicySerializer 的 deserialize() 方法。

deserialize

status_t PolicySerializer::deserialize(const char *configFile, AudioPolicyConfig *config)
{// 解析XML配置文件auto doc = make_xmlUnique(xmlParseFile(configFile));if (doc == nullptr) {ALOGE("%s: Could not parse %s document.", __func__, configFile);return BAD_VALUE;}// 获取XML文档的根节点xmlNodePtr root = xmlDocGetRootElement(doc.get());if (root == NULL) {ALOGE("%s: Could not parse %s document: empty.", __func__, configFile);return BAD_VALUE;}// 处理XML文档中的XInclude指令if (xmlXIncludeProcess(doc.get()) < 0) {ALOGE("%s: libxml failed to resolve XIncludes on %s document.", __func__, configFile);}// 检查根节点的名称是否与"rootName = "audioPolicyConfiguration""的一致if (xmlStrcmp(root->name, reinterpret_cast<const xmlChar*>(rootName)))  {ALOGE("%s: No %s root element found in xml data %s.", __func__, rootName,reinterpret_cast<const char*>(root->name));return BAD_VALUE;}//  获取根节点的版本属性std::string version = getXmlAttribute(root, versionAttribute);if (version.empty()) {ALOGE("%s: No version found in root node %s", __func__, rootName);return BAD_VALUE;}if (version != mVersion) {ALOGE("%s: Version does not match; expect %s got %s", __func__, mVersion.c_str(),version.c_str());return BAD_VALUE;}// 反序列化子模块ModuleTraits::Collection modules;status_t status = deserializeCollection<ModuleTraits>(root, &modules, config);if (status != NO_ERROR) {return status;}config->setHwModules(modules);// 全局配置,利用C++的函数模板语法调用到不同的deserializeGlobalConfigTraits::deserialize(root, config);// Surround configurationSurroundSoundTraits::deserialize(root, config);return android::OK;
}

        这里主要调用 deserializeCollection() 方法解析文件的。 

template <class Trait>
status_t deserializeCollection(const xmlNode *cur, typename Trait::Collection *collection,typename Trait::PtrSerializingCtx serializingContext)
{// 用两个for循环,遍历当前xml节点的所有子节点for (cur = cur->xmlChildrenNode; cur != NULL; cur = cur->next) {const xmlNode *child = NULL;if (!xmlStrcmp(cur->name, reinterpret_cast<const xmlChar*>(Trait::collectionTag))) {child = cur->xmlChildrenNode;} else if (!xmlStrcmp(cur->name, reinterpret_cast<const xmlChar*>(Trait::tag))) {child = cur;}for (; child != NULL; child = child->next) {if (!xmlStrcmp(child->name, reinterpret_cast<const xmlChar*>(Trait::tag))) {// 找到对应模板的名字,然后再调用auto element = Trait::deserialize(child, serializingContext);if (element.isOk()) {status_t status = Trait::addElementToCollection(element, collection);if (status != NO_ERROR) {ALOGE("%s: could not add element to %s collection", __func__,Trait::collectionTag);return status;}} else {return BAD_VALUE;}}}if (!xmlStrcmp(cur->name, reinterpret_cast<const xmlChar*>(Trait::tag))) {return NO_ERROR;}}return NO_ERROR;
}

        该函数是音频系统中用于恢复音频策略配置的一个重要步骤,尤其是在系统启动或音频服务重启时。它通过调用序列化器类的方法来读取外部存储的配置信息,以便于正确配置音频策略,保证系统能够按照预定的规则处理音频流。

        这样我们就将 audio_policy_configuration.xml 中的 Audio 相关信息保存到 AudioPolicyConfig 中了。

二、配置信息

1、audio_policy_configuration.xml

        audio_policy_configuration.xml 文件是 Android 音频框架中用于定义音频策略配置的 XML 文件,该文件通常随硬件驱动一起更新,且不会随着操作系统升级而改变。所以该文件通常在 Android 源码中无法看到,可以通过 adb shell 进入到车机的 /vendor/etc 目录下进行查看

<audio-policy-config><devices><device type="speaker" /><device type="headphone" /><device type="earpiece" /></devices><use-cases><use-case type="voice_call" /><use-case type="ringtone" /><use-case type="alarm" /><use-case type="media" /></use-cases><routes><route use-case="voice_call"><output device="earpiece" /></route><route use-case="ringtone"><output device="speaker" /></route><route use-case="alarm"><output device="speaker" /></route><route use-case="media"><output device="headphone" optional="true" /><output device="speaker" /></route></routes><volume-ranges><range type="media"><min-value>0</min-value><max-value>15</max-value></range><range type="ring"><min-value>0</min-value><max-value>15</max-value></range></volume-ranges><!-- Additional configuration elements -->
</audio-policy-config>

        这是一个简化版的 audio_policy_configuration.xml 示例,它包含了各种音频设备的配置信息,如设备类型、音量级别、路由策略等。解析这个文件的过程通常涉及以下几个步骤:

解析设备(devices):识别音频设备类型,如扬声器(speaker)、耳机(headphone)和听筒(earpiece)。
解析使用场景(use-cases):确定音频的使用场景,如语音通话(voice_call)、铃声(ringtone)、闹钟(alarm)和媒体播放(media)。
解析路由(routes):根据使用场景将音频路由到相应的输出设备。例如,语音通话使用听筒,铃声和闹钟使用扬声器,媒体播放首选耳机,如果没有耳机则使用扬声器。
解析音量范围(volume-ranges):设置不同类型的音量范围,如媒体音量和铃声音量的最大值和最小值。
其他配置:文件可能还包含其他配置元素,如音频效果、策略规则等。

        在 Android 系统中,音频服务(AudioService)会读取并解析这个 XML 文件,然后根据配置信息来管理音频路由、音量控制和其他音频策略。 

2、AudioPolicyConfig

        AudioPolicyConfig  是 Android 音频框架中一个关键的类,它封装了音频策略配置的细节。这个类通常包含了一系列的属性和方法,用于描述音频服务如何处理音频流的路由、音量控制、混音策略等。

源码位置:/frameworks/base/media/java/android/media/audiopolicy/AudioPolicyConfig.java

/*** @hide* AudioPolicy配置的内部存储类*/
public class AudioPolicyConfig implements Parcelable {// AudioMix定义了一组音频流的混合规则,包括路由、格式、匹配条件等。protected final ArrayList<AudioMix> mMixes;// 降音策略,用于控制当有更高优先级的音频流开始播放时,当前音频流降音。protected int mDuckingPolicy = AudioPolicy.FOCUS_POLICY_DUCKING_IN_APP;// 注册ID,用于标识音频策略配置的唯一性。private String mRegistrationId = null;// 记录添加到配置中的音频混合策略的数量。private int mMixCounter = 0;protected AudioPolicyConfig(AudioPolicyConfig conf) {mMixes = conf.mMixes;}AudioPolicyConfig(ArrayList<AudioMix> mixes) {mMixes = mixes;}/*** 添加一个AudioMix到配置中*/public void addMix(AudioMix mix) throws IllegalArgumentException {if (mix == null) {throw new IllegalArgumentException("Illegal null AudioMix argument");}mMixes.add(mix);}public ArrayList<AudioMix> getMixes() {return mMixes;}……@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeInt(mMixes.size());for (AudioMix mix : mMixes) {// 写入混合路由标志,这些标志指示如何处理音频流的路由。dest.writeInt(mix.getRouteFlags());// 写入回调标志,可能用于指定何时触发回调事件。dest.writeInt(mix.mCallbackFlags);// 写入设备的系统类型,如蓝牙、USB或其他设备类型。dest.writeInt(mix.mDeviceSystemType);// 写入设备的地址,如蓝牙设备的MAC地址或USB设备的唯一标识。dest.writeString(mix.mDeviceAddress);// 写入音频格式的采样率,如44100 Hz。dest.writeInt(mix.getFormat().getSampleRate());// 写入音频编码,如PCM、AAC等dest.writeInt(mix.getFormat().getEncoding());// 写入音频通道掩码,表示音频流的声道布局,如立体声或环绕声。dest.writeInt(mix.getFormat().getChannelMask());// 写入是否允许特权播放捕获,这涉及到音频流的权限控制。dest.writeBoolean(mix.getRule().allowPrivilegedPlaybackCapture());// 写入是否允许语音通信捕获,可能与电话或VoIP应用相关dest.writeBoolean(mix.getRule().voiceCommunicationCaptureAllowed());// 写入混合规则列表的大小,即AudioMixMatchCriterion的数量。final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria();dest.writeInt(criteria.size());for (AudioMixMatchCriterion criterion : criteria) {criterion.writeToParcel(dest);}}}……
}

        综上所述,AudioPolicyConfig 类是 Android 音频策略的核心组件之一,用于定义复杂的音频混合逻辑和路由策略,支持跨进程传输,对于构建灵活、可扩展的音频管理系统至关重要。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/21441.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

将下拉弹层渲染节点固定在触发器的父元素中

将下拉弹层渲染节点固定在触发器的父元素中 注意: 如果发现下拉菜单跟随页面滚动&#xff0c;或者需要在其他弹层中触发 Select&#xff0c; 请尝试使用 getPopupContainer{triggerNode > triggerNode.parentElement} 将下拉弹层渲染节点固定在触发器的父元素中。

【MySQL】探索 MySQL 的 GROUP_CONCAT 函数

缘分让我们相遇乱世以外 命运却要我们危难中相爱 也许未来遥远在光年之外 我愿守候未知里为你等待 我没想到为了你我能疯狂到 山崩海啸没有你根本不想逃 我的大脑为了你已经疯狂到 脉搏心跳没有你根本不重要 &#x1f3b5; 邓紫棋《光年之外》 什么是 GRO…

遗传算法与应用分析

遗传算法的概念 简单来说&#xff0c;遗传算法&#xff08;Genetic Algorithm&#xff0c;GA&#xff09;是一种模拟自然进化过程的优化算法。它通过模拟生物进化的遗传机制&#xff0c;通过选择、交叉和变异等操作&#xff0c;逐代优化搜索空间中的解。遗传算法最初由约翰霍兰…

【面试题-001】什么是面向对象?

文章目录 什么是面向对象&#xff1f;与面向过程的区别&#xff1f;哪些语言是面向对象 哪些是面向过程&#xff1f; 什么是面向对象&#xff1f; 面向对象&#xff08;Object-oriented&#xff09;是一种程序设计范例&#xff0c;它通过将数据与对数据操作的函数&#xff08;…

V90 PN伺服驱动器附加报文750详细使用介绍(算法分析)

1、V90PN伺服驱动器转矩控制(750报文) V90 PN伺服驱动器转矩控制(750报文)_v90pn转矩控制-CSDN博客文章浏览阅读3.4k次,点赞2次,收藏3次。主要介绍通过标准报文加附加报文 750 实现发送驱动报文的控制字、速度给定、转矩限幅及附加转矩给定的功能,首先就是V90在博途环境下…

算法学习笔记——对数器

对数器 对数器的实现&#xff1a; 你想要测的方法a&#xff08;最优解&#xff09;实现复杂度不好但是容易实现的方法b&#xff08;暴力解&#xff09;实现一个随机样本产生器&#xff08;长度也随机、值也随机&#xff09;把方法a和方法b跑相同的输入样本&#xff0c;看看得…

分享5款.NET开源免费的Redis客户端组件库

前言 今天大姚给大家分享5款.NET开源、免费的Redis客户端组件库&#xff0c;希望可以帮助到有需要的同学。 StackExchange.Redis StackExchange.Redis是一个基于.NET的高性能Redis客户端&#xff0c;提供了完整的Redis数据库功能支持&#xff0c;并且具有多节点支持、异步编…

总结2024/6/3

省流&#xff0c;蓝桥杯国优&#xff0c;还是太菜了&#xff0c;听说都是板子题但是还是写不出来&#xff0c;靠暴力好歹没有爆0&#xff0c;还是得多练&#xff0c;明年加油了

JWT 签名用对称加密还是非对称加密?

一 概念梳理 对称加密和非对称加密是两种基本的加密方法&#xff0c;它们在现代密码学中扮演着核心角色&#xff0c;用于保护数据的安全和隐私。 1.1 对称加密&#xff08;Symmetric Encryption&#xff09; 对称加密是指加密和解密使用同一个密钥的过程。这意味着发送方和接…

!力扣 108. 将有序数组转换为二叉搜索树

给你一个整数数组 nums &#xff0c;其中元素已经按升序排列&#xff0c;请你将其转换为一棵 平衡二叉搜索树。 示例 1&#xff1a; 输入&#xff1a;nums [-10,-3,0,5,9] 输出&#xff1a;[0,-3,9,-10,null,5] 解释&#xff1a;[0,-10,5,null,-3,null,9] 也将被视为正确答案…

封装了一个使用UICollectionViewLayout 实现的吸附居左banner图

首先查看效果图 实现的原理就是通过自定义UICollectionView layout&#xff0c;然后 设置减速速率是快速就可以达到吸附的效果 _collectionView.decelerationRate UIScrollViewDecelerationRateFast; 下面贴出所有代码 这里是.h // // LBMiddleExpandLayout.h // Liubo…

文章解读与仿真程序复现思路——电力系统自动化EI\CSCD\北大核心《具有源荷不平衡特性的配电网智能软开关和储能联合规划》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

CTF_RE学习

学了一个 map&#xff08;&#xff09;函数的使用 import base64rawData "e3nifIH9b_CndH" target list(map(ord, rawData)) # map 函数将 rawData 中的每个字符传递给 ord 函数。ord 函数返回给定字符的 Unicode 码点 print(target) # 打印 map 对象的内存地址&…

汽车线束搭铁与接地

一、搭铁与接地的概念 首先在这里解释一下“搭铁”与“接地”的概念&#xff0c;不要混为一团&#xff01; 先说接地&#xff0c;大地是可导电的&#xff0c;其电位通常取为零。电力系统和电气装置的中性点、电气设备的外露导电部分及装置外导电部分通过导体与大地相连&#xf…

MySQL数据库的约束

MySQL对于数据库存储的数据, 做出一些限制性要求, 就叫做数据库的"约束". 在每一列的 列名, 类型 后面加上"约束". 一. not null (非空) 指定某列不能存储null值. 二. unique (唯一) 保证这一列的每行必须有唯一值. 我们可以看到, 给 table 的 sn 列插…

【微服务】docker部署redis,一主二从三哨兵,读写分离

配置redis读写分离 3台虚拟机 创建目录用于挂载 mkdir -p /root/redis/{conf,data,logs} #master配置文件 bind 0.0.0.0 //任何ip都能访问 port 6379 //redis端口号 logfile "/data/redis.log" //日志文件存放位置&#xff0c;启动redis之前设置为空&#xff…

prometheus docker部署

1.安装Docker sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-EOF {"registry-mirrors":["https://hub-mirror.c.163.com"] } EOF export DOWNLOAD_URL"https://hub-mirror.163.com/docker-ce" curl -fsSL https://ge…

TypeScript 中的声明合并

1. 声明合并的概念 声明合并是指当 TypeScript 遇到多个同名的声明时&#xff0c;会将它们合并为一个单一的声明。这使得开发者可以分散地定义同一个实体的不同部分&#xff0c;最终将它们合并为一个整体。在进行声明合并时&#xff0c;TypeScript 会根据不同类型的声明进行不…

【LIN】STM32新能源汽车LIN通信实现过程

【LIN】STM32新能源汽车LIN通信实现过程 文章目录 前言一、软件二、接线图三、硬件原理图四、上位机五、PICO示波器串行解码1.软件中的LIN波特率设置-192002.PIC设置3.PIC串行解码 六.引用总结 前言 【电机控制】直流有刷电机、无刷电机汇总——持续更新 使用工具&#xff1a;…

godot.bk

1.搜索godot国内镜像&#xff0c;直接安装&#xff0c;mono是csharp版本 2.直接解压&#xff0c;50m&#xff0c;无需安装&#xff0c;直接运行 3.godot里分为场景&#xff0c;节点 主场景用control场景&#xff0c;下面挂textureact放背景图片&#xff0c;右键实例化子场景把…