![]() |
Mixxx
|
00001 /* -*- mode:C++; indent-tabs-mode:t; tab-width:8; c-basic-offset:4; -*- */ 00002 /*************************************************************************** 00003 soundsourceproxy.cpp - description 00004 ------------------- 00005 begin : Wed Oct 13 2004 00006 copyright : (C) 2004 Tue Haste Andersen 00007 email : 00008 ***************************************************************************/ 00009 00010 /*************************************************************************** 00011 * * 00012 * This program is free software; you can redistribute it and/or modify * 00013 * it under the terms of the GNU General Public License as published by * 00014 * the Free Software Foundation; either version 2 of the License, or * 00015 * (at your option) any later version. * 00016 * * 00017 ***************************************************************************/ 00018 00019 #include "trackinfoobject.h" 00020 #include "soundsourceproxy.h" 00021 #ifdef __MAD__ 00022 #include "soundsourcemp3.h" 00023 #endif 00024 #include "soundsourceoggvorbis.h" 00025 #ifdef __COREAUDIO__ 00026 #include "soundsourcecoreaudio.h" 00027 #endif 00028 #ifdef __SNDFILE__ 00029 #include "soundsourcesndfile.h" 00030 #endif 00031 #ifdef __FFMPEGFILE__ 00032 #include "soundsourceffmpeg.h" 00033 #endif 00034 #include "soundsourceflac.h" 00035 00036 #include <QLibrary> 00037 #include <QMutexLocker> 00038 #include <QMutex> 00039 #include <QDebug> 00040 #include <QDir> 00041 #include <QDesktopServices> 00042 #include <QCoreApplication> 00043 #include <QApplication> 00044 00045 00046 //Static memory allocation 00047 QRegExp SoundSourceProxy::m_supportedFileRegex; 00048 QMap<QString, QLibrary*> SoundSourceProxy::m_plugins; 00049 QMap<QString, getSoundSourceFunc> SoundSourceProxy::m_extensionsSupportedByPlugins; 00050 QMutex SoundSourceProxy::m_extensionsMutex; 00051 00052 00053 //Constructor 00054 SoundSourceProxy::SoundSourceProxy(QString qFilename) 00055 : Mixxx::SoundSource(qFilename), 00056 m_pSoundSource(NULL), 00057 m_pTrack() { 00058 m_pSoundSource = initialize(qFilename); 00059 } 00060 00061 //Other constructor 00062 SoundSourceProxy::SoundSourceProxy(TrackPointer pTrack) 00063 : SoundSource(pTrack->getLocation()), 00064 m_pSoundSource(NULL) { 00065 00066 m_pSoundSource = initialize(pTrack->getLocation()); 00067 m_pTrack = pTrack; 00068 } 00069 00070 void SoundSourceProxy::loadPlugins() 00071 { 00074 QList<QDir> pluginDirs; 00075 QStringList nameFilters; 00076 00077 QStringList clArgs = QApplication::arguments(); 00078 int pluginPath = clArgs.indexOf("--pluginPath"); 00079 if (pluginPath != -1 && pluginPath + 1 < clArgs.size()) { 00080 qDebug() << "Adding plugin path from commandline arg:" << clArgs.at(pluginPath + 1); 00081 pluginDirs.append(QDir(clArgs.at(pluginPath + 1))); 00082 } 00083 #ifdef __LINUX__ 00084 pluginDirs.append(QDir("/usr/local/lib/mixxx/plugins/soundsource/")); 00085 pluginDirs.append(QDir("/usr/lib/mixxx/plugins/soundsource/")); 00086 pluginDirs.append(QDir(QDesktopServices::storageLocation(QDesktopServices::HomeLocation) + "/.mixxx/plugins/soundsource/")); 00087 #elif __WINDOWS__ 00088 pluginDirs.append(QDir(QCoreApplication::applicationDirPath() + "/plugins/soundsource/")); 00089 #elif __APPLE__ 00090 QString bundlePluginDir = QCoreApplication::applicationDirPath(); //blah/Mixxx.app/Contents/MacOS 00091 bundlePluginDir.remove("MacOS"); 00092 //blah/Mixxx.app/Contents/PlugIns/soundsource 00093 //bundlePluginDir.append("PlugIns/soundsource"); //Our SCons bundle target doesn't handle plugin subdirectories :( 00094 bundlePluginDir.append("PlugIns/"); 00095 pluginDirs.append(QDir(bundlePluginDir)); 00096 pluginDirs.append(QDir("/Library/Application Support/Mixxx/Plugins/soundsource/")); 00097 nameFilters << "libsoundsource*"; 00098 #endif 00099 00100 QDir dir; 00101 foreach(dir, pluginDirs) 00102 { 00103 QStringList files = dir.entryList(nameFilters, QDir::Files | QDir::NoDotAndDotDot); 00104 QString file; 00105 foreach (file, files) 00106 { 00107 getPlugin(dir.filePath(file)); 00108 } 00109 } 00110 } 00111 00112 Mixxx::SoundSource* SoundSourceProxy::initialize(QString qFilename) { 00113 00114 Mixxx::SoundSource* sndsrc = NULL; 00115 QString extension = qFilename; 00116 extension.remove(0, (qFilename.lastIndexOf(".")+1)); 00117 extension = extension.toLower(); 00118 00119 #ifdef __FFMPEGFILE__ 00120 return new SoundSourceFFmpeg(qFilename); 00121 #endif 00122 if (SoundSourceOggVorbis::supportedFileExtensions().contains(extension)) { 00123 return new SoundSourceOggVorbis(qFilename); 00124 #ifdef __MAD__ 00125 } else if (SoundSourceMp3::supportedFileExtensions().contains(extension)) { 00126 return new SoundSourceMp3(qFilename); 00127 #endif 00128 } else if (SoundSourceFLAC::supportedFileExtensions().contains(extension)) { 00129 return new SoundSourceFLAC(qFilename); 00130 #ifdef __COREAUDIO__ 00131 } else if (SoundSourceCoreAudio::supportedFileExtensions().contains(extension)) { 00132 return new SoundSourceCoreAudio(qFilename); 00133 #endif 00134 } else if (m_extensionsSupportedByPlugins.contains(extension)) { 00135 getSoundSourceFunc getter = m_extensionsSupportedByPlugins.value(extension); 00136 if (getter) 00137 { 00138 qDebug() << "Getting SoundSource plugin object for" << extension; 00139 return getter(qFilename); 00140 } 00141 else { 00142 qDebug() << "Failed to resolve getSoundSource in plugin for" << 00143 extension; 00144 return NULL; //Failed to load plugin 00145 } 00146 #ifdef __SNDFILE__ 00147 } else if (SoundSourceSndFile::supportedFileExtensions().contains(extension)) { 00148 return new SoundSourceSndFile(qFilename); 00149 #endif 00150 } else { //Unsupported filetype 00151 return NULL; 00152 } 00153 } 00154 00155 SoundSourceProxy::~SoundSourceProxy() 00156 { 00157 delete m_pSoundSource; 00158 } 00159 00160 QLibrary* SoundSourceProxy::getPlugin(QString lib_filename) 00161 { 00162 static QMutex mutex; 00163 QMutexLocker locker(&mutex); 00164 QLibrary* plugin; 00165 if (m_plugins.contains(lib_filename)) 00166 plugin = m_plugins.value(lib_filename); 00167 else { 00168 plugin = new QLibrary(lib_filename); 00169 if (!plugin->load()) { 00170 qDebug() << "Failed to dynamically load" << lib_filename << plugin->errorString(); 00171 } else { 00172 qDebug() << "Dynamically loaded" << lib_filename; 00173 //Add the plugin to our list of loaded QLibraries/plugins 00174 m_plugins.insert(lib_filename, plugin); 00175 00176 bool incompatible = false; 00177 //Plugin API version check 00178 getSoundSourceAPIVersionFunc getver = (getSoundSourceAPIVersionFunc)plugin->resolve("getSoundSourceAPIVersion"); 00179 if (getver) { 00180 int pluginAPIVersion = getver(); 00181 if (pluginAPIVersion != MIXXX_SOUNDSOURCE_API_VERSION) { 00182 //SoundSource API version mismatch 00183 incompatible = true; 00184 } 00185 } else { 00186 //Missing getSoundSourceAPIVersion symbol 00187 incompatible = true; 00188 } 00189 if (incompatible) 00190 { 00191 //Plugin is using an older/incompatible version of the 00192 //plugin API! 00193 qDebug() << "Plugin" << lib_filename << "is incompatible with your version of Mixxx!"; 00194 return NULL; 00195 } 00196 00197 //Map the file extensions this plugin supports onto a function 00198 //pointer to the "getter" function that gets a SoundSourceBlah. 00199 getSoundSourceFunc getter = (getSoundSourceFunc)plugin->resolve("getSoundSource"); 00200 Q_ASSERT(getter); //Getter function not found. 00201 //Did you export it properly in your plugin? 00202 getSupportedFileExtensionsFunc getFileExts = (getSupportedFileExtensionsFunc)plugin->resolve("supportedFileExtensions"); 00203 Q_ASSERT(getFileExts); 00204 freeFileExtensionsFunc freeFileExts = 00205 reinterpret_cast<freeFileExtensionsFunc>( 00206 plugin->resolve("freeFileExtensions")); 00207 Q_ASSERT(freeFileExts); 00208 char** supportedFileExtensions = getFileExts(); 00209 int i = 0; 00210 while (supportedFileExtensions[i] != NULL) 00211 { 00212 qDebug() << "Plugin supports:" << supportedFileExtensions[i]; 00213 m_extensionsSupportedByPlugins.insert(QString(supportedFileExtensions[i]), getter); 00214 i++; 00215 } 00216 freeFileExts(supportedFileExtensions); 00217 //So now we have a list of file extensions (eg. "m4a", "mp4", etc) 00218 //that map onto the getter function for this plugin (eg. the 00219 //function that returns a SoundSourceM4A object) 00220 } 00221 } 00222 return plugin; 00223 } 00224 00225 00226 int SoundSourceProxy::open() 00227 { 00228 if (!m_pSoundSource) { 00229 return 0; 00230 } 00231 int retVal = m_pSoundSource->open(); 00232 00233 //Update some metadata (currently only the duration) 00234 //after a song is open()'d. Eg. We don't know the length 00235 //of VBR MP3s until we've seeked through and counted all 00236 //the frames. We don't do that in ParseHeader() to keep 00237 //library scanning fast. 00238 // .... but only do this if the song doesn't already 00239 // have a duration parsed. (Some SoundSources don't 00240 // parse metadata on open(), so they won't have the 00241 // duration.) 00242 // SSMP3 will set duration to -1 on VBR files, 00243 // so we must look for that here too 00244 if (m_pTrack->getDuration() <= 0) 00245 m_pTrack->setDuration(m_pSoundSource->getDuration()); 00246 00247 return retVal; 00248 } 00249 00250 long SoundSourceProxy::seek(long l) 00251 { 00252 if (!m_pSoundSource) { 00253 return 0; 00254 } 00255 return m_pSoundSource->seek(l); 00256 } 00257 00258 unsigned SoundSourceProxy::read(unsigned long size, const SAMPLE * p) 00259 { 00260 if (!m_pSoundSource) { 00261 return 0; 00262 } 00263 return m_pSoundSource->read(size, p); 00264 } 00265 00266 long unsigned SoundSourceProxy::length() 00267 { 00268 if (!m_pSoundSource) { 00269 return 0; 00270 } 00271 return m_pSoundSource->length(); 00272 } 00273 00274 int SoundSourceProxy::parseHeader() 00275 { 00276 //TODO: Reorganize code so that the static ParseHeader isn't needed, and use this function instead? 00277 return 0; 00278 } 00279 00280 int SoundSourceProxy::ParseHeader(TrackInfoObject* p) 00281 { 00282 00283 QString qFilename = p->getLocation(); 00284 SoundSource* sndsrc = initialize(qFilename); 00285 if (sndsrc == NULL) 00286 return ERR; 00287 00288 if (sndsrc->parseHeader() == OK) { 00289 //Dump the metadata from the soundsource into the TIO 00290 //qDebug() << "Album:" << sndsrc->getAlbum(); //Sanity check to make sure we've actually parsed metadata and not the filename 00291 p->setArtist(sndsrc->getArtist()); 00292 QString title = sndsrc->getTitle(); 00293 if (title.isEmpty()) { 00294 // If no title is returned, use the file name (without the extension) 00295 int start = qFilename.lastIndexOf(QRegExp("[/\\\\]"))+1; 00296 int end = qFilename.lastIndexOf('.'); 00297 if (end == -1) end = qFilename.length(); 00298 title = qFilename.mid(start,end-start); 00299 } 00300 p->setTitle(title); 00301 p->setAlbum(sndsrc->getAlbum()); 00302 p->setType(sndsrc->getType()); 00303 p->setYear(sndsrc->getYear()); 00304 p->setGenre(sndsrc->getGenre()); 00305 p->setComment(sndsrc->getComment()); 00306 p->setTrackNumber(sndsrc->getTrackNumber()); 00307 p->setReplayGain(sndsrc->getReplayGain()); 00308 p->setBpm(sndsrc->getBPM()); 00309 p->setDuration(sndsrc->getDuration()); 00310 p->setBitrate(sndsrc->getBitrate()); 00311 p->setSampleRate(sndsrc->getSampleRate()); 00312 p->setChannels(sndsrc->getChannels()); 00313 p->setKey(sndsrc->getKey()); 00314 p->setHeaderParsed(true); 00315 } 00316 else 00317 { 00318 p->setHeaderParsed(false); 00319 } 00320 delete sndsrc; 00321 00322 return 0; 00323 } 00324 00325 QStringList SoundSourceProxy::supportedFileExtensions() 00326 { 00327 QMutexLocker locker(&m_extensionsMutex); 00328 QList<QString> supportedFileExtensions; 00329 #ifdef __MAD__ 00330 supportedFileExtensions.append(SoundSourceMp3::supportedFileExtensions()); 00331 #endif 00332 supportedFileExtensions.append(SoundSourceOggVorbis::supportedFileExtensions()); 00333 #ifdef __SNDFILE__ 00334 supportedFileExtensions.append(SoundSourceSndFile::supportedFileExtensions()); 00335 #endif 00336 #ifdef __COREAUDIO__ 00337 supportedFileExtensions.append(SoundSourceCoreAudio::supportedFileExtensions()); 00338 #endif 00339 supportedFileExtensions.append(m_extensionsSupportedByPlugins.keys()); 00340 00341 return supportedFileExtensions; 00342 } 00343 00344 QStringList SoundSourceProxy::supportedFileExtensionsByPlugins() { 00345 QMutexLocker locker(&m_extensionsMutex); 00346 QList<QString> supportedFileExtensions; 00347 supportedFileExtensions.append(m_extensionsSupportedByPlugins.keys()); 00348 return supportedFileExtensions; 00349 } 00350 00351 QString SoundSourceProxy::supportedFileExtensionsString() { 00352 QStringList supportedFileExtList = SoundSourceProxy::supportedFileExtensions(); 00353 // Turn the list into a "*.mp3 *.wav *.etc" style string 00354 for (int i = 0; i < supportedFileExtList.size(); ++i) { 00355 supportedFileExtList[i] = QString("*.%1").arg(supportedFileExtList[i]); 00356 } 00357 return supportedFileExtList.join(" "); 00358 } 00359 00360 QString SoundSourceProxy::supportedFileExtensionsRegex() { 00361 QStringList supportedFileExtList = SoundSourceProxy::supportedFileExtensions(); 00362 00363 // Escape every extension appropriately 00364 for (int i = 0; i < supportedFileExtList.size(); ++i) { 00365 supportedFileExtList[i] = QRegExp::escape(supportedFileExtList[i]); 00366 } 00367 00368 // Turn the list into a "\\.(mp3|wav|etc)$" style regex string 00369 return QString("\\.(%1)$").arg(supportedFileExtList.join("|")); 00370 } 00371 00372 bool SoundSourceProxy::isFilenameSupported(QString fileName) { 00373 if (m_supportedFileRegex.isValid()) { 00374 QString regex = SoundSourceProxy::supportedFileExtensionsRegex(); 00375 m_supportedFileRegex = QRegExp(regex, Qt::CaseInsensitive); 00376 } 00377 return fileName.contains(m_supportedFileRegex); 00378 } 00379 00380 00381 unsigned int SoundSourceProxy::getSampleRate() 00382 { 00383 if (!m_pSoundSource) { 00384 return 0; 00385 } 00386 return m_pSoundSource->getSampleRate(); 00387 } 00388 00389 00390 QString SoundSourceProxy::getFilename() 00391 { 00392 if (!m_pSoundSource) { 00393 return ""; 00394 } 00395 return m_pSoundSource->getFilename(); 00396 }