cannam@56: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ cannam@56: cannam@56: /* cannam@56: Vamp cannam@56: cannam@56: An API for audio analysis and feature extraction plugins. cannam@56: cannam@56: Centre for Digital Music, Queen Mary, University of London. cannam@56: Copyright 2006 Chris Cannam. cannam@56: cannam@56: Permission is hereby granted, free of charge, to any person cannam@56: obtaining a copy of this software and associated documentation cannam@56: files (the "Software"), to deal in the Software without cannam@56: restriction, including without limitation the rights to use, copy, cannam@56: modify, merge, publish, distribute, sublicense, and/or sell copies cannam@56: of the Software, and to permit persons to whom the Software is cannam@56: furnished to do so, subject to the following conditions: cannam@56: cannam@56: The above copyright notice and this permission notice shall be cannam@56: included in all copies or substantial portions of the Software. cannam@56: cannam@56: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, cannam@56: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF cannam@56: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND cannam@56: NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR cannam@56: ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF cannam@56: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION cannam@56: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cannam@56: cannam@56: Except as contained in this notice, the names of the Centre for cannam@56: Digital Music; Queen Mary, University of London; and Chris Cannam cannam@56: shall not be used in advertising or otherwise to promote the sale, cannam@56: use or other dealings in this Software without prior written cannam@56: authorization. cannam@56: */ cannam@56: cannam@59: #include "vamp-sdk/PluginHostAdapter.h" cannam@56: #include "PluginLoader.h" cannam@61: #include "PluginInputDomainAdapter.h" cannam@61: #include "PluginChannelAdapter.h" cannam@56: cannam@57: #include cannam@57: cannam@59: #ifdef _WIN32 cannam@59: cannam@59: #include cannam@59: #include cannam@59: #define PLUGIN_SUFFIX "dll" cannam@59: cannam@59: #else /* ! _WIN32 */ cannam@59: cannam@59: #include cannam@59: #include cannam@59: cannam@59: #ifdef __APPLE__ cannam@59: #define PLUGIN_SUFFIX "dylib" cannam@59: #else /* ! __APPLE__ */ cannam@59: #define PLUGIN_SUFFIX "so" cannam@59: #endif /* ! __APPLE__ */ cannam@59: cannam@59: #endif /* ! _WIN32 */ cannam@56: cannam@57: using namespace std; cannam@57: cannam@56: namespace Vamp { cannam@56: cannam@59: namespace HostExt { cannam@59: cannam@59: PluginLoader * cannam@59: PluginLoader::m_instance = 0; cannam@59: cannam@56: PluginLoader::PluginLoader() cannam@56: { cannam@56: } cannam@56: cannam@56: PluginLoader::~PluginLoader() cannam@56: { cannam@56: } cannam@56: cannam@59: PluginLoader * cannam@59: PluginLoader::getInstance() cannam@59: { cannam@59: if (!m_instance) m_instance = new PluginLoader(); cannam@59: return m_instance; cannam@59: } cannam@59: cannam@57: vector cannam@56: PluginLoader::listPlugins() cannam@56: { cannam@59: if (m_pluginLibraryNameMap.empty()) generateLibraryMap(); cannam@56: cannam@57: vector plugins; cannam@57: for (map::iterator mi = cannam@59: m_pluginLibraryNameMap.begin(); cannam@59: mi != m_pluginLibraryNameMap.end(); ++mi) { cannam@56: plugins.push_back(mi->first); cannam@56: } cannam@56: cannam@56: return plugins; cannam@56: } cannam@56: cannam@59: void cannam@59: PluginLoader::generateLibraryMap() cannam@59: { cannam@59: vector path = PluginHostAdapter::getPluginPath(); cannam@59: cannam@59: for (size_t i = 0; i < path.size(); ++i) { cannam@59: cannam@59: vector files = listFiles(path[i], PLUGIN_SUFFIX); cannam@59: cannam@59: for (vector::iterator fi = files.begin(); cannam@59: fi != files.end(); ++fi) { cannam@59: cannam@59: string fullPath = path[i]; cannam@59: fullPath = splicePath(fullPath, *fi); cannam@59: void *handle = loadLibrary(fullPath); cannam@59: if (!handle) continue; cannam@59: cannam@59: VampGetPluginDescriptorFunction fn = cannam@59: (VampGetPluginDescriptorFunction)lookupInLibrary cannam@59: (handle, "vampGetPluginDescriptor"); cannam@59: cannam@59: if (!fn) { cannam@59: unloadLibrary(handle); cannam@59: continue; cannam@59: } cannam@59: cannam@59: int index = 0; cannam@59: const VampPluginDescriptor *descriptor = 0; cannam@59: cannam@59: while ((descriptor = fn(VAMP_API_VERSION, index))) { cannam@59: PluginKey key = composePluginKey(*fi, descriptor->identifier); cannam@59: if (m_pluginLibraryNameMap.find(key) == cannam@59: m_pluginLibraryNameMap.end()) { cannam@59: m_pluginLibraryNameMap[key] = fullPath; cannam@59: } cannam@59: ++index; cannam@59: } cannam@59: cannam@59: unloadLibrary(handle); cannam@59: } cannam@59: } cannam@59: } cannam@59: cannam@59: PluginLoader::PluginKey cannam@59: PluginLoader::composePluginKey(string libraryName, string identifier) cannam@59: { cannam@59: string basename = libraryName; cannam@59: cannam@59: string::size_type li = basename.rfind('/'); cannam@59: if (li != string::npos) basename = basename.substr(li + 1); cannam@59: cannam@59: li = basename.find('.'); cannam@59: if (li != string::npos) basename = basename.substr(0, li); cannam@59: cannam@59: return basename + ":" + identifier; cannam@59: } cannam@59: cannam@57: PluginLoader::PluginCategoryHierarchy cannam@57: PluginLoader::getPluginCategory(PluginKey plugin) cannam@57: { cannam@57: if (m_taxonomy.empty()) generateTaxonomy(); cannam@57: if (m_taxonomy.find(plugin) == m_taxonomy.end()) return PluginCategoryHierarchy(); cannam@57: return m_taxonomy[plugin]; cannam@57: } cannam@57: cannam@57: string cannam@57: PluginLoader::getLibraryPathForPlugin(PluginKey plugin) cannam@56: { cannam@59: if (m_pluginLibraryNameMap.empty()) generateLibraryMap(); cannam@59: if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) return ""; cannam@59: return m_pluginLibraryNameMap[plugin]; cannam@56: } cannam@56: cannam@56: Plugin * cannam@61: PluginLoader::loadPlugin(PluginKey key, float inputSampleRate, int adapterFlags) cannam@56: { cannam@57: string fullPath = getLibraryPathForPlugin(key); cannam@56: if (fullPath == "") return 0; cannam@56: cannam@57: string::size_type ki = key.find(':'); cannam@57: if (ki == string::npos) { cannam@56: //!!! flag error cannam@56: return 0; cannam@56: } cannam@56: cannam@57: string identifier = key.substr(ki + 1); cannam@56: cannam@59: void *handle = loadLibrary(fullPath); cannam@59: if (!handle) return 0; cannam@56: cannam@56: VampGetPluginDescriptorFunction fn = cannam@59: (VampGetPluginDescriptorFunction)lookupInLibrary cannam@56: (handle, "vampGetPluginDescriptor"); cannam@56: cannam@56: if (!fn) { cannam@59: unloadLibrary(handle); cannam@56: return 0; cannam@56: } cannam@56: cannam@56: int index = 0; cannam@56: const VampPluginDescriptor *descriptor = 0; cannam@56: cannam@56: while ((descriptor = fn(VAMP_API_VERSION, index))) { cannam@59: cannam@57: if (string(descriptor->identifier) == identifier) { cannam@59: cannam@59: Vamp::PluginHostAdapter *plugin = cannam@59: new Vamp::PluginHostAdapter(descriptor, inputSampleRate); cannam@59: cannam@61: Plugin *adapter = new PluginDeletionNotifyAdapter(plugin, this); cannam@59: cannam@59: m_pluginLibraryHandleMap[adapter] = handle; cannam@61: cannam@61: if (adapterFlags & ADAPT_INPUT_DOMAIN) { cannam@61: if (adapter->getInputDomain() == Plugin::FrequencyDomain) { cannam@61: adapter = new PluginInputDomainAdapter(adapter); cannam@61: } cannam@61: } cannam@61: cannam@61: if (adapterFlags & ADAPT_CHANNEL_COUNT) { cannam@61: adapter = new PluginChannelAdapter(adapter); cannam@61: } cannam@61: cannam@59: return adapter; cannam@56: } cannam@59: cannam@56: ++index; cannam@56: } cannam@59: cannam@59: cerr << "Vamp::HostExt::PluginLoader: Plugin \"" cannam@59: << identifier << "\" not found in library \"" cannam@59: << fullPath << "\"" << endl; cannam@59: cannam@56: return 0; cannam@56: } cannam@56: cannam@57: void cannam@57: PluginLoader::generateTaxonomy() cannam@57: { cannam@57: // cerr << "PluginLoader::generateTaxonomy" << endl; cannam@57: cannam@57: vector path = PluginHostAdapter::getPluginPath(); cannam@57: string libfragment = "/lib/"; cannam@57: vector catpath; cannam@57: cannam@57: string suffix = "cat"; cannam@57: cannam@57: for (vector::iterator i = path.begin(); cannam@57: i != path.end(); ++i) { cannam@59: cannam@59: // It doesn't matter that we're using literal forward-slash in cannam@59: // this bit, as it's only relevant if the path contains cannam@59: // "/lib/", which is only meaningful and only plausible on cannam@59: // systems with forward-slash delimiters cannam@57: cannam@57: string dir = *i; cannam@57: string::size_type li = dir.find(libfragment); cannam@57: cannam@57: if (li != string::npos) { cannam@57: catpath.push_back cannam@57: (dir.substr(0, li) cannam@57: + "/share/" cannam@57: + dir.substr(li + libfragment.length())); cannam@57: } cannam@57: cannam@57: catpath.push_back(dir); cannam@57: } cannam@57: cannam@57: char buffer[1024]; cannam@57: cannam@57: for (vector::iterator i = catpath.begin(); cannam@57: i != catpath.end(); ++i) { cannam@57: cannam@59: vector files = listFiles(*i, suffix); cannam@57: cannam@57: for (vector::iterator fi = files.begin(); cannam@57: fi != files.end(); ++fi) { cannam@57: cannam@59: string filepath = splicePath(*i, *fi); cannam@57: ifstream is(filepath.c_str(), ifstream::in | ifstream::binary); cannam@57: cannam@57: if (is.fail()) { cannam@57: // cerr << "failed to open: " << filepath << endl; cannam@57: continue; cannam@57: } cannam@57: cannam@57: // cerr << "opened: " << filepath << endl; cannam@57: cannam@57: while (!!is.getline(buffer, 1024)) { cannam@57: cannam@57: string line(buffer); cannam@57: cannam@57: // cerr << "line = " << line << endl; cannam@57: cannam@57: string::size_type di = line.find("::"); cannam@57: if (di == string::npos) continue; cannam@57: cannam@57: string id = line.substr(0, di); cannam@57: string encodedCat = line.substr(di + 2); cannam@57: cannam@57: if (id.substr(0, 5) != "vamp:") continue; cannam@57: id = id.substr(5); cannam@57: cannam@57: while (encodedCat.length() >= 1 && cannam@57: encodedCat[encodedCat.length()-1] == '\r') { cannam@57: encodedCat = encodedCat.substr(0, encodedCat.length()-1); cannam@57: } cannam@57: cannam@57: // cerr << "id = " << id << ", cat = " << encodedCat << endl; cannam@57: cannam@57: PluginCategoryHierarchy category; cannam@57: string::size_type ai; cannam@57: while ((ai = encodedCat.find(" > ")) != string::npos) { cannam@57: category.push_back(encodedCat.substr(0, ai)); cannam@57: encodedCat = encodedCat.substr(ai + 3); cannam@57: } cannam@57: if (encodedCat != "") category.push_back(encodedCat); cannam@57: cannam@57: m_taxonomy[id] = category; cannam@57: } cannam@57: } cannam@57: } cannam@57: } cannam@57: cannam@59: void * cannam@59: PluginLoader::loadLibrary(string path) cannam@59: { cannam@59: void *handle = 0; cannam@59: #ifdef _WIN32 cannam@59: handle = LoadLibrary(path.c_str()); cannam@59: if (!handle) { cannam@59: cerr << "Vamp::HostExt::PluginLoader: Unable to load library \"" cannam@59: << path << "\"" << endl; cannam@59: } cannam@59: #else cannam@59: handle = dlopen(path.c_str(), RTLD_LAZY); cannam@59: if (!handle) { cannam@59: cerr << "Vamp::HostExt::PluginLoader: Unable to load library \"" cannam@59: << path << "\": " << dlerror() << endl; cannam@59: } cannam@59: #endif cannam@59: return handle; cannam@59: } cannam@59: cannam@59: void cannam@59: PluginLoader::unloadLibrary(void *handle) cannam@59: { cannam@59: #ifdef _WIN32 cannam@59: FreeLibrary((HINSTANCE)handle); cannam@59: #else cannam@59: dlclose(handle); cannam@59: #endif cannam@59: } cannam@59: cannam@59: void * cannam@59: PluginLoader::lookupInLibrary(void *handle, const char *symbol) cannam@59: { cannam@59: #ifdef _WIN32 cannam@59: return (void *)GetProcAddress((HINSTANCE)handle, symbol); cannam@59: #else cannam@59: return (void *)dlsym(handle, symbol); cannam@59: #endif cannam@59: } cannam@59: cannam@59: string cannam@59: PluginLoader::splicePath(string a, string b) cannam@59: { cannam@59: #ifdef _WIN32 cannam@59: return a + "\\" + b; cannam@59: #else cannam@59: return a + "/" + b; cannam@59: #endif cannam@59: } cannam@59: cannam@59: vector cannam@59: PluginLoader::listFiles(string dir, string extension) cannam@59: { cannam@59: vector files; cannam@59: size_t extlen = extension.length(); cannam@59: cannam@59: #ifdef _WIN32 cannam@59: cannam@59: string expression = dir + "\\*." + extension; cannam@59: WIN32_FIND_DATA data; cannam@59: HANDLE fh = FindFirstFile(expression.c_str(), &data); cannam@59: if (fh == INVALID_HANDLE_VALUE) return files; cannam@59: cannam@59: bool ok = true; cannam@59: while (ok) { cannam@59: files.push_back(data.cFileName); cannam@59: ok = FindNextFile(fh, &data); cannam@59: } cannam@59: cannam@59: FindClose(fh); cannam@59: cannam@59: #else cannam@59: DIR *d = opendir(dir.c_str()); cannam@59: if (!d) return files; cannam@59: cannam@59: struct dirent *e = 0; cannam@59: while ((e = readdir(d))) { cannam@59: cannam@59: if (!(e->d_type & DT_REG) || !e->d_name) continue; cannam@59: cannam@59: size_t len = strlen(e->d_name); cannam@59: if (len < extlen + 2 || cannam@59: e->d_name + len - extlen - 1 != "." + extension) { cannam@59: continue; cannam@59: } cannam@59: cannam@59: files.push_back(e->d_name); cannam@59: } cannam@59: cannam@59: closedir(d); cannam@59: #endif cannam@59: cannam@59: return files; cannam@59: } cannam@59: cannam@59: void cannam@59: PluginLoader::pluginDeleted(PluginDeletionNotifyAdapter *adapter) cannam@59: { cannam@59: void *handle = m_pluginLibraryHandleMap[adapter]; cannam@59: if (handle) unloadLibrary(handle); cannam@59: m_pluginLibraryHandleMap.erase(adapter); cannam@59: } cannam@59: cannam@59: PluginLoader::PluginDeletionNotifyAdapter::PluginDeletionNotifyAdapter(Plugin *plugin, cannam@59: PluginLoader *loader) : cannam@59: PluginWrapper(plugin), cannam@59: m_loader(loader) cannam@59: { cannam@59: } cannam@59: cannam@59: PluginLoader::PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter() cannam@59: { cannam@59: // We need to delete the plugin before calling pluginDeleted, as cannam@59: // the delete call may require calling through to the descriptor cannam@59: // (for e.g. cleanup) but pluginDeleted may unload the required cannam@59: // library for the call. To prevent a double deletion when our cannam@59: // parent's destructor runs (after this one), be sure to set cannam@59: // m_plugin to 0 after deletion. cannam@59: delete m_plugin; cannam@59: m_plugin = 0; cannam@59: cannam@59: if (m_loader) m_loader->pluginDeleted(this); cannam@59: } cannam@57: cannam@57: } cannam@59: cannam@59: }