ges-pipeline.c 43 KB
Newer Older
Edward Hervey's avatar
Edward Hervey committed
1
/* GStreamer Editing Services
Edward Hervey's avatar
Edward Hervey committed
2
3
 * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk>
 *               2009 Nokia Corporation
Edward Hervey's avatar
Edward Hervey committed
4
5
6
7
8
9
10
11
12
13
14
15
16
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
Tim-Philipp Müller's avatar
Tim-Philipp Müller committed
17
18
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
Edward Hervey's avatar
Edward Hervey committed
19
20
 */

21
/**
22
 * SECTION:gespipeline
23
 * @title: GESPipeline
Thibault Saunier's avatar
Thibault Saunier committed
24
 * @short_description: Convenience GstPipeline for editing.
Edward Hervey's avatar
Edward Hervey committed
25
 *
26
 * #GESPipeline allows developers to view and render #GESTimeline
27
28
 * in a simple fashion.
 * Its usage is inspired by the 'playbin' element from gst-plugins-base.
Edward Hervey's avatar
Edward Hervey committed
29
 */
30
31
32
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
Edward Hervey's avatar
Edward Hervey committed
33

34
#include <gst/gst.h>
35
#include <gst/video/videooverlay.h>
36
#include <stdio.h>
37

38
#include "ges-internal.h"
39
#include "ges-pipeline.h"
40
#include "ges-screenshot.h"
41
42
#include "ges-audio-track.h"
#include "ges-video-track.h"
43

Thibault Saunier's avatar
Thibault Saunier committed
44
45
46
47
GST_DEBUG_CATEGORY_STATIC (ges_pipeline_debug);
#undef GST_CAT_DEFAULT
#define GST_CAT_DEFAULT ges_pipeline_debug

48
#define DEFAULT_TIMELINE_MODE  GES_PIPELINE_MODE_PREVIEW
49
#define IN_RENDERING_MODE(timeline) ((timeline->priv->mode) & (GES_PIPELINE_MODE_RENDER | GES_PIPELINE_MODE_SMART_RENDER))
50
#define CHECK_THREAD(pipeline) g_assert(pipeline->priv->valid_thread == g_thread_self())
51

52
53
54
55
/* Structure corresponding to a timeline - sink link */

typedef struct
{
56
  GESTrack *track;
57
58
  GstElement *tee;
  GstPad *srcpad;               /* Timeline source pad */
59
60
  GstPad *playsinkpad;
  GstPad *encodebinpad;
61
62

  guint query_position_id;
63
64
} OutputChain;

65

66
struct _GESPipelinePrivate
67
68
69
70
71
72
73
74
75
{
  GESTimeline *timeline;
  GstElement *playsink;
  GstElement *encodebin;
  /* Note : urisink is only created when a URI has been provided */
  GstElement *urisink;

  GESPipelineFlags mode;

76
  GMutex dyn_mutex;
77
  GList *chains;
78
  GList *not_rendered_tracks;
79
80

  GstEncodingProfile *profile;
81
82

  GThread *valid_thread;
83
84
};

85
86
87
88
89
enum
{
  PROP_0,
  PROP_AUDIO_SINK,
  PROP_VIDEO_SINK,
90
91
  PROP_TIMELINE,
  PROP_MODE,
92
93
  PROP_AUDIO_FILTER,
  PROP_VIDEO_FILTER,
94
95
96
97
98
  PROP_LAST
};

static GParamSpec *properties[PROP_LAST];

99
static GstStateChangeReturn ges_pipeline_change_state (GstElement *
100
    element, GstStateChange transition);
Edward Hervey's avatar
Edward Hervey committed
101

102
static OutputChain *get_output_chain_for_track (GESPipeline * self,
103
    GESTrack * track);
104
static OutputChain *new_output_chain_for_track (GESPipeline * self,
105
    GESTrack * track);
Thibault Saunier's avatar
Thibault Saunier committed
106
107
static void _link_track (GESPipeline * self, GESTrack * track);
static void _unlink_track (GESPipeline * self, GESTrack * track);
Edward Hervey's avatar
Edward Hervey committed
108

109
110
111
112
113
114
/****************************************************
 *    Video Overlay vmethods implementation         *
 ****************************************************/
static void
_overlay_expose (GstVideoOverlay * overlay)
{
115
  GESPipeline *pipeline = GES_PIPELINE (overlay);
116
117
118
119
120
121
122

  gst_video_overlay_expose (GST_VIDEO_OVERLAY (pipeline->priv->playsink));
}

static void
_overlay_handle_events (GstVideoOverlay * overlay, gboolean handle_events)
{
123
  GESPipeline *pipeline = GES_PIPELINE (overlay);
124
125
126
127
128
129
130
131
132

  gst_video_overlay_handle_events (GST_VIDEO_OVERLAY (pipeline->priv->playsink),
      handle_events);
}

static void
_overlay_set_render_rectangle (GstVideoOverlay * overlay, gint x,
    gint y, gint width, gint height)
{
133
  GESPipeline *pipeline = GES_PIPELINE (overlay);
134

135
136
  gst_video_overlay_set_render_rectangle (GST_VIDEO_OVERLAY (pipeline->priv->
          playsink), x, y, width, height);
137
138
139
140
141
}

