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 "medianode.h"
20
#include "mediaobject.h"
21
#include "message.h"
22
#include "backend.h"
23
#include "gsthelper.h"
24
25
QT_BEGIN_NAMESPACE
26
27
namespace Phonon
28
{
29
namespace Gstreamer
30
{
31
32
MediaNode::MediaNode(Backend *backend, NodeDescription description) :
33
        m_isValid(false),
34
        m_root(0),
35
        m_audioTee(0),
36
        m_videoTee(0),
37
        m_fakeAudioSink(0),
38
        m_fakeVideoSink(0),
39
        m_backend(backend),
40
        m_description(description)
41
{
42
    if ((description & AudioSink) && (description & VideoSink)) {
43
        Q_ASSERT(0); // A node cannot accept both audio and video
44
    }
45
46
    if (description & AudioSource) {
47
        m_audioTee = gst_element_factory_make("tee", NULL);
48
        gst_object_ref (GST_OBJECT (m_audioTee));
49
        gst_object_sink (GST_OBJECT (m_audioTee));     
50
51
        // Fake audio sink to swallow unconnected audio pads
52
        m_fakeAudioSink = gst_element_factory_make("fakesink", NULL);
53
        g_object_set (G_OBJECT (m_fakeAudioSink), "sync", TRUE, (const char*)NULL);
54
        gst_object_ref (GST_OBJECT (m_fakeAudioSink));
55
        gst_object_sink (GST_OBJECT (m_fakeAudioSink));
56
    }
57
58
    if (description & VideoSource) {
59
        m_videoTee = gst_element_factory_make("tee", NULL);
60
        gst_object_ref (GST_OBJECT (m_videoTee));
61
        gst_object_sink (GST_OBJECT (m_videoTee));     
62
63
        // Fake video sink to swallow unconnected video pads
64
        m_fakeVideoSink = gst_element_factory_make("fakesink", NULL);
65
        g_object_set (G_OBJECT (m_fakeVideoSink), "sync", TRUE, (const char*)NULL);
66
        gst_object_ref (GST_OBJECT (m_fakeVideoSink));
67
        gst_object_sink (GST_OBJECT (m_fakeVideoSink));
68
    }
69
}
70
71
MediaNode::~MediaNode()
72
{
73
    if (m_videoTee) {
74
        gst_element_set_state(m_videoTee, GST_STATE_NULL);
75
        gst_object_unref(m_videoTee);
76
    }
77
78
    if (m_audioTee) {
79
        gst_element_set_state(m_audioTee, GST_STATE_NULL);
80
        gst_object_unref(m_audioTee);
81
    }
82
83
    if (m_fakeAudioSink) {
84
        gst_element_set_state(m_fakeAudioSink, GST_STATE_NULL);
85
        gst_object_unref(m_fakeAudioSink);
86
    }
87
88
    if (m_fakeVideoSink) {
89
        gst_element_set_state(m_fakeVideoSink, GST_STATE_NULL);
90
        gst_object_unref(m_fakeVideoSink);
91
    }
92
}
93
94
95
/**
96
 * Connects children recursively from a mediaobject root
97
 */
98
bool MediaNode::buildGraph()
99
{
100
    Q_ASSERT(root()); //We cannot build the graph without a root element source
101
102
    bool success = link();
103
104
    if (success) {
105
        // connect children recursively
106
        for (int i=0; i< m_audioSinkList.size(); ++i) {
107
            if (MediaNode *node = qobject_cast<MediaNode*>(m_audioSinkList[i])) {
108
                node->setRoot(root());
109
                if (!node->buildGraph())
110
                    success = false;
111
            }
112
        }
113
114
        for (int i=0; i < m_videoSinkList.size(); ++i) {
115
            if (MediaNode *node = qobject_cast<MediaNode*>(m_videoSinkList[i])) {
116
                node->setRoot(root());
117
                if (!node->buildGraph())
118
                    success = false;
119
            }
120
        }
121
    }
122
123
    if (!success)
124
        unlink();
125
126
    return success;
127
}
128
129
/**
130
 *  Disconnects children recursively
131
 */
132
bool MediaNode::breakGraph()
133
{
134
    for (int i=0; i<m_audioSinkList.size(); ++i) {
135
        MediaNode *node = qobject_cast<MediaNode*>(m_audioSinkList[i]);
136
        if (!node || !node->breakGraph())
137
            return false;
138
        node->setRoot(0);
139
    }
140
141
    for (int i=0; i <m_videoSinkList.size(); ++i) {
142
        MediaNode *node = qobject_cast<MediaNode*>(m_videoSinkList[i]);
143
        if (!node || !node->breakGraph())
144
            return false;
145
        node->setRoot(0);
146
    }
147
    unlink();
148
    return true;
149
}
150
151
bool MediaNode::connectNode(QObject *obj)
152
{
153
    MediaNode *sink = qobject_cast<MediaNode*>(obj);
154
155
    bool success = false;
156
157
    if (sink) {
158
159
        if (!sink->isValid()) {
160
            m_backend->logMessage(QString("Trying to link to an invalid node (%0)").arg(sink->name()), Backend::Warning);
161
            return false;
162
        }
163
164
        if (sink->root()) {
165
            m_backend->logMessage("Trying to link a node that is already linked to a different mediasource ", Backend::Warning);
166
            return false;
167
        }
168
169
        if ((m_description & AudioSource) && (sink->m_description & AudioSink)) {
170
            m_audioSinkList << obj;
171
            MediaNodeEvent event(MediaNodeEvent::AudioSinkAdded, sink);
172
            root()->mediaNodeEvent(&event);
173
            success = true;
174
        }
175
176
        if ((m_description & VideoSource) && (sink->m_description & VideoSink)) {
177
            m_videoSinkList << obj;
178
            MediaNodeEvent event(MediaNodeEvent::VideoSinkAdded, sink);
179
            root()->mediaNodeEvent(&event);
180
            success = true;
181
        }
182
183
        // If we have a root source, and we are connected
184
        // try to link the gstreamer elements
185
        if (success && root()) {
186
            MediaNodeEvent mediaObjectConnected(MediaNodeEvent::MediaObjectConnected, root());
187
            notify(&mediaObjectConnected);
188
            root()->buildGraph();
189
        }
190
    }
191
    return success;
192
}
193
194
bool MediaNode::disconnectNode(QObject *obj)
195
{
196
    MediaNode *sink = qobject_cast<MediaNode*>(obj);
197
    if (root()) {
198
        // Disconnecting elements while playing or paused seems to cause
199
        // potential deadlock. Hence we force the pipeline into ready state
200
        // before any nodes are disconnected.
201
        gst_element_set_state(root()->pipeline(), GST_STATE_READY);
202
203
        Q_ASSERT(sink->root()); //sink has to have a root since it is connected
204
205
        if (sink->description() & (AudioSink)) {
206
            GstPad *sinkPad = gst_element_get_pad(sink->audioElement(), "sink");
207
            // Release requested src pad from tee
208
            GstPad *requestedPad = gst_pad_get_peer(sinkPad);
209
            if (requestedPad) {
210
                gst_element_release_request_pad(m_audioTee, requestedPad);
211
                gst_object_unref(requestedPad);
212
            }
213
            if (GST_ELEMENT_PARENT(sink->audioElement()))
214
                gst_bin_remove(GST_BIN(root()->audioGraph()), sink->audioElement());
215
            gst_object_unref(sinkPad);
216
        }
217
218
        if (sink->description() & (VideoSink)) {
219
            GstPad *sinkPad = gst_element_get_pad(sink->videoElement(), "sink");
220
            // Release requested src pad from tee
221
            GstPad *requestedPad = gst_pad_get_peer(sinkPad);
222
            if (requestedPad) {
223
                gst_element_release_request_pad(m_videoTee, requestedPad);
224
                gst_object_unref(requestedPad);
225
            }
226
            if (GST_ELEMENT_PARENT(sink->videoElement()))
227
                gst_bin_remove(GST_BIN(root()->videoGraph()), sink->videoElement());
228
            gst_object_unref(sinkPad);
229
        }
230
231
        sink->breakGraph();
232
        sink->setRoot(0);
233
    }
234
235
    m_videoSinkList.removeAll(obj);
236
    m_audioSinkList.removeAll(obj);
237
238
    if (sink->m_description & AudioSink) {
239
        // Remove sink from graph
240
        MediaNodeEvent event(MediaNodeEvent::AudioSinkRemoved, sink);
241
        mediaNodeEvent(&event);
242
        return true;
243
    }
244
245
    if ((m_description & VideoSource) && (sink->m_description & VideoSink)) {
246
        // Remove sink from graph
247
        MediaNodeEvent event(MediaNodeEvent::VideoSinkRemoved, sink);
248
        mediaNodeEvent(&event);
249
        return true;
250
    }
251
252
    return false;
253
}
254
255
void MediaNode::mediaNodeEvent(const MediaNodeEvent *) {}
256
257
/**
258
 * Propagates an event down the graph
259
 * sender is responsible for deleting the event
260
 */
261
void MediaNode::notify(const MediaNodeEvent *event)
262
{
263
    Q_ASSERT(event);
264
    mediaNodeEvent(event);
265
    for (int i=0; i<m_audioSinkList.size(); ++i) {
266
        MediaNode *node = qobject_cast<MediaNode*>(m_audioSinkList[i]);
267
        node->notify(event);
268
    }
269
270
    for (int i=0; i<m_videoSinkList.size(); ++i) {
271
        MediaNode *node = qobject_cast<MediaNode*>(m_videoSinkList[i]);
272
        node->notify(event);
273
    }
274
}
275
276
/*
277
 * Requests a new tee pad and connects a node to it
278
 */
279
bool MediaNode::addOutput(MediaNode *output, GstElement *tee)
280
{
281
    Q_ASSERT(root());
282
283
    bool success = true;
284
285
    GstElement *sinkElement = 0;
286
    if (output->description() & AudioSink)
287
        sinkElement = output->audioElement();
288
    else if (output->description() & VideoSink)
289
        sinkElement = output->videoElement();
290
291
    Q_ASSERT(sinkElement);
292
293
    if (!sinkElement)
294
        return false;
295
296
    GstState state = GST_STATE (root()->pipeline());
297
    GstPad *srcPad = gst_element_get_request_pad (tee, "src%d");
298
    GstPad *sinkPad = gst_element_get_pad (sinkElement, "sink");
299
300
    if (!sinkPad) {
301
        success = false;
302
    } else if (gst_pad_is_linked(sinkPad)) {
303
        gst_object_unref (GST_OBJECT (sinkPad));
304
        gst_object_unref (GST_OBJECT (srcPad));
305
        return true;
306
    }
307
308
    if (success) {
309
        if (output->description() & AudioSink)
310
            gst_bin_add(GST_BIN(root()->audioGraph()), sinkElement);
311
        else if (output->description() & VideoSink)
312
            gst_bin_add(GST_BIN(root()->videoGraph()), sinkElement);
313
    }
314
315
    if (success) {
316
        gst_pad_link(srcPad, sinkPad);
317
        gst_element_set_state(sinkElement, state);
318
    } else {
319
        gst_element_release_request_pad(tee, srcPad);
320
    }
321
322
    gst_object_unref (GST_OBJECT (srcPad));
323
    gst_object_unref (GST_OBJECT (sinkPad));
324
325
    return success;
326
}
327
328
// Used to seal up unconnected source nodes by connecting unconnected src pads to fake sinks
329
bool MediaNode::connectToFakeSink(GstElement *tee, GstElement *sink, GstElement *bin)
330
{
331
    bool success = true;
332
    GstPad *sinkPad = gst_element_get_pad (sink, "sink");
333
334
    if (GST_PAD_IS_LINKED (sinkPad)) {
335
        //This fakesink is already connected
336
        gst_object_unref (sinkPad);
337
        return true;
338
    }
339
340
    GstPad *srcPad = gst_element_get_request_pad (tee, "src%d");
341
    gst_bin_add(GST_BIN(bin), sink);
342
    if (success)
343
        success = (gst_pad_link (srcPad, sinkPad) == GST_PAD_LINK_OK);
344
    if (success)
345
        success = (gst_element_set_state(sink, GST_STATE(bin)) != GST_STATE_CHANGE_FAILURE);
346
    gst_object_unref (srcPad);
347
    gst_object_unref (sinkPad);
348
    return success;
349
}
350
351
// Used to seal up unconnected source nodes by connecting unconnected src pads to fake sinks
352
bool MediaNode::releaseFakeSinkIfConnected(GstElement *tee, GstElement *fakesink, GstElement *bin)
353
{
354
    if (GST_ELEMENT_PARENT(fakesink) == GST_ELEMENT(bin)) {
355
        GstPad *sinkPad = gst_element_get_pad(fakesink, "sink");
356
357
        // Release requested src pad from tee
358
        GstPad *requestedPad = gst_pad_get_peer(sinkPad);
359
        if (requestedPad) {
360
            gst_element_release_request_pad(tee, requestedPad);
361
            gst_object_unref(requestedPad);
362
        }
363
        gst_object_unref(sinkPad);
364
365
        gst_element_set_state(fakesink, GST_STATE_NULL);
366
        gst_bin_remove(GST_BIN(bin), fakesink);
367
        Q_ASSERT(!GST_ELEMENT_PARENT(fakesink));
368
    }
369
    return true;
370
}
371
372
bool MediaNode::linkMediaNodeList(QList<QObject *> &list, GstElement *bin, GstElement *tee, GstElement *fakesink, GstElement *src)
373
{
374
    if (!GST_ELEMENT_PARENT(tee)) {
375
        gst_bin_add(GST_BIN(bin), tee);
376
        if (!gst_element_link_pads(src, "src", tee, "sink"))
377
            return false;
378
        gst_element_set_state(tee, GST_STATE(bin));
379
    }
380
    if (list.isEmpty()) {
381
        //connect node to a fake sink to avoid clogging the pipeline
382
        if (!connectToFakeSink(tee, fakesink, bin))
383
            return false;
384
    } else {
385
        // Remove fake sink if previously connected
386
        if (!releaseFakeSinkIfConnected(tee, fakesink, bin))
387
            return false;
388
389
        for (int i = 0 ; i < list.size() ; ++i) {
390
            QObject *sink = list[i];
391
            if (MediaNode *output = qobject_cast<MediaNode*>(sink)) {
392
                if (!addOutput(output, tee))
393
                    return false;
394
            }
395
        }
396
    }
397
    return true;
398
}
399
400
bool MediaNode::link()
401
{
402
    // Rewire everything
403
    if ((description() & AudioSource)) {
404
        if (!linkMediaNodeList(m_audioSinkList, root()->audioGraph(), m_audioTee, m_fakeAudioSink, audioElement()))
405
            return false;
406
    }
407
408
    if ((description() & VideoSource)) {
409
        if (!linkMediaNodeList(m_videoSinkList, root()->videoGraph(), m_videoTee, m_fakeVideoSink, videoElement()))
410
            return false;
411
    }
412
    return true;
413
}
414
415
bool MediaNode::unlink()
416
{
417
    Q_ASSERT(root());
418
    if (description() & AudioSource) {
419
        if (GST_ELEMENT_PARENT(m_audioTee) == GST_ELEMENT(root()->audioGraph())) {
420
           gst_element_set_state(m_audioTee, GST_STATE_NULL);    
421
           gst_bin_remove(GST_BIN(root()->audioGraph()), m_audioTee);
422
       }
423
        for (int i=0; i<m_audioSinkList.size(); ++i) {
424
            QObject *audioSink = m_audioSinkList[i];
425
            if (MediaNode *output = qobject_cast<MediaNode*>(audioSink)) {
426
                GstElement *element = output->audioElement();
427
                if (GST_ELEMENT_PARENT(element) == GST_ELEMENT(root()->audioGraph())) {
428
                    gst_element_set_state(element, GST_STATE_NULL);    
429
                    gst_bin_remove(GST_BIN(root()->audioGraph()), element);
430
                }
431
            }
432
        }
433
    } else if (description() & VideoSource) {
434
        if (GST_ELEMENT_PARENT(m_videoTee) == GST_ELEMENT(root()->videoGraph())) {
435
           gst_element_set_state(m_videoTee, GST_STATE_NULL);    
436
           gst_bin_remove(GST_BIN(root()->videoGraph()), m_videoTee);
437
        }
438
        for (int i=0; i <m_videoSinkList.size(); ++i) {
439
            QObject *videoSink = m_videoSinkList[i];
440
            if (MediaNode *vw = qobject_cast<MediaNode*>(videoSink)) {
441
                GstElement *element = vw->videoElement();
442
                if (GST_ELEMENT_PARENT(element) == GST_ELEMENT(root()->videoGraph())) {
443
                    gst_element_set_state(element, GST_STATE_NULL);    
444
                    gst_bin_remove(GST_BIN(root()->videoGraph()), element);
445
                }
446
            }
447
        }
448
    }
449
    return true;
450
}
451
452
453
} // ns Gstreamer
454
} // ns Phonon
455
456
QT_END_NAMESPACE