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@202: This file copyright 2006 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@0: #include "FeatureExtractionPluginFactory.h" Chris@0: #include "PluginIdentifier.h" Chris@0: Chris@475: #include Chris@475: #include Chris@66: Chris@150: #include "system/System.h" Chris@66: 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@1164: using namespace std; Chris@1164: Chris@249: //#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1 Chris@249: Chris@298: class PluginDeletionNotifyAdapter : public Vamp::HostExt::PluginWrapper { Chris@298: public: Chris@298: PluginDeletionNotifyAdapter(Vamp::Plugin *plugin, Chris@298: FeatureExtractionPluginFactory *factory) : Chris@298: PluginWrapper(plugin), m_factory(factory) { } Chris@298: virtual ~PluginDeletionNotifyAdapter(); Chris@298: protected: Chris@298: FeatureExtractionPluginFactory *m_factory; Chris@298: }; Chris@298: Chris@298: PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter() Chris@298: { Chris@298: // see notes in vamp-sdk/hostext/PluginLoader.cpp from which this is drawn Chris@298: Vamp::Plugin *p = m_plugin; Chris@298: delete m_plugin; Chris@298: m_plugin = 0; Chris@973: // acceptable use after free here, as pluginDeleted uses p only as Chris@973: // pointer key and does not deref it Chris@298: if (m_factory) m_factory->pluginDeleted(p); Chris@298: } Chris@298: Chris@0: static FeatureExtractionPluginFactory *_nativeInstance = 0; Chris@0: Chris@0: FeatureExtractionPluginFactory * Chris@0: FeatureExtractionPluginFactory::instance(QString pluginType) Chris@0: { Chris@71: if (pluginType == "vamp") { Chris@0: if (!_nativeInstance) { Chris@690: // SVDEBUG << "FeatureExtractionPluginFactory::instance(" << pluginType// << "): creating new FeatureExtractionPluginFactory" << endl; Chris@0: _nativeInstance = new FeatureExtractionPluginFactory(); Chris@0: } Chris@0: return _nativeInstance; Chris@0: } Chris@0: Chris@0: else return 0; Chris@0: } Chris@0: Chris@0: FeatureExtractionPluginFactory * Chris@0: FeatureExtractionPluginFactory::instanceFor(QString identifier) Chris@0: { Chris@0: QString type, soName, label; Chris@0: PluginIdentifier::parseIdentifier(identifier, type, soName, label); Chris@0: return instance(type); Chris@0: } Chris@0: Chris@1164: vector Chris@66: FeatureExtractionPluginFactory::getPluginPath() Chris@66: { Chris@117: if (!m_pluginPath.empty()) return m_pluginPath; Chris@117: Chris@1164: vector p = Vamp::PluginHostAdapter::getPluginPath(); Chris@186: for (size_t i = 0; i < p.size(); ++i) m_pluginPath.push_back(p[i].c_str()); Chris@186: return m_pluginPath; Chris@66: } Chris@66: Chris@1164: vector Chris@0: FeatureExtractionPluginFactory::getAllPluginIdentifiers() Chris@0: { Chris@0: FeatureExtractionPluginFactory *factory; Chris@1164: vector rv; Chris@0: Chris@66: factory = instance("vamp"); Chris@0: if (factory) { Chris@1164: vector tmp = factory->getPluginIdentifiers(); Chris@0: for (size_t i = 0; i < tmp.size(); ++i) { Chris@843: // cerr << "identifier: " << tmp[i] << endl; Chris@0: rv.push_back(tmp[i]); Chris@0: } Chris@0: } Chris@0: Chris@0: // Plugins can change the locale, revert it to default. Chris@608: RestoreStartupLocale(); Chris@608: Chris@0: return rv; Chris@0: } Chris@0: Chris@1164: vector Chris@1164: FeatureExtractionPluginFactory::getPluginCandidateFiles() Chris@1164: { Chris@1164: vector path = getPluginPath(); Chris@1164: vector candidates; Chris@1164: Chris@1164: for (QString dirname : path) { Chris@1164: Chris@1164: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1160: cerr << "FeatureExtractionPluginFactory::getPluginIdentifiers: scanning directory " << dirname << endl; Chris@1164: #endif Chris@1164: Chris@1164: QDir pluginDir(dirname, PLUGIN_GLOB, Chris@1164: QDir::Name | QDir::IgnoreCase, Chris@1164: QDir::Files | QDir::Readable); Chris@1164: Chris@1164: for (unsigned int j = 0; j < pluginDir.count(); ++j) { Chris@1164: QString soname = pluginDir.filePath(pluginDir[j]); Chris@1164: candidates.push_back(soname); Chris@1164: } Chris@1164: } Chris@1164: Chris@1164: return candidates; Chris@1164: } Chris@1164: Chris@1164: vector Chris@1166: FeatureExtractionPluginFactory::winnowPluginCandidates(vector candidates, Chris@1166: QString &warningMessage) Chris@1165: { Chris@1165: vector good, bad; Chris@1165: vector badStatuses; Chris@1165: Chris@1165: for (QString c: candidates) { Chris@1165: Chris@1165: PluginLoadStatus status = Chris@1165: TestPluginLoadability(c, "vampGetPluginDescriptor"); Chris@1165: Chris@1165: if (status == PluginLoadOK) { Chris@1165: good.push_back(c); Chris@1165: } else if (status == UnknownPluginLoadStatus) { Chris@1165: cerr << "WARNING: Unknown load status for plugin candidate \"" Chris@1165: << c << "\", continuing" << endl; Chris@1165: good.push_back(c); Chris@1165: } else { Chris@1165: bad.push_back(c); Chris@1165: badStatuses.push_back(status); Chris@1165: } Chris@1165: } Chris@1165: Chris@1165: if (!bad.empty()) { Chris@1166: warningMessage = Chris@1166: QObject::tr("Failed to load plugins" Chris@1166: "