static void
_overlay_set_window_handle (GstVideoOverlay * overlay, guintptr handle)
{
142
  GESPipeline *pipeline = GES_PIPELINE (overlay);
143

144
145
  gst_video_overlay_set_window_handle (GST_VIDEO_OVERLAY (pipeline->
          priv->playsink), handle);
146
147
148
149
150
151
152
153
154
155
156
157
158
}

static void
video_overlay_init (gpointer g_iface, gpointer g_iface_data)
{
  GstVideoOverlayInterface *iface = (GstVideoOverlayInterface *) g_iface;

  iface->expose = _overlay_expose;
  iface->handle_events = _overlay_handle_events;
  iface->set_render_rectangle = _overlay_set_render_rectangle;
  iface->set_window_handle = _overlay_set_window_handle;
}

159
G_DEFINE_TYPE_WITH_CODE (GESPipeline, ges_pipeline,
160
    GST_TYPE_PIPELINE, G_ADD_PRIVATE (GESPipeline)
161
162
    G_IMPLEMENT_INTERFACE (GST_TYPE_VIDEO_OVERLAY, video_overlay_init));

163
static void
164
ges_pipeline_get_property (GObject * object, guint property_id,
165
166
    GValue * value, GParamSpec * pspec)
{
167
  GESPipeline *self = GES_PIPELINE (object);
168
169
170
171

  switch (property_id) {
    case PROP_AUDIO_SINK:
      g_object_get_property (G_OBJECT (self->priv->playsink), "audio-sink",
172
          value);
173
174
175
      break;
    case PROP_VIDEO_SINK:
      g_object_get_property (G_OBJECT (self->priv->playsink), "video-sink",
176
          value);
177
      break;
178
179
180
181
182
183
    case PROP_TIMELINE:
      g_value_set_object (value, self->priv->timeline);
      break;
    case PROP_MODE:
      g_value_set_flags (value, self->priv->mode);
      break;
184
185
186
187
188
189
190
191
    case PROP_AUDIO_FILTER:
      g_object_get_property (G_OBJECT (self->priv->playsink), "audio-filter",
          value);
      break;
    case PROP_VIDEO_FILTER:
      g_object_get_property (G_OBJECT (self->priv->playsink), "video-filter",
          value);
      break;
192
193
194
195
196
197
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
  }
}

static void
198
ges_pipeline_set_property (GObject * object, guint property_id,
199
200
    const GValue * value, GParamSpec * pspec)
{
201
  GESPipeline *self = GES_PIPELINE (object);
202
203
204
205

  switch (property_id) {
    case PROP_AUDIO_SINK:
      g_object_set_property (G_OBJECT (self->priv->playsink), "audio-sink",
206
          value);
207
208
209
      break;
    case PROP_VIDEO_SINK:
      g_object_set_property (G_OBJECT (self->priv->playsink), "video-sink",
210
          value);
211
      break;
212
    case PROP_TIMELINE:
213
      ges_pipeline_set_timeline (GES_PIPELINE (object),
214
          g_value_get_object (value));
215
216
      break;
    case PROP_MODE:
217
      ges_pipeline_set_mode (GES_PIPELINE (object), g_value_get_flags (value));
218
      break;
219
220
221
222
223
224
225
226
    case PROP_AUDIO_FILTER:
      g_object_set (self->priv->playsink, "audio-filter",
          GST_ELEMENT (g_value_get_object (value)), NULL);
      break;
    case PROP_VIDEO_FILTER:
      g_object_set (self->priv->playsink, "video-filter",
          GST_ELEMENT (g_value_get_object (value)), NULL);
      break;
227
228
229
230
231
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
  }
}

232
static void
233
234
_timeline_track_added_cb (GESTimeline * timeline, GESTrack * track,
    GESPipeline * pipeline)
235
{
236
237
238
  track_disable_last_gap (track,
      ! !(pipeline->priv->mode & (GES_PIPELINE_MODE_RENDER |
              GES_PIPELINE_MODE_SMART_RENDER)));
239
240
241
242
  _link_track (pipeline, track);
}

static void
243
244
_timeline_track_removed_cb (GESTimeline * timeline, GESTrack * track,
    GESPipeline * pipeline)
245
246
247
248
{
  _unlink_track (pipeline, track);
}

