1 /* GStreamer Editing Services
2 * Copyright (C) 2009 Edward Hervey <edward.hervey@collabora.co.uk>
3 * 2009 Nokia Corporation
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 /**
22 * SECTION:gestrack
23 * @title: GESTrack
24 * @short_description: Composition of objects
25 *
26 * Corresponds to one output format (i.e. audio OR video).
27 *
28 * Contains the compatible TrackElement(s).
29 */
30 #ifdef HAVE_CONFIG_H
31 #include "config.h"
32 #endif
33
34 #include "ges-internal.h"
35 #include "ges-track.h"
36 #include "ges-track-element.h"
37 #include "ges-meta-container.h"
38 #include "ges-video-track.h"
39 #include "ges-audio-track.h"
40
41 #define CHECK_THREAD(track) g_assert(track->priv->valid_thread == g_thread_self())
42
43 static GstStaticPadTemplate ges_track_src_pad_template =
44 GST_STATIC_PAD_TEMPLATE ("src",
45 GST_PAD_SRC,
46 GST_PAD_ALWAYS,
47 GST_STATIC_CAPS_ANY);
48
49 /* Structure that represents gaps and keep knowledge
50 * of the gaps filled in the track */
51 typedef struct
52 {
53 GstElement *nleobj;
54
55 GstClockTime start;
56 GstClockTime duration;
57 GESTrack *track;
58 } Gap;
59
60 struct _GESTrackPrivate
61 {
62 /*< private > */
63 GESTimeline *timeline;
64 GSequence *trackelements_by_start;
65 GHashTable *trackelements_iter;
66 GList *gaps;
67 gboolean last_gap_disabled;
68
69 guint64 duration;
70
71 GstCaps *caps;
72 GstCaps *restriction_caps;
73
74 GstElement *composition; /* The composition associated with this track */
75 GstPad *srcpad; /* The source GhostPad */
76
77 gboolean updating;
78
79 gboolean mixing;
80 GstElement *mixing_operation;
81 GstElement *capsfilter;
82
83 /* Virtual method to create GstElement that fill gaps */
84 GESCreateElementForGapFunc create_element_for_gaps;
85
86 GThread *valid_thread;
87 };
88
89 enum
90 {
91 ARG_0,
92 ARG_CAPS,
93 ARG_RESTRICTION_CAPS,
94 ARG_TYPE,
95 ARG_DURATION,
96 ARG_MIXING,
97 ARG_LAST,
98 TRACK_ELEMENT_ADDED,
99 TRACK_ELEMENT_REMOVED,
100 COMMITED,
101 LAST_SIGNAL
102 };
103
104 static guint ges_track_signals[LAST_SIGNAL] = { 0 };
105
106 static GParamSpec *properties[ARG_LAST];
107
108 G_DEFINE_TYPE_WITH_CODE (GESTrack, ges_track, GST_TYPE_BIN,
109 G_ADD_PRIVATE (GESTrack)
110 G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER, NULL));
111
112
113 static void composition_duration_cb (GstElement * composition, GParamSpec * arg
114 G_GNUC_UNUSED, GESTrack * obj);
115
116 /* Private methods/functions/callbacks */
117 static void
add_trackelement_to_list_foreach(GESTrackElement * trackelement,GList ** list)118 add_trackelement_to_list_foreach (GESTrackElement * trackelement, GList ** list)
119 {
120 gst_object_ref (trackelement);
121 *list = g_list_prepend (*list, trackelement);
122 }
123
124 static Gap *
gap_new(GESTrack * track,GstClockTime start,GstClockTime duration)125 gap_new (GESTrack * track, GstClockTime start, GstClockTime duration)
126 {
127 GstElement *nlesrc, *elem;
128
129 Gap *new_gap;
130
131 nlesrc = gst_element_factory_make ("nlesource", NULL);
132 elem = track->priv->create_element_for_gaps (track);
133 if (G_UNLIKELY (gst_bin_add (GST_BIN (nlesrc), elem) == FALSE)) {
134 GST_WARNING_OBJECT (track, "Could not create gap filler");
135
136 if (nlesrc)
137 gst_object_unref (nlesrc);
138
139 if (elem)
140 gst_object_unref (elem);
141
142 return NULL;
143 }
144
145 if (G_UNLIKELY (ges_nle_composition_add_object (track->priv->composition,
146 nlesrc) == FALSE)) {
147 GST_WARNING_OBJECT (track, "Could not add gap to the composition");
148
149 if (nlesrc)
150 gst_object_unref (nlesrc);
151
152 if (elem)
153 gst_object_unref (elem);
154
155 return NULL;
156 }
157
158 new_gap = g_slice_new (Gap);
159 new_gap->start = start;
160 new_gap->duration = duration;
161 new_gap->track = track;
162 new_gap->nleobj = nlesrc;
163
164
165 g_object_set (nlesrc, "start", new_gap->start, "duration", new_gap->duration,
166 "priority", 1, NULL);
167
168 GST_DEBUG_OBJECT (track,
169 "Created gap with start %" GST_TIME_FORMAT " duration %" GST_TIME_FORMAT,
170 GST_TIME_ARGS (new_gap->start), GST_TIME_ARGS (new_gap->duration));
171
172
173 return new_gap;
174 }
175
176 static void
free_gap(Gap * gap)177 free_gap (Gap * gap)
178 {
179 GESTrack *track = gap->track;
180
181 GST_DEBUG_OBJECT (track, "Removed gap with start %" GST_TIME_FORMAT
182 " duration %" GST_TIME_FORMAT, GST_TIME_ARGS (gap->start),
183 GST_TIME_ARGS (gap->duration));
184 ges_nle_composition_remove_object (track->priv->composition, gap->nleobj);
185
186 g_slice_free (Gap, gap);
187 }
188
189 static inline void
update_gaps(GESTrack * track)190 update_gaps (GESTrack * track)
191 {
192 Gap *gap;
193 GList *gaps;
194 GSequenceIter *it;
195
196 GESTrackElement *trackelement;
197 GstClockTime start, end, duration = 0, timeline_duration = 0;
198
199 GESTrackPrivate *priv = track->priv;
200
201 if (priv->create_element_for_gaps == NULL) {
202 GST_INFO ("Not filling the gaps as no create_element_for_gaps vmethod"
203 " provided");
204 return;
205 }
206
207 gaps = priv->gaps;
208 priv->gaps = NULL;
209
210 /* 1- And recalculate gaps */
211 for (it = g_sequence_get_begin_iter (priv->trackelements_by_start);
212 g_sequence_iter_is_end (it) == FALSE; it = g_sequence_iter_next (it)) {
213 trackelement = g_sequence_get (it);
214
215 if (!ges_track_element_is_active (trackelement))
216 continue;
217
218 start = _START (trackelement);
219 end = start + _DURATION (trackelement);
220
221 if (start > duration) {
222 /* 2- Fill gap */
223 gap = gap_new (track, duration, start - duration);
224
225 if (G_LIKELY (gap != NULL))
226 priv->gaps = g_list_prepend (priv->gaps, gap);
227 }
228
229 duration = MAX (duration, end);
230 }
231
232 /* 3- Add a gap at the end of the timeline if needed */
233 if (priv->timeline) {
234 g_object_get (priv->timeline, "duration", &timeline_duration, NULL);
235
236 if (duration < timeline_duration) {
237 gap = gap_new (track, duration, timeline_duration - duration);
238
239 if (G_LIKELY (gap != NULL)) {
240 priv->gaps = g_list_prepend (priv->gaps, gap);
241 }
242
243 priv->duration = timeline_duration;
244 }
245 }
246
247 if (!track->priv->last_gap_disabled) {
248 GST_DEBUG_OBJECT (track, "Adding a one second gap at the end");
249 gap = gap_new (track, timeline_duration, 1);
250 priv->gaps = g_list_prepend (priv->gaps, gap);
251 }
252
253 /* 4- Remove old gaps */
254 g_list_free_full (gaps, (GDestroyNotify) free_gap);
255 }
256
257 void
track_disable_last_gap(GESTrack * track,gboolean disabled)258 track_disable_last_gap (GESTrack * track, gboolean disabled)
259 {
260 track->priv->last_gap_disabled = disabled;
261 update_gaps (track);
262 }
263
264 void
track_resort_and_fill_gaps(GESTrack * track)265 track_resort_and_fill_gaps (GESTrack * track)
266 {
267 g_sequence_sort (track->priv->trackelements_by_start,
268 (GCompareDataFunc) element_start_compare, NULL);
269
270 if (track->priv->updating == TRUE) {
271 update_gaps (track);
272 }
273 }
274
275 static gboolean
update_field(GQuark field_id,const GValue * value,GstStructure * original)276 update_field (GQuark field_id, const GValue * value, GstStructure * original)
277 {
278 gst_structure_id_set_value (original, field_id, value);
279 return TRUE;
280 }
281
282 /* callbacks */
283 static void
_ghost_nlecomposition_srcpad(GESTrack * track)284 _ghost_nlecomposition_srcpad (GESTrack * track)
285 {
286 GstPad *capsfilter_sink;
287 GstPad *capsfilter_src;
288 GESTrackPrivate *priv = track->priv;
289 GstPad *pad = gst_element_get_static_pad (priv->composition, "src");
290
291 capsfilter_sink = gst_element_get_static_pad (priv->capsfilter, "sink");
292
293 GST_DEBUG ("track:%p, pad %s:%s", track, GST_DEBUG_PAD_NAME (pad));
294
295 gst_pad_link (pad, capsfilter_sink);
296 gst_object_unref (capsfilter_sink);
297 gst_object_unref (pad);
298
299 capsfilter_src = gst_element_get_static_pad (priv->capsfilter, "src");
300 /* ghost the pad */
301 priv->srcpad = gst_ghost_pad_new ("src", capsfilter_src);
302 gst_object_unref (capsfilter_src);
303 gst_pad_set_active (priv->srcpad, TRUE);
304 gst_element_add_pad (GST_ELEMENT (track), priv->srcpad);
305
306 GST_DEBUG ("done");
307 }
308
309 static void
composition_duration_cb(GstElement * composition,GParamSpec * arg G_GNUC_UNUSED,GESTrack * track)310 composition_duration_cb (GstElement * composition,
311 GParamSpec * arg G_GNUC_UNUSED, GESTrack * track)
312 {
313 guint64 duration;
314
315 g_object_get (composition, "duration", &duration, NULL);
316
317 if (track->priv->duration != duration) {
318 GST_DEBUG_OBJECT (track,
319 "composition duration : %" GST_TIME_FORMAT " current : %"
320 GST_TIME_FORMAT, GST_TIME_ARGS (duration),
321 GST_TIME_ARGS (track->priv->duration));
322
323 track->priv->duration = duration;
324
325 g_object_notify_by_pspec (G_OBJECT (track), properties[ARG_DURATION]);
326 }
327 }
328
329 static void
composition_commited_cb(GstElement * composition,gboolean changed,GESTrack * self)330 composition_commited_cb (GstElement * composition, gboolean changed,
331 GESTrack * self)
332 {
333 g_signal_emit (self, ges_track_signals[COMMITED], 0);
334 }
335
336 /* Internal */
337 GstElement *
ges_track_get_composition(GESTrack * track)338 ges_track_get_composition (GESTrack * track)
339 {
340 return track->priv->composition;
341 }
342
343 /* FIXME: Find out how to avoid doing this "hack" using the GDestroyNotify
344 * function pointer in the trackelements_by_start GSequence
345 *
346 * Remove @object from @track, but keeps it in the sequence this is needed
347 * when finalizing as we can not change a GSequence at the same time we are
348 * accessing it
349 */
350 static gboolean
remove_object_internal(GESTrack * track,GESTrackElement * object)351 remove_object_internal (GESTrack * track, GESTrackElement * object)
352 {
353 GESTrackPrivate *priv;
354 GstElement *nleobject;
355
356 GST_DEBUG_OBJECT (track, "object:%p", object);
357
358 priv = track->priv;
359
360 if (G_UNLIKELY (ges_track_element_get_track (object) != track)) {
361 GST_WARNING ("Object belongs to another track");
362 return FALSE;
363 }
364
365 if ((nleobject = ges_track_element_get_nleobject (object))) {
366 GST_DEBUG ("Removing NleObject '%s' from composition '%s'",
367 GST_ELEMENT_NAME (nleobject), GST_ELEMENT_NAME (priv->composition));
368
369 if (!ges_nle_composition_remove_object (priv->composition, nleobject)) {
370 GST_WARNING ("Failed to remove nleobject from composition");
371 return FALSE;
372 }
373 }
374
375 ges_track_element_set_track (object, NULL);
376 ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL);
377
378 g_signal_emit (track, ges_track_signals[TRACK_ELEMENT_REMOVED], 0,
379 GES_TRACK_ELEMENT (object));
380
381 gst_object_unref (object);
382
383 return TRUE;
384 }
385
386 static void
dispose_trackelements_foreach(GESTrackElement * trackelement,GESTrack * track)387 dispose_trackelements_foreach (GESTrackElement * trackelement, GESTrack * track)
388 {
389 remove_object_internal (track, trackelement);
390 }
391
392 /* GstElement virtual methods */
393
394 static GstStateChangeReturn
ges_track_change_state(GstElement * element,GstStateChange transition)395 ges_track_change_state (GstElement * element, GstStateChange transition)
396 {
397 if (transition == GST_STATE_CHANGE_READY_TO_PAUSED)
398 track_resort_and_fill_gaps (GES_TRACK (element));
399
400 return GST_ELEMENT_CLASS (ges_track_parent_class)->change_state (element,
401 transition);
402 }
403
404 /* GObject virtual methods */
405 static void
ges_track_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)406 ges_track_get_property (GObject * object, guint property_id,
407 GValue * value, GParamSpec * pspec)
408 {
409 GESTrack *track = GES_TRACK (object);
410
411 switch (property_id) {
412 case ARG_CAPS:
413 gst_value_set_caps (value, track->priv->caps);
414 break;
415 case ARG_TYPE:
416 g_value_set_flags (value, track->type);
417 break;
418 case ARG_DURATION:
419 g_value_set_uint64 (value, track->priv->duration);
420 break;
421 case ARG_RESTRICTION_CAPS:
422 gst_value_set_caps (value, track->priv->restriction_caps);
423 break;
424 case ARG_MIXING:
425 g_value_set_boolean (value, track->priv->mixing);
426 break;
427 default:
428 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
429 }
430 }
431
432 static void
ges_track_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)433 ges_track_set_property (GObject * object, guint property_id,
434 const GValue * value, GParamSpec * pspec)
435 {
436 GESTrack *track = GES_TRACK (object);
437
438 switch (property_id) {
439 case ARG_CAPS:
440 ges_track_set_caps (track, gst_value_get_caps (value));
441 break;
442 case ARG_TYPE:
443 track->type = g_value_get_flags (value);
444 break;
445 case ARG_RESTRICTION_CAPS:
446 ges_track_set_restriction_caps (track, gst_value_get_caps (value));
447 break;
448 case ARG_MIXING:
449 ges_track_set_mixing (track, g_value_get_boolean (value));
450 break;
451 default:
452 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
453 }
454 }
455
456 static void
ges_track_dispose(GObject * object)457 ges_track_dispose (GObject * object)
458 {
459 GESTrack *track = (GESTrack *) object;
460 GESTrackPrivate *priv = track->priv;
461
462 /* Remove all TrackElements and drop our reference */
463 g_hash_table_unref (priv->trackelements_iter);
464 g_sequence_foreach (track->priv->trackelements_by_start,
465 (GFunc) dispose_trackelements_foreach, track);
466 g_sequence_free (priv->trackelements_by_start);
467 g_list_free_full (priv->gaps, (GDestroyNotify) free_gap);
468 ges_nle_object_commit (track->priv->composition, TRUE);
469
470 if (priv->composition) {
471 gst_element_remove_pad (GST_ELEMENT (track), priv->srcpad);
472 gst_bin_remove (GST_BIN (object), priv->composition);
473 priv->composition = NULL;
474 }
475
476 if (priv->caps) {
477 gst_caps_unref (priv->caps);
478 priv->caps = NULL;
479 }
480
481 if (priv->restriction_caps) {
482 gst_caps_unref (priv->restriction_caps);
483 priv->restriction_caps = NULL;
484 }
485
486 G_OBJECT_CLASS (ges_track_parent_class)->dispose (object);
487 }
488
489 static void
ges_track_finalize(GObject * object)490 ges_track_finalize (GObject * object)
491 {
492 G_OBJECT_CLASS (ges_track_parent_class)->finalize (object);
493 }
494
495 static void
ges_track_constructed(GObject * object)496 ges_track_constructed (GObject * object)
497 {
498 GESTrack *self = GES_TRACK (object);
499 gchar *componame = NULL;
500
501 if (self->type == GES_TRACK_TYPE_VIDEO) {
502 componame =
503 g_strdup_printf ("video_%s", GST_OBJECT_NAME (self->priv->composition));
504 } else if (self->type == GES_TRACK_TYPE_AUDIO) {
505 componame =
506 g_strdup_printf ("audio_%s", GST_OBJECT_NAME (self->priv->composition));
507 }
508
509 if (componame) {
510 gst_object_set_name (GST_OBJECT (self->priv->composition), componame);
511
512 g_free (componame);
513 }
514
515 if (!gst_bin_add (GST_BIN (self), self->priv->composition))
516 GST_ERROR ("Couldn't add composition to bin !");
517
518 if (!gst_bin_add (GST_BIN (self), self->priv->capsfilter))
519 GST_ERROR ("Couldn't add capsfilter to bin !");
520
521 _ghost_nlecomposition_srcpad (self);
522 if (GES_TRACK_GET_CLASS (self)->get_mixing_element) {
523 GstElement *nleobject;
524 GstElement *mixer = GES_TRACK_GET_CLASS (self)->get_mixing_element (self);
525
526 if (mixer == NULL) {
527 GST_WARNING_OBJECT (self, "Got no element fron get_mixing_element");
528
529 return;
530 }
531
532 nleobject = gst_element_factory_make ("nleoperation", "mixing-operation");
533 if (!gst_bin_add (GST_BIN (nleobject), mixer)) {
534 GST_WARNING_OBJECT (self, "Could not add the mixer to our composition");
535 gst_object_unref (mixer);
536 gst_object_unref (nleobject);
537
538 return;
539 }
540 g_object_set (nleobject, "expandable", TRUE, NULL);
541
542 if (self->priv->mixing) {
543 if (!ges_nle_composition_add_object (self->priv->composition, nleobject)) {
544 GST_WARNING_OBJECT (self, "Could not add the mixer to our composition");
545 gst_object_unref (nleobject);
546
547 return;
548 }
549 }
550
551 self->priv->mixing_operation = nleobject;
552
553 } else {
554 GST_INFO_OBJECT (self, "No way to create a main mixer");
555 }
556 }
557
558 static void
ges_track_class_init(GESTrackClass * klass)559 ges_track_class_init (GESTrackClass * klass)
560 {
561 GObjectClass *object_class = G_OBJECT_CLASS (klass);
562 GstElementClass *gstelement_class = (GstElementClass *) klass;
563
564 gstelement_class->change_state = GST_DEBUG_FUNCPTR (ges_track_change_state);
565
566 object_class->get_property = ges_track_get_property;
567 object_class->set_property = ges_track_set_property;
568 object_class->dispose = ges_track_dispose;
569 object_class->finalize = ges_track_finalize;
570 object_class->constructed = ges_track_constructed;
571
572 /**
573 * GESTrack:caps:
574 *
575 * Caps used to filter/choose the output stream. This is generally set to
576 * a generic set of caps like 'video/x-raw' for raw video.
577 *
578 * Default value: #GST_CAPS_ANY.
579 */
580 properties[ARG_CAPS] = g_param_spec_boxed ("caps", "Caps",
581 "Caps used to filter/choose the output stream",
582 GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
583 g_object_class_install_property (object_class, ARG_CAPS,
584 properties[ARG_CAPS]);
585
586 /**
587 * GESTrack:restriction-caps:
588 *
589 * Caps used to filter/choose the output stream.
590 *
591 * Default value: #GST_CAPS_ANY.
592 */
593 properties[ARG_RESTRICTION_CAPS] =
594 g_param_spec_boxed ("restriction-caps", "Restriction caps",
595 "Caps used to filter/choose the output stream", GST_TYPE_CAPS,
596 G_PARAM_READWRITE);
597 g_object_class_install_property (object_class, ARG_RESTRICTION_CAPS,
598 properties[ARG_RESTRICTION_CAPS]);
599
600 /**
601 * GESTrack:duration:
602 *
603 * Current duration of the track
604 *
605 * Default value: O
606 */
607 properties[ARG_DURATION] = g_param_spec_uint64 ("duration", "Duration",
608 "The current duration of the track", 0, G_MAXUINT64, GST_SECOND,
609 G_PARAM_READABLE);
610 g_object_class_install_property (object_class, ARG_DURATION,
611 properties[ARG_DURATION]);
612
613 /**
614 * GESTrack:track-type:
615 *
616 * Type of stream the track outputs. This is used when creating the #GESTrack
617 * to specify in generic terms what type of content will be outputted.
618 *
619 * It also serves as a 'fast' way to check what type of data will be outputted
620 * from the #GESTrack without having to actually check the #GESTrack's caps
621 * property.
622 */
623 properties[ARG_TYPE] = g_param_spec_flags ("track-type", "TrackType",
624 "Type of stream the track outputs",
625 GES_TYPE_TRACK_TYPE, GES_TRACK_TYPE_CUSTOM,
626 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
627 g_object_class_install_property (object_class, ARG_TYPE,
628 properties[ARG_TYPE]);
629
630 /**
631 * GESTrack:mixing:
632 *
633 * Whether layer mixing is activated or not on the track.
634 */
635 properties[ARG_MIXING] = g_param_spec_boolean ("mixing", "Mixing",
636 "Whether layer mixing is activated on the track or not",
637 TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
638 g_object_class_install_property (object_class, ARG_MIXING,
639 properties[ARG_MIXING]);
640
641 gst_element_class_add_static_pad_template (gstelement_class,
642 &ges_track_src_pad_template);
643
644 /**
645 * GESTrack::track-element-added:
646 * @object: the #GESTrack
647 * @effect: the #GESTrackElement that was added.
648 *
649 * Will be emitted after a track element was added to the track.
650 */
651 ges_track_signals[TRACK_ELEMENT_ADDED] =
652 g_signal_new ("track-element-added", G_TYPE_FROM_CLASS (klass),
653 G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_generic,
654 G_TYPE_NONE, 1, GES_TYPE_TRACK_ELEMENT);
655
656 /**
657 * GESTrack::track-element-removed:
658 * @object: the #GESTrack
659 * @effect: the #GESTrackElement that was removed.
660 *
661 * Will be emitted after a track element was removed from the track.
662 */
663 ges_track_signals[TRACK_ELEMENT_REMOVED] =
664 g_signal_new ("track-element-removed", G_TYPE_FROM_CLASS (klass),
665 G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_generic,
666 G_TYPE_NONE, 1, GES_TYPE_TRACK_ELEMENT);
667
668 /**
669 * GESTrack::commited:
670 * @track: the #GESTrack
671 */
672 ges_track_signals[COMMITED] =
673 g_signal_new ("commited", G_TYPE_FROM_CLASS (klass),
674 G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0);
675
676 klass->get_mixing_element = NULL;
677 }
678
679 static void
ges_track_init(GESTrack * self)680 ges_track_init (GESTrack * self)
681 {
682 self->priv = ges_track_get_instance_private (self);
683 self->priv->valid_thread = g_thread_self ();
684
685 self->priv->composition = gst_element_factory_make ("nlecomposition", NULL);
686 self->priv->capsfilter = gst_element_factory_make ("capsfilter", NULL);
687 self->priv->updating = TRUE;
688 self->priv->trackelements_by_start = g_sequence_new (NULL);
689 self->priv->trackelements_iter =
690 g_hash_table_new (g_direct_hash, g_direct_equal);
691 self->priv->create_element_for_gaps = NULL;
692 self->priv->gaps = NULL;
693 self->priv->mixing = TRUE;
694 self->priv->restriction_caps = NULL;
695
696 g_signal_connect (G_OBJECT (self->priv->composition), "notify::duration",
697 G_CALLBACK (composition_duration_cb), self);
698 g_signal_connect (G_OBJECT (self->priv->composition), "commited",
699 G_CALLBACK (composition_commited_cb), self);
700 }
701
702 /**
703 * ges_track_new:
704 * @type: The type of track
705 * @caps: (transfer full): The caps to restrict the output of the track to.
706 *
707 * Creates a new #GESTrack with the given @type and @caps.
708 *
709 * The newly created track will steal a reference to the caps. If you wish to
710 * use those caps elsewhere, you will have to take an extra reference.
711 *
712 * Returns: (transfer floating): A new #GESTrack.
713 */
714 GESTrack *
ges_track_new(GESTrackType type,GstCaps * caps)715 ges_track_new (GESTrackType type, GstCaps * caps)
716 {
717 GESTrack *track;
718 GstCaps *tmpcaps;
719
720 /* TODO Be smarter with well known track types */
721 if (type == GES_TRACK_TYPE_VIDEO) {
722 tmpcaps = gst_caps_new_empty_simple ("video/x-raw");
723 gst_caps_set_features (tmpcaps, 0, gst_caps_features_new_any ());
724
725 if (gst_caps_is_subset (caps, tmpcaps)) {
726 track = GES_TRACK (ges_video_track_new ());
727 ges_track_set_caps (track, caps);
728
729 gst_caps_unref (tmpcaps);
730 return track;
731 }
732 gst_caps_unref (tmpcaps);
733 } else if (type == GES_TRACK_TYPE_AUDIO) {
734 tmpcaps = gst_caps_new_empty_simple ("audio/x-raw");
735 gst_caps_set_features (tmpcaps, 0, gst_caps_features_new_any ());
736
737 if (gst_caps_is_subset (caps, tmpcaps)) {
738 track = GES_TRACK (ges_audio_track_new ());
739 ges_track_set_caps (track, caps);
740
741 gst_caps_unref (tmpcaps);
742 return track;
743 }
744
745 gst_caps_unref (tmpcaps);
746 }
747
748 track = g_object_new (GES_TYPE_TRACK, "caps", caps, "track-type", type, NULL);
749 gst_caps_unref (caps);
750
751 return track;
752 }
753
754
755 /**
756 * ges_track_set_timeline:
757 * @track: a #GESTrack
758 * @timeline: a #GESTimeline
759 *
760 * Sets @timeline as the timeline controlling @track.
761 */
762 void
ges_track_set_timeline(GESTrack * track,GESTimeline * timeline)763 ges_track_set_timeline (GESTrack * track, GESTimeline * timeline)
764 {
765 GST_DEBUG ("track:%p, timeline:%p", track, timeline);
766
767 track->priv->timeline = timeline;
768 track_resort_and_fill_gaps (track);
769 }
770
771 /**
772 * ges_track_set_caps:
773 * @track: a #GESTrack
774 * @caps: the #GstCaps to set
775 *
776 * Sets the given @caps on the track.
777 * Note that the capsfeatures of @caps will always be set
778 * to ANY. If you want to restrict them, you should
779 * do it in #ges_track_set_restriction_caps.
780 */
781 void
ges_track_set_caps(GESTrack * track,const GstCaps * caps)782 ges_track_set_caps (GESTrack * track, const GstCaps * caps)
783 {
784 GESTrackPrivate *priv;
785 gint i;
786
787 g_return_if_fail (GES_IS_TRACK (track));
788 CHECK_THREAD (track);
789
790 GST_DEBUG ("track:%p, caps:%" GST_PTR_FORMAT, track, caps);
791 g_return_if_fail (GST_IS_CAPS (caps));
792
793 priv = track->priv;
794
795 if (priv->caps)
796 gst_caps_unref (priv->caps);
797 priv->caps = gst_caps_copy (caps);
798
799 for (i = 0; i < (int) gst_caps_get_size (priv->caps); i++)
800 gst_caps_set_features (priv->caps, i, gst_caps_features_new_any ());
801
802 g_object_set (priv->composition, "caps", caps, NULL);
803 /* FIXME : update all trackelements ? */
804 }
805
806 /**
807 * ges_track_set_restriction_caps:
808 * @track: a #GESTrack
809 * @caps: the #GstCaps to set
810 *
811 * Sets the given @caps as the caps the track has to output.
812 */
813 void
ges_track_set_restriction_caps(GESTrack * track,const GstCaps * caps)814 ges_track_set_restriction_caps (GESTrack * track, const GstCaps * caps)
815 {
816 GESTrackPrivate *priv;
817
818 g_return_if_fail (GES_IS_TRACK (track));
819 CHECK_THREAD (track);
820
821 GST_DEBUG ("track:%p, restriction caps:%" GST_PTR_FORMAT, track, caps);
822 g_return_if_fail (GST_IS_CAPS (caps));
823
824 priv = track->priv;
825
826 if (priv->restriction_caps)
827 gst_caps_unref (priv->restriction_caps);
828 priv->restriction_caps = gst_caps_copy (caps);
829
830 g_object_set (priv->capsfilter, "caps", caps, NULL);
831
832 g_object_notify (G_OBJECT (track), "restriction-caps");
833 }
834
835 /**
836 * ges_track_update_restriction_caps:
837 * @track: a #GESTrack
838 * @caps: the #GstCaps to update with
839 *
840 * Updates the restriction caps by modifying all the fields present in @caps
841 * in the original restriction caps. If for example the current restriction caps
842 * are video/x-raw, format=I420, width=360 and @caps is video/x-raw, format=RGB,
843 * the restriction caps will be updated to video/x-raw, format=RGB, width=360.
844 *
845 * Modification happens for each structure in the new caps, and
846 * one can add new fields or structures through that function.
847 */
848 void
ges_track_update_restriction_caps(GESTrack * self,const GstCaps * caps)849 ges_track_update_restriction_caps (GESTrack * self, const GstCaps * caps)
850 {
851 guint i;
852 GstCaps *new_restriction_caps;
853
854 g_return_if_fail (GES_IS_TRACK (self));
855 CHECK_THREAD (self);
856
857 if (!self->priv->restriction_caps) {
858 ges_track_set_restriction_caps (self, caps);
859 return;
860 }
861
862 new_restriction_caps = gst_caps_copy (self->priv->restriction_caps);
863 for (i = 0; i < gst_caps_get_size (caps); i++) {
864 GstStructure *new = gst_caps_get_structure (caps, i);
865
866 if (gst_caps_get_size (new_restriction_caps) > i) {
867 GstStructure *original = gst_caps_get_structure (new_restriction_caps, i);
868 gst_structure_foreach (new, (GstStructureForeachFunc) update_field,
869 original);
870 } else
871 gst_caps_append_structure (new_restriction_caps,
872 gst_structure_copy (new));
873 }
874
875 ges_track_set_restriction_caps (self, new_restriction_caps);
876 gst_caps_unref (new_restriction_caps);
877 }
878
879 /**
880 * ges_track_set_mixing:
881 * @track: a #GESTrack
882 * @mixing: TRUE if the track should be mixing, FALSE otherwise.
883 *
884 * Sets if the #GESTrack should be mixing.
885 */
886 void
ges_track_set_mixing(GESTrack * track,gboolean mixing)887 ges_track_set_mixing (GESTrack * track, gboolean mixing)
888 {
889 g_return_if_fail (GES_IS_TRACK (track));
890 CHECK_THREAD (track);
891
892 if (!track->priv->mixing_operation) {
893 GST_DEBUG_OBJECT (track, "Track will be set to mixing = %d", mixing);
894 track->priv->mixing = mixing;
895 return;
896 }
897
898 if (mixing == track->priv->mixing) {
899 GST_DEBUG_OBJECT (track, "Mixing is already set to the same value");
900 }
901
902 if (mixing) {
903 if (!ges_nle_composition_add_object (track->priv->composition,
904 track->priv->mixing_operation)) {
905 GST_WARNING_OBJECT (track, "Could not add the mixer to our composition");
906 return;
907 }
908 } else {
909 if (!ges_nle_composition_remove_object (track->priv->composition,
910 track->priv->mixing_operation)) {
911 GST_WARNING_OBJECT (track,
912 "Could not remove the mixer from our composition");
913 return;
914 }
915 }
916
917 track->priv->mixing = mixing;
918
919 GST_DEBUG_OBJECT (track, "The track has been set to mixing = %d", mixing);
920 }
921
922 /**
923 * ges_track_add_element:
924 * @track: a #GESTrack
925 * @object: (transfer floating): the #GESTrackElement to add
926 *
927 * Adds the given object to the track. Sets the object's controlling track,
928 * and thus takes ownership of the @object.
929 *
930 * An object can only be added to one track.
931 *
932 * Returns: #TRUE if the object was properly added. #FALSE if the track does not
933 * want to accept the object.
934 */
935 gboolean
ges_track_add_element(GESTrack * track,GESTrackElement * object)936 ges_track_add_element (GESTrack * track, GESTrackElement * object)
937 {
938 g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
939 g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE);
940 CHECK_THREAD (track);
941
942 GST_DEBUG ("track:%p, object:%p", track, object);
943
944 if (G_UNLIKELY (ges_track_element_get_track (object) != NULL)) {
945 GST_WARNING ("Object already belongs to another track");
946 gst_object_ref_sink (object);
947 gst_object_unref (object);
948 return FALSE;
949 }
950
951 if (G_UNLIKELY (!ges_track_element_set_track (object, track))) {
952 GST_ERROR ("Couldn't properly add the object to the Track");
953 gst_object_ref_sink (object);
954 gst_object_unref (object);
955 return FALSE;
956 }
957
958 GST_DEBUG ("Adding object %s to ourself %s",
959 GST_OBJECT_NAME (ges_track_element_get_nleobject (object)),
960 GST_OBJECT_NAME (track->priv->composition));
961
962 if (G_UNLIKELY (!ges_nle_composition_add_object (track->priv->composition,
963 ges_track_element_get_nleobject (object)))) {
964 GST_WARNING ("Couldn't add object to the NleComposition");
965 gst_object_ref_sink (object);
966 gst_object_unref (object);
967 return FALSE;
968 }
969
970 gst_object_ref_sink (object);
971 g_hash_table_insert (track->priv->trackelements_iter, object,
972 g_sequence_insert_sorted (track->priv->trackelements_by_start, object,
973 (GCompareDataFunc) element_start_compare, NULL));
974
975 ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object),
976 track->priv->timeline);
977 g_signal_emit (track, ges_track_signals[TRACK_ELEMENT_ADDED], 0,
978 GES_TRACK_ELEMENT (object));
979
980 return TRUE;
981 }
982
983 /**
984 * ges_track_get_elements:
985 * @track: a #GESTrack
986 *
987 * Gets the #GESTrackElement contained in @track
988 *
989 * Returns: (transfer full) (element-type GESTrackElement): the list of
990 * #GESTrackElement present in the Track sorted by priority and start.
991 */
992 GList *
ges_track_get_elements(GESTrack * track)993 ges_track_get_elements (GESTrack * track)
994 {
995 GList *ret = NULL;
996
997 g_return_val_if_fail (GES_IS_TRACK (track), NULL);
998 CHECK_THREAD (track);
999
1000 g_sequence_foreach (track->priv->trackelements_by_start,
1001 (GFunc) add_trackelement_to_list_foreach, &ret);
1002
1003 ret = g_list_reverse (ret);
1004 return ret;
1005 }
1006
1007 /**
1008 * ges_track_remove_element:
1009 * @track: a #GESTrack
1010 * @object: the #GESTrackElement to remove
1011 *
1012 * Removes the object from the track and unparents it.
1013 * Unparenting it means the reference owned by @track on the @object will be
1014 * removed. If you wish to use the @object after this function, make sure you
1015 * call gst_object_ref() before removing it from the @track.
1016 *
1017 * Returns: #TRUE if the object was removed, else #FALSE if the track
1018 * could not remove the object (like if it didn't belong to the track).
1019 */
1020 gboolean
ges_track_remove_element(GESTrack * track,GESTrackElement * object)1021 ges_track_remove_element (GESTrack * track, GESTrackElement * object)
1022 {
1023 GSequenceIter *it;
1024 GESTrackPrivate *priv;
1025
1026 g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
1027 g_return_val_if_fail (GES_IS_TRACK_ELEMENT (object), FALSE);
1028 CHECK_THREAD (track);
1029
1030 priv = track->priv;
1031
1032 GST_DEBUG_OBJECT (track, "Removing %" GST_PTR_FORMAT, object);
1033
1034 it = g_hash_table_lookup (priv->trackelements_iter, object);
1035 g_sequence_remove (it);
1036
1037 if (remove_object_internal (track, object) == TRUE) {
1038 ges_timeline_element_set_timeline (GES_TIMELINE_ELEMENT (object), NULL);
1039
1040 return TRUE;
1041 }
1042
1043 g_hash_table_insert (track->priv->trackelements_iter, object,
1044 g_sequence_insert_sorted (track->priv->trackelements_by_start, object,
1045 (GCompareDataFunc) element_start_compare, NULL));
1046
1047 return FALSE;
1048 }
1049
1050 /**
1051 * ges_track_get_caps:
1052 * @track: a #GESTrack
1053 *
1054 * Get the #GstCaps this track is configured to output.
1055 *
1056 * Returns: The #GstCaps this track is configured to output.
1057 */
1058 const GstCaps *
ges_track_get_caps(GESTrack * track)1059 ges_track_get_caps (GESTrack * track)
1060 {
1061 g_return_val_if_fail (GES_IS_TRACK (track), NULL);
1062 CHECK_THREAD (track);
1063
1064 return track->priv->caps;
1065 }
1066
1067 /**
1068 * ges_track_get_timeline:
1069 * @track: a #GESTrack
1070 *
1071 * Get the #GESTimeline this track belongs to. Can be %NULL.
1072 *
1073 * Returns: (nullable): The #GESTimeline this track belongs to. Can be %NULL.
1074 */
1075 const GESTimeline *
ges_track_get_timeline(GESTrack * track)1076 ges_track_get_timeline (GESTrack * track)
1077 {
1078 g_return_val_if_fail (GES_IS_TRACK (track), NULL);
1079 CHECK_THREAD (track);
1080
1081 return track->priv->timeline;
1082 }
1083
1084 /**
1085 * ges_track_get_mixing:
1086 * @track: a #GESTrack
1087 *
1088 * Gets if the underlying #NleComposition contains an expandable mixer.
1089 *
1090 * Returns: #True if there is a mixer, #False otherwise.
1091 */
1092 gboolean
ges_track_get_mixing(GESTrack * track)1093 ges_track_get_mixing (GESTrack * track)
1094 {
1095 g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
1096
1097 return track->priv->mixing;
1098 }
1099
1100 /**
1101 * ges_track_commit:
1102 * @track: a #GESTrack
1103 *
1104 * Commits all the pending changes of the TrackElement contained in the
1105 * track.
1106 *
1107 * When timing changes happen in a timeline, the changes are not
1108 * directly done inside NLE. This method needs to be called so any changes
1109 * on a clip contained in the timeline actually happen at the media
1110 * processing level.
1111 *
1112 * Returns: %TRUE if something as been commited %FALSE if nothing needed
1113 * to be commited
1114 */
1115 gboolean
ges_track_commit(GESTrack * track)1116 ges_track_commit (GESTrack * track)
1117 {
1118 g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
1119 CHECK_THREAD (track);
1120
1121 track_resort_and_fill_gaps (track);
1122
1123 return ges_nle_object_commit (track->priv->composition, TRUE);
1124 }
1125
1126
1127 /**
1128 * ges_track_set_create_element_for_gap_func:
1129 * @track: a #GESTrack
1130 * @func: (scope notified): The #GESCreateElementForGapFunc that will be used
1131 * to create #GstElement to fill gaps
1132 *
1133 * Sets the function that should be used to create the GstElement used to fill gaps.
1134 * To avoid to provide such a function we advice you to use the
1135 * #ges_audio_track_new and #ges_video_track_new constructor when possible.
1136 */
1137 void
ges_track_set_create_element_for_gap_func(GESTrack * track,GESCreateElementForGapFunc func)1138 ges_track_set_create_element_for_gap_func (GESTrack * track,
1139 GESCreateElementForGapFunc func)
1140 {
1141 g_return_if_fail (GES_IS_TRACK (track));
1142 CHECK_THREAD (track);
1143
1144 track->priv->create_element_for_gaps = func;
1145 }
1146