diff --git a/ges/ges-effect.c b/ges/ges-effect.c index 9d69ee50c3..4de3aaf10b 100644 --- a/ges/ges-effect.c +++ b/ges/ges-effect.c @@ -131,33 +131,6 @@ property_name_compare (gconstpointer s1, gconstpointer s2) return g_strcmp0 ((const gchar *) s1, (const gchar *) s2); } -static void -_set_child_property (GESTimelineElement * self G_GNUC_UNUSED, GObject * child, - GParamSpec * pspec, GValue * value) -{ - GESEffectClass *klass = GES_EFFECT_GET_CLASS (self); - gchar *full_property_name; - - GES_TIMELINE_ELEMENT_CLASS - (ges_effect_parent_class)->set_child_property (self, child, pspec, value); - - full_property_name = g_strdup_printf ("%s::%s", G_OBJECT_TYPE_NAME (child), - pspec->name); - - if (g_list_find_custom (klass->rate_properties, full_property_name, - property_name_compare)) { - GstElement *nleobject = - ges_track_element_get_nleobject (GES_TRACK_ELEMENT (self)); - gdouble media_duration_factor = - ges_timeline_element_get_media_duration_factor (self); - - g_object_set (nleobject, "media-duration-factor", media_duration_factor, - NULL); - } - - g_free (full_property_name); -} - static void ges_effect_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) @@ -193,11 +166,9 @@ ges_effect_class_init (GESEffectClass * klass) { GObjectClass *object_class; GESTrackElementClass *obj_bg_class; - GESTimelineElementClass *element_class; object_class = G_OBJECT_CLASS (klass); obj_bg_class = GES_TRACK_ELEMENT_CLASS (klass); - element_class = GES_TIMELINE_ELEMENT_CLASS (klass); object_class->get_property = ges_effect_get_property; object_class->set_property = ges_effect_set_property; @@ -205,7 +176,6 @@ ges_effect_class_init (GESEffectClass * klass) object_class->finalize = ges_effect_finalize; obj_bg_class->create_element = ges_effect_create_element; - element_class->set_child_property = _set_child_property; klass->rate_properties = NULL; ges_effect_class_register_rate_property (klass, "scaletempo", "rate"); diff --git a/ges/ges-track-element.c b/ges/ges-track-element.c index 2f9eff3b28..a8adc0141b 100644 --- a/ges/ges-track-element.c +++ b/ges/ges-track-element.c @@ -262,7 +262,6 @@ ges_track_element_set_asset (GESExtractable * extractable, GESAsset * asset) { GESTrackElementClass *class; GstElement *nleobject; - gdouble media_duration_factor; gchar *tmp; GESTrackElement *object = GES_TRACK_ELEMENT (extractable); @@ -298,12 +297,6 @@ ges_track_element_set_asset (GESExtractable * extractable, GESAsset * asset) "duration", GES_TIMELINE_ELEMENT_DURATION (object), "priority", GES_TIMELINE_ELEMENT_PRIORITY (object), "active", object->active & object->priv->layer_active, NULL); - - media_duration_factor = - ges_timeline_element_get_media_duration_factor (GES_TIMELINE_ELEMENT - (object)); - g_object_set (object->priv->nleobject, - "media-duration-factor", media_duration_factor, NULL); } static void diff --git a/plugins/nle/nleghostpad.c b/plugins/nle/nleghostpad.c index 75778c9a28..cb1e5ec24d 100644 --- a/plugins/nle/nleghostpad.c +++ b/plugins/nle/nleghostpad.c @@ -98,6 +98,12 @@ nle_object_translate_incoming_seek (NleObject * object, GstEvent * event) GST_LOG_OBJECT (object, "Setting stop to %" GST_TIME_FORMAT, GST_TIME_ARGS (nstop)); } else { + /* NOTE: for an element that is upstream from a time effect we do not + * want to limit the seek to the object->stop because a time effect + * can reasonably increase the final time. + * In such situations, the seek stop should be set once by the + * source pad of the most downstream object (highest priority in the + * nlecomposition). */ GST_DEBUG_OBJECT (object, "Limiting end of seek to media_stop"); nle_object_to_media_time (object, object->stop, &nstop); if (nstop > G_MAXINT64) @@ -190,6 +196,9 @@ translate_outgoing_seek (NleObject * object, GstEvent * event) GST_LOG_OBJECT (object, "Setting stop to %" GST_TIME_FORMAT, GST_TIME_ARGS (nstop)); } else { + /* NOTE: when we have time effects, the object stop is not the + * correct stop limit. Therefore, the seek stop time should already + * be set at this point */ GST_DEBUG_OBJECT (object, "Limiting end of seek to stop"); nstop = object->stop; if (nstop > G_MAXINT64) @@ -423,6 +432,9 @@ translate_incoming_duration_query (NleObject * object, GstQuery * query) return FALSE; } + /* NOTE: returns the duration of the object, but this is not the same + * as the source duration when time effects are used. Nor is it the + * duration of the current nlecomposition stack */ gst_query_set_duration (query, GST_FORMAT_TIME, object->duration); return TRUE; diff --git a/plugins/nle/nleobject.c b/plugins/nle/nleobject.c index 6e16f8e032..1c6266b9b9 100644 --- a/plugins/nle/nleobject.c +++ b/plugins/nle/nleobject.c @@ -248,6 +248,10 @@ nle_object_class_init (NleObjectClass * klass) * relation between the rate of media entering and leaving this object. I.e. * if object pulls data at twice the speed it sends it (e.g. `pitch * tempo=2.0`), this value is set to 2.0. + * + * Deprecated: 1.18: This property is ignored since the wrapped + * #GstElement-s themselves should internally perform any additional time + * translations. */ properties[PROP_MEDIA_DURATION_FACTOR] = g_param_spec_double ("media-duration-factor", "Media duration factor", @@ -292,8 +296,6 @@ nle_object_init (NleObject * object, NleObjectClass * klass) object->segment_rate = 1.0; object->segment_start = -1; object->segment_stop = -1; - object->media_duration_factor = 1.0; - object->recursive_media_duration_factor = 1.0; object->srcpad = nle_object_ghost_pad_no_target (object, "src", GST_PAD_SRC, @@ -326,15 +328,41 @@ nle_object_dispose (GObject * object) * @objecttime: The #GstClockTime we want to convert * @mediatime: A pointer on a #GstClockTime to fill * - * Converts a #GstClockTime from the object (container) context to the media context + * Converts a #GstClockTime timestamp received from another nleobject pad + * in the same nlecomposition, or from the nlecomposition itself, to an + * internal source time. * - * Returns: TRUE if @objecttime was within the limits of the @object start/stop time, - * FALSE otherwise + * If the object is furthest downstream in the nlecomposition (highest + * priority in the current stack), this will convert the timestamp from + * the composition coordinate time to the internal source coordinate time + * of the object. + * + * If the object is upstream from another nleobject, then this can convert + * the timestamp received from the downstream sink pad to the internal + * source coordinates of the object, to be passed to its internal + * elements. + * + * If the object is downstream from another nleobject, then this can + * convert the timestamp received from the upstream source pad to the + * internal sink coordinates of the object, to be passed to its internal + * elements. + * + * In these latter two cases, the timestamp should have been converted + * by the peer pad using nle_media_to_object_time(). + * + * Note, if an object introduces a time effect, it must have a 0 in-point + * and the same #nleobject:start and #nleobject:duration as all the other + * objects that are further upstream. + * + * Returns: TRUE if @objecttime was below the @object start time, + * FALSE otherwise. */ gboolean nle_object_to_media_time (NleObject * object, GstClockTime otime, GstClockTime * mtime) { + gboolean ret = TRUE; + g_return_val_if_fail (mtime, FALSE); GST_DEBUG_OBJECT (object, "ObjectTime : %" GST_TIME_FORMAT, @@ -345,39 +373,61 @@ nle_object_to_media_time (NleObject * object, GstClockTime otime, "Media start: %" GST_TIME_FORMAT, GST_TIME_ARGS (object->start), GST_TIME_ARGS (object->stop), GST_TIME_ARGS (object->inpoint)); - /* limit check */ + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (otime))) { + GST_DEBUG_OBJECT (object, "converting none object time to none"); + *mtime = GST_CLOCK_TIME_NONE; + return TRUE; + } + + /* We do not allow @otime to be below the start of the object. + * If it was below, then the object would have a negative external + * source/sink time. + * + * Note that ges only supports time effects that map the time 0 to + * 0. Such time effects would also not produce an external timestamp + * below start, nor can they receive such a timestamp. */ if (G_UNLIKELY ((otime < object->start))) { GST_DEBUG_OBJECT (object, "ObjectTime is before start"); - *mtime = (object->inpoint == GST_CLOCK_TIME_NONE) ? 0 : object->inpoint; - return FALSE; + otime = object->start; + ret = FALSE; } + /* NOTE: if an nlecomposition contains time effect operations, then + * @otime can reasonably exceed the stop time of the object. So we + * do not limit it here. */ - if (G_UNLIKELY ((otime >= object->stop))) { - GST_DEBUG_OBJECT (object, "ObjectTime is after stop"); - if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (object->inpoint))) - *mtime = - object->inpoint + - object->duration * object->recursive_media_duration_factor; - else - *mtime = - (object->stop - - object->start) * object->recursive_media_duration_factor; - return FALSE; - } + /* first we convert the timestamp to the object's external source/sink + * coordinates: + * + For an object that is furthest downstream, we translate from the + * composition coordinates to the external source coordinates by + * subtracting the object start. + * + For an object that is upstream from d_object, we need to + * translate from its external sink coordinates to our external + * source coordinates. This is done by adding + * (d_object->start - object->start) + * However, the sink pad of d_object should have already added the + * d_object->start to the timestamp (see nle_media_to_object_time) + * so we also only need to subtract the object start. + * + For an object that is downstream from u_object, we need to + * translate from its external source coordinates to our external + * sink coordinates. This is similarly done by adding + * (u_object->start - object->start) + * However, the source pad of u_object should have already added the + * u_object->start to the timestamp (see nle_media_to_object_time) + * so we also only need to subtract the object start. + */ + *mtime = otime - object->start; - if (G_UNLIKELY (object->inpoint == GST_CLOCK_TIME_NONE)) { - /* no time shifting, for live sources ? */ - *mtime = (otime - object->start) * object->recursive_media_duration_factor; - } else { - *mtime = - (otime - object->start) * object->recursive_media_duration_factor + - object->inpoint; - } + /* we then convert the timestamp from the object's external source/sink + * coordinates to its internal source/sink coordinates, to be used by + * internal elements that the object wraps. This is done by adding + * the object in-point. */ + if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (object->inpoint))) + *mtime += object->inpoint; GST_DEBUG_OBJECT (object, "Returning MediaTime : %" GST_TIME_FORMAT, GST_TIME_ARGS (*mtime)); - return TRUE; + return ret; } /** @@ -386,10 +436,28 @@ nle_object_to_media_time (NleObject * object, GstClockTime otime, * @mediatime: The #GstClockTime we want to convert * @objecttime: A pointer on a #GstClockTime to fill * - * Converts a #GstClockTime from the media context to the object (container) context + * Converts a #GstClockTime timestamp from an internal time to an + * nleobject pad time. * - * Returns: TRUE if @objecttime was within the limits of the @object media start/stop time, - * FALSE otherwise + * If the object is furthest downstream in an nlecomposition (highest + * priority in the current stack), this will convert the timestamp from + * the internal source coordinate time of the object to the composition + * coordinate time. + * + * If the object is upstream from another nleobject, then this can convert + * the timestamp from the internal source coordinates of the object to be + * sent to the downstream sink pad. + * + * If the object is downstream from another nleobject, then this can + * convert the timestamp from the internal sink coordinates of the object + * to be sent to the upstream source pad. + * + * Note, if an object introduces a time effect, it must have a 0 in-point + * and the same #nleobject:start and #nleobject:duration as all the other + * objects that are further upstream. + * + * Returns: TRUE if @objecttime was below the @object in-point time, + * FALSE otherwise. */ gboolean @@ -406,19 +474,35 @@ nle_media_to_object_time (NleObject * object, GstClockTime mtime, "inpoint %" GST_TIME_FORMAT, GST_TIME_ARGS (object->start), GST_TIME_ARGS (object->stop), GST_TIME_ARGS (object->inpoint)); + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (mtime))) { + GST_DEBUG_OBJECT (object, "converting none media time to none"); + *otime = GST_CLOCK_TIME_NONE; + return TRUE; + } - /* limit check */ - if (G_UNLIKELY ((object->inpoint != GST_CLOCK_TIME_NONE) + /* the internal time should never go below the in-point! */ + if (G_UNLIKELY (GST_CLOCK_TIME_IS_VALID (object->inpoint) && (mtime < object->inpoint))) { GST_DEBUG_OBJECT (object, "media time is before inpoint, forcing to start"); *otime = object->start; return FALSE; } - if (G_LIKELY (object->inpoint != GST_CLOCK_TIME_NONE)) { - *otime = mtime - object->inpoint + object->start; - } else - *otime = mtime + object->start; + /* first we convert the timestamp to the object's external source/sink + * coordinates by removing the in-point */ + if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (object->inpoint))) + *otime = mtime - object->inpoint; + else + *otime = mtime; + + /* then we convert the timestamp by adding start. + * If the object is furthest downstream, this will translate it from + * the external source coordinates to the composition coordinates. + * Otherwise, this will perform part of the conversion from the object's + * source/sink coordinates to the downstream/upstream sink/source + * coordinates (the conversion is completed in + * nle_object_to_media_time). */ + *otime += object->start; GST_DEBUG_OBJECT (object, "Returning ObjectTime : %" GST_TIME_FORMAT, GST_TIME_ARGS (*otime)); @@ -563,7 +647,12 @@ nle_object_set_property (GObject * object, guint prop_id, GST_OBJECT_FLAG_UNSET (nleobject, NLE_OBJECT_EXPANDABLE); break; case PROP_MEDIA_DURATION_FACTOR: - nleobject->media_duration_factor = g_value_get_double (value); + { + gdouble val = g_value_get_double (value); + if (val != 1.0) + g_warning ("Ignoring media-duration-factor value of %g since the " + "property is deprecated", val); + } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -604,7 +693,8 @@ nle_object_get_property (GObject * object, guint prop_id, g_value_set_boolean (value, NLE_OBJECT_IS_EXPANDABLE (object)); break; case PROP_MEDIA_DURATION_FACTOR: - g_value_set_double (value, nleobject->media_duration_factor); + g_warning ("The media-duration-factor property is deprecated"); + g_value_set_double (value, 1.0); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); diff --git a/plugins/nle/nleobject.h b/plugins/nle/nleobject.h index 5af971702c..1e0272e4ae 100644 --- a/plugins/nle/nleobject.h +++ b/plugins/nle/nleobject.h @@ -126,12 +126,6 @@ struct _NleObject gint64 segment_start; gint64 segment_stop; - /* the rate at which this object speeds up or slows down media */ - gdouble media_duration_factor; - /* the rate at which this object and all of its children speed up or slow - * down media */ - gdouble recursive_media_duration_factor; - gboolean in_composition; }; diff --git a/plugins/nle/nleoperation.c b/plugins/nle/nleoperation.c index 6f37eaa4e0..62e27a779c 100644 --- a/plugins/nle/nleoperation.c +++ b/plugins/nle/nleoperation.c @@ -31,6 +31,46 @@ * A NleOperation performs a transformation or mixing operation on the * data from one or more #NleSources, which is used to implement filters or * effects. + * + * ## Time Effects + * + * An #nleoperation that wraps a #GstElement that transforms seek and + * segment times is considered a time effect. Nle only tries to support + * time effect's whose overall seek transformation: + * + * + Maps the time `0` to `0`. So initial time-shifting effects are + * excluded (the #nlesource:inpoint can sometimes be used instead). + * + Is monotonically increasing. So reversing effects, and effects that + * jump backwards in the stream are excluded. + * + Can handle a reasonable #GstClockTime, relative to the project. So + * this would exclude a time effect with an extremely large speed-up + * that would cause the converted #GstClockTime seeks to overflow. + * + Is 'continuously reversible'. This essentially means that for every + * seek position found on the sink pad of the element, we can, to 'good + * enough' accuracy, calculate the corresponding seek position that was + * received on the source pad. Moreover, this calculation should + * correspond to how the element transforms its #GstSegment + * @time field. This is needed so that a seek can result in an accurate + * segment. + * + * Note that a constant-rate-change effect that is not extremely fast or + * slow would satisfy these conditions. + * + * For such a time effect, they should be configured in nle such that: + * + * + Their #nleoperation:inpoint is `0`. Otherwise this will introduce + * seeking problems in its #nlecomposition. + * + They must share the same #nleoperation:start and + * #nleoperation:duration as all nleobjects of lower priority in its + * #nlecomposition. Otherwise this will introduce jumps in playback. + * + * Note that, at the moment, nle only converts the #GstSegment + * @time field which means the other fields, such as @start and @stop, can + * end up with non-meaningful values when time effects are involved. + * Moreover, it does not convert #GstBuffer times either, which can result + * in them having non-meaningful values. In particular, this means that + * #GstControlBinding-s will not work well with #nlecomposition-s when + * they include time effects. */ static GstStaticPadTemplate nle_operation_src_template = diff --git a/tests/check/ges/tempochange.c b/tests/check/ges/tempochange.c index b537d91550..ba38e122df 100644 --- a/tests/check/ges/tempochange.c +++ b/tests/check/ges/tempochange.c @@ -30,8 +30,6 @@ GST_START_TEST (test_tempochange) GESEffect *effect; GESTestClip *clip; GESClip *clip2, *clip3; - GList *tmp; - int found; ges_init (); @@ -78,45 +76,6 @@ GST_START_TEST (test_tempochange) fail_unless_equals_int64 (_INPOINT (clip3), 7.5 * GST_SECOND); fail_unless_equals_int64 (_DURATION (clip3), 3 * GST_SECOND); -#define MDF_OF_CLIP(x) \ - ((NleObject *) ges_track_element_get_nleobject \ - (ges_clip_find_track_element (GES_CLIP (x), track_audio, \ - G_TYPE_NONE)))->media_duration_factor - - fail_unless_equals_float (MDF_OF_CLIP (clip), 1.0); - fail_unless_equals_float (MDF_OF_CLIP (clip2), 1.5); - fail_unless_equals_float (MDF_OF_CLIP (clip3), 1.5); - - found = 0; - - for (tmp = GES_CONTAINER_CHILDREN (clip2); tmp; tmp = tmp->next) { - // A clip may have children other than the effect we added. As such, we - // need to find the child which is the pitch effect in order to check its - // value. - GstElement *nle = ges_track_element_get_nleobject (tmp->data); - if (strncmp (GST_OBJECT_NAME (nle), "GESEffect:", 10) == 0) { - fail_if (found); - found = 1; - fail_unless_equals_float (((NleObject *) nle)->media_duration_factor, - 1.5); - } - } - - fail_unless (found); - - found = 0; - for (tmp = GES_CONTAINER_CHILDREN (clip3); tmp; tmp = tmp->next) { - GstElement *nle = ges_track_element_get_nleobject (tmp->data); - if (strncmp (GST_OBJECT_NAME (nle), "GESEffect:", 10) == 0) { - fail_if (found); - found = 1; - fail_unless_equals_float (((NleObject *) nle)->media_duration_factor, - 1.5); - } - } - - fail_unless (found); - ges_layer_remove_clip (layer, (GESClip *) clip); ges_layer_remove_clip (layer, clip2); ges_layer_remove_clip (layer, clip3); diff --git a/tests/check/nle/tempochange.c b/tests/check/nle/tempochange.c index f1ccf4e1e3..2ac27d71be 100644 --- a/tests/check/nle/tempochange.c +++ b/tests/check/nle/tempochange.c @@ -20,140 +20,610 @@ #include "common.h" #include "plugins/nle/nleobject.h" -GST_START_TEST (test_tempochange) +typedef struct _PadEventData { - GstElement *pipeline; - GstElement *comp, *source1, *def, *sink, *oper; - GList *segments = NULL; - GstBus *bus; - GstMessage *message; - gboolean carry_on, ret = FALSE; - CollectStructure *collect; - GstPad *sinkpad; + gchar *name; + guint expect_num_segments; + guint num_segments; + GArray *expect_segment_time; + GArray *expect_segment_num_seeks; + guint expect_num_seeks; + guint num_seeks; + GArray *expect_seek_start; + GArray *expect_seek_stop; + GArray *expect_seek_num_segments; + guint num_eos; + guint expect_num_eos; +} PadEventData; - pipeline = gst_pipeline_new ("test_pipeline"); +#define _SEGMENT_FORMAT "flags: %i, rate: %g, applied_rate: %g, format: %i" \ + ", base: %" G_GUINT64_FORMAT ", offset: %" G_GUINT64_FORMAT ", start: %" \ + G_GUINT64_FORMAT ", stop: %" G_GUINT64_FORMAT ", time: %" G_GUINT64_FORMAT \ + ", position: %" G_GUINT64_FORMAT ", duration: %" G_GUINT64_FORMAT + +#define _SEGMENT_ARGS(seg) (seg).flags, (seg).rate, (seg).applied_rate, \ + (seg).format, (seg).base, (seg).offset, (seg).start, (seg).stop, \ + (seg).time, (seg).position, (seg).duration + +static GstPadProbeReturn +_test_pad_events (GstPad * pad, GstPadProbeInfo * info, PadEventData * data) +{ + guint num; + GstEvent *event = info->data; + if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) { + guint expect_num_seeks; + const GstSegment *segment; + /* copy the segment start, stop, position and duration since these are + * not yet translated by nleghostpad. Also, don't care about flags. */ + GstSegment expect_segment; + + gst_event_parse_segment (event, &segment); + GST_DEBUG ("%s segment: " _SEGMENT_FORMAT, data->name, + _SEGMENT_ARGS (*segment)); + + if (!GST_CLOCK_TIME_IS_VALID (segment->stop)) { + GST_DEBUG ("%s: ignoring pre-roll segment", data->name); + return GST_PAD_PROBE_OK; + } + + data->num_segments++; + num = data->num_segments; + + fail_unless (num <= data->expect_num_segments, "%s received %u " + "segments, more than the expected %u segments", data->name, num, + data->expect_num_segments); + + expect_num_seeks = + g_array_index (data->expect_segment_num_seeks, gint, num - 1); + + fail_unless (data->num_seeks == expect_num_seeks, "%s has received %u " + "segments, compared to %u seeks, but expected %u seeks", + data->name, num, data->num_seeks, expect_num_seeks); + + /* copy the segment start, stop, position, duration, offset, base + * since these are not yet translated by nleghostpad. */ + gst_segment_copy_into (segment, &expect_segment); + expect_segment.rate = 1.0; + expect_segment.applied_rate = 1.0; + expect_segment.format = GST_FORMAT_TIME; + expect_segment.time = g_array_index (data->expect_segment_time, + GstClockTime, num - 1); + + fail_unless (gst_segment_is_equal (segment, &expect_segment), + "%s %uth segment is not equal to the expected. Received:\n" + _SEGMENT_FORMAT "\nExpected\n" _SEGMENT_FORMAT, data->name, + num - 1, _SEGMENT_ARGS (*segment), _SEGMENT_ARGS (expect_segment)); + + } else if (GST_EVENT_TYPE (event) == GST_EVENT_SEEK) { + gdouble rate; + GstFormat format; + GstClockTime expect; + gint64 start, stop; + GstSeekType start_type, stop_type; + guint expect_num_segments; + + gst_event_parse_seek (event, &rate, &format, NULL, &start_type, &start, + &stop_type, &stop); + + GST_DEBUG ("%s seek: rate: %g, start: %" G_GINT64_FORMAT ", stop: %" + G_GINT64_FORMAT, data->name, rate, start, stop); + + data->num_seeks++; + num = data->num_seeks; + + fail_unless (num <= data->expect_num_seeks, "%s received %u " + "seeks, more than the expected %u seeks", data->name, num, + data->expect_num_seeks); + + expect_num_segments = + g_array_index (data->expect_seek_num_segments, gint, num - 1); + + fail_unless (data->num_segments == expect_num_segments, "%s has " + "received %u seeks, compared to %u segments, but expected %u " + "segments", data->name, num, data->num_segments, expect_num_segments); + + fail_unless (rate == 1.0, "%s %uth seek has a rate of %g rather than 1.0", + data->name, num - 1, rate); + fail_unless (format == GST_FORMAT_TIME, "%s %uth seek has a format of %i " + " than a time format", data->name, num - 1, format); + + /* expect seek-set or seek-none */ + fail_if (start_type == GST_SEEK_TYPE_END, "%s %uth seek-start is " + "seek-end", data->name, num - 1); + fail_if (stop_type == GST_SEEK_TYPE_END, "%s %uth seek-stop is " + "seek-end", data->name, num - 1); + + expect = g_array_index (data->expect_seek_start, GstClockTime, num - 1); + fail_unless (start == expect, "%s %uth seek start is %" GST_TIME_FORMAT + ", rather than the expected %" GST_TIME_FORMAT, data->name, num - 1, + GST_TIME_ARGS (start), GST_TIME_ARGS (expect)); + + expect = g_array_index (data->expect_seek_stop, GstClockTime, num - 1); + fail_unless (stop == expect, "%s %uth seek stop is %" GST_TIME_FORMAT + ", rather than the expected %" GST_TIME_FORMAT, data->name, num - 1, + GST_TIME_ARGS (stop), GST_TIME_ARGS (expect)); + + } else if (GST_EVENT_TYPE (event) == GST_EVENT_EOS) { + data->num_eos++; + fail_unless (data->num_eos <= data->expect_num_eos, "%s received %u " + "EOS, more than the expected %u EOS", data->name, data->num_eos, + data->expect_num_seeks); + } + + return GST_PAD_PROBE_OK; +} + +static void +_pad_event_data_check_received (PadEventData * data) +{ + fail_unless (data->num_eos == data->expect_num_eos, "%s received %u " + "EOS, rather than %u", data->num_eos, data->expect_num_eos); + fail_unless (data->num_segments == data->expect_num_segments, + "%s received %u segments, rather than %u", data->name, + data->num_segments, data->expect_num_segments); + fail_unless (data->num_seeks == data->expect_num_seeks, + "%s received %u seeks, rather than %u", data->name, data->num_seeks, + data->expect_num_seeks); +} + +static void +_pad_event_data_free (PadEventData * data) +{ + g_free (data->name); + g_array_unref (data->expect_segment_time); + g_array_unref (data->expect_seek_start); + g_array_unref (data->expect_seek_stop); + g_array_unref (data->expect_segment_num_seeks); + g_array_unref (data->expect_seek_num_segments); + g_free (data); +} + +static PadEventData * +_pad_event_data_new (GstElement * element, const gchar * pad_name, + const gchar * suffix) +{ + GstPad *pad; + PadEventData *data = g_new0 (PadEventData, 1); + data->expect_segment_time = g_array_new (FALSE, FALSE, sizeof (GstClockTime)); + data->expect_seek_start = g_array_new (FALSE, FALSE, sizeof (GstClockTime)); + data->expect_seek_stop = g_array_new (FALSE, FALSE, sizeof (GstClockTime)); + data->expect_seek_num_segments = g_array_new (FALSE, FALSE, sizeof (guint)); + data->expect_segment_num_seeks = g_array_new (FALSE, FALSE, sizeof (guint)); + data->name = g_strdup_printf ("%s:%s(%s):%s", G_OBJECT_TYPE_NAME (element), + GST_ELEMENT_NAME (element), pad_name, suffix); + + pad = gst_element_get_static_pad (element, pad_name); + fail_unless (pad, "%s not found", data->name); + gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM | + GST_PAD_PROBE_TYPE_EVENT_UPSTREAM, (GstPadProbeCallback) _test_pad_events, + data, (GDestroyNotify) _pad_event_data_free); + gst_object_unref (pad); + + return data; +} + +static void +_pad_event_data_add_expect_segment (PadEventData * data, GstClockTime time) +{ + data->expect_num_segments++; + g_array_append_val (data->expect_segment_time, time); + g_array_append_val (data->expect_segment_num_seeks, data->expect_num_seeks); +} + +static void +_pad_event_data_add_expect_seek (PadEventData * data, GstClockTime start, + GstClockTime stop) +{ + data->expect_num_seeks++; + g_array_append_val (data->expect_seek_start, start); + g_array_append_val (data->expect_seek_stop, stop); + g_array_append_val (data->expect_seek_num_segments, + data->expect_num_segments); +} + +static void +_pad_event_data_add_expect_seek_then_segment (PadEventData * data, + GstClockTime start, GstClockTime stop) +{ + _pad_event_data_add_expect_seek (data, start, stop); + _pad_event_data_add_expect_segment (data, start); +} + +#define _EXPECT_SEEK_SEGMENT(data, start, stop) \ + _pad_event_data_add_expect_seek_then_segment (data, start, stop) + +static GstElement * +_get_source (GstElement * nle_source) +{ + GList *tmp; + GstElement *bin, *src = NULL; + + fail_unless (g_list_length (GST_BIN_CHILDREN (nle_source)), 1); + bin = GST_BIN_CHILDREN (nle_source)->data; + fail_unless (GST_IS_BIN (bin)); + + for (tmp = GST_BIN_CHILDREN (bin); src == NULL && tmp; tmp = tmp->next) { + if (g_strrstr (GST_ELEMENT_NAME (tmp->data), "audiotestsrc")) + src = tmp->data; + } + fail_unless (src); + return src; +} + +enum +{ + NLE_PREV_SRC, + NLE_POST_SRC, + NLE_SOURCE_SRC, + NLE_OPER_SRC, + NLE_OPER_SINK, + NLE_IDENTITY_SRC, + PREV_SRC, + POST_SRC, + SOURCE_SRC, + PITCH_SRC, + PITCH_SINK, + IDENTITY_SRC, + SINK_SINK, + NUM_DATA +}; + +static PadEventData ** +_setup_test (GstElement * pipeline, gdouble rate) +{ + GstElement *sink, *pitch, *src, *prev, *post, *identity; + GstElement *comp, *nle_source, *nle_prev, *nle_post, *nle_oper, *nle_identity; + gboolean ret; + gchar *suffix; + PadEventData **data = g_new0 (PadEventData *, NUM_DATA); + + /* composition */ comp = gst_element_factory_make_or_warn ("nlecomposition", "test_composition"); - gst_element_set_state (comp, GST_STATE_READY); + /* sink */ sink = gst_element_factory_make_or_warn ("fakesink", "sink"); gst_bin_add_many (GST_BIN (pipeline), comp, sink, NULL); gst_element_link (comp, sink); - /* - source1 - Start : 0s - Duration : 2s - Priority : 2 - */ + /* sources */ + nle_source = + audiotest_bin_src ("nle_source", 3 * GST_SECOND, 4 * GST_SECOND, 3, + FALSE); + g_object_set (nle_source, "inpoint", 7 * GST_SECOND, NULL); + src = _get_source (nle_source); + g_object_set (src, "name", "middle-source", NULL); - source1 = audiotest_bin_src ("source1", 0, 2 * GST_SECOND, 2, 2); + nle_prev = + audiotest_bin_src ("nle_previous", 0 * GST_SECOND, 3 * GST_SECOND, 2, + FALSE); + g_object_set (nle_prev, "inpoint", 99 * GST_SECOND, NULL); + prev = _get_source (nle_prev); + g_object_set (src, "name", "previous-source", NULL); - /* - def (default source) - Priority = G_MAXUINT32 - */ - def = - audiotest_bin_src ("default", 0 * GST_SECOND, 0 * GST_SECOND, G_MAXUINT32, - 1); - g_object_set (def, "expandable", TRUE, NULL); + nle_post = + audiotest_bin_src ("post", 7 * GST_SECOND, 5 * GST_SECOND, 2, FALSE); + g_object_set (nle_post, "inpoint", 20 * GST_SECOND, NULL); + post = _get_source (nle_post); + g_object_set (src, "name", "post-source", NULL); - /* Operation */ - oper = new_operation ("oper", "identity", 0, 2 * GST_SECOND, 1); - fail_if (oper == NULL); - ((NleObject *) oper)->media_duration_factor = 2.0; + /* Operation, must share the same start and duration as the upstream + * source */ + nle_oper = + new_operation ("nle_oper", "pitch", 3 * GST_SECOND, 4 * GST_SECOND, 2); + fail_unless (g_list_length (GST_BIN_CHILDREN (nle_oper)) == 1); + pitch = GST_ELEMENT (GST_BIN_CHILDREN (nle_oper)->data); + g_object_set (pitch, "rate", rate, NULL); - ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); - ASSERT_OBJECT_REFCOUNT (def, "default", 1); - ASSERT_OBJECT_REFCOUNT (oper, "oper", 1); + /* cover with an identity operation + * rate effect has lower priority, so we don't need the same start or + * duration */ + nle_identity = + new_operation ("nle_identity", "identity", 0, 12 * GST_SECOND, 1); + g_object_set (nle_identity, "inpoint", 5 * GST_SECOND, NULL); + fail_unless (g_list_length (GST_BIN_CHILDREN (nle_oper)) == 1); + identity = GST_ELEMENT (GST_BIN_CHILDREN (nle_identity)->data); - /* Add source 1 */ - - nle_composition_add (GST_BIN (comp), source1); - nle_composition_add (GST_BIN (comp), def); - nle_composition_add (GST_BIN (comp), oper); + nle_composition_add (GST_BIN (comp), nle_source); + nle_composition_add (GST_BIN (comp), nle_prev); + nle_composition_add (GST_BIN (comp), nle_post); + nle_composition_add (GST_BIN (comp), nle_oper); + nle_composition_add (GST_BIN (comp), nle_identity); + ret = FALSE; commit_and_wait (comp, &ret); - check_start_stop_duration (source1, 0, 2 * GST_SECOND, 2 * GST_SECOND); - check_start_stop_duration (comp, 0, 2 * GST_SECOND, 2 * GST_SECOND); - check_start_stop_duration (oper, 0, 2 * GST_SECOND, 2 * GST_SECOND); + fail_unless (ret); - /* Define expected segments */ - segments = g_list_append (segments, - segment_new (1.0, GST_FORMAT_TIME, 0 * GST_SECOND, 4.0 * GST_SECOND, 0)); - collect = g_new0 (CollectStructure, 1); - collect->comp = comp; - collect->sink = sink; + check_start_stop_duration (nle_source, 3 * GST_SECOND, 7 * GST_SECOND, + 4 * GST_SECOND); + check_start_stop_duration (nle_oper, 3 * GST_SECOND, 7 * GST_SECOND, + 4 * GST_SECOND); + check_start_stop_duration (nle_prev, 0, 3 * GST_SECOND, 3 * GST_SECOND); + check_start_stop_duration (nle_post, 7 * GST_SECOND, 12 * GST_SECOND, + 5 * GST_SECOND); + check_start_stop_duration (nle_identity, 0, 12 * GST_SECOND, 12 * GST_SECOND); + check_start_stop_duration (comp, 0, 12 * GST_SECOND, 12 * GST_SECOND); - collect->expected_segments = segments; - collect->keep_expected_segments = FALSE; + /* create data */ + suffix = g_strdup_printf ("rate=%g", rate); - sinkpad = gst_element_get_static_pad (sink, "sink"); - gst_pad_add_probe (sinkpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, - (GstPadProbeCallback) sinkpad_probe, collect, NULL); - gst_object_unref (sinkpad); + /* source */ + data[NLE_SOURCE_SRC] = _pad_event_data_new (nle_source, "src", suffix); + data[NLE_PREV_SRC] = _pad_event_data_new (nle_prev, "src", suffix); + data[NLE_POST_SRC] = _pad_event_data_new (nle_post, "src", suffix); + data[SOURCE_SRC] = _pad_event_data_new (src, "src", suffix); + data[PREV_SRC] = _pad_event_data_new (prev, "src", suffix); + data[POST_SRC] = _pad_event_data_new (post, "src", suffix); - bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + /* rate operation */ + data[NLE_OPER_SRC] = _pad_event_data_new (nle_oper, "src", suffix); + data[NLE_OPER_SINK] = _pad_event_data_new (nle_oper, "sink", suffix); + data[PITCH_SRC] = _pad_event_data_new (pitch, "src", suffix); + data[PITCH_SINK] = _pad_event_data_new (pitch, "sink", suffix); - GST_DEBUG ("Setting pipeline to PAUSED"); - ASSERT_OBJECT_REFCOUNT (source1, "source1", 1); + /* identity: only care about the source pads */ + data[NLE_IDENTITY_SRC] = _pad_event_data_new (nle_identity, "src", suffix); + data[IDENTITY_SRC] = _pad_event_data_new (identity, "src", suffix); - fail_if (gst_element_set_state (GST_ELEMENT (pipeline), - GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE); + /* sink */ + data[SINK_SINK] = _pad_event_data_new (sink, "sink", suffix); - GST_DEBUG ("Let's poll the bus"); + g_free (suffix); - carry_on = TRUE; - while (carry_on) { - message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); - if (message) { - switch (GST_MESSAGE_TYPE (message)) { - case GST_MESSAGE_ASYNC_DONE: - { - carry_on = FALSE; - GST_DEBUG ("Pipeline reached PAUSED, stopping polling"); - break; + return data; +} + + +GST_START_TEST (test_tempochange_play) +{ + GstElement *pipeline; + GstBus *bus; + GstMessage *message; + gboolean carry_on; + PadEventData **data; + gdouble rates[3] = { 0.5, 4.0, 1.0 }; + guint i, j; + + for (i = 0; i < G_N_ELEMENTS (rates); i++) { + gdouble rate = rates[i]; + GST_DEBUG ("rate = %g", rate); + + pipeline = gst_pipeline_new ("test_pipeline"); + + data = _setup_test (pipeline, rate); + + /* initial seek */ + _EXPECT_SEEK_SEGMENT (data[SINK_SINK], 0, 3 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_IDENTITY_SRC], 0, 3 * GST_SECOND); + /* nleobject will convert the seek by removing start and adding inpoint */ + _EXPECT_SEEK_SEGMENT (data[IDENTITY_SRC], 5 * GST_SECOND, 8 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_PREV_SRC], 0, 3 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[PREV_SRC], 99 * GST_SECOND, 102 * GST_SECOND); + + /* rate-stack seek */ + _EXPECT_SEEK_SEGMENT (data[SINK_SINK], 3 * GST_SECOND, 7 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_IDENTITY_SRC], 3 * GST_SECOND, + 7 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[IDENTITY_SRC], 8 * GST_SECOND, 12 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_OPER_SRC], 3 * GST_SECOND, 7 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[PITCH_SRC], 0, 4 * GST_SECOND); + /* pitch element will change the stop time, e.g. if rate=2.0, then we + * want to use up twice as much source, so the stop time doubles */ + _EXPECT_SEEK_SEGMENT (data[PITCH_SINK], 0, rate * 4 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_OPER_SINK], 3 * GST_SECOND, + (GstClockTime) (rate * 4 * GST_SECOND) + 3 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_SOURCE_SRC], 3 * GST_SECOND, + (GstClockTime) (rate * 4 * GST_SECOND) + 3 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[SOURCE_SRC], 7 * GST_SECOND, + (GstClockTime) (rate * 4 * GST_SECOND) + 7 * GST_SECOND); + + /* final part only involves post source */ + _EXPECT_SEEK_SEGMENT (data[SINK_SINK], 7 * GST_SECOND, 12 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_IDENTITY_SRC], 7 * GST_SECOND, + 12 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[IDENTITY_SRC], 12 * GST_SECOND, 17 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_POST_SRC], 7 * GST_SECOND, 12 * GST_SECOND); + /* nleobject will convert the seek by removing start and adding + * inpoint */ + _EXPECT_SEEK_SEGMENT (data[POST_SRC], 20 * GST_SECOND, 25 * GST_SECOND); + + /* expect 1 EOS from each, apart from identity, which will get 3 since + * part of 3 stacks */ + for (j = 0; j < NUM_DATA; j++) + data[j]->expect_num_eos = 1; + + data[IDENTITY_SRC]->expect_num_eos = 3; + data[NLE_IDENTITY_SRC]->expect_num_eos = 3; + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + GST_DEBUG ("Setting pipeline to PLAYING"); + fail_if (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE); + + GST_DEBUG ("Let's poll the bus"); + + carry_on = TRUE; + while (carry_on) { + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); + if (message) { + switch (GST_MESSAGE_TYPE (message)) { + case GST_MESSAGE_EOS: + if (message->src == GST_OBJECT (pipeline)) { + GST_DEBUG ("Setting pipeline to NULL"); + fail_unless (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS); + carry_on = FALSE; + } + break; + case GST_MESSAGE_ERROR: + fail_error_message (message); + break; + default: + break; } - case GST_MESSAGE_EOS: - { - GST_WARNING ("Saw EOS"); - - fail_if (TRUE); - } - case GST_MESSAGE_ERROR: - fail_error_message (message); - default: - break; + gst_message_unref (message); } - gst_mini_object_unref (GST_MINI_OBJECT (message)); } + + for (j = 0; j < NUM_DATA; j++) + _pad_event_data_check_received (data[j]); + + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + g_free (data); } +} - fail_unless_equals_float (((NleObject *) source1)->media_duration_factor, - 1.0f); - fail_unless_equals_float (((NleObject *) - source1)->recursive_media_duration_factor, 2.0f); - fail_unless_equals_float (((NleObject *) oper)->media_duration_factor, 2.0f); - fail_unless_equals_float (((NleObject *) - oper)->recursive_media_duration_factor, 2.0f); +GST_END_TEST; - GST_DEBUG ("Setting pipeline to READY"); +#define _WAIT_UNTIL_ASYNC_DONE \ +{ \ + GST_DEBUG ("Let's poll the bus"); \ + carry_on = TRUE; \ + while (carry_on) { \ + message = gst_bus_poll (bus, GST_MESSAGE_ANY, GST_SECOND / 10); \ + if (message) { \ + switch (GST_MESSAGE_TYPE (message)) { \ + case GST_MESSAGE_EOS: \ + fail_if (TRUE, "Received EOS"); \ + break; \ + case GST_MESSAGE_ERROR: \ + fail_error_message (message); \ + break; \ + case GST_MESSAGE_ASYNC_DONE: \ + carry_on = FALSE; \ + break; \ + default: \ + break; \ + } \ + gst_message_unref (message); \ + } \ + } \ +} - fail_if (gst_element_set_state (GST_ELEMENT (pipeline), - GST_STATE_READY) == GST_STATE_CHANGE_FAILURE); +GST_START_TEST (test_tempochange_seek) +{ + GstElement *pipeline; + GstBus *bus; + GstMessage *message; + gboolean carry_on; + PadEventData **data; + gdouble rates[3] = { 2.0, 0.25, 1.0 }; + guint i, j; + GstClockTime offset = 0.1 * GST_SECOND; - fail_if (collect->expected_segments != NULL); + for (i = 0; i < G_N_ELEMENTS (rates); i++) { + gdouble rate = rates[i]; + GST_DEBUG ("rate = %g", rate); - fail_if (gst_element_set_state (GST_ELEMENT (pipeline), - GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE); + pipeline = gst_pipeline_new ("test_pipeline"); - ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); - gst_object_unref (pipeline); - ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); - gst_object_unref (bus); + data = _setup_test (pipeline, rate); - collect_free (collect); + /* initial seek from the pause */ + _EXPECT_SEEK_SEGMENT (data[SINK_SINK], 0, 3 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_IDENTITY_SRC], 0, 3 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[IDENTITY_SRC], 5 * GST_SECOND, 8 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_PREV_SRC], 0, 3 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[PREV_SRC], 99 * GST_SECOND, 102 * GST_SECOND); + + GST_DEBUG ("Setting pipeline to PAUSED"); + fail_unless (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_PAUSED) == GST_STATE_CHANGE_ASYNC); + + bus = gst_element_get_bus (GST_ELEMENT (pipeline)); + + _WAIT_UNTIL_ASYNC_DONE; + + for (j = 0; j < NUM_DATA; j++) + _pad_event_data_check_received (data[j]); + + /* first seek for just after the start of the rate effect */ + /* NOTE: neither prev nor post should receive anything */ + + /* sink will receive two seeks: one that initiates the pre-roll, and + * then the seek with the stop set */ + /* expect no segment for the first seek */ + _pad_event_data_add_expect_seek (data[SINK_SINK], 3 * GST_SECOND + offset, + GST_CLOCK_TIME_NONE); + _EXPECT_SEEK_SEGMENT (data[SINK_SINK], 3 * GST_SECOND + offset, + 7 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_IDENTITY_SRC], 3 * GST_SECOND + offset, + 7 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[IDENTITY_SRC], 8 * GST_SECOND + offset, + 12 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_OPER_SRC], 3 * GST_SECOND + offset, + 7 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[PITCH_SRC], offset, 4 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[PITCH_SINK], rate * offset, + rate * 4 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_OPER_SINK], + 3 * GST_SECOND + (GstClockTime) (rate * offset), + 3 * GST_SECOND + (GstClockTime) (rate * 4 * GST_SECOND)); + _EXPECT_SEEK_SEGMENT (data[NLE_SOURCE_SRC], + 3 * GST_SECOND + (GstClockTime) (rate * offset), + 3 * GST_SECOND + (GstClockTime) (rate * 4 * GST_SECOND)); + _EXPECT_SEEK_SEGMENT (data[SOURCE_SRC], + 7 * GST_SECOND + (GstClockTime) (rate * offset), + 7 * GST_SECOND + (GstClockTime) (rate * 4 * GST_SECOND)); + + /* perform seek */ + fail_unless (gst_element_seek_simple (pipeline, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, 3 * GST_SECOND + offset)); + + _WAIT_UNTIL_ASYNC_DONE; + + for (j = 0; j < NUM_DATA; j++) + _pad_event_data_check_received (data[j]); + + /* now seek to just before the end */ + _pad_event_data_add_expect_seek (data[SINK_SINK], 7 * GST_SECOND - offset, + GST_CLOCK_TIME_NONE); + _EXPECT_SEEK_SEGMENT (data[SINK_SINK], 7 * GST_SECOND - offset, + 7 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_IDENTITY_SRC], 7 * GST_SECOND - offset, + 7 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[IDENTITY_SRC], 12 * GST_SECOND - offset, + 12 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_OPER_SRC], 7 * GST_SECOND - offset, + 7 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[PITCH_SRC], 4 * GST_SECOND - offset, + 4 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[PITCH_SINK], + rate * (4 * GST_SECOND) - rate * offset, rate * 4 * GST_SECOND); + _EXPECT_SEEK_SEGMENT (data[NLE_OPER_SINK], + 3 * GST_SECOND + (GstClockTime) (rate * (4 * GST_SECOND - offset)), + 3 * GST_SECOND + (GstClockTime) (rate * 4 * GST_SECOND)); + _EXPECT_SEEK_SEGMENT (data[NLE_SOURCE_SRC], + 3 * GST_SECOND + (GstClockTime) (rate * (4 * GST_SECOND - offset)), + 3 * GST_SECOND + (GstClockTime) (rate * 4 * GST_SECOND)); + _EXPECT_SEEK_SEGMENT (data[SOURCE_SRC], + 7 * GST_SECOND + (GstClockTime) (rate * (4 * GST_SECOND - offset)), + 7 * GST_SECOND + (GstClockTime) (rate * 4 * GST_SECOND)); + + /* perform seek */ + fail_unless (gst_element_seek_simple (pipeline, GST_FORMAT_TIME, + GST_SEEK_FLAG_FLUSH, 7 * GST_SECOND - offset)); + + _WAIT_UNTIL_ASYNC_DONE; + + for (j = 0; j < NUM_DATA; j++) + _pad_event_data_check_received (data[j]); + + GST_DEBUG ("Setting pipeline to NULL"); + fail_unless (gst_element_set_state (GST_ELEMENT (pipeline), + GST_STATE_NULL) == GST_STATE_CHANGE_SUCCESS); + + ASSERT_OBJECT_REFCOUNT_BETWEEN (pipeline, "main pipeline", 1, 2); + gst_object_unref (pipeline); + ASSERT_OBJECT_REFCOUNT_BETWEEN (bus, "main bus", 1, 2); + gst_object_unref (bus); + g_free (data); + } } GST_END_TEST; @@ -171,7 +641,8 @@ gnonlin_suite (void) ges_init (); suite_add_tcase (s, tc_chain); - tcase_add_test (tc_chain, test_tempochange); + tcase_add_test (tc_chain, test_tempochange_play); + tcase_add_test (tc_chain, test_tempochange_seek); return s; }