Mercurial > hg > vamp-plugin-sdk
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 } |