1 //=========================================================
2 //  MusE
3 //  Linux Music Editor
4 //
5 //  audio_converter_list.cpp
6 //  (C) Copyright 2010-2020 Tim E. Real (terminator356 A T sourceforge D O T net)
7 //
8 //  This program is free software; you can redistribute it and/or
9 //  modify it under the terms of the GNU General Public License
10 //  as published by the Free Software Foundation; version 2 of
11 //  the License, or (at your option) any later version.
12 //
13 //  This program is distributed in the hope that it will be useful,
14 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 //  GNU General Public License for more details.
17 //
18 //  You should have received a copy of the GNU General Public License
19 //  along with this program; if not, write to the Free Software
20 //  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 //
22 //=========================================================
23 
24 #include "audio_converter_plugin.h"
25 
26 #include <QDir>
27 #include <QString>
28 #include <QFileInfo>
29 #include <QFileInfoList>
30 #include <QByteArray>
31 
32 #include <string.h>
33 
34 #include <dlfcn.h>
35 
36 // For debugging output: Uncomment the fprintf section.
37 #define ERROR_AUDIOCONVERT(dev, format, args...) fprintf(dev, format, ##args)
38 #define INFO_AUDIOCONVERT(dev, format, args...)  fprintf(dev, format, ##args)
39 #define DEBUG_AUDIOCONVERT(dev, format, args...) // fprintf(dev, format, ##args)
40 
41 namespace MusECore {
42 
discover(const QString & museGlobalLib,bool debugMsg)43 void AudioConverterPluginList::discover(const QString& museGlobalLib, bool debugMsg)
44 {
45   QString s = museGlobalLib + "/converters";
46 
47   QDir pluginDir(s, QString("*.so"));
48   if(debugMsg)
49   {
50     INFO_AUDIOCONVERT(stderr, "searching for audio converters in <%s>\n", s.toLatin1().constData());
51   }
52   if(pluginDir.exists())
53   {
54     QFileInfoList list = pluginDir.entryInfoList();
55     const QFileInfo* fi;
56     for(QFileInfoList::const_iterator it=list.cbegin(); it!=list.cend(); ++it)
57     {
58       fi = &*it;
59 
60       QByteArray ba = fi->filePath().toLatin1();
61       const char* path = ba.constData();
62 
63       void* handle = dlopen(path, RTLD_NOW);
64       if(!handle)
65       {
66         ERROR_AUDIOCONVERT(stderr, "AudioConverterList::discover(): dlopen(%s) failed: %s\n", path, dlerror());
67         continue;
68       }
69       Audio_Converter_Descriptor_Function desc_func =
70           (Audio_Converter_Descriptor_Function)dlsym(handle, "audio_converter_descriptor");
71 
72       if(!desc_func)
73       {
74         const char *txt = dlerror();
75         if(txt)
76         {
77           ERROR_AUDIOCONVERT(stderr,
78             "Unable to find audio_converter_descriptor() function in plugin "
79             "library file \"%s\": %s.\n"
80             "Are you sure this is a MusE Audio Converter plugin file?\n",
81             path, txt);
82         }
83         dlclose(handle);
84         continue;
85       }
86 
87       const AudioConverterDescriptor* descr;
88 
89       for(unsigned long i = 0;; ++i)
90       {
91         descr = desc_func(i);
92         if(!descr)
93               break;
94         // Make sure it doesn't already exist.
95         if(find(descr->_name, descr->_ID))
96           continue;
97         add(fi, descr);
98       }
99 
100 #ifndef __KEEP_LIBRARY_OPEN__
101       // The library must be kept open to use the shared object code.
102       dlclose(handle);
103 #endif
104 
105     }
106     if(debugMsg)
107     {
108       INFO_AUDIOCONVERT(stderr, "%zd Audio converters found\n", size());
109     }
110   }
111 }
112 
113 //---------------------------------------------------------
114 //   AudioConverterPluginList
115 //---------------------------------------------------------
116 
~AudioConverterPluginList()117 AudioConverterPluginList::~AudioConverterPluginList()
118 {
119   DEBUG_AUDIOCONVERT(stderr, "AudioConverterPluginList dtor: Deleting plugins...\\n");
120   // Must close the libraries.
121   for(const_iterator ip = cbegin(); ip != cend(); ++ip)
122     if(*ip)
123       delete *ip;
124 }
125 
clearDelete()126 void AudioConverterPluginList::clearDelete()
127 {
128   DEBUG_AUDIOCONVERT(stderr, "AudioConverterPluginList clearDelete: Deleting plugins...\n");
129   // Must close the libraries.
130   for(const_iterator ip = cbegin(); ip != cend(); ++ip)
131     if(*ip)
132       delete *ip;
133   clear();
134 }
135 
add(const QFileInfo * fi,const AudioConverterDescriptor * d)136 void AudioConverterPluginList::add(const QFileInfo* fi, const AudioConverterDescriptor* d)
137 {
138   push_back(new AudioConverterPlugin(fi, d));
139 }
140 
141 //---------------------------------------------------------
142 //   find
143 //---------------------------------------------------------
144 
find(const char * name,int ID,int capabilities)145 AudioConverterPlugin* AudioConverterPluginList::find(const char* name, int ID, int capabilities)
146 {
147   const bool id_valid = (ID != -1);
148   const bool caps_valid = (capabilities != -1);
149   AudioConverterPlugin* cap_res = NULL;
150   for(const_iterator i = cbegin(); i != cend(); ++i)
151   {
152     AudioConverterPlugin* plugin = *i;
153     const bool name_match = (name && (strcmp(name, plugin->name().toLatin1().constData()) == 0));
154     const bool ID_match = (id_valid && ID == plugin->id());
155     const bool caps_match = (caps_valid && (plugin->capabilities() & capabilities) == capabilities);
156     if((name && id_valid && name_match && ID_match) ||
157        name_match ||
158        ID_match)
159       return plugin;
160     else if(caps_match)
161     {
162       if(!cap_res)
163         cap_res = plugin;
164     }
165   }
166   return cap_res;
167 }
168 
169 //---------------------------------------------------------
170 //   AudioConverterPlugin
171 //---------------------------------------------------------
172 
AudioConverterPlugin(const QFileInfo * f,const AudioConverterDescriptor * d)173 AudioConverterPlugin::AudioConverterPlugin(const QFileInfo* f, const AudioConverterDescriptor* d)
174 {
175   fi = *f;
176   plugin = NULL;
177   _descriptorFunction = NULL;
178   _handle = NULL;
179   _references = 0;
180   _instNo     = 0;
181   _label = QString(d->_label);
182   _name = QString(d->_name);
183   _uniqueID = d->_ID;
184   _maxChannels = d->_maxChannels;
185   _capabilities = d->_capabilities;
186 
187   _minStretchRatio = d->_minStretchRatio;
188   _maxStretchRatio = d->_maxStretchRatio;
189   _minSamplerateRatio = d->_minSamplerateRatio;
190   _maxSamplerateRatio = d->_maxSamplerateRatio;
191   _minPitchShiftRatio = d->_minPitchShiftRatio;
192   _maxPitchShiftRatio = d->_maxPitchShiftRatio;
193 }
194 
~AudioConverterPlugin()195 AudioConverterPlugin::~AudioConverterPlugin()
196 {
197   DEBUG_AUDIOCONVERT(stderr, "AudioConverterPlugin dtor: this:%p plugin:%p id:%d\n", this, plugin, id());
198 
199   // Actually, not an error, currently. We are the ones who purposely leave the library open.
200   // Therefore we are the ones who must close it here.
201   // TODO: Try to find a way to NOT leave the library open?
202   //if(plugin)
203   //{
204   //  ERROR_AUDIOCONVERT(stderr, "AudioConverterPlugin dtor: Error: plugin is not NULL\n");
205   //}
206 
207   if(_handle)
208   {
209     DEBUG_AUDIOCONVERT(stderr, "AudioConverterPlugin dtor: _handle is not NULL, closing library...\n");
210     dlclose(_handle);
211   }
212 
213   _handle = NULL;
214   _descriptorFunction = NULL;
215   plugin = NULL;
216 }
217 
218 
incReferences(int val)219 int AudioConverterPlugin::incReferences(int val)
220 {
221   DEBUG_AUDIOCONVERT(stderr, "AudioConverterPlugin::incReferences: this:%p id:%d _references:%d val:%d\n", this, id(), _references, val);
222 
223   int newref = _references + val;
224 
225   if(newref <= 0)
226   {
227     _references = 0;
228 
229 #ifndef __KEEP_LIBRARY_OPEN__
230     if(_handle)
231     {
232       DEBUG_AUDIOCONVERT(stderr, "  no more instances, closing library\n");
233       dlclose(_handle);
234     }
235 
236     _handle = NULL;
237     _descriptorFunction = NULL;
238     plugin = NULL;
239 
240 #endif
241 
242     return 0;
243   }
244 
245   if(!_handle)
246   {
247     _handle = dlopen(fi.filePath().toLatin1().constData(), RTLD_NOW);
248 
249     if(!_handle)
250     {
251       ERROR_AUDIOCONVERT(stderr, "AudioConverterPlugin::incReferences dlopen(%s) failed: %s\n",
252               fi.filePath().toLatin1().constData(), dlerror());
253       return 0;
254     }
255 
256     Audio_Converter_Descriptor_Function acdf = (Audio_Converter_Descriptor_Function)dlsym(_handle, "audio_converter_descriptor");
257     if(acdf)
258     {
259       const AudioConverterDescriptor* descr;
260       for(unsigned long i = 0;; ++i)
261       {
262         descr = acdf(i);
263         if(!descr)
264           break;
265 
266         QString label(descr->_label);
267         if(label == _label)
268         {
269           _descriptorFunction = acdf;
270           plugin = descr;
271           break;
272         }
273       }
274     }
275 
276     if(plugin)
277     {
278       _name = QString(plugin->_name);
279       _uniqueID = plugin->_ID;
280     }
281   }
282 
283   if(!plugin)
284   {
285     dlclose(_handle);
286     _handle = NULL;
287     _references = 0;
288     ERROR_AUDIOCONVERT(stderr, "AudioConverterPlugin::incReferences Error: %s no plugin!\n", fi.filePath().toLatin1().constData());
289     return 0;
290   }
291 
292   _references = newref;
293 
294   return _references;
295 }
296 
instantiate(AudioConverterPluginI *,int systemSampleRate,int channels,AudioConverterSettings * settings,AudioConverterSettings::ModeType mode)297 AudioConverterHandle AudioConverterPlugin::instantiate(AudioConverterPluginI* /*plugi*/,
298                                                        int systemSampleRate,
299                                                        int channels,
300                                                        AudioConverterSettings* settings,
301                                                        AudioConverterSettings::ModeType mode)
302 {
303   DEBUG_AUDIOCONVERT(stderr, "AudioConverterPlugin::instantiate\n");
304   AudioConverterHandle h = plugin->instantiate(systemSampleRate, plugin, channels, settings, mode);
305   if(!h)
306   {
307     ERROR_AUDIOCONVERT(stderr, "AudioConverterPlugin::instantiate() Error: plugin:%s instantiate failed!\n", plugin->_name);
308     return 0;
309   }
310 
311   return h;
312 }
313 
314 
315 //---------------------------------------------------------
316 //   AudioConverterPluginI
317 //---------------------------------------------------------
318 
AudioConverterPluginI()319 AudioConverterPluginI::AudioConverterPluginI()
320 {
321   DEBUG_AUDIOCONVERT(stderr, "AudioConverterPluginI ctor\n");
322   init();
323 }
324 
~AudioConverterPluginI()325 AudioConverterPluginI::~AudioConverterPluginI()
326 {
327   DEBUG_AUDIOCONVERT(stderr, "AudioConverterPluginI dtor: this:%p handle:%p _plugin:%p\n", this, handle, _plugin);
328   if(handle)
329   {
330     for(int i = 0; i < instances; ++i)
331     {
332       // Cleanup each of the converters.
333       if(_plugin)
334         _plugin->cleanup(handle[i]);
335     }
336     // Delete the array of converters.
337     delete[] handle;
338   }
339 
340   if (_plugin)
341   {
342     _plugin->incReferences(-1);
343   }
344 }
345 
init()346 void AudioConverterPluginI::init()
347 {
348   _plugin           = NULL;
349   instances         = 0;
350   handle            = NULL;
351   _channels         = 0;
352 }
353 
initPluginInstance(AudioConverterPlugin * plug,int systemSampleRate,int channels,AudioConverterSettings * settings,AudioConverterSettings::ModeType mode)354 bool AudioConverterPluginI::initPluginInstance(AudioConverterPlugin* plug,
355                                                int systemSampleRate,
356                                                int channels,
357                                                AudioConverterSettings* settings,
358                                                AudioConverterSettings::ModeType mode)
359 {
360   if(!plug)
361   {
362     ERROR_AUDIOCONVERT(stderr, "AudioConverterPluginI::initPluginInstance: Error: plug is zero\n");
363     return true;
364   }
365 
366   DEBUG_AUDIOCONVERT(stderr, "AudioConverterPluginI::initPluginInstance: plug:%p id:%d\n", plug, plug->id());
367 
368   _plugin = plug;
369   _channels = channels;
370 
371   // We are creating an object from the library. Increment the references
372   //  so that the library may be opened.
373   if(_plugin->incReferences(1) == 0)
374     return true;
375 
376 
377   QString inst("-" + QString::number(_plugin->instNo()));
378   _name  = _plugin->name() + inst;
379   _label = _plugin->label() + inst;
380 
381   int max_chans = _plugin->maxChannels(); // -1 = don't care, infinite channels.
382 
383   if(max_chans > 0)
384   {
385     instances = _channels / max_chans;
386     if(instances < 1)
387       instances = 1;
388   }
389   else
390   // Don't care. Unlimited channels.
391     instances = 1;
392 
393   handle = new AudioConverterHandle[instances];
394   for(int i = 0; i < instances; ++i)
395     handle[i] = NULL;
396 
397   for(int i = 0; i < instances; ++i)
398   {
399     DEBUG_AUDIOCONVERT(stderr, "  instance:%d\n", i);
400 
401     handle[i] = _plugin->instantiate(this, systemSampleRate, _channels, settings, mode);
402     if(!handle[i])
403       return true;
404   }
405 
406   return false;
407 }
408 
isValid() const409 bool AudioConverterPluginI::isValid() const
410 {
411   if(!handle)
412     return false;
413   for(int i = 0; i < instances; ++i)
414     if(!handle[i] || !handle[i]->isValid())
415       return false;
416   return true;
417 }
418 
setChannels(int channels)419 void AudioConverterPluginI::setChannels(int channels)
420 {
421   if(!handle) return;
422   for(int i = 0; i < instances; ++i)
423     if(handle[i])
424     {
425       // FIXME: Multiple instances point to the SAME SndFile.
426       //        Make seperate SndFile instances per-channel,
427       //         make this function per-handle.
428       //        Finish this. For now just return after the first handle found:
429       handle[i]->setChannels(channels);
430       return;
431     }
432 }
433 
reset()434 void AudioConverterPluginI::reset()
435 {
436   if(!handle) return;
437   for(int i = 0; i < instances; ++i)
438     if(handle[i])
439       handle[i]->reset();
440 }
441 
mode() const442 AudioConverterSettings::ModeType AudioConverterPluginI::mode() const
443 {
444   if(!handle)
445     return AudioConverterSettings::RealtimeMode;
446   AudioConverterSettings::ModeType fin_m = AudioConverterSettings::RealtimeMode;
447   AudioConverterSettings::ModeType m;
448   bool first = true;
449   for(int i = 0; i < instances; ++i)
450     if(handle[i])
451     {
452       m = handle[i]->mode();
453       if(m != fin_m)
454       {
455         // Check: Each instance should be in the same mode.
456         if(!first)
457         {
458           ERROR_AUDIOCONVERT(stderr,
459             "AudioConverterPluginI::mode(): Error: Different mode:%d than first:%d in instance\n", m, fin_m);
460         }
461         first = false;
462         fin_m = m;
463       }
464     }
465   return fin_m;
466 }
467 
process(SNDFILE * sf_handle,const int sf_chans,const double sf_sr_ratio,const StretchList * sf_stretch_list,const sf_count_t pos,float ** buffer,const int channels,const int frames,const bool overwrite)468 int AudioConverterPluginI::process(
469   SNDFILE* sf_handle,
470   const int sf_chans, const double sf_sr_ratio, const StretchList* sf_stretch_list,
471   const sf_count_t pos,
472   float** buffer, const int channels, const int frames, const bool overwrite)
473 {
474   if(!handle) return 0;
475   for(int i = 0; i < instances; ++i)
476     if(handle[i])
477     {
478       int count = handle[i]->process(sf_handle, sf_chans, sf_sr_ratio, sf_stretch_list,
479                                      pos, buffer, channels, frames, overwrite);
480       // FIXME: Multiple instances point to the SAME SndFile.
481       //        Make separate SndFile instances per-channel,
482       //         make this function per-handle, and work with
483       //         each count returned.
484       //        No choice, for now just return after the first handle found:
485       return count;
486     }
487   return 0;
488 }
489 
490 
491 //---------------------------------------------------------
492 //   AudioConverterSettingsI
493 //---------------------------------------------------------
494 
AudioConverterSettingsI()495 AudioConverterSettingsI::AudioConverterSettingsI()
496 {
497   DEBUG_AUDIOCONVERT(stderr, "AudioConverterSettingsI ctor\n");
498   init();
499 }
500 
~AudioConverterSettingsI()501 AudioConverterSettingsI::~AudioConverterSettingsI()
502 {
503   DEBUG_AUDIOCONVERT(stderr, "AudioConverterSettingsI dtor: this:%p\n", this);
504 
505   if(_plugin)
506   {
507     DEBUG_AUDIOCONVERT(stderr, "  _plugin:%p id:%d\n", _plugin, _plugin->id());
508     if(_settings)
509       _plugin->cleanupSettings(_settings);
510 
511     _plugin->incReferences(-1);
512   }
513 }
514 
init()515 void AudioConverterSettingsI::init()
516 {
517   _plugin    = NULL;
518   _settings  = NULL;
519 }
520 
assign(const AudioConverterSettingsI & other)521 void AudioConverterSettingsI::assign(const AudioConverterSettingsI& other)
522 {
523   _plugin = other._plugin;
524   if(!_settings)
525   {
526     // We are creating an object from the library. Increment the references
527     //  so that the library may be opened.
528     if(_plugin->incReferences(1) == 0)
529       return;
530 
531     _settings = _plugin->createSettings(false); // Don't care about isLocal argument.
532     if(!_settings)
533     {
534       // Give up the reference.
535       _plugin->incReferences(-1);
536       return;
537     }
538   }
539   _settings->assign(*other._settings);
540 }
541 
initSettingsInstance(AudioConverterPlugin * plug,bool isLocal)542 bool AudioConverterSettingsI::initSettingsInstance(AudioConverterPlugin* plug, bool isLocal)
543 {
544   if(!plug)
545   {
546     ERROR_AUDIOCONVERT(stderr, "AudioConverterSettingsI::createSettings: Error: plug is zero\n");
547     return true;
548   }
549 
550   DEBUG_AUDIOCONVERT(stderr, "AudioConverterSettingsI::initSettingsInstance: this:%p plug:%p id:%d\n", this, plug, plug->id());
551 
552   _plugin = plug;
553 
554   // We are creating an object from the library. Increment the references
555   //  so that the library may be opened.
556   if(_plugin->incReferences(1) == 0)
557     return true;
558 
559   _settings = _plugin->createSettings(isLocal);
560   if(!_settings)
561   {
562     // Give up the reference.
563     _plugin->incReferences(-1);
564     return true;
565   }
566 
567   return false;
568 }
569 
570 
571 } // namespace MusECore
572