通过前面的文章,我们知道在 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 音频策略的核心组件之一,用于定义复杂的音频混合逻辑和路由策略,支持跨进程传输,对于构建灵活、可扩展的音频管理系统至关重要。