event_res = bclass->event (basesink, event);
/* when we get here we could be flushing again when the event handler calls
- * _wait_eos() or releases the preroll lock in any other way.
- * We have to ignore this object in that case. */
+ * _wait_eos(). We have to ignore this object in that case. */
if (G_UNLIKELY (basesink->flushing))
goto flushing;
GST_MINI_OBJECT_CAST (event), FALSE);
if (G_UNLIKELY (ret != GST_FLOW_OK))
result = FALSE;
- else
+ else {
+ GST_OBJECT_LOCK (basesink);
basesink->have_newsegment = TRUE;
+ GST_OBJECT_UNLOCK (basesink);
+ }
}
GST_PAD_PREROLL_UNLOCK (pad);
break;
* event. */
gst_base_sink_set_flushing (basesink, pad, FALSE);
- /* we need new segment info after the flush. */
- gst_segment_init (&basesink->segment, GST_FORMAT_UNDEFINED);
- gst_segment_init (basesink->abidata.ABI.clip_segment,
- GST_FORMAT_UNDEFINED);
- basesink->have_newsegment = FALSE;
-
/* for position reporting */
GST_OBJECT_LOCK (basesink);
basesink->priv->current_sstart = -1;
basesink->priv->current_sstop = -1;
basesink->priv->eos_rtime = -1;
+ basesink->have_newsegment = FALSE;
GST_OBJECT_UNLOCK (basesink);
+ /* we need new segment info after the flush. */
+ gst_segment_init (&basesink->segment, GST_FORMAT_UNDEFINED);
+ gst_segment_init (basesink->abidata.ABI.clip_segment,
+ GST_FORMAT_UNDEFINED);
+
gst_event_unref (event);
break;
default:
("Received buffer without a new-segment. Assuming timestamps start from 0."));
}
- basesink->have_newsegment = TRUE;
/* this means this sink will assume timestamps start from 0 */
+ GST_OBJECT_LOCK (basesink);
clip_segment->start = 0;
clip_segment->stop = -1;
basesink->segment.start = 0;
basesink->segment.stop = -1;
+ basesink->have_newsegment = TRUE;
+ GST_OBJECT_UNLOCK (basesink);
}
bclass = GST_BASE_SINK_GET_CLASS (basesink);
gst_base_sink_negotiate_pull (GstBaseSink * basesink)
{
GstCaps *caps;
- GstPad *pad;
+ gboolean result;
- GST_OBJECT_LOCK (basesink);
- pad = basesink->sinkpad;
- gst_object_ref (pad);
- GST_OBJECT_UNLOCK (basesink);
+ result = FALSE;
- caps = gst_pad_get_allowed_caps (pad);
- if (gst_caps_is_empty (caps))
+ /* this returns the intersection between our caps and the peer caps. If there
+ * is no peer, it returns NULL and we can't operate in pull mode so we can
+ * fail the negotiation. */
+ caps = gst_pad_get_allowed_caps (GST_BASE_SINK_PAD (basesink));
+ if (caps == NULL || gst_caps_is_empty (caps))
goto no_caps_possible;
+ GST_DEBUG_OBJECT (basesink, "allowed caps: %" GST_PTR_FORMAT, caps);
+
caps = gst_caps_make_writable (caps);
+ /* get the first (prefered) format */
gst_caps_truncate (caps);
- gst_pad_fixate_caps (pad, caps);
+ /* try to fixate */
+ gst_pad_fixate_caps (GST_BASE_SINK_PAD (basesink), caps);
+
+ GST_DEBUG_OBJECT (basesink, "fixated to: %" GST_PTR_FORMAT, caps);
if (gst_caps_is_any (caps)) {
GST_DEBUG_OBJECT (basesink, "caps were ANY after fixating, "
"allowing pull()");
/* neither side has template caps in this case, so they are prepared for
pull() without setcaps() */
- } else {
- if (!gst_pad_set_caps (pad, caps))
+ result = TRUE;
+ } else if (gst_caps_is_fixed (caps)) {
+ if (!gst_pad_set_caps (GST_BASE_SINK_PAD (basesink), caps))
goto could_not_set_caps;
+ result = TRUE;
}
gst_caps_unref (caps);
- gst_object_unref (pad);
- return TRUE;
+ return result;
no_caps_possible:
{
GST_INFO_OBJECT (basesink, "Pipeline could not agree on caps");
GST_DEBUG_OBJECT (basesink, "get_allowed_caps() returned EMPTY");
- gst_object_unref (pad);
+ if (caps)
+ gst_caps_unref (caps);
return FALSE;
}
could_not_set_caps:
{
GST_INFO_OBJECT (basesink, "Could not set caps: %" GST_PTR_FORMAT, caps);
gst_caps_unref (caps);
- gst_object_unref (pad);
return FALSE;
}
}
gst_segment_init (&basesink->segment, GST_FORMAT_UNDEFINED);
gst_segment_init (basesink->abidata.ABI.clip_segment,
GST_FORMAT_UNDEFINED);
+ GST_OBJECT_LOCK (basesink);
basesink->have_newsegment = TRUE;
+ GST_OBJECT_UNLOCK (basesink);
/* set the pad mode before starting the task so that it's in the
correct state for the new thread. also the sink set_caps function
if (G_UNLIKELY (basesink->eos))
goto in_eos;
- /* in PAUSE we cannot read from the clock so we
- * report time based on the last seen timestamp. */
- if (GST_STATE (basesink) == GST_STATE_PAUSED)
- goto in_pause;
-
- /* We get position from clock only in PLAYING, we checked
- * the PAUSED case above, so this is check is to test
- * READY and NULL, where the position is always 0 */
- if (GST_STATE (basesink) != GST_STATE_PLAYING)
+ /* we can only get the segment when we are not NULL or READY */
+ if (!basesink->have_newsegment)
goto wrong_state;
+ /* when not in PLAYING or when we're busy with a state change, we
+ * cannot read from the clock so we report time based on the
+ * last seen timestamp. */
+ if (GST_STATE (basesink) != GST_STATE_PLAYING ||
+ GST_STATE_PENDING (basesink) != GST_STATE_VOID_PENDING)
+ goto in_pause;
+
/* we need to sync on the clock. */
if (basesink->sync == FALSE)
goto no_sync;
* is no data flow in READY so we can safely assume we need to preroll. */
GST_PAD_PREROLL_LOCK (basesink->sinkpad);
GST_DEBUG_OBJECT (basesink, "READY to PAUSED");
+ basesink->have_newsegment = FALSE;
gst_segment_init (&basesink->segment, GST_FORMAT_UNDEFINED);
gst_segment_init (basesink->abidata.ABI.clip_segment,
GST_FORMAT_UNDEFINED);
- basesink->have_newsegment = FALSE;
basesink->offset = 0;
basesink->have_preroll = FALSE;
basesink->need_preroll = TRUE;
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
GST_PAD_PREROLL_LOCK (basesink->sinkpad);
+ /* start by reseting our position state with the object lock so that the
+ * position query gets the right idea. We do this before we post the
+ * messages so that the message handlers pick this up. */
+ GST_OBJECT_LOCK (basesink);
+ basesink->have_newsegment = FALSE;
+ priv->current_sstart = -1;
+ priv->current_sstop = -1;
+ priv->have_latency = FALSE;
+ GST_OBJECT_UNLOCK (basesink);
+
+ gst_base_sink_set_last_buffer (basesink, NULL);
+
if (!priv->commited) {
if (priv->async_enabled) {
GST_DEBUG_OBJECT (basesink, "PAUSED to READY, posting async-done");
} else {
GST_DEBUG_OBJECT (basesink, "PAUSED to READY, don't need_preroll");
}
- priv->current_sstart = -1;
- priv->current_sstop = -1;
- priv->have_latency = FALSE;
- gst_base_sink_set_last_buffer (basesink, NULL);
GST_PAD_PREROLL_UNLOCK (basesink->sinkpad);
break;
case GST_STATE_CHANGE_READY_TO_NULL:
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;
+ GstFormat qformat;
+ 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 */
+ qformat = GST_FORMAT_TIME;
+ qret = gst_element_query_position (sink, &qformat, &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 */
+ qformat = GST_FORMAT_TIME;
+ qret = gst_element_query_position (sink, &qformat, &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 */
+ qformat = GST_FORMAT_TIME;
+ qret = gst_element_query_position (sink, &qformat, &qcur);
+ fail_unless (qret == FALSE);
+
+ /* send segment, this should work */
+ {
+ GST_DEBUG ("sending segment");
+ event = gst_event_new_new_segment (FALSE,
+ 1.0, GST_FORMAT_TIME, 1 * GST_SECOND, 3 * GST_SECOND, 1 * GST_SECOND);
+
+ 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. */
+ qformat = GST_FORMAT_TIME;
+ qret = gst_element_query_position (sink, &qformat, &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. */
+ qformat = GST_FORMAT_TIME;
+ qret = gst_element_query_position (sink, &qformat, &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_WRONG_STATE);
+
+ /* do position query, this should succeed with the time value from the
+ * segment before the flush. */
+ qformat = GST_FORMAT_TIME;
+ qret = gst_element_query_position (sink, &qformat, &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 ();
+
+ eret = gst_pad_send_event (sinkpad, event);
+ fail_if (eret == FALSE);
+ }
+
+ /* do position query, this should fail, the segment is flushed */
+ qformat = GST_FORMAT_TIME;
+ qret = gst_element_query_position (sink, &qformat, &qcur);
+ fail_unless (qret == FALSE);
+
+ /* send segment, this should work */
+ {
+ GST_DEBUG ("sending segment");
+ event = gst_event_new_new_segment (FALSE,
+ 1.0, GST_FORMAT_TIME, 2 * GST_SECOND, 4 * GST_SECOND, 1 * GST_SECOND);
+
+ 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. */
+ qformat = GST_FORMAT_TIME;
+ qret = gst_element_query_position (sink, &qformat, &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++) {
+ qformat = GST_FORMAT_TIME;
+ qret = gst_element_query_position (sink, &qformat, &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 */
+ qformat = GST_FORMAT_TIME;
+ qret = gst_element_query_position (sink, &qformat, &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 stream time of the last buffer */
+ qformat = GST_FORMAT_TIME;
+ qret = gst_element_query_position (sink, &qformat, &qcur);
+ fail_unless (qret == TRUE);
+ fail_unless (qcur == 2 * GST_SECOND);
+
+ {
+ GST_DEBUG ("sending flush_stop");
+ event = gst_event_new_flush_stop ();
+
+ eret = gst_pad_send_event (sinkpad, event);
+ fail_if (eret == FALSE);
+ }
+
+ /* do position query, this should fail, the segment is flushed */
+ qformat = GST_FORMAT_TIME;
+ qret = gst_element_query_position (sink, &qformat, &qcur);
+ fail_unless (qret == FALSE);
+
+ /* send segment, this should work */
+ {
+ GST_DEBUG ("sending segment");
+ event = gst_event_new_new_segment (FALSE,
+ 1.0, GST_FORMAT_TIME, 2 * GST_SECOND, 4 * GST_SECOND, 1 * GST_SECOND);
+
+ eret = gst_pad_send_event (sinkpad, event);
+ fail_if (eret == FALSE);
+ }
+
+ /* send buffer that should return UNEXPECTED */
+ 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_UNEXPECTED);
+
+ /* 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. */
+ qformat = GST_FORMAT_TIME;
+ qret = gst_element_query_position (sink, &qformat, &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);
+
+ qformat = GST_FORMAT_TIME;
+ qret = gst_element_query_position (sink, &qformat, &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 */
+ qformat = GST_FORMAT_TIME;
+ qret = gst_element_query_position (sink, &qformat, &qcur);
+ fail_unless (qret == TRUE);
+ fail_unless (qcur == 2 * 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 */
+ qformat = GST_FORMAT_TIME;
+ qret = gst_element_query_position (sink, &qformat, &qcur);
+ fail_unless (qret == FALSE);
+
+ gst_element_set_state (pipeline, GST_STATE_NULL);
+
+ qformat = GST_FORMAT_TIME;
+ qret = gst_element_query_position (sink, &qformat, &qcur);
+ fail_unless (qret == FALSE);
+
+ gst_object_unref (sinkpad);
+ gst_object_unref (pipeline);
+}
+
+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);
return s;
}