Edward Hervey's avatar
Edward Hervey committed
249
static void
250
ges_pipeline_dispose (GObject * object)
Edward Hervey's avatar
Edward Hervey committed
251
{
252
  GESPipeline *self = GES_PIPELINE (object);
253

254
  if (self->priv->playsink) {
255
    if (self->priv->mode & (GES_PIPELINE_MODE_PREVIEW))
256
      gst_bin_remove (GST_BIN (object), self->priv->playsink);
257
    else
258
259
      gst_object_unref (self->priv->playsink);
    self->priv->playsink = NULL;
260
  }
261

262
  if (self->priv->encodebin) {
263
264
    if (self->priv->mode & (GES_PIPELINE_MODE_RENDER |
            GES_PIPELINE_MODE_SMART_RENDER))
265
      gst_bin_remove (GST_BIN (object), self->priv->encodebin);
266
    else
267
268
      gst_object_unref (self->priv->encodebin);
    self->priv->encodebin = NULL;
269
270
  }

271
  if (self->priv->profile) {
272
    gst_encoding_profile_unref (self->priv->profile);
273
    self->priv->profile = NULL;
274
275
  }

276
277
278
279
280
  if (self->priv->timeline) {
    g_signal_handlers_disconnect_by_func (self->priv->timeline,
        _timeline_track_added_cb, self);
    g_signal_handlers_disconnect_by_func (self->priv->timeline,
        _timeline_track_removed_cb, self);
281
    gst_element_set_state (GST_ELEMENT (self->priv->timeline), GST_STATE_NULL);
282
283
  }

284
  G_OBJECT_CLASS (ges_pipeline_parent_class)->dispose (object);
Edward Hervey's avatar
Edward Hervey committed
285
286
287
}

static void
288
ges_pipeline_class_init (GESPipelineClass * klass)
Edward Hervey's avatar
Edward Hervey committed
289
290
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
291
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
Edward Hervey's avatar
Edward Hervey committed
292

Thibault Saunier's avatar
Thibault Saunier committed
293
294
295
  GST_DEBUG_CATEGORY_INIT (ges_pipeline_debug, "gespipeline",
      GST_DEBUG_FG_YELLOW, "ges pipeline");

296
297
298
  object_class->dispose = ges_pipeline_dispose;
  object_class->get_property = ges_pipeline_get_property;
  object_class->set_property = ges_pipeline_set_property;
299
300

  /**
301
   * GESPipeline:audio-sink:
302
303
304
305
306
307
308
309
   *
   * Audio sink for the preview.
   */
  properties[PROP_AUDIO_SINK] = g_param_spec_object ("audio-sink", "Audio Sink",
      "Audio sink for the preview.",
      GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  /**
310
   * GESPipeline:video-sink:
311
312
313
314
315
316
   *
   * Video sink for the preview.
   */
  properties[PROP_VIDEO_SINK] = g_param_spec_object ("video-sink", "Video Sink",
      "Video sink for the preview.",
      GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
317

318
  /**
319
   * GESPipeline:timeline:
320
321
   *
   * Timeline to use in this pipeline. See also
322
   * ges_pipeline_set_timeline() for more info.
323
324
325
   */
  properties[PROP_TIMELINE] = g_param_spec_object ("timeline", "Timeline",
      "Timeline to use in this pipeline. See also "
326
      "ges_pipeline_set_timeline() for more info.",
327
328
329
      GES_TYPE_TIMELINE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  /**
330
   * GESPipeline:mode:
331
   *
332
   * Pipeline mode. See ges_pipeline_set_mode() for more
333
334
335
   * info.
   */
  properties[PROP_MODE] = g_param_spec_flags ("mode", "Mode",
336
      "Pipeline mode. See ges_pipeline_set_mode() for more info.",
337
338
      GES_TYPE_PIPELINE_FLAGS, DEFAULT_TIMELINE_MODE,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364

  /**
   * GESPipeline::audio-filter
   *
   * The audio filter(s) to apply during playback right before the audio sink
   *
   * Since: 1.6.0
   */
  properties[PROP_AUDIO_FILTER] =
      g_param_spec_object ("audio-filter", "Audio filter",
      "the audio filter(s) to apply, if possible", GST_TYPE_ELEMENT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  /**
   * GESPipeline::video-filter
   *
   * The video filter(s) to apply during playback right before the video sink
   *
   * Since: 1.6.0
   */
  properties[PROP_VIDEO_FILTER] =
      g_param_spec_object ("video-filter", "Video filter",
      "the Video filter(s) to apply, if possible", GST_TYPE_ELEMENT,
      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

  g_object_class_install_properties (object_class, PROP_LAST, properties);
365

366
  element_class->change_state = GST_DEBUG_FUNCPTR (ges_pipeline_change_state);
367
368
369

  /* TODO : Add state_change handlers
   * Don't change state if we don't have a timeline */
Edward Hervey's avatar
Edward Hervey committed
370
371
372
}

static void
373
ges_pipeline_init (GESPipeline * self)
Edward Hervey's avatar
Edward Hervey committed
374
{
375
  GST_INFO_OBJECT (self, "Creating new 'playsink'");
376
  self->priv = ges_pipeline_get_instance_private (self);
377
  self->priv->valid_thread = g_thread_self ();
378

379
380
381
  self->priv->playsink =
      gst_element_factory_make ("playsink", "internal-sinks");
  self->priv->encodebin =
382
      gst_element_factory_make ("encodebin", "internal-encodebin");
383
  g_object_set (self->priv->encodebin, "avoid-reencoding", TRUE, NULL);
384

385
  if (G_UNLIKELY (self->priv->playsink == NULL))
386
    goto no_playsink;
387
  if (G_UNLIKELY (self->priv->encodebin == NULL))
388
    goto no_encodebin;
389

390
  ges_pipeline_set_mode (self, DEFAULT_TIMELINE_MODE);
391

392
393
  return;

394
395
396
397
398
399
400
401
402
403
no_playsink:
  {
    GST_ERROR_OBJECT (self, "Can't create playsink instance !");
    return;
  }
no_encodebin:
  {
    GST_ERROR_OBJECT (self, "Can't create encodebin instance !");
    return;
  }
Edward Hervey's avatar
Edward Hervey committed
404
405
}

406
/**
407
 * ges_pipeline_new:
408
 *
409
 * Creates a new conveninence #GESPipeline.
410
 *
411
 * Returns: (transfer floating): the new #GESPipeline.
412
 */
413
414
GESPipeline *
ges_pipeline_new (void)
Edward Hervey's avatar
Edward Hervey committed
415
{
416
  return GES_PIPELINE (gst_element_factory_make ("gespipeline", NULL));
Edward Hervey's avatar
Edward Hervey committed
417
}
418

419
420
421
#define TRACK_COMPATIBLE_PROFILE(tracktype, profile)			\
  ( (GST_IS_ENCODING_AUDIO_PROFILE (profile) && (tracktype) == GES_TRACK_TYPE_AUDIO) || \
    (GST_IS_ENCODING_VIDEO_PROFILE (profile) && (tracktype) == GES_TRACK_TYPE_VIDEO))
422

423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
static gboolean
_track_is_compatible_with_profile (GESPipeline * self, GESTrack * track,
    GstEncodingProfile * prof)
{
  if (TRACK_COMPATIBLE_PROFILE (track->type, prof)) {
    if (self->priv->mode == GES_PIPELINE_MODE_SMART_RENDER) {
      GstCaps *ocaps, *rcaps;

      GST_DEBUG ("Smart Render mode, setting input caps");
      ocaps = gst_encoding_profile_get_input_caps (prof);
      ocaps = gst_caps_make_writable (ocaps);
      if (track->type == GES_TRACK_TYPE_AUDIO)
        rcaps = gst_caps_new_empty_simple ("audio/x-raw");
      else
        rcaps = gst_caps_new_empty_simple ("video/x-raw");
      gst_caps_append (ocaps, rcaps);
      ges_track_set_caps (track, ocaps);
      gst_caps_unref (ocaps);
    } else {
      GstCaps *caps = NULL;

      /* Raw preview or rendering mode */
      if (track->type == GES_TRACK_TYPE_VIDEO)
        caps = gst_caps_new_empty_simple ("video/x-raw");
      else if (track->type == GES_TRACK_TYPE_AUDIO)
        caps = gst_caps_new_empty_simple ("audio/x-raw");

      if (caps) {
        ges_track_set_caps (track, caps);
        gst_caps_unref (caps);
      }
    }

    return TRUE;
  }

  return FALSE;
}

462
static gboolean
463
ges_pipeline_update_caps (GESPipeline * self)
464
465
466
{
  GList *ltrack, *tracks, *lstream;

467
  if (!self->priv->profile)
468
469
470
471
    return TRUE;

  GST_DEBUG ("Updating track caps");

472
  tracks = ges_timeline_get_tracks (self->priv->timeline);
473
474
475
476
477

  /* Take each stream of the encoding profile and find a matching
   * track to set the caps on */
  for (ltrack = tracks; ltrack; ltrack = ltrack->next) {
    GESTrack *track = (GESTrack *) ltrack->data;
478
479
    GList *allstreams;

480
481
482
483
484
485
486
487
488
489
490
    if (!GST_IS_ENCODING_CONTAINER_PROFILE (self->priv->profile)) {
      if (_track_is_compatible_with_profile (self, track, self->priv->profile)) {
        gst_object_unref (track);

        goto done;
      } else {
        gst_object_unref (track);
        continue;
      }
    }

491
492
493
    allstreams = (GList *)
        gst_encoding_container_profile_get_profiles (
        (GstEncodingContainerProfile *) self->priv->profile);
494
495

    /* Find a matching stream setting */
496
497
    for (lstream = allstreams; lstream; lstream = lstream->next) {
      GstEncodingProfile *prof = (GstEncodingProfile *) lstream->data;
498
      if (_track_is_compatible_with_profile (self, track, prof))
499
500
501
        break;
    }

502
    gst_object_unref (track);
503
504
  }

505
done:
506
507
508
509
510
511
512
513
  if (tracks)
    g_list_free (tracks);

  GST_DEBUG ("Done updating caps");

  return TRUE;
}

Thibault Saunier's avatar
Thibault Saunier committed
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
static void
_link_tracks (GESPipeline * pipeline)
{
  GList *tmp;

  GST_DEBUG_OBJECT (pipeline, "Linking tracks");

  if (!pipeline->priv->timeline) {
    GST_INFO_OBJECT (pipeline, "Not timeline set yet, doing nothing");

    return;
  }

  for (tmp = pipeline->priv->timeline->tracks; tmp; tmp = tmp->next)
    _link_track (pipeline, tmp->data);
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578

  if (IN_RENDERING_MODE (pipeline)) {
    GString *unlinked_issues = NULL;
    GstIterator *pads;
    gboolean done = FALSE;
    GValue paditem = { 0, };

    pads = gst_element_iterate_sink_pads (pipeline->priv->encodebin);
    while (!done) {
      switch (gst_iterator_next (pads, &paditem)) {
        case GST_ITERATOR_OK:
        {
          GstPad *testpad = g_value_get_object (&paditem);
          if (!gst_pad_is_linked (testpad)) {
            GstCaps *sinkcaps = gst_pad_query_caps (testpad, NULL);
            gchar *caps_string = gst_caps_to_string (sinkcaps);
            gchar *path_string =
                gst_object_get_path_string (GST_OBJECT (testpad));
            gst_caps_unref (sinkcaps);

            if (!unlinked_issues)
              unlinked_issues =
                  g_string_new ("Following encodebin pads are not linked:\n");

            g_string_append_printf (unlinked_issues, " - %s: %s", path_string,
                caps_string);
            g_free (caps_string);
            g_free (path_string);
          }
          g_value_reset (&paditem);
        }
          break;
        case GST_ITERATOR_DONE:
        case GST_ITERATOR_ERROR:
          done = TRUE;
          break;
        case GST_ITERATOR_RESYNC:
          gst_iterator_resync (pads);
          break;
      }
    }
    g_value_reset (&paditem);
    gst_iterator_free (pads);

    if (unlinked_issues) {
      GST_ELEMENT_ERROR (pipeline, STREAM, FAILED, (NULL), ("%s",
              unlinked_issues->str));
      g_string_free (unlinked_issues, TRUE);
    }
  }
Thibault Saunier's avatar
Thibault Saunier committed
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
}

static void
_unlink_tracks (GESPipeline * pipeline)
{
  GList *tmp;

  GST_DEBUG_OBJECT (pipeline, "Disconnecting all tracks");
  if (!pipeline->priv->timeline) {
    GST_INFO_OBJECT (pipeline, "Not timeline set yet, doing nothing");

    return;
  }

  for (tmp = pipeline->priv->timeline->tracks; tmp; tmp = tmp->next)
    _unlink_track (pipeline, tmp->data);
}

597
static GstStateChangeReturn
598
ges_pipeline_change_state (GstElement * element, GstStateChange transition)
599
{
600
  GESPipeline *self;
601
602
  GstStateChangeReturn ret;

603
  self = GES_PIPELINE (element);
604
605
606

  switch (transition) {
    case GST_STATE_CHANGE_READY_TO_PAUSED:
607
      if (G_UNLIKELY (self->priv->timeline == NULL)) {
608
609
610
611
612
        GST_ERROR_OBJECT (element,
            "No GESTimeline set on the pipeline, cannot play !");
        ret = GST_STATE_CHANGE_FAILURE;
        goto done;
      }
613
      if (IN_RENDERING_MODE (self)) {
614
        GST_DEBUG ("rendering => Updating pipeline caps");
615
616
617
618
619
620
        /* Set caps on all tracks according to profile if present */
        if (!ges_pipeline_update_caps (self)) {
          GST_ERROR_OBJECT (element, "Error setting the caps for rendering");
          ret = GST_STATE_CHANGE_FAILURE;
          goto done;
        }
621
      }
Thibault Saunier's avatar
Thibault Saunier committed
622
      _link_tracks (self);
623
      break;
624
625
626
627
628
629
630
631
    case GST_STATE_CHANGE_PAUSED_TO_READY:
    {
      GList *tmp;

      for (tmp = self->priv->not_rendered_tracks; tmp; tmp = tmp->next)
        gst_element_set_locked_state (tmp->data, FALSE);
    }
      break;
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
    {
      GstElement *queue = gst_bin_get_by_name (GST_BIN (self->priv->playsink),
          "vqueue");

      if (queue) {
        GST_INFO_OBJECT (self, "Setting playsink video queue max-size-time to"
            " 2 seconds.");
        g_object_set (G_OBJECT (queue), "max-size-buffers", 0,
            "max-size-bytes", 0, "max-size-time", (gint64) 2 * GST_SECOND,
            NULL);
        gst_object_unref (queue);
      }
      break;
    }
647
648
649
650
651
    default:
      break;
  }

  ret =
652
      GST_ELEMENT_CLASS (ges_pipeline_parent_class)->change_state
Edward Hervey's avatar
Edward Hervey committed
653
      (element, transition);
654

Thibault Saunier's avatar
Thibault Saunier committed
655
656
  switch (transition) {
    case GST_STATE_CHANGE_PAUSED_TO_READY:
657
658
    case GST_STATE_CHANGE_READY_TO_NULL:
    case GST_STATE_CHANGE_NULL_TO_NULL:
Thibault Saunier's avatar
Thibault Saunier committed
659
660
661
662
663
664
      _unlink_tracks (self);
      break;
    default:
      break;
  }

665
666
667
668
done:
  return ret;
}

669
static OutputChain *
670
new_output_chain_for_track (GESPipeline * self, GESTrack * track)
671
672
673
674
675
676
677
678
679
680
{
  OutputChain *chain;

  chain = g_new0 (OutputChain, 1);
  chain->track = track;

  return chain;
}

static OutputChain *
681
get_output_chain_for_track (GESPipeline * self, GESTrack * track)
682
683
684
{
  GList *tmp;

685
  for (tmp = self->priv->chains; tmp; tmp = tmp->next) {
686
687
688
689
690
691
692
693
    OutputChain *chain = (OutputChain *) tmp->data;
    if (chain->track == track)
      return chain;
  }

  return NULL;
}

694
/* Fetches a compatible pad on the target element which isn't already
695
696
 * linked */
static GstPad *
Thibault Saunier's avatar
Thibault Saunier committed
697
get_compatible_unlinked_pad (GstElement * element, GESTrack * track)
698
699
700
701
{
  GstPad *res = NULL;
  GstIterator *pads;
  gboolean done = FALSE;
Thibault Saunier's avatar
Thibault Saunier committed
702
  const GstCaps *srccaps;
Edward Hervey's avatar
Edward Hervey committed
703
  GValue paditem = { 0, };
704

Thibault Saunier's avatar
Thibault Saunier committed
705
706
  if (G_UNLIKELY (track == NULL))
    goto no_track;
707

Thibault Saunier's avatar
Thibault Saunier committed
708
  GST_DEBUG_OBJECT (element, " track %" GST_PTR_FORMAT, track);
709

Thibault Saunier's avatar
Thibault Saunier committed
710
711
  pads = gst_element_iterate_sink_pads (element);
  srccaps = ges_track_get_caps (track);
712

713
  GST_DEBUG_OBJECT (track, "srccaps %" GST_PTR_FORMAT, srccaps);
714
715

  while (!done) {
Edward Hervey's avatar
Edward Hervey committed
716
    switch (gst_iterator_next (pads, &paditem)) {
717
718
      case GST_ITERATOR_OK:
      {
Edward Hervey's avatar
Edward Hervey committed
719
        GstPad *testpad = g_value_get_object (&paditem);
720

Edward Hervey's avatar
Edward Hervey committed
721
        if (!gst_pad_is_linked (testpad)) {
Edward Hervey's avatar
Edward Hervey committed
722
          GstCaps *sinkcaps = gst_pad_query_caps (testpad, NULL);
723

724
          GST_DEBUG_OBJECT (track, "sinkccaps %" GST_PTR_FORMAT, sinkcaps);
725
726

          if (gst_caps_can_intersect (srccaps, sinkcaps)) {
Edward Hervey's avatar
Edward Hervey committed
727
            res = gst_object_ref (testpad);
728
            done = TRUE;
Edward Hervey's avatar
Edward Hervey committed
729
          }
730
731
          gst_caps_unref (sinkcaps);
        }
Edward Hervey's avatar
Edward Hervey committed
732
        g_value_reset (&paditem);
733
      }
734
        break;
735
736
737
738
739
740
741
742
743
      case GST_ITERATOR_DONE:
      case GST_ITERATOR_ERROR:
        done = TRUE;
        break;
      case GST_ITERATOR_RESYNC:
        gst_iterator_resync (pads);
        break;
    }
  }
Edward Hervey's avatar
Edward Hervey committed
744
  g_value_reset (&paditem);
745
746
747
  gst_iterator_free (pads);

  return res;
748

Thibault Saunier's avatar
Thibault Saunier committed
749
no_track:
750
  {
Thibault Saunier's avatar
Thibault Saunier committed
751
    GST_ERROR ("No track to check against");
752
753
    return NULL;
  }
754
755
}

756
757
758
759
760
761
762
763
764
765
766
767
static GstClockTime
_query_position_cb (GstElement * composition, GESPipeline * self)
{
  gint64 position;

  if (gst_element_query_position (GST_ELEMENT (self), GST_FORMAT_TIME,
          &position))
    return position;

  return GST_CLOCK_TIME_NONE;
}

768
static void
Thibault Saunier's avatar
Thibault Saunier committed
769
_link_track (GESPipeline * self, GESTrack * track)
770
{
Thibault Saunier's avatar
Thibault Saunier committed
771
  GstPad *pad;
772
773
  OutputChain *chain;
  GstPad *sinkpad;
Edward Hervey's avatar
Edward Hervey committed
774
  GstCaps *caps;
Thibault Saunier's avatar
Thibault Saunier committed
775
  GstPadLinkReturn lret;
776
  gboolean reconfigured = FALSE;
777

Thibault Saunier's avatar
Thibault Saunier committed
778
  pad = ges_timeline_get_pad_for_track (self->priv->timeline, track);
Edward Hervey's avatar
Edward Hervey committed
779
  caps = gst_pad_query_caps (pad, NULL);
Edward Hervey's avatar
Edward Hervey committed
780

781
  GST_DEBUG_OBJECT (self, "new pad %s:%s , caps:%" GST_PTR_FORMAT,
Edward Hervey's avatar
Edward Hervey committed
782
      GST_DEBUG_PAD_NAME (pad), caps);
783

Edward Hervey's avatar
Edward Hervey committed
784
785
  gst_caps_unref (caps);

786
787
788
789
  if (G_UNLIKELY (!pad)) {
    GST_ELEMENT_ERROR (self, STREAM, FAILED, (NULL),
        ("Trying to link %" GST_PTR_FORMAT
            " but no pad is exposed for it.", track));
790
791
792
    return;
  }

793
794
  /* Don't connect track if it's not going to be used */
  if (track->type == GES_TRACK_TYPE_VIDEO &&
795
796
797
      !(self->priv->mode & GES_PIPELINE_MODE_PREVIEW_VIDEO) &&
      !(self->priv->mode & GES_PIPELINE_MODE_RENDER) &&
      !(self->priv->mode & GES_PIPELINE_MODE_SMART_RENDER)) {
798
    GST_DEBUG_OBJECT (self, "Video track... but we don't need it. Not linking");
799
  }
800
  if (track->type == GES_TRACK_TYPE_AUDIO &&
801
802
803
      !(self->priv->mode & GES_PIPELINE_MODE_PREVIEW_AUDIO) &&
      !(self->priv->mode & GES_PIPELINE_MODE_RENDER) &&
      !(self->priv->mode & GES_PIPELINE_MODE_SMART_RENDER)) {
804
    GST_DEBUG_OBJECT (self, "Audio track... but we don't need it. Not linking");
805
806
  }

807
808
809
  /* Get an existing chain or create it */
  if (!(chain = get_output_chain_for_track (self, track)))
    chain = new_output_chain_for_track (self, track);
810

811
  if (chain->tee) {
812
813
    GST_INFO_OBJECT (self, "Chain is already built (%" GST_PTR_FORMAT ")",
        chain->encodebinpad ? chain->encodebinpad : chain->playsinkpad);
814

815
    return;
816
  }
817

818
819
820
821
  chain->query_position_id =
      g_signal_connect (ges_track_get_composition (track), "query-position",
      G_CALLBACK (_query_position_cb), self);

822
  chain->srcpad = pad;
Thibault Saunier's avatar
Thibault Saunier committed
823
  gst_object_unref (pad);
824
825

  /* Adding tee */
826
827
828
  chain->tee = gst_element_factory_make ("tee", NULL);
  gst_bin_add (GST_BIN_CAST (self), chain->tee);
  gst_element_sync_state_with_parent (chain->tee);
829
830

  /* Linking pad to tee */
831
  sinkpad = gst_element_get_static_pad (chain->tee, "sink");
Thibault Saunier's avatar
Thibault Saunier committed
832
833
  lret = gst_pad_link (pad, sinkpad);
  if (lret != GST_PAD_LINK_OK) {
834
835
    GST_ELEMENT_ERROR (self, CORE, NEGOTIATION,
        (NULL), ("Could not link the tee (%s)", gst_pad_link_get_name (lret)));
Thibault Saunier's avatar
Thibault Saunier committed
836
837
838
    goto error;
  }

839
  gst_object_unref (sinkpad);
840
841

  /* Connect playsink */
842
  if (self->priv->mode & GES_PIPELINE_MODE_PREVIEW) {
843
    const gchar *sinkpad_name;
844
    GstPad *tmppad;
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865

    GST_DEBUG_OBJECT (self, "Connecting to playsink");

    switch (track->type) {
      case GES_TRACK_TYPE_VIDEO:
        sinkpad_name = "video_sink";
        break;
      case GES_TRACK_TYPE_AUDIO:
        sinkpad_name = "audio_sink";
        break;
      case GES_TRACK_TYPE_TEXT:
        sinkpad_name = "text_sink";
        break;
      default:
        GST_WARNING_OBJECT (self, "Can't handle tracks of type %d yet",
            track->type);
        goto error;
    }

    /* Request a sinkpad from playsink */
    if (G_UNLIKELY (!(sinkpad =
866
867
                gst_element_get_request_pad (self->priv->playsink,
                    sinkpad_name)))) {
868
869
      GST_ELEMENT_ERROR (self, CORE, NEGOTIATION,
          (NULL), ("Could not get a pad from playsink for %s", sinkpad_name));
870
871
872
      goto error;
    }

873
    tmppad = gst_element_get_request_pad (chain->tee, "src_%u");
874
875
    lret = gst_pad_link_full (tmppad, sinkpad, GST_PAD_LINK_CHECK_NOTHING);
    if (G_UNLIKELY (lret != GST_PAD_LINK_OK)) {
876
      gst_object_unref (tmppad);
877
878
879
880
      GST_ELEMENT_ERROR (self, CORE, NEGOTIATION,
          (NULL),
          ("Could not link %" GST_PTR_FORMAT " and %" GST_PTR_FORMAT " (%s)",
              tmppad, sinkpad, gst_pad_link_get_name (lret)));
881
882
      goto error;
    }
Justin Kim's avatar
Justin Kim committed
883
    gst_object_unref (tmppad);
884
885
886
887

    GST_DEBUG ("Reconfiguring playsink");

    /* reconfigure playsink */
888
    g_signal_emit_by_name (self->priv->playsink, "reconfigure", &reconfigured);
889
890
    GST_DEBUG ("'reconfigure' returned %d", reconfigured);

891
    /* We still hold a reference on the sinkpad */
892
893
894
895
    chain->playsinkpad = sinkpad;
  }

  /* Connect to encodebin */
896
  if (IN_RENDERING_MODE (self)) {
897
    GstPad *tmppad;
898
899
    GST_DEBUG_OBJECT (self, "Connecting to encodebin");

900
901
    if (!chain->encodebinpad) {
      /* Check for unused static pads */
Thibault Saunier's avatar
Thibault Saunier committed
902
      sinkpad = get_compatible_unlinked_pad (self->priv->encodebin, track);
903
904

      if (sinkpad == NULL) {
Edward Hervey's avatar
Edward Hervey committed
905
        GstCaps *caps = gst_pad_query_caps (pad, NULL);
Edward Hervey's avatar
Edward Hervey committed
906

907
        /* If no compatible static pad is available, request a pad */
908
909
        g_signal_emit_by_name (self->priv->encodebin, "request-pad", caps,
            &sinkpad);
Edward Hervey's avatar
Edward Hervey committed
910

911
        if (G_UNLIKELY (sinkpad == NULL)) {
912
913
914
915
916
          gst_element_set_locked_state (GST_ELEMENT (track), TRUE);

          self->priv->not_rendered_tracks =
              g_list_append (self->priv->not_rendered_tracks, track);

917
918
919
          GST_INFO_OBJECT (self,
              "Couldn't get a pad from encodebin for: %" GST_PTR_FORMAT, caps);
          gst_caps_unref (caps);
920
921
          goto error;
        }
922
923

        gst_caps_unref (caps);
924
      }
925
      chain->encodebinpad = sinkpad;
926
      GST_INFO_OBJECT (track, "Linked to %" GST_PTR_FORMAT, sinkpad);
927
928
    }

929
    tmppad = gst_element_get_request_pad (chain->tee, "src_%u");
Thibault Saunier's avatar
Thibault Saunier committed
930
    if (G_UNLIKELY (gst_pad_link_full (tmppad, chain->encodebinpad,
931
                GST_PAD_LINK_CHECK_NOTHING) != GST_PAD_LINK_OK)) {
Thibault Saunier's avatar
Thibault Saunier committed
932
      GST_ERROR_OBJECT (self, "Couldn't link track pad to encodebin");
933
934
      goto error;
    }
935
    gst_object_unref (tmppad);
936
937

  }
938

939
940
  /* If chain wasn't already present, insert it in list */
  if (!get_output_chain_for_track (self, track))
941
    self->priv->chains = g_list_append (self->priv->chains, chain);
942
943

  GST_DEBUG ("done");
944
945
946
947
948
  return;

error:
  {
    if (chain->tee) {
949
      gst_element_set_state (chain->tee, GST_STATE_NULL);
950
951
952
953
      gst_bin_remove (GST_BIN_CAST (self), chain->tee);
    }
    if (sinkpad)
      gst_object_unref (sinkpad);
954

955
956
    g_free (chain);
  }
957
958
959
}

static void
Thibault Saunier's avatar
Thibault Saunier committed
960
_unlink_track (GESPipeline * self, GESTrack * track)
961
{
962
  OutputChain *chain;
963

Thibault Saunier's avatar
Thibault Saunier committed
964
  GST_DEBUG_OBJECT (self, "Unlinking removed %" GST_PTR_FORMAT, track);
965
966

  if (G_UNLIKELY (!(chain = get_output_chain_for_track (self, track)))) {
Thibault Saunier's avatar
Thibault Saunier committed
967
    GST_DEBUG_OBJECT (self, "Track wasn't used");
968
969
970
971
972
    return;
  }

  /* Unlink encodebin */
  if (chain->encodebinpad) {
973
    GstPad *peer = gst_pad_get_peer (chain->encodebinpad);
974
    gst_pad_unlink (peer, chain->encodebinpad);
975
    gst_object_unref (peer);
976
977
    gst_element_release_request_pad (self->priv->encodebin,
        chain->encodebinpad);
978
    gst_object_unref (chain->encodebinpad);
979
980
981
982
  }

  /* Unlink playsink */
  if (chain->playsinkpad) {
983
    GstPad *peer = gst_pad_get_peer (chain->playsinkpad);
984
    gst_pad_unlink (peer, chain->playsinkpad);
985
    gst_object_unref (peer);
986
    gst_element_release_request_pad (self->priv->playsink, chain->playsinkpad);
987
    gst_object_unref (chain->playsinkpad);
988
989
990
991
  }

  gst_element_set_state (chain->tee, GST_STATE_NULL);
  gst_bin_remove (GST_BIN (self), chain->tee);
992
993
994
995
996
  if (chain->query_position_id) {
    g_signal_handler_disconnect (ges_track_get_composition (track),
        chain->query_position_id);
    chain->query_position_id = 0;
  }
997

998
  self->priv->chains = g_list_remove (self->priv->chains, chain);
999
  g_free (chain);
1000

For faster browsing, not all history is shown. View entire blame