comparison vamp-sdk/hostext/PluginLoader.cpp @ 64:9d3272c7db60

* Merge from host-factory-stuff branch: this adds several helper classes in the hostext directory that should make a host's life much easier. This will become version 1.1 of the SDK, eventually.
author cannam
date Fri, 01 Jun 2007 15:10:17 +0000
parents
children 3456fe86d385
comparison
equal deleted inserted replaced
54:933fee59d33a 64:9d3272c7db60
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 Chris Cannam.
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-sdk/PluginHostAdapter.h"
38 #include "PluginLoader.h"
39 #include "PluginInputDomainAdapter.h"
40 #include "PluginChannelAdapter.h"
41
42 #include <fstream>
43
44 #ifdef _WIN32
45
46 #include <windows.h>
47 #include <tchar.h>
48 #define PLUGIN_SUFFIX "dll"
49
50 #else /* ! _WIN32 */
51
52 #include <dirent.h>
53 #include <dlfcn.h>
54
55 #ifdef __APPLE__
56 #define PLUGIN_SUFFIX "dylib"
57 #else /* ! __APPLE__ */
58 #define PLUGIN_SUFFIX "so"
59 #endif /* ! __APPLE__ */
60
61 #endif /* ! _WIN32 */
62
63 using namespace std;
64
65 namespace Vamp {
66
67 namespace HostExt {
68
69 PluginLoader *
70 PluginLoader::m_instance = 0;
71
72 PluginLoader::PluginLoader()
73 {
74 }
75
76 PluginLoader::~PluginLoader()
77 {
78 }
79
80 PluginLoader *
81 PluginLoader::getInstance()
82 {
83 if (!m_instance) m_instance = new PluginLoader();
84 return m_instance;
85 }
86
87 vector<PluginLoader::PluginKey>
88 PluginLoader::listPlugins()
89 {
90 if (m_pluginLibraryNameMap.empty()) generateLibraryMap();
91
92 vector<PluginKey> plugins;
93 for (map<PluginKey, string>::iterator mi =
94 m_pluginLibraryNameMap.begin();
95 mi != m_pluginLibraryNameMap.end(); ++mi) {
96 plugins.push_back(mi->first);
97 }
98
99 return plugins;
100 }
101
102 void
103 PluginLoader::generateLibraryMap()
104 {
105 vector<string> path = PluginHostAdapter::getPluginPath();
106
107 for (size_t i = 0; i < path.size(); ++i) {
108
109 vector<string> files = listFiles(path[i], PLUGIN_SUFFIX);
110
111 for (vector<string>::iterator fi = files.begin();
112 fi != files.end(); ++fi) {
113
114 string fullPath = path[i];
115 fullPath = splicePath(fullPath, *fi);
116 void *handle = loadLibrary(fullPath);
117 if (!handle) continue;
118
119 VampGetPluginDescriptorFunction fn =
120 (VampGetPluginDescriptorFunction)lookupInLibrary
121 (handle, "vampGetPluginDescriptor");
122
123 if (!fn) {
124 unloadLibrary(handle);
125 continue;
126 }
127
128 int index = 0;
129 const VampPluginDescriptor *descriptor = 0;
130
131 while ((descriptor = fn(VAMP_API_VERSION, index))) {
132 PluginKey key = composePluginKey(*fi, descriptor->identifier);
133 if (m_pluginLibraryNameMap.find(key) ==
134 m_pluginLibraryNameMap.end()) {
135 m_pluginLibraryNameMap[key] = fullPath;
136 }
137 ++index;
138 }
139
140 unloadLibrary(handle);
141 }
142 }
143 }
144
145 PluginLoader::PluginKey
146 PluginLoader::composePluginKey(string libraryName, string identifier)
147 {
148 string basename = libraryName;
149
150 string::size_type li = basename.rfind('/');
151 if (li != string::npos) basename = basename.substr(li + 1);
152
153 li = basename.find('.');
154 if (li != string::npos) basename = basename.substr(0, li);
155
156 return basename + ":" + identifier;
157 }
158
159 PluginLoader::PluginCategoryHierarchy
160 PluginLoader::getPluginCategory(PluginKey plugin)
161 {
162 if (m_taxonomy.empty()) generateTaxonomy();
163 if (m_taxonomy.find(plugin) == m_taxonomy.end()) return PluginCategoryHierarchy();
164 return m_taxonomy[plugin];
165 }
166
167 string
168 PluginLoader::getLibraryPathForPlugin(PluginKey plugin)
169 {
170 if (m_pluginLibraryNameMap.empty()) generateLibraryMap();
171 if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) return "";
172 return m_pluginLibraryNameMap[plugin];
173 }
174
175 Plugin *
176 PluginLoader::loadPlugin(PluginKey key, float inputSampleRate, int adapterFlags)
177 {
178 string fullPath = getLibraryPathForPlugin(key);
179 if (fullPath == "") return 0;
180
181 string::size_type ki = key.find(':');
182 if (ki == string::npos) {
183 //!!! flag error
184 return 0;
185 }
186
187 string identifier = key.substr(ki + 1);
188
189 void *handle = loadLibrary(fullPath);
190 if (!handle) return 0;
191
192 VampGetPluginDescriptorFunction fn =
193 (VampGetPluginDescriptorFunction)lookupInLibrary
194 (handle, "vampGetPluginDescriptor");
195
196 if (!fn) {
197 unloadLibrary(handle);
198 return 0;
199 }
200
201 int index = 0;
202 const VampPluginDescriptor *descriptor = 0;
203
204 while ((descriptor = fn(VAMP_API_VERSION, index))) {
205
206 if (string(descriptor->identifier) == identifier) {
207
208 Vamp::PluginHostAdapter *plugin =
209 new Vamp::PluginHostAdapter(descriptor, inputSampleRate);
210
211 Plugin *adapter = new PluginDeletionNotifyAdapter(plugin, this);
212
213 m_pluginLibraryHandleMap[adapter] = handle;
214
215 if (adapterFlags & ADAPT_INPUT_DOMAIN) {
216 if (adapter->getInputDomain() == Plugin::FrequencyDomain) {
217 adapter = new PluginInputDomainAdapter(adapter);
218 }
219 }
220
221 if (adapterFlags & ADAPT_CHANNEL_COUNT) {
222 adapter = new PluginChannelAdapter(adapter);
223 }
224
225 return adapter;
226 }
227
228 ++index;
229 }
230
231 cerr << "Vamp::HostExt::PluginLoader: Plugin \""
232 << identifier << "\" not found in library \""
233 << fullPath << "\"" << endl;
234
235 return 0;
236 }
237
238 void
239 PluginLoader::generateTaxonomy()
240 {
241 // cerr << "PluginLoader::generateTaxonomy" << endl;
242
243 vector<string> path = PluginHostAdapter::getPluginPath();
244 string libfragment = "/lib/";
245 vector<string> catpath;
246
247 string suffix = "cat";
248
249 for (vector<string>::iterator i = path.begin();
250 i != path.end(); ++i) {
251
252 // It doesn't matter that we're using literal forward-slash in
253 // this bit, as it's only relevant if the path contains
254 // "/lib/", which is only meaningful and only plausible on
255 // systems with forward-slash delimiters
256
257 string dir = *i;
258 string::size_type li = dir.find(libfragment);
259
260 if (li != string::npos) {
261 catpath.push_back
262 (dir.substr(0, li)
263 + "/share/"
264 + dir.substr(li + libfragment.length()));
265 }
266
267 catpath.push_back(dir);
268 }
269
270 char buffer[1024];
271
272 for (vector<string>::iterator i = catpath.begin();
273 i != catpath.end(); ++i) {
274
275 vector<string> files = listFiles(*i, suffix);
276
277 for (vector<string>::iterator fi = files.begin();
278 fi != files.end(); ++fi) {
279
280 string filepath = splicePath(*i, *fi);
281 ifstream is(filepath.c_str(), ifstream::in | ifstream::binary);
282
283 if (is.fail()) {
284 // cerr << "failed to open: " << filepath << endl;
285 continue;
286 }
287
288 // cerr << "opened: " << filepath << endl;
289
290 while (!!is.getline(buffer, 1024)) {
291
292 string line(buffer);
293
294 // cerr << "line = " << line << endl;
295
296 string::size_type di = line.find("::");
297 if (di == string::npos) continue;
298
299 string id = line.substr(0, di);
300 string encodedCat = line.substr(di + 2);
301
302 if (id.substr(0, 5) != "vamp:") continue;
303 id = id.substr(5);
304
305 while (encodedCat.length() >= 1 &&
306 encodedCat[encodedCat.length()-1] == '\r') {
307 encodedCat = encodedCat.substr(0, encodedCat.length()-1);
308 }
309
310 // cerr << "id = " << id << ", cat = " << encodedCat << endl;
311
312 PluginCategoryHierarchy category;
313 string::size_type ai;
314 while ((ai = encodedCat.find(" > ")) != string::npos) {
315 category.push_back(encodedCat.substr(0, ai));
316 encodedCat = encodedCat.substr(ai + 3);
317 }
318 if (encodedCat != "") category.push_back(encodedCat);
319
320 m_taxonomy[id] = category;
321 }
322 }
323 }
324 }
325
326 void *
327 PluginLoader::loadLibrary(string path)
328 {
329 void *handle = 0;
330 #ifdef _WIN32
331 handle = LoadLibrary(path.c_str());
332 if (!handle) {
333 cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
334 << path << "\"" << endl;
335 }
336 #else
337 handle = dlopen(path.c_str(), RTLD_LAZY);
338 if (!handle) {
339 cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
340 << path << "\": " << dlerror() << endl;
341 }
342 #endif
343 return handle;
344 }
345
346 void
347 PluginLoader::unloadLibrary(void *handle)
348 {
349 #ifdef _WIN32
350 FreeLibrary((HINSTANCE)handle);
351 #else
352 dlclose(handle);
353 #endif
354 }
355
356 void *
357 PluginLoader::lookupInLibrary(void *handle, const char *symbol)
358 {
359 #ifdef _WIN32
360 return (void *)GetProcAddress((HINSTANCE)handle, symbol);
361 #else
362 return (void *)dlsym(handle, symbol);
363 #endif
364 }
365
366 string
367 PluginLoader::splicePath(string a, string b)
368 {
369 #ifdef _WIN32
370 return a + "\\" + b;
371 #else
372 return a + "/" + b;
373 #endif
374 }
375
376 vector<string>
377 PluginLoader::listFiles(string dir, string extension)
378 {
379 vector<string> files;
380 size_t extlen = extension.length();
381
382 #ifdef _WIN32
383
384 string expression = dir + "\\*." + extension;
385 WIN32_FIND_DATA data;
386 HANDLE fh = FindFirstFile(expression.c_str(), &data);
387 if (fh == INVALID_HANDLE_VALUE) return files;
388
389 bool ok = true;
390 while (ok) {
391 files.push_back(data.cFileName);
392 ok = FindNextFile(fh, &data);
393 }
394
395 FindClose(fh);
396
397 #else
398 DIR *d = opendir(dir.c_str());
399 if (!d) return files;
400
401 struct dirent *e = 0;
402 while ((e = readdir(d))) {
403
404 if (!(e->d_type & DT_REG) || !e->d_name) continue;
405
406 size_t len = strlen(e->d_name);
407 if (len < extlen + 2 ||
408 e->d_name + len - extlen - 1 != "." + extension) {
409 continue;
410 }
411
412 files.push_back(e->d_name);
413 }
414
415 closedir(d);
416 #endif
417
418 return files;
419 }
420
421 void
422 PluginLoader::pluginDeleted(PluginDeletionNotifyAdapter *adapter)
423 {
424 void *handle = m_pluginLibraryHandleMap[adapter];
425 if (handle) unloadLibrary(handle);
426 m_pluginLibraryHandleMap.erase(adapter);
427 }
428
429 PluginLoader::PluginDeletionNotifyAdapter::PluginDeletionNotifyAdapter(Plugin *plugin,
430 PluginLoader *loader) :
431 PluginWrapper(plugin),
432 m_loader(loader)
433 {
434 }
435
436 PluginLoader::PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter()
437 {
438 // We need to delete the plugin before calling pluginDeleted, as
439 // the delete call may require calling through to the descriptor
440 // (for e.g. cleanup) but pluginDeleted may unload the required
441 // library for the call. To prevent a double deletion when our
442 // parent's destructor runs (after this one), be sure to set
443 // m_plugin to 0 after deletion.
444 delete m_plugin;
445 m_plugin = 0;
446
447 if (m_loader) m_loader->pluginDeleted(this);
448 }
449
450 }
451
452 }