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

Last change on this file since 134 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

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