Merge branch 'master' of git://gitorious.org/phonon/phonon-vlc
[phonon:phonon-vlc.git] / vlc / backend.cpp
1 /*****************************************************************************
2  * libVLC backend for the Phonon library                                     *
3  * Copyright (C) 2007-2008 Tanguy Krotoff <tkrotoff@gmail.com>               *
4  * Copyright (C) 2008 Lukas Durfina <lukas.durfina@gmail.com>                *
5  * Copyright (C) 2009 Fathi Boudra <fabo@kde.org>                            *
6  * Copyright (C) 2009-2010 vlc-phonon AUTHORS                                *
7  *                                                                           *
8  * This program is free software; you can redistribute it and/or             *
9  * modify it under the terms of the GNU Lesser General Public                *
10  * License as published by the Free Software Foundation; either              *
11  * version 2.1 of 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 GNU         *
16  * Lesser General Public License for more details.                           *
17  *                                                                           *
18  * You should have received a copy of the GNU Lesser General Public          *
19  * License along with this package; if not, write to the Free Software       *
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA *
21  *****************************************************************************/
22
23 #include "backend.h"
24
25 #include "audiooutput.h"
26 #include "audiodataoutput.h"
27 #include "mediaobject.h"
28 #include "videowidget.h"
29 #include "devicemanager.h"
30 #include "effectmanager.h"
31 #include "effect.h"
32 #include "sinknode.h"
33 //#include "videodataoutput.h"
34 #include "vlcloader.h"
35 #include "vlcmediaobject.h"
36
37 #ifndef PHONON_VLC_NO_EXPERIMENTAL
38 #include "experimental/avcapture.h"
39 #endif // PHONON_VLC_NO_EXPERIMENTAL
40
41 #ifdef PHONON_PULSESUPPORT
42 #include <phonon/pulsesupport.h>
43 #endif
44
45 #include <QtCore/QSet>
46 #include <QtCore/QVariant>
47 #include <QtCore/QtPlugin>
48
49 Q_EXPORT_PLUGIN2(phonon_vlc, Phonon::VLC::Backend)
50
51 namespace Phonon
52 {
53 namespace VLC
54 {
55
56 /**
57  * Constructs the backend. Sets the backend properties, fetches the debug level from the
58  * environment, initializes libVLC, constructs the device and effect managers, initializes
59  * PulseAudio support.
60  *
61  * \param parent A parent object for the backend (passed to the QObject constructor)
62  */
63 Backend::Backend(QObject *parent, const QVariantList &)
64     : QObject(parent)
65     , m_deviceManager(NULL)
66     , m_effectManager(NULL)
67 {
68     /* Backend information properties */
69     setProperty("identifier",     QLatin1String("phonon_vlc"));
70     setProperty("backendName",    QLatin1String("VLC"));
71     setProperty("backendComment", QLatin1String("VLC backend for Phonon"));
72     setProperty("backendVersion", QLatin1String("0.3-Git"));
73     setProperty("backendIcon",    QLatin1String("vlc"));
74     setProperty("backendWebsite", QLatin1String("http://gitorious.org/phonon/phonon-vlc"));
75
76     // Check if we should enable debug output
77     int debugLevel = qgetenv("PHONON_VLC_DEBUG").toInt();
78     if (debugLevel > 3) { // 3 is maximum
79         debugLevel = 3;
80     }
81     m_debugLevel = (DebugLevel)debugLevel;
82
83     /* Actual libVLC initialisation */
84     if (vlcInit(debugLevel)) {
85         logMessage(QString("Using VLC version %0").arg(libvlc_get_version()));
86     } else {
87         qWarning("Phonon::VLC::vlcInit: Failed to initialize VLC");
88     }
89
90     m_deviceManager = new DeviceManager(this);
91     m_effectManager = new EffectManager(this);
92
93 #ifdef PHONON_PULSESUPPORT
94     // Initialise PulseAudio support
95     PulseSupport *pulse = PulseSupport::getInstance();
96     pulse->enable();
97     connect(pulse, SIGNAL(objectDescriptionChanged(ObjectDescriptionType)), SIGNAL(objectDescriptionChanged(ObjectDescriptionType)));
98 #endif
99 }
100
101 Backend::~Backend()
102 {
103 //    vlcRelease();
104 #ifdef PHONON_PULSESUPPORT
105     PulseSupport::shutdown();
106 #endif
107 }
108
109 /**
110  * Creates a backend object of the desired class and with the desired parent. Extra arguments can be provided.
111  *
112  * \param c The class of object that is to be created
113  * \param parent The object that will be the parent of the new object
114  * \param args Optional arguments for the object creation
115  * \return The desired object or NULL if the class is not implemented.
116  */
117 QObject *Backend::createObject(BackendInterface::Class c, QObject* parent, const QList<QVariant>& args)
118 {
119     #ifndef PHONON_VLC_NO_EXPERIMENTAL
120     Phonon::Experimental::BackendInterface::Class cex = static_cast<Phonon::Experimental::BackendInterface::Class>(c);
121
122     switch (cex) {
123     case Phonon::Experimental::BackendInterface::AvCaptureClass:
124         logMessage("createObject() : AvCapture created - WARNING: Experimental!");
125         return new Experimental::AvCapture(parent);
126     default:
127         if ((quint32) cex >= (quint32) Phonon::Experimental::BackendInterface::VideoDataOutputClass)
128             logMessage("createObject() - experimental : Backend object not available");
129     }
130     #endif // PHONON_VLC_NO_EXPERIMENTAL
131
132     switch (c) {
133     case MediaObjectClass:
134         return new VLCMediaObject(parent);
135     case VolumeFaderEffectClass:
136         logMessage("createObject() : VolumeFaderEffect not implemented");
137         break;
138     case AudioOutputClass: {
139         AudioOutput *ao = new AudioOutput(this, parent);
140         m_audioOutputs.append(ao);
141         return ao;
142     }
143     case AudioDataOutputClass:
144         return new AudioDataOutput(this, parent);
145         logMessage("createObject() : AudioDataOutput created - WARNING: POSSIBLY INCORRECTLY IMPLEMENTED");
146         break;
147     case VisualizationClass:
148         logMessage("createObject() : Visualization not implemented");
149         break;
150     case VideoDataOutputClass:
151 //        return new Phonon::VLC::VideoDataOutput(this, parent);
152         logMessage("createObject() : VideoDataOutput not implemented");
153         break;
154     case EffectClass:
155         return new Effect(m_effectManager, args[0].toInt(), parent);
156     case VideoWidgetClass:
157         return new VideoWidget(qobject_cast<QWidget *>(parent));
158     default:
159         logMessage("createObject() : Backend object not available");
160     }
161     return 0;
162 }
163
164 bool Backend::supportsVideo() const
165 {
166     return true;
167 }
168
169 bool Backend::supportsOSD() const
170 {
171     return true;
172 }
173
174 bool Backend::supportsFourcc(quint32 fourcc) const
175 {
176     return true;
177 }
178
179 bool Backend::supportsSubtitles() const
180 {
181     return true;
182 }
183
184 QStringList Backend::availableMimeTypes() const
185 {
186     if (m_supportedMimeTypes.isEmpty()) {
187         const_cast<Backend *>(this)->m_supportedMimeTypes
188                 << QLatin1String("application/ogg")
189                 << QLatin1String("application/vnd.rn-realmedia")
190                 << QLatin1String("application/x-annodex")
191                 << QLatin1String("application/x-flash-video")
192                 << QLatin1String("application/x-quicktimeplayer")
193                 << QLatin1String("audio/168sv")
194                 << QLatin1String("audio/8svx")
195                 << QLatin1String("audio/aiff")
196                 << QLatin1String("audio/basic")
197                 << QLatin1String("audio/mp3")
198                 << QLatin1String("audio/mp4")
199                 << QLatin1String("audio/mpeg")
200                 << QLatin1String("audio/mpeg2")
201                 << QLatin1String("audio/mpeg3")
202                 << QLatin1String("audio/vnd.rn-realaudio")
203                 << QLatin1String("audio/vnd.rn-realmedia")
204                 << QLatin1String("audio/wav")
205                 << QLatin1String("audio/webm")
206                 << QLatin1String("audio/x-16sv")
207                 << QLatin1String("audio/x-8svx")
208                 << QLatin1String("audio/x-aiff")
209                 << QLatin1String("audio/x-basic")
210                 << QLatin1String("audio/x-m4a")
211                 << QLatin1String("audio/x-mp3")
212                 << QLatin1String("audio/x-mpeg")
213                 << QLatin1String("audio/x-mpeg2")
214                 << QLatin1String("audio/x-mpeg3")
215                 << QLatin1String("audio/x-mpegurl")
216                 << QLatin1String("audio/x-ms-wma")
217                 << QLatin1String("audio/x-ogg")
218                 << QLatin1String("audio/x-pn-aiff")
219                 << QLatin1String("audio/x-pn-au")
220                 << QLatin1String("audio/x-pn-realaudio-plugin")
221                 << QLatin1String("audio/x-pn-wav")
222                 << QLatin1String("audio/x-pn-windows-acm")
223                 << QLatin1String("audio/x-real-audio")
224                 << QLatin1String("audio/x-realaudio")
225                 << QLatin1String("audio/x-speex+ogg")
226                 << QLatin1String("audio/x-vorbis+ogg")
227                 << QLatin1String("audio/x-wav")
228                 << QLatin1String("image/ilbm")
229                 << QLatin1String("image/png")
230                 << QLatin1String("image/x-ilbm")
231                 << QLatin1String("image/x-png")
232                 << QLatin1String("video/anim")
233                 << QLatin1String("video/avi")
234                 << QLatin1String("video/mkv")
235                 << QLatin1String("video/mng")
236                 << QLatin1String("video/mp4")
237                 << QLatin1String("video/mpeg")
238                 << QLatin1String("video/mpg")
239                 << QLatin1String("video/msvideo")
240                 << QLatin1String("video/quicktime")
241                 << QLatin1String("video/webm")
242                 << QLatin1String("video/x-anim")
243                 << QLatin1String("video/x-flic")
244                 << QLatin1String("video/x-mng")
245                 << QLatin1String("video/x-mpeg")
246                 << QLatin1String("video/x-ms-asf")
247                 << QLatin1String("video/x-ms-wmv")
248                 << QLatin1String("video/x-msvideo")
249                 << QLatin1String("video/x-quicktime")
250                 << QLatin1String("audio/x-flac")
251                 << QLatin1String("audio/x-ape");
252     }
253     return m_supportedMimeTypes;
254 }
255
256 /**
257  * Returns a list of indexes for the desired object types. It specifies a list of objects
258  * of a particular category that the backend knows about. These indexes can be used with
259  * objectDescriptionProperties() to get the properties of a particular object.
260  *
261  * \param type The type of objects for the list
262  */
263 QList<int> Backend::objectDescriptionIndexes(ObjectDescriptionType type) const
264 {
265     QList<int> list;
266     QList<DeviceInfo> deviceList;
267     int dev;
268
269     switch (type) {
270     case Phonon::AudioOutputDeviceType: {
271         deviceList = deviceManager()->audioOutputDevices();
272         for (dev = 0 ; dev < deviceList.size() ; ++dev)
273             list.append(deviceList[dev].id);
274     }
275     break;
276     case Phonon::AudioCaptureDeviceType: {
277         deviceList = deviceManager()->audioCaptureDevices();
278         for (dev = 0 ; dev < deviceList.size() ; ++dev)
279             list.append(deviceList[dev].id);
280     }
281     break;
282 #ifndef PHONON_VLC_NO_EXPERIMENTAL
283     case Phonon::VideoCaptureDeviceType: {
284         deviceList = deviceManager()->videoCaptureDevices();
285         for (dev = 0 ; dev < deviceList.size() ; ++dev)
286             list.append(deviceList[dev].id);
287     }
288     break;
289 #endif // PHONON_VLC_NO_EXPERIMENTAL
290     case Phonon::EffectType: {
291         QList<EffectInfo *> effectList = effectManager()->effects();
292         for (int eff = 0; eff < effectList.size(); ++eff) {
293             list.append(eff);
294         }
295     }
296     break;
297     default:
298         break;
299     }
300
301     return list;
302 }
303
304 /**
305  * Returns a list of properties for a particular object of the desired category.
306  *
307  * \param type The type of object for the index
308  * \param index The index for the object of the desired type
309  * \return The property list. If the object is inexistent, an empty list is returned.
310  */
311 QHash<QByteArray, QVariant> Backend::objectDescriptionProperties(ObjectDescriptionType type, int index) const
312 {
313     QHash<QByteArray, QVariant> ret;
314     QList<DeviceInfo> deviceList;
315
316     switch (type) {
317     case Phonon::AudioOutputDeviceType: {
318         deviceList = deviceManager()->audioOutputDevices();
319         if (index >= 0 && index < deviceList.size()) {
320             ret.insert("name", deviceList[index].name);
321             ret.insert("description", deviceList[index].description);
322             ret.insert("icon", QLatin1String("audio-card"));
323         }
324     }
325     break;
326     case Phonon::AudioCaptureDeviceType: {
327         deviceList = deviceManager()->audioCaptureDevices();
328         if (index >= 0 && index < deviceList.size()) {
329             ret.insert("name", deviceList[index].name);
330             ret.insert("description", deviceList[index].description);
331             ret.insert("icon", QLatin1String("audio-input-microphone"));
332 #ifndef PHONON_VLC_NO_EXPERIMENTAL
333             ret.insert("deviceAccessList", QVariant::fromValue<Phonon::DeviceAccessList>(deviceList[index].accessList));
334 #endif // PHONON_VLC_NO_EXPERIMENTAL
335             if (deviceList[index].capabilities & DeviceInfo::VideoCapture)
336                 ret.insert("hasvideo", true);
337         }
338     }
339     break;
340 #ifndef PHONON_VLC_NO_EXPERIMENTAL
341     case Phonon::VideoCaptureDeviceType: {
342         deviceList = deviceManager()->videoCaptureDevices();
343         if (index >= 0 && index < deviceList.size()) {
344             ret.insert("name", deviceList[index].name);
345             ret.insert("description", deviceList[index].description);
346             ret.insert("icon", QLatin1String("camera-web"));
347             ret.insert("deviceAccessList", QVariant::fromValue<Phonon::DeviceAccessList>(deviceList[index].accessList));
348             if (deviceList[index].capabilities & DeviceInfo::AudioCapture)
349                 ret.insert("hasaudio", true);
350         }
351     }
352 #endif // PHONON_VLC_NO_EXPERIMENTAL
353     break;
354     case Phonon::EffectType: {
355         QList<EffectInfo *> effectList = effectManager()->effects();
356         if (index >= 0 && index <= effectList.size()) {
357             const EffectInfo *effect = effectList[ index ];
358             ret.insert("name", effect->name());
359             ret.insert("description", effect->description());
360             ret.insert("author", effect->author());
361         } else {
362             Q_ASSERT(1); // Since we use list position as ID, this should not happen
363         }
364     }
365     break;
366     default:
367         break;
368     }
369
370     return ret;
371 }
372
373 /**
374  * Called when a connection between nodes is about to be changed
375  *
376  * \param objects A set of objects that will be involved in the change
377  */
378 bool Backend::startConnectionChange(QSet<QObject *> objects)
379 {
380     //FIXME
381     foreach(QObject * object, objects) {
382         logMessage(QString("Object: %0").arg(object->metaObject()->className()));
383     }
384
385     // There is nothing we can do but hope the connection changes will not take too long
386     // so that buffers would underrun
387     // But we should be pretty safe the way xine works by not doing anything here.
388     return true;
389 }
390
391 /**
392  * Connects two media nodes. The sink is informed that it should connect itself to the source.
393  *
394  * \param source The source media node for the connection
395  * \param sink The sink media node for the connection
396  * \return True if the connection was successful
397  */
398 bool Backend::connectNodes(QObject *source, QObject *sink)
399 {
400     logMessage(QString("Backend connected %0 to %1")
401                .arg(source->metaObject()->className())
402                .arg(sink->metaObject()->className()));
403
404     // Example:
405     // source = Phonon::VLC_MPlayer::MediaObject
406     // sink = Phonon::VLC_MPlayer::VideoWidget
407
408     // Example:
409     // source = Phonon::VLC_MPlayer::MediaObject
410     // sink = Phonon::VLC_MPlayer::AudioOutput
411
412     // Example:
413     // source = Phonon::VLC_MPlayer::MediaObject
414     // sink = Phonon::VLC_MPlayer::Effect
415
416     // Example:
417     // source = Phonon::VLC_MPlayer::Effect
418     // sink = Phonon::VLC_MPlayer::AudioOutput
419
420     // Example:
421     // source = Phonon::Experimental::AvCapture
422     // sink = Phonon::VLC::VideoWidget
423
424     SinkNode *sinkNode = qobject_cast<SinkNode *>(sink);
425     if (sinkNode) {
426         PrivateMediaObject *mediaObject = qobject_cast<PrivateMediaObject *>(source);
427         if (mediaObject) {
428             // Connect the SinkNode to a MediaObject
429             sinkNode->connectToMediaObject(mediaObject);
430             return true;
431         }
432
433         #ifndef PHONON_VLC_NO_EXPERIMENTAL
434         Experimental::AvCapture *avCapture = qobject_cast<Experimental::AvCapture *>(source);
435         if (avCapture) {
436             // Connect the SinkNode to a AvCapture
437             sinkNode->connectToAvCapture(avCapture);
438             return true;
439         }
440         #endif // PHONON_VLC_NO_EXPERIMENTAL
441
442         /*
443         Effect *effect = qobject_cast<Effect *>(source);
444         if (effect) {
445             // FIXME connect the effect
446             return true;
447         }
448         */
449     }
450
451     logMessage(QString("Linking %0 to %1 failed")
452                .arg(source->metaObject()->className())
453                .arg(sink->metaObject()->className()),
454                Warning);
455
456     return false;
457 }
458
459 /**
460  * Disconnects two previously connected media nodes. It disconnects the sink node from the source node.
461  *
462  * \param source The source node for the disconnection
463  * \param sink The sink node for the disconnection
464  * \return True if the disconnection was successful
465  */
466 bool Backend::disconnectNodes(QObject *source, QObject *sink)
467 {
468     SinkNode *sinkNode = qobject_cast<SinkNode *>(sink);
469     if (sinkNode) {
470         PrivateMediaObject *const mediaObject = qobject_cast<PrivateMediaObject *>(source);
471         if (mediaObject) {
472             // Disconnect the SinkNode from a MediaObject
473             sinkNode->disconnectFromMediaObject(mediaObject);
474             return true;
475         }
476
477         #ifndef PHONON_VLC_NO_EXPERIMENTAL
478         Experimental::AvCapture *avCapture = qobject_cast<Experimental::AvCapture *>(source);
479         if (avCapture) {
480             // Disconnect the SinkNode from a AvCapture
481             sinkNode->disconnectFromAvCapture(avCapture);
482             return true;
483         }
484         #endif // PHONON_VLC_NO_EXPERIMENTAL
485
486         /*
487         Effect *effect = qobject_cast<Effect *>(source);
488         if (effect) {
489             // FIXME disconnect the effect
490             return true;
491         }
492         */
493     }
494
495     return false;
496 }
497
498 /**
499  * Called after a connection between nodes has been changed
500  *
501  * \param objects Nodes involved in the disconnection
502  */
503 bool Backend::endConnectionChange(QSet<QObject *> objects)
504 {
505     foreach(QObject * object, objects) {
506         logMessage(QString("Object: %0").arg(object->metaObject()->className()));
507     }
508
509     return true;
510 }
511
512 /**
513  * \return The device manager that is associated with this backend object
514  */
515 DeviceManager *Backend::deviceManager() const
516 {
517     return m_deviceManager;
518 }
519
520 /**
521  * \return The effect manager that is associated with this backend object.
522  */
523 EffectManager *Backend::effectManager() const
524 {
525     return m_effectManager;
526 }
527
528 /**
529  * Return a debuglevel that is determined by the
530  * PHONON_VLC_DEBUG environment variable.
531  *
532  * \li Warning - important warnings
533  * \li Info    - general info
534  * \li Debug   - gives extra info
535  *
536  * \return The debug level.
537  */
538 Backend::DebugLevel Backend::debugLevel() const
539 {
540     return m_debugLevel;
541 }
542
543 /**
544  * Print a conditional debug message based on the current debug level
545  * If obj is provided, classname and objectname will be printed as well
546  *
547  * \param message Debug message
548  * \param priority Priority of the debug message, see debugLevel()
549  * \param obj Who gives the message, or some object relevant for the message
550  */
551 void Backend::logMessage(const QString &message, int priority, QObject *obj) const
552 {
553     if (debugLevel() > 0) {
554         QString output;
555         if (obj) {
556             // Strip away namespace from className
557             QString className(obj->metaObject()->className());
558             const int nameLength = className.length() - className.lastIndexOf(':') - 1;
559             className = className.right(nameLength);
560             output.sprintf("%s %s (%s %p)", message.toLatin1().constData(),
561                            obj->objectName().toLatin1().constData(),
562                            className.toLatin1().constData(), obj);
563         } else {
564             output = message;
565         }
566         if (priority <= (int)debugLevel()) {
567             qDebug() << QString("PVLC(%1): %2").arg(priority).arg(output);
568         }
569     }
570 }
571
572 }
573 } // Namespace Phonon::VLC