Somewhat guesswork-laden fix for VAMP plugin assertion failures (#3897, #3878, #3893).
[ardour2.git] / libs / vamp-sdk / src / vamp-hostsdk / PluginLoader.cpp
blob96637105778582907d883028a14d519aa60c8e10
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
3 /*
4 Vamp
6 An API for audio analysis and feature extraction plugins.
8 Centre for Digital Music, Queen Mary, University of London.
9 Copyright 2006-2009 Chris Cannam and QMUL.
11 Permission is hereby granted, free of charge, to any person
12 obtaining a copy of this software and associated documentation
13 files (the "Software"), to deal in the Software without
14 restriction, including without limitation the rights to use, copy,
15 modify, merge, publish, distribute, sublicense, and/or sell copies
16 of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
19 The above copyright notice and this permission notice shall be
20 included in all copies or substantial portions of the Software.
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
26 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
27 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 Except as contained in this notice, the names of the Centre for
31 Digital Music; Queen Mary, University of London; and Chris Cannam
32 shall not be used in advertising or otherwise to promote the sale,
33 use or other dealings in this Software without prior written
34 authorization.
37 #include "vamp-hostsdk/PluginHostAdapter.h"
38 #include "vamp-hostsdk/PluginLoader.h"
39 #include "vamp-hostsdk/PluginInputDomainAdapter.h"
40 #include "vamp-hostsdk/PluginChannelAdapter.h"
41 #include "vamp-hostsdk/PluginBufferingAdapter.h"
43 #include <fstream>
44 #include <cctype> // tolower
46 #include <cstring>
48 #ifdef _WIN32
50 #include <windows.h>
51 #include <tchar.h>
52 #define PLUGIN_SUFFIX "dll"
54 #else /* ! _WIN32 */
56 #include <dirent.h>
57 #include <dlfcn.h>
59 #ifdef __APPLE__
60 #define PLUGIN_SUFFIX "dylib"
61 #else /* ! __APPLE__ */
62 #define PLUGIN_SUFFIX "so"
63 #endif /* ! __APPLE__ */
65 #endif /* ! _WIN32 */
67 using namespace std;
69 _VAMP_SDK_HOSTSPACE_BEGIN(PluginLoader.cpp)
71 namespace Vamp {
73 namespace HostExt {
75 class PluginLoader::Impl
77 public:
78 Impl();
79 virtual ~Impl();
81 PluginKeyList listPlugins();
83 Plugin *loadPlugin(PluginKey key,
84 float inputSampleRate,
85 int adapterFlags);
87 PluginKey composePluginKey(string libraryName, string identifier);
89 PluginCategoryHierarchy getPluginCategory(PluginKey key);
91 string getLibraryPathForPlugin(PluginKey key);
93 static void setInstanceToClean(PluginLoader *instance);
95 protected:
96 class PluginDeletionNotifyAdapter : public PluginWrapper {
97 public:
98 PluginDeletionNotifyAdapter(Plugin *plugin, Impl *loader);
99 virtual ~PluginDeletionNotifyAdapter();
100 protected:
101 Impl *m_loader;
104 class InstanceCleaner {
105 public:
106 InstanceCleaner() : m_instance(0) { }
107 ~InstanceCleaner() { delete m_instance; }
108 void setInstance(PluginLoader *instance) { m_instance = instance; }
109 protected:
110 PluginLoader *m_instance;
113 virtual void pluginDeleted(PluginDeletionNotifyAdapter *adapter);
115 map<PluginKey, string> m_pluginLibraryNameMap;
116 bool m_allPluginsEnumerated;
117 void enumeratePlugins(PluginKey forPlugin = "");
119 map<PluginKey, PluginCategoryHierarchy> m_taxonomy;
120 void generateTaxonomy();
122 map<Plugin *, void *> m_pluginLibraryHandleMap;
124 bool decomposePluginKey(PluginKey key,
125 string &libraryName, string &identifier);
127 void *loadLibrary(string path);
128 void unloadLibrary(void *handle);
129 void *lookupInLibrary(void *handle, const char *symbol);
131 string splicePath(string a, string b);
132 vector<string> listFiles(string dir, string ext);
134 static InstanceCleaner m_cleaner;
137 PluginLoader *
138 PluginLoader::m_instance = 0;
140 PluginLoader::Impl::InstanceCleaner
141 PluginLoader::Impl::m_cleaner;
143 PluginLoader::PluginLoader()
145 m_impl = new Impl();
148 PluginLoader::~PluginLoader()
150 delete m_impl;
153 PluginLoader *
154 PluginLoader::getInstance()
156 if (!m_instance) {
157 // The cleaner doesn't own the instance, because we leave the
158 // instance pointer in the base class for binary backwards
159 // compatibility reasons and to avoid waste
160 m_instance = new PluginLoader();
161 Impl::setInstanceToClean(m_instance);
163 return m_instance;
166 vector<PluginLoader::PluginKey>
167 PluginLoader::listPlugins()
169 return m_impl->listPlugins();
172 Plugin *
173 PluginLoader::loadPlugin(PluginKey key,
174 float inputSampleRate,
175 int adapterFlags)
177 return m_impl->loadPlugin(key, inputSampleRate, adapterFlags);
180 PluginLoader::PluginKey
181 PluginLoader::composePluginKey(string libraryName, string identifier)
183 return m_impl->composePluginKey(libraryName, identifier);
186 PluginLoader::PluginCategoryHierarchy
187 PluginLoader::getPluginCategory(PluginKey key)
189 return m_impl->getPluginCategory(key);
192 string
193 PluginLoader::getLibraryPathForPlugin(PluginKey key)
195 return m_impl->getLibraryPathForPlugin(key);
198 PluginLoader::Impl::Impl() :
199 m_allPluginsEnumerated(false)
203 PluginLoader::Impl::~Impl()
207 void
208 PluginLoader::Impl::setInstanceToClean(PluginLoader *instance)
210 m_cleaner.setInstance(instance);
213 vector<PluginLoader::PluginKey>
214 PluginLoader::Impl::listPlugins()
216 if (!m_allPluginsEnumerated) enumeratePlugins();
218 vector<PluginKey> plugins;
219 for (map<PluginKey, string>::iterator mi = m_pluginLibraryNameMap.begin();
220 mi != m_pluginLibraryNameMap.end(); ++mi) {
221 plugins.push_back(mi->first);
224 return plugins;
227 void
228 PluginLoader::Impl::enumeratePlugins(PluginKey forPlugin)
230 vector<string> path = PluginHostAdapter::getPluginPath();
232 string libraryName, identifier;
233 if (forPlugin != "") {
234 if (!decomposePluginKey(forPlugin, libraryName, identifier)) {
235 std::cerr << "WARNING: Vamp::HostExt::PluginLoader: Invalid plugin key \""
236 << forPlugin << "\" in enumerate" << std::endl;
237 return;
241 for (size_t i = 0; i < path.size(); ++i) {
243 vector<string> files = listFiles(path[i], PLUGIN_SUFFIX);
245 for (vector<string>::iterator fi = files.begin();
246 fi != files.end(); ++fi) {
248 if (libraryName != "") {
249 // libraryName is lowercased and lacking an extension,
250 // as it came from the plugin key
251 string temp = *fi;
252 for (size_t i = 0; i < temp.length(); ++i) {
253 temp[i] = tolower(temp[i]);
255 string::size_type pi = temp.find('.');
256 if (pi == string::npos) {
257 if (libraryName != temp) continue;
258 } else {
259 if (libraryName != temp.substr(0, pi)) continue;
263 string fullPath = path[i];
264 fullPath = splicePath(fullPath, *fi);
265 void *handle = loadLibrary(fullPath);
266 if (!handle) continue;
268 VampGetPluginDescriptorFunction fn =
269 (VampGetPluginDescriptorFunction)lookupInLibrary
270 (handle, "vampGetPluginDescriptor");
272 if (!fn) {
273 if (forPlugin != "") {
274 cerr << "Vamp::HostExt::PluginLoader: No vampGetPluginDescriptor function found in library \""
275 << fullPath << "\"" << endl;
277 unloadLibrary(handle);
278 continue;
281 int index = 0;
282 const VampPluginDescriptor *descriptor = 0;
283 bool found = false;
285 while ((descriptor = fn(VAMP_API_VERSION, index))) {
286 ++index;
287 if (identifier != "") {
288 if (descriptor->identifier != identifier) continue;
291 found = true;
292 PluginKey key = composePluginKey(*fi, descriptor->identifier);
294 if (m_pluginLibraryNameMap.find(key) ==
295 m_pluginLibraryNameMap.end()) {
296 m_pluginLibraryNameMap[key] = fullPath;
300 if (!found && forPlugin != "") {
301 cerr << "Vamp::HostExt::PluginLoader: Plugin \""
302 << identifier << "\" not found in library \""
303 << fullPath << "\"" << endl;
306 unloadLibrary(handle);
310 if (forPlugin == "") m_allPluginsEnumerated = true;
313 PluginLoader::PluginKey
314 PluginLoader::Impl::composePluginKey(string libraryName, string identifier)
316 string basename = libraryName;
318 string::size_type li = basename.rfind('/');
319 if (li != string::npos) basename = basename.substr(li + 1);
321 li = basename.find('.');
322 if (li != string::npos) basename = basename.substr(0, li);
324 for (size_t i = 0; i < basename.length(); ++i) {
325 basename[i] = tolower(basename[i]);
328 return basename + ":" + identifier;
331 bool
332 PluginLoader::Impl::decomposePluginKey(PluginKey key,
333 string &libraryName,
334 string &identifier)
336 string::size_type ki = key.find(':');
337 if (ki == string::npos) {
338 return false;
341 libraryName = key.substr(0, ki);
342 identifier = key.substr(ki + 1);
343 return true;
346 PluginLoader::PluginCategoryHierarchy
347 PluginLoader::Impl::getPluginCategory(PluginKey plugin)
349 if (m_taxonomy.empty()) generateTaxonomy();
350 if (m_taxonomy.find(plugin) == m_taxonomy.end()) {
351 return PluginCategoryHierarchy();
353 return m_taxonomy[plugin];
356 string
357 PluginLoader::Impl::getLibraryPathForPlugin(PluginKey plugin)
359 if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) {
360 if (m_allPluginsEnumerated) return "";
361 cerr << "plug " << plugin << " not found enumerate" << endl;
362 enumeratePlugins(plugin);
364 if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) {
365 cerr << "plug " << plugin << " not found enumerate" << endl;
366 return "";
368 cerr << "Did find plugin " << plugin << endl;
369 return m_pluginLibraryNameMap[plugin];
372 Plugin *
373 PluginLoader::Impl::loadPlugin(PluginKey key,
374 float inputSampleRate, int adapterFlags)
376 string libname, identifier;
377 if (!decomposePluginKey(key, libname, identifier)) {
378 std::cerr << "Vamp::HostExt::PluginLoader: Invalid plugin key \""
379 << key << "\" in loadPlugin" << std::endl;
380 return 0;
383 string fullPath = getLibraryPathForPlugin(key);
384 if (fullPath == "") {
385 std::cerr << "Vamp::HostExt::PluginLoader: No library found in Vamp path for plugin \"" << key << "\"" << std::endl;
386 return 0;
389 void *handle = loadLibrary(fullPath);
390 if (!handle) return 0;
392 VampGetPluginDescriptorFunction fn =
393 (VampGetPluginDescriptorFunction)lookupInLibrary
394 (handle, "vampGetPluginDescriptor");
396 if (!fn) {
397 cerr << "Vamp::HostExt::PluginLoader: No vampGetPluginDescriptor function found in library \""
398 << fullPath << "\"" << endl;
399 unloadLibrary(handle);
400 return 0;
403 int index = 0;
404 const VampPluginDescriptor *descriptor = 0;
406 while ((descriptor = fn(VAMP_API_VERSION, index))) {
408 if (string(descriptor->identifier) == identifier) {
410 Vamp::PluginHostAdapter *plugin =
411 new Vamp::PluginHostAdapter(descriptor, inputSampleRate);
413 Plugin *adapter = new PluginDeletionNotifyAdapter(plugin, this);
415 m_pluginLibraryHandleMap[adapter] = handle;
417 if (adapterFlags & ADAPT_BUFFER_SIZE) {
418 PluginBufferingAdapter* a = new PluginBufferingAdapter(adapter);
419 adapter = a;
422 if (adapterFlags & ADAPT_INPUT_DOMAIN) {
423 if (adapter->getInputDomain() == Plugin::FrequencyDomain) {
424 adapter = new PluginInputDomainAdapter(adapter);
428 if (adapterFlags & ADAPT_CHANNEL_COUNT) {
429 adapter = new PluginChannelAdapter(adapter);
432 return adapter;
435 ++index;
438 cerr << "Vamp::HostExt::PluginLoader: Plugin \""
439 << identifier << "\" not found in library \""
440 << fullPath << "\"" << endl;
442 return 0;
445 void
446 PluginLoader::Impl::generateTaxonomy()
448 // cerr << "PluginLoader::Impl::generateTaxonomy" << endl;
450 vector<string> path = PluginHostAdapter::getPluginPath();
451 string libfragment = "/lib/";
452 vector<string> catpath;
454 string suffix = "cat";
456 for (vector<string>::iterator i = path.begin();
457 i != path.end(); ++i) {
459 // It doesn't matter that we're using literal forward-slash in
460 // this bit, as it's only relevant if the path contains
461 // "/lib/", which is only meaningful and only plausible on
462 // systems with forward-slash delimiters
464 string dir = *i;
465 string::size_type li = dir.find(libfragment);
467 if (li != string::npos) {
468 catpath.push_back
469 (dir.substr(0, li)
470 + "/share/"
471 + dir.substr(li + libfragment.length()));
474 catpath.push_back(dir);
477 char buffer[1024];
479 for (vector<string>::iterator i = catpath.begin();
480 i != catpath.end(); ++i) {
482 vector<string> files = listFiles(*i, suffix);
484 for (vector<string>::iterator fi = files.begin();
485 fi != files.end(); ++fi) {
487 string filepath = splicePath(*i, *fi);
488 ifstream is(filepath.c_str(), ifstream::in | ifstream::binary);
490 if (is.fail()) {
491 // cerr << "failed to open: " << filepath << endl;
492 continue;
495 // cerr << "opened: " << filepath << endl;
497 while (!!is.getline(buffer, 1024)) {
499 string line(buffer);
501 // cerr << "line = " << line << endl;
503 string::size_type di = line.find("::");
504 if (di == string::npos) continue;
506 string id = line.substr(0, di);
507 string encodedCat = line.substr(di + 2);
509 if (id.substr(0, 5) != "vamp:") continue;
510 id = id.substr(5);
512 while (encodedCat.length() >= 1 &&
513 encodedCat[encodedCat.length()-1] == '\r') {
514 encodedCat = encodedCat.substr(0, encodedCat.length()-1);
517 // cerr << "id = " << id << ", cat = " << encodedCat << endl;
519 PluginCategoryHierarchy category;
520 string::size_type ai;
521 while ((ai = encodedCat.find(" > ")) != string::npos) {
522 category.push_back(encodedCat.substr(0, ai));
523 encodedCat = encodedCat.substr(ai + 3);
525 if (encodedCat != "") category.push_back(encodedCat);
527 m_taxonomy[id] = category;
533 void *
534 PluginLoader::Impl::loadLibrary(string path)
536 void *handle = 0;
537 #ifdef _WIN32
538 #ifdef UNICODE
539 int len = path.length(); // cannot be more wchars than length in bytes of utf8 string
540 wchar_t *buffer = new wchar_t[len];
541 int rv = MultiByteToWideChar(CP_UTF8, 0, path.c_str(), len, buffer, len);
542 if (rv <= 0) {
543 cerr << "Vamp::HostExt::PluginLoader: Unable to convert library path \""
544 << path << "\" to wide characters " << endl;
545 delete[] buffer;
546 return handle;
548 handle = LoadLibrary(buffer);
549 delete[] buffer;
550 #else
551 handle = LoadLibrary(path.c_str());
552 #endif
553 if (!handle) {
554 cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
555 << path << "\"" << endl;
557 #else
558 handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
559 if (!handle) {
560 cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
561 << path << "\": " << dlerror() << endl;
563 #endif
564 return handle;
567 void
568 PluginLoader::Impl::unloadLibrary(void *handle)
570 #ifdef _WIN32
571 FreeLibrary((HINSTANCE)handle);
572 #else
573 dlclose(handle);
574 #endif
577 void *
578 PluginLoader::Impl::lookupInLibrary(void *handle, const char *symbol)
580 #ifdef _WIN32
581 return (void *)GetProcAddress((HINSTANCE)handle, symbol);
582 #else
583 return (void *)dlsym(handle, symbol);
584 #endif
587 string
588 PluginLoader::Impl::splicePath(string a, string b)
590 #ifdef _WIN32
591 return a + "\\" + b;
592 #else
593 return a + "/" + b;
594 #endif
597 vector<string>
598 PluginLoader::Impl::listFiles(string dir, string extension)
600 vector<string> files;
602 #ifdef _WIN32
603 string expression = dir + "\\*." + extension;
604 #ifdef UNICODE
605 int len = expression.length(); // cannot be more wchars than length in bytes of utf8 string
606 wchar_t *buffer = new wchar_t[len];
607 int rv = MultiByteToWideChar(CP_UTF8, 0, expression.c_str(), len, buffer, len);
608 if (rv <= 0) {
609 cerr << "Vamp::HostExt::PluginLoader: Unable to convert wildcard path \""
610 << expression << "\" to wide characters" << endl;
611 delete[] buffer;
612 return files;
614 WIN32_FIND_DATA data;
615 HANDLE fh = FindFirstFile(buffer, &data);
616 if (fh == INVALID_HANDLE_VALUE) {
617 delete[] buffer;
618 return files;
621 bool ok = true;
622 while (ok) {
623 wchar_t *fn = data.cFileName;
624 int wlen = wcslen(fn);
625 int maxlen = wlen * 6;
626 char *conv = new char[maxlen];
627 int rv = WideCharToMultiByte(CP_UTF8, 0, fn, wlen, conv, maxlen, 0, 0);
628 if (rv > 0) {
629 files.push_back(conv);
631 delete[] conv;
632 ok = FindNextFile(fh, &data);
635 FindClose(fh);
636 delete[] buffer;
637 #else
638 WIN32_FIND_DATA data;
639 HANDLE fh = FindFirstFile(expression.c_str(), &data);
640 if (fh == INVALID_HANDLE_VALUE) return files;
642 bool ok = true;
643 while (ok) {
644 files.push_back(data.cFileName);
645 ok = FindNextFile(fh, &data);
648 FindClose(fh);
649 #endif
650 #else
652 size_t extlen = extension.length();
653 DIR *d = opendir(dir.c_str());
654 if (!d) return files;
656 struct dirent *e = 0;
657 while ((e = readdir(d))) {
659 if (!e->d_name) continue;
661 size_t len = strlen(e->d_name);
662 if (len < extlen + 2 ||
663 e->d_name + len - extlen - 1 != "." + extension) {
664 continue;
667 files.push_back(e->d_name);
670 closedir(d);
671 #endif
673 return files;
676 void
677 PluginLoader::Impl::pluginDeleted(PluginDeletionNotifyAdapter *adapter)
679 void *handle = m_pluginLibraryHandleMap[adapter];
680 if (handle) unloadLibrary(handle);
681 m_pluginLibraryHandleMap.erase(adapter);
684 PluginLoader::Impl::PluginDeletionNotifyAdapter::PluginDeletionNotifyAdapter(Plugin *plugin,
685 Impl *loader) :
686 PluginWrapper(plugin),
687 m_loader(loader)
691 PluginLoader::Impl::PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter()
693 // We need to delete the plugin before calling pluginDeleted, as
694 // the delete call may require calling through to the descriptor
695 // (for e.g. cleanup) but pluginDeleted may unload the required
696 // library for the call. To prevent a double deletion when our
697 // parent's destructor runs (after this one), be sure to set
698 // m_plugin to 0 after deletion.
699 delete m_plugin;
700 m_plugin = 0;
702 if (m_loader) m_loader->pluginDeleted(this);
709 _VAMP_SDK_HOSTSPACE_END(PluginLoader.cpp)