From ce8afba36700a6b0bac00c11ec6b8d5cf62a5930 Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Thu, 17 May 2012 20:49:01 -0400 Subject: [PATCH 01/19] timeline: Add a method to get the timeline duration + Bind it in python API: ges_timeline_get_duration --- bindings/python/ges.defs | 6 ++++++ docs/libs/ges-sections.txt | 1 + ges/ges-timeline.c | 16 ++++++++++++++++ ges/ges-timeline.h | 2 ++ 4 files changed, 25 insertions(+) diff --git a/bindings/python/ges.defs b/bindings/python/ges.defs index 3805b3cacd..4052591a5b 100644 --- a/bindings/python/ges.defs +++ b/bindings/python/ges.defs @@ -911,6 +911,12 @@ (return-type "gboolean") ) +(define-method get_duration + (of-object "GESTimeline") + (c-name "ges_timeline_get_duration") + (return-type "guint64") +) + ;; From ges-timeline-layer.h diff --git a/docs/libs/ges-sections.txt b/docs/libs/ges-sections.txt index 66f79a1d56..3b94fc64b7 100644 --- a/docs/libs/ges-sections.txt +++ b/docs/libs/ges-sections.txt @@ -266,6 +266,7 @@ ges_timeline_is_updating ges_timeline_get_tracks ges_timeline_get_layers ges_timeline_get_track_for_pad +ges_timeline_get_duration GESTimelinePrivate GESTimelineClass diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index dcb09f283d..b096e93c3f 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -2397,3 +2397,19 @@ ges_timeline_enable_update (GESTimeline * timeline, gboolean enabled) return res; } + +/** + * ges_timeline_get_duration + * @timeline: a #GESTimeline + * + * Get the current duration of @timeline + * + * Returns: The current duration of @timeline + */ +GstClockTime +ges_timeline_get_duration (GESTimeline * timeline) +{ + g_return_val_if_fail (GES_IS_TIMELINE (timeline), GST_CLOCK_TIME_NONE); + + return timeline->priv->duration; +} diff --git a/ges/ges-timeline.h b/ges/ges-timeline.h index de51bcb1d0..ad1992ca6e 100644 --- a/ges/ges-timeline.h +++ b/ges/ges-timeline.h @@ -102,6 +102,8 @@ GList *ges_timeline_get_tracks (GESTimeline *timeline); gboolean ges_timeline_enable_update(GESTimeline * timeline, gboolean enabled); gboolean ges_timeline_is_updating (GESTimeline * timeline); +GstClockTime ges_timeline_get_duration (GESTimeline *timeline); + G_END_DECLS #endif /* _GES_TIMELINE */ From 171bf8e627c3b3c692f6246e68df194badbe8884 Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Wed, 16 May 2012 12:59:33 -0400 Subject: [PATCH 02/19] timeline: Make use of our own knowledge of the timeline duration Do not use each Track durations as it end going in loop as we have the Tracks that need to know about timeline's duration to create or not gaps in the end and then the timeline references on Tracks duration for its duration. We have this information locally so just make proper use of it. --- ges/ges-timeline.c | 65 ++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index b096e93c3f..8ce1e69bb3 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -449,6 +449,28 @@ sort_layers (gpointer a, gpointer b) return 0; } +static void +timeline_update_duration (GESTimeline * timeline) +{ + GstClockTime *cduration; + GSequenceIter *it = g_sequence_get_end_iter (timeline->priv->starts_ends); + + it = g_sequence_iter_prev (it); + if (g_sequence_iter_is_end (it)) + return; + + cduration = g_sequence_get (it); + if (cduration && timeline->priv->duration != *cduration) { + GST_DEBUG ("track duration : %" GST_TIME_FORMAT " current : %" + GST_TIME_FORMAT, GST_TIME_ARGS (*cduration), + GST_TIME_ARGS (timeline->priv->duration)); + + timeline->priv->duration = *cduration; + + g_object_notify_by_pspec (G_OBJECT (timeline), properties[PROP_DURATION]); + } +} + static gint objects_start_compare (GESTrackObject * a, GESTrackObject * b) { @@ -553,6 +575,7 @@ sort_starts_ends_end (GESTimeline * timeline, GESTrackObject * obj) *end = obj->start + obj->duration; g_sequence_sort_changed (iter, (GCompareDataFunc) compare_uint64, NULL); + timeline_update_duration (timeline); } static inline void @@ -567,6 +590,7 @@ sort_starts_ends_start (GESTimeline * timeline, GESTrackObject * obj) *start = obj->start; g_sequence_sort_changed (iter, (GCompareDataFunc) compare_uint64, NULL); + timeline_update_duration (timeline); } static inline void @@ -590,6 +614,7 @@ resort_all_starts_ends (GESTimeline * timeline) } g_sequence_sort (priv->starts_ends, (GCompareDataFunc) compare_uint64, NULL); + timeline_update_duration (timeline); } static inline void @@ -645,7 +670,7 @@ stop_tracking_for_snapping (GESTimeline * timeline, GESTrackObject * tckobj) g_sequence_remove (iter_start); g_sequence_remove (iter_end); g_sequence_remove (tckobj_iter); - + timeline_update_duration (timeline); } static void @@ -672,6 +697,8 @@ start_tracking_track_obj (GESTimeline * timeline, GESTrackObject * tckobj) g_hash_table_insert (priv->by_object, pend, tckobj); timeline->priv->movecontext.needs_move_ctx = TRUE; + + timeline_update_duration (timeline); } static inline void @@ -1707,33 +1734,6 @@ track_object_removed_cb (GESTrack * track, GESTrackObject * object, } } -static void -track_duration_cb (GstElement * track, - GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline) -{ - guint64 duration, max_duration = 0; - GList *tmp; - - for (tmp = timeline->priv->tracks; tmp; tmp = g_list_next (tmp)) { - TrackPrivate *tr_priv = (TrackPrivate *) tmp->data; - g_object_get (tr_priv->track, "duration", &duration, NULL); - - GST_DEBUG_OBJECT (track, "track duration : %" GST_TIME_FORMAT, - GST_TIME_ARGS (duration)); - max_duration = MAX (duration, max_duration); - } - - if (timeline->priv->duration != max_duration) { - GST_DEBUG ("track duration : %" GST_TIME_FORMAT " current : %" - GST_TIME_FORMAT, GST_TIME_ARGS (max_duration), - GST_TIME_ARGS (timeline->priv->duration)); - - timeline->priv->duration = max_duration; - - g_object_notify_by_pspec (G_OBJECT (timeline), properties[PROP_DURATION]); - } -} - static GstStateChangeReturn ges_timeline_change_state (GstElement * element, GstStateChange transition) { @@ -2169,11 +2169,6 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track) /* ensure that each existing timeline object has the opportunity to create a * track object for this track*/ - /* We connect to the duration change notify, so we can update - * our duration accordingly */ - g_signal_connect (G_OBJECT (track), "notify::duration", - G_CALLBACK (track_duration_cb), timeline); - /* We connect to the object for the timeline editing mode management */ g_signal_connect (G_OBJECT (track), "track-object-added", G_CALLBACK (track_object_added_cb), timeline); @@ -2192,8 +2187,6 @@ ges_timeline_add_track (GESTimeline * timeline, GESTrack * track) g_list_free (objects); } - track_duration_cb (GST_ELEMENT (track), NULL, timeline); - return TRUE; } @@ -2246,8 +2239,6 @@ ges_timeline_remove_track (GESTimeline * timeline, GESTrack * track) /* Remove pad-added/-removed handlers */ g_signal_handlers_disconnect_by_func (track, pad_added_cb, tr_priv); g_signal_handlers_disconnect_by_func (track, pad_removed_cb, tr_priv); - g_signal_handlers_disconnect_by_func (track, track_duration_cb, - tr_priv->track); g_signal_handlers_disconnect_by_func (track, track_object_added_cb, timeline); g_signal_handlers_disconnect_by_func (track, track_object_removed_cb, timeline); From c2bedb182ec59fcda2f9dc7974dac47ba62bc7fc Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Wed, 16 May 2012 12:23:52 -0400 Subject: [PATCH 03/19] track: Use a GSequence to keep the sorted list of TrackObject-s Use a GSequence instead of a GList to optimise the process. Conflicts: ges/ges-track.c --- ges/ges-track.c | 137 ++++++++++++++++++++++++++++++------------------ 1 file changed, 86 insertions(+), 51 deletions(-) diff --git a/ges/ges-track.c b/ges/ges-track.c index c7ddf3c35d..ea46f72895 100644 --- a/ges/ges-track.c +++ b/ges/ges-track.c @@ -40,7 +40,7 @@ struct _GESTrackPrivate { /*< private > */ GESTimeline *timeline; - GList *trackobjects; + GSequence *tckobjs_by_start; guint64 duration; GstCaps *caps; @@ -119,18 +119,77 @@ ges_track_set_property (GObject * object, guint property_id, } } +static void +add_trackobj_to_list_foreach (GESTrackObject * trackobj, GList ** list) +{ + g_object_ref (trackobj); + *list = g_list_prepend (*list, trackobj); +} + +/* Remove @object from @track, but keeps it in the sequence this is needed + * when finalizing as we can not change a GSequence at the same time we are + * accessing it + */ +static gboolean +remove_object_internal (GESTrack * track, GESTrackObject * object) +{ + GESTrackPrivate *priv; + GstElement *gnlobject; + + GST_DEBUG ("track:%p, object:%p", track, object); + + priv = track->priv; + + if (G_UNLIKELY (ges_track_object_get_track (object) != track)) { + GST_WARNING ("Object belongs to another track"); + return FALSE; + } + + if ((gnlobject = ges_track_object_get_gnlobject (object))) { + GST_DEBUG ("Removing GnlObject '%s' from composition '%s'", + GST_ELEMENT_NAME (gnlobject), GST_ELEMENT_NAME (priv->composition)); + + if (!gst_bin_remove (GST_BIN (priv->composition), gnlobject)) { + GST_WARNING ("Failed to remove gnlobject from composition"); + return FALSE; + } + + gst_element_set_state (gnlobject, GST_STATE_NULL); + } + + g_signal_handlers_disconnect_by_func (object, sort_track_objects_cb, NULL); + + ges_track_object_set_track (object, NULL); + + g_signal_emit (track, ges_track_signals[TRACK_OBJECT_REMOVED], 0, + GES_TRACK_OBJECT (object)); + + g_object_unref (object); + + return TRUE; +} + +static void +dispose_tckobjs_foreach (GESTrackObject * tckobj, GESTrack * track) +{ + GESTimelineObject *tlobj; + + tlobj = ges_track_object_get_timeline_object (tckobj); + + remove_object_internal (track, tckobj); + ges_timeline_object_release_track_object (tlobj, tckobj); +} + static void ges_track_dispose (GObject * object) { GESTrack *track = (GESTrack *) object; GESTrackPrivate *priv = track->priv; - while (priv->trackobjects) { - GESTrackObject *trobj = GES_TRACK_OBJECT (priv->trackobjects->data); - ges_track_remove_object (track, trobj); - ges_timeline_object_release_track_object ((GESTimelineObject *) - ges_track_object_get_timeline_object (trobj), trobj); - } + /* Remove all TrackObjects and drop our reference */ + g_sequence_foreach (track->priv->tckobjs_by_start, + (GFunc) dispose_tckobjs_foreach, track); + g_sequence_free (priv->tckobjs_by_start); if (priv->composition) { gst_bin_remove (GST_BIN (object), priv->composition); @@ -287,6 +346,7 @@ ges_track_init (GESTrack * self) self->priv->composition = gst_element_factory_make ("gnlcomposition", NULL); self->priv->updating = TRUE; + self->priv->tckobjs_by_start = g_sequence_new (NULL); g_signal_connect (G_OBJECT (self->priv->composition), "notify::duration", G_CALLBACK (composition_duration_cb), self); @@ -414,8 +474,10 @@ ges_track_set_caps (GESTrack * track, const GstCaps * caps) /* FIXME : put the compare function in the utils */ static gint -objects_start_compare (GESTrackObject * a, GESTrackObject * b) +objects_start_compare (GESTrackObject * a, GESTrackObject * b, + gpointer user_data) { + if (a->start == b->start) { if (a->priority < b->priority) return -1; @@ -472,9 +534,8 @@ ges_track_add_object (GESTrack * track, GESTrackObject * object) } g_object_ref_sink (object); - track->priv->trackobjects = - g_list_insert_sorted (track->priv->trackobjects, object, - (GCompareFunc) objects_start_compare); + g_sequence_insert_sorted (track->priv->tckobjs_by_start, object, + (GCompareDataFunc) objects_start_compare, NULL); g_signal_emit (track, ges_track_signals[TRACK_OBJECT_ADDED], 0, GES_TRACK_OBJECT (object)); @@ -482,6 +543,9 @@ ges_track_add_object (GESTrack * track, GESTrackObject * object) g_signal_connect (GES_TRACK_OBJECT (object), "notify::start", G_CALLBACK (sort_track_objects_cb), track); + g_signal_connect (GES_TRACK_OBJECT (object), "notify::duration", + G_CALLBACK (sort_track_objects_cb), track); + g_signal_connect (GES_TRACK_OBJECT (object), "notify::priority", G_CALLBACK (sort_track_objects_cb), track); @@ -501,14 +565,11 @@ GList * ges_track_get_objects (GESTrack * track) { GList *ret = NULL; - GList *tmp; g_return_val_if_fail (GES_IS_TRACK (track), NULL); - for (tmp = track->priv->trackobjects; tmp; tmp = tmp->next) { - ret = g_list_prepend (ret, tmp->data); - g_object_ref (tmp->data); - } + g_sequence_foreach (track->priv->tckobjs_by_start, + (GFunc) add_trackobj_to_list_foreach, &ret); ret = g_list_reverse (ret); return ret; @@ -530,45 +591,20 @@ ges_track_get_objects (GESTrack * track) gboolean ges_track_remove_object (GESTrack * track, GESTrackObject * object) { - GESTrackPrivate *priv; - GstElement *gnlobject; + GSequenceIter *it; g_return_val_if_fail (GES_IS_TRACK (track), FALSE); g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), FALSE); - GST_DEBUG ("track:%p, object:%p", track, object); + if (remove_object_internal (track, object) == TRUE) { + it = g_sequence_lookup (track->priv->tckobjs_by_start, object, + (GCompareDataFunc) objects_start_compare, NULL); + g_sequence_remove (it); - priv = track->priv; - - if (G_UNLIKELY (ges_track_object_get_track (object) != track)) { - GST_WARNING ("Object belongs to another track"); - return FALSE; + return TRUE; } - if ((gnlobject = ges_track_object_get_gnlobject (object))) { - GST_DEBUG ("Removing GnlObject '%s' from composition '%s'", - GST_ELEMENT_NAME (gnlobject), GST_ELEMENT_NAME (priv->composition)); - - if (!gst_bin_remove (GST_BIN (priv->composition), gnlobject)) { - GST_WARNING ("Failed to remove gnlobject from composition"); - return FALSE; - } - - gst_element_set_state (gnlobject, GST_STATE_NULL); - } - - g_signal_handlers_disconnect_by_func (object, sort_track_objects_cb, NULL); - - ges_track_object_set_track (object, NULL); - - g_signal_emit (track, ges_track_signals[TRACK_OBJECT_REMOVED], 0, - GES_TRACK_OBJECT (object)); - - priv->trackobjects = g_list_remove (priv->trackobjects, object); - - g_object_unref (object); - - return TRUE; + return FALSE; } static void @@ -628,9 +664,8 @@ static void sort_track_objects_cb (GESTrackObject * child, GParamSpec * arg G_GNUC_UNUSED, GESTrack * track) { - track->priv->trackobjects = - g_list_sort (track->priv->trackobjects, - (GCompareFunc) objects_start_compare); + g_sequence_sort (track->priv->tckobjs_by_start, + (GCompareDataFunc) objects_start_compare, NULL); } static void From b0e1b20cc306f8ffcdb1f78b4a19f2dcbb80d85e Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Wed, 9 May 2012 11:20:24 -0400 Subject: [PATCH 04/19] track: Restructurate file so we have private method and API properly separeted --- ges/ges-track.c | 267 ++++++++++++++++++++++++------------------------ 1 file changed, 131 insertions(+), 136 deletions(-) diff --git a/ges/ges-track.c b/ges/ges-track.c index ea46f72895..c920ec2705 100644 --- a/ges/ges-track.c +++ b/ges/ges-track.c @@ -73,50 +73,26 @@ static void pad_removed_cb (GstElement * element, GstPad * pad, GESTrack * track); static void composition_duration_cb (GstElement * composition, GParamSpec * arg G_GNUC_UNUSED, GESTrack * obj); -static void -sort_track_objects_cb (GESTrackObject * child, - GParamSpec * arg G_GNUC_UNUSED, GESTrack * track); -static void timeline_duration_cb (GESTimeline * timeline, - GParamSpec * arg G_GNUC_UNUSED, GESTrack * track); +/* Private methods/functions/callbacks */ -static void -ges_track_get_property (GObject * object, guint property_id, - GValue * value, GParamSpec * pspec) +static gint +objects_start_compare (GESTrackObject * a, GESTrackObject * b, + gpointer user_data) { - GESTrack *track = GES_TRACK (object); - switch (property_id) { - case ARG_CAPS: - gst_value_set_caps (value, track->priv->caps); - break; - case ARG_TYPE: - g_value_set_flags (value, track->type); - break; - case ARG_DURATION: - g_value_set_uint64 (value, track->priv->duration); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); - } -} - -static void -ges_track_set_property (GObject * object, guint property_id, - const GValue * value, GParamSpec * pspec) -{ - GESTrack *track = GES_TRACK (object); - - switch (property_id) { - case ARG_CAPS: - ges_track_set_caps (track, gst_value_get_caps (value)); - break; - case ARG_TYPE: - track->type = g_value_get_flags (value); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + if (a->start == b->start) { + if (a->priority < b->priority) + return -1; + if (a->priority > b->priority) + return 1; + return 0; } + if (a->start < b->start) + return -1; + if (a->start > b->start) + return 1; + return 0; } static void @@ -126,6 +102,82 @@ add_trackobj_to_list_foreach (GESTrackObject * trackobj, GList ** list) *list = g_list_prepend (*list, trackobj); } + +static void +sort_track_objects_cb (GESTrackObject * child, + GParamSpec * arg G_GNUC_UNUSED, GESTrack * track) +{ + g_sequence_sort (track->priv->tckobjs_by_start, + (GCompareDataFunc) objects_start_compare, NULL); +} + +static void +timeline_duration_cb (GESTimeline * timeline, + GParamSpec * arg G_GNUC_UNUSED, GESTrack * track) +{ + guint64 duration; + + g_object_get (timeline, "duration", &duration, NULL); + g_object_set (GES_TRACK (track)->priv->background, "duration", duration, + NULL); + + GST_DEBUG_OBJECT (track, "Updating background duration to %" GST_TIME_FORMAT, + GST_TIME_ARGS (duration)); +} + +static void +pad_added_cb (GstElement * element, GstPad * pad, GESTrack * track) +{ + GESTrackPrivate *priv = track->priv; + + GST_DEBUG ("track:%p, pad %s:%s", track, GST_DEBUG_PAD_NAME (pad)); + + /* ghost the pad */ + priv->srcpad = gst_ghost_pad_new ("src", pad); + + gst_pad_set_active (priv->srcpad, TRUE); + + gst_element_add_pad (GST_ELEMENT (track), priv->srcpad); + + GST_DEBUG ("done"); +} + +static void +pad_removed_cb (GstElement * element, GstPad * pad, GESTrack * track) +{ + GESTrackPrivate *priv = track->priv; + + GST_DEBUG ("track:%p, pad %s:%s", track, GST_DEBUG_PAD_NAME (pad)); + + if (G_LIKELY (priv->srcpad)) { + gst_pad_set_active (priv->srcpad, FALSE); + gst_element_remove_pad (GST_ELEMENT (track), priv->srcpad); + priv->srcpad = NULL; + } + + GST_DEBUG ("done"); +} + +static void +composition_duration_cb (GstElement * composition, + GParamSpec * arg G_GNUC_UNUSED, GESTrack * obj) +{ + guint64 duration; + + g_object_get (composition, "duration", &duration, NULL); + + + if (obj->priv->duration != duration) { + GST_DEBUG ("composition duration : %" GST_TIME_FORMAT " current : %" + GST_TIME_FORMAT, GST_TIME_ARGS (duration), + GST_TIME_ARGS (obj->priv->duration)); + + obj->priv->duration = duration; + + g_object_notify_by_pspec (G_OBJECT (obj), properties[ARG_DURATION]); + } +} + /* Remove @object from @track, but keeps it in the sequence this is needed * when finalizing as we can not change a GSequence at the same time we are * accessing it @@ -180,6 +232,46 @@ dispose_tckobjs_foreach (GESTrackObject * tckobj, GESTrack * track) ges_timeline_object_release_track_object (tlobj, tckobj); } +/* GObject virtual methods */ +static void +ges_track_get_property (GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + GESTrack *track = GES_TRACK (object); + + switch (property_id) { + case ARG_CAPS: + gst_value_set_caps (value, track->priv->caps); + break; + case ARG_TYPE: + g_value_set_flags (value, track->type); + break; + case ARG_DURATION: + g_value_set_uint64 (value, track->priv->duration); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +ges_track_set_property (GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + GESTrack *track = GES_TRACK (object); + + switch (property_id) { + case ARG_CAPS: + ges_track_set_caps (track, gst_value_get_caps (value)); + break; + case ARG_TYPE: + track->type = g_value_get_flags (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + static void ges_track_dispose (GObject * object) { @@ -470,28 +562,6 @@ ges_track_set_caps (GESTrack * track, const GstCaps * caps) /* FIXME : update all trackobjects ? */ } - -/* FIXME : put the compare function in the utils */ - -static gint -objects_start_compare (GESTrackObject * a, GESTrackObject * b, - gpointer user_data) -{ - - if (a->start == b->start) { - if (a->priority < b->priority) - return -1; - if (a->priority > b->priority) - return 1; - return 0; - } - if (a->start < b->start) - return -1; - if (a->start > b->start) - return 1; - return 0; -} - /** * ges_track_add_object: * @track: a #GESTrack @@ -607,81 +677,6 @@ ges_track_remove_object (GESTrack * track, GESTrackObject * object) return FALSE; } -static void -pad_added_cb (GstElement * element, GstPad * pad, GESTrack * track) -{ - GESTrackPrivate *priv = track->priv; - - GST_DEBUG ("track:%p, pad %s:%s", track, GST_DEBUG_PAD_NAME (pad)); - - /* ghost the pad */ - priv->srcpad = gst_ghost_pad_new ("src", pad); - - gst_pad_set_active (priv->srcpad, TRUE); - - gst_element_add_pad (GST_ELEMENT (track), priv->srcpad); - - GST_DEBUG ("done"); -} - -static void -pad_removed_cb (GstElement * element, GstPad * pad, GESTrack * track) -{ - GESTrackPrivate *priv = track->priv; - - GST_DEBUG ("track:%p, pad %s:%s", track, GST_DEBUG_PAD_NAME (pad)); - - if (G_LIKELY (priv->srcpad)) { - gst_pad_set_active (priv->srcpad, FALSE); - gst_element_remove_pad (GST_ELEMENT (track), priv->srcpad); - priv->srcpad = NULL; - } - - GST_DEBUG ("done"); -} - -static void -composition_duration_cb (GstElement * composition, - GParamSpec * arg G_GNUC_UNUSED, GESTrack * obj) -{ - guint64 duration; - - g_object_get (composition, "duration", &duration, NULL); - - - if (obj->priv->duration != duration) { - GST_DEBUG ("composition duration : %" GST_TIME_FORMAT " current : %" - GST_TIME_FORMAT, GST_TIME_ARGS (duration), - GST_TIME_ARGS (obj->priv->duration)); - - obj->priv->duration = duration; - - g_object_notify_by_pspec (G_OBJECT (obj), properties[ARG_DURATION]); - } -} - -static void -sort_track_objects_cb (GESTrackObject * child, - GParamSpec * arg G_GNUC_UNUSED, GESTrack * track) -{ - g_sequence_sort (track->priv->tckobjs_by_start, - (GCompareDataFunc) objects_start_compare, NULL); -} - -static void -timeline_duration_cb (GESTimeline * timeline, - GParamSpec * arg G_GNUC_UNUSED, GESTrack * track) -{ - guint64 duration; - - g_object_get (timeline, "duration", &duration, NULL); - g_object_set (GES_TRACK (track)->priv->background, "duration", duration, - NULL); - - GST_DEBUG_OBJECT (track, "Updating background duration to %" GST_TIME_FORMAT, - GST_TIME_ARGS (duration)); -} - /** * ges_track_get_caps: * @track: a #GESTrack From f84c183f63a435184e9d030c4490f3e63dc15184 Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Wed, 9 May 2012 11:45:02 -0400 Subject: [PATCH 05/19] track: Properly fill gaps API: GESCreateElementForGapFunc Virtual method type API: ges_track_set_create_element_for_gap_func --- docs/libs/ges-sections.txt | 1 + ges/ges-track.c | 320 ++++++++++++++++++++++++++++--------- ges/ges-track.h | 16 ++ 3 files changed, 265 insertions(+), 72 deletions(-) diff --git a/docs/libs/ges-sections.txt b/docs/libs/ges-sections.txt index 3b94fc64b7..f443e2ee79 100644 --- a/docs/libs/ges-sections.txt +++ b/docs/libs/ges-sections.txt @@ -61,6 +61,7 @@ ges_track_get_caps ges_track_enable_update ges_track_get_objects ges_track_is_updating +ges_track_set_create_element_for_gap_func GESTrackClass GESTrackPrivate diff --git a/ges/ges-track.c b/ges/ges-track.c index c920ec2705..a44348c7cf 100644 --- a/ges/ges-track.c +++ b/ges/ges-track.c @@ -36,20 +36,35 @@ G_DEFINE_TYPE (GESTrack, ges_track, GST_TYPE_BIN); +/* Structure that represents gaps and keep knowledge + * of the gaps filled in the track */ +typedef struct +{ + GstElement *gnlobj; + + GstClockTime start; + GstClockTime duration; + GESTrack *track; +} Gap; + struct _GESTrackPrivate { /*< private > */ GESTimeline *timeline; GSequence *tckobjs_by_start; + GList *gaps; + guint64 duration; GstCaps *caps; GstElement *composition; /* The composition associated with this track */ - GstElement *background; /* The backgrond, handle the gaps in the track */ GstPad *srcpad; /* The source GhostPad */ gboolean updating; + + /* Virtual method to create GstElement that fill gaps */ + GESCreateElementForGapFunc create_element_for_gaps; }; enum @@ -76,6 +91,7 @@ static void composition_duration_cb (GstElement * composition, GParamSpec * arg /* Private methods/functions/callbacks */ +/* Utilities */ static gint objects_start_compare (GESTrackObject * a, GESTrackObject * b, gpointer user_data) @@ -102,27 +118,165 @@ add_trackobj_to_list_foreach (GESTrackObject * trackobj, GList ** list) *list = g_list_prepend (*list, trackobj); } +static Gap * +gap_new (GESTrack * track, GstClockTime start, GstClockTime duration) +{ + GstElement *gnlsrc, *elem; + + Gap *new_gap; + + gnlsrc = gst_element_factory_make ("gnlsource", NULL); + elem = track->priv->create_element_for_gaps (track); + if (G_UNLIKELY (gst_bin_add (GST_BIN (gnlsrc), elem) == FALSE)) { + GST_WARNING_OBJECT (track, "Could not create gap filler"); + + if (gnlsrc) + gst_object_unref (gnlsrc); + + if (elem) + gst_object_unref (elem); + + return NULL; + } + + if (G_UNLIKELY (gst_bin_add (GST_BIN (track->priv->composition), + gnlsrc) == FALSE)) { + GST_WARNING_OBJECT (track, "Could not add gap to the composition"); + + if (gnlsrc) + gst_object_unref (gnlsrc); + + if (elem) + gst_object_unref (elem); + + return NULL; + } + + new_gap = g_slice_new (Gap); + new_gap->start = start; + new_gap->duration = duration; + new_gap->track = track; + new_gap->gnlobj = gst_object_ref (gnlsrc); + + + g_object_set (gnlsrc, "start", new_gap->start, "duration", new_gap->duration, + "priority", 0, NULL); + + GST_DEBUG_OBJECT (track, + "Created gap with start %" GST_TIME_FORMAT " duration %" GST_TIME_FORMAT, + GST_TIME_ARGS (new_gap->start), GST_TIME_ARGS (new_gap->duration)); + + + return new_gap; +} + +static void +free_gap (Gap * gap) +{ + GESTrack *track = gap->track; + + GST_DEBUG_OBJECT (track, "Removed gap with start %" GST_TIME_FORMAT + " duration %" GST_TIME_FORMAT, GST_TIME_ARGS (gap->start), + GST_TIME_ARGS (gap->duration)); + gst_bin_remove (GST_BIN (track->priv->composition), gap->gnlobj); + gst_element_set_state (gap->gnlobj, GST_STATE_NULL); + gst_object_unref (gap->gnlobj); + + g_slice_free (Gap, gap); +} + +static inline void +update_gaps (GESTrack * track) +{ + Gap *gap; + GSequenceIter *it; + + GESTrackObject *tckobj; + GstClockTime start, end, duration = 0, timeline_duration; + + GESTrackPrivate *priv = track->priv; + + if (priv->create_element_for_gaps == NULL) { + GST_INFO ("Not filling the gaps as no create_element_for_gaps vmethod" + " provided"); + return; + } + + /* 1- Remove all gaps */ + g_list_free_full (priv->gaps, (GDestroyNotify) free_gap); + priv->gaps = NULL; + + /* 2- And recalculate gaps */ + for (it = g_sequence_get_begin_iter (priv->tckobjs_by_start); + g_sequence_iter_is_end (it) == FALSE; it = g_sequence_iter_next (it)) { + tckobj = g_sequence_get (it); + + start = GES_TRACK_OBJECT_START (tckobj); + end = start + GES_TRACK_OBJECT_DURATION (tckobj); + + if (start > duration) { + /* 3- Fill gap */ + gap = gap_new (track, duration, start - duration); + + if (G_LIKELY (gap != NULL)) + priv->gaps = g_list_prepend (priv->gaps, gap); + } + + duration = MAX (duration, end); + } + + /* 4- Add a gap at the end of the timeline if needed */ + if (priv->timeline) { + g_object_get (priv->timeline, "duration", &timeline_duration, NULL); + + if (duration < timeline_duration) { + gap = gap_new (track, duration, timeline_duration - duration); + + if (G_LIKELY (gap != NULL)) { + priv->gaps = g_list_prepend (priv->gaps, gap); + } + + priv->duration = timeline_duration; + } + + } +} + +static inline void +resort_and_fill_gaps (GESTrack * track) +{ + g_sequence_sort (track->priv->tckobjs_by_start, + (GCompareDataFunc) objects_start_compare, NULL); + + if (track->priv->updating == TRUE) { + update_gaps (track); + } +} + +/* callbacks */ +static void +timeline_duration_changed_cb (GESTimeline * timeline, + GParamSpec * arg, GESTrack * track) +{ + GESTrackPrivate *priv = track->priv; + + /* Remove the last gap on the timeline if not needed anymore */ + if (priv->updating == TRUE && priv->gaps) { + Gap *gap = (Gap *) priv->gaps->data; + GstClockTime tl_duration = ges_timeline_get_duration (timeline); + + if (gap->start + gap->duration > tl_duration) { + free_gap (gap); + priv->gaps = g_list_remove (priv->gaps, gap); + } + } +} static void sort_track_objects_cb (GESTrackObject * child, GParamSpec * arg G_GNUC_UNUSED, GESTrack * track) { - g_sequence_sort (track->priv->tckobjs_by_start, - (GCompareDataFunc) objects_start_compare, NULL); -} - -static void -timeline_duration_cb (GESTimeline * timeline, - GParamSpec * arg G_GNUC_UNUSED, GESTrack * track) -{ - guint64 duration; - - g_object_get (timeline, "duration", &duration, NULL); - g_object_set (GES_TRACK (track)->priv->background, "duration", duration, - NULL); - - GST_DEBUG_OBJECT (track, "Updating background duration to %" GST_TIME_FORMAT, - GST_TIME_ARGS (duration)); + resort_and_fill_gaps (track); } static void @@ -160,24 +314,48 @@ pad_removed_cb (GstElement * element, GstPad * pad, GESTrack * track) static void composition_duration_cb (GstElement * composition, - GParamSpec * arg G_GNUC_UNUSED, GESTrack * obj) + GParamSpec * arg G_GNUC_UNUSED, GESTrack * track) { guint64 duration; g_object_get (composition, "duration", &duration, NULL); - if (obj->priv->duration != duration) { - GST_DEBUG ("composition duration : %" GST_TIME_FORMAT " current : %" + if (track->priv->duration != duration) { + GST_DEBUG_OBJECT (track, + "composition duration : %" GST_TIME_FORMAT " current : %" GST_TIME_FORMAT, GST_TIME_ARGS (duration), - GST_TIME_ARGS (obj->priv->duration)); + GST_TIME_ARGS (track->priv->duration)); - obj->priv->duration = duration; + track->priv->duration = duration; - g_object_notify_by_pspec (G_OBJECT (obj), properties[ARG_DURATION]); + g_object_notify_by_pspec (G_OBJECT (track), properties[ARG_DURATION]); } } +/* GESCreateElementForGapFunc Gaps filler for raw tracks */ +static GstElement * +create_element_for_raw_audio_gap (GESTrack * track) +{ + GstElement *elem; + + elem = gst_element_factory_make ("audiotestsrc", NULL); + g_object_set (elem, "wave", 4, NULL); + + return elem; +} + +static GstElement * +create_element_for_raw_video_gap (GESTrack * track) +{ + GstElement *elem; + + elem = gst_element_factory_make ("videotestsrc", NULL); + g_object_set (elem, "pattern", 2, NULL); + + return elem; +} + /* Remove @object from @track, but keeps it in the sequence this is needed * when finalizing as we can not change a GSequence at the same time we are * accessing it @@ -188,7 +366,7 @@ remove_object_internal (GESTrack * track, GESTrackObject * object) GESTrackPrivate *priv; GstElement *gnlobject; - GST_DEBUG ("track:%p, object:%p", track, object); + GST_DEBUG_OBJECT (track, "object:%p", object); priv = track->priv; @@ -282,6 +460,7 @@ ges_track_dispose (GObject * object) g_sequence_foreach (track->priv->tckobjs_by_start, (GFunc) dispose_tckobjs_foreach, track); g_sequence_free (priv->tckobjs_by_start); + g_list_free_full (priv->gaps, (GDestroyNotify) free_gap); if (priv->composition) { gst_bin_remove (GST_BIN (object), priv->composition); @@ -296,48 +475,6 @@ ges_track_dispose (GObject * object) G_OBJECT_CLASS (ges_track_parent_class)->dispose (object); } -static void -ges_track_constructed (GObject * object) -{ - GObjectClass *parent_class; - GstElement *background = NULL; - GESTrack *self = GES_TRACK (object); - GESTrackPrivate *priv = self->priv; - - if ((priv->background = gst_element_factory_make ("gnlsource", "background"))) { - g_object_set (priv->background, "priority", G_MAXUINT, NULL); - - switch (self->type) { - case GES_TRACK_TYPE_VIDEO: - background = gst_element_factory_make ("videotestsrc", "background"); - g_object_set (background, "pattern", 2, NULL); - break; - case GES_TRACK_TYPE_AUDIO: - background = gst_element_factory_make ("audiotestsrc", "background"); - g_object_set (background, "wave", 4, NULL); - break; - default: - break; - } - - if (background) { - if (!gst_bin_add (GST_BIN (priv->background), background)) - GST_ERROR ("Couldn't add background"); - else { - if (!gst_bin_add (GST_BIN (priv->composition), priv->background)) - GST_ERROR ("Couldn't add background"); - } - - } - } - - parent_class = ges_track_parent_class; - if (parent_class->constructed) - parent_class->constructed (object); - - G_OBJECT_CLASS (parent_class)->constructed (object); -} - static void ges_track_finalize (GObject * object) { @@ -355,7 +492,6 @@ ges_track_class_init (GESTrackClass * klass) object_class->set_property = ges_track_set_property; object_class->dispose = ges_track_dispose; object_class->finalize = ges_track_finalize; - object_class->constructed = ges_track_constructed; /** * GESTrack:caps @@ -439,6 +575,8 @@ ges_track_init (GESTrack * self) self->priv->composition = gst_element_factory_make ("gnlcomposition", NULL); self->priv->updating = TRUE; self->priv->tckobjs_by_start = g_sequence_new (NULL); + self->priv->create_element_for_gaps = NULL; + self->priv->gaps = NULL; g_signal_connect (G_OBJECT (self->priv->composition), "notify::duration", G_CALLBACK (composition_duration_cb), self); @@ -489,6 +627,10 @@ ges_track_video_raw_new (void) GstCaps *caps = gst_caps_from_string ("video/x-raw-yuv;video/x-raw-rgb"); track = ges_track_new (GES_TRACK_TYPE_VIDEO, caps); + ges_track_set_create_element_for_gap_func (track, + create_element_for_raw_video_gap); + + GST_DEBUG_OBJECT (track, "New raw video track"); return track; } @@ -508,7 +650,11 @@ ges_track_audio_raw_new (void) GstCaps *caps = gst_caps_from_string ("audio/x-raw-int;audio/x-raw-float"); track = ges_track_new (GES_TRACK_TYPE_AUDIO, caps); + ges_track_set_create_element_for_gap_func (track, + create_element_for_raw_audio_gap); + GST_DEBUG_OBJECT (track, "New raw audio track %p", + track->priv->create_element_for_gaps); return track; } @@ -525,12 +671,12 @@ ges_track_set_timeline (GESTrack * track, GESTimeline * timeline) GST_DEBUG ("track:%p, timeline:%p", track, timeline); if (track->priv->timeline) - g_signal_handlers_disconnect_by_func (track, - timeline_duration_cb, track->priv->timeline); + g_signal_handlers_disconnect_by_func (track->priv->timeline, + timeline_duration_changed_cb, track); if (timeline) - g_signal_connect (G_OBJECT (timeline), "notify::duration", - G_CALLBACK (timeline_duration_cb), track); + g_signal_connect (timeline, "notify::duration", + G_CALLBACK (timeline_duration_changed_cb), track); track->priv->timeline = timeline; } @@ -619,6 +765,8 @@ ges_track_add_object (GESTrack * track, GESTrackObject * object) g_signal_connect (GES_TRACK_OBJECT (object), "notify::priority", G_CALLBACK (sort_track_objects_cb), track); + resort_and_fill_gaps (track); + return TRUE; } @@ -662,15 +810,20 @@ gboolean ges_track_remove_object (GESTrack * track, GESTrackObject * object) { GSequenceIter *it; + GESTrackPrivate *priv; g_return_val_if_fail (GES_IS_TRACK (track), FALSE); g_return_val_if_fail (GES_IS_TRACK_OBJECT (object), FALSE); + priv = track->priv; + if (remove_object_internal (track, object) == TRUE) { - it = g_sequence_lookup (track->priv->tckobjs_by_start, object, + it = g_sequence_lookup (priv->tckobjs_by_start, object, (GCompareDataFunc) objects_start_compare, NULL); g_sequence_remove (it); + resort_and_fill_gaps (track); + return TRUE; } @@ -733,6 +886,9 @@ ges_track_enable_update (GESTrack * track, gboolean enabled) track->priv->updating = update; + if (update == TRUE) + resort_and_fill_gaps (track); + return update == enabled; } @@ -751,3 +907,23 @@ ges_track_is_updating (GESTrack * track) return track->priv->updating; } + + +/** + * ges_track_set_create_element_for_gap_func: + * @track: a #GESTrack + * @func: (scope notified): The #GESCreateElementForGapFunc that will be used + * to create #GstElement to fill gaps + * + * Sets the function that should be used to create the GstElement used to fill gaps. + * To avoid to provide such a function we advice you to use the + * #ges_track_audio_raw_new and #ges_track_video_raw_new constructor when possible. + */ +void +ges_track_set_create_element_for_gap_func (GESTrack * track, + GESCreateElementForGapFunc func) +{ + g_return_if_fail (GES_IS_TRACK (track)); + + track->priv->create_element_for_gaps = func; +} diff --git a/ges/ges-track.h b/ges/ges-track.h index b5bad827b8..033f777b4f 100644 --- a/ges/ges-track.h +++ b/ges/ges-track.h @@ -47,6 +47,18 @@ G_BEGIN_DECLS typedef struct _GESTrackPrivate GESTrackPrivate; +/** + * GESCreateElementForGapFunc: + * @track: the #GESTrack + * + * A function that will be called to create the #GstElement that will be used + * as a source to fill the gaps in @track. + * + * Returns: A #GstElement (must be a source) that will be used to + * fill the gaps (periods of time in @track that containes no source). + */ +typedef GstElement* (*GESCreateElementForGapFunc) (GESTrack *track); + /** * GESTrack: * @type: a #GESTrackType indicting the basic type of the track. @@ -107,6 +119,10 @@ gboolean ges_track_is_updating (GESTrack * track); GList* ges_track_get_objects (GESTrack *track); +void +ges_track_set_create_element_for_gap_func (GESTrack *track, + GESCreateElementForGapFunc func); + G_END_DECLS #endif /* _GES_TRACK */ From 8fbe9c90b618bfcc08933a62126f91fe3f9d6be3 Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Wed, 9 May 2012 11:51:33 -0400 Subject: [PATCH 06/19] tests: Add basic gaps tests --- tests/check/ges/backgroundsource.c | 121 +++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/tests/check/ges/backgroundsource.c b/tests/check/ges/backgroundsource.c index 77a511dc93..ee1a58b4c2 100644 --- a/tests/check/ges/backgroundsource.c +++ b/tests/check/ges/backgroundsource.c @@ -206,6 +206,126 @@ GST_START_TEST (test_test_source_in_layer) GST_END_TEST; +static gint +find_composition_func (GstElement * element) +{ + GstElementFactory *fac = gst_element_get_factory (element); + const gchar *name = gst_plugin_feature_get_name (GST_PLUGIN_FEATURE (fac)); + + if (g_strcmp0 (name, "gnlcomposition") == 0) + return 0; + + return 1; +} + +static GstElement * +find_composition (GESTrack * track) +{ + GstIterator *it = gst_bin_iterate_recurse (GST_BIN (track)); + GstElement *ret = + gst_iterator_find_custom (it, (GCompareFunc) find_composition_func, NULL); + + gst_iterator_free (it); + + return ret; +} + +#define gap_object_check(gnlobj, start, duration, priority) \ +{ \ + guint64 pstart, pdur, pprio; \ + g_object_get (gnlobj, "start", &pstart, "duration", &pdur, \ + "priority", &pprio, NULL); \ + assert_equals_uint64 (pstart, start); \ + assert_equals_uint64 (pdur, duration); \ + assert_equals_int (pprio, priority); \ +} +GST_START_TEST (test_gap_filling_basic) +{ + GESTrack *track; + GESTrackObject *trackobject, *trackobject1, *trackobject2; + /*GESTimelineLayer *layer; */ + GESTimelineObject *object, *object1, *object2; + GstElement *gnlsrc, *gnlsrc1, *gap = NULL; + GstElement *composition; + GList *tmp; + + ges_init (); + + track = ges_track_audio_raw_new (); + fail_unless (track != NULL); + + composition = find_composition (track); + fail_unless (composition != NULL); + + object = GES_TIMELINE_OBJECT (ges_timeline_test_source_new ()); + fail_unless (object != NULL); + + /* Set some properties */ + g_object_set (object, "start", (guint64) 0, "duration", (guint64) 5, NULL); + assert_equals_uint64 (GES_TIMELINE_OBJECT_START (object), 0); + assert_equals_uint64 (GES_TIMELINE_OBJECT_DURATION (object), 5); + + trackobject = ges_timeline_object_create_track_object (object, track); + ges_timeline_object_add_track_object (object, trackobject); + fail_unless (ges_track_add_object (track, trackobject)); + fail_unless (trackobject != NULL); + gnlsrc = ges_track_object_get_gnlobject (trackobject); + fail_unless (gnlsrc != NULL); + + /* Check that trackobject has the same properties */ + assert_equals_uint64 (GES_TRACK_OBJECT_START (trackobject), 0); + assert_equals_uint64 (GES_TRACK_OBJECT_DURATION (trackobject), 5); + + /* Check no gap were wrongly added */ + assert_equals_int (g_list_length (GST_BIN_CHILDREN (composition)), 1); + + object1 = GES_TIMELINE_OBJECT (ges_timeline_test_source_new ()); + fail_unless (object1 != NULL); + + g_object_set (object1, "start", (guint64) 15, "duration", (guint64) 5, NULL); + assert_equals_uint64 (GES_TIMELINE_OBJECT_START (object1), 15); + assert_equals_uint64 (GES_TIMELINE_OBJECT_DURATION (object1), 5); + + trackobject1 = ges_timeline_object_create_track_object (object1, track); + ges_timeline_object_add_track_object (object1, trackobject1); + fail_unless (ges_track_add_object (track, trackobject1)); + fail_unless (trackobject1 != NULL); + gnlsrc1 = ges_track_object_get_gnlobject (trackobject1); + fail_unless (gnlsrc1 != NULL); + + /* Check that trackobject1 has the same properties */ + assert_equals_uint64 (GES_TRACK_OBJECT_START (trackobject1), 15); + assert_equals_uint64 (GES_TRACK_OBJECT_DURATION (trackobject1), 5); + + /* Check the gap as properly been added */ + assert_equals_int (g_list_length (GST_BIN_CHILDREN (composition)), 3); + + for (tmp = GST_BIN_CHILDREN (composition); tmp; tmp = tmp->next) { + GstElement *tmp_gnlobj = GST_ELEMENT (tmp->data); + + if (tmp_gnlobj != gnlsrc && tmp_gnlobj != gnlsrc1) { + gap = tmp_gnlobj; + } + } + fail_unless (gap != NULL); + gap_object_check (gap, 5, 10, 0) + + object2 = GES_TIMELINE_OBJECT (ges_timeline_test_source_new ()); + fail_unless (object2 != NULL); + g_object_set (object2, "start", (guint64) 35, "duration", (guint64) 5, NULL); + trackobject2 = ges_timeline_object_create_track_object (object2, track); + ges_timeline_object_add_track_object (object2, trackobject2); + fail_unless (ges_track_add_object (track, trackobject2)); + fail_unless (trackobject2 != NULL); + assert_equals_uint64 (GES_TRACK_OBJECT_START (trackobject2), 35); + assert_equals_uint64 (GES_TRACK_OBJECT_DURATION (trackobject2), 5); + assert_equals_int (g_list_length (GST_BIN_CHILDREN (composition)), 5); + + gst_object_unref (track); +} + +GST_END_TEST; + static Suite * ges_suite (void) { @@ -217,6 +337,7 @@ ges_suite (void) tcase_add_test (tc_chain, test_test_source_basic); tcase_add_test (tc_chain, test_test_source_properties); tcase_add_test (tc_chain, test_test_source_in_layer); + tcase_add_test (tc_chain, test_gap_filling_basic); return s; } From 1e3e7c5276b4693032ebae84e064b82589b93da0 Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Wed, 9 May 2012 12:12:38 -0400 Subject: [PATCH 07/19] docs: Misc documentation fixing --- docs/libs/ges-sections.txt | 9 ++++++++- ges/ges-pitivi-formatter.c | 5 +++++ ges/ges-pitivi-formatter.h | 6 ++++-- ges/ges-screenshot.c | 10 ++++++++++ ges/ges-timeline-object.c | 1 + ges/ges-timeline.c | 3 +-- ges/ges-track-object.c | 2 +- ges/ges-track-video-transition.c | 2 +- 8 files changed, 31 insertions(+), 7 deletions(-) diff --git a/docs/libs/ges-sections.txt b/docs/libs/ges-sections.txt index f443e2ee79..2fc7619d4e 100644 --- a/docs/libs/ges-sections.txt +++ b/docs/libs/ges-sections.txt @@ -45,12 +45,17 @@ GES_VIDEO_TEST_PATTERN_TYPE ges_video_test_pattern_get_type GES_TYPE_PIPELINE_FLAGS ges_pipeline_flags_get_type +GES_TYPE_EDGE +ges_edge_get_type +GES_TYPE_EDIT_MODE +ges_edit_mode_get_type
ges-track GESTrack GESTrack +GESCreateElementForGapFunc ges_track_audio_raw_new ges_track_video_raw_new ges_track_new @@ -323,7 +328,6 @@ ges_timeline_object_set_duration ges_timeline_object_get_layer ges_timeline_object_find_track_object ges_timeline_object_add_track_object -ges_timeline_object_release_track_object ges_timeline_object_get_top_effects ges_timeline_object_get_top_effect_position ges_timeline_object_move_to_layer @@ -337,6 +341,9 @@ ges_timeline_object_ripple_end ges_timeline_object_roll_start ges_timeline_object_roll_end ges_timeline_object_trim_start +ges_timeline_object_get_max_duration +ges_timeline_object_objects_set_locked +ges_timeline_object_set_max_duration GES_TIMELINE_OBJECT_DURATION GES_TIMELINE_OBJECT_INPOINT diff --git a/ges/ges-pitivi-formatter.c b/ges/ges-pitivi-formatter.c index f828eecb70..0ad06f4f0f 100644 --- a/ges/ges-pitivi-formatter.c +++ b/ges/ges-pitivi-formatter.c @@ -17,6 +17,11 @@ * Boston, MA 02111-1307, USA. */ +/** + * SECTION: ges-pitivi-formatter + * @short_description: A formatter for the PiTiVi project file format + */ + #include #include #include diff --git a/ges/ges-pitivi-formatter.h b/ges/ges-pitivi-formatter.h index 80751aa4d4..987be4b8b1 100644 --- a/ges/ges-pitivi-formatter.h +++ b/ges/ges-pitivi-formatter.h @@ -39,10 +39,11 @@ typedef struct _GESPitiviFormatterPrivate GESPitiviFormatterPrivate; + /** * GESPitiviFormatter: * - * Serializes a #GESTimeline to a file using + * Serializes a #GESTimeline to a file using the Xml PiTiVi file format */ struct _GESPitiviFormatter { @@ -55,7 +56,8 @@ struct _GESPitiviFormatter { gpointer _ges_reserved[GES_PADDING]; }; -struct _GESPitiviFormatterClass { +struct _GESPitiviFormatterClass +{ /*< private >*/ GESFormatterClass parent_class; diff --git a/ges/ges-screenshot.c b/ges/ges-screenshot.c index 9d13af9026..5bac786916 100644 --- a/ges/ges-screenshot.c +++ b/ges/ges-screenshot.c @@ -23,6 +23,16 @@ #include "ges-screenshot.h" #include "ges-internal.h" +/** + * ges_play_sink_convert_frame: + * @playsink: The olaysink to get last frame from + * @caps: The caps defining the format the return value will have + * + * Get the last buffer @playsink showed + * + * Returns: (transfer full): A #GstBuffer containing the last frame from + * @playsink in the format defined by the @caps + */ GstBuffer * ges_play_sink_convert_frame (GstElement * playsink, GstCaps * caps) { diff --git a/ges/ges-timeline-object.c b/ges/ges-timeline-object.c index 1338f33fae..c918c8bbaf 100644 --- a/ges/ges-timeline-object.c +++ b/ges/ges-timeline-object.c @@ -1748,6 +1748,7 @@ ges_timeline_object_ripple_end (GESTimelineObject * object, guint64 end) /** * ges_timeline_object_roll_start: + * @object: The #GESTimelineObject to roll * @start: The new start of @object in roll mode, it will also adapat * the in-point of @object according * diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index 8ce1e69bb3..a3682373a3 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -56,8 +56,7 @@ G_DEFINE_TYPE (GESTimeline, ges_timeline, GST_TYPE_BIN); #define GES_TIMELINE_PENDINGOBJS_UNLOCK(timeline) \ (g_mutex_unlock(GES_TIMELINE_PENDINGOBJS_GET_LOCK (timeline))) -/** - * The move context is used for the timeline editing modes functions in order to +/* The move context is used for the timeline editing modes functions in order to * + Ripple / Roll / Slide / Move / Trim * * The context aims at avoiding to recalculate values/objects on each call of the diff --git a/ges/ges-track-object.c b/ges/ges-track-object.c index 13461f99f4..b8e420aaee 100644 --- a/ges/ges-track-object.c +++ b/ges/ges-track-object.c @@ -1555,7 +1555,7 @@ ges_track_object_set_max_duration (GESTrackObject * object, guint64 maxduration) * * Copies @object * - * Returns: The newly create #GESTrackObject, copied from @object + * Returns: (transfer full): The newly create #GESTrackObject, copied from @object * * Since: 0.10.XX */ diff --git a/ges/ges-track-video-transition.c b/ges/ges-track-video-transition.c index 295455f755..02b67253b4 100644 --- a/ges/ges-track-video-transition.c +++ b/ges/ges-track-video-transition.c @@ -793,7 +793,7 @@ ges_track_video_transition_get_border (GESTrackVideoTransition * self) /** * ges_track_video_transition_set_inverted: * @self: The #GESTrackVideoTransition to set invert on - * @value: The value of the to set on @object + * @inverted: %TRUE to invert the transition %FALSE otherwise * * Set the invert property of @self, this value represents * the direction of the transition. In case this value does From f19a8af81f192b4322abf26896b6394b4fa16bc7 Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Tue, 15 May 2012 14:38:38 -0400 Subject: [PATCH 08/19] timeline-layer: Rework the way we calculate in which layer a TrackObject is --- ges/ges-timeline-layer.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ges/ges-timeline-layer.c b/ges/ges-timeline-layer.c index 45008bf613..89fa85c2aa 100644 --- a/ges/ges-timeline-layer.c +++ b/ges/ges-timeline-layer.c @@ -216,17 +216,18 @@ objects_start_compare (GESTimelineObject * a, GESTimelineObject * b) static GList * track_get_by_layer (GESTimelineLayer * layer, GESTrack * track) { + GESTrackObject *tckobj; + guint32 layer_prio = layer->priv->priority; + GList *tck_objects_list = NULL, *tmp = NULL, *return_list = NULL; - GESTimelineObject *tl_obj; tck_objects_list = ges_track_get_objects (track); for (tmp = tck_objects_list; tmp; tmp = tmp->next) { - tl_obj = ges_track_object_get_timeline_object (tmp->data); + tckobj = GES_TRACK_OBJECT (tmp->data); - if (ges_timeline_object_get_layer (tl_obj) == layer) { + if (tckobj->priority / LAYER_HEIGHT == layer_prio) { /* We steal the reference from tck_objects_list */ return_list = g_list_append (return_list, tmp->data); - } else g_object_unref (tmp->data); } From 4c28e59046be626d6c407cfab10b4c1f164f6965 Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Wed, 16 May 2012 15:53:07 -0400 Subject: [PATCH 09/19] timeline: Properly set TimelineFileSource-s duration and max duration When we get the information of duration of files after discoverying them, use that information to set the values on the TimelineFileSource-s --- ges/ges-timeline.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index a3682373a3..44e51656f6 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -1463,6 +1463,7 @@ discoverer_discovered_cb (GstDiscoverer * discoverer, { GList *tmp; GList *stream_list; + GESTimelineObject *tlobj; GESTrackType tfs_supportedformats; gboolean found = FALSE; @@ -1547,18 +1548,26 @@ discoverer_discovered_cb (GstDiscoverer * discoverer, check_image: + tlobj = GES_TIMELINE_OBJECT (tfs); if (is_image) { /* don't set max-duration on still images */ g_object_set (tfs, "is_image", (gboolean) TRUE, NULL); + } else { + GstClockTime file_duration, tlobj_max_duration; + + /* Properly set duration informations from the discovery */ + file_duration = gst_discoverer_info_get_duration (info); + tlobj_max_duration = ges_timeline_object_get_max_duration (tlobj); + + if (tlobj_max_duration == G_MAXUINT64) + ges_timeline_object_set_max_duration (tlobj, file_duration); + + if (GST_CLOCK_TIME_IS_VALID (tlobj->duration) == FALSE) + ges_timeline_object_set_duration (tlobj, file_duration); } /* Continue the processing on tfs */ - add_object_to_tracks (timeline, GES_TIMELINE_OBJECT (tfs)); - - if (!is_image) { - g_object_set (tfs, "max-duration", - gst_discoverer_info_get_duration (info), NULL); - } + add_object_to_tracks (timeline, tlobj); /* Remove the ref as the timeline file source is no longer needed here */ g_object_unref (tfs); From 5a29478c2d87524b644fe3a2fa8d6cf64a6d77ef Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Fri, 18 May 2012 10:28:26 -0400 Subject: [PATCH 10/19] timeline: Create a debug logging category for the timeline --- ges/ges-timeline.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index 44e51656f6..f61adcdde5 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -49,6 +49,10 @@ static inline void init_movecontext (MoveContext * mv_ctx); G_DEFINE_TYPE (GESTimeline, ges_timeline, GST_TYPE_BIN); +GST_DEBUG_CATEGORY_STATIC (ges_timeline_debug); +#undef GST_CAT_DEFAULT +#define GST_CAT_DEFAULT ges_timeline_debug + #define GES_TIMELINE_PENDINGOBJS_GET_LOCK(timeline) \ (GES_TIMELINE(timeline)->priv->pendingobjects_lock) #define GES_TIMELINE_PENDINGOBJS_LOCK(timeline) \ @@ -396,6 +400,9 @@ ges_timeline_init (GESTimeline * self) { GESTimelinePrivate *priv; + GST_DEBUG_CATEGORY_INIT (ges_timeline_debug, "gestimeline", + GST_DEBUG_FG_YELLOW, "ges timeline"); + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GES_TYPE_TIMELINE, GESTimelinePrivate); From 9913d48e5c9e560c6bc9b0ec1c676ee3444beb3e Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Fri, 18 May 2012 10:33:44 -0400 Subject: [PATCH 11/19] timeline: Avoid to recalculate the moving context unecessarly --- ges/ges-timeline.c | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index f61adcdde5..9fde9aaf2f 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -1587,8 +1587,7 @@ layer_object_added_cb (GESTimelineLayer * layer, GESTimelineObject * object, if (ges_timeline_object_is_moving_from_layer (object)) { GST_DEBUG ("TimelineObject %p is moving from a layer to another, not doing" " anything on it", object); - if (!timeline->priv->movecontext.ignore_needs_ctx) - timeline->priv->movecontext.needs_move_ctx = TRUE; + timeline->priv->movecontext.needs_move_ctx = TRUE; return; } @@ -1691,7 +1690,12 @@ trackobj_start_changed_cb (GESTrackObject * child, sort_starts_ends_start (timeline, child); sort_starts_ends_end (timeline, child); - if (!timeline->priv->movecontext.ignore_needs_ctx) + /* If the timeline is set to snap objects together, we + * are sure that all movement of TrackObject-s are done within + * the moving context, so we do not need to recalculate the + * move context as often */ + if (timeline->priv->movecontext.ignore_needs_ctx && + timeline->priv->snapping_distance == 0) timeline->priv->movecontext.needs_move_ctx = TRUE; } @@ -1701,15 +1705,12 @@ trackobj_duration_changed_cb (GESTrackObject * child, { sort_starts_ends_end (timeline, child); - if (!timeline->priv->movecontext.ignore_needs_ctx) - timeline->priv->movecontext.needs_move_ctx = TRUE; -} - -static void -trackobj_inpoint_changed_cb (GESTrackObject * child, - GParamSpec * arg G_GNUC_UNUSED, GESTimeline * timeline) -{ - if (!timeline->priv->movecontext.ignore_needs_ctx) + /* If the timeline is set to snap objects together, we + * are sure that all movement of TrackObject-s are done within + * the moving context, so we do not need to recalculate the + * move context as often */ + if (timeline->priv->movecontext.ignore_needs_ctx && + timeline->priv->snapping_distance == 0) timeline->priv->movecontext.needs_move_ctx = TRUE; } @@ -1725,8 +1726,6 @@ track_object_added_cb (GESTrack * track, GESTrackObject * object, G_CALLBACK (trackobj_start_changed_cb), timeline); g_signal_connect (GES_TRACK_OBJECT (object), "notify::duration", G_CALLBACK (trackobj_duration_changed_cb), timeline); - g_signal_connect (GES_TRACK_OBJECT (object), "notify::in-point", - G_CALLBACK (trackobj_inpoint_changed_cb), timeline); } } @@ -1740,8 +1739,6 @@ track_object_removed_cb (GESTrack * track, GESTrackObject * object, NULL); g_signal_handlers_disconnect_by_func (object, trackobj_duration_changed_cb, NULL); - g_signal_handlers_disconnect_by_func (object, trackobj_inpoint_changed_cb, - NULL); /* Make sure to reinitialise the moving context next time */ timeline->priv->movecontext.needs_move_ctx = TRUE; From 4c79afcbe4390111817b75f768d4e14f6386df9c Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Fri, 18 May 2012 11:13:11 -0400 Subject: [PATCH 12/19] timeline: Try to resnap at same snapping point before calculating new value --- ges/ges-timeline.c | 47 ++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index 9fde9aaf2f..ea8948821e 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -94,7 +94,7 @@ struct _MoveContext /* Last snapping properties */ GESTrackObject *last_snaped1; GESTrackObject *last_snaped2; - GstClockTime last_snap_ts; + GstClockTime *last_snap_ts; }; struct _GESTimelinePrivate @@ -641,7 +641,7 @@ init_movecontext (MoveContext * mv_ctx) mv_ctx->max_layer_prio = 0; mv_ctx->last_snaped1 = NULL; mv_ctx->last_snaped2 = NULL; - mv_ctx->last_snap_ts = GST_CLOCK_TIME_NONE; + mv_ctx->last_snap_ts = NULL; } static inline void @@ -713,11 +713,18 @@ ges_timeline_emit_snappig (GESTimeline * timeline, GESTrackObject * obj1, { GESTrackObject *obj2; MoveContext *mv_ctx = &timeline->priv->movecontext; + GstClockTime snap_time = timecode ? *timecode : 0; + GstClockTime last_snap_ts = mv_ctx->last_snap_ts ? + *mv_ctx->last_snap_ts : GST_CLOCK_TIME_NONE; + + GST_DEBUG_OBJECT (timeline, "Distance: %" GST_TIME_FORMAT " snapping at %" + GST_TIME_FORMAT, GST_TIME_ARGS (timeline->priv->snapping_distance), + GST_TIME_ARGS (snap_time)); if (timecode == NULL) { if (mv_ctx->last_snaped1 != NULL && mv_ctx->last_snaped2 != NULL) { g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0, - mv_ctx->last_snaped1, mv_ctx->last_snaped2, mv_ctx->last_snap_ts); + mv_ctx->last_snaped1, mv_ctx->last_snaped2, last_snap_ts); /* We then need to recalculate the moving context */ timeline->priv->movecontext.needs_move_ctx = TRUE; @@ -728,19 +735,19 @@ ges_timeline_emit_snappig (GESTimeline * timeline, GESTrackObject * obj1, obj2 = g_hash_table_lookup (timeline->priv->by_object, timecode); - if (mv_ctx->last_snap_ts != *timecode) { + if (last_snap_ts != *timecode) { g_signal_emit (timeline, ges_timeline_signals[SNAPING_ENDED], 0, - mv_ctx->last_snaped1, mv_ctx->last_snaped2, mv_ctx->last_snap_ts); + mv_ctx->last_snaped1, mv_ctx->last_snaped2, (last_snap_ts)); /* We want the snap start signal to be emited anyway */ - mv_ctx->last_snap_ts = GST_CLOCK_TIME_NONE; + mv_ctx->last_snap_ts = NULL; } - if (GST_CLOCK_TIME_IS_VALID (mv_ctx->last_snap_ts) == FALSE) { + if (mv_ctx->last_snap_ts == NULL) { mv_ctx->last_snaped1 = obj1; mv_ctx->last_snaped2 = obj2; - mv_ctx->last_snap_ts = *timecode; + mv_ctx->last_snap_ts = timecode; g_signal_emit (timeline, ges_timeline_signals[SNAPING_STARTED], 0, obj1, obj2, *timecode); @@ -757,8 +764,8 @@ ges_timeline_snap_position (GESTimeline * timeline, GESTrackObject * trackobj, GESTrackObject *tmp_tckobj; GESTimelineObject *tmp_tlobj, *tlobj; + GstClockTime *last_snap_ts = priv->movecontext.last_snap_ts; guint64 snap_distance = timeline->priv->snapping_distance; - guint64 *prev_tc, *next_tc, *ret = NULL, off = G_MAXUINT64, off1 = G_MAXUINT64; @@ -766,6 +773,16 @@ ges_timeline_snap_position (GESTimeline * timeline, GESTrackObject * trackobj, if (snap_distance == 0) return NULL; + /* If we can just resnap as last snap... do it */ + if (last_snap_ts) { + off = timecode > *last_snap_ts ? + timecode - *last_snap_ts : *last_snap_ts - timecode; + if (off <= snap_distance) { + ret = last_snap_ts; + goto done; + } + } + tlobj = ges_track_object_get_timeline_object (trackobj); iter = g_sequence_search (priv->starts_ends, &timecode, @@ -806,6 +823,7 @@ ges_timeline_snap_position (GESTimeline * timeline, GESTrackObject * trackobj, prev_iter = g_sequence_iter_prev (prev_iter); } +done: /* We emit the snapping signal only if we snapped with a different value * than the current one */ if (emit) { @@ -1305,11 +1323,12 @@ ges_timeline_move_object_simple (GESTimeline * timeline, { guint64 *snap_end, *snap_st, *cur, off1, off2, end; - GST_DEBUG_OBJECT (timeline, "Moving to %" GST_TIME_FORMAT, - GST_TIME_ARGS (position)); - end = position + object->duration; cur = g_hash_table_lookup (timeline->priv->by_end, object); + + GST_DEBUG_OBJECT (timeline, "Moving to %" GST_TIME_FORMAT " (end %" + GST_TIME_FORMAT ")", GST_TIME_ARGS (position), GST_TIME_ARGS (end)); + snap_end = ges_timeline_snap_position (timeline, object, cur, end, FALSE); if (snap_end) off1 = end > *snap_end ? end - *snap_end : *snap_end - end; @@ -1327,13 +1346,9 @@ ges_timeline_move_object_simple (GESTimeline * timeline, if (snap_end && off1 <= off2) { position = position + *snap_end - end; ges_timeline_emit_snappig (timeline, object, snap_end); - GST_DEBUG_OBJECT (timeline, "Real snap at %" GST_TIME_FORMAT, - GST_TIME_ARGS (position)); } else if (snap_st) { position = position + *snap_st - position; ges_timeline_emit_snappig (timeline, object, snap_st); - GST_DEBUG_OBJECT (timeline, "Real snap at %" GST_TIME_FORMAT, - GST_TIME_ARGS (position)); } else ges_timeline_emit_snappig (timeline, object, NULL); From c199c24507d1e83637f14bc4efffb5868a40ae75 Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Fri, 18 May 2012 13:16:50 -0400 Subject: [PATCH 13/19] timeline: Make the update property a GObject property API: timeline::update property --- ges/ges-timeline.c | 64 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index ea8948821e..8280a29ec3 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -147,6 +147,7 @@ enum PROP_0, PROP_DURATION, PROP_SNAPPING_DISTANCE, + PROP_UPDATE, PROP_LAST }; @@ -177,6 +178,26 @@ static void discoverer_discovered_cb (GstDiscoverer * discoverer, GstDiscovererInfo * info, GError * err, GESTimeline * timeline); +/* Internal methods */ +static gboolean +ges_timeline_enable_update_internal (GESTimeline * timeline, gboolean enabled) +{ + GList *tmp; + gboolean res = TRUE; + + GST_DEBUG_OBJECT (timeline, "%s updates", enabled ? "Enabling" : "Disabling"); + + for (tmp = timeline->priv->tracks; tmp; tmp = tmp->next) { + if (!ges_track_enable_update (((TrackPrivate *) tmp->data)->track, enabled)) + res = FALSE; + } + + /* Make sure we reset the context */ + timeline->priv->movecontext.needs_move_ctx = TRUE; + + return res; +} + /* GObject Standard vmethods*/ static void ges_timeline_get_property (GObject * object, guint property_id, @@ -193,6 +214,9 @@ ges_timeline_get_property (GObject * object, guint property_id, case PROP_SNAPPING_DISTANCE: g_value_set_uint64 (value, timeline->priv->snapping_distance); break; + case PROP_UPDATE: + g_value_set_boolean (value, ges_timeline_is_updating (timeline)); + break; } } @@ -206,6 +230,10 @@ ges_timeline_set_property (GObject * object, guint property_id, case PROP_SNAPPING_DISTANCE: timeline->priv->snapping_distance = g_value_get_uint64 (value); break; + case PROP_UPDATE: + ges_timeline_enable_update_internal (timeline, + g_value_get_boolean (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } @@ -298,6 +326,27 @@ ges_timeline_class_init (GESTimelineClass * klass) g_object_class_install_property (object_class, PROP_SNAPPING_DISTANCE, properties[PROP_SNAPPING_DISTANCE]); + /** + * GESTimeline:update: + * + * If %TRUE, then all modifications to objects within the timeline will + * cause a internal pipeline update (if required). + * If %FALSE, then only the timeline start/duration/stop properties + * will be updated, and the internal pipeline will only be updated when the + * property is set back to %TRUE. + * + * It is recommended to temporarily set this property to %FALSE before doing + * more than one modification in the timeline (like adding or moving + * several objects at once) in order to speed up the process, and then setting + * back the property to %TRUE when done. + */ + + properties[PROP_UPDATE] = g_param_spec_boolean ("update", "Update", + "Update the internal pipeline on every modification", TRUE, + G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_UPDATE, + properties[PROP_UPDATE]); + /** * GESTimeline::track-added * @timeline: the #GESTimeline @@ -2400,20 +2449,13 @@ ges_timeline_is_updating (GESTimeline * timeline) gboolean ges_timeline_enable_update (GESTimeline * timeline, gboolean enabled) { - GList *tmp; - gboolean res = TRUE; + if (ges_timeline_enable_update_internal (timeline, enabled)) { + g_object_notify_by_pspec (G_OBJECT (timeline), properties[PROP_UPDATE]); - GST_DEBUG_OBJECT (timeline, "%s updates", enabled ? "Enabling" : "Disabling"); - - for (tmp = timeline->priv->tracks; tmp; tmp = tmp->next) { - if (!ges_track_enable_update (((TrackPrivate *) tmp->data)->track, enabled)) - res = FALSE; + return TRUE; } - /* Make sure we reset the context */ - timeline->priv->movecontext.needs_move_ctx = TRUE; - - return res; + return FALSE; } /** From 861a06e7ce1913999211d9206e8e5408611ee8ee Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Fri, 18 May 2012 13:17:17 -0400 Subject: [PATCH 14/19] timeline: Move all callbacks to the callback section of the file --- ges/ges-timeline.c | 133 +++++++++++++++++++++++---------------------- 1 file changed, 68 insertions(+), 65 deletions(-) diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index 8280a29ec3..df5b76e52a 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -1810,6 +1810,73 @@ track_object_removed_cb (GESTrack * track, GESTrackObject * object, } } +static void +pad_added_cb (GESTrack * track, GstPad * pad, TrackPrivate * tr_priv) +{ + gchar *padname; + gboolean no_more; + GList *tmp; + + GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad)); + + if (G_UNLIKELY (tr_priv->pad)) { + GST_WARNING ("We are already controlling a pad for this track"); + return; + } + + /* Remember the pad */ + GST_OBJECT_LOCK (track); + tr_priv->pad = pad; + + no_more = TRUE; + for (tmp = tr_priv->timeline->priv->tracks; tmp; tmp = g_list_next (tmp)) { + TrackPrivate *tr_priv = (TrackPrivate *) tmp->data; + + if (!tr_priv->pad) { + GST_LOG ("Found track without pad %p", tr_priv->track); + no_more = FALSE; + } + } + GST_OBJECT_UNLOCK (track); + + /* ghost it ! */ + GST_DEBUG ("Ghosting pad and adding it to ourself"); + padname = g_strdup_printf ("track_%p_src", track); + tr_priv->ghostpad = gst_ghost_pad_new (padname, pad); + g_free (padname); + gst_pad_set_active (tr_priv->ghostpad, TRUE); + gst_element_add_pad (GST_ELEMENT (tr_priv->timeline), tr_priv->ghostpad); + + if (no_more) { + GST_DEBUG ("Signaling no-more-pads"); + gst_element_no_more_pads (GST_ELEMENT (tr_priv->timeline)); + } +} + +static void +pad_removed_cb (GESTrack * track, GstPad * pad, TrackPrivate * tr_priv) +{ + GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad)); + + if (G_UNLIKELY (tr_priv->pad != pad)) { + GST_WARNING ("Not the pad we're controlling"); + return; + } + + if (G_UNLIKELY (tr_priv->ghostpad == NULL)) { + GST_WARNING ("We don't have a ghostpad for this pad !"); + return; + } + + GST_DEBUG ("Removing ghostpad"); + gst_pad_set_active (tr_priv->ghostpad, FALSE); + gst_element_remove_pad (GST_ELEMENT (tr_priv->timeline), tr_priv->ghostpad); + tr_priv->ghostpad = NULL; + tr_priv->pad = NULL; +} + + +/* GstElement Virtual methods */ static GstStateChangeReturn ges_timeline_change_state (GstElement * element, GstStateChange transition) { @@ -1853,6 +1920,7 @@ ges_timeline_change_state (GstElement * element, GstStateChange transition) } +/**** API *****/ /** * ges_timeline_new: * @@ -2120,71 +2188,6 @@ ges_timeline_remove_layer (GESTimeline * timeline, GESTimelineLayer * layer) return TRUE; } -static void -pad_added_cb (GESTrack * track, GstPad * pad, TrackPrivate * tr_priv) -{ - gchar *padname; - gboolean no_more; - GList *tmp; - - GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad)); - - if (G_UNLIKELY (tr_priv->pad)) { - GST_WARNING ("We are already controlling a pad for this track"); - return; - } - - /* Remember the pad */ - GST_OBJECT_LOCK (track); - tr_priv->pad = pad; - - no_more = TRUE; - for (tmp = tr_priv->timeline->priv->tracks; tmp; tmp = g_list_next (tmp)) { - TrackPrivate *tr_priv = (TrackPrivate *) tmp->data; - - if (!tr_priv->pad) { - GST_LOG ("Found track without pad %p", tr_priv->track); - no_more = FALSE; - } - } - GST_OBJECT_UNLOCK (track); - - /* ghost it ! */ - GST_DEBUG ("Ghosting pad and adding it to ourself"); - padname = g_strdup_printf ("track_%p_src", track); - tr_priv->ghostpad = gst_ghost_pad_new (padname, pad); - g_free (padname); - gst_pad_set_active (tr_priv->ghostpad, TRUE); - gst_element_add_pad (GST_ELEMENT (tr_priv->timeline), tr_priv->ghostpad); - - if (no_more) { - GST_DEBUG ("Signaling no-more-pads"); - gst_element_no_more_pads (GST_ELEMENT (tr_priv->timeline)); - } -} - -static void -pad_removed_cb (GESTrack * track, GstPad * pad, TrackPrivate * tr_priv) -{ - GST_DEBUG ("track:%p, pad:%s:%s", track, GST_DEBUG_PAD_NAME (pad)); - - if (G_UNLIKELY (tr_priv->pad != pad)) { - GST_WARNING ("Not the pad we're controlling"); - return; - } - - if (G_UNLIKELY (tr_priv->ghostpad == NULL)) { - GST_WARNING ("We don't have a ghostpad for this pad !"); - return; - } - - GST_DEBUG ("Removing ghostpad"); - gst_pad_set_active (tr_priv->ghostpad, FALSE); - gst_element_remove_pad (GST_ELEMENT (tr_priv->timeline), tr_priv->ghostpad); - tr_priv->ghostpad = NULL; - tr_priv->pad = NULL; -} - /** * ges_timeline_add_track: * @timeline: a #GESTimeline From d8488cf061cb2685a49705b48ff4119127718339 Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Mon, 21 May 2012 13:05:14 -0400 Subject: [PATCH 15/19] videotransition: Do not wait pad to be blocked before switching transitions ... from smpte to crossfad and the other way around This avoid useless async operations --- ges/ges-track-video-transition.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/ges/ges-track-video-transition.c b/ges/ges-track-video-transition.c index 02b67253b4..1b3f3008fe 100644 --- a/ges/ges-track-video-transition.c +++ b/ges/ges-track-video-transition.c @@ -459,9 +459,9 @@ replace_mixer (GESTrackVideoTransitionPrivate * priv) mixer_src_pad = gst_element_get_static_pad (priv->mixer, "src"); color_sink_pad = gst_pad_get_peer (mixer_src_pad); - gst_element_set_state (priv->mixer, GST_STATE_NULL); gst_bin_remove (GST_BIN (priv->topbin), priv->mixer); + gst_element_set_state (priv->mixer, GST_STATE_NULL); gst_object_unref (priv->mixer); priv->mixer = gst_object_ref (create_mixer (priv->topbin)); @@ -479,8 +479,7 @@ replace_mixer (GESTrackVideoTransitionPrivate * priv) } static void -switch_to_smpte_cb (GstPad * sink, gboolean blocked, - GESTrackVideoTransition * transition) +switch_to_smpte (GESTrackVideoTransition * transition) { GstElement *smptealpha = gst_element_factory_make ("smptealpha", NULL); GstElement *smptealphab = gst_element_factory_make ("smptealpha", NULL); @@ -551,8 +550,8 @@ remove_smpte_from_bin (GESTrackVideoTransitionPrivate * priv, GstPad * sink) gst_pad_unlink (peer_src, smpte_sink); gst_pad_unlink (smpte_src, sink); - gst_element_set_state (smpte, GST_STATE_NULL); gst_bin_remove (GST_BIN (priv->topbin), smpte); + gst_element_set_state (smpte, GST_STATE_NULL); gst_object_unref (smpte); gst_object_unref (smpte_sink); @@ -562,8 +561,7 @@ remove_smpte_from_bin (GESTrackVideoTransitionPrivate * priv, GstPad * sink) } static void -switch_to_crossfade_cb (GstPad * sink, gboolean blocked, - GESTrackVideoTransition * transition) +switch_to_crossfade (GESTrackVideoTransition * transition) { GstElement *peera; GstElement *peerb; @@ -708,7 +706,7 @@ ges_track_video_transition_set_transition_type_internal (GESTrackVideoTransition { GESTrackVideoTransitionPrivate *priv = self->priv; - GST_LOG ("%p %d => %d", self, priv->type, type); + GST_LOG_OBJECT (self, "Changing from type %d to %d", priv->type, type); if (type == priv->type && !priv->pending_type) { GST_INFO ("This type is already set on this transition\n"); @@ -727,16 +725,20 @@ ges_track_video_transition_set_transition_type_internal (GESTrackVideoTransition if (!priv->topbin) return FALSE; priv->smpte = NULL; - gst_pad_set_blocked_async (gst_element_get_static_pad (priv->topbin, - "sinka"), TRUE, (GstPadBlockCallback) switch_to_smpte_cb, self); + + gst_pad_set_blocked_async (priv->sinka, TRUE, + (GstPadBlockCallback) block_pad_cb, NULL); + switch_to_smpte (self); } else { if (!priv->topbin) return FALSE; + priv->start_value = 1.0; priv->end_value = 0.0; - gst_pad_set_blocked_async (gst_element_get_static_pad (priv->topbin, - "sinka"), TRUE, (GstPadBlockCallback) switch_to_crossfade_cb, - self); + + gst_pad_set_blocked_async (priv->sinka, TRUE, + (GstPadBlockCallback) block_pad_cb, NULL); + switch_to_crossfade (self); } return TRUE; } From 28585bc87a71dfd59081fdb8b84fb4238339406b Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Mon, 21 May 2012 13:05:53 -0400 Subject: [PATCH 16/19] videotransition: Misc cleanup in the smpte/crossfade transition type switches --- ges/ges-track-video-transition.c | 91 ++++++++++++++------------------ 1 file changed, 40 insertions(+), 51 deletions(-) diff --git a/ges/ges-track-video-transition.c b/ges/ges-track-video-transition.c index 1b3f3008fe..027eb72740 100644 --- a/ges/ges-track-video-transition.c +++ b/ges/ges-track-video-transition.c @@ -80,9 +80,6 @@ static GParamSpec *properties[PROP_LAST]; #define fast_element_link(a,b) gst_element_link_pads_full((a),"src",(b),"sink",GST_PAD_LINK_CHECK_NOTHING) -static GObject *link_element_to_mixer (GstElement * element, - GstElement * mixer); - static GObject *link_element_to_mixer_with_smpte (GstBin * bin, GstElement * element, GstElement * mixer, gint type, GstElement ** smpteref); @@ -332,6 +329,18 @@ set_interpolation (GObject * element, GESTrackVideoTransitionPrivate * priv, (priv->control_source, GST_INTERPOLATE_LINEAR); } +static GstPad * +link_element_to_mixer (GstElement * element, GstElement * mixer) +{ + GstPad *sinkpad = gst_element_get_request_pad (mixer, "sink_%d"); + GstPad *srcpad = gst_element_get_static_pad (element, "src"); + + gst_pad_link_full (srcpad, sinkpad, GST_PAD_LINK_CHECK_NOTHING); + gst_object_unref (srcpad); + + return sinkpad; +} + static GstElement * ges_track_video_transition_create_element (GESTrackObject * object) { @@ -381,8 +390,8 @@ ges_track_video_transition_create_element (GESTrackObject * object) gst_element_link_pads_full (scaleb, "src", capsfilt, "sink", GST_PAD_LINK_CHECK_NOTHING); - priv->sinka = (GstPad *) link_element_to_mixer (scalea, mixer); - priv->sinkb = (GstPad *) link_element_to_mixer (capsfilt, mixer); + priv->sinka = link_element_to_mixer (scalea, mixer); + priv->sinkb = link_element_to_mixer (capsfilt, mixer); target = (GObject *) priv->sinkb; propname = "alpha"; priv->start_value = 0.0; @@ -425,9 +434,9 @@ ges_track_video_transition_create_element (GESTrackObject * object) } static void -unblock_pad_cb (GstPad * sink, gboolean blocked, void *nil_ptr) +block_pad_cb (GstPad * pad, gboolean blocked, void *nil_ptr) { - /*Dummy function to make sure the unblocking is async */ + /* Dummy function to make sure the unblocking is async */ } static void @@ -458,6 +467,8 @@ replace_mixer (GESTrackVideoTransitionPrivate * priv) mixer_src_pad = gst_element_get_static_pad (priv->mixer, "src"); color_sink_pad = gst_pad_get_peer (mixer_src_pad); + gst_object_unref (mixer_src_pad); + gst_object_unref (color_sink_pad); gst_bin_remove (GST_BIN (priv->topbin), priv->mixer); @@ -468,13 +479,10 @@ replace_mixer (GESTrackVideoTransitionPrivate * priv) gst_element_sync_state_with_parent (priv->mixer); - gst_object_unref (mixer_src_pad); - mixer_src_pad = gst_element_get_static_pad (priv->mixer, "src"); gst_pad_link (mixer_src_pad, color_sink_pad); gst_object_unref (mixer_src_pad); - gst_object_unref (color_sink_pad); } @@ -484,9 +492,7 @@ switch_to_smpte (GESTrackVideoTransition * transition) GstElement *smptealpha = gst_element_factory_make ("smptealpha", NULL); GstElement *smptealphab = gst_element_factory_make ("smptealpha", NULL); GESTrackVideoTransitionPrivate *priv = transition->priv; - - if (priv->pending_type == GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE) - goto beach; + GstPad *oldsinka = priv->sinka, *oldsinkb = priv->sinkb; GST_INFO ("Bin %p switching from crossfade to smpte", priv->topbin); @@ -513,8 +519,8 @@ switch_to_smpte (GESTrackVideoTransition * transition) priv->dur); - priv->sinka = (GstPad *) link_element_to_mixer (smptealpha, priv->mixer); - priv->sinkb = (GstPad *) link_element_to_mixer (smptealphab, priv->mixer); + priv->sinka = link_element_to_mixer (smptealpha, priv->mixer); + priv->sinkb = link_element_to_mixer (smptealphab, priv->mixer); priv->smpte = smptealphab; @@ -522,10 +528,10 @@ switch_to_smpte (GESTrackVideoTransition * transition) GST_INFO ("Bin %p switched from crossfade to smpte", priv->topbin); -beach: priv->pending_type = GES_VIDEO_STANDARD_TRANSITION_TYPE_NONE; - gst_pad_set_blocked_async (sink, - FALSE, (GstPadBlockCallback) unblock_pad_cb, NULL); + + gst_object_unref (oldsinka); + gst_object_unref (oldsinkb); } static GstElement * @@ -557,7 +563,8 @@ remove_smpte_from_bin (GESTrackVideoTransitionPrivate * priv, GstPad * sink) gst_object_unref (smpte_sink); gst_object_unref (smpte_src); gst_object_unref (peer_src); - return (peer); + + return peer; } static void @@ -566,21 +573,19 @@ switch_to_crossfade (GESTrackVideoTransition * transition) GstElement *peera; GstElement *peerb; GESTrackVideoTransitionPrivate *priv = transition->priv; + GstPad *oldsinka = priv->sinka, *oldsinkb = priv->sinkb; GST_INFO ("Bin %p switching from smpte to crossfade", priv->topbin); - if (priv->pending_type != GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE) - goto beach; - peera = remove_smpte_from_bin (priv, priv->sinka); peerb = remove_smpte_from_bin (priv, priv->sinkb); if (!peera || !peerb) - goto beach; + return; replace_mixer (priv); - priv->sinka = (GstPad *) link_element_to_mixer (peera, priv->mixer); - priv->sinkb = (GstPad *) link_element_to_mixer (peerb, priv->mixer); + priv->sinka = link_element_to_mixer (peera, priv->mixer); + priv->sinkb = link_element_to_mixer (peerb, priv->mixer); priv->start_value = 0.0; priv->end_value = 1.0; @@ -597,22 +602,10 @@ switch_to_crossfade (GESTrackVideoTransition * transition) GST_INFO ("Bin %p switched from smpte to crossfade", priv->topbin); -beach: priv->pending_type = GES_VIDEO_STANDARD_TRANSITION_TYPE_NONE; - gst_pad_set_blocked_async (sink, FALSE, - (GstPadBlockCallback) unblock_pad_cb, NULL); -} -static GObject * -link_element_to_mixer (GstElement * element, GstElement * mixer) -{ - GstPad *sinkpad = gst_element_get_request_pad (mixer, "sink_%d"); - GstPad *srcpad = gst_element_get_static_pad (element, "src"); - - gst_pad_link_full (srcpad, sinkpad, GST_PAD_LINK_CHECK_NOTHING); - gst_object_unref (srcpad); - - return G_OBJECT (sinkpad); + gst_object_unref (oldsinka); + gst_object_unref (oldsinkb); } static GObject * @@ -720,28 +713,24 @@ ges_track_video_transition_set_transition_type_internal (GESTrackVideoTransition ((priv->type != type) || (priv->type != priv->pending_type)) && ((type == GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE) || (priv->type == GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE))) { + + if (!priv->topbin) + return FALSE; + + gst_pad_set_blocked_async (priv->sinka, TRUE, + (GstPadBlockCallback) block_pad_cb, NULL); priv->pending_type = type; if (type != GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE) { - if (!priv->topbin) - return FALSE; - priv->smpte = NULL; - - gst_pad_set_blocked_async (priv->sinka, TRUE, - (GstPadBlockCallback) block_pad_cb, NULL); switch_to_smpte (self); } else { - if (!priv->topbin) - return FALSE; - priv->start_value = 1.0; priv->end_value = 0.0; - - gst_pad_set_blocked_async (priv->sinka, TRUE, - (GstPadBlockCallback) block_pad_cb, NULL); switch_to_crossfade (self); } + return TRUE; } + priv->pending_type = type; if (priv->smpte && (type != GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE)) { g_object_set (priv->smpte, "type", (gint) type, NULL); From a8f32d2e4826f33fb93f941f9d8fe8ad43faef0c Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Mon, 21 May 2012 12:45:00 -0400 Subject: [PATCH 17/19] videotransition: Some explanations about the invert property --- ges/ges-track-video-transition.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ges/ges-track-video-transition.c b/ges/ges-track-video-transition.c index 027eb72740..05aa78e33c 100644 --- a/ges/ges-track-video-transition.c +++ b/ges/ges-track-video-transition.c @@ -29,6 +29,10 @@ G_DEFINE_TYPE (GESTrackVideoTransition, ges_track_video_transition, GES_TYPE_TRACK_TRANSITION); +/* The description of the smpte transitions type correspond to those transition types + * being inverted, so in GES invert == !smptealpha.invert */ +#define DEFAULT_SMPTE_INVERT TRUE + static inline void ges_track_video_transition_set_border_internal (GESTrackVideoTransition * self, guint border); @@ -445,8 +449,9 @@ add_smpte_to_bin (GstPad * sink, GstElement * smptealpha, { GstPad *peer, *sinkpad; - g_object_set (smptealpha, - "type", (gint) priv->pending_type, "invert", (gboolean) TRUE, NULL); + g_object_set (smptealpha, "type", (gint) priv->pending_type, + "invert", (gboolean) DEFAULT_SMPTE_INVERT, NULL); + gst_bin_add (GST_BIN (priv->topbin), smptealpha); gst_element_sync_state_with_parent (smptealpha); From 410a3e4c3f69cab1a2d827c0366948c05cf96523 Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Mon, 21 May 2012 18:10:29 -0400 Subject: [PATCH 18/19] formatter: Disable updates when loading a project --- ges/ges-formatter.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ges/ges-formatter.c b/ges/ges-formatter.c index 41934cf176..b481f8c428 100644 --- a/ges/ges-formatter.c +++ b/ges/ges-formatter.c @@ -401,6 +401,7 @@ gboolean ges_formatter_load_from_uri (GESFormatter * formatter, GESTimeline * timeline, const gchar * uri) { + gboolean ret = FALSE; GESFormatterClass *klass = GES_FORMATTER_GET_CLASS (formatter); g_return_val_if_fail (GES_IS_FORMATTER (formatter), FALSE); @@ -408,10 +409,13 @@ ges_formatter_load_from_uri (GESFormatter * formatter, GESTimeline * timeline, g_signal_connect (timeline, "discovery-error", G_CALLBACK (discovery_error_cb), formatter); - if (klass->load_from_uri) - return klass->load_from_uri (formatter, timeline, uri); + if (klass->load_from_uri) { + ges_timeline_enable_update (timeline, FALSE); + ret = klass->load_from_uri (formatter, timeline, uri); + ges_timeline_enable_update (timeline, TRUE); + } - return FALSE; + return ret; } static gboolean @@ -600,6 +604,7 @@ discovery_error_cb (GESTimeline * timeline, static gboolean project_loaded (GESFormatter * formatter, GESTimeline * timeline) { + GST_INFO_OBJECT (formatter, "Emit project loaded"); g_signal_emit (formatter, ges_formatter_signals[LOADED_SIGNAL], 0, timeline); return TRUE; From 7012280e481617f040923daded849c9a03f6e222 Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Mon, 21 May 2012 19:38:10 -0400 Subject: [PATCH 19/19] timeline: Do not use meaningless offset values when snapping --- ges/ges-timeline.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ges/ges-timeline.c b/ges/ges-timeline.c index df5b76e52a..0b92c02c67 100644 --- a/ges/ges-timeline.c +++ b/ges/ges-timeline.c @@ -855,6 +855,9 @@ ges_timeline_snap_position (GESTimeline * timeline, GESTrackObject * trackobj, nxt_iter = g_sequence_iter_next (nxt_iter); } + if (ret == NULL) + off = G_MAXUINT64; + prev_iter = g_sequence_iter_prev (iter); while (!g_sequence_iter_is_begin (prev_iter)) { prev_tc = g_sequence_get (prev_iter);