??????? 通過前面的文章,我們知道在 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 音頻策略的核心組件之一,用于定義復雜的音頻混合邏輯和路由策略,支持跨進程傳輸,對于構建靈活、可擴展的音頻管理系統至關重要。