source: trunk/src/3rdparty/phonon/gstreamer/backend.cpp

Last change on this file was 846, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 14.9 KB
Line 
1/* This file is part of the KDE project.
2
3 Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4
5 This library is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation, either version 2.1 or 3 of the License.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public License
15 along with this library. If not, see <http://www.gnu.org/licenses/>.
16*/
17
18#include "common.h"
19#include "backend.h"
20#include "audiooutput.h"
21#include "audiodataoutput.h"
22#include "audioeffect.h"
23#include "mediaobject.h"
24#include "videowidget.h"
25#include "devicemanager.h"
26#include "effectmanager.h"
27#include "message.h"
28#include "volumefadereffect.h"
29#include <gst/interfaces/propertyprobe.h>
30#include <phonon/pulsesupport.h>
31
32#include <QtCore/QSet>
33#include <QtCore/QVariant>
34#include <QtCore/QtPlugin>
35
36QT_BEGIN_NAMESPACE
37
38Q_EXPORT_PLUGIN2(phonon_gstreamer, Phonon::Gstreamer::Backend)
39
40namespace Phonon
41{
42namespace Gstreamer
43{
44
45class MediaNode;
46
47Backend::Backend(QObject *parent, const QVariantList &)
48 : QObject(parent)
49 , m_deviceManager(0)
50 , m_effectManager(0)
51 , m_debugLevel(Warning)
52 , m_isValid(false)
53{
54 // Initialise PulseAudio support
55 PulseSupport *pulse = PulseSupport::getInstance();
56 pulse->enable();
57 connect(pulse, SIGNAL(objectDescriptionChanged(ObjectDescriptionType)), SIGNAL(objectDescriptionChanged(ObjectDescriptionType)));
58
59 // In order to support reloading, we only set the app name once...
60 static bool first = true;
61 if (first) {
62 first = false;
63 g_set_application_name(qApp->applicationName().toUtf8());
64 }
65 GError *err = 0;
66 bool wasInit = gst_init_check(0, 0, &err); //init gstreamer: must be called before any gst-related functions
67 if (err)
68 g_error_free(err);
69
70 qRegisterMetaType<Message>("Message");
71#ifndef QT_NO_PROPERTIES
72 setProperty("identifier", QLatin1String("phonon_gstreamer"));
73 setProperty("backendName", QLatin1String("Gstreamer"));
74 setProperty("backendComment", QLatin1String("Gstreamer plugin for Phonon"));
75 setProperty("backendVersion", QLatin1String("0.2"));
76 setProperty("backendWebsite", QLatin1String("http://qt.nokia.com/"));
77#endif //QT_NO_PROPERTIES
78
79 //check if we should enable debug output
80 QString debugLevelString = qgetenv("PHONON_GST_DEBUG");
81 int debugLevel = debugLevelString.toInt();
82 if (debugLevel > 3) //3 is maximum
83 debugLevel = 3;
84 m_debugLevel = (DebugLevel)debugLevel;
85
86 if (wasInit) {
87 m_isValid = checkDependencies();
88 gchar *versionString = gst_version_string();
89 logMessage(QString("Using %0").arg(versionString));
90 g_free(versionString);
91 }
92 if (!m_isValid)
93 qWarning("Phonon::GStreamer::Backend: Failed to initialize GStreamer");
94
95 m_deviceManager = new DeviceManager(this);
96 m_effectManager = new EffectManager(this);
97}
98
99Backend::~Backend()
100{
101 delete m_effectManager;
102 delete m_deviceManager;
103 PulseSupport::shutdown();
104}
105
106gboolean Backend::busCall(GstBus *bus, GstMessage *msg, gpointer data)
107{
108 Q_UNUSED(bus);
109 Q_ASSERT(msg);
110
111 MediaObject *mediaObject = static_cast<MediaObject*>(data);
112 Q_ASSERT(mediaObject);
113
114 Message message(msg, mediaObject);
115 QMetaObject::invokeMethod(mediaObject->backend(), "handleBusMessage", Qt::QueuedConnection, Q_ARG(Message, message));
116
117 return true;
118}
119
120/***
121 * !reimp
122 */
123QObject *Backend::createObject(BackendInterface::Class c, QObject *parent, const QList<QVariant> &args)
124{
125 // Return nothing if dependencies are not met
126
127 switch (c) {
128 case MediaObjectClass:
129 return new MediaObject(this, parent);
130
131 case AudioOutputClass:
132 return new AudioOutput(this, parent);
133
134#ifndef QT_NO_PHONON_EFFECT
135 case EffectClass:
136 return new AudioEffect(this, args[0].toInt(), parent);
137#endif //QT_NO_PHONON_EFFECT
138 case AudioDataOutputClass:
139 return new AudioDataOutput(this, parent);
140
141#ifndef QT_NO_PHONON_VIDEO
142 case VideoDataOutputClass:
143 logMessage("createObject() : VideoDataOutput not implemented");
144 break;
145
146 case VideoWidgetClass: {
147 QWidget *widget = qobject_cast<QWidget*>(parent);
148 return new VideoWidget(this, widget);
149 }
150#endif //QT_NO_PHONON_VIDEO
151#ifndef QT_NO_PHONON_VOLUMEFADEREFFECT
152 case VolumeFaderEffectClass:
153 return new VolumeFaderEffect(this, parent);
154#endif //QT_NO_PHONON_VOLUMEFADEREFFECT
155
156 case VisualizationClass: //Fall through
157 default:
158 logMessage("createObject() : Backend object not available");
159 }
160 return 0;
161}
162
163// Returns true if all dependencies are met
164// and gstreamer is usable, otherwise false
165bool Backend::isValid() const
166{
167 return m_isValid;
168}
169
170bool Backend::supportsVideo() const
171{
172 return isValid();
173}
174
175bool Backend::checkDependencies() const
176{
177 bool success = false;
178 // Verify that gst-plugins-base is installed
179 GstElementFactory *acFactory = gst_element_factory_find ("audioconvert");
180 if (acFactory) {
181 gst_object_unref(acFactory);
182 success = true;
183 // Check if gst-plugins-good is installed
184 GstElementFactory *csFactory = gst_element_factory_find ("videobalance");
185 if (csFactory) {
186 gst_object_unref(csFactory);
187 } else {
188 QString message = tr("Warning: You do not seem to have the package gstreamer0.10-plugins-good installed.\n"
189 " Some video features have been disabled.");
190 qDebug() << message;
191 }
192 } else {
193 qWarning() << tr("Warning: You do not seem to have the base GStreamer plugins installed.\n"
194 " All audio and video support has been disabled");
195 }
196 return success;
197}
198
199/***
200 * !reimp
201 */
202QStringList Backend::availableMimeTypes() const
203{
204 QStringList availableMimeTypes;
205
206 if (!isValid())
207 return availableMimeTypes;
208
209 GstElementFactory *mpegFactory;
210 // Add mp3 as a separate mime type as people are likely to look for it.
211 if ((mpegFactory = gst_element_factory_find ("ffmpeg")) ||
212 (mpegFactory = gst_element_factory_find ("mad"))) {
213 availableMimeTypes << QLatin1String("audio/x-mp3");
214 gst_object_unref(GST_OBJECT(mpegFactory));
215 }
216
217 // Iterate over all audio and video decoders and extract mime types from sink caps
218 GList* factoryList = gst_registry_get_feature_list(gst_registry_get_default (), GST_TYPE_ELEMENT_FACTORY);
219 for (GList* iter = g_list_first(factoryList) ; iter != NULL ; iter = g_list_next(iter)) {
220 GstPluginFeature *feature = GST_PLUGIN_FEATURE(iter->data);
221 QString klass = gst_element_factory_get_klass(GST_ELEMENT_FACTORY(feature));
222
223 if (klass == QLatin1String("Codec/Decoder") ||
224 klass == QLatin1String("Codec/Decoder/Audio") ||
225 klass == QLatin1String("Codec/Decoder/Video") ||
226 klass == QLatin1String("Codec/Demuxer") ||
227 klass == QLatin1String("Codec/Demuxer/Audio") ||
228 klass == QLatin1String("Codec/Demuxer/Video") ||
229 klass == QLatin1String("Codec/Parser") ||
230 klass == QLatin1String("Codec/Parser/Audio") ||
231 klass == QLatin1String("Codec/Parser/Video")) {
232
233 const GList *static_templates;
234 GstElementFactory *factory = GST_ELEMENT_FACTORY(feature);
235 static_templates = gst_element_factory_get_static_pad_templates(factory);
236
237 for (; static_templates != NULL ; static_templates = static_templates->next) {
238 GstStaticPadTemplate *padTemplate = (GstStaticPadTemplate *) static_templates->data;
239 if (padTemplate && padTemplate->direction == GST_PAD_SINK) {
240 GstCaps *caps = gst_static_pad_template_get_caps (padTemplate);
241
242 if (caps) {
243 for (unsigned int struct_idx = 0; struct_idx < gst_caps_get_size (caps); struct_idx++) {
244
245 const GstStructure* capsStruct = gst_caps_get_structure (caps, struct_idx);
246 QString mime = QString::fromUtf8(gst_structure_get_name (capsStruct));
247 if (!availableMimeTypes.contains(mime))
248 availableMimeTypes.append(mime);
249 }
250 }
251 }
252 }
253 }
254 }
255 g_list_free(factoryList);
256 if (availableMimeTypes.contains("audio/x-vorbis")
257 && availableMimeTypes.contains("application/x-ogm-audio")) {
258 if (!availableMimeTypes.contains("audio/x-vorbis+ogg"))
259 availableMimeTypes.append("audio/x-vorbis+ogg");
260 if (!availableMimeTypes.contains("application/ogg")) /* *.ogg */
261 availableMimeTypes.append("application/ogg");
262 if (!availableMimeTypes.contains("audio/ogg")) /* *.oga */
263 availableMimeTypes.append("audio/ogg");
264 }
265 availableMimeTypes.sort();
266 return availableMimeTypes;
267}
268
269/***
270 * !reimp
271 */
272QList<int> Backend::objectDescriptionIndexes(ObjectDescriptionType type) const
273{
274 QList<int> list;
275
276 if (!isValid())
277 return list;
278
279 switch (type) {
280 case Phonon::AudioOutputDeviceType: {
281 QList<AudioDevice> deviceList = deviceManager()->audioOutputDevices();
282 for (int dev = 0 ; dev < deviceList.size() ; ++dev)
283 list.append(deviceList[dev].id);
284 break;
285 }
286 break;
287
288 case Phonon::EffectType: {
289 QList<EffectInfo*> effectList = effectManager()->audioEffects();
290 for (int eff = 0 ; eff < effectList.size() ; ++eff)
291 list.append(eff);
292 break;
293 }
294 break;
295 default:
296 break;
297 }
298 return list;
299}
300
301/***
302 * !reimp
303 */
304QHash<QByteArray, QVariant> Backend::objectDescriptionProperties(ObjectDescriptionType type, int index) const
305{
306
307 QHash<QByteArray, QVariant> ret;
308
309 if (!isValid())
310 return ret;
311
312 switch (type) {
313 case Phonon::AudioOutputDeviceType: {
314 AudioDevice* ad;
315 if ((ad = deviceManager()->audioDevice(index))) {
316 ret.insert("name", ad->gstId);
317 ret.insert("description", ad->description);
318 ret.insert("icon", ad->icon);
319 }
320 }
321 break;
322
323 case Phonon::EffectType: {
324 QList<EffectInfo*> effectList = effectManager()->audioEffects();
325 if (index >= 0 && index <= effectList.size()) {
326 const EffectInfo *effect = effectList[index];
327 ret.insert("name", effect->name());
328 ret.insert("description", effect->description());
329 ret.insert("author", effect->author());
330 } else
331 Q_ASSERT(1); // Since we use list position as ID, this should not happen
332 }
333 default:
334 break;
335 }
336 return ret;
337}
338
339/***
340 * !reimp
341 */
342bool Backend::startConnectionChange(QSet<QObject *> objects)
343{
344 foreach (QObject *object, objects) {
345 MediaNode *sourceNode = qobject_cast<MediaNode *>(object);
346 MediaObject *media = sourceNode->root();
347 if (media) {
348 media->saveState();
349 return true;
350 }
351 }
352 return true;
353}
354
355/***
356 * !reimp
357 */
358bool Backend::connectNodes(QObject *source, QObject *sink)
359{
360 if (isValid()) {
361 MediaNode *sourceNode = qobject_cast<MediaNode *>(source);
362 MediaNode *sinkNode = qobject_cast<MediaNode *>(sink);
363 if (sourceNode && sinkNode) {
364 if (sourceNode->connectNode(sink)) {
365 sourceNode->root()->invalidateGraph();
366 logMessage(QString("Backend connected %0 to %1").arg(source->metaObject()->className()).arg(sink->metaObject()->className()));
367 return true;
368 }
369 }
370 }
371 logMessage(QString("Linking %0 to %1 failed").arg(source->metaObject()->className()).arg(sink->metaObject()->className()), Warning);
372 return false;
373}
374
375/***
376 * !reimp
377 */
378bool Backend::disconnectNodes(QObject *source, QObject *sink)
379{
380 MediaNode *sourceNode = qobject_cast<MediaNode *>(source);
381 MediaNode *sinkNode = qobject_cast<MediaNode *>(sink);
382
383 if (sourceNode && sinkNode)
384 return sourceNode->disconnectNode(sink);
385 else
386 return false;
387}
388
389/***
390 * !reimp
391 */
392bool Backend::endConnectionChange(QSet<QObject *> objects)
393{
394 foreach (QObject *object, objects) {
395 MediaNode *sourceNode = qobject_cast<MediaNode *>(object);
396 MediaObject *media = sourceNode->root();
397 if (media) {
398 media->resumeState();
399 return true;
400 }
401 }
402 return true;
403}
404
405/***
406 * Request bus messages for this mediaobject
407 */
408void Backend::addBusWatcher(MediaObject* node)
409{
410 Q_ASSERT(node);
411 GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE(node->pipeline()));
412 gst_bus_add_watch (bus, busCall, node);
413 gst_object_unref(bus);
414}
415
416/***
417 * Ignore bus messages for this mediaobject
418 */
419void Backend::removeBusWatcher(MediaObject* node)
420{
421 Q_ASSERT(node);
422 g_source_remove_by_user_data(node);
423}
424
425/***
426 * Polls each mediaobject's pipeline and delivers
427 * pending any pending messages
428 */
429void Backend::handleBusMessage(Message message)
430{
431 MediaObject *mediaObject = message.source();
432 mediaObject->handleBusMessage(message);
433}
434
435DeviceManager* Backend::deviceManager() const
436{
437 return m_deviceManager;
438}
439
440EffectManager* Backend::effectManager() const
441{
442 return m_effectManager;
443}
444
445/**
446 * Returns a debuglevel that is determined by the
447 * PHONON_GST_DEBUG environment variable.
448 *
449 * Warning - important warnings
450 * Info - general info
451 * Debug - gives extra info
452 */
453Backend::DebugLevel Backend::debugLevel() const
454{
455 return m_debugLevel;
456}
457
458/***
459 * Prints a conditional debug message based on the current debug level
460 * If obj is provided, classname and objectname will be printed as well
461 *
462 * see debugLevel()
463 */
464void Backend::logMessage(const QString &message, int priority, QObject *obj) const
465{
466 if (debugLevel() > 0) {
467 QString output;
468 if (obj) {
469 // Strip away namespace from className
470 QString className(obj->metaObject()->className());
471 int nameLength = className.length() - className.lastIndexOf(':') - 1;
472 className = className.right(nameLength);
473 output.sprintf("%s %s (%s %p)", message.toLatin1().constData(),
474 obj->objectName().toLatin1().constData(),
475 className.toLatin1().constData(), obj);
476 }
477 else {
478 output = message;
479 }
480 if (priority <= (int)debugLevel()) {
481 qDebug() << QString("PGST(%1): %2").arg(priority).arg(output);
482 }
483 }
484}
485
486}
487}
488
489QT_END_NAMESPACE
490
491#include "moc_backend.cpp"
Note: See TracBrowser for help on using the repository browser.