Effects factory: Implement xml config loading
For treble, files shared between vendor and system must have a schema.
As .conf did not support such feature, transition to an xml based
configuration file.
This new format has several changes:
- library path are now relative to the standard effect directories
- effect parameters are no longer supported (nobody used them)
Test: Check that the dumpsys is similar than with the legacy formal
Bug: 37492580
Change-Id: I01d79d016ec0bf52fbaf073f4452862214ab9344
Signed-off-by: Kevin Rocard <krocard@google.com>
diff --git a/media/libeffects/factory/Android.bp b/media/libeffects/factory/Android.bp
index 5ef875c..9c6b5b6 100644
--- a/media/libeffects/factory/Android.bp
+++ b/media/libeffects/factory/Android.bp
@@ -12,12 +12,15 @@
vendor: true,
srcs: ["EffectsFactory.c",
"EffectsConfigLoader.c",
- "EffectsFactoryState.c"],
+ "EffectsFactoryState.c",
+ "EffectsXmlConfigLoader.cpp",
+ ],
shared_libs: [
"libcutils",
"liblog",
"libdl",
+ "libeffectsconfig",
],
cflags: ["-fvisibility=hidden"],
diff --git a/media/libeffects/factory/EffectsConfigLoader.h b/media/libeffects/factory/EffectsConfigLoader.h
index ac0d7ab..1dbf638 100644
--- a/media/libeffects/factory/EffectsConfigLoader.h
+++ b/media/libeffects/factory/EffectsConfigLoader.h
@@ -17,8 +17,20 @@
#ifndef ANDROID_EFFECTSCONFIGLOADER_H
#define ANDROID_EFFECTSCONFIGLOADER_H
+#include "EffectsFactoryState.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
/** Parses the platform effect configuration
* and stores its content in the global EffectFactoryState. */
int loadEffectConfig();
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
#endif // ANDROID_EFFECTSCONFIGLOADER_H
diff --git a/media/libeffects/factory/EffectsFactory.c b/media/libeffects/factory/EffectsFactory.c
index a8a0fe6..a2fdcc5 100644
--- a/media/libeffects/factory/EffectsFactory.c
+++ b/media/libeffects/factory/EffectsFactory.c
@@ -27,6 +27,7 @@
#include <media/EffectsFactoryApi.h>
#include "EffectsConfigLoader.h"
+#include "EffectsXmlConfigLoader.h"
#include "EffectsFactoryState.h"
#include "EffectsFactory.h"
@@ -436,7 +437,13 @@
if (ignoreFxConfFiles) {
ALOGI("Audio effects in configuration files will be ignored");
} else {
- loadEffectConfig();
+ ssize_t loadResult = loadXmlEffectConfig(NULL);
+ if (loadResult < 0) {
+ ALOGW("Failed to load XML effect configuration, fallback to .conf");
+ loadEffectConfig();
+ } else if (loadResult > 0) {
+ ALOGE("Effect config is partially invalid, skipped %zd elements", loadResult);
+ }
}
updateNumEffects();
diff --git a/media/libeffects/factory/EffectsFactoryState.h b/media/libeffects/factory/EffectsFactoryState.h
index 697cf76..aef945e 100644
--- a/media/libeffects/factory/EffectsFactoryState.h
+++ b/media/libeffects/factory/EffectsFactoryState.h
@@ -19,6 +19,10 @@
#include "EffectsFactory.h"
+#if __cplusplus
+extern "C" {
+#endif
+
/** @file Contains the state shared with configuration loader of the Effect factory.
* This global state should probably be refactor in a structure
* provided by the config loader on EffectsFactory init.
@@ -26,7 +30,8 @@
*/
extern list_elem_t *gLibraryList; // list of lib_entry_t: all currently loaded libraries
-extern list_elem_t *gSkippedEffects; // list of effects skipped because of duplicate uuid
+// list of effects skipped because of duplicate uuid or invalid version
+extern list_elem_t *gSkippedEffects;
// list of effect_descriptor and list of sub effects : all currently loaded
// It does not contain effects without sub effects.
extern list_sub_elem_t *gSubEffectList;
@@ -48,4 +53,8 @@
/** Used for debuging. */
void dumpEffectDescriptor(effect_descriptor_t *desc, char *str, size_t len, int indent);
+#if __cplusplus
+} // extern "C"
+#endif
+
#endif // ANDROID_EFFECTSFACTORYSTATE_H_
diff --git a/media/libeffects/factory/EffectsXmlConfigLoader.cpp b/media/libeffects/factory/EffectsXmlConfigLoader.cpp
new file mode 100644
index 0000000..6b12571
--- /dev/null
+++ b/media/libeffects/factory/EffectsXmlConfigLoader.cpp
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "EffectsFactoryConfigLoader"
+//#define LOG_NDEBUG 0
+
+#include <dlfcn.h>
+#include <set>
+#include <stdlib.h>
+#include <string>
+
+#include <log/log.h>
+
+#include <media/EffectsConfig.h>
+
+#include "EffectsConfigLoader.h"
+#include "EffectsFactoryState.h"
+#include "EffectsXmlConfigLoader.h"
+
+namespace android {
+
+using namespace effectsConfig;
+
+/////////////////////////////////////////////////
+// Local functions
+/////////////////////////////////////////////////
+
+namespace {
+
+/** Similarly to dlopen, looks for the provided path in LD_EFFECT_LIBRARY_PATH.
+ * @return true if the library is found and set resolvedPath to its absolute path.
+ * false if not found
+ */
+bool resolveLibrary(const std::string& path, std::string* resolvedPath) {
+ for (auto* libraryDirectory : LD_EFFECT_LIBRARY_PATH) {
+ std::string candidatePath = std::string(libraryDirectory) + '/' + path;
+ if (access(candidatePath.c_str(), R_OK) == 0) {
+ *resolvedPath = std::move(candidatePath);
+ return true;
+ }
+ }
+ return false;
+}
+
+/** Loads a library given its relative path and stores the result in libEntry.
+ * @return true on success with libEntry's path, handle and desc filled
+ * false on success with libEntry's path filled with the path of the failed lib
+ * The caller MUST free the resources path (free) and handle (dlclose) if filled.
+ */
+bool loadLibrary(const char* relativePath, lib_entry_t* libEntry) noexcept {
+
+ std::string absolutePath;
+ if (!resolveLibrary(relativePath, &absolutePath)) {
+ ALOGE("Could not find library in effect directories: %s", relativePath);
+ libEntry->path = strdup(relativePath);
+ return false;
+ }
+ const char* path = absolutePath.c_str();
+ libEntry->path = strdup(path);
+
+ // Make sure the lib is closed on early return
+ std::unique_ptr<void, decltype(dlclose)*> libHandle(dlopen(path, RTLD_NOW),
+ dlclose);
+ if (libHandle == nullptr) {
+ ALOGE("Could not dlopen library %s: %s", path, dlerror());
+ return false;
+ }
+
+ auto* description = static_cast<audio_effect_library_t*>(
+ dlsym(libHandle.get(), AUDIO_EFFECT_LIBRARY_INFO_SYM_AS_STR));
+ if (description == nullptr) {
+ ALOGE("Invalid effect library, failed not find symbol '%s' in %s: %s",
+ AUDIO_EFFECT_LIBRARY_INFO_SYM_AS_STR, path, dlerror());
+ return false;
+ }
+
+ if (description->tag != AUDIO_EFFECT_LIBRARY_TAG) {
+ ALOGE("Bad tag %#08x in description structure, expected %#08x for library %s",
+ description->tag, AUDIO_EFFECT_LIBRARY_TAG, path);
+ return false;
+ }
+
+ uint32_t majorVersion = EFFECT_API_VERSION_MAJOR(description->version);
+ uint32_t expectedMajorVersion = EFFECT_API_VERSION_MAJOR(EFFECT_LIBRARY_API_VERSION);
+ if (majorVersion != expectedMajorVersion) {
+ ALOGE("Unsupported major version %#08x, expected %#08x for library %s",
+ majorVersion, expectedMajorVersion, path);
+ return false;
+ }
+
+ libEntry->handle = libHandle.release();
+ libEntry->desc = description;
+ return true;
+}
+
+/** Because the structures will be destroyed by c code, using new to allocate shared structure
+ * is not possible. Provide a equivalent of unique_ptr for malloc/freed structure to make sure
+ * they are not leaked in the c++ code.
+ @{ */
+struct FreeDeleter {
+ void operator()(void* p) {
+ free(p);
+ }
+};
+/** unique_ptr for object created with malloc. */
+template <class T>
+using UniqueCPtr = std::unique_ptr<T, FreeDeleter>;
+
+/** c version of std::make_unique. Uses malloc and free. */
+template <class T>
+UniqueCPtr<T> makeUniqueC() {
+ T* ptr = new (malloc(sizeof(T))) T{}; // Use placement new to initialize the structure
+ return UniqueCPtr<T>{ptr};
+}
+
+/** @} */
+
+/** Push an not owned element in a list_elem link list with an optional lock. */
+template <class T, class ListElem>
+void listPush(T* object, ListElem** list, pthread_mutex_t* mutex = nullptr) noexcept {
+ auto listElem = makeUniqueC<ListElem>();
+ listElem->object = object;
+ if (mutex != nullptr) {
+ pthread_mutex_lock(mutex);
+ }
+ listElem->next = *list;
+ *list = listElem.release();
+ if (mutex != nullptr) {
+ pthread_mutex_unlock(mutex);
+ }
+}
+
+/** Push an owned element in a list_elem link list with an optional lock. */
+template <class T, class ListElem>
+void listPush(UniqueCPtr<T>&& object, ListElem** list, pthread_mutex_t* mutex = nullptr) noexcept {
+ listPush(object.release(), list, mutex);
+}
+
+size_t loadLibraries(const effectsConfig::Libraries& libs,
+ list_elem_t** libList, pthread_mutex_t* libListLock,
+ list_elem_t** libFailedList)
+{
+ size_t nbSkippedElement = 0;
+ for (auto& library : libs) {
+
+ // Construct a lib entry
+ auto libEntry = makeUniqueC<lib_entry_t>();
+ libEntry->name = strdup(library.name.c_str());
+ libEntry->effects = nullptr;
+ pthread_mutex_init(&libEntry->lock, nullptr);
+
+ if (!loadLibrary(library.path.c_str(), libEntry.get())) {
+ // Register library load failure
+ listPush(std::move(libEntry), libFailedList);
+ ++nbSkippedElement;
+ continue;
+ }
+ listPush(std::move(libEntry), libList, libListLock);
+ }
+ return nbSkippedElement;
+}
+
+/** Find a library with the given name in the given list. */
+lib_entry_t* findLibrary(const char* name, list_elem_t* list) {
+
+ while (list != nullptr) {
+ auto* object = static_cast<lib_entry_t*>(list->object);
+ if (strcmp(object->name, name) == 0) {
+ return object;
+ }
+ list = list->next;
+ }
+ return nullptr;
+}
+
+struct UuidStr {
+ /** Length of an uuid represented as string. @TODO: use a constant instead of 40. */
+ char buff[40];
+};
+
+/** @return a string representing the provided uuid.
+ * By not providing an output buffer, it is implicitly created in the caller context.
+ * In such case the return pointer has the same lifetime as the expression containing uuidToString()
+ */
+char* uuidToString(const effect_uuid_t& uuid, UuidStr&& str = {}) {
+ uuidToString(&uuid, str.buff, sizeof(str.buff));
+ return str.buff;
+}
+
+struct LoadEffectResult {
+ /** true if the effect is usable (aka, existing lib, desc, right version, unique uuid) */
+ bool success = false;
+ /** Set if the effect lib was found*/
+ lib_entry_t* lib = nullptr;
+ //* Set if the description was successfuly retrieved from the lib */
+ UniqueCPtr<effect_descriptor_t> effectDesc;
+};
+
+LoadEffectResult loadEffect(const EffectImpl& effect, const std::string& name,
+ list_elem_t* libList) {
+ LoadEffectResult result;
+
+ // Find the effect library
+ result.lib = findLibrary(effect.library->name.c_str(), libList);
+ if (result.lib == nullptr) {
+ ALOGE("Could not find library %s to load effect %s",
+ effect.library->name.c_str(), name.c_str());
+ return result;
+ }
+
+ result.effectDesc = makeUniqueC<effect_descriptor_t>();
+
+ // Get the effect descriptor
+ if (result.lib->desc->get_descriptor(&effect.uuid, result.effectDesc.get()) != 0) {
+ ALOGE("Error querying effect %s on lib %s",
+ uuidToString(effect.uuid), result.lib->name);
+ result.effectDesc.reset();
+ return result;
+ }
+
+ // Dump effect for debug
+#if (LOG_NDEBUG==0)
+ char s[512];
+ dumpEffectDescriptor(result.effectDesc.get(), s, sizeof(s), 0 /* indent */);
+ ALOGV("loadEffect() read descriptor %p:%s", result.effectDesc.get(), s);
+#endif
+
+ // Check effect is supported
+ uint32_t expectedMajorVersion = EFFECT_API_VERSION_MAJOR(EFFECT_CONTROL_API_VERSION);
+ if (EFFECT_API_VERSION_MAJOR(result.effectDesc->apiVersion) != expectedMajorVersion) {
+ ALOGE("Bad API version %#08x for effect %s in lib %s, expected major %#08x",
+ result.effectDesc->apiVersion, name.c_str(), result.lib->name, expectedMajorVersion);
+ return result;
+ }
+
+ lib_entry_t *_;
+ if (findEffect(nullptr, &effect.uuid, &_, nullptr) == 0) {
+ ALOGE("Effect %s uuid %s already exist", uuidToString(effect.uuid), name.c_str());
+ return result;
+ }
+
+ result.success = true;
+ return result;
+}
+
+size_t loadEffects(const Effects& effects, list_elem_t* libList, list_elem_t** skippedEffects,
+ list_sub_elem_t** subEffectList) {
+ size_t nbSkippedElement = 0;
+
+ for (auto& effect : effects) {
+
+ auto effectLoadResult = loadEffect(effect, effect.name, libList);
+ if (!effectLoadResult.success) {
+ if (effectLoadResult.effectDesc != nullptr) {
+ listPush(std::move(effectLoadResult.effectDesc), skippedEffects);
+ }
+ ++nbSkippedElement;
+ continue;
+ }
+
+ if (effect.isProxy) {
+ auto swEffectLoadResult = loadEffect(effect.libSw, effect.name + " libsw", libList);
+ auto hwEffectLoadResult = loadEffect(effect.libHw, effect.name + " libhw", libList);
+ if (!swEffectLoadResult.success || !hwEffectLoadResult.success) {
+ // Push the main effect in the skipped list even if only a subeffect is invalid
+ // as the main effect is not usable without its subeffects.
+ listPush(std::move(effectLoadResult.effectDesc), skippedEffects);
+ ++nbSkippedElement;
+ continue;
+ }
+ listPush(&effectLoadResult.effectDesc, subEffectList);
+
+ // Since we return a dummy descriptor for the proxy during
+ // get_descriptor call, we replace it with the corresponding
+ // sw effect descriptor, but keep the Proxy UUID
+ *effectLoadResult.effectDesc = *swEffectLoadResult.effectDesc;
+ effectLoadResult.effectDesc->uuid = effect.uuid;
+
+ effectLoadResult.effectDesc->flags |= EFFECT_FLAG_OFFLOAD_SUPPORTED;
+
+ auto registerSubEffect = [subEffectList](auto&& result) {
+ auto entry = makeUniqueC<sub_effect_entry_t>();
+ entry->object = result.effectDesc.release();
+ // lib_entry_t is stored since the sub effects are not linked to the library
+ entry->lib = result.lib;
+ listPush(std::move(entry), &(*subEffectList)->sub_elem);
+ };
+ registerSubEffect(std::move(swEffectLoadResult));
+ registerSubEffect(std::move(hwEffectLoadResult));
+ }
+
+ listPush(std::move(effectLoadResult.effectDesc), &effectLoadResult.lib->effects);
+ }
+ return nbSkippedElement;
+}
+
+} // namespace
+
+/////////////////////////////////////////////////
+// Interface function
+/////////////////////////////////////////////////
+
+extern "C" ssize_t loadXmlEffectConfig(const char* path)
+{
+ using effectsConfig::parse;
+ auto result = path ? parse(path) : parse();
+ if (result.parsedConfig == nullptr) {
+ ALOGE("Failed to parse XML configuration file");
+ return -1;
+ }
+ result.nbSkippedElement += loadLibraries(result.parsedConfig->libraries,
+ &gLibraryList, &gLibLock, &gLibraryFailedList) +
+ loadEffects(result.parsedConfig->effects, gLibraryList,
+ &gSkippedEffects, &gSubEffectList);
+
+ ALOGE_IF(result.nbSkippedElement != 0, "%zu errors during loading of configuration: %s",
+ result.nbSkippedElement, path ?: effectsConfig::DEFAULT_PATH);
+
+ return result.nbSkippedElement;
+}
+
+} // namespace android
diff --git a/media/libeffects/factory/EffectsXmlConfigLoader.h b/media/libeffects/factory/EffectsXmlConfigLoader.h
new file mode 100644
index 0000000..9bf4d18
--- /dev/null
+++ b/media/libeffects/factory/EffectsXmlConfigLoader.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_EFFECTSXMLCONFIGLOADER_H
+#define ANDROID_EFFECTSXMLCONFIGLOADER_H
+
+#include <unistd.h>
+
+#if __cplusplus
+extern "C" {
+#endif
+
+/** Parses the platform effect xml configuration and stores its content in EffectFactoryState.
+ * @param[in] path of the configuration file or NULL to load the default one
+ * @return -1 on unrecoverable error (eg: no configuration file)
+ * 0 on success
+ * the number of invalid elements (lib & effect) skipped if the config is partially invalid
+ */
+ssize_t loadXmlEffectConfig(const char* path);
+
+#if __cplusplus
+} // extern "C"
+#endif
+
+#endif // ANDROID_EFFECTSXMLCONFIGLOADER_H