Failed to load one or more plugin libraries:

\n"); Chris@1166: warningMessage += "
    "; Chris@1167: for (int i = 0; in_range_for(bad, i); ++i) { Chris@1165: QString m; Chris@1165: if (badStatuses[i] == PluginLoadFailedToLoadLibrary) { Chris@1166: m = QObject::tr("Failed to load library"); Chris@1165: } else if (badStatuses[i] == PluginLoadFailedToFindDescriptor) { Chris@1166: m = QObject::tr("Failed to query plugins from library after loading"); Chris@1165: } else if (badStatuses[i] == PluginLoadFailedElsewhere) { Chris@1166: m = QObject::tr("Unknown failure"); Chris@1165: } else { Chris@1166: m = QObject::tr("Success: internal error?"); Chris@1165: } Chris@1165: warningMessage += QString("
  • %1 (%2)
  • \n") Chris@1165: .arg(bad[i]) Chris@1165: .arg(m); Chris@1165: } Chris@1165: warningMessage += "
"; Chris@1165: } Chris@1165: return good; Chris@1165: } Chris@1165: Chris@1165: vector Chris@0: FeatureExtractionPluginFactory::getPluginIdentifiers() Chris@0: { Chris@408: Profiler profiler("FeatureExtractionPluginFactory::getPluginIdentifiers"); Chris@408: Chris@1164: vector rv; Chris@1166: vector candidates = winnowPluginCandidates(getPluginCandidateFiles(), Chris@1166: m_pluginScanError); Chris@1165: Chris@1164: for (QString soname : candidates) { Chris@66: Chris@1164: void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL); Chris@1164: Chris@1164: if (!libraryHandle) { Chris@1164: cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to load library " << soname << ": " << DLERROR() << endl; Chris@1164: continue; Chris@1164: } Chris@66: Chris@1164: VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction) Chris@1164: DLSYM(libraryHandle, "vampGetPluginDescriptor"); Chris@1164: Chris@1164: if (!fn) { Chris@1164: cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: No descriptor function in " << soname << endl; Chris@1164: if (DLCLOSE(libraryHandle) != 0) { Chris@1164: cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl; Chris@1164: } Chris@1164: continue; Chris@1164: } Chris@1164: Chris@1164: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1140: cerr << "FeatureExtractionPluginFactory::getPluginIdentifiers: Vamp descriptor found" << endl; Chris@1164: #endif Chris@1164: Chris@1164: const VampPluginDescriptor *descriptor = 0; Chris@1164: int index = 0; Chris@1164: Chris@1164: map known; Chris@1164: bool ok = true; Chris@1164: Chris@1164: while ((descriptor = fn(VAMP_API_VERSION, index))) { Chris@1164: Chris@1164: if (known.find(descriptor->identifier) != known.end()) { Chris@1164: cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Plugin library " Chris@1164: << soname Chris@1164: << " returns the same plugin identifier \"" Chris@1164: << descriptor->identifier << "\" at indices " Chris@1164: << known[descriptor->identifier] << " and " Chris@1164: << index << endl; Chris@1160: cerr << "FeatureExtractionPluginFactory::getPluginIdentifiers: Avoiding this library (obsolete API?)" << endl; Chris@1164: ok = false; Chris@1164: break; Chris@1164: } else { Chris@1164: known[descriptor->identifier] = index; Chris@66: } Chris@66: Chris@1164: ++index; Chris@1164: } Chris@249: Chris@1164: if (ok) { Chris@66: Chris@1164: index = 0; Chris@251: Chris@239: while ((descriptor = fn(VAMP_API_VERSION, index))) { Chris@251: Chris@1164: QString id = PluginIdentifier::createIdentifier Chris@1164: ("vamp", soname, descriptor->identifier); Chris@1164: rv.push_back(id); Chris@1164: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1170: cerr << "FeatureExtractionPluginFactory::getPluginIdentifiers: Found plugin id " << id << " at index " << index << endl; Chris@1164: #endif Chris@251: ++index; Chris@251: } Chris@1164: } Chris@66: Chris@1164: if (DLCLOSE(libraryHandle) != 0) { Chris@1164: cerr << "WARNING: FeatureExtractionPluginFactory::getPluginIdentifiers: Failed to unload library " << soname << endl; Chris@1164: } Chris@66: } Chris@66: Chris@165: generateTaxonomy(); Chris@165: Chris@0: return rv; Chris@0: } Chris@0: Chris@66: QString Chris@66: FeatureExtractionPluginFactory::findPluginFile(QString soname, QString inDir) Chris@66: { Chris@66: QString file = ""; Chris@66: Chris@249: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1140: cerr << "FeatureExtractionPluginFactory::findPluginFile(\"" Chris@686: << soname << "\", \"" << inDir << "\")" Chris@687: << endl; Chris@249: #endif Chris@249: Chris@66: if (inDir != "") { Chris@66: Chris@66: QDir dir(inDir, PLUGIN_GLOB, Chris@66: QDir::Name | QDir::IgnoreCase, Chris@66: QDir::Files | QDir::Readable); Chris@66: if (!dir.exists()) return ""; Chris@66: Chris@66: file = dir.filePath(QFileInfo(soname).fileName()); Chris@249: Chris@249: if (QFileInfo(file).exists() && QFileInfo(file).isFile()) { Chris@249: Chris@249: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1140: cerr << "FeatureExtractionPluginFactory::findPluginFile: " Chris@687: << "found trivially at " << file << endl; Chris@249: #endif Chris@249: Chris@66: return file; Chris@66: } Chris@66: Chris@66: for (unsigned int j = 0; j < dir.count(); ++j) { Chris@66: file = dir.filePath(dir[j]); Chris@66: if (QFileInfo(file).baseName() == QFileInfo(soname).baseName()) { Chris@249: Chris@249: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1140: cerr << "FeatureExtractionPluginFactory::findPluginFile: " Chris@687: << "found \"" << soname << "\" at " << file << endl; Chris@249: #endif Chris@249: Chris@66: return file; Chris@66: } Chris@66: } Chris@66: Chris@249: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1140: cerr << "FeatureExtractionPluginFactory::findPluginFile (with dir): " Chris@687: << "not found" << endl; Chris@249: #endif Chris@249: Chris@66: return ""; Chris@66: Chris@66: } else { Chris@66: Chris@66: QFileInfo fi(soname); Chris@249: Chris@249: if (fi.isAbsolute() && fi.exists() && fi.isFile()) { Chris@249: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1140: cerr << "FeatureExtractionPluginFactory::findPluginFile: " Chris@687: << "found trivially at " << soname << endl; Chris@249: #endif Chris@249: return soname; Chris@249: } Chris@66: Chris@66: if (fi.isAbsolute() && fi.absolutePath() != "") { Chris@66: file = findPluginFile(soname, fi.absolutePath()); Chris@66: if (file != "") return file; Chris@66: } Chris@66: Chris@1164: vector path = getPluginPath(); Chris@1164: for (vector::iterator i = path.begin(); Chris@66: i != path.end(); ++i) { Chris@66: if (*i != "") { Chris@66: file = findPluginFile(soname, *i); Chris@66: if (file != "") return file; Chris@66: } Chris@66: } Chris@66: Chris@249: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1140: cerr << "FeatureExtractionPluginFactory::findPluginFile: " Chris@687: << "not found" << endl; Chris@249: #endif Chris@249: Chris@66: return ""; Chris@66: } Chris@66: } Chris@66: Chris@66: Vamp::Plugin * Chris@0: FeatureExtractionPluginFactory::instantiatePlugin(QString identifier, Chris@1040: sv_samplerate_t inputSampleRate) Chris@0: { Chris@408: Profiler profiler("FeatureExtractionPluginFactory::instantiatePlugin"); Chris@408: Chris@66: Vamp::Plugin *rv = 0; Chris@298: Vamp::PluginHostAdapter *plugin = 0; Chris@66: Chris@66: const VampPluginDescriptor *descriptor = 0; Chris@66: int index = 0; Chris@66: Chris@66: QString type, soname, label; Chris@66: PluginIdentifier::parseIdentifier(identifier, type, soname, label); Chris@71: if (type != "vamp") { Chris@1167: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1167: cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Wrong factory for plugin type " << type << endl; Chris@1167: #endif Chris@0: return 0; Chris@0: } Chris@0: Chris@66: QString found = findPluginFile(soname); Chris@66: Chris@66: if (found == "") { Chris@843: cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Failed to find library file " << soname << endl; Chris@117: return 0; Chris@66: } else if (found != soname) { Chris@249: Chris@249: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1140: cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Given library name was " << soname << ", found at " << found << endl; Chris@843: cerr << soname << " -> " << found << endl; Chris@249: #endif Chris@249: Chris@249: } Chris@0: Chris@66: soname = found; Chris@66: Chris@435: void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL); Chris@66: Chris@66: if (!libraryHandle) { Chris@843: cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Failed to load library " << soname << ": " << DLERROR() << endl; Chris@66: return 0; Chris@19: } Chris@19: Chris@66: VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction) Chris@66: DLSYM(libraryHandle, "vampGetPluginDescriptor"); Chris@66: Chris@66: if (!fn) { Chris@1140: cerr << "FeatureExtractionPluginFactory::instantiatePlugin: No descriptor function in " << soname << endl; Chris@66: goto done; Chris@0: } Chris@0: Chris@239: while ((descriptor = fn(VAMP_API_VERSION, index))) { Chris@238: if (label == descriptor->identifier) break; Chris@66: ++index; Chris@47: } Chris@47: Chris@66: if (!descriptor) { Chris@843: cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Failed to find plugin \"" << label << "\" in library " << soname << endl; Chris@66: goto done; Martin@37: } Martin@37: Chris@1040: plugin = new Vamp::PluginHostAdapter(descriptor, float(inputSampleRate)); Chris@298: Chris@298: if (plugin) { Chris@298: m_handleMap[plugin] = libraryHandle; Chris@298: rv = new PluginDeletionNotifyAdapter(plugin, this); Chris@298: } Chris@66: Chris@690: // SVDEBUG << "FeatureExtractionPluginFactory::instantiatePlugin: Constructed Vamp plugin, rv is " << rv << endl; Chris@79: Chris@66: //!!! need to dlclose() when plugins from a given library are unloaded Chris@66: Chris@66: done: Chris@66: if (!rv) { Chris@66: if (DLCLOSE(libraryHandle) != 0) { Chris@843: cerr << "WARNING: FeatureExtractionPluginFactory::instantiatePlugin: Failed to unload library " << soname << endl; Chris@66: } Chris@66: } Chris@73: Chris@1167: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1167: cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Instantiated plugin " << label << " from library " << soname << ": descriptor " << descriptor << ", rv "<< rv << ", label " << rv->getName() << ", outputs " << rv->getOutputDescriptors().size() << endl; Chris@1167: #endif Chris@73: Chris@66: return rv; Chris@0: } Chris@0: Chris@298: void Chris@298: FeatureExtractionPluginFactory::pluginDeleted(Vamp::Plugin *plugin) Chris@298: { Chris@298: void *handle = m_handleMap[plugin]; Chris@298: if (handle) { Chris@1167: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE Chris@1167: cerr << "unloading library " << handle << " for plugin " << plugin << endl; Chris@1167: #endif Chris@298: DLCLOSE(handle); Chris@298: } Chris@298: m_handleMap.erase(plugin); Chris@298: } Chris@298: Chris@165: QString Chris@165: FeatureExtractionPluginFactory::getPluginCategory(QString identifier) Chris@165: { Chris@165: return m_taxonomy[identifier]; Chris@165: } Chris@165: Chris@165: void Chris@165: FeatureExtractionPluginFactory::generateTaxonomy() Chris@165: { Chris@1164: vector pluginPath = getPluginPath(); Chris@1164: vector path; Chris@165: Chris@165: for (size_t i = 0; i < pluginPath.size(); ++i) { Chris@165: if (pluginPath[i].contains("/lib/")) { Chris@165: QString p(pluginPath[i]); Chris@165: path.push_back(p); Chris@165: p.replace("/lib/", "/share/"); Chris@165: path.push_back(p); Chris@165: } Chris@165: path.push_back(pluginPath[i]); Chris@165: } Chris@165: Chris@165: for (size_t i = 0; i < path.size(); ++i) { Chris@165: Chris@165: QDir dir(path[i], "*.cat"); Chris@165: Chris@690: // SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: directory " << path[i] << " has " << dir.count() << " .cat files" << endl; Chris@165: for (unsigned int j = 0; j < dir.count(); ++j) { Chris@165: Chris@165: QFile file(path[i] + "/" + dir[j]); Chris@165: Chris@690: // SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: about to open " << (path[i]+ "/" + dir[j]) << endl; Chris@165: Chris@165: if (file.open(QIODevice::ReadOnly)) { Chris@843: // cerr << "...opened" << endl; Chris@165: QTextStream stream(&file); Chris@165: QString line; Chris@165: Chris@165: while (!stream.atEnd()) { Chris@165: line = stream.readLine(); Chris@843: // cerr << "line is: \"" << line << "\"" << endl; Chris@165: QString id = PluginIdentifier::canonicalise Chris@165: (line.section("::", 0, 0)); Chris@165: QString cat = line.section("::", 1, 1); Chris@165: m_taxonomy[id] = cat; Chris@843: // cerr << "FeatureExtractionPluginFactory: set id \"" << id << "\" to cat \"" << cat << "\"" << endl; Chris@165: } Chris@165: } Chris@165: } Chris@165: } Chris@165: }