Mercurial > hg > vamp-plugin-sdk
comparison src/vamp-hostsdk/PluginLoader.cpp @ 233:521734d2b498 distinct-libraries
* Flatten directory tree a bit, update doxygen
author | cannam |
---|---|
date | Fri, 07 Nov 2008 15:28:33 +0000 |
parents | |
children | 4454843ff384 |
comparison
equal
deleted
inserted
replaced
232:71ea10a3cbe7 | 233:521734d2b498 |
---|---|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | |
2 | |
3 /* | |
4 Vamp | |
5 | |
6 An API for audio analysis and feature extraction plugins. | |
7 | |
8 Centre for Digital Music, Queen Mary, University of London. | |
9 Copyright 2006-2007 Chris Cannam and QMUL. | |
10 | |
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: | |
18 | |
19 The above copyright notice and this permission notice shall be | |
20 included in all copies or substantial portions of the Software. | |
21 | |
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. | |
29 | |
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. | |
35 */ | |
36 | |
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> | |
42 | |
43 #include <fstream> | |
44 #include <cctype> // tolower | |
45 | |
46 #include <cstring> | |
47 | |
48 #ifdef _WIN32 | |
49 | |
50 #include <windows.h> | |
51 #include <tchar.h> | |
52 #define PLUGIN_SUFFIX "dll" | |
53 | |
54 #else /* ! _WIN32 */ | |
55 | |
56 #include <dirent.h> | |
57 #include <dlfcn.h> | |
58 | |
59 #ifdef __APPLE__ | |
60 #define PLUGIN_SUFFIX "dylib" | |
61 #else /* ! __APPLE__ */ | |
62 #define PLUGIN_SUFFIX "so" | |
63 #endif /* ! __APPLE__ */ | |
64 | |
65 #endif /* ! _WIN32 */ | |
66 | |
67 using namespace std; | |
68 | |
69 namespace Vamp { | |
70 | |
71 namespace HostExt { | |
72 | |
73 class PluginLoader::Impl | |
74 { | |
75 public: | |
76 Impl(); | |
77 virtual ~Impl(); | |
78 | |
79 PluginKeyList listPlugins(); | |
80 | |
81 Plugin *loadPlugin(PluginKey key, | |
82 float inputSampleRate, | |
83 int adapterFlags); | |
84 | |
85 PluginKey composePluginKey(string libraryName, string identifier); | |
86 | |
87 PluginCategoryHierarchy getPluginCategory(PluginKey key); | |
88 | |
89 string getLibraryPathForPlugin(PluginKey key); | |
90 | |
91 static void setInstanceToClean(PluginLoader *instance); | |
92 | |
93 protected: | |
94 class PluginDeletionNotifyAdapter : public PluginWrapper { | |
95 public: | |
96 PluginDeletionNotifyAdapter(Plugin *plugin, Impl *loader); | |
97 virtual ~PluginDeletionNotifyAdapter(); | |
98 protected: | |
99 Impl *m_loader; | |
100 }; | |
101 | |
102 class InstanceCleaner { | |
103 public: | |
104 InstanceCleaner() : m_instance(0) { } | |
105 ~InstanceCleaner() { delete m_instance; } | |
106 void setInstance(PluginLoader *instance) { m_instance = instance; } | |
107 protected: | |
108 PluginLoader *m_instance; | |
109 }; | |
110 | |
111 virtual void pluginDeleted(PluginDeletionNotifyAdapter *adapter); | |
112 | |
113 map<PluginKey, string> m_pluginLibraryNameMap; | |
114 bool m_allPluginsEnumerated; | |
115 void enumeratePlugins(PluginKey forPlugin = ""); | |
116 | |
117 map<PluginKey, PluginCategoryHierarchy> m_taxonomy; | |
118 void generateTaxonomy(); | |
119 | |
120 map<Plugin *, void *> m_pluginLibraryHandleMap; | |
121 | |
122 bool decomposePluginKey(PluginKey key, | |
123 string &libraryName, string &identifier); | |
124 | |
125 void *loadLibrary(string path); | |
126 void unloadLibrary(void *handle); | |
127 void *lookupInLibrary(void *handle, const char *symbol); | |
128 | |
129 string splicePath(string a, string b); | |
130 vector<string> listFiles(string dir, string ext); | |
131 | |
132 static InstanceCleaner m_cleaner; | |
133 }; | |
134 | |
135 PluginLoader * | |
136 PluginLoader::m_instance = 0; | |
137 | |
138 PluginLoader::Impl::InstanceCleaner | |
139 PluginLoader::Impl::m_cleaner; | |
140 | |
141 PluginLoader::PluginLoader() | |
142 { | |
143 m_impl = new Impl(); | |
144 } | |
145 | |
146 PluginLoader::~PluginLoader() | |
147 { | |
148 delete m_impl; | |
149 } | |
150 | |
151 PluginLoader * | |
152 PluginLoader::getInstance() | |
153 { | |
154 if (!m_instance) { | |
155 // The cleaner doesn't own the instance, because we leave the | |
156 // instance pointer in the base class for binary backwards | |
157 // compatibility reasons and to avoid waste | |
158 m_instance = new PluginLoader(); | |
159 Impl::setInstanceToClean(m_instance); | |
160 } | |
161 return m_instance; | |
162 } | |
163 | |
164 vector<PluginLoader::PluginKey> | |
165 PluginLoader::listPlugins() | |
166 { | |
167 return m_impl->listPlugins(); | |
168 } | |
169 | |
170 Plugin * | |
171 PluginLoader::loadPlugin(PluginKey key, | |
172 float inputSampleRate, | |
173 int adapterFlags) | |
174 { | |
175 return m_impl->loadPlugin(key, inputSampleRate, adapterFlags); | |
176 } | |
177 | |
178 PluginLoader::PluginKey | |
179 PluginLoader::composePluginKey(string libraryName, string identifier) | |
180 { | |
181 return m_impl->composePluginKey(libraryName, identifier); | |
182 } | |
183 | |
184 PluginLoader::PluginCategoryHierarchy | |
185 PluginLoader::getPluginCategory(PluginKey key) | |
186 { | |
187 return m_impl->getPluginCategory(key); | |
188 } | |
189 | |
190 string | |
191 PluginLoader::getLibraryPathForPlugin(PluginKey key) | |
192 { | |
193 return m_impl->getLibraryPathForPlugin(key); | |
194 } | |
195 | |
196 PluginLoader::Impl::Impl() : | |
197 m_allPluginsEnumerated(false) | |
198 { | |
199 } | |
200 | |
201 PluginLoader::Impl::~Impl() | |
202 { | |
203 } | |
204 | |
205 void | |
206 PluginLoader::Impl::setInstanceToClean(PluginLoader *instance) | |
207 { | |
208 m_cleaner.setInstance(instance); | |
209 } | |
210 | |
211 vector<PluginLoader::PluginKey> | |
212 PluginLoader::Impl::listPlugins() | |
213 { | |
214 if (!m_allPluginsEnumerated) enumeratePlugins(); | |
215 | |
216 vector<PluginKey> plugins; | |
217 for (map<PluginKey, string>::iterator mi = m_pluginLibraryNameMap.begin(); | |
218 mi != m_pluginLibraryNameMap.end(); ++mi) { | |
219 plugins.push_back(mi->first); | |
220 } | |
221 | |
222 return plugins; | |
223 } | |
224 | |
225 void | |
226 PluginLoader::Impl::enumeratePlugins(PluginKey forPlugin) | |
227 { | |
228 vector<string> path = PluginHostAdapter::getPluginPath(); | |
229 | |
230 string libraryName, identifier; | |
231 if (forPlugin != "") { | |
232 if (!decomposePluginKey(forPlugin, libraryName, identifier)) { | |
233 std::cerr << "WARNING: Vamp::HostExt::PluginLoader: Invalid plugin key \"" | |
234 << forPlugin << "\" in enumerate" << std::endl; | |
235 return; | |
236 } | |
237 } | |
238 | |
239 for (size_t i = 0; i < path.size(); ++i) { | |
240 | |
241 vector<string> files = listFiles(path[i], PLUGIN_SUFFIX); | |
242 | |
243 for (vector<string>::iterator fi = files.begin(); | |
244 fi != files.end(); ++fi) { | |
245 | |
246 if (libraryName != "") { | |
247 // libraryName is lowercased and lacking an extension, | |
248 // as it came from the plugin key | |
249 string temp = *fi; | |
250 for (size_t i = 0; i < temp.length(); ++i) { | |
251 temp[i] = tolower(temp[i]); | |
252 } | |
253 string::size_type pi = temp.find('.'); | |
254 if (pi == string::npos) { | |
255 if (libraryName != temp) continue; | |
256 } else { | |
257 if (libraryName != temp.substr(0, pi)) continue; | |
258 } | |
259 } | |
260 | |
261 string fullPath = path[i]; | |
262 fullPath = splicePath(fullPath, *fi); | |
263 void *handle = loadLibrary(fullPath); | |
264 if (!handle) continue; | |
265 | |
266 VampGetPluginDescriptorFunction fn = | |
267 (VampGetPluginDescriptorFunction)lookupInLibrary | |
268 (handle, "vampGetPluginDescriptor"); | |
269 | |
270 if (!fn) { | |
271 unloadLibrary(handle); | |
272 continue; | |
273 } | |
274 | |
275 int index = 0; | |
276 const VampPluginDescriptor *descriptor = 0; | |
277 | |
278 while ((descriptor = fn(VAMP_API_VERSION, index))) { | |
279 ++index; | |
280 if (identifier != "") { | |
281 if (descriptor->identifier != identifier) continue; | |
282 } | |
283 PluginKey key = composePluginKey(*fi, descriptor->identifier); | |
284 // std::cerr << "enumerate: " << key << " (path: " << fullPath << ")" << std::endl; | |
285 if (m_pluginLibraryNameMap.find(key) == | |
286 m_pluginLibraryNameMap.end()) { | |
287 m_pluginLibraryNameMap[key] = fullPath; | |
288 } | |
289 } | |
290 | |
291 unloadLibrary(handle); | |
292 } | |
293 } | |
294 | |
295 if (forPlugin == "") m_allPluginsEnumerated = true; | |
296 } | |
297 | |
298 PluginLoader::PluginKey | |
299 PluginLoader::Impl::composePluginKey(string libraryName, string identifier) | |
300 { | |
301 string basename = libraryName; | |
302 | |
303 string::size_type li = basename.rfind('/'); | |
304 if (li != string::npos) basename = basename.substr(li + 1); | |
305 | |
306 li = basename.find('.'); | |
307 if (li != string::npos) basename = basename.substr(0, li); | |
308 | |
309 for (size_t i = 0; i < basename.length(); ++i) { | |
310 basename[i] = tolower(basename[i]); | |
311 } | |
312 | |
313 return basename + ":" + identifier; | |
314 } | |
315 | |
316 bool | |
317 PluginLoader::Impl::decomposePluginKey(PluginKey key, | |
318 string &libraryName, | |
319 string &identifier) | |
320 { | |
321 string::size_type ki = key.find(':'); | |
322 if (ki == string::npos) { | |
323 return false; | |
324 } | |
325 | |
326 libraryName = key.substr(0, ki); | |
327 identifier = key.substr(ki + 1); | |
328 return true; | |
329 } | |
330 | |
331 PluginLoader::PluginCategoryHierarchy | |
332 PluginLoader::Impl::getPluginCategory(PluginKey plugin) | |
333 { | |
334 if (m_taxonomy.empty()) generateTaxonomy(); | |
335 if (m_taxonomy.find(plugin) == m_taxonomy.end()) { | |
336 return PluginCategoryHierarchy(); | |
337 } | |
338 return m_taxonomy[plugin]; | |
339 } | |
340 | |
341 string | |
342 PluginLoader::Impl::getLibraryPathForPlugin(PluginKey plugin) | |
343 { | |
344 if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) { | |
345 if (m_allPluginsEnumerated) return ""; | |
346 enumeratePlugins(plugin); | |
347 } | |
348 if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) { | |
349 return ""; | |
350 } | |
351 return m_pluginLibraryNameMap[plugin]; | |
352 } | |
353 | |
354 Plugin * | |
355 PluginLoader::Impl::loadPlugin(PluginKey key, | |
356 float inputSampleRate, int adapterFlags) | |
357 { | |
358 string libname, identifier; | |
359 if (!decomposePluginKey(key, libname, identifier)) { | |
360 std::cerr << "Vamp::HostExt::PluginLoader: Invalid plugin key \"" | |
361 << key << "\" in loadPlugin" << std::endl; | |
362 return 0; | |
363 } | |
364 | |
365 string fullPath = getLibraryPathForPlugin(key); | |
366 if (fullPath == "") return 0; | |
367 | |
368 void *handle = loadLibrary(fullPath); | |
369 if (!handle) return 0; | |
370 | |
371 VampGetPluginDescriptorFunction fn = | |
372 (VampGetPluginDescriptorFunction)lookupInLibrary | |
373 (handle, "vampGetPluginDescriptor"); | |
374 | |
375 if (!fn) { | |
376 unloadLibrary(handle); | |
377 return 0; | |
378 } | |
379 | |
380 int index = 0; | |
381 const VampPluginDescriptor *descriptor = 0; | |
382 | |
383 while ((descriptor = fn(VAMP_API_VERSION, index))) { | |
384 | |
385 if (string(descriptor->identifier) == identifier) { | |
386 | |
387 Vamp::PluginHostAdapter *plugin = | |
388 new Vamp::PluginHostAdapter(descriptor, inputSampleRate); | |
389 | |
390 Plugin *adapter = new PluginDeletionNotifyAdapter(plugin, this); | |
391 | |
392 m_pluginLibraryHandleMap[adapter] = handle; | |
393 | |
394 if (adapterFlags & ADAPT_INPUT_DOMAIN) { | |
395 if (adapter->getInputDomain() == Plugin::FrequencyDomain) { | |
396 adapter = new PluginInputDomainAdapter(adapter); | |
397 } | |
398 } | |
399 | |
400 if (adapterFlags & ADAPT_BUFFER_SIZE) { | |
401 adapter = new PluginBufferingAdapter(adapter); | |
402 } | |
403 | |
404 if (adapterFlags & ADAPT_CHANNEL_COUNT) { | |
405 adapter = new PluginChannelAdapter(adapter); | |
406 } | |
407 | |
408 return adapter; | |
409 } | |
410 | |
411 ++index; | |
412 } | |
413 | |
414 cerr << "Vamp::HostExt::PluginLoader: Plugin \"" | |
415 << identifier << "\" not found in library \"" | |
416 << fullPath << "\"" << endl; | |
417 | |
418 return 0; | |
419 } | |
420 | |
421 void | |
422 PluginLoader::Impl::generateTaxonomy() | |
423 { | |
424 // cerr << "PluginLoader::Impl::generateTaxonomy" << endl; | |
425 | |
426 vector<string> path = PluginHostAdapter::getPluginPath(); | |
427 string libfragment = "/lib/"; | |
428 vector<string> catpath; | |
429 | |
430 string suffix = "cat"; | |
431 | |
432 for (vector<string>::iterator i = path.begin(); | |
433 i != path.end(); ++i) { | |
434 | |
435 // It doesn't matter that we're using literal forward-slash in | |
436 // this bit, as it's only relevant if the path contains | |
437 // "/lib/", which is only meaningful and only plausible on | |
438 // systems with forward-slash delimiters | |
439 | |
440 string dir = *i; | |
441 string::size_type li = dir.find(libfragment); | |
442 | |
443 if (li != string::npos) { | |
444 catpath.push_back | |
445 (dir.substr(0, li) | |
446 + "/share/" | |
447 + dir.substr(li + libfragment.length())); | |
448 } | |
449 | |
450 catpath.push_back(dir); | |
451 } | |
452 | |
453 char buffer[1024]; | |
454 | |
455 for (vector<string>::iterator i = catpath.begin(); | |
456 i != catpath.end(); ++i) { | |
457 | |
458 vector<string> files = listFiles(*i, suffix); | |
459 | |
460 for (vector<string>::iterator fi = files.begin(); | |
461 fi != files.end(); ++fi) { | |
462 | |
463 string filepath = splicePath(*i, *fi); | |
464 ifstream is(filepath.c_str(), ifstream::in | ifstream::binary); | |
465 | |
466 if (is.fail()) { | |
467 // cerr << "failed to open: " << filepath << endl; | |
468 continue; | |
469 } | |
470 | |
471 // cerr << "opened: " << filepath << endl; | |
472 | |
473 while (!!is.getline(buffer, 1024)) { | |
474 | |
475 string line(buffer); | |
476 | |
477 // cerr << "line = " << line << endl; | |
478 | |
479 string::size_type di = line.find("::"); | |
480 if (di == string::npos) continue; | |
481 | |
482 string id = line.substr(0, di); | |
483 string encodedCat = line.substr(di + 2); | |
484 | |
485 if (id.substr(0, 5) != "vamp:") continue; | |
486 id = id.substr(5); | |
487 | |
488 while (encodedCat.length() >= 1 && | |
489 encodedCat[encodedCat.length()-1] == '\r') { | |
490 encodedCat = encodedCat.substr(0, encodedCat.length()-1); | |
491 } | |
492 | |
493 // cerr << "id = " << id << ", cat = " << encodedCat << endl; | |
494 | |
495 PluginCategoryHierarchy category; | |
496 string::size_type ai; | |
497 while ((ai = encodedCat.find(" > ")) != string::npos) { | |
498 category.push_back(encodedCat.substr(0, ai)); | |
499 encodedCat = encodedCat.substr(ai + 3); | |
500 } | |
501 if (encodedCat != "") category.push_back(encodedCat); | |
502 | |
503 m_taxonomy[id] = category; | |
504 } | |
505 } | |
506 } | |
507 } | |
508 | |
509 void * | |
510 PluginLoader::Impl::loadLibrary(string path) | |
511 { | |
512 void *handle = 0; | |
513 #ifdef _WIN32 | |
514 handle = LoadLibrary(path.c_str()); | |
515 if (!handle) { | |
516 cerr << "Vamp::HostExt::PluginLoader: Unable to load library \"" | |
517 << path << "\"" << endl; | |
518 } | |
519 #else | |
520 handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL); | |
521 if (!handle) { | |
522 cerr << "Vamp::HostExt::PluginLoader: Unable to load library \"" | |
523 << path << "\": " << dlerror() << endl; | |
524 } | |
525 #endif | |
526 return handle; | |
527 } | |
528 | |
529 void | |
530 PluginLoader::Impl::unloadLibrary(void *handle) | |
531 { | |
532 #ifdef _WIN32 | |
533 FreeLibrary((HINSTANCE)handle); | |
534 #else | |
535 dlclose(handle); | |
536 #endif | |
537 } | |
538 | |
539 void * | |
540 PluginLoader::Impl::lookupInLibrary(void *handle, const char *symbol) | |
541 { | |
542 #ifdef _WIN32 | |
543 return (void *)GetProcAddress((HINSTANCE)handle, symbol); | |
544 #else | |
545 return (void *)dlsym(handle, symbol); | |
546 #endif | |
547 } | |
548 | |
549 string | |
550 PluginLoader::Impl::splicePath(string a, string b) | |
551 { | |
552 #ifdef _WIN32 | |
553 return a + "\\" + b; | |
554 #else | |
555 return a + "/" + b; | |
556 #endif | |
557 } | |
558 | |
559 vector<string> | |
560 PluginLoader::Impl::listFiles(string dir, string extension) | |
561 { | |
562 vector<string> files; | |
563 | |
564 #ifdef _WIN32 | |
565 | |
566 string expression = dir + "\\*." + extension; | |
567 WIN32_FIND_DATA data; | |
568 HANDLE fh = FindFirstFile(expression.c_str(), &data); | |
569 if (fh == INVALID_HANDLE_VALUE) return files; | |
570 | |
571 bool ok = true; | |
572 while (ok) { | |
573 files.push_back(data.cFileName); | |
574 ok = FindNextFile(fh, &data); | |
575 } | |
576 | |
577 FindClose(fh); | |
578 | |
579 #else | |
580 | |
581 size_t extlen = extension.length(); | |
582 DIR *d = opendir(dir.c_str()); | |
583 if (!d) return files; | |
584 | |
585 struct dirent *e = 0; | |
586 while ((e = readdir(d))) { | |
587 | |
588 if (!e->d_name) continue; | |
589 | |
590 size_t len = strlen(e->d_name); | |
591 if (len < extlen + 2 || | |
592 e->d_name + len - extlen - 1 != "." + extension) { | |
593 continue; | |
594 } | |
595 | |
596 files.push_back(e->d_name); | |
597 } | |
598 | |
599 closedir(d); | |
600 #endif | |
601 | |
602 return files; | |
603 } | |
604 | |
605 void | |
606 PluginLoader::Impl::pluginDeleted(PluginDeletionNotifyAdapter *adapter) | |
607 { | |
608 void *handle = m_pluginLibraryHandleMap[adapter]; | |
609 if (handle) unloadLibrary(handle); | |
610 m_pluginLibraryHandleMap.erase(adapter); | |
611 } | |
612 | |
613 PluginLoader::Impl::PluginDeletionNotifyAdapter::PluginDeletionNotifyAdapter(Plugin *plugin, | |
614 Impl *loader) : | |
615 PluginWrapper(plugin), | |
616 m_loader(loader) | |
617 { | |
618 } | |
619 | |
620 PluginLoader::Impl::PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter() | |
621 { | |
622 // We need to delete the plugin before calling pluginDeleted, as | |
623 // the delete call may require calling through to the descriptor | |
624 // (for e.g. cleanup) but pluginDeleted may unload the required | |
625 // library for the call. To prevent a double deletion when our | |
626 // parent's destructor runs (after this one), be sure to set | |
627 // m_plugin to 0 after deletion. | |
628 delete m_plugin; | |
629 m_plugin = 0; | |
630 | |
631 if (m_loader) m_loader->pluginDeleted(this); | |
632 } | |
633 | |
634 } | |
635 | |
636 } |