cannam@233: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ cannam@233: cannam@233: /* cannam@233: Vamp cannam@233: cannam@233: An API for audio analysis and feature extraction plugins. cannam@233: cannam@233: Centre for Digital Music, Queen Mary, University of London. Chris@423: Copyright 2006-2016 Chris Cannam and QMUL. cannam@233: cannam@233: Permission is hereby granted, free of charge, to any person cannam@233: obtaining a copy of this software and associated documentation cannam@233: files (the "Software"), to deal in the Software without cannam@233: restriction, including without limitation the rights to use, copy, cannam@233: modify, merge, publish, distribute, sublicense, and/or sell copies cannam@233: of the Software, and to permit persons to whom the Software is cannam@233: furnished to do so, subject to the following conditions: cannam@233: cannam@233: The above copyright notice and this permission notice shall be cannam@233: included in all copies or substantial portions of the Software. cannam@233: cannam@233: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, cannam@233: EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF cannam@233: MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND cannam@233: NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR cannam@233: ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF cannam@233: CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION cannam@233: WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cannam@233: cannam@233: Except as contained in this notice, the names of the Centre for cannam@233: Digital Music; Queen Mary, University of London; and Chris Cannam cannam@233: shall not be used in advertising or otherwise to promote the sale, cannam@233: use or other dealings in this Software without prior written cannam@233: authorization. cannam@233: */ cannam@233: cannam@233: #include cannam@233: #include cannam@233: #include cannam@233: #include Chris@390: #include Chris@390: Chris@390: #include Chris@390: Chris@390: #include "Files.h" cannam@233: cannam@233: #include cannam@233: cannam@233: using namespace std; cannam@233: cannam@263: _VAMP_SDK_HOSTSPACE_BEGIN(PluginLoader.cpp) cannam@263: cannam@233: namespace Vamp { cannam@233: cannam@233: namespace HostExt { cannam@233: cannam@233: class PluginLoader::Impl cannam@233: { cannam@233: public: cannam@233: Impl(); cannam@233: virtual ~Impl(); cannam@233: cannam@233: PluginKeyList listPlugins(); Chris@473: PluginKeyList listPluginsIn(vector); Chris@473: PluginKeyList listPluginsNotIn(vector); Chris@456: cannam@233: Plugin *loadPlugin(PluginKey key, cannam@233: float inputSampleRate, cannam@233: int adapterFlags); Chris@423: cannam@233: PluginKey composePluginKey(string libraryName, string identifier); cannam@233: cannam@233: PluginCategoryHierarchy getPluginCategory(PluginKey key); cannam@233: cannam@233: string getLibraryPathForPlugin(PluginKey key); cannam@233: cannam@233: static void setInstanceToClean(PluginLoader *instance); cannam@233: cannam@233: protected: cannam@233: class PluginDeletionNotifyAdapter : public PluginWrapper { cannam@233: public: cannam@233: PluginDeletionNotifyAdapter(Plugin *plugin, Impl *loader); cannam@233: virtual ~PluginDeletionNotifyAdapter(); cannam@233: protected: cannam@233: Impl *m_loader; cannam@233: }; cannam@233: cannam@233: class InstanceCleaner { cannam@233: public: cannam@233: InstanceCleaner() : m_instance(0) { } cannam@233: ~InstanceCleaner() { delete m_instance; } cannam@233: void setInstance(PluginLoader *instance) { m_instance = instance; } cannam@233: protected: cannam@233: PluginLoader *m_instance; cannam@233: }; cannam@233: cannam@233: virtual void pluginDeleted(PluginDeletionNotifyAdapter *adapter); cannam@233: cannam@233: map m_pluginLibraryNameMap; cannam@233: bool m_allPluginsEnumerated; Chris@473: Chris@473: struct Enumeration { Chris@473: enum { All, SinglePlugin, InLibraries, NotInLibraries } type; Chris@473: PluginKey key; Chris@473: vector libraryNames; Chris@473: Enumeration() : type(All) { } Chris@473: }; Chris@473: vector listLibraryFilesFor(Enumeration); Chris@473: Chris@473: /// Populate m_pluginLibraryNameMap and return a list of the keys Chris@473: /// that were added to it Chris@473: vector enumeratePlugins(Enumeration); cannam@233: cannam@233: map m_taxonomy; cannam@233: void generateTaxonomy(); cannam@233: cannam@233: map m_pluginLibraryHandleMap; cannam@233: cannam@233: bool decomposePluginKey(PluginKey key, cannam@233: string &libraryName, string &identifier); cannam@233: cannam@233: static InstanceCleaner m_cleaner; cannam@233: }; cannam@233: cannam@233: PluginLoader * cannam@233: PluginLoader::m_instance = 0; cannam@233: cannam@233: PluginLoader::Impl::InstanceCleaner cannam@233: PluginLoader::Impl::m_cleaner; cannam@233: cannam@233: PluginLoader::PluginLoader() cannam@233: { cannam@233: m_impl = new Impl(); cannam@233: } cannam@233: cannam@233: PluginLoader::~PluginLoader() cannam@233: { cannam@233: delete m_impl; cannam@233: } cannam@233: cannam@233: PluginLoader * cannam@233: PluginLoader::getInstance() cannam@233: { cannam@233: if (!m_instance) { cannam@233: // The cleaner doesn't own the instance, because we leave the cannam@233: // instance pointer in the base class for binary backwards cannam@233: // compatibility reasons and to avoid waste cannam@233: m_instance = new PluginLoader(); cannam@233: Impl::setInstanceToClean(m_instance); cannam@233: } cannam@233: return m_instance; cannam@233: } cannam@233: Chris@426: PluginLoader::PluginKeyList cannam@233: PluginLoader::listPlugins() cannam@233: { cannam@233: return m_impl->listPlugins(); cannam@233: } cannam@233: Chris@473: PluginLoader::PluginKeyList Chris@473: PluginLoader::listPluginsIn(vector libs) Chris@473: { Chris@473: return m_impl->listPluginsIn(libs); Chris@473: } Chris@473: Chris@473: PluginLoader::PluginKeyList Chris@473: PluginLoader::listPluginsNotIn(vector libs) Chris@473: { Chris@473: return m_impl->listPluginsNotIn(libs); Chris@473: } Chris@473: cannam@233: Plugin * cannam@233: PluginLoader::loadPlugin(PluginKey key, cannam@233: float inputSampleRate, cannam@233: int adapterFlags) cannam@233: { cannam@233: return m_impl->loadPlugin(key, inputSampleRate, adapterFlags); cannam@233: } cannam@233: cannam@233: PluginLoader::PluginKey cannam@233: PluginLoader::composePluginKey(string libraryName, string identifier) cannam@233: { cannam@233: return m_impl->composePluginKey(libraryName, identifier); cannam@233: } cannam@233: cannam@233: PluginLoader::PluginCategoryHierarchy cannam@233: PluginLoader::getPluginCategory(PluginKey key) cannam@233: { cannam@233: return m_impl->getPluginCategory(key); cannam@233: } cannam@233: cannam@233: string cannam@233: PluginLoader::getLibraryPathForPlugin(PluginKey key) cannam@233: { cannam@233: return m_impl->getLibraryPathForPlugin(key); cannam@233: } cannam@233: cannam@233: PluginLoader::Impl::Impl() : cannam@233: m_allPluginsEnumerated(false) cannam@233: { cannam@233: } cannam@233: cannam@233: PluginLoader::Impl::~Impl() cannam@233: { cannam@233: } cannam@233: cannam@233: void cannam@233: PluginLoader::Impl::setInstanceToClean(PluginLoader *instance) cannam@233: { cannam@233: m_cleaner.setInstance(instance); cannam@233: } cannam@233: Chris@426: PluginLoader::PluginKeyList cannam@233: PluginLoader::Impl::listPlugins() cannam@233: { Chris@477: if (!m_allPluginsEnumerated) enumeratePlugins(Enumeration()); cannam@233: cannam@233: vector plugins; Chris@477: for (map::const_iterator i = Chris@477: m_pluginLibraryNameMap.begin(); Chris@477: i != m_pluginLibraryNameMap.end(); ++i) { Chris@477: plugins.push_back(i->first); cannam@233: } cannam@233: cannam@233: return plugins; cannam@233: } cannam@233: Chris@473: PluginLoader::PluginKeyList Chris@473: PluginLoader::Impl::listPluginsIn(vector libs) Chris@473: { Chris@473: Enumeration enumeration; Chris@473: enumeration.type = Enumeration::InLibraries; Chris@473: enumeration.libraryNames = libs; Chris@473: return enumeratePlugins(enumeration); Chris@473: } Chris@473: Chris@473: PluginLoader::PluginKeyList Chris@473: PluginLoader::Impl::listPluginsNotIn(vector libs) Chris@473: { Chris@473: Enumeration enumeration; Chris@473: enumeration.type = Enumeration::NotInLibraries; Chris@473: enumeration.libraryNames = libs; Chris@473: return enumeratePlugins(enumeration); Chris@473: } Chris@473: Chris@473: vector Chris@473: PluginLoader::Impl::listLibraryFilesFor(Enumeration enumeration) Chris@473: { Chris@473: Files::Filter filter; Chris@473: Chris@473: switch (enumeration.type) { Chris@473: Chris@473: case Enumeration::All: Chris@473: filter.type = Files::Filter::All; Chris@473: break; Chris@473: Chris@473: case Enumeration::SinglePlugin: Chris@473: { Chris@473: string libraryName, identifier; Chris@473: if (!decomposePluginKey(enumeration.key, libraryName, identifier)) { Chris@473: std::cerr << "WARNING: Vamp::HostExt::PluginLoader: " Chris@473: << "Invalid plugin key \"" << enumeration.key Chris@473: << "\" in enumerate" << std::endl; Chris@477: return vector(); Chris@473: } Chris@473: filter.type = Files::Filter::Matching; Chris@477: filter.libraryNames.clear(); Chris@477: filter.libraryNames.push_back(libraryName); Chris@473: break; Chris@473: } Chris@473: Chris@473: case Enumeration::InLibraries: Chris@473: filter.type = Files::Filter::Matching; Chris@473: filter.libraryNames = enumeration.libraryNames; Chris@473: break; Chris@473: Chris@473: case Enumeration::NotInLibraries: Chris@473: filter.type = Files::Filter::NotMatching; Chris@473: filter.libraryNames = enumeration.libraryNames; Chris@473: break; Chris@473: } Chris@473: Chris@473: return Files::listLibraryFilesMatching(filter); Chris@473: } Chris@473: Chris@473: vector Chris@473: PluginLoader::Impl::enumeratePlugins(Enumeration enumeration) cannam@233: { cannam@233: string libraryName, identifier; Chris@473: if (enumeration.type == Enumeration::SinglePlugin) { Chris@473: decomposePluginKey(enumeration.key, libraryName, identifier); Chris@473: } Chris@390: Chris@473: vector fullPaths = listLibraryFilesFor(enumeration); cannam@233: Chris@473: // For these we should warn if a plugin can be loaded from a library Chris@473: bool specific = (enumeration.type == Enumeration::SinglePlugin || Chris@473: enumeration.type == Enumeration::InLibraries); Chris@473: Chris@473: vector added; Chris@473: Chris@390: for (size_t i = 0; i < fullPaths.size(); ++i) { cannam@233: Chris@390: string fullPath = fullPaths[i]; Chris@390: void *handle = Files::loadLibrary(fullPath); Chris@390: if (!handle) continue; cannam@233: Chris@390: VampGetPluginDescriptorFunction fn = Chris@390: (VampGetPluginDescriptorFunction)Files::lookupInLibrary Chris@390: (handle, "vampGetPluginDescriptor"); cannam@233: Chris@390: if (!fn) { Chris@473: if (specific) { Chris@473: cerr << "Vamp::HostExt::PluginLoader: " Chris@473: << "No vampGetPluginDescriptor function found in library \"" cannam@295: << fullPath << "\"" << endl; cannam@295: } Chris@390: Files::unloadLibrary(handle); Chris@390: continue; Chris@390: } cannam@233: Chris@390: int index = 0; Chris@390: const VampPluginDescriptor *descriptor = 0; Chris@390: bool found = false; Chris@390: Chris@390: while ((descriptor = fn(VAMP_API_VERSION, index))) { Chris@390: ++index; Chris@390: if (identifier != "") { Chris@473: if (descriptor->identifier != identifier) { Chris@473: continue; Chris@473: } Chris@390: } Chris@390: found = true; Chris@390: PluginKey key = composePluginKey(fullPath, descriptor->identifier); Chris@390: if (m_pluginLibraryNameMap.find(key) == Chris@390: m_pluginLibraryNameMap.end()) { Chris@390: m_pluginLibraryNameMap[key] = fullPath; Chris@390: } Chris@473: added.push_back(key); cannam@233: } Chris@390: Chris@473: if (!found && specific) { Chris@390: cerr << "Vamp::HostExt::PluginLoader: Plugin \"" Chris@390: << identifier << "\" not found in library \"" Chris@390: << fullPath << "\"" << endl; Chris@390: } Chris@390: Chris@390: Files::unloadLibrary(handle); cannam@233: } cannam@233: Chris@473: if (enumeration.type == Enumeration::All) { Chris@473: m_allPluginsEnumerated = true; Chris@473: } Chris@473: Chris@473: return added; cannam@233: } cannam@233: cannam@233: PluginLoader::PluginKey cannam@233: PluginLoader::Impl::composePluginKey(string libraryName, string identifier) cannam@233: { Chris@390: string basename = Files::lcBasename(libraryName); cannam@233: return basename + ":" + identifier; cannam@233: } cannam@233: cannam@233: bool cannam@233: PluginLoader::Impl::decomposePluginKey(PluginKey key, cannam@233: string &libraryName, cannam@233: string &identifier) cannam@233: { cannam@233: string::size_type ki = key.find(':'); cannam@233: if (ki == string::npos) { cannam@233: return false; cannam@233: } cannam@233: cannam@233: libraryName = key.substr(0, ki); cannam@233: identifier = key.substr(ki + 1); cannam@233: return true; cannam@233: } cannam@233: cannam@233: PluginLoader::PluginCategoryHierarchy cannam@233: PluginLoader::Impl::getPluginCategory(PluginKey plugin) cannam@233: { cannam@233: if (m_taxonomy.empty()) generateTaxonomy(); cannam@233: if (m_taxonomy.find(plugin) == m_taxonomy.end()) { cannam@233: return PluginCategoryHierarchy(); cannam@233: } cannam@233: return m_taxonomy[plugin]; cannam@233: } cannam@233: cannam@233: string cannam@233: PluginLoader::Impl::getLibraryPathForPlugin(PluginKey plugin) cannam@233: { cannam@233: if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) { cannam@233: if (m_allPluginsEnumerated) return ""; Chris@473: Enumeration enumeration; Chris@473: enumeration.type = Enumeration::SinglePlugin; Chris@473: enumeration.key = plugin; Chris@473: enumeratePlugins(enumeration); cannam@233: } cannam@233: if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) { cannam@233: return ""; cannam@233: } cannam@233: return m_pluginLibraryNameMap[plugin]; cannam@233: } cannam@233: cannam@233: Plugin * cannam@233: PluginLoader::Impl::loadPlugin(PluginKey key, cannam@233: float inputSampleRate, int adapterFlags) cannam@233: { cannam@233: string libname, identifier; cannam@233: if (!decomposePluginKey(key, libname, identifier)) { cannam@233: std::cerr << "Vamp::HostExt::PluginLoader: Invalid plugin key \"" cannam@233: << key << "\" in loadPlugin" << std::endl; cannam@233: return 0; cannam@233: } cannam@233: cannam@233: string fullPath = getLibraryPathForPlugin(key); cannam@293: if (fullPath == "") { cannam@295: std::cerr << "Vamp::HostExt::PluginLoader: No library found in Vamp path for plugin \"" << key << "\"" << std::endl; cannam@293: return 0; cannam@293: } cannam@233: Chris@390: void *handle = Files::loadLibrary(fullPath); cannam@233: if (!handle) return 0; cannam@233: cannam@233: VampGetPluginDescriptorFunction fn = Chris@390: (VampGetPluginDescriptorFunction)Files::lookupInLibrary cannam@233: (handle, "vampGetPluginDescriptor"); cannam@233: cannam@233: if (!fn) { cannam@293: cerr << "Vamp::HostExt::PluginLoader: No vampGetPluginDescriptor function found in library \"" cannam@293: << fullPath << "\"" << endl; Chris@390: Files::unloadLibrary(handle); cannam@233: return 0; cannam@233: } cannam@233: cannam@233: int index = 0; cannam@233: const VampPluginDescriptor *descriptor = 0; cannam@233: cannam@233: while ((descriptor = fn(VAMP_API_VERSION, index))) { cannam@233: cannam@233: if (string(descriptor->identifier) == identifier) { cannam@233: cannam@233: Vamp::PluginHostAdapter *plugin = cannam@233: new Vamp::PluginHostAdapter(descriptor, inputSampleRate); cannam@233: cannam@233: Plugin *adapter = new PluginDeletionNotifyAdapter(plugin, this); cannam@233: cannam@233: m_pluginLibraryHandleMap[adapter] = handle; cannam@233: cannam@233: if (adapterFlags & ADAPT_INPUT_DOMAIN) { cannam@233: if (adapter->getInputDomain() == Plugin::FrequencyDomain) { cannam@233: adapter = new PluginInputDomainAdapter(adapter); cannam@233: } cannam@233: } cannam@233: cannam@233: if (adapterFlags & ADAPT_BUFFER_SIZE) { cannam@233: adapter = new PluginBufferingAdapter(adapter); cannam@233: } cannam@233: cannam@233: if (adapterFlags & ADAPT_CHANNEL_COUNT) { cannam@233: adapter = new PluginChannelAdapter(adapter); cannam@233: } cannam@233: cannam@233: return adapter; cannam@233: } cannam@233: cannam@233: ++index; cannam@233: } cannam@233: cannam@233: cerr << "Vamp::HostExt::PluginLoader: Plugin \"" cannam@233: << identifier << "\" not found in library \"" cannam@233: << fullPath << "\"" << endl; cannam@233: cannam@233: return 0; cannam@233: } cannam@233: cannam@233: void cannam@233: PluginLoader::Impl::generateTaxonomy() cannam@233: { cannam@233: // cerr << "PluginLoader::Impl::generateTaxonomy" << endl; cannam@233: cannam@233: vector path = PluginHostAdapter::getPluginPath(); cannam@233: string libfragment = "/lib/"; cannam@233: vector catpath; cannam@233: cannam@233: string suffix = "cat"; cannam@233: cannam@233: for (vector::iterator i = path.begin(); cannam@233: i != path.end(); ++i) { cannam@233: cannam@233: // It doesn't matter that we're using literal forward-slash in cannam@233: // this bit, as it's only relevant if the path contains cannam@233: // "/lib/", which is only meaningful and only plausible on cannam@233: // systems with forward-slash delimiters cannam@233: cannam@233: string dir = *i; cannam@233: string::size_type li = dir.find(libfragment); cannam@233: cannam@233: if (li != string::npos) { cannam@233: catpath.push_back cannam@233: (dir.substr(0, li) cannam@233: + "/share/" cannam@233: + dir.substr(li + libfragment.length())); cannam@233: } cannam@233: cannam@233: catpath.push_back(dir); cannam@233: } cannam@233: cannam@233: char buffer[1024]; cannam@233: cannam@233: for (vector::iterator i = catpath.begin(); cannam@233: i != catpath.end(); ++i) { cannam@233: Chris@390: vector files = Files::listFiles(*i, suffix); cannam@233: cannam@233: for (vector::iterator fi = files.begin(); cannam@233: fi != files.end(); ++fi) { cannam@233: Chris@390: string filepath = Files::splicePath(*i, *fi); cannam@233: ifstream is(filepath.c_str(), ifstream::in | ifstream::binary); cannam@233: cannam@233: if (is.fail()) { cannam@233: // cerr << "failed to open: " << filepath << endl; cannam@233: continue; cannam@233: } cannam@233: cannam@233: // cerr << "opened: " << filepath << endl; cannam@233: cannam@233: while (!!is.getline(buffer, 1024)) { cannam@233: cannam@233: string line(buffer); cannam@233: cannam@233: // cerr << "line = " << line << endl; cannam@233: cannam@233: string::size_type di = line.find("::"); cannam@233: if (di == string::npos) continue; cannam@233: cannam@233: string id = line.substr(0, di); cannam@233: string encodedCat = line.substr(di + 2); cannam@233: cannam@233: if (id.substr(0, 5) != "vamp:") continue; cannam@233: id = id.substr(5); cannam@233: cannam@233: while (encodedCat.length() >= 1 && cannam@233: encodedCat[encodedCat.length()-1] == '\r') { cannam@233: encodedCat = encodedCat.substr(0, encodedCat.length()-1); cannam@233: } cannam@233: cannam@233: // cerr << "id = " << id << ", cat = " << encodedCat << endl; cannam@233: cannam@233: PluginCategoryHierarchy category; cannam@233: string::size_type ai; cannam@233: while ((ai = encodedCat.find(" > ")) != string::npos) { cannam@233: category.push_back(encodedCat.substr(0, ai)); cannam@233: encodedCat = encodedCat.substr(ai + 3); cannam@233: } cannam@233: if (encodedCat != "") category.push_back(encodedCat); cannam@233: cannam@233: m_taxonomy[id] = category; cannam@233: } cannam@233: } cannam@233: } cannam@233: } cannam@233: cannam@233: void cannam@233: PluginLoader::Impl::pluginDeleted(PluginDeletionNotifyAdapter *adapter) cannam@233: { cannam@233: void *handle = m_pluginLibraryHandleMap[adapter]; Chris@524: if (!handle) return; Chris@524: cannam@233: m_pluginLibraryHandleMap.erase(adapter); Chris@524: Chris@524: for (auto h: m_pluginLibraryHandleMap) { Chris@524: if (h.second == handle) { Chris@524: // still in use Chris@524: return; Chris@524: } Chris@524: } Chris@524: Chris@524: Files::unloadLibrary(handle); cannam@233: } cannam@233: cannam@233: PluginLoader::Impl::PluginDeletionNotifyAdapter::PluginDeletionNotifyAdapter(Plugin *plugin, cannam@233: Impl *loader) : cannam@233: PluginWrapper(plugin), cannam@233: m_loader(loader) cannam@233: { cannam@233: } cannam@233: cannam@233: PluginLoader::Impl::PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter() cannam@233: { cannam@233: // We need to delete the plugin before calling pluginDeleted, as cannam@233: // the delete call may require calling through to the descriptor cannam@233: // (for e.g. cleanup) but pluginDeleted may unload the required cannam@233: // library for the call. To prevent a double deletion when our cannam@233: // parent's destructor runs (after this one), be sure to set cannam@233: // m_plugin to 0 after deletion. cannam@233: delete m_plugin; cannam@233: m_plugin = 0; cannam@233: cannam@233: if (m_loader) m_loader->pluginDeleted(this); cannam@233: } cannam@233: cannam@233: } cannam@233: cannam@233: } cannam@263: cannam@263: _VAMP_SDK_HOSTSPACE_END(PluginLoader.cpp) cannam@263: