1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2
3 /*
4 Sonic Visualiser
5 An audio file viewer and annotation editor.
6 Centre for Digital Music, Queen Mary, University of London.
7 This file copyright 2006-2016 Chris Cannam and QMUL.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version. See the file
13 COPYING included with this distribution for more information.
14 */
15
16 #include "NativeVampPluginFactory.h"
17 #include "PluginIdentifier.h"
18
19 #include <vamp-hostsdk/PluginHostAdapter.h>
20 #include <vamp-hostsdk/PluginWrapper.h>
21
22 #include "system/System.h"
23
24 #include "PluginScan.h"
25
26 #include <QDir>
27 #include <QFile>
28 #include <QFileInfo>
29 #include <QTextStream>
30
31 #include <iostream>
32
33 #include "base/Profiler.h"
34
35 #include <QMutex>
36 #include <QMutexLocker>
37
38 using namespace std;
39
40 //#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1
41
42 class PluginDeletionNotifyAdapter : public Vamp::HostExt::PluginWrapper {
43 public:
PluginDeletionNotifyAdapter(Vamp::Plugin * plugin,NativeVampPluginFactory * factory)44 PluginDeletionNotifyAdapter(Vamp::Plugin *plugin,
45 NativeVampPluginFactory *factory) :
46 PluginWrapper(plugin), m_factory(factory) { }
47 ~PluginDeletionNotifyAdapter() override;
48 protected:
49 NativeVampPluginFactory *m_factory;
50 };
51
~PluginDeletionNotifyAdapter()52 PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter()
53 {
54 // see notes in vamp-sdk/hostext/PluginLoader.cpp from which this is drawn
55 Vamp::Plugin *p = m_plugin;
56
57 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
58 SVCERR << "PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter("
59 << this << " for plugin " << p << ")" << endl;
60 #endif
61
62 delete m_plugin;
63 m_plugin = nullptr;
64 // acceptable use after free here, as pluginDeleted uses p only as
65 // pointer key and does not deref it
66 if (m_factory) m_factory->pluginDeleted(p);
67 }
68
69 vector<QString>
getPluginPath()70 NativeVampPluginFactory::getPluginPath()
71 {
72 if (!m_pluginPath.empty()) return m_pluginPath;
73
74 vector<string> p = Vamp::PluginHostAdapter::getPluginPath();
75 for (size_t i = 0; i < p.size(); ++i) m_pluginPath.push_back(p[i].c_str());
76 return m_pluginPath;
77 }
78
79 static
80 QList<PluginScan::Candidate>
getCandidateLibraries()81 getCandidateLibraries()
82 {
83 #ifdef HAVE_PLUGIN_CHECKER_HELPER
84 return PluginScan::getInstance()->getCandidateLibrariesFor
85 (PluginScan::VampPlugin);
86 #else
87 auto path = Vamp::PluginHostAdapter::getPluginPath();
88 QList<PluginScan::Candidate> candidates;
89 for (string dirname: path) {
90 SVDEBUG << "NativeVampPluginFactory: scanning directory myself: "
91 << dirname << endl;
92 #if defined(_WIN32)
93 #define PLUGIN_GLOB "*.dll"
94 #elif defined(__APPLE__)
95 #define PLUGIN_GLOB "*.dylib *.so"
96 #else
97 #define PLUGIN_GLOB "*.so"
98 #endif
99 QDir dir(dirname.c_str(), PLUGIN_GLOB,
100 QDir::Name | QDir::IgnoreCase,
101 QDir::Files | QDir::Readable);
102
103 for (unsigned int i = 0; i < dir.count(); ++i) {
104 QString libpath = dir.filePath(dir[i]);
105 candidates.push_back({ libpath, "" });
106 }
107 }
108
109 return candidates;
110 #endif
111 }
112
113 vector<QString>
getPluginIdentifiers(QString &)114 NativeVampPluginFactory::getPluginIdentifiers(QString &)
115 {
116 Profiler profiler("NativeVampPluginFactory::getPluginIdentifiers");
117
118 QMutexLocker locker(&m_mutex);
119
120 if (!m_identifiers.empty()) {
121 return m_identifiers;
122 }
123
124 auto candidates = getCandidateLibraries();
125
126 SVDEBUG << "INFO: Have " << candidates.size() << " candidate Vamp plugin libraries" << endl;
127
128 for (auto candidate : candidates) {
129
130 QString libpath = candidate.libraryPath;
131
132 SVDEBUG << "INFO: Considering candidate Vamp plugin library " << libpath << endl;
133
134 void *libraryHandle = DLOPEN(libpath, RTLD_LAZY | RTLD_LOCAL);
135
136 if (!libraryHandle) {
137 SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to load library " << libpath << ": " << DLERROR() << endl;
138 continue;
139 }
140
141 VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction)
142 DLSYM(libraryHandle, "vampGetPluginDescriptor");
143
144 if (!fn) {
145 SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: No descriptor function in " << libpath << endl;
146 if (DLCLOSE(libraryHandle) != 0) {
147 SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << libpath << endl;
148 }
149 continue;
150 }
151
152 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
153 SVCERR << "NativeVampPluginFactory::getPluginIdentifiers: Vamp descriptor found" << endl;
154 #endif
155
156 const VampPluginDescriptor *descriptor = nullptr;
157 int index = 0;
158
159 map<string, int> known;
160 bool ok = true;
161
162 while ((descriptor = fn(VAMP_API_VERSION, index))) {
163
164 if (known.find(descriptor->identifier) != known.end()) {
165 SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Plugin library "
166 << libpath
167 << " returns the same plugin identifier \""
168 << descriptor->identifier << "\" at indices "
169 << known[descriptor->identifier] << " and "
170 << index << endl;
171 SVDEBUG << "NativeVampPluginFactory::getPluginIdentifiers: Avoiding this library (obsolete API?)" << endl;
172 ok = false;
173 break;
174 } else {
175 known[descriptor->identifier] = index;
176 }
177
178 ++index;
179 }
180
181 if (ok) {
182
183 index = 0;
184
185 while ((descriptor = fn(VAMP_API_VERSION, index))) {
186
187 QString id = PluginIdentifier::createIdentifier
188 ("vamp", libpath, descriptor->identifier);
189 m_identifiers.push_back(id);
190 m_libraries[id] = libpath;
191 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
192 SVCERR << "NativeVampPluginFactory::getPluginIdentifiers: Found plugin id " << id << " at index " << index << endl;
193 #endif
194 ++index;
195 }
196 }
197
198 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
199 SVCERR << "NativeVampPluginFactory::getPluginIdentifiers: unloading library " << libraryHandle << endl;
200 #endif
201
202 if (DLCLOSE(libraryHandle) != 0) {
203 SVDEBUG << "WARNING: NativeVampPluginFactory::getPluginIdentifiers: Failed to unload library " << libpath << endl;
204 }
205 }
206
207 generateTaxonomy();
208
209 // Plugins can change the locale, revert it to default.
210 RestoreStartupLocale();
211
212 return m_identifiers;
213 }
214
215 QString
findPluginFile(QString soname,QString inDir)216 NativeVampPluginFactory::findPluginFile(QString soname, QString inDir)
217 {
218 QString file = "";
219
220 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
221 SVCERR << "NativeVampPluginFactory::findPluginFile(\""
222 << soname << "\", \"" << inDir << "\")"
223 << endl;
224 #endif
225
226 if (inDir != "") {
227
228 QDir dir(inDir, PLUGIN_GLOB,
229 QDir::Name | QDir::IgnoreCase,
230 QDir::Files | QDir::Readable);
231 if (!dir.exists()) return "";
232
233 file = dir.filePath(QFileInfo(soname).fileName());
234
235 if (QFileInfo(file).exists() && QFileInfo(file).isFile()) {
236
237 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
238 SVCERR << "NativeVampPluginFactory::findPluginFile: "
239 << "found trivially at " << file << endl;
240 #endif
241
242 return file;
243 }
244
245 for (unsigned int j = 0; j < dir.count(); ++j) {
246 file = dir.filePath(dir[j]);
247 if (QFileInfo(file).baseName() == QFileInfo(soname).baseName()) {
248
249 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
250 SVCERR << "NativeVampPluginFactory::findPluginFile: "
251 << "found \"" << soname << "\" at " << file << endl;
252 #endif
253
254 return file;
255 }
256 }
257
258 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
259 SVCERR << "NativeVampPluginFactory::findPluginFile (with dir): "
260 << "not found" << endl;
261 #endif
262
263 return "";
264
265 } else {
266
267 QFileInfo fi(soname);
268
269 if (fi.isAbsolute() && fi.exists() && fi.isFile()) {
270 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
271 SVCERR << "NativeVampPluginFactory::findPluginFile: "
272 << "found trivially at " << soname << endl;
273 #endif
274 return soname;
275 }
276
277 if (fi.isAbsolute() && fi.absolutePath() != "") {
278 file = findPluginFile(soname, fi.absolutePath());
279 if (file != "") return file;
280 }
281
282 vector<QString> path = getPluginPath();
283 for (vector<QString>::iterator i = path.begin();
284 i != path.end(); ++i) {
285 if (*i != "") {
286 file = findPluginFile(soname, *i);
287 if (file != "") return file;
288 }
289 }
290
291 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
292 SVCERR << "NativeVampPluginFactory::findPluginFile: "
293 << "not found" << endl;
294 #endif
295
296 return "";
297 }
298 }
299
300 Vamp::Plugin *
instantiatePlugin(QString identifier,sv_samplerate_t inputSampleRate)301 NativeVampPluginFactory::instantiatePlugin(QString identifier,
302 sv_samplerate_t inputSampleRate)
303 {
304 Profiler profiler("NativeVampPluginFactory::instantiatePlugin");
305
306 Vamp::Plugin *rv = nullptr;
307 Vamp::PluginHostAdapter *plugin = nullptr;
308
309 const VampPluginDescriptor *descriptor = nullptr;
310 int index = 0;
311
312 QString type, soname, label;
313 PluginIdentifier::parseIdentifier(identifier, type, soname, label);
314 if (type != "vamp") {
315 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
316 SVCERR << "NativeVampPluginFactory::instantiatePlugin: Wrong factory for plugin type " << type << endl;
317 #endif
318 return nullptr;
319 }
320
321 QString found = findPluginFile(soname);
322
323 if (found == "") {
324 SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Failed to find library file " << soname << endl;
325 return nullptr;
326 } else if (found != soname) {
327
328 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
329 SVCERR << "NativeVampPluginFactory::instantiatePlugin: Given library name was " << soname << ", found at " << found << endl;
330 SVCERR << soname << " -> " << found << endl;
331 #endif
332
333 }
334
335 soname = found;
336
337 void *libraryHandle = DLOPEN(soname, RTLD_LAZY | RTLD_LOCAL);
338
339 if (!libraryHandle) {
340 SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Failed to load library " << soname << ": " << DLERROR() << endl;
341 return nullptr;
342 }
343
344 VampGetPluginDescriptorFunction fn = (VampGetPluginDescriptorFunction)
345 DLSYM(libraryHandle, "vampGetPluginDescriptor");
346
347 if (!fn) {
348 SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: No descriptor function in " << soname << endl;
349 goto done;
350 }
351
352 while ((descriptor = fn(VAMP_API_VERSION, index))) {
353 if (label == descriptor->identifier) break;
354 ++index;
355 }
356
357 if (!descriptor) {
358 SVDEBUG << "NativeVampPluginFactory::instantiatePlugin: Failed to find plugin \"" << label << "\" in library " << soname << endl;
359 goto done;
360 }
361
362 plugin = new Vamp::PluginHostAdapter(descriptor, float(inputSampleRate));
363
364 if (plugin) {
365 m_handleMap[plugin] = libraryHandle;
366 rv = new PluginDeletionNotifyAdapter(plugin, this);
367 }
368
369 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
370 if (rv) {
371 SVCERR << "NativeVampPluginFactory::instantiatePlugin: Instantiated plugin " << label << " from library " << soname << ": descriptor " << descriptor << ", rv "<< rv << ", label " << rv->getName() << ", outputs " << rv->getOutputDescriptors().size() << endl;
372 }
373 #endif
374
375 done:
376 if (!rv) {
377 SVCERR << "NativeVampPluginFactory::instantiatePlugin: Failed to construct plugin" << endl;
378 if (DLCLOSE(libraryHandle) != 0) {
379 SVDEBUG << "WARNING: NativeVampPluginFactory::instantiatePlugin: Failed to unload library " << soname << endl;
380 }
381 }
382
383 return rv;
384 }
385
386 void
pluginDeleted(Vamp::Plugin * plugin)387 NativeVampPluginFactory::pluginDeleted(Vamp::Plugin *plugin)
388 {
389 void *handle = m_handleMap[plugin];
390 if (!handle) return;
391
392 m_handleMap.erase(plugin);
393
394 #ifdef DEBUG_PLUGIN_SCAN_AND_INSTANTIATE
395 SVCERR << "NativeVampPluginFactory::pluginDeleted: Removed from handle map, which now has " << m_handleMap.size() << " entries" << endl;
396 #endif
397
398 for (auto h: m_handleMap) {
399 if (h.second == handle) {
400 // still in use
401 SVDEBUG << "NativeVampPluginFactory::pluginDeleted: Not unloading library " << handle << " as other plugins are still loaded from it" << endl;
402 return;
403 }
404 }
405
406 SVDEBUG << "NativeVampPluginFactory::pluginDeleted: Unloading library " << handle << " after last plugin from this library " << plugin << " was deleted" << endl;
407 DLCLOSE(handle);
408 }
409
410 QString
getPluginCategory(QString identifier)411 NativeVampPluginFactory::getPluginCategory(QString identifier)
412 {
413 return m_taxonomy[identifier];
414 }
415
416 QString
getPluginLibraryPath(QString identifier)417 NativeVampPluginFactory::getPluginLibraryPath(QString identifier)
418 {
419 return m_libraries[identifier];
420 }
421
422 void
generateTaxonomy()423 NativeVampPluginFactory::generateTaxonomy()
424 {
425 vector<QString> pluginPath = getPluginPath();
426 vector<QString> path;
427
428 for (size_t i = 0; i < pluginPath.size(); ++i) {
429 if (pluginPath[i].contains("/lib/")) {
430 QString p(pluginPath[i]);
431 path.push_back(p);
432 p.replace("/lib/", "/share/");
433 path.push_back(p);
434 }
435 path.push_back(pluginPath[i]);
436 }
437
438 for (size_t i = 0; i < path.size(); ++i) {
439
440 QDir dir(path[i], "*.cat");
441
442 // SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: directory " << path[i] << " has " << dir.count() << " .cat files" << endl;
443 for (unsigned int j = 0; j < dir.count(); ++j) {
444
445 QFile file(path[i] + "/" + dir[j]);
446
447 // SVDEBUG << "LADSPAPluginFactory::generateFallbackCategories: about to open " << (path[i]+ "/" + dir[j]) << endl;
448
449 if (file.open(QIODevice::ReadOnly)) {
450 QTextStream stream(&file);
451 QString line;
452
453 while (!stream.atEnd()) {
454 line = stream.readLine();
455 QString id = PluginIdentifier::canonicalise
456 (line.section("::", 0, 0));
457 QString cat = line.section("::", 1, 1);
458 m_taxonomy[id] = cat;
459 }
460 }
461 }
462 }
463 }
464
465 piper_vamp::PluginStaticData
getPluginStaticData(QString identifier)466 NativeVampPluginFactory::getPluginStaticData(QString identifier)
467 {
468 QMutexLocker locker(&m_mutex);
469
470 if (m_pluginData.find(identifier) != m_pluginData.end()) {
471 return m_pluginData[identifier];
472 }
473
474 QString type, soname, label;
475 PluginIdentifier::parseIdentifier(identifier, type, soname, label);
476 std::string pluginKey = (soname + ":" + label).toStdString();
477
478 std::vector<std::string> catlist;
479 for (auto s: getPluginCategory(identifier).split(" > ")) {
480 catlist.push_back(s.toStdString());
481 }
482
483 Vamp::Plugin *p = instantiatePlugin(identifier, 44100);
484 if (!p) return {};
485
486 auto psd = piper_vamp::PluginStaticData::fromPlugin(pluginKey,
487 catlist,
488 p);
489
490 delete p;
491
492 m_pluginData[identifier] = psd;
493 return psd;
494 }
495
496