annotate 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
rev   line source
cannam@64 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
cannam@64 2
cannam@64 3 /*
cannam@64 4 Vamp
cannam@64 5
cannam@64 6 An API for audio analysis and feature extraction plugins.
cannam@64 7
cannam@64 8 Centre for Digital Music, Queen Mary, University of London.
cannam@64 9 Copyright 2006 Chris Cannam.
cannam@64 10
cannam@64 11 Permission is hereby granted, free of charge, to any person
cannam@64 12 obtaining a copy of this software and associated documentation
cannam@64 13 files (the "Software"), to deal in the Software without
cannam@64 14 restriction, including without limitation the rights to use, copy,
cannam@64 15 modify, merge, publish, distribute, sublicense, and/or sell copies
cannam@64 16 of the Software, and to permit persons to whom the Software is
cannam@64 17 furnished to do so, subject to the following conditions:
cannam@64 18
cannam@64 19 The above copyright notice and this permission notice shall be
cannam@64 20 included in all copies or substantial portions of the Software.
cannam@64 21
cannam@64 22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
cannam@64 23 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
cannam@64 24 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
cannam@64 25 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
cannam@64 26 ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
cannam@64 27 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
cannam@64 28 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
cannam@64 29
cannam@64 30 Except as contained in this notice, the names of the Centre for
cannam@64 31 Digital Music; Queen Mary, University of London; and Chris Cannam
cannam@64 32 shall not be used in advertising or otherwise to promote the sale,
cannam@64 33 use or other dealings in this Software without prior written
cannam@64 34 authorization.
cannam@64 35 */
cannam@64 36
cannam@64 37 #include "vamp-sdk/PluginHostAdapter.h"
cannam@64 38 #include "PluginLoader.h"
cannam@64 39 #include "PluginInputDomainAdapter.h"
cannam@64 40 #include "PluginChannelAdapter.h"
cannam@64 41
cannam@64 42 #include <fstream>
cannam@64 43
cannam@64 44 #ifdef _WIN32
cannam@64 45
cannam@64 46 #include <windows.h>
cannam@64 47 #include <tchar.h>
cannam@64 48 #define PLUGIN_SUFFIX "dll"
cannam@64 49
cannam@64 50 #else /* ! _WIN32 */
cannam@64 51
cannam@64 52 #include <dirent.h>
cannam@64 53 #include <dlfcn.h>
cannam@64 54
cannam@64 55 #ifdef __APPLE__
cannam@64 56 #define PLUGIN_SUFFIX "dylib"
cannam@64 57 #else /* ! __APPLE__ */
cannam@64 58 #define PLUGIN_SUFFIX "so"
cannam@64 59 #endif /* ! __APPLE__ */
cannam@64 60
cannam@64 61 #endif /* ! _WIN32 */
cannam@64 62
cannam@64 63 using namespace std;
cannam@64 64
cannam@64 65 namespace Vamp {
cannam@64 66
cannam@64 67 namespace HostExt {
cannam@64 68
cannam@64 69 PluginLoader *
cannam@64 70 PluginLoader::m_instance = 0;
cannam@64 71
cannam@64 72 PluginLoader::PluginLoader()
cannam@64 73 {
cannam@64 74 }
cannam@64 75
cannam@64 76 PluginLoader::~PluginLoader()
cannam@64 77 {
cannam@64 78 }
cannam@64 79
cannam@64 80 PluginLoader *
cannam@64 81 PluginLoader::getInstance()
cannam@64 82 {
cannam@64 83 if (!m_instance) m_instance = new PluginLoader();
cannam@64 84 return m_instance;
cannam@64 85 }
cannam@64 86
cannam@64 87 vector<PluginLoader::PluginKey>
cannam@64 88 PluginLoader::listPlugins()
cannam@64 89 {
cannam@64 90 if (m_pluginLibraryNameMap.empty()) generateLibraryMap();
cannam@64 91
cannam@64 92 vector<PluginKey> plugins;
cannam@64 93 for (map<PluginKey, string>::iterator mi =
cannam@64 94 m_pluginLibraryNameMap.begin();
cannam@64 95 mi != m_pluginLibraryNameMap.end(); ++mi) {
cannam@64 96 plugins.push_back(mi->first);
cannam@64 97 }
cannam@64 98
cannam@64 99 return plugins;
cannam@64 100 }
cannam@64 101
cannam@64 102 void
cannam@64 103 PluginLoader::generateLibraryMap()
cannam@64 104 {
cannam@64 105 vector<string> path = PluginHostAdapter::getPluginPath();
cannam@64 106
cannam@64 107 for (size_t i = 0; i < path.size(); ++i) {
cannam@64 108
cannam@64 109 vector<string> files = listFiles(path[i], PLUGIN_SUFFIX);
cannam@64 110
cannam@64 111 for (vector<string>::iterator fi = files.begin();
cannam@64 112 fi != files.end(); ++fi) {
cannam@64 113
cannam@64 114 string fullPath = path[i];
cannam@64 115 fullPath = splicePath(fullPath, *fi);
cannam@64 116 void *handle = loadLibrary(fullPath);
cannam@64 117 if (!handle) continue;
cannam@64 118
cannam@64 119 VampGetPluginDescriptorFunction fn =
cannam@64 120 (VampGetPluginDescriptorFunction)lookupInLibrary
cannam@64 121 (handle, "vampGetPluginDescriptor");
cannam@64 122
cannam@64 123 if (!fn) {
cannam@64 124 unloadLibrary(handle);
cannam@64 125 continue;
cannam@64 126 }
cannam@64 127
cannam@64 128 int index = 0;
cannam@64 129 const VampPluginDescriptor *descriptor = 0;
cannam@64 130
cannam@64 131 while ((descriptor = fn(VAMP_API_VERSION, index))) {
cannam@64 132 PluginKey key = composePluginKey(*fi, descriptor->identifier);
cannam@64 133 if (m_pluginLibraryNameMap.find(key) ==
cannam@64 134 m_pluginLibraryNameMap.end()) {
cannam@64 135 m_pluginLibraryNameMap[key] = fullPath;
cannam@64 136 }
cannam@64 137 ++index;
cannam@64 138 }
cannam@64 139
cannam@64 140 unloadLibrary(handle);
cannam@64 141 }
cannam@64 142 }
cannam@64 143 }
cannam@64 144
cannam@64 145 PluginLoader::PluginKey
cannam@64 146 PluginLoader::composePluginKey(string libraryName, string identifier)
cannam@64 147 {
cannam@64 148 string basename = libraryName;
cannam@64 149
cannam@64 150 string::size_type li = basename.rfind('/');
cannam@64 151 if (li != string::npos) basename = basename.substr(li + 1);
cannam@64 152
cannam@64 153 li = basename.find('.');
cannam@64 154 if (li != string::npos) basename = basename.substr(0, li);
cannam@64 155
cannam@64 156 return basename + ":" + identifier;
cannam@64 157 }
cannam@64 158
cannam@64 159 PluginLoader::PluginCategoryHierarchy
cannam@64 160 PluginLoader::getPluginCategory(PluginKey plugin)
cannam@64 161 {
cannam@64 162 if (m_taxonomy.empty()) generateTaxonomy();
cannam@64 163 if (m_taxonomy.find(plugin) == m_taxonomy.end()) return PluginCategoryHierarchy();
cannam@64 164 return m_taxonomy[plugin];
cannam@64 165 }
cannam@64 166
cannam@64 167 string
cannam@64 168 PluginLoader::getLibraryPathForPlugin(PluginKey plugin)
cannam@64 169 {
cannam@64 170 if (m_pluginLibraryNameMap.empty()) generateLibraryMap();
cannam@64 171 if (m_pluginLibraryNameMap.find(plugin) == m_pluginLibraryNameMap.end()) return "";
cannam@64 172 return m_pluginLibraryNameMap[plugin];
cannam@64 173 }
cannam@64 174
cannam@64 175 Plugin *
cannam@64 176 PluginLoader::loadPlugin(PluginKey key, float inputSampleRate, int adapterFlags)
cannam@64 177 {
cannam@64 178 string fullPath = getLibraryPathForPlugin(key);
cannam@64 179 if (fullPath == "") return 0;
cannam@64 180
cannam@64 181 string::size_type ki = key.find(':');
cannam@64 182 if (ki == string::npos) {
cannam@64 183 //!!! flag error
cannam@64 184 return 0;
cannam@64 185 }
cannam@64 186
cannam@64 187 string identifier = key.substr(ki + 1);
cannam@64 188
cannam@64 189 void *handle = loadLibrary(fullPath);
cannam@64 190 if (!handle) return 0;
cannam@64 191
cannam@64 192 VampGetPluginDescriptorFunction fn =
cannam@64 193 (VampGetPluginDescriptorFunction)lookupInLibrary
cannam@64 194 (handle, "vampGetPluginDescriptor");
cannam@64 195
cannam@64 196 if (!fn) {
cannam@64 197 unloadLibrary(handle);
cannam@64 198 return 0;
cannam@64 199 }
cannam@64 200
cannam@64 201 int index = 0;
cannam@64 202 const VampPluginDescriptor *descriptor = 0;
cannam@64 203
cannam@64 204 while ((descriptor = fn(VAMP_API_VERSION, index))) {
cannam@64 205
cannam@64 206 if (string(descriptor->identifier) == identifier) {
cannam@64 207
cannam@64 208 Vamp::PluginHostAdapter *plugin =
cannam@64 209 new Vamp::PluginHostAdapter(descriptor, inputSampleRate);
cannam@64 210
cannam@64 211 Plugin *adapter = new PluginDeletionNotifyAdapter(plugin, this);
cannam@64 212
cannam@64 213 m_pluginLibraryHandleMap[adapter] = handle;
cannam@64 214
cannam@64 215 if (adapterFlags & ADAPT_INPUT_DOMAIN) {
cannam@64 216 if (adapter->getInputDomain() == Plugin::FrequencyDomain) {
cannam@64 217 adapter = new PluginInputDomainAdapter(adapter);
cannam@64 218 }
cannam@64 219 }
cannam@64 220
cannam@64 221 if (adapterFlags & ADAPT_CHANNEL_COUNT) {
cannam@64 222 adapter = new PluginChannelAdapter(adapter);
cannam@64 223 }
cannam@64 224
cannam@64 225 return adapter;
cannam@64 226 }
cannam@64 227
cannam@64 228 ++index;
cannam@64 229 }
cannam@64 230
cannam@64 231 cerr << "Vamp::HostExt::PluginLoader: Plugin \""
cannam@64 232 << identifier << "\" not found in library \""
cannam@64 233 << fullPath << "\"" << endl;
cannam@64 234
cannam@64 235 return 0;
cannam@64 236 }
cannam@64 237
cannam@64 238 void
cannam@64 239 PluginLoader::generateTaxonomy()
cannam@64 240 {
cannam@64 241 // cerr << "PluginLoader::generateTaxonomy" << endl;
cannam@64 242
cannam@64 243 vector<string> path = PluginHostAdapter::getPluginPath();
cannam@64 244 string libfragment = "/lib/";
cannam@64 245 vector<string> catpath;
cannam@64 246
cannam@64 247 string suffix = "cat";
cannam@64 248
cannam@64 249 for (vector<string>::iterator i = path.begin();
cannam@64 250 i != path.end(); ++i) {
cannam@64 251
cannam@64 252 // It doesn't matter that we're using literal forward-slash in
cannam@64 253 // this bit, as it's only relevant if the path contains
cannam@64 254 // "/lib/", which is only meaningful and only plausible on
cannam@64 255 // systems with forward-slash delimiters
cannam@64 256
cannam@64 257 string dir = *i;
cannam@64 258 string::size_type li = dir.find(libfragment);
cannam@64 259
cannam@64 260 if (li != string::npos) {
cannam@64 261 catpath.push_back
cannam@64 262 (dir.substr(0, li)
cannam@64 263 + "/share/"
cannam@64 264 + dir.substr(li + libfragment.length()));
cannam@64 265 }
cannam@64 266
cannam@64 267 catpath.push_back(dir);
cannam@64 268 }
cannam@64 269
cannam@64 270 char buffer[1024];
cannam@64 271
cannam@64 272 for (vector<string>::iterator i = catpath.begin();
cannam@64 273 i != catpath.end(); ++i) {
cannam@64 274
cannam@64 275 vector<string> files = listFiles(*i, suffix);
cannam@64 276
cannam@64 277 for (vector<string>::iterator fi = files.begin();
cannam@64 278 fi != files.end(); ++fi) {
cannam@64 279
cannam@64 280 string filepath = splicePath(*i, *fi);
cannam@64 281 ifstream is(filepath.c_str(), ifstream::in | ifstream::binary);
cannam@64 282
cannam@64 283 if (is.fail()) {
cannam@64 284 // cerr << "failed to open: " << filepath << endl;
cannam@64 285 continue;
cannam@64 286 }
cannam@64 287
cannam@64 288 // cerr << "opened: " << filepath << endl;
cannam@64 289
cannam@64 290 while (!!is.getline(buffer, 1024)) {
cannam@64 291
cannam@64 292 string line(buffer);
cannam@64 293
cannam@64 294 // cerr << "line = " << line << endl;
cannam@64 295
cannam@64 296 string::size_type di = line.find("::");
cannam@64 297 if (di == string::npos) continue;
cannam@64 298
cannam@64 299 string id = line.substr(0, di);
cannam@64 300 string encodedCat = line.substr(di + 2);
cannam@64 301
cannam@64 302 if (id.substr(0, 5) != "vamp:") continue;
cannam@64 303 id = id.substr(5);
cannam@64 304
cannam@64 305 while (encodedCat.length() >= 1 &&
cannam@64 306 encodedCat[encodedCat.length()-1] == '\r') {
cannam@64 307 encodedCat = encodedCat.substr(0, encodedCat.length()-1);
cannam@64 308 }
cannam@64 309
cannam@64 310 // cerr << "id = " << id << ", cat = " << encodedCat << endl;
cannam@64 311
cannam@64 312 PluginCategoryHierarchy category;
cannam@64 313 string::size_type ai;
cannam@64 314 while ((ai = encodedCat.find(" > ")) != string::npos) {
cannam@64 315 category.push_back(encodedCat.substr(0, ai));
cannam@64 316 encodedCat = encodedCat.substr(ai + 3);
cannam@64 317 }
cannam@64 318 if (encodedCat != "") category.push_back(encodedCat);
cannam@64 319
cannam@64 320 m_taxonomy[id] = category;
cannam@64 321 }
cannam@64 322 }
cannam@64 323 }
cannam@64 324 }
cannam@64 325
cannam@64 326 void *
cannam@64 327 PluginLoader::loadLibrary(string path)
cannam@64 328 {
cannam@64 329 void *handle = 0;
cannam@64 330 #ifdef _WIN32
cannam@64 331 handle = LoadLibrary(path.c_str());
cannam@64 332 if (!handle) {
cannam@64 333 cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
cannam@64 334 << path << "\"" << endl;
cannam@64 335 }
cannam@64 336 #else
cannam@64 337 handle = dlopen(path.c_str(), RTLD_LAZY);
cannam@64 338 if (!handle) {
cannam@64 339 cerr << "Vamp::HostExt::PluginLoader: Unable to load library \""
cannam@64 340 << path << "\": " << dlerror() << endl;
cannam@64 341 }
cannam@64 342 #endif
cannam@64 343 return handle;
cannam@64 344 }
cannam@64 345
cannam@64 346 void
cannam@64 347 PluginLoader::unloadLibrary(void *handle)
cannam@64 348 {
cannam@64 349 #ifdef _WIN32
cannam@64 350 FreeLibrary((HINSTANCE)handle);
cannam@64 351 #else
cannam@64 352 dlclose(handle);
cannam@64 353 #endif
cannam@64 354 }
cannam@64 355
cannam@64 356 void *
cannam@64 357 PluginLoader::lookupInLibrary(void *handle, const char *symbol)
cannam@64 358 {
cannam@64 359 #ifdef _WIN32
cannam@64 360 return (void *)GetProcAddress((HINSTANCE)handle, symbol);
cannam@64 361 #else
cannam@64 362 return (void *)dlsym(handle, symbol);
cannam@64 363 #endif
cannam@64 364 }
cannam@64 365
cannam@64 366 string
cannam@64 367 PluginLoader::splicePath(string a, string b)
cannam@64 368 {
cannam@64 369 #ifdef _WIN32
cannam@64 370 return a + "\\" + b;
cannam@64 371 #else
cannam@64 372 return a + "/" + b;
cannam@64 373 #endif
cannam@64 374 }
cannam@64 375
cannam@64 376 vector<string>
cannam@64 377 PluginLoader::listFiles(string dir, string extension)
cannam@64 378 {
cannam@64 379 vector<string> files;
cannam@64 380 size_t extlen = extension.length();
cannam@64 381
cannam@64 382 #ifdef _WIN32
cannam@64 383
cannam@64 384 string expression = dir + "\\*." + extension;
cannam@64 385 WIN32_FIND_DATA data;
cannam@64 386 HANDLE fh = FindFirstFile(expression.c_str(), &data);
cannam@64 387 if (fh == INVALID_HANDLE_VALUE) return files;
cannam@64 388
cannam@64 389 bool ok = true;
cannam@64 390 while (ok) {
cannam@64 391 files.push_back(data.cFileName);
cannam@64 392 ok = FindNextFile(fh, &data);
cannam@64 393 }
cannam@64 394
cannam@64 395 FindClose(fh);
cannam@64 396
cannam@64 397 #else
cannam@64 398 DIR *d = opendir(dir.c_str());
cannam@64 399 if (!d) return files;
cannam@64 400
cannam@64 401 struct dirent *e = 0;
cannam@64 402 while ((e = readdir(d))) {
cannam@64 403
cannam@64 404 if (!(e->d_type & DT_REG) || !e->d_name) continue;
cannam@64 405
cannam@64 406 size_t len = strlen(e->d_name);
cannam@64 407 if (len < extlen + 2 ||
cannam@64 408 e->d_name + len - extlen - 1 != "." + extension) {
cannam@64 409 continue;
cannam@64 410 }
cannam@64 411
cannam@64 412 files.push_back(e->d_name);
cannam@64 413 }
cannam@64 414
cannam@64 415 closedir(d);
cannam@64 416 #endif
cannam@64 417
cannam@64 418 return files;
cannam@64 419 }
cannam@64 420
cannam@64 421 void
cannam@64 422 PluginLoader::pluginDeleted(PluginDeletionNotifyAdapter *adapter)
cannam@64 423 {
cannam@64 424 void *handle = m_pluginLibraryHandleMap[adapter];
cannam@64 425 if (handle) unloadLibrary(handle);
cannam@64 426 m_pluginLibraryHandleMap.erase(adapter);
cannam@64 427 }
cannam@64 428
cannam@64 429 PluginLoader::PluginDeletionNotifyAdapter::PluginDeletionNotifyAdapter(Plugin *plugin,
cannam@64 430 PluginLoader *loader) :
cannam@64 431 PluginWrapper(plugin),
cannam@64 432 m_loader(loader)
cannam@64 433 {
cannam@64 434 }
cannam@64 435
cannam@64 436 PluginLoader::PluginDeletionNotifyAdapter::~PluginDeletionNotifyAdapter()
cannam@64 437 {
cannam@64 438 // We need to delete the plugin before calling pluginDeleted, as
cannam@64 439 // the delete call may require calling through to the descriptor
cannam@64 440 // (for e.g. cleanup) but pluginDeleted may unload the required
cannam@64 441 // library for the call. To prevent a double deletion when our
cannam@64 442 // parent's destructor runs (after this one), be sure to set
cannam@64 443 // m_plugin to 0 after deletion.
cannam@64 444 delete m_plugin;
cannam@64 445 m_plugin = 0;
cannam@64 446
cannam@64 447 if (m_loader) m_loader->pluginDeleted(this);
cannam@64 448 }
cannam@64 449
cannam@64 450 }
cannam@64 451
cannam@64 452 }