summaryrefslogtreecommitdiff
path: root/subprojects/gstreamer/tests/check/elements/fakesink.c
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/gstreamer/tests/check/elements/fakesink.c')
-rw-r--r--subprojects/gstreamer/tests/check/elements/fakesink.c1200
1 files changed, 1200 insertions, 0 deletions
diff --git a/subprojects/gstreamer/tests/check/elements/fakesink.c b/subprojects/gstreamer/tests/check/elements/fakesink.c
new file mode 100644
index 0000000000..352031ab62
--- /dev/null
+++ b/subprojects/gstreamer/tests/check/elements/fakesink.c
@@ -0,0 +1,1200 @@
+/* GStreamer
+ *
+ * unit test for fakesink
+ *
+ * Copyright (C) <2005> Thomas Vander Stichele <thomas at apestaart dot org>
+ * <2007> Wim Taymans <wim@fluendo.com>
+ * <2009> Tim-Philipp Müller <tim centricular net>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gst/base/gstpushsrc.h>
+#include <gst/check/gstcheck.h>
+
+typedef struct
+{
+ GstPad *pad;
+ GstBuffer *buffer;
+ GThread *thread;
+ GstFlowReturn ret;
+} ChainData;
+
+static gpointer
+chain_async_buffer (gpointer data)
+{
+ ChainData *chain_data = (ChainData *) data;
+
+ chain_data->ret = gst_pad_chain (chain_data->pad, chain_data->buffer);
+
+ return chain_data;
+}
+
+static ChainData *
+chain_async (GstPad * pad, GstBuffer * buffer)
+{
+ GThread *thread;
+ ChainData *chain_data;
+ GError *error = NULL;
+
+ chain_data = g_new (ChainData, 1);
+ chain_data->pad = pad;
+ chain_data->buffer = buffer;
+ chain_data->ret = GST_FLOW_ERROR;
+
+ thread =
+ g_thread_try_new ("gst-check", chain_async_buffer, chain_data, &error);
+ if (error != NULL) {
+ g_warning ("could not create thread reason: %s", error->message);
+ g_free (chain_data);
+ return NULL;
+ }
+ chain_data->thread = thread;
+
+ return chain_data;
+}
+
+static GstFlowReturn
+chain_async_return (ChainData * data)
+{
+ GstFlowReturn ret;
+
+ g_thread_join (data->thread);
+ ret = data->ret;
+ g_free (data);
+
+ return ret;
+}
+
+GST_START_TEST (test_clipping)
+{
+ GstElement *sink;
+ GstPad *sinkpad;
+ GstStateChangeReturn ret;
+
+ /* create sink */
+ sink = gst_element_factory_make ("fakesink", "sink");
+ fail_if (sink == NULL);
+
+ sinkpad = gst_element_get_static_pad (sink, "sink");
+ fail_if (sinkpad == NULL);
+
+ /* make element ready to accept data */
+ ret = gst_element_set_state (sink, GST_STATE_PAUSED);
+ fail_unless (ret == GST_STATE_CHANGE_ASYNC);
+
+ gst_pad_send_event (sinkpad, gst_event_new_stream_start ("test"));
+
+ /* send segment */
+ {
+ GstEvent *event;
+ GstSegment segment;
+ gboolean eret;
+
+ GST_DEBUG ("sending segment");
+ gst_segment_init (&segment, GST_FORMAT_TIME);
+ segment.start = 1 * GST_SECOND;
+ segment.stop = 5 * GST_SECOND;
+ segment.time = 1 * GST_SECOND;
+
+ event = gst_event_new_segment (&segment);
+
+ eret = gst_pad_send_event (sinkpad, event);
+ fail_if (eret == FALSE);
+ }
+
+ /* new segment should not have finished preroll */
+ ret = gst_element_get_state (sink, NULL, NULL, 0);
+ fail_unless (ret == GST_STATE_CHANGE_ASYNC);
+
+ /* send buffer that should be dropped */
+ {
+ GstBuffer *buffer;
+ GstFlowReturn fret;
+
+ buffer = gst_buffer_new ();
+ GST_BUFFER_TIMESTAMP (buffer) = 0;
+ GST_BUFFER_DURATION (buffer) = 1 * GST_MSECOND;
+
+ GST_DEBUG ("sending buffer to be dropped");
+ fret = gst_pad_chain (sinkpad, buffer);
+ fail_if (fret != GST_FLOW_OK);
+ }
+ /* dropped buffer should not have finished preroll */
+ ret = gst_element_get_state (sink, NULL, NULL, 0);
+ fail_unless (ret == GST_STATE_CHANGE_ASYNC);
+
+ /* send buffer that should be dropped */
+ {
+ GstBuffer *buffer;
+ GstFlowReturn fret;
+
+ buffer = gst_buffer_new ();
+ GST_BUFFER_TIMESTAMP (buffer) = 5 * GST_SECOND;
+ GST_BUFFER_DURATION (buffer) = 1 * GST_MSECOND;
+
+ GST_DEBUG ("sending buffer to be dropped");
+ fret = gst_pad_chain (sinkpad, buffer);
+ fail_if (fret != GST_FLOW_OK);
+ }
+ /* dropped buffer should not have finished preroll */
+ ret = gst_element_get_state (sink, NULL, NULL, 0);
+ fail_unless (ret == GST_STATE_CHANGE_ASYNC);
+
+ /* send buffer that should block and finish preroll */
+ {
+ GstBuffer *buffer;
+ GstFlowReturn fret;
+ ChainData *data;
+ GstState current, pending;
+
+ buffer = gst_buffer_new ();
+ GST_BUFFER_TIMESTAMP (buffer) = 1 * GST_SECOND;
+ GST_BUFFER_DURATION (buffer) = 1 * GST_MSECOND;
+
+ GST_DEBUG ("sending buffer to finish preroll");
+ data = chain_async (sinkpad, buffer);
+ fail_if (data == NULL);
+
+ /* state should now eventually change to PAUSED */
+ ret = gst_element_get_state (sink, &current, &pending, GST_CLOCK_TIME_NONE);
+ fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
+ fail_unless (current == GST_STATE_PAUSED);
+ fail_unless (pending == GST_STATE_VOID_PENDING);
+
+ /* playing should render the buffer */
+ ret = gst_element_set_state (sink, GST_STATE_PLAYING);
+ fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
+
+ /* and we should get a success return value */
+ fret = chain_async_return (data);
+ fail_if (fret != GST_FLOW_OK);
+ }
+
+ /* send some buffer that will be dropped or clipped, this can
+ * only be observed in the debug log. */
+ {
+ GstBuffer *buffer;
+ GstFlowReturn fret;
+
+ buffer = gst_buffer_new ();
+ GST_BUFFER_TIMESTAMP (buffer) = 6 * GST_SECOND;
+ GST_BUFFER_DURATION (buffer) = 1 * GST_MSECOND;
+
+ /* should be dropped */
+ GST_DEBUG ("sending buffer to drop");
+ fret = gst_pad_chain (sinkpad, buffer);
+ fail_if (fret != GST_FLOW_OK);
+
+ buffer = gst_buffer_new ();
+ GST_BUFFER_TIMESTAMP (buffer) = 0 * GST_SECOND;
+ GST_BUFFER_DURATION (buffer) = 2 * GST_SECOND;
+
+ /* should be clipped */
+ GST_DEBUG ("sending buffer to clip");
+ fret = gst_pad_chain (sinkpad, buffer);
+ fail_if (fret != GST_FLOW_OK);
+
+ buffer = gst_buffer_new ();
+ GST_BUFFER_TIMESTAMP (buffer) = 4 * GST_SECOND;
+ GST_BUFFER_DURATION (buffer) = 2 * GST_SECOND;
+
+ /* should be clipped */
+ GST_DEBUG ("sending buffer to clip");
+ fret = gst_pad_chain (sinkpad, buffer);
+ fail_if (fret != GST_FLOW_OK);
+ }
+
+ gst_element_set_state (sink, GST_STATE_NULL);
+ gst_element_get_state (sink, NULL, NULL, GST_CLOCK_TIME_NONE);
+ gst_object_unref (sinkpad);
+ gst_object_unref (sink);
+}
+
+GST_END_TEST;
+
+static gint num_preroll = 0;
+
+static void
+preroll_count (GstElement * sink)
+{
+ num_preroll++;
+ GST_DEBUG ("got preroll handoff %d", num_preroll);
+}
+
+GST_START_TEST (test_preroll_sync)
+{
+ GstElement *pipeline, *sink;
+ GstPad *sinkpad;
+ GstStateChangeReturn ret;
+
+ /* create sink */
+ pipeline = gst_pipeline_new ("pipeline");
+ fail_if (pipeline == NULL);
+
+ sink = gst_element_factory_make ("fakesink", "sink");
+ fail_if (sink == NULL);
+ g_object_set (G_OBJECT (sink), "sync", TRUE, NULL);
+ g_object_set (G_OBJECT (sink), "signal-handoffs", TRUE, NULL);
+ g_signal_connect (sink, "preroll-handoff", G_CALLBACK (preroll_count), NULL);
+
+ fail_unless (num_preroll == 0);
+
+ gst_bin_add (GST_BIN (pipeline), sink);
+
+ sinkpad = gst_element_get_static_pad (sink, "sink");
+ fail_if (sinkpad == NULL);
+
+ /* make pipeline and element ready to accept data */
+ ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+ fail_unless (ret == GST_STATE_CHANGE_ASYNC);
+
+ gst_pad_send_event (sinkpad, gst_event_new_stream_start ("test"));
+
+ /* send segment */
+ {
+ GstEvent *event;
+ GstSegment segment;
+ gboolean eret;
+
+ GST_DEBUG ("sending segment");
+ gst_segment_init (&segment, GST_FORMAT_TIME);
+ segment.start = 0 * GST_SECOND;
+ segment.stop = 102 * GST_SECOND;
+ segment.time = 0 * GST_SECOND;
+
+ event = gst_event_new_segment (&segment);
+ eret = gst_pad_send_event (sinkpad, event);
+ fail_if (eret == FALSE);
+ }
+
+ /* send buffer that should block and finish preroll */
+ {
+ GstBuffer *buffer;
+ GstFlowReturn fret;
+ ChainData *data;
+ GstState current, pending;
+
+ buffer = gst_buffer_new ();
+ GST_BUFFER_TIMESTAMP (buffer) = 1 * GST_SECOND;
+ GST_BUFFER_DURATION (buffer) = 1 * GST_SECOND;
+
+ GST_DEBUG ("sending buffer to finish preroll");
+ data = chain_async (sinkpad, buffer);
+ fail_if (data == NULL);
+
+ /* state should now eventually change to PAUSED */
+ ret =
+ gst_element_get_state (pipeline, &current, &pending,
+ GST_CLOCK_TIME_NONE);
+ fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
+ fail_unless (current == GST_STATE_PAUSED);
+ fail_unless (pending == GST_STATE_VOID_PENDING);
+
+ fail_unless (num_preroll == 1);
+
+ /* playing should render the buffer */
+ ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+ fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
+
+ /* and we should get a success return value */
+ fret = chain_async_return (data);
+ fail_if (fret != GST_FLOW_OK);
+
+ /* now we are playing no new preroll was done */
+ fail_unless (num_preroll == 1);
+
+ buffer = gst_buffer_new ();
+ /* far in the future to make sure we block */
+ GST_BUFFER_TIMESTAMP (buffer) = 100 * GST_SECOND;
+ GST_BUFFER_DURATION (buffer) = 100 * GST_SECOND;
+ data = chain_async (sinkpad, buffer);
+ fail_if (data == NULL);
+
+ g_usleep (1000000);
+
+ /* pause again. Since the buffer has a humongous timestamp we likely
+ * interrupt the clock_wait and we should preroll on this buffer again */
+ ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+ fail_unless (ret == GST_STATE_CHANGE_ASYNC);
+
+ ret =
+ gst_element_get_state (pipeline, &current, &pending,
+ GST_CLOCK_TIME_NONE);
+ fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
+ fail_unless (current == GST_STATE_PAUSED);
+ fail_unless (pending == GST_STATE_VOID_PENDING);
+
+ fail_unless (num_preroll == 2);
+
+ /* shutdown */
+ ret = gst_element_set_state (pipeline, GST_STATE_READY);
+ fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
+
+ /* should be wrong state now */
+ fret = chain_async_return (data);
+ fail_if (fret != GST_FLOW_FLUSHING);
+ }
+ gst_element_set_state (pipeline, GST_STATE_NULL);
+ gst_element_get_state (pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
+ gst_object_unref (sinkpad);
+ gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+/* after EOS, we refuse everything */
+GST_START_TEST (test_eos)
+{
+ GstElement *pipeline, *sink;
+ GstPad *sinkpad;
+ GstStateChangeReturn ret;
+ GstMessage *message;
+ GstBus *bus;
+
+ /* create sink */
+ pipeline = gst_pipeline_new ("pipeline");
+ fail_if (pipeline == NULL);
+
+ bus = gst_pipeline_get_bus (GST_PIPELINE_CAST (pipeline));
+ fail_if (bus == NULL);
+
+ sink = gst_element_factory_make ("fakesink", "sink");
+ fail_if (sink == NULL);
+ g_object_set (G_OBJECT (sink), "sync", TRUE, NULL);
+
+ gst_bin_add (GST_BIN (pipeline), sink);
+
+ sinkpad = gst_element_get_static_pad (sink, "sink");
+ fail_if (sinkpad == NULL);
+
+ /* make pipeline and element ready to accept data */
+ ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+ fail_unless (ret == GST_STATE_CHANGE_ASYNC);
+
+ /* send EOS, this should work fine */
+ {
+ GstEvent *eos;
+ gboolean eret;
+
+ GST_DEBUG ("sending EOS");
+ eos = gst_event_new_eos ();
+
+ eret = gst_pad_send_event (sinkpad, eos);
+ fail_if (eret == FALSE);
+ }
+
+ /* wait for preroll */
+ gst_element_get_state (pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
+
+ /* EOS should be on the bus at some point */
+ while (TRUE) {
+ GstMessageType type;
+
+ /* blocking wait for messages */
+ message = gst_bus_timed_pop (bus, GST_CLOCK_TIME_NONE);
+ type = GST_MESSAGE_TYPE (message);
+ gst_message_unref (message);
+
+ GST_DEBUG ("got message %s", gst_message_type_get_name (type));
+
+ if (type == GST_MESSAGE_EOS)
+ break;
+ }
+ gst_object_unref (bus);
+
+ /* send another EOS, this should fail */
+ {
+ GstEvent *eos;
+ gboolean eret;
+
+ GST_DEBUG ("sending second EOS");
+ eos = gst_event_new_eos ();
+
+ eret = gst_pad_send_event (sinkpad, eos);
+ fail_if (eret == TRUE);
+ }
+
+ /* send segment, this should fail */
+ {
+ GstEvent *event;
+ GstSegment segment;
+ gboolean eret;
+
+ GST_DEBUG ("sending segment");
+ gst_segment_init (&segment, GST_FORMAT_TIME);
+ segment.start = 0 * GST_SECOND;
+ segment.stop = 2 * GST_SECOND;
+ segment.time = 0 * GST_SECOND;
+ event = gst_event_new_segment (&segment);
+
+ eret = gst_pad_send_event (sinkpad, event);
+ fail_if (eret == TRUE);
+ }
+
+ /* send buffer that should fail after EOS */
+ {
+ GstBuffer *buffer;
+ GstFlowReturn fret;
+
+ buffer = gst_buffer_new ();
+ GST_BUFFER_TIMESTAMP (buffer) = 1 * GST_SECOND;
+ GST_BUFFER_DURATION (buffer) = 1 * GST_SECOND;
+
+ GST_DEBUG ("sending buffer");
+
+ /* buffer after EOS is not EOS */
+ fret = gst_pad_chain (sinkpad, buffer);
+ fail_unless (fret == GST_FLOW_EOS);
+ }
+
+ /* flush, EOS state is flushed again. */
+ {
+ GstEvent *event;
+ gboolean eret;
+
+ GST_DEBUG ("sending FLUSH_START");
+ event = gst_event_new_flush_start ();
+ eret = gst_pad_send_event (sinkpad, event);
+ fail_unless (eret == TRUE);
+
+ GST_DEBUG ("sending FLUSH_STOP");
+ event = gst_event_new_flush_stop (TRUE);
+ eret = gst_pad_send_event (sinkpad, event);
+ fail_unless (eret == TRUE);
+ }
+
+ gst_pad_send_event (sinkpad, gst_event_new_stream_start ("test"));
+
+ /* send segment, this should now work again */
+ {
+ GstEvent *event;
+ GstSegment segment;
+ gboolean eret;
+
+ GST_DEBUG ("sending segment");
+ gst_segment_init (&segment, GST_FORMAT_TIME);
+ segment.start = 0 * GST_SECOND;
+ segment.stop = 2 * GST_SECOND;
+ segment.time = 0 * GST_SECOND;
+ event = gst_event_new_segment (&segment);
+
+ eret = gst_pad_send_event (sinkpad, event);
+ fail_unless (eret == TRUE);
+ }
+
+ /* send buffer that should work and block */
+ {
+ GstBuffer *buffer;
+ GstFlowReturn fret;
+
+ buffer = gst_buffer_new ();
+ GST_BUFFER_TIMESTAMP (buffer) = 1 * GST_SECOND;
+ GST_BUFFER_DURATION (buffer) = 1 * GST_SECOND;
+
+ GST_DEBUG ("sending buffer");
+
+ fret = gst_pad_chain (sinkpad, buffer);
+ fail_unless (fret == GST_FLOW_OK);
+ }
+
+ gst_element_set_state (pipeline, GST_STATE_NULL);
+ gst_element_get_state (pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
+ gst_object_unref (sinkpad);
+ gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+/* test EOS triggered by the element */
+GST_START_TEST (test_eos2)
+{
+ GstElement *pipeline, *sink;
+ GstPad *sinkpad;
+ GstStateChangeReturn ret;
+
+ /* create sink */
+ pipeline = gst_pipeline_new ("pipeline");
+ fail_if (pipeline == NULL);
+
+ sink = gst_element_factory_make ("fakesink", "sink");
+ fail_if (sink == NULL);
+ g_object_set (G_OBJECT (sink), "sync", TRUE, NULL);
+ g_object_set (G_OBJECT (sink), "num-buffers", 1, NULL);
+
+ gst_bin_add (GST_BIN (pipeline), sink);
+
+ sinkpad = gst_element_get_static_pad (sink, "sink");
+ fail_if (sinkpad == NULL);
+
+ /* make pipeline and element ready to accept data */
+ ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+ fail_unless (ret == GST_STATE_CHANGE_ASYNC);
+
+ gst_pad_send_event (sinkpad, gst_event_new_stream_start ("test"));
+
+ /* send segment, this should work */
+ {
+ GstEvent *event;
+ GstSegment segment;
+ gboolean eret;
+
+ GST_DEBUG ("sending segment");
+ gst_segment_init (&segment, GST_FORMAT_TIME);
+ segment.start = 0 * GST_SECOND;
+ segment.stop = 2 * GST_SECOND;
+ segment.time = 0 * GST_SECOND;
+ event = gst_event_new_segment (&segment);
+
+ eret = gst_pad_send_event (sinkpad, event);
+ fail_if (eret == FALSE);
+ }
+
+ /* send buffer that should return EOS */
+ {
+ GstBuffer *buffer;
+ GstFlowReturn fret;
+
+ buffer = gst_buffer_new ();
+ GST_BUFFER_TIMESTAMP (buffer) = 1 * GST_SECOND;
+ GST_BUFFER_DURATION (buffer) = 1 * GST_SECOND;
+
+ GST_DEBUG ("sending buffer");
+
+ /* this buffer will generate EOS */
+ fret = gst_pad_chain (sinkpad, buffer);
+ fail_unless (fret == GST_FLOW_EOS);
+ }
+
+ /* send buffer that should return EOS */
+ {
+ GstBuffer *buffer;
+ GstFlowReturn fret;
+
+ buffer = gst_buffer_new ();
+ GST_BUFFER_TIMESTAMP (buffer) = 1 * GST_SECOND;
+ GST_BUFFER_DURATION (buffer) = 1 * GST_SECOND;
+
+ GST_DEBUG ("sending buffer");
+
+ fret = gst_pad_chain (sinkpad, buffer);
+ fail_unless (fret == GST_FLOW_EOS);
+ }
+
+ gst_element_set_state (pipeline, GST_STATE_NULL);
+ gst_element_get_state (pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);
+ gst_object_unref (sinkpad);
+ gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+/* test position reporting before, during and after flush
+ * in PAUSED and PLAYING */
+GST_START_TEST (test_position)
+{
+ GstElement *pipeline, *sink;
+ GstPad *sinkpad;
+ GstStateChangeReturn ret;
+ gboolean qret;
+ gint64 qcur;
+ GstBuffer *buffer;
+ GstFlowReturn fret;
+ ChainData *data;
+ GstEvent *event;
+ gboolean eret;
+ gint i;
+
+ /* create sink */
+ pipeline = gst_pipeline_new ("pipeline");
+ fail_if (pipeline == NULL);
+
+ sink = gst_element_factory_make ("fakesink", "sink");
+ fail_if (sink == NULL);
+ g_object_set (G_OBJECT (sink), "sync", TRUE, NULL);
+ g_object_set (G_OBJECT (sink), "num-buffers", 2, NULL);
+
+ gst_bin_add (GST_BIN (pipeline), sink);
+
+ sinkpad = gst_element_get_static_pad (sink, "sink");
+ fail_if (sinkpad == NULL);
+
+ /* do position query, this should fail, we have nothing received yet */
+ qret = gst_element_query_position (sink, GST_FORMAT_TIME, &qcur);
+ fail_unless (qret == FALSE);
+
+ ret = gst_element_set_state (pipeline, GST_STATE_READY);
+ fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
+
+ /* do position query, this should fail, we have nothing received yet */
+ qret = gst_element_query_position (sink, GST_FORMAT_TIME, &qcur);
+ fail_unless (qret == FALSE);
+
+ /* make pipeline and element ready to accept data */
+ ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+ fail_unless (ret == GST_STATE_CHANGE_ASYNC);
+
+ /* do position query, this should fail, we have nothing received yet */
+ qret = gst_element_query_position (sink, GST_FORMAT_TIME, &qcur);
+ fail_unless (qret == FALSE);
+
+ gst_pad_send_event (sinkpad, gst_event_new_stream_start ("test"));
+
+ /* send segment, this should work */
+ {
+ GstSegment segment;
+
+ GST_DEBUG ("sending segment");
+ gst_segment_init (&segment, GST_FORMAT_TIME);
+ segment.start = 1 * GST_SECOND;
+ segment.stop = 3 * GST_SECOND;
+ segment.time = 1 * GST_SECOND;
+ event = gst_event_new_segment (&segment);
+
+ eret = gst_pad_send_event (sinkpad, event);
+ fail_if (eret == FALSE);
+ }
+
+ /* FIXME, do position query, this should succeed with the time value from the
+ * segment. */
+ qret = gst_element_query_position (sink, GST_FORMAT_TIME, &qcur);
+ fail_unless (qret == TRUE);
+ fail_unless (qcur == 1 * GST_SECOND);
+
+ /* send buffer that we will flush out */
+ buffer = gst_buffer_new ();
+ GST_BUFFER_TIMESTAMP (buffer) = 2 * GST_SECOND;
+ GST_BUFFER_DURATION (buffer) = 1 * GST_SECOND;
+
+ GST_DEBUG ("sending buffer");
+
+ /* this buffer causes the sink to preroll */
+ data = chain_async (sinkpad, buffer);
+ fail_if (data == NULL);
+
+ /* wait for preroll */
+ ret = gst_element_get_state (pipeline, NULL, NULL, -1);
+
+ /* do position query, this should succeed with the time value from the
+ * segment. */
+ qret = gst_element_query_position (sink, GST_FORMAT_TIME, &qcur);
+ fail_unless (qret == TRUE);
+ fail_unless (qcur == 1 * GST_SECOND);
+
+ /* start flushing, no timing is affected yet */
+ {
+ GST_DEBUG ("sending flush_start");
+ event = gst_event_new_flush_start ();
+
+ eret = gst_pad_send_event (sinkpad, event);
+ fail_if (eret == FALSE);
+ }
+
+ /* preroll buffer is flushed out */
+ fret = chain_async_return (data);
+ fail_unless (fret == GST_FLOW_FLUSHING);
+
+ /* do position query, this should succeed with the time value from the
+ * segment before the flush. */
+ qret = gst_element_query_position (sink, GST_FORMAT_TIME, &qcur);
+ fail_unless (qret == TRUE);
+ fail_unless (qcur == 1 * GST_SECOND);
+
+ /* stop flushing, timing is affected now */
+ {
+ GST_DEBUG ("sending flush_stop");
+ event = gst_event_new_flush_stop (TRUE);
+
+ eret = gst_pad_send_event (sinkpad, event);
+ fail_if (eret == FALSE);
+ }
+
+ /* do position query, this should fail, the segment is flushed */
+ qret = gst_element_query_position (sink, GST_FORMAT_TIME, &qcur);
+ fail_unless (qret == FALSE);
+
+ /* send segment, this should work */
+ {
+ GstSegment segment;
+
+ GST_DEBUG ("sending segment");
+ gst_segment_init (&segment, GST_FORMAT_TIME);
+ segment.start = 2 * GST_SECOND;
+ segment.stop = 4 * GST_SECOND;
+ segment.time = 1 * GST_SECOND;
+ event = gst_event_new_segment (&segment);
+
+ eret = gst_pad_send_event (sinkpad, event);
+ fail_if (eret == FALSE);
+ }
+
+ /* send buffer that should return OK */
+ buffer = gst_buffer_new ();
+ GST_BUFFER_TIMESTAMP (buffer) = 3 * GST_SECOND;
+ GST_BUFFER_DURATION (buffer) = 1 * GST_SECOND;
+
+ GST_DEBUG ("sending buffer");
+
+ /* this buffer causes the sink to preroll */
+ data = chain_async (sinkpad, buffer);
+ fail_if (data == NULL);
+
+ /* wait for preroll */
+ ret = gst_element_get_state (pipeline, NULL, NULL, -1);
+
+ /* do position query, this should succeed with the time value from the
+ * segment. */
+ qret = gst_element_query_position (sink, GST_FORMAT_TIME, &qcur);
+ fail_unless (qret == TRUE);
+ fail_unless (qcur == 1 * GST_SECOND);
+
+ ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+ fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
+
+ /* position now is increasing but never exceeds the boundaries of the segment */
+ for (i = 0; i < 5; i++) {
+ qret = gst_element_query_position (sink, GST_FORMAT_TIME, &qcur);
+ GST_DEBUG ("position %" GST_TIME_FORMAT, GST_TIME_ARGS (qcur));
+ fail_unless (qret == TRUE);
+ fail_unless (qcur >= 1 * GST_SECOND && qcur <= 3 * GST_SECOND);
+ g_usleep (1000 * 250);
+ }
+
+ /* preroll buffer is rendered, we expect one more buffer after this one */
+ fret = chain_async_return (data);
+ fail_unless (fret == GST_FLOW_OK);
+
+ /* after rendering the position must be bigger then the stream_time of the
+ * buffer */
+ qret = gst_element_query_position (sink, GST_FORMAT_TIME, &qcur);
+ fail_unless (qret == TRUE);
+ fail_unless (qcur >= 2 * GST_SECOND && qcur <= 3 * GST_SECOND);
+
+ /* start flushing in PLAYING */
+ {
+ GST_DEBUG ("sending flush_start");
+ event = gst_event_new_flush_start ();
+
+ eret = gst_pad_send_event (sinkpad, event);
+ fail_if (eret == FALSE);
+ }
+
+ /* this should now just report the last stream time */
+ qret = gst_element_query_position (sink, GST_FORMAT_TIME, &qcur);
+ fail_unless (qret == TRUE);
+ fail_unless (qcur >= 2 * GST_SECOND && qcur <= 3 * GST_SECOND);
+
+ {
+ GST_DEBUG ("sending flush_stop");
+ event = gst_event_new_flush_stop (TRUE);
+
+ eret = gst_pad_send_event (sinkpad, event);
+ fail_if (eret == FALSE);
+ }
+
+ /* do position query, this should fail, the segment is flushed */
+ qret = gst_element_query_position (sink, GST_FORMAT_TIME, &qcur);
+ fail_unless (qret == FALSE);
+
+ /* send segment, this should work */
+ {
+ GstSegment segment;
+
+ GST_DEBUG ("sending segment");
+ gst_segment_init (&segment, GST_FORMAT_TIME);
+ segment.start = 2 * GST_SECOND;
+ segment.stop = 4 * GST_SECOND;
+ segment.time = 1 * GST_SECOND;
+ event = gst_event_new_segment (&segment);
+
+ eret = gst_pad_send_event (sinkpad, event);
+ fail_if (eret == FALSE);
+ }
+
+ /* send buffer that should return EOS */
+ buffer = gst_buffer_new ();
+ GST_BUFFER_TIMESTAMP (buffer) = 3 * GST_SECOND;
+ GST_BUFFER_DURATION (buffer) = 1 * GST_SECOND;
+
+ GST_DEBUG ("sending buffer");
+
+ /* this buffer causes the sink to preroll */
+ data = chain_async (sinkpad, buffer);
+ fail_if (data == NULL);
+
+ /* wait for preroll */
+ ret = gst_element_get_state (pipeline, NULL, NULL, -1);
+
+ /* preroll buffer is rendered, we expect no more buffer after this one */
+ fret = chain_async_return (data);
+ fail_unless (fret == GST_FLOW_EOS);
+
+ /* do position query, this should succeed with the stream time of the buffer
+ * against the clock. Since the buffer is synced against the clock, the time
+ * should be at least the stream time of the buffer. */
+ qret = gst_element_query_position (sink, GST_FORMAT_TIME, &qcur);
+ fail_unless (qret == TRUE);
+ fail_unless (qcur >= 2 * GST_SECOND && qcur <= 3 * GST_SECOND);
+
+ /* wait 2 more seconds, enough to test if the position was clipped correctly
+ * against the segment */
+ g_usleep (2 * G_USEC_PER_SEC);
+
+ qret = gst_element_query_position (sink, GST_FORMAT_TIME, &qcur);
+ fail_unless (qret == TRUE);
+ fail_unless (qcur == 3 * GST_SECOND);
+
+ GST_DEBUG ("going to PAUSED");
+
+ ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+ fail_unless (ret == GST_STATE_CHANGE_ASYNC);
+
+ /* we report the time of the last start of the buffer. This is slightly
+ * incorrect, we should report the exact time when we paused but there is no
+ * record of that anywhere */
+ qret = gst_element_query_position (sink, GST_FORMAT_TIME, &qcur);
+ fail_unless (qret == TRUE);
+ fail_unless (qcur == 3 * GST_SECOND);
+
+ ret = gst_element_set_state (pipeline, GST_STATE_READY);
+ fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
+
+ /* fails again because we are in the wrong state */
+ qret = gst_element_query_position (sink, GST_FORMAT_TIME, &qcur);
+ fail_unless (qret == FALSE);
+
+ gst_element_set_state (pipeline, GST_STATE_NULL);
+
+ qret = gst_element_query_position (sink, GST_FORMAT_TIME, &qcur);
+ fail_unless (qret == FALSE);
+
+ gst_object_unref (sinkpad);
+ gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+/* like fakesrc, but also pushes an OOB event after each buffer */
+typedef GstPushSrc OOBSource;
+typedef GstPushSrcClass OOBSourceClass;
+
+GType oob_source_get_type (void);
+G_DEFINE_TYPE (OOBSource, oob_source, GST_TYPE_PUSH_SRC);
+
+static GstFlowReturn
+oob_source_create (GstPushSrc * src, GstBuffer ** p_buf)
+{
+ *p_buf = gst_buffer_new ();
+
+ gst_pad_push_event (GST_BASE_SRC_PAD (src),
+ gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_OOB, NULL));
+
+ return GST_FLOW_OK;
+}
+
+static void
+oob_source_class_init (OOBSourceClass * klass)
+{
+ static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY);
+ GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
+ GstPushSrcClass *pushsrc_class = GST_PUSH_SRC_CLASS (klass);
+
+ gst_element_class_add_static_pad_template (element_class, &sinktemplate);
+
+ pushsrc_class->create = GST_DEBUG_FUNCPTR (oob_source_create);
+}
+
+static void
+oob_source_init (OOBSource * src)
+{
+ /* nothing to do */
+}
+
+#define NOTIFY_RACE_NUM_PIPELINES 10
+
+typedef struct
+{
+ GstElement *src;
+ GstElement *queue;
+ GstElement *sink;
+ GstElement *pipe;
+} NotifyRacePipeline;
+
+static void
+test_notify_race_setup_pipeline (NotifyRacePipeline * p)
+{
+ GST_DEBUG ("Creating pipeline");
+ p->pipe = gst_pipeline_new ("pipeline");
+ p->src = g_object_new (oob_source_get_type (), NULL);
+
+ p->queue = gst_element_factory_make ("queue", NULL);
+ g_object_set (p->queue, "max-size-buffers", 2, NULL);
+
+ p->sink = gst_element_factory_make ("fakesink", NULL);
+ gst_bin_add (GST_BIN (p->pipe), p->src);
+ gst_bin_add (GST_BIN (p->pipe), p->queue);
+ gst_bin_add (GST_BIN (p->pipe), p->sink);
+ gst_element_link_many (p->src, p->queue, p->sink, NULL);
+
+ GST_DEBUG ("Setting pipeline to PAUSED..");
+ fail_unless_equals_int (gst_element_set_state (p->pipe, GST_STATE_PAUSED),
+ GST_STATE_CHANGE_ASYNC);
+ GST_DEBUG ("Waiting for pipeline to preroll..");
+ fail_unless_equals_int (gst_element_get_state (p->pipe, NULL, NULL, -1),
+ GST_STATE_CHANGE_SUCCESS);
+ GST_DEBUG ("Ready to party!");
+}
+
+static void
+test_notify_race_cleanup_pipeline (NotifyRacePipeline * p)
+{
+ gst_element_set_state (p->pipe, GST_STATE_NULL);
+ gst_object_unref (p->pipe);
+ memset (p, 0, sizeof (NotifyRacePipeline));
+}
+
+/* we create N pipelines to make sure the notify race isn't per-class, but
+ * only per instance */
+GST_START_TEST (test_notify_race)
+{
+ NotifyRacePipeline pipelines[NOTIFY_RACE_NUM_PIPELINES];
+ int i;
+
+ for (i = 0; i < G_N_ELEMENTS (pipelines); ++i) {
+ GST_DEBUG ("Setting up pipeline %d", i);
+ test_notify_race_setup_pipeline (&pipelines[i]);
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (pipelines); ++i) {
+ GST_DEBUG ("Starting pipeline %d", i);
+ gst_element_set_state (pipelines[i].pipe, GST_STATE_PLAYING);
+ }
+
+ g_usleep (2 * G_USEC_PER_SEC);
+
+ for (i = 0; i < G_N_ELEMENTS (pipelines); ++i) {
+ GST_DEBUG ("Cleaning up pipeline %d", i);
+ test_notify_race_cleanup_pipeline (&pipelines[i]);
+ }
+}
+
+GST_END_TEST;
+
+static void
+last_message_cb (GObject * obj, GParamSpec * pspec, gpointer user_data)
+{
+ gint *p_counter = user_data;
+ gchar *s, *end, *last_msg = NULL;
+ guint64 offset, count;
+
+ g_object_get (obj, "last-message", &last_msg, NULL);
+ fail_unless (last_msg != NULL);
+
+ if (!strstr (last_msg, "chain"))
+ goto skip;
+
+ GST_LOG_OBJECT (obj, "%s", last_msg);
+
+ s = strstr (last_msg, "offset: ");
+ fail_unless (s != NULL);
+
+ s += strlen ("offset: ");
+
+ offset = g_ascii_strtoull (s, &end, 10);
+ fail_unless (offset < G_MAXUINT64);
+ fail_if (end == s);
+
+ count = *p_counter;
+
+ fail_unless_equals_int (count, offset);
+
+ *p_counter = count + 1;
+
+skip:
+
+ g_free (last_msg);
+}
+
+#define NUM_BUFFERS 500
+
+GST_START_TEST (test_last_message_notify)
+{
+ GstElement *pipe, *src, *tee, *q1, *q2, *sink1, *sink2;
+ gint counter1 = 0;
+ gint counter2 = 0;
+ GstMessage *m;
+
+ pipe = gst_pipeline_new ("pipeline");
+ src = gst_element_factory_make ("fakesrc", NULL);
+ gst_util_set_object_arg (G_OBJECT (src), "sizetype", "fixed");
+ g_object_set (src, "num-buffers", NUM_BUFFERS, "sizemax", 1, NULL);
+
+ tee = gst_element_factory_make ("tee", NULL);
+
+ q1 = gst_element_factory_make ("queue", NULL);
+ sink1 = gst_element_factory_make ("fakesink", NULL);
+ g_object_set (sink1, "silent", FALSE, NULL);
+
+ q2 = gst_element_factory_make ("queue", NULL);
+ sink2 = gst_element_factory_make ("fakesink", NULL);
+ g_object_set (sink2, "silent", FALSE, NULL);
+
+ gst_bin_add_many (GST_BIN (pipe), src, tee, q1, q2, sink1, sink2, NULL);
+ fail_unless (gst_element_link_many (src, tee, NULL));
+ fail_unless (gst_element_link_many (tee, q1, sink1, NULL));
+ fail_unless (gst_element_link_many (tee, q2, sink2, NULL));
+
+ g_signal_connect (sink1, "notify::last-message",
+ G_CALLBACK (last_message_cb), &counter1);
+ g_signal_connect (sink2, "notify::last-message",
+ G_CALLBACK (last_message_cb), &counter2);
+
+ GST_DEBUG ("Setting pipeline to PLAYING");
+ fail_unless_equals_int (gst_element_set_state (pipe, GST_STATE_PLAYING),
+ GST_STATE_CHANGE_ASYNC);
+
+ m = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipe), -1, GST_MESSAGE_EOS);
+ gst_message_unref (m);
+
+ fail_unless_equals_int (gst_element_set_state (pipe, GST_STATE_NULL),
+ GST_STATE_CHANGE_SUCCESS);
+
+ fail_unless_equals_int (counter1, NUM_BUFFERS);
+ fail_unless_equals_int (counter2, NUM_BUFFERS);
+
+ gst_object_unref (pipe);
+}
+
+GST_END_TEST;
+
+static void
+deep_notify_last_message_cb (GstObject * obj, GstObject * prop_obj,
+ GParamSpec * pspec, gpointer user_data)
+{
+ gint *counter_array = user_data;
+ gint *p_counter;
+ gchar *s, *end, *last_msg = NULL;
+ guint64 offset, count;
+
+ if (strcmp (GST_OBJECT_NAME (prop_obj), "fakesink0") == 0)
+ p_counter = counter_array;
+ else if (strcmp (GST_OBJECT_NAME (prop_obj), "fakesink1") == 0)
+ p_counter = counter_array + 1;
+ else
+ g_assert_not_reached ();
+
+ g_object_get (prop_obj, "last-message", &last_msg, NULL);
+ fail_unless (last_msg != NULL);
+
+ if (!strstr (last_msg, "chain"))
+ goto skip;
+
+ GST_LOG_OBJECT (prop_obj, "%s", last_msg);
+
+ s = strstr (last_msg, "offset: ");
+ fail_unless (s != NULL);
+
+ s += strlen ("offset: ");
+
+ offset = g_ascii_strtoull (s, &end, 10);
+ fail_unless (offset < G_MAXUINT64);
+ fail_if (end == s);
+
+ count = *p_counter;
+
+// fail_unless_equals_int (count, offset);
+
+ *p_counter = count + 1;
+
+skip:
+
+ g_free (last_msg);
+}
+
+GST_START_TEST (test_last_message_deep_notify)
+{
+ GstElement *pipe, *src, *tee, *q1, *q2, *sink1, *sink2;
+ gint counter[2] = { 0, 0 };
+ GstMessage *m;
+
+ pipe = gst_pipeline_new ("pipeline");
+ src = gst_element_factory_make ("fakesrc", NULL);
+ gst_util_set_object_arg (G_OBJECT (src), "sizetype", "fixed");
+ g_object_set (src, "num-buffers", NUM_BUFFERS, "sizemax", 1, NULL);
+
+ tee = gst_element_factory_make ("tee", NULL);
+
+ q1 = gst_element_factory_make ("queue", NULL);
+ sink1 = gst_element_factory_make ("fakesink", NULL);
+ g_object_set (sink1, "silent", FALSE, NULL);
+
+ q2 = gst_element_factory_make ("queue", NULL);
+ sink2 = gst_element_factory_make ("fakesink", NULL);
+ g_object_set (sink2, "silent", FALSE, NULL);
+
+ gst_bin_add_many (GST_BIN (pipe), src, tee, q1, q2, sink1, sink2, NULL);
+ fail_unless (gst_element_link_many (src, tee, NULL));
+ fail_unless (gst_element_link_many (tee, q1, sink1, NULL));
+ fail_unless (gst_element_link_many (tee, q2, sink2, NULL));
+
+ g_signal_connect (pipe, "deep-notify::last-message",
+ G_CALLBACK (deep_notify_last_message_cb), counter);
+
+ GST_DEBUG ("Setting pipeline to PLAYING");
+ fail_unless_equals_int (gst_element_set_state (pipe, GST_STATE_PLAYING),
+ GST_STATE_CHANGE_ASYNC);
+
+ m = gst_bus_timed_pop_filtered (GST_ELEMENT_BUS (pipe), -1, GST_MESSAGE_EOS);
+ gst_message_unref (m);
+
+ fail_unless_equals_int (gst_element_set_state (pipe, GST_STATE_NULL),
+ GST_STATE_CHANGE_SUCCESS);
+
+ GST_ERROR ("sink1: %d, sink2: %d, total: %d", counter[0], counter[1],
+ counter[0] + counter[1]);
+
+ fail_unless_equals_int (counter[0], NUM_BUFFERS);
+ fail_unless_equals_int (counter[1], NUM_BUFFERS);
+}
+
+GST_END_TEST;
+
+static Suite *
+fakesink_suite (void)
+{
+ Suite *s = suite_create ("fakesink");
+ TCase *tc_chain = tcase_create ("general");
+
+ tcase_set_timeout (tc_chain, 20);
+
+ suite_add_tcase (s, tc_chain);
+ tcase_add_test (tc_chain, test_clipping);
+ tcase_add_test (tc_chain, test_preroll_sync);
+ tcase_add_test (tc_chain, test_eos);
+ tcase_add_test (tc_chain, test_eos2);
+ tcase_add_test (tc_chain, test_position);
+ tcase_add_test (tc_chain, test_notify_race);
+ tcase_add_test (tc_chain, test_last_message_notify);
+ tcase_skip_broken_test (tc_chain, test_last_message_deep_notify);
+
+ return s;
+}
+
+GST_CHECK_MAIN (fakesink);