* 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",
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,
* @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.
+ *
+ * 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.
*
- * Returns: TRUE if @objecttime was within the limits of the @object start/stop time,
- * FALSE otherwise
+ * 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,
"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 ((otime < object->start))) {
- GST_DEBUG_OBJECT (object, "ObjectTime is before start");
- *mtime = (object->inpoint == GST_CLOCK_TIME_NONE) ? 0 : object->inpoint;
- return FALSE;
+ 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;
}
- 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;
+ /* 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");
+ 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. */
+
+ /* 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;
}
/**
* @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.
+ *
+ * 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.
*
- * Returns: TRUE if @objecttime was within the limits of the @object media start/stop time,
- * FALSE otherwise
+ * 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
"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));
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);
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);
#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;
+
+#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);
+}
- pipeline = gst_pipeline_new ("test_pipeline");
+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
- */
-
- source1 = audiotest_bin_src ("source1", 0, 2 * GST_SECOND, 2, 2);
-
- /*
- 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);
-
- /* Operation */
- oper = new_operation ("oper", "identity", 0, 2 * GST_SECOND, 1);
- fail_if (oper == NULL);
- ((NleObject *) oper)->media_duration_factor = 2.0;
-
- ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
- ASSERT_OBJECT_REFCOUNT (def, "default", 1);
- ASSERT_OBJECT_REFCOUNT (oper, "oper", 1);
-
- /* Add source 1 */
-
- nle_composition_add (GST_BIN (comp), source1);
- nle_composition_add (GST_BIN (comp), def);
- nle_composition_add (GST_BIN (comp), oper);
+ /* 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);
+
+ 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);
+
+ 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, 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);
+
+ /* 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);
+
+ 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);
-
- /* 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;
-
- collect->expected_segments = segments;
- collect->keep_expected_segments = FALSE;
-
- 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);
-
- bus = gst_element_get_bus (GST_ELEMENT (pipeline));
-
- GST_DEBUG ("Setting pipeline to PAUSED");
- ASSERT_OBJECT_REFCOUNT (source1, "source1", 1);
-
- fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
- GST_STATE_PAUSED) == 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_ASYNC_DONE:
- {
- carry_on = FALSE;
- GST_DEBUG ("Pipeline reached PAUSED, stopping polling");
- break;
- }
- case GST_MESSAGE_EOS:
- {
- GST_WARNING ("Saw EOS");
+ fail_unless (ret);
+
+ 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);
+
+ /* create data */
+ suffix = g_strdup_printf ("rate=%g", rate);
+
+ /* 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);
+
+ /* 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);
+
+ /* 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);
+
+ /* sink */
+ data[SINK_SINK] = _pad_event_data_new (sink, "sink", suffix);
+
+ g_free (suffix);
+
+ return data;
+}
- fail_if (TRUE);
+
+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_ERROR:
- fail_error_message (message);
- default:
- break;
+ gst_message_unref (message);
}
- gst_mini_object_unref (GST_MINI_OBJECT (message));
}
- }
-
- 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_DEBUG ("Setting pipeline to READY");
+ for (j = 0; j < NUM_DATA; j++)
+ _pad_event_data_check_received (data[j]);
- fail_if (gst_element_set_state (GST_ELEMENT (pipeline),
- GST_STATE_READY) == GST_STATE_CHANGE_FAILURE);
-
- fail_if (collect->expected_segments != NULL);
+ 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_if (gst_element_set_state (GST_ELEMENT (pipeline),
- GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE);
+GST_END_TEST;
- 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);
+#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); \
+ } \
+ } \
+}
- collect_free (collect);
+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;
+
+ 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 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;
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;
}