ges: modifies emission of the track/object-removed signal
[platform/upstream/gstreamer.git] / ges / ges-track.c
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., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 /**
22  * SECTION:ges-track
23  * @short_description: Composition of objects
24  *
25  * Corresponds to one output format (i.e. audio OR video).
26  *
27  * Contains the compatible TrackObject(s).
28  *
29  * Wraps GNonLin's 'gnlcomposition' element.
30  */
31
32 #include "ges-internal.h"
33 #include "ges-track.h"
34 #include "ges-track-object.h"
35 #include "gesmarshal.h"
36
37 G_DEFINE_TYPE (GESTrack, ges_track, GST_TYPE_BIN);
38
39 struct _GESTrackPrivate
40 {
41   /*< private > */
42   GESTimeline *timeline;
43   GList *trackobjects;
44   guint64 duration;
45
46   GstCaps *caps;
47
48   GstElement *composition;      /* The composition associated with this track */
49   GstPad *srcpad;               /* The source GhostPad */
50 };
51
52 enum
53 {
54   ARG_0,
55   ARG_CAPS,
56   ARG_TYPE,
57   ARG_DURATION,
58   ARG_LAST,
59   TRACK_OBJECT_ADDED,
60   TRACK_OBJECT_REMOVED,
61   LAST_SIGNAL
62 };
63
64 static guint ges_track_signals[LAST_SIGNAL] = { 0 };
65
66 static GParamSpec *properties[ARG_LAST];
67
68 static void pad_added_cb (GstElement * element, GstPad * pad, GESTrack * track);
69 static void
70 pad_removed_cb (GstElement * element, GstPad * pad, GESTrack * track);
71 static void composition_duration_cb (GstElement * composition, GParamSpec * arg
72     G_GNUC_UNUSED, GESTrack * obj);
73 static void
74 sort_track_objects_cb (GESTrackObject * child,
75     GParamSpec * arg G_GNUC_UNUSED, GESTrack * track);
76
77 static void
78 ges_track_get_property (GObject * object, guint property_id,
79     GValue * value, GParamSpec * pspec)
80 {
81   GESTrack *track = GES_TRACK (object);
82
83   switch (property_id) {
84     case ARG_CAPS:
85       gst_value_set_caps (value, track->priv->caps);
86       break;
87     case ARG_TYPE:
88       g_value_set_flags (value, track->type);
89       break;
90     case ARG_DURATION:
91       g_value_set_uint64 (value, track->priv->duration);
92       break;
93     default:
94       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
95   }
96 }
97
98 static void
99 ges_track_set_property (GObject * object, guint property_id,
100     const GValue * value, GParamSpec * pspec)
101 {
102   GESTrack *track = GES_TRACK (object);
103
104   switch (property_id) {
105     case ARG_CAPS:
106       ges_track_set_caps (track, gst_value_get_caps (value));
107       break;
108     case ARG_TYPE:
109       track->type = g_value_get_flags (value);
110       break;
111     default:
112       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
113   }
114 }
115
116 static void
117 ges_track_dispose (GObject * object)
118 {
119   GESTrack *track = (GESTrack *) object;
120   GESTrackPrivate *priv = track->priv;
121
122   while (priv->trackobjects) {
123     GESTrackObject *trobj = GES_TRACK_OBJECT (priv->trackobjects->data);
124     ges_track_remove_object (track, trobj);
125     ges_timeline_object_release_track_object ((GESTimelineObject *)
126         ges_track_object_get_timeline_object (trobj), trobj);
127   }
128
129   if (priv->composition) {
130     gst_bin_remove (GST_BIN (object), priv->composition);
131     priv->composition = NULL;
132   }
133
134   if (priv->caps) {
135     gst_caps_unref (priv->caps);
136     priv->caps = NULL;
137   }
138
139   G_OBJECT_CLASS (ges_track_parent_class)->dispose (object);
140 }
141
142 static void
143 ges_track_finalize (GObject * object)
144 {
145   G_OBJECT_CLASS (ges_track_parent_class)->finalize (object);
146 }
147
148 static void
149 ges_track_class_init (GESTrackClass * klass)
150 {
151   GObjectClass *object_class = G_OBJECT_CLASS (klass);
152
153   g_type_class_add_private (klass, sizeof (GESTrackPrivate));
154
155   object_class->get_property = ges_track_get_property;
156   object_class->set_property = ges_track_set_property;
157   object_class->dispose = ges_track_dispose;
158   object_class->finalize = ges_track_finalize;
159
160   /**
161    * GESTrack:caps
162    *
163    * Caps used to filter/choose the output stream. This is generally set to
164    * a generic set of caps like 'video/x-raw-rgb;video/x-raw-yuv' for raw video.
165    *
166    * Default value: #GST_CAPS_ANY.
167    */
168   properties[ARG_CAPS] = g_param_spec_boxed ("caps", "Caps",
169       "Caps used to filter/choose the output stream",
170       GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
171   g_object_class_install_property (object_class, ARG_CAPS,
172       properties[ARG_CAPS]);
173
174   /**
175    * GESTrack:duration
176    *
177    * Current duration of the track
178    *
179    * Default value: O
180    */
181   properties[ARG_DURATION] = g_param_spec_uint64 ("duration", "Duration",
182       "The current duration of the track", 0, G_MAXUINT64, GST_SECOND,
183       G_PARAM_READABLE);
184   g_object_class_install_property (object_class, ARG_DURATION,
185       properties[ARG_DURATION]);
186
187   /**
188    * GESTrack:track-type
189    *
190    * Type of stream the track outputs. This is used when creating the #GESTrack
191    * to specify in generic terms what type of content will be outputted.
192    *
193    * It also serves as a 'fast' way to check what type of data will be outputted
194    * from the #GESTrack without having to actually check the #GESTrack's caps
195    * property.
196    */
197   properties[ARG_TYPE] = g_param_spec_flags ("track-type", "TrackType",
198       "Type of stream the track outputs",
199       GES_TYPE_TRACK_TYPE, GES_TRACK_TYPE_CUSTOM,
200       G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
201   g_object_class_install_property (object_class, ARG_TYPE,
202       properties[ARG_TYPE]);
203
204   /**
205    * GESTrack::track-object-added
206    * @object: the #GESTrack
207    * @effect: the #GESTrackObject that was added.
208    *
209    * Will be emitted after a track object was added to the track.
210    *
211    * Since: 0.10.2
212    */
213   ges_track_signals[TRACK_OBJECT_ADDED] =
214       g_signal_new ("track-object-added", G_TYPE_FROM_CLASS (klass),
215       G_SIGNAL_RUN_FIRST, 0, NULL, NULL, ges_marshal_VOID__OBJECT,
216       G_TYPE_NONE, 1, GES_TYPE_TRACK_OBJECT);
217
218   /**
219    * GESTrack::track-object-removed
220    * @object: the #GESTrack
221    * @effect: the #GESTrackObject that was removed.
222    *
223    * Will be emitted after a track object was removed from the track.
224    *
225    * Since: 0.10.2
226    */
227   ges_track_signals[TRACK_OBJECT_REMOVED] =
228       g_signal_new ("track-object-removed", G_TYPE_FROM_CLASS (klass),
229       G_SIGNAL_RUN_FIRST, 0, NULL, NULL, ges_marshal_VOID__OBJECT,
230       G_TYPE_NONE, 1, GES_TYPE_TRACK_OBJECT);
231 }
232
233 static void
234 ges_track_init (GESTrack * self)
235 {
236   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
237       GES_TYPE_TRACK, GESTrackPrivate);
238
239   self->priv->composition = gst_element_factory_make ("gnlcomposition", NULL);
240
241   g_signal_connect (G_OBJECT (self->priv->composition), "notify::duration",
242       G_CALLBACK (composition_duration_cb), self);
243   g_signal_connect (self->priv->composition, "pad-added",
244       (GCallback) pad_added_cb, self);
245   g_signal_connect (self->priv->composition, "pad-removed",
246       (GCallback) pad_removed_cb, self);
247
248   if (!gst_bin_add (GST_BIN (self), self->priv->composition))
249     GST_ERROR ("Couldn't add composition to bin !");
250 }
251
252 /**
253  * ges_track_new:
254  * @type: The type of track
255  * @caps: The caps to restrict the output of the track to.
256  *
257  * Creates a new #GESTrack with the given @type and @caps.
258  *
259  * The newly created track will steal a reference to the caps. If you wish to 
260  * use those caps elsewhere, you will have to take an extra reference.
261  *
262  * Returns: A new #GESTrack.
263  */
264 GESTrack *
265 ges_track_new (GESTrackType type, GstCaps * caps)
266 {
267   GESTrack *track;
268
269   track = g_object_new (GES_TYPE_TRACK, "caps", caps, "track-type", type, NULL);
270   gst_caps_unref (caps);
271
272   return track;
273 }
274
275 /**
276  * ges_track_video_raw_new:
277  *
278  * Creates a new #GESTrack of type #GES_TRACK_TYPE_VIDEO and with generic
279  * raw video caps ("video/x-raw-yuv;video/x-raw-rgb");
280  *
281  * Returns: A new #GESTrack.
282  */
283 GESTrack *
284 ges_track_video_raw_new (void)
285 {
286   GESTrack *track;
287   GstCaps *caps = gst_caps_from_string ("video/x-raw-yuv;video/x-raw-rgb");
288
289   track = ges_track_new (GES_TRACK_TYPE_VIDEO, caps);
290
291   return track;
292 }
293
294 /**
295  * ges_track_audio_raw_new:
296  *
297  * Creates a new #GESTrack of type #GES_TRACK_TYPE_AUDIO and with generic
298  * raw audio caps ("audio/x-raw-int;audio/x-raw-float");
299  *
300  * Returns: A new #GESTrack.
301  */
302 GESTrack *
303 ges_track_audio_raw_new (void)
304 {
305   GESTrack *track;
306   GstCaps *caps = gst_caps_from_string ("audio/x-raw-int;audio/x-raw-float");
307
308   track = ges_track_new (GES_TRACK_TYPE_AUDIO, caps);
309
310   return track;
311 }
312
313 /**
314  * ges_track_set_timeline:
315  * @track: a #GESTrack
316  * @timeline: a #GESTimeline
317  *
318  * Sets @timeline as the timeline controlling @track.
319  */
320 void
321 ges_track_set_timeline (GESTrack * track, GESTimeline * timeline)
322 {
323   GST_DEBUG ("track:%p, timeline:%p", track, timeline);
324
325   track->priv->timeline = timeline;
326 }
327
328 /**
329  * ges_track_set_caps:
330  * @track: a #GESTrack
331  * @caps: the #GstCaps to set
332  *
333  * Sets the given @caps on the track.
334  */
335 void
336 ges_track_set_caps (GESTrack * track, const GstCaps * caps)
337 {
338   GESTrackPrivate *priv;
339
340   g_return_if_fail (GES_IS_TRACK (track));
341   g_return_if_fail (GST_IS_CAPS (caps));
342
343   GST_DEBUG ("track:%p, caps:%" GST_PTR_FORMAT, track, caps);
344
345   priv = track->priv;
346
347   if (priv->caps)
348     gst_caps_unref (priv->caps);
349   priv->caps = gst_caps_copy (caps);
350
351   g_object_set (priv->composition, "caps", caps, NULL);
352   /* FIXME : update all trackobjects ? */
353 }
354
355
356 /* FIXME : put the compare function in the utils */
357
358 static gint
359 objects_start_compare (GESTrackObject * a, GESTrackObject * b)
360 {
361   if (a->start == b->start) {
362     if (a->priority < b->priority)
363       return -1;
364     if (a->priority > b->priority)
365       return 1;
366     return 0;
367   }
368   if (a->start < b->start)
369     return -1;
370   if (a->start > b->start)
371     return 1;
372   return 0;
373 }
374
375 /**
376  * ges_track_add_object:
377  * @track: a #GESTrack
378  * @object: (transfer full): the #GESTrackObject to add
379  *
380  * Adds the given object to the track. Sets the object's controlling track,
381  * and thus takes ownership of the @object.
382  *
383  * An object can only be added to one track.
384  *
385  * Returns: #TRUE if the object was properly added. #FALSE if the track does not
386  * want to accept the object.
387  */
388 gboolean
389 ges_track_add_object (GESTrack * track, GESTrackObject * object)
390 {
391   g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
392   g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), FALSE);
393
394   GST_DEBUG ("track:%p, object:%p", track, object);
395
396   if (G_UNLIKELY (ges_track_object_get_track (object) != NULL)) {
397     GST_WARNING ("Object already belongs to another track");
398     return FALSE;
399   }
400
401   /* At this point, the track object shouldn't have any gnlobject since
402    * it hasn't been added to a track yet.
403    * FIXME : This check seems a bit obsolete */
404   if (G_UNLIKELY (ges_track_object_get_gnlobject (object) != NULL)) {
405     GST_ERROR ("TrackObject already controls a gnlobject !");
406     return FALSE;
407   }
408
409   if (G_UNLIKELY (!ges_track_object_set_track (object, track))) {
410     GST_ERROR ("Couldn't properly add the object to the Track");
411     return FALSE;
412   }
413
414   GST_DEBUG ("Adding object to ourself");
415
416   if (G_UNLIKELY (!gst_bin_add (GST_BIN (track->priv->composition),
417               ges_track_object_get_gnlobject (object)))) {
418     GST_WARNING ("Couldn't add object to the GnlComposition");
419     return FALSE;
420   }
421
422   g_object_ref_sink (object);
423   track->priv->trackobjects =
424       g_list_insert_sorted (track->priv->trackobjects, object,
425       (GCompareFunc) objects_start_compare);
426
427   g_signal_emit (track, ges_track_signals[TRACK_OBJECT_ADDED], 0,
428       GES_TRACK_OBJECT (object));
429
430   g_signal_connect (GES_TRACK_OBJECT (object), "notify::start",
431       G_CALLBACK (sort_track_objects_cb), track);
432
433   g_signal_connect (GES_TRACK_OBJECT (object), "notify::priority",
434       G_CALLBACK (sort_track_objects_cb), track);
435
436   return TRUE;
437 }
438
439 GList *
440 ges_track_get_objects (GESTrack * track)
441 {
442   GList *ret = NULL;
443   GList *tmp;
444
445   g_return_val_if_fail (GES_IS_TRACK (track), NULL);
446
447   for (tmp = track->priv->trackobjects; tmp; tmp = tmp->next) {
448     ret = g_list_prepend (ret, tmp->data);
449     g_object_ref (tmp->data);
450   }
451
452   ret = g_list_reverse (ret);
453   return ret;
454 }
455
456 /**
457  * ges_track_remove_object:
458  * @track: a #GESTrack
459  * @object: the #GESTrackObject to remove
460  *
461  * Removes the object from the track and unparents it.
462  * Unparenting it means the reference owned by @track on the @object will be
463  * removed. If you wish to use the @object after this function, make sure you
464  * call g_object_ref() before removing it from the @track.
465  *
466  * Returns: #TRUE if the object was removed, else #FALSE if the track
467  * could not remove the object (like if it didn't belong to the track).
468  */
469 gboolean
470 ges_track_remove_object (GESTrack * track, GESTrackObject * object)
471 {
472   GESTrackPrivate *priv;
473   GstElement *gnlobject;
474
475   g_return_val_if_fail (GES_IS_TRACK (track), FALSE);
476   g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), FALSE);
477
478   GST_DEBUG ("track:%p, object:%p", track, object);
479
480   priv = track->priv;
481
482   if (G_UNLIKELY (ges_track_object_get_track (object) != track)) {
483     GST_WARNING ("Object belongs to another track");
484     return FALSE;
485   }
486
487   if ((gnlobject = ges_track_object_get_gnlobject (object))) {
488     GST_DEBUG ("Removing GnlObject '%s' from composition '%s'",
489         GST_ELEMENT_NAME (gnlobject), GST_ELEMENT_NAME (priv->composition));
490     if (!gst_bin_remove (GST_BIN (priv->composition), gnlobject)) {
491       GST_WARNING ("Failed to remove gnlobject from composition");
492       return FALSE;
493     }
494   }
495
496   ges_track_object_set_track (object, NULL);
497
498   g_signal_emit (track, ges_track_signals[TRACK_OBJECT_REMOVED], 0,
499       GES_TRACK_OBJECT (object));
500
501   priv->trackobjects = g_list_remove (priv->trackobjects, object);
502
503   g_object_unref (object);
504
505   return TRUE;
506 }
507
508 static void
509 pad_added_cb (GstElement * element, GstPad * pad, GESTrack * track)
510 {
511   GESTrackPrivate *priv = track->priv;
512
513   GST_DEBUG ("track:%p, pad %s:%s", track, GST_DEBUG_PAD_NAME (pad));
514
515   /* ghost the pad */
516   priv->srcpad = gst_ghost_pad_new ("src", pad);
517
518   gst_pad_set_active (priv->srcpad, TRUE);
519
520   gst_element_add_pad (GST_ELEMENT (track), priv->srcpad);
521
522   GST_DEBUG ("done");
523 }
524
525 static void
526 pad_removed_cb (GstElement * element, GstPad * pad, GESTrack * track)
527 {
528   GESTrackPrivate *priv = track->priv;
529
530   GST_DEBUG ("track:%p, pad %s:%s", track, GST_DEBUG_PAD_NAME (pad));
531
532   if (G_LIKELY (priv->srcpad)) {
533     gst_pad_set_active (priv->srcpad, FALSE);
534     gst_element_remove_pad (GST_ELEMENT (track), priv->srcpad);
535     priv->srcpad = NULL;
536   }
537
538   GST_DEBUG ("done");
539 }
540
541 static void
542 composition_duration_cb (GstElement * composition,
543     GParamSpec * arg G_GNUC_UNUSED, GESTrack * obj)
544 {
545   guint64 duration;
546
547   g_object_get (composition, "duration", &duration, NULL);
548
549
550   if (obj->priv->duration != duration) {
551     GST_DEBUG ("composition duration : %" GST_TIME_FORMAT " current : %"
552         GST_TIME_FORMAT, GST_TIME_ARGS (duration),
553         GST_TIME_ARGS (obj->priv->duration));
554
555     obj->priv->duration = duration;
556
557 #if GLIB_CHECK_VERSION(2,26,0)
558     g_object_notify_by_pspec (G_OBJECT (obj), properties[ARG_DURATION]);
559 #else
560     g_object_notify (G_OBJECT (obj), "duration");
561 #endif
562   }
563 }
564
565 static void
566 sort_track_objects_cb (GESTrackObject * child,
567     GParamSpec * arg G_GNUC_UNUSED, GESTrack * track)
568 {
569   track->priv->trackobjects =
570       g_list_sort (track->priv->trackobjects,
571       (GCompareFunc) objects_start_compare);
572 }
573
574 /**
575  * ges_track_get_caps:
576  * @track: a #GESTrack
577  *
578  * Get the #GstCaps this track is configured to output.
579  *
580  * Returns: The #GstCaps this track is configured to output.
581  */
582 const GstCaps *
583 ges_track_get_caps (GESTrack * track)
584 {
585   g_return_val_if_fail (GES_IS_TRACK (track), NULL);
586
587   return track->priv->caps;
588 }
589
590 /**
591  * ges_track_get_timeline:
592  * @track: a #GESTrack
593  *
594  * Get the #GESTimeline this track belongs to. Can be %NULL.
595  *
596  * Returns: The #GESTimeline this track belongs to. Can be %NULL.
597  */
598 const GESTimeline *
599 ges_track_get_timeline (GESTrack * track)
600 {
601   g_return_val_if_fail (GES_IS_TRACK (track), NULL);
602
603   return track->priv->timeline;
604 }
605
606 /**
607  * ges_track_enable_update:
608  * @track : a #GESTrack
609  * @enabled : TRUE if the composition must be updated, FALSE otherwise.
610  *
611  * Sets the @track 's composition update property to @enabled .
612  *
613  * Returns : True if success, FALSE otherwise.
614  */
615 gboolean
616 ges_track_enable_update (GESTrack * track, gboolean enabled)
617 {
618   gboolean update;
619
620   g_object_set (track->priv->composition, "update", enabled, NULL);
621
622   g_object_get (track->priv->composition, "update", &update, NULL);
623
624   if (update == enabled) {
625     return TRUE;
626   } else {
627     return FALSE;
628   }
629 }