From e970c8675372319b3ed727a55c08074da6fbf89a Mon Sep 17 00:00:00 2001 From: Edward Hervey Date: Thu, 16 Dec 2010 19:29:14 +0100 Subject: [PATCH] GESTimelineObject: Add mapping/offset support [start/priority properties] Allows moving independently (or not) timelineobjects and trackobjects and have them synchronized with the offsets taken into account. Right now only the start and priority properties are synchronized. The duration and in-point properties will require more thoughts. --- ges/ges-timeline-object.c | 257 +++++++++++++++++++++++++++---- ges/ges-timeline-pipeline.c | 4 +- tests/check/ges/layer.c | 10 -- tests/check/ges/timelineobject.c | 20 +++ 4 files changed, 247 insertions(+), 44 deletions(-) diff --git a/ges/ges-timeline-object.c b/ges/ges-timeline-object.c index f12238e34d..2d5aca10e0 100644 --- a/ges/ges-timeline-object.c +++ b/ges/ges-timeline-object.c @@ -42,11 +42,41 @@ ges_timeline_object_create_track_objects_func (GESTimelineObject * object, GESTrack * track); static void -track_object_priority_offset_changed_cb (GESTrackObject * child, - GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * obj); +track_object_start_changed_cb (GESTrackObject * child, + GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * object); +static void +track_object_inpoint_changed_cb (GESTrackObject * child, + GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * object); +static void +track_object_duration_changed_cb (GESTrackObject * child, + GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * object); +static void +track_object_priority_changed_cb (GESTrackObject * child, + GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * object); G_DEFINE_ABSTRACT_TYPE (GESTimelineObject, ges_timeline_object, G_TYPE_OBJECT); +/* Mapping of relationship between a TimelineObject and the TrackObjects + * it controls + * + * NOTE : how do we make this public in the future ? + */ +typedef struct +{ + GESTrackObject *object; + gint64 start_offset; + gint64 duration_offset; + gint64 inpoint_offset; + gint32 priority_offset; + + guint start_notifyid; + guint duration_notifyid; + guint inpoint_notifyid; + guint priority_notifyid; + + /* track mapping ?? */ +} ObjectMapping; + struct _GESTimelineObjectPrivate { /*< public > */ @@ -55,6 +85,13 @@ struct _GESTimelineObjectPrivate /*< private > */ /* A list of TrackObject controlled by this TimelineObject */ GList *trackobjects; + + /* Set to TRUE when the timelineobject is doing updates of track object + * properties so we don't end up in infinite property update loops + */ + gboolean ignore_notifies; + + GList *mappings; }; enum @@ -66,8 +103,11 @@ enum PROP_PRIORITY, PROP_HEIGHT, PROP_LAYER, + PROP_LAST }; +static GParamSpec *properties[PROP_LAST]; + static void ges_timeline_object_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) @@ -138,10 +178,10 @@ ges_timeline_object_class_init (GESTimelineObjectClass * klass) * * The position of the object in the #GESTimelineLayer (in nanoseconds). */ + properties[PROP_START] = g_param_spec_uint64 ("start", "Start", + "The position in the container", 0, G_MAXUINT64, 0, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_START, - g_param_spec_uint64 ("start", "Start", - "The position in the container", 0, G_MAXUINT64, 0, - G_PARAM_READWRITE)); + properties[PROP_START]); /** * GESTimelineObject:in-point @@ -152,9 +192,11 @@ ges_timeline_object_class_init (GESTimelineObjectClass * klass) * Ex : an in-point of 5 seconds means that the first outputted buffer will * be the one located 5 seconds in the controlled resource. */ - g_object_class_install_property (object_class, PROP_INPOINT, + properties[PROP_INPOINT] = g_param_spec_uint64 ("in-point", "In-point", "The in-point", 0, - G_MAXUINT64, 0, G_PARAM_READWRITE)); + G_MAXUINT64, 0, G_PARAM_READWRITE); + g_object_class_install_property (object_class, PROP_INPOINT, + properties[PROP_INPOINT]); /** * GESTimelineObject:duration @@ -162,19 +204,21 @@ ges_timeline_object_class_init (GESTimelineObjectClass * klass) * The duration (in nanoseconds) which will be used in the container #GESTrack * starting from 'in-point'. */ + properties[PROP_DURATION] = + g_param_spec_uint64 ("duration", "Duration", "The duration to use", 0, + G_MAXUINT64, GST_CLOCK_TIME_NONE, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_DURATION, - g_param_spec_uint64 ("duration", "Duration", "The duration to use", - 0, G_MAXUINT64, GST_CLOCK_TIME_NONE, G_PARAM_READWRITE)); + properties[PROP_DURATION]); /** * GESTimelineObject:priority * * The layer priority of the timeline object. */ - + properties[PROP_PRIORITY] = g_param_spec_uint ("priority", "Priority", + "The priority of the object", 0, G_MAXUINT, 0, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_PRIORITY, - g_param_spec_uint ("priority", "Priority", - "The priority of the object", 0, G_MAXUINT, 0, G_PARAM_READWRITE)); + properties[PROP_PRIORITY]); /** * GESTimelineObject:height @@ -306,6 +350,8 @@ gboolean ges_timeline_object_add_track_object (GESTimelineObject * object, GESTrackObject * trobj) { + ObjectMapping *mapping; + GST_LOG ("Got a TrackObject : %p , setting the timeline object as its" "creator", trobj); @@ -314,6 +360,10 @@ ges_timeline_object_add_track_object (GESTimelineObject * object, GESTrackObject ges_track_object_set_timeline_object (trobj, object); + mapping = g_slice_new0 (ObjectMapping); + mapping->object = trobj; + object->priv->mappings = g_list_append (object->priv->mappings, mapping); + GST_DEBUG ("Adding TrackObject to the list of controlled track objects"); /* We steal the initial reference */ object->priv->trackobjects = @@ -328,8 +378,19 @@ ges_timeline_object_add_track_object (GESTimelineObject * object, GESTrackObject GST_DEBUG ("Returning trobj:%p", trobj); - g_signal_connect (G_OBJECT (trobj), "notify::priority-offset", G_CALLBACK - (track_object_priority_offset_changed_cb), object); + /* Listen to all property changes */ + mapping->start_notifyid = + g_signal_connect (G_OBJECT (trobj), "notify::start", + G_CALLBACK (track_object_start_changed_cb), object); + mapping->duration_notifyid = + g_signal_connect (G_OBJECT (trobj), "notify::duration", + G_CALLBACK (track_object_duration_changed_cb), object); + mapping->inpoint_notifyid = + g_signal_connect (G_OBJECT (trobj), "notify::inpoint", + G_CALLBACK (track_object_inpoint_changed_cb), object); + mapping->priority_notifyid = + g_signal_connect (G_OBJECT (trobj), "notify::priority", + G_CALLBACK (track_object_priority_changed_cb), object); return TRUE; } @@ -338,11 +399,18 @@ ges_timeline_object_add_track_object (GESTimelineObject * object, GESTrackObject * ges_timeline_object_release_track_object: * @object: a #GESTimelineObject * @trackobject: the #GESTrackObject to release + * + * Release the @trackobject from the control of @object. + * + * Returns: %TRUE if the @trackobject was properly released, else %FALSE. */ gboolean ges_timeline_object_release_track_object (GESTimelineObject * object, GESTrackObject * trackobject) { + GList *tmp; + ObjectMapping *mapping = NULL; + GST_DEBUG ("object:%p, trackobject:%p", object, trackobject); if (!(g_list_find (object->priv->trackobjects, trackobject))) { @@ -353,6 +421,25 @@ ges_timeline_object_release_track_object (GESTimelineObject * object, /* FIXME : Do we need to tell the subclasses ? * If so, add a new virtual-method */ + for (tmp = object->priv->mappings; tmp; tmp = tmp->next) { + mapping = (ObjectMapping *) tmp->data; + if (mapping->object == trackobject) + break; + } + + if (tmp && mapping) { + + /* Disconnect all notify listeners */ + g_signal_handler_disconnect (trackobject, mapping->start_notifyid); + g_signal_handler_disconnect (trackobject, mapping->duration_notifyid); + g_signal_handler_disconnect (trackobject, mapping->inpoint_notifyid); + g_signal_handler_disconnect (trackobject, mapping->priority_notifyid); + + g_slice_free (ObjectMapping, mapping); + + object->priv->mappings = g_list_delete_link (object->priv->mappings, tmp); + } + object->priv->trackobjects = g_list_remove (object->priv->trackobjects, trackobject); @@ -360,6 +447,8 @@ ges_timeline_object_release_track_object (GESTimelineObject * object, g_object_unref (trackobject); + /* FIXME : resync properties ? */ + return TRUE; } @@ -407,6 +496,20 @@ ges_timeline_object_fill_track_object_func (GESTimelineObject * object, return FALSE; } +static ObjectMapping * +find_object_mapping (GESTimelineObject * object, GESTrackObject * child) +{ + GList *tmp; + + for (tmp = object->priv->mappings; tmp; tmp = tmp->next) { + ObjectMapping *map = (ObjectMapping *) tmp->data; + if (map->object == child) + return map; + } + + return NULL; +} + /** * ges_timeline_object_set_start: * @object: a #GESTimelineObject @@ -419,17 +522,28 @@ ges_timeline_object_set_start (GESTimelineObject * object, guint64 start) { GList *tmp; GESTrackObject *tr; + ObjectMapping *map; GST_DEBUG ("object:%p, start:%" GST_TIME_FORMAT, object, GST_TIME_ARGS (start)); + object->priv->ignore_notifies = TRUE; + for (tmp = object->priv->trackobjects; tmp; tmp = g_list_next (tmp)) { tr = (GESTrackObject *) tmp->data; - if (ges_track_object_is_locked (tr)) - /* call set_start on each trackobject */ - ges_track_object_set_start (tr, start); + map = find_object_mapping (object, tr); + + if (ges_track_object_is_locked (tr)) { + /* Move the child... */ + ges_track_object_set_start (tr, start + map->start_offset); + } else { + /* ... or update the offset */ + map->start_offset = start - tr->start; + } } + object->priv->ignore_notifies = FALSE; + object->start = start; } @@ -500,16 +614,28 @@ ges_timeline_object_set_priority (GESTimelineObject * object, guint priority) { GList *tmp; GESTrackObject *tr; + ObjectMapping *map; - GST_DEBUG ("object:%p, priority:%d", object, priority); + GST_DEBUG ("object:%p, priority:%" GST_TIME_FORMAT, + object, GST_TIME_ARGS (priority)); + + object->priv->ignore_notifies = TRUE; for (tmp = object->priv->trackobjects; tmp; tmp = g_list_next (tmp)) { tr = (GESTrackObject *) tmp->data; - if (ges_track_object_is_locked (tr)) - /* call set_priority on each trackobject */ - ges_track_object_set_priority (tr, priority); + map = find_object_mapping (object, tr); + + if (ges_track_object_is_locked (tr)) { + /* Move the child... */ + ges_track_object_set_priority (tr, priority + map->priority_offset); + } else { + /* ... or update the offset */ + map->priority_offset = priority - tr->priority; + } } + object->priv->ignore_notifies = FALSE; + object->priority = priority; } @@ -605,22 +731,89 @@ ges_timeline_object_get_track_objects (GESTimelineObject * object) /* * PROPERTY NOTIFICATIONS FROM TRACK OBJECTS */ + static void -track_object_priority_offset_changed_cb (GESTrackObject * child, +track_object_start_changed_cb (GESTrackObject * child, GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * object) { - guint new, old; + ObjectMapping *map; - /* all track objects have height 1 */ - new = ges_track_object_get_priority_offset (child) + 1; - old = GES_TIMELINE_OBJECT_HEIGHT (object); + if (object->priv->ignore_notifies) + return; - GST_LOG ("object %p, new=%d, old=%d", object, new, old); + map = find_object_mapping (object, child); + if (G_UNLIKELY (map == NULL)) + /* something massively screwed up if we get this */ + return; - if (new > old) { - object->height = new; - GST_LOG ("emitting notify signal"); - /* FIXME : use g_object_notify_by_pspec */ - g_object_notify ((GObject *) object, "height"); + if (!ges_track_object_is_locked (child)) { + /* Update the internal start_offset */ + map->start_offset = object->start - child->start; + } else { + /* Or update the parent start */ + ges_timeline_object_set_start (object, child->start + map->start_offset); + } +} + +static void +track_object_inpoint_changed_cb (GESTrackObject * child, + GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * object) +{ + if (object->priv->ignore_notifies) + return; + +} + +static void +track_object_duration_changed_cb (GESTrackObject * child, + GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * object) +{ + if (object->priv->ignore_notifies) + return; + +} + +static void +track_object_priority_changed_cb (GESTrackObject * child, + GParamSpec * arg G_GNUC_UNUSED, GESTimelineObject * object) +{ + ObjectMapping *map; + + if (object->priv->ignore_notifies) + return; + + map = find_object_mapping (object, child); + if (G_UNLIKELY (map == NULL)) + /* something massively screwed up if we get this */ + return; + + if (!ges_track_object_is_locked (child)) { + GList *tmp; + guint32 min_prio = G_MAXUINT32, max_prio = 0; + + /* Update the internal priority_offset */ + map->priority_offset = object->priority - child->priority; + + /* Go over all childs and check if height has changed */ + for (tmp = object->priv->trackobjects; tmp; tmp = tmp->next) { + GESTrackObject *tmpo = (GESTrackObject *) tmp->data; + + if (tmpo->priority < min_prio) + min_prio = tmpo->priority; + if (tmpo->priority > max_prio) + max_prio = tmpo->priority; + } + + /* FIXME : We only grow the height */ + if (object->height < (max_prio - min_prio + 1)) { + object->height = max_prio - min_prio + 1; + g_object_notify_by_pspec (G_OBJECT (object), properties[PROP_HEIGHT]); + } + } else { + /* Or update the parent priority */ + ges_timeline_object_set_priority (object, + child->priority + map->priority_offset); + /* For the locked situation, we don't need to check the height, + * since all object priorities are moving together */ } } diff --git a/ges/ges-timeline-pipeline.c b/ges/ges-timeline-pipeline.c index 690d22c906..6835216148 100644 --- a/ges/ges-timeline-pipeline.c +++ b/ges/ges-timeline-pipeline.c @@ -253,8 +253,8 @@ ges_timeline_pipeline_change_state (GstElement * element, ret = GST_STATE_CHANGE_FAILURE; goto done; } - if (self->priv-> - mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER)) + if (self-> + priv->mode & (TIMELINE_MODE_RENDER | TIMELINE_MODE_SMART_RENDER)) GST_DEBUG ("rendering => Updating pipeline caps"); if (!ges_timeline_pipeline_update_caps (self)) { GST_ERROR_OBJECT (element, "Error setting the caps for rendering"); diff --git a/tests/check/ges/layer.c b/tests/check/ges/layer.c index 3fa40bf53c..3b2eaad623 100644 --- a/tests/check/ges/layer.c +++ b/tests/check/ges/layer.c @@ -128,16 +128,6 @@ GST_START_TEST (test_layer_properties) gnl_object_check (ges_track_object_get_gnlobject (trackobject), 42, 51, 12, 51, 0, TRUE); - /* check priority offsets */ - assert_equals_int (GES_TIMELINE_OBJECT_HEIGHT (object), 1); - g_object_set (trackobject, "priority-offset", (guint32) 3, NULL); - gnl_object_check (ges_track_object_get_gnlobject (trackobject), 42, 51, 12, - 51, 3, TRUE); - g_object_set (object, "priority", 5, NULL); - gnl_object_check (ges_track_object_get_gnlobject (trackobject), 42, 51, 12, - 51, 8, TRUE); - assert_equals_int (GES_TIMELINE_OBJECT_HEIGHT (object), 4); - g_object_unref (trackobject); fail_unless (ges_timeline_layer_remove_object (layer, object)); fail_unless (ges_timeline_remove_track (timeline, track)); diff --git a/tests/check/ges/timelineobject.c b/tests/check/ges/timelineobject.c index dacba20798..caf90460d4 100644 --- a/tests/check/ges/timelineobject.c +++ b/tests/check/ges/timelineobject.c @@ -105,6 +105,15 @@ GST_START_TEST (test_object_properties) gnl_object_check (ges_track_object_get_gnlobject (trackobject), 420, 510, 120, 510, 0, TRUE); + + /* This time, we move the trackobject to see if the changes move + * along to the parent and the gnonlin object */ + g_object_set (trackobject, "start", (guint64) 400, NULL); + assert_equals_uint64 (GES_TIMELINE_OBJECT_START (object), 400); + assert_equals_uint64 (GES_TRACK_OBJECT_START (trackobject), 400); + gnl_object_check (ges_track_object_get_gnlobject (trackobject), 400, 510, 120, + 510, 0, TRUE); + ges_timeline_object_release_track_object (object, trackobject); g_object_unref (object); @@ -166,6 +175,17 @@ GST_START_TEST (test_object_properties_unlocked) gnl_object_check (ges_track_object_get_gnlobject (trackobject), 42, 51, 12, 51, 0, TRUE); + /* When unlocked, moving the GESTrackObject won't move the GESTimelineObject + * either */ + /* This time, we move the trackobject to see if the changes move + * along to the parent and the gnonlin object */ + g_object_set (trackobject, "start", (guint64) 400, NULL); + assert_equals_uint64 (GES_TIMELINE_OBJECT_START (object), 420); + assert_equals_uint64 (GES_TRACK_OBJECT_START (trackobject), 400); + gnl_object_check (ges_track_object_get_gnlobject (trackobject), 400, 51, 12, + 51, 0, TRUE); + + ges_timeline_object_release_track_object (object, trackobject); g_object_unref (object);