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@1164: SVDEBUG << "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@1165: FeatureExtractionPluginFactory::winnowPluginCandidates(vector candidates)
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@1165: QString warningMessage = "Failed to load plugins
Failed to load one or more plugin libraries:\n";
Chris@1165: for (int i = 0; i < bad.size(); ++i) {
Chris@1165: QString m;
Chris@1165: if (badStatuses[i] == PluginLoadFailedToLoadLibrary) {
Chris@1165: m = "Failed to load library";
Chris@1165: } else if (badStatuses[i] == PluginLoadFailedToFindDescriptor) {
Chris@1165: m = "Failed to query plugins from library after loading";
Chris@1165: } else if (badStatuses[i] == PluginLoadFailedElsewhere) {
Chris@1165: m = "Unknown failure";
Chris@1165: } else {
Chris@1165: m = "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: cerr << warningMessage; //!!! for now!
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@1165: vector candidates = winnowPluginCandidates(getPluginCandidateFiles());
Chris@1165:
Chris@1164: for (QString soname : candidates) {
Chris@66:
Chris@249: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
Chris@1164: SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: trying potential library " << soname << endl;
Chris@249: #endif
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@249: #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
Chris@1164: SVDEBUG << "FeatureExtractionPluginFactory::getPluginIdentifiers: It's a library all right, checking for descriptor" << endl;
Chris@249: #endif
Chris@249:
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@1164: SVDEBUG << "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@1164: SVDEBUG << "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@1164: SVDEBUG << "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@1140: cerr << "FeatureExtractionPluginFactory::instantiatePlugin: Wrong factory for plugin type " << type << endl;
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@690: // SVDEBUG << "FeatureExtractionPluginFactory::instantiatePlugin: Instantiated plugin " << label << " from library " << soname << ": descriptor " << descriptor << ", rv "<< rv << ", label " << rv->getName() << ", outputs " << rv->getOutputDescriptors().size() << endl;
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@690: // SVDEBUG << "unloading library " << handle << " for plugin " << plugin << endl;
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: }