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