Chris@49: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@0: Chris@0: /* Chris@52: Sonic Visualiser Chris@52: An audio file viewer and annotation editor. Chris@52: Centre for Digital Music, Queen Mary, University of London. Chris@1225: This file copyright 2006-2016 Chris Cannam and QMUL. Chris@0: Chris@52: This program is free software; you can redistribute it and/or Chris@52: modify it under the terms of the GNU General Public License as Chris@52: published by the Free Software Foundation; either version 2 of the Chris@52: License, or (at your option) any later version. See the file Chris@52: COPYING included with this distribution for more information. Chris@0: */ Chris@0: Chris@1225: #include "NativeVampPluginFactory.h" Chris@0: #include "PluginIdentifier.h" Chris@0: Chris@1225: #include Chris@1225: #include Chris@1225: Chris@150: #include "system/System.h" Chris@66: Chris@1179: #include "PluginScan.h" Chris@1179: Chris@66: #include Chris@66: #include Chris@66: #include Chris@165: #include Chris@66: Chris@0: #include Chris@0: Chris@408: #include "base/Profiler.h" Chris@408: Chris@1225: #include Chris@1225: #include Chris@1223: Chris@1164: using namespace std; Chris@1164: Chris@249: //#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1 Chris@249: Chris@1225: class PluginDeletionNotifyAdapter : public Vamp::HostExt::PluginWrapper { Chris@1225: public: Chris@1225: PluginDeletionNotifyAdapter(Vamp::Plugin *plugin, Chris@1225: NativeVampPluginFactory *factory) : Chris@1225: PluginWrapper(plugin), m_factory(factory) { } Chris@1579: ~PluginDeletionNotifyAdapter() override; Chris@1225: protected: Chris@1225: NativeVampPluginFactory *m_factory; Chris@1225: }; Chris@0: Chris@1225: PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter() Chris@0: { Chris@1225: // see notes in vamp-sdk/hostext/PluginLoader.cpp from which this is drawn Chris@1225: Vamp::Plugin *p = m_plugin; Chris@1225: delete m_plugin; Chris@1582: m_plugin = nullptr; Chris@1225: // acceptable use after free here, as pluginDeleted uses p only as Chris@1225: // pointer key and does not deref it Chris@1225: if (m_factory) m_factory->pluginDeleted(p); Chris@66: } Chris@66: Chris@1164: vector Chris@1225: NativeVampPluginFactory::getPluginPath() Chris@0: { Chris@1225: if (!m_pluginPath.empty()) return m_pluginPath; Chris@1225: Chris@1225: vector p = Vamp::PluginHostAdapter::getPluginPath(); Chris@1225: for (size_t i = 0; i < p.size(); ++i) m_pluginPath.push_back(p[i].c_str()); Chris@1225: return m_pluginPath; Chris@1225: } Chris@1225: Chris@1249: static Chris@1249: QList Chris@1249: getCandidateLibraries() Chris@1249: { Chris@1249: #ifdef HAVE_PLUGIN_CHECKER_HELPER Chris@1249: return PluginScan::getInstance()->getCandidateLibrariesFor Chris@1249: (PluginScan::VampPlugin); Chris@1249: #else Chris@1249: auto path = Vamp::PluginHostAdapter::getPluginPath(); Chris@1249: QList candidates; Chris@1249: for (string dirname: path) { Chris@1249: SVDEBUG << "NativeVampPluginFactory: scanning directory myself: " Chris@1249: << dirname << endl; Chris@1249: #if defined(_WIN32) Chris@1249: #define PLUGIN_GLOB "*.dll" Chris@1249: #elif defined(__APPLE__) Chris@1249: #define PLUGIN_GLOB "*.dylib *.so" Chris@1249: #else Chris@1249: #define PLUGIN_GLOB "*.so" Chris@1249: #endif Chris@1249: QDir dir(dirname.c_str(), PLUGIN_GLOB, Chris@1249: QDir::Name | QDir::IgnoreCase, Chris@1249: QDir::Files | QDir::Readable); Chris@1249: Chris@1249: for (unsigned int i = 0; i < dir.count(); ++i) { Chris@1464: QString libpath = dir.filePath(dir[i]); Chris@1464: candidates.push_back({ libpath, "" }); Chris@1249: } Chris@1249: } Chris@1249: Chris@1249: return candidates; Chris@1249: #endif Chris@1249: } Chris@1249: Chris@1225: vector Chris@1227: NativeVampPluginFactory::getPluginIdentifiers(QString &) Chris@1225: { Chris@1225: Profiler profiler("NativeVampPluginFactory::getPluginIdentifiers"); Chris@1225: Chris@1225: QMutexLocker locker(&m_mutex); Chris@1225: Chris@1225: if (!m_identifiers.empty()) { Chris@1225: return m_identifiers; Chris@1225: } Chris@1249: Chris@1249: auto candidates = getCandidateLibraries(); Chris@0: Chris@1249: SVDEBUG << "INFO: Have " << candidates.size() << " candidate Vamp plugin libraries" << endl; Chris@1249: Chris@1246: for (auto candidate : candidates) { Chris@1246: Chris@1464: QString libpath = candidate.libraryPath; Chris@1225: Chris@1464: SVDEBUG << "INFO: Considering candidate Vamp plugin library " << libpath << endl; Chris@1249: Chris@1464: void *libraryHandle = DLOPEN(libpath, RTLD_LAZY | RTLD_LOCAL); Chris@1225: Chris@1225: if (!libraryHandle) { Chris@1464: SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to load library " << libpath << ": " << DLERROR() << endl; Chris@1225: continue; Chris@1225: } Chris@1225: Chris@1225: VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction) Chris@1225: DLSYM(libraryHandle, "vampGetPluginDescriptor"); Chris@1225: Chris@1225: if (!fn) { Chris@1464: SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: No descriptor function in " << libpath << endl; Chris@1225: if (DLCLOSE(libraryHandle) != 0) { Chris@1464: SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << libpath << endl; Chris@1225: } Chris@1225: continue; Chris@1225: } Chris@1225: Chris@1225: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1225: cerr << "NativeVampPluginFactory::getPluginIdentifiers: Vamp descriptor found" << endl; Chris@1225: #endif Chris@1225: Chris@1582: const VampPluginDescriptor *descriptor = nullptr; Chris@1225: int index = 0; Chris@1225: Chris@1225: map known; Chris@1225: bool ok = true; Chris@1225: Chris@1225: while ((descriptor = fn(VAMP_API_VERSION, index))) { Chris@1225: Chris@1225: if (known.find(descriptor->identifier) != known.end()) { Chris@1247: SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Plugin library " Chris@1464: << libpath Chris@1464: << " returns the same plugin identifier \"" Chris@1464: << descriptor->identifier << "\" at indices " Chris@1464: << known[descriptor->identifier] << " and " Chris@1464: << index << endl; Chris@1464: SVDEBUG << "NativeVampPluginFactory::getPluginIdentifiers: Avoiding this library (obsolete API?)" << endl; Chris@1225: ok = false; Chris@1225: break; Chris@1225: } else { Chris@1225: known[descriptor->identifier] = index; Chris@1225: } Chris@1225: Chris@1225: ++index; Chris@1225: } Chris@1225: Chris@1225: if (ok) { Chris@1225: Chris@1225: index = 0; Chris@1225: Chris@1225: while ((descriptor = fn(VAMP_API_VERSION, index))) { Chris@1225: Chris@1225: QString id = PluginIdentifier::createIdentifier Chris@1464: ("vamp", libpath, descriptor->identifier); Chris@1225: m_identifiers.push_back(id); Chris@1464: m_libraries[id] = libpath; Chris@1225: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1225: cerr << "NativeVampPluginFactory::getPluginIdentifiers: Found plugin id " << id << " at index " << index << endl; Chris@1225: #endif Chris@1225: ++index; Chris@1225: } Chris@1225: } Chris@1225: Chris@1225: if (DLCLOSE(libraryHandle) != 0) { Chris@1464: SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << libpath << endl; Chris@1225: } Chris@0: } Chris@0: Chris@1225: generateTaxonomy(); Chris@1225: Chris@0: // Plugins can change the locale, revert it to default. Chris@608: RestoreStartupLocale(); Chris@608: Chris@1225: return m_identifiers; Chris@1225: } Chris@1225: Chris@1225: QString Chris@1225: NativeVampPluginFactory::findPluginFile(QString soname, QString inDir) Chris@1225: { Chris@1225: QString file = ""; Chris@1225: Chris@1225: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1225: cerr << "NativeVampPluginFactory::findPluginFile(\"" Chris@1225: << soname << "\", \"" << inDir << "\")" Chris@1225: << endl; Chris@1225: #endif Chris@1225: Chris@1225: if (inDir != "") { Chris@1225: Chris@1225: QDir dir(inDir, PLUGIN_GLOB, Chris@1225: QDir::Name | QDir::IgnoreCase, Chris@1225: QDir::Files | QDir::Readable); Chris@1225: if (!dir.exists()) return ""; Chris@1225: Chris@1225: file = dir.filePath(QFileInfo(soname).fileName()); Chris@1225: Chris@1225: if (QFileInfo(file).exists() && QFileInfo(file).isFile()) { Chris@1225: Chris@1225: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1225: cerr << "NativeVampPluginFactory::findPluginFile: " Chris@1225: << "found trivially at " << file << endl; Chris@1225: #endif Chris@1225: Chris@1225: return file; Chris@1225: } Chris@1225: Chris@1429: for (unsigned int j = 0; j < dir.count(); ++j) { Chris@1225: file = dir.filePath(dir[j]); Chris@1225: if (QFileInfo(file).baseName() == QFileInfo(soname).baseName()) { Chris@1225: Chris@1225: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1225: cerr << "NativeVampPluginFactory::findPluginFile: " Chris@1225: << "found \"" << soname << "\" at " << file << endl; Chris@1225: #endif Chris@1225: Chris@1225: return file; Chris@1225: } Chris@1225: } Chris@1225: Chris@1225: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1225: cerr << "NativeVampPluginFactory::findPluginFile (with dir): " Chris@1225: << "not found" << endl; Chris@1225: #endif Chris@1225: Chris@1225: return ""; Chris@1225: Chris@1225: } else { Chris@1225: Chris@1225: QFileInfo fi(soname); Chris@1225: Chris@1225: if (fi.isAbsolute() && fi.exists() && fi.isFile()) { Chris@1225: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1225: cerr << "NativeVampPluginFactory::findPluginFile: " Chris@1225: << "found trivially at " << soname << endl; Chris@1225: #endif Chris@1225: return soname; Chris@1225: } Chris@1225: Chris@1225: if (fi.isAbsolute() && fi.absolutePath() != "") { Chris@1225: file = findPluginFile(soname, fi.absolutePath()); Chris@1225: if (file != "") return file; Chris@1225: } Chris@1225: Chris@1225: vector path = getPluginPath(); Chris@1225: for (vector::iterator i = path.begin(); Chris@1225: i != path.end(); ++i) { Chris@1225: if (*i != "") { Chris@1225: file = findPluginFile(soname, *i); Chris@1225: if (file != "") return file; Chris@1225: } Chris@1225: } Chris@1225: Chris@1225: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1225: cerr << "NativeVampPluginFactory::findPluginFile: " Chris@1225: << "not found" << endl; Chris@1225: #endif Chris@1225: Chris@1225: return ""; Chris@1225: } Chris@1225: } Chris@1225: Chris@1225: Vamp::Plugin * Chris@1225: NativeVampPluginFactory::instantiatePlugin(QString identifier, Chris@1225: sv_samplerate_t inputSampleRate) Chris@1225: { Chris@1225: Profiler profiler("NativeVampPluginFactory::instantiatePlugin"); Chris@1225: Chris@1582: Vamp::Plugin *rv = nullptr; Chris@1582: Vamp::PluginHostAdapter *plugin = nullptr; Chris@1225: Chris@1582: const VampPluginDescriptor *descriptor = nullptr; Chris@1225: int index = 0; Chris@1225: Chris@1225: QString type, soname, label; Chris@1225: PluginIdentifier::parseIdentifier(identifier, type, soname, label); Chris@1225: if (type != "vamp") { Chris@1225: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1225: cerr << "NativeVampPluginFactory::instantiatePlugin: Wrong factory for plugin type " << type << endl; Chris@1225: #endif Chris@1582: return nullptr; Chris@1225: } Chris@1225: Chris@1225: QString found = findPluginFile(soname); Chris@1225: Chris@1225: if (found == "") { Chris@1247: SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Failed to find library file " << soname << endl; Chris@1582: return nullptr; Chris@1225: } else if (found != soname) { Chris@1225: Chris@1225: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1225: cerr << "NativeVampPluginFactory::instantiatePlugin: Given library name was " << soname << ", found at " << found << endl; Chris@1225: cerr << soname << " -> " << found << endl; Chris@1225: #endif Chris@1225: Chris@1225: } Chris@1225: Chris@1225: soname = found; Chris@1225: Chris@1225: void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL); Chris@1225: Chris@1225: if (!libraryHandle) { Chris@1247: SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Failed to load library " << soname << ": " << DLERROR() << endl; Chris@1582: return nullptr; Chris@1225: } Chris@1225: Chris@1225: VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction) Chris@1225: DLSYM(libraryHandle, "vampGetPluginDescriptor"); Chris@1225: Chris@1225: if (!fn) { Chris@1247: SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: No descriptor function in " << soname << endl; Chris@1225: goto done; Chris@1225: } Chris@1225: Chris@1225: while ((descriptor = fn(VAMP_API_VERSION, index))) { Chris@1225: if (label == descriptor->identifier) break; Chris@1225: ++index; Chris@1225: } Chris@1225: Chris@1225: if (!descriptor) { Chris@1247: SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Failed to find plugin \"" << label << "\" in library " << soname << endl; Chris@1225: goto done; Chris@1225: } Chris@1225: Chris@1225: plugin = new Vamp::PluginHostAdapter(descriptor, float(inputSampleRate)); Chris@1225: Chris@1225: if (plugin) { Chris@1225: m_handleMap[plugin] = libraryHandle; Chris@1225: rv = new PluginDeletionNotifyAdapter(plugin, this); Chris@1225: } Chris@1225: Chris@1225: // SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Constructed Vamp plugin, rv is " << rv << endl; Chris@1225: Chris@1225: //!!! need to dlclose() when plugins from a given library are unloaded Chris@1225: Chris@1225: done: Chris@1225: if (!rv) { Chris@1225: if (DLCLOSE(libraryHandle) != 0) { Chris@1247: SVDEBUG << "WARNING: NativeVampPluginFactory::instantiatePlugin: Failed to unload library " << soname << endl; Chris@1225: } Chris@1225: } Chris@1225: Chris@1225: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1225: cerr << "NativeVampPluginFactory::instantiatePlugin: Instantiated plugin " << label << " from library " << soname << ": descriptor " << descriptor << ", rv "<< rv << ", label " << rv->getName() << ", outputs " << rv->getOutputDescriptors().size() << endl; Chris@1225: #endif Chris@1225: Chris@0: return rv; Chris@0: } Chris@0: Chris@1225: void Chris@1225: NativeVampPluginFactory::pluginDeleted(Vamp::Plugin *plugin) Chris@0: { Chris@1225: void *handle = m_handleMap[plugin]; Chris@1225: if (handle) { Chris@1225: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1225: cerr << "unloading library " << handle << " for plugin " << plugin << endl; Chris@1225: #endif Chris@1225: DLCLOSE(handle); Chris@1225: } Chris@1225: m_handleMap.erase(plugin); Chris@1225: } Chris@408: Chris@1225: QString Chris@1225: NativeVampPluginFactory::getPluginCategory(QString identifier) Chris@1225: { Chris@1225: return m_taxonomy[identifier]; Chris@1225: } Chris@1225: Chris@1464: QString Chris@1464: NativeVampPluginFactory::getPluginLibraryPath(QString identifier) Chris@1464: { Chris@1464: return m_libraries[identifier]; Chris@1464: } Chris@1464: Chris@1225: void Chris@1225: NativeVampPluginFactory::generateTaxonomy() Chris@1225: { Chris@1225: vector pluginPath = getPluginPath(); Chris@1225: vector path; Chris@1225: Chris@1225: for (size_t i = 0; i < pluginPath.size(); ++i) { Chris@1429: if (pluginPath[i].contains("/lib/")) { Chris@1429: QString p(pluginPath[i]); Chris@1225: path.push_back(p); Chris@1429: p.replace("/lib/", "/share/"); Chris@1429: path.push_back(p); Chris@1429: } Chris@1429: path.push_back(pluginPath[i]); Chris@1225: } Chris@1225: Chris@1225: for (size_t i = 0; i < path.size(); ++i) { Chris@1225: Chris@1429: QDir dir(path[i], "*.cat"); Chris@1225: Chris@1429: // SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: directory " << path[i] << " has " << dir.count() << " .cat files" << endl; Chris@1429: for (unsigned int j = 0; j < dir.count(); ++j) { Chris@1225: Chris@1429: QFile file(path[i] + "/" + dir[j]); Chris@1225: Chris@1429: // SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: about to open " << (path[i]+ "/" + dir[j]) << endl; Chris@1225: Chris@1429: if (file.open(QIODevice::ReadOnly)) { Chris@1429: // cerr << "...opened" << endl; Chris@1429: QTextStream stream(&file); Chris@1429: QString line; Chris@1225: Chris@1429: while (!stream.atEnd()) { Chris@1429: line = stream.readLine(); Chris@1429: // cerr << "line is: \"" << line << "\"" << endl; Chris@1429: QString id = PluginIdentifier::canonicalise Chris@1225: (line.section("::", 0, 0)); Chris@1429: QString cat = line.section("::", 1, 1); Chris@1429: m_taxonomy[id] = cat; Chris@1429: // cerr << "NativeVampPluginFactory: set id \"" << id << "\" to cat \"" << cat << "\"" << endl; Chris@1429: } Chris@1429: } Chris@1429: } Chris@1225: } Chris@1225: } Chris@1225: Chris@1225: piper_vamp::PluginStaticData Chris@1225: NativeVampPluginFactory::getPluginStaticData(QString identifier) Chris@1225: { Chris@1209: QMutexLocker locker(&m_mutex); Chris@1209: Chris@1225: if (m_pluginData.find(identifier) != m_pluginData.end()) { Chris@1225: return m_pluginData[identifier]; Chris@1209: } Chris@1210: Chris@66: QString type, soname, label; Chris@66: PluginIdentifier::parseIdentifier(identifier, type, soname, label); Chris@1210: std::string pluginKey = (soname + ":" + label).toStdString(); Chris@0: Chris@1225: std::vector catlist; Chris@1225: for (auto s: getPluginCategory(identifier).split(" > ")) { Chris@1225: catlist.push_back(s.toStdString()); Chris@1225: } Chris@1225: Chris@1225: Vamp::Plugin *p = instantiatePlugin(identifier, 44100); Chris@1225: if (!p) return {}; Chris@66: Chris@1225: auto psd = piper_vamp::PluginStaticData::fromPlugin(pluginKey, Chris@1225: catlist, Chris@1225: p); Chris@1225: Chris@1225: delete p; Chris@1225: Chris@1225: m_pluginData[identifier] = psd; Chris@1225: return psd; Chris@298: } Chris@298: