From eb6c9dd8014e39a2abc7f22f129812017ed70f48 Mon Sep 17 00:00:00 2001 From: Andy Wingo Date: Mon, 7 Mar 2005 10:35:12 +0000 Subject: [PATCH] check/gst/gstobject.c (test_fake_object_name): The object *does* have a name after g_object_new. Original commit message from CVS: 2005-03-07 Andy Wingo * check/gst/gstobject.c (test_fake_object_name): The object *does* have a name after g_object_new. * gst/base/gstbasesink.c: Change "arguments" to "properties" in the code. Die GtkObject die! (GstBaseSink::preroll-queue-len): New object property, the number of buffers to queue in preroll. By default, does not queue any buffers. Set to a higher number if you have a one-threaded demuxer. (gst_basesink_preroll_queue_push) (gst_basesink_preroll_queue_empty) (gst_basesink_preroll_queue_flush): Implement a queue of buffers for preroll. All must be called with PREROLL_LOCK. (gst_basesink_finish_preroll): Instead of always blocking, push the buffer onto the queue. preroll_queue_push will block if the queue is full. (gst_basesink_event): Make sure the preroll queue is emptied on eos and flushed on flush. (gst_basesink_handle_buffer): Does the work of chain_unlocked, but without going into finish_preroll. (gst_basesink_change_state): Handle setting up the queue and flushing it in READY<->PAUSED transitions. --- ChangeLog | 25 ++++ check/gst/gstobject.c | 4 +- docs/gst/tmpl/gstobject.sgml | 4 +- gst/base/gstbasesink.c | 273 ++++++++++++++++++++++++++--------- gst/base/gstbasesink.h | 3 + libs/gst/base/gstbasesink.c | 273 ++++++++++++++++++++++++++--------- libs/gst/base/gstbasesink.h | 3 + tests/check/gst/gstobject.c | 4 +- 8 files changed, 451 insertions(+), 138 deletions(-) diff --git a/ChangeLog b/ChangeLog index be1919682b..ca45b3fb0c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,28 @@ +2005-03-07 Andy Wingo + + * check/gst/gstobject.c (test_fake_object_name): The object *does* + have a name after g_object_new. + + * gst/base/gstbasesink.c: Change "arguments" to "properties" in + the code. Die GtkObject die! + (GstBaseSink::preroll-queue-len): New object property, the number + of buffers to queue in preroll. By default, does not queue any + buffers. Set to a higher number if you have a one-threaded + demuxer. + (gst_basesink_preroll_queue_push) + (gst_basesink_preroll_queue_empty) + (gst_basesink_preroll_queue_flush): Implement a queue of buffers + for preroll. All must be called with PREROLL_LOCK. + (gst_basesink_finish_preroll): Instead of always blocking, push + the buffer onto the queue. preroll_queue_push will block if the + queue is full. + (gst_basesink_event): Make sure the preroll queue is emptied on + eos and flushed on flush. + (gst_basesink_handle_buffer): Does the work of chain_unlocked, but + without going into finish_preroll. + (gst_basesink_change_state): Handle setting up the queue and + flushing it in READY<->PAUSED transitions. + 2005-03-03 Wim Taymans * docs/design/part-MT-refcounting.txt: diff --git a/check/gst/gstobject.c b/check/gst/gstobject.c index 06339cf49b..52f92e9ba0 100644 --- a/check/gst/gstobject.c +++ b/check/gst/gstobject.c @@ -98,7 +98,9 @@ START_TEST (test_fake_object_name) object = g_object_new (gst_fake_object_get_type (), NULL); name = gst_object_get_name (object); - fail_if (name != NULL, "Newly created object has a name"); + fail_if (name == NULL, "Newly created object has no name"); + fail_if (strncmp (name, "fakeobject", 10) != 0, + "Random name %s does not start with Gst", name); /* give a random name by setting with NULL; * GstFakeObject class -> fakeobject%d */ diff --git a/docs/gst/tmpl/gstobject.sgml b/docs/gst/tmpl/gstobject.sgml index 16fb2d863a..029d64bf2b 100644 --- a/docs/gst/tmpl/gstobject.sgml +++ b/docs/gst/tmpl/gstobject.sgml @@ -221,10 +221,10 @@ Acquire a reference to the mutex of this object. @object: -@name_prefix: +@name: @Returns: -@name: +@name_prefix: diff --git a/gst/base/gstbasesink.c b/gst/base/gstbasesink.c index 9fba36011b..7e071e756a 100644 --- a/gst/base/gstbasesink.c +++ b/gst/base/gstbasesink.c @@ -34,7 +34,14 @@ static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", GST_DEBUG_CATEGORY_STATIC (gst_basesink_debug); #define GST_CAT_DEFAULT gst_basesink_debug -/* BaseSink signals and args */ +/* #define DEBUGGING */ +#ifdef DEBUGGING +#define DEBUG(str,args...) g_print (str,##args) +#else +#define DEBUG(str,args...) +#endif + +/* BaseSink signals and properties */ enum { /* FILL ME */ @@ -48,9 +55,10 @@ enum enum { - ARG_0, - ARG_HAS_LOOP, - ARG_HAS_CHAIN + PROP_0, + PROP_HAS_LOOP, + PROP_HAS_CHAIN, + PROP_PREROLL_QUEUE_LEN }; #define _do_init(bla) \ @@ -82,6 +90,8 @@ static void gst_basesink_loop (GstPad * pad); static GstFlowReturn gst_basesink_chain (GstPad * pad, GstBuffer * buffer); static gboolean gst_basesink_activate (GstPad * pad, GstActivateMode mode); static gboolean gst_basesink_event (GstPad * pad, GstEvent * event); +static inline void gst_basesink_handle_buffer (GstBaseSink * basesink, + GstBuffer * buf); static GstStaticPadTemplate * gst_basesink_get_template (GstBaseSink * bsink) @@ -123,14 +133,19 @@ gst_basesink_class_init (GstBaseSinkClass * klass) gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_basesink_set_property); gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_basesink_get_property); - g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_HAS_LOOP, + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HAS_LOOP, g_param_spec_boolean ("has-loop", "has-loop", "Enable loop-based operation", DEFAULT_HAS_LOOP, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_HAS_CHAIN, + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HAS_CHAIN, g_param_spec_boolean ("has-chain", "has-chain", "Enable chain-based operation", DEFAULT_HAS_CHAIN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (G_OBJECT_CLASS (klass), + PROP_PREROLL_QUEUE_LEN, + g_param_spec_uint ("preroll-queue-len", "preroll-queue-len", + "Number of buffers to queue during preroll", 0, G_MAXUINT, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); gstelement_class->set_clock = GST_DEBUG_FUNCPTR (gst_basesink_set_clock); gstelement_class->change_state = @@ -262,22 +277,29 @@ gst_basesink_set_property (GObject * object, guint prop_id, { GstBaseSink *sink; - /* it's not null if we got it, but it might not be ours */ sink = GST_BASESINK (object); + GST_LOCK (sink); switch (prop_id) { - case ARG_HAS_LOOP: + case PROP_HAS_LOOP: sink->has_loop = g_value_get_boolean (value); gst_basesink_set_all_pad_functions (sink); break; - case ARG_HAS_CHAIN: + case PROP_HAS_CHAIN: sink->has_chain = g_value_get_boolean (value); gst_basesink_set_all_pad_functions (sink); break; + case PROP_PREROLL_QUEUE_LEN: + /* preroll lock necessary to serialize with finish_preroll */ + GST_PREROLL_LOCK (sink->sinkpad); + sink->preroll_queue_max_len = g_value_get_uint (value); + GST_PREROLL_UNLOCK (sink->sinkpad); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } + GST_UNLOCK (sink); } static void @@ -286,22 +308,24 @@ gst_basesink_get_property (GObject * object, guint prop_id, GValue * value, { GstBaseSink *sink; - /* it's not null if we got it, but it might not be ours */ - g_return_if_fail (GST_IS_BASESINK (object)); - sink = GST_BASESINK (object); + GST_LOCK (sink); switch (prop_id) { - case ARG_HAS_LOOP: + case PROP_HAS_LOOP: g_value_set_boolean (value, sink->has_loop); break; - case ARG_HAS_CHAIN: + case PROP_HAS_CHAIN: g_value_set_boolean (value, sink->has_chain); break; + case PROP_PREROLL_QUEUE_LEN: + g_value_set_uint (value, sink->preroll_queue_max_len); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } + GST_UNLOCK (sink); } static GstStaticPadTemplate * @@ -329,35 +353,94 @@ gst_base_sink_buffer_alloc (GstBaseSink * sink, guint64 offset, guint size, return NULL; } -/* STREAM_LOCK should be held */ -GstFlowReturn +/* with PREROLL_LOCK */ +static void +gst_basesink_preroll_queue_push (GstBaseSink * basesink, GstPad * pad, + GstBuffer * buffer) +{ + if (basesink->preroll_queue->length == 0) { + GstBaseSinkClass *bclass = GST_BASESINK_GET_CLASS (basesink); + + if (bclass->preroll) + bclass->preroll (basesink, buffer); + } + + if (basesink->preroll_queue->length < basesink->preroll_queue_max_len) { + DEBUG ("push %p %p\n", basesink, buffer); + g_queue_push_tail (basesink->preroll_queue, buffer); + } else { + /* block until the state changes, or we get a flush, or something */ + DEBUG ("block %p %p\n", basesink, buffer); + GST_DEBUG ("element %s waiting to finish preroll", + GST_ELEMENT_NAME (basesink)); + basesink->need_preroll = FALSE; + basesink->have_preroll = TRUE; + GST_PREROLL_WAIT (pad); + GST_DEBUG ("done preroll"); + basesink->have_preroll = FALSE; + } +} + +/* with PREROLL_LOCK */ +static void +gst_basesink_preroll_queue_empty (GstBaseSink * basesink, GstPad * pad) +{ + GstBuffer *buf; + GQueue *q = basesink->preroll_queue; + + if (q) { + DEBUG ("empty queue\n"); + while ((buf = g_queue_pop_head (q))) { + DEBUG ("pop %p\n", buf); + gst_basesink_handle_buffer (basesink, buf); + } + DEBUG ("queue len %p %d\n", basesink, q->length); + } +} + +/* with PREROLL_LOCK */ +static void +gst_basesink_preroll_queue_flush (GstBaseSink * basesink) +{ + GstBuffer *buf; + GQueue *q = basesink->preroll_queue; + + DEBUG ("flush %p\n", basesink); + if (q) { + while ((buf = g_queue_pop_head (q))) { + DEBUG ("pop %p\n", buf); + gst_buffer_unref (buf); + } + } +} + +typedef enum +{ + PREROLL_QUEUEING, + PREROLL_PLAYING, + PREROLL_FLUSHING, + PREROLL_ERROR +} PrerollReturn; + +/* with STREAM_LOCK */ +PrerollReturn gst_basesink_finish_preroll (GstBaseSink * basesink, GstPad * pad, GstBuffer * buffer) { gboolean usable; - GstBaseSinkClass *bclass; + DEBUG ("finish preroll %p <\n", basesink); /* lock order is important */ GST_STATE_LOCK (basesink); GST_PREROLL_LOCK (pad); + DEBUG ("finish preroll %p >\n", basesink); if (!basesink->need_preroll) goto no_preroll; - bclass = GST_BASESINK_GET_CLASS (basesink); - - if (bclass->preroll) - bclass->preroll (basesink, buffer); - gst_element_commit_state (GST_ELEMENT (basesink)); GST_STATE_UNLOCK (basesink); - GST_DEBUG ("element %s waiting to finish preroll", - GST_ELEMENT_NAME (basesink)); - basesink->need_preroll = FALSE; - basesink->have_preroll = TRUE; - GST_PREROLL_WAIT (pad); - GST_DEBUG ("done preroll"); - basesink->have_preroll = FALSE; + gst_basesink_preroll_queue_push (basesink, pad, buffer); GST_LOCK (pad); usable = !GST_RPAD_IS_FLUSHING (pad) && GST_RPAD_IS_ACTIVE (pad); @@ -365,26 +448,40 @@ gst_basesink_finish_preroll (GstBaseSink * basesink, GstPad * pad, if (!usable) goto unusable; + if (basesink->need_preroll) + goto still_queueing; + GST_DEBUG ("done preroll"); + gst_basesink_preroll_queue_empty (basesink, pad); + GST_PREROLL_UNLOCK (pad); - return GST_FLOW_OK; + + return PREROLL_PLAYING; no_preroll: { + /* maybe it was another sink that blocked in preroll, need to check for + buffers to drain */ + if (basesink->preroll_queue->length) + gst_basesink_preroll_queue_empty (basesink, pad); GST_PREROLL_UNLOCK (pad); GST_STATE_UNLOCK (basesink); - return GST_FLOW_OK; + return PREROLL_PLAYING; } unusable: { GST_DEBUG ("pad is flushing"); GST_PREROLL_UNLOCK (pad); - return GST_FLOW_UNEXPECTED; + return PREROLL_FLUSHING; + } +still_queueing: + { + GST_PREROLL_UNLOCK (pad); + return PREROLL_QUEUEING; } } - static gboolean gst_basesink_event (GstPad * pad, GstEvent * event) { @@ -396,38 +493,40 @@ gst_basesink_event (GstPad * pad, GstEvent * event) bclass = GST_BASESINK_GET_CLASS (basesink); + DEBUG ("event %p\n", basesink); + if (bclass->event) bclass->event (basesink, event); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_EOS: { - GstFlowReturn ret; + gboolean need_eos; GST_STREAM_LOCK (pad); - ret = gst_basesink_finish_preroll (basesink, pad, NULL); - if (ret == GST_FLOW_OK) { - gboolean need_eos; - GST_LOCK (basesink); - need_eos = basesink->eos = TRUE; - if (basesink->clock) { - /* wait for last buffer to finish if we have a valid end time */ - if (GST_CLOCK_TIME_IS_VALID (basesink->end_time)) { - basesink->clock_id = gst_clock_new_single_shot_id (basesink->clock, - basesink->end_time + GST_ELEMENT (basesink)->base_time); - GST_UNLOCK (basesink); + GST_PREROLL_LOCK (pad); + gst_basesink_preroll_queue_empty (basesink, pad); + GST_PREROLL_UNLOCK (pad); - gst_clock_id_wait (basesink->clock_id, NULL); + GST_LOCK (basesink); + need_eos = basesink->eos = TRUE; + if (basesink->clock) { + /* wait for last buffer to finish if we have a valid end time */ + if (GST_CLOCK_TIME_IS_VALID (basesink->end_time)) { + basesink->clock_id = gst_clock_new_single_shot_id (basesink->clock, + basesink->end_time + GST_ELEMENT (basesink)->base_time); + GST_UNLOCK (basesink); - GST_LOCK (basesink); - if (basesink->clock_id) { - gst_clock_id_unref (basesink->clock_id); - basesink->clock_id = NULL; - } - basesink->end_time = GST_CLOCK_TIME_NONE; - need_eos = basesink->eos; + gst_clock_id_wait (basesink->clock_id, NULL); + + GST_LOCK (basesink); + if (basesink->clock_id) { + gst_clock_id_unref (basesink->clock_id); + basesink->clock_id = NULL; } + basesink->end_time = GST_CLOCK_TIME_NONE; + need_eos = basesink->eos; } GST_UNLOCK (basesink); @@ -462,6 +561,7 @@ gst_basesink_event (GstPad * pad, GstEvent * event) /* unlock from a possible state change/preroll */ GST_PREROLL_LOCK (pad); basesink->need_preroll = TRUE; + gst_basesink_preroll_queue_flush (basesink); GST_PREROLL_SIGNAL (pad); GST_PREROLL_UNLOCK (pad); } @@ -528,29 +628,47 @@ gst_basesink_do_sync (GstBaseSink * basesink, GstBuffer * buffer) } } -static GstFlowReturn -gst_basesink_chain_unlocked (GstPad * pad, GstBuffer * buf) +static inline void +gst_basesink_handle_buffer (GstBaseSink * basesink, GstBuffer * buf) { - GstBaseSink *basesink; - GstFlowReturn result = GST_FLOW_OK; GstBaseSinkClass *bclass; - basesink = GST_BASESINK (GST_OBJECT_PARENT (pad)); - - result = gst_basesink_finish_preroll (basesink, pad, buf); - if (result != GST_FLOW_OK) - goto exit; - gst_basesink_do_sync (basesink, buf); bclass = GST_BASESINK_GET_CLASS (basesink); if (bclass->render) bclass->render (basesink, buf); -exit: + DEBUG ("unref %p %p\n", basesink, buf); gst_buffer_unref (buf); +} - return result; +static GstFlowReturn +gst_basesink_chain_unlocked (GstPad * pad, GstBuffer * buf) +{ + GstBaseSink *basesink; + PrerollReturn result; + + basesink = GST_BASESINK (GST_OBJECT_PARENT (pad)); + + DEBUG ("chain_unlocked %p\n", basesink); + + result = gst_basesink_finish_preroll (basesink, pad, buf); + + DEBUG ("chain_unlocked %p after\n", basesink); + + switch (result) { + case PREROLL_QUEUEING: + return GST_FLOW_OK; + case PREROLL_PLAYING: + gst_basesink_handle_buffer (basesink, buf); + return GST_FLOW_OK; + case PREROLL_FLUSHING: + return GST_FLOW_UNEXPECTED; + default: + g_assert_not_reached (); + return GST_FLOW_UNEXPECTED; + } } static GstFlowReturn @@ -591,13 +709,14 @@ gst_basesink_loop (GstPad * pad) if (result != GST_FLOW_OK) goto paused; -exit: + /* default */ GST_STREAM_UNLOCK (pad); return; paused: gst_task_pause (GST_RPAD_TASK (pad)); - goto exit; + GST_STREAM_UNLOCK (pad); + return; } static gboolean @@ -665,6 +784,8 @@ gst_basesink_change_state (GstElement * element) GstBaseSink *basesink = GST_BASESINK (element); GstElementState transition = GST_STATE_TRANSITION (element); + DEBUG ("state change > %p %x\n", basesink, transition); + switch (transition) { case GST_STATE_NULL_TO_READY: break; @@ -673,6 +794,7 @@ gst_basesink_change_state (GstElement * element) * is no data flow in READY so we cqn safely assume we need to preroll. */ basesink->offset = 0; GST_PREROLL_LOCK (basesink->sinkpad); + basesink->preroll_queue = g_queue_new (); basesink->need_preroll = TRUE; basesink->have_preroll = FALSE; GST_PREROLL_UNLOCK (basesink->sinkpad); @@ -715,6 +837,22 @@ gst_basesink_change_state (GstElement * element) break; } case GST_STATE_PAUSED_TO_READY: + /* flush out the data thread if it's locked in finish_preroll */ + GST_PREROLL_LOCK (basesink->sinkpad); + + gst_basesink_preroll_queue_flush (basesink); + g_queue_free (basesink->preroll_queue); + basesink->preroll_queue = NULL; + + if (basesink->have_preroll) + GST_PREROLL_SIGNAL (basesink->sinkpad); + + basesink->need_preroll = FALSE; + basesink->have_preroll = FALSE; + GST_PREROLL_UNLOCK (basesink->sinkpad); + /* make sure the element is finished processing */ + GST_STREAM_LOCK (basesink->sinkpad); + GST_STREAM_UNLOCK (basesink->sinkpad); break; case GST_STATE_READY_TO_NULL: break; @@ -723,5 +861,6 @@ gst_basesink_change_state (GstElement * element) } GST_ELEMENT_CLASS (parent_class)->change_state (element); + DEBUG ("state change < %p %x\n", basesink, transition); return ret; } diff --git a/gst/base/gstbasesink.h b/gst/base/gstbasesink.h index b65d62a2d7..3c64f78ed3 100644 --- a/gst/base/gstbasesink.h +++ b/gst/base/gstbasesink.h @@ -47,6 +47,9 @@ struct _GstBaseSink { GstPad *sinkpad; GstActivateMode pad_mode; + GQueue *preroll_queue; /* with PREROLL_LOCK */ + gint preroll_queue_max_len; /* with PREROLL_LOCK */ + guint64 offset; gboolean has_loop; gboolean has_chain; diff --git a/libs/gst/base/gstbasesink.c b/libs/gst/base/gstbasesink.c index 9fba36011b..7e071e756a 100644 --- a/libs/gst/base/gstbasesink.c +++ b/libs/gst/base/gstbasesink.c @@ -34,7 +34,14 @@ static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", GST_DEBUG_CATEGORY_STATIC (gst_basesink_debug); #define GST_CAT_DEFAULT gst_basesink_debug -/* BaseSink signals and args */ +/* #define DEBUGGING */ +#ifdef DEBUGGING +#define DEBUG(str,args...) g_print (str,##args) +#else +#define DEBUG(str,args...) +#endif + +/* BaseSink signals and properties */ enum { /* FILL ME */ @@ -48,9 +55,10 @@ enum enum { - ARG_0, - ARG_HAS_LOOP, - ARG_HAS_CHAIN + PROP_0, + PROP_HAS_LOOP, + PROP_HAS_CHAIN, + PROP_PREROLL_QUEUE_LEN }; #define _do_init(bla) \ @@ -82,6 +90,8 @@ static void gst_basesink_loop (GstPad * pad); static GstFlowReturn gst_basesink_chain (GstPad * pad, GstBuffer * buffer); static gboolean gst_basesink_activate (GstPad * pad, GstActivateMode mode); static gboolean gst_basesink_event (GstPad * pad, GstEvent * event); +static inline void gst_basesink_handle_buffer (GstBaseSink * basesink, + GstBuffer * buf); static GstStaticPadTemplate * gst_basesink_get_template (GstBaseSink * bsink) @@ -123,14 +133,19 @@ gst_basesink_class_init (GstBaseSinkClass * klass) gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_basesink_set_property); gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_basesink_get_property); - g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_HAS_LOOP, + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HAS_LOOP, g_param_spec_boolean ("has-loop", "has-loop", "Enable loop-based operation", DEFAULT_HAS_LOOP, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); - g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_HAS_CHAIN, + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HAS_CHAIN, g_param_spec_boolean ("has-chain", "has-chain", "Enable chain-based operation", DEFAULT_HAS_CHAIN, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (G_OBJECT_CLASS (klass), + PROP_PREROLL_QUEUE_LEN, + g_param_spec_uint ("preroll-queue-len", "preroll-queue-len", + "Number of buffers to queue during preroll", 0, G_MAXUINT, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); gstelement_class->set_clock = GST_DEBUG_FUNCPTR (gst_basesink_set_clock); gstelement_class->change_state = @@ -262,22 +277,29 @@ gst_basesink_set_property (GObject * object, guint prop_id, { GstBaseSink *sink; - /* it's not null if we got it, but it might not be ours */ sink = GST_BASESINK (object); + GST_LOCK (sink); switch (prop_id) { - case ARG_HAS_LOOP: + case PROP_HAS_LOOP: sink->has_loop = g_value_get_boolean (value); gst_basesink_set_all_pad_functions (sink); break; - case ARG_HAS_CHAIN: + case PROP_HAS_CHAIN: sink->has_chain = g_value_get_boolean (value); gst_basesink_set_all_pad_functions (sink); break; + case PROP_PREROLL_QUEUE_LEN: + /* preroll lock necessary to serialize with finish_preroll */ + GST_PREROLL_LOCK (sink->sinkpad); + sink->preroll_queue_max_len = g_value_get_uint (value); + GST_PREROLL_UNLOCK (sink->sinkpad); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } + GST_UNLOCK (sink); } static void @@ -286,22 +308,24 @@ gst_basesink_get_property (GObject * object, guint prop_id, GValue * value, { GstBaseSink *sink; - /* it's not null if we got it, but it might not be ours */ - g_return_if_fail (GST_IS_BASESINK (object)); - sink = GST_BASESINK (object); + GST_LOCK (sink); switch (prop_id) { - case ARG_HAS_LOOP: + case PROP_HAS_LOOP: g_value_set_boolean (value, sink->has_loop); break; - case ARG_HAS_CHAIN: + case PROP_HAS_CHAIN: g_value_set_boolean (value, sink->has_chain); break; + case PROP_PREROLL_QUEUE_LEN: + g_value_set_uint (value, sink->preroll_queue_max_len); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } + GST_UNLOCK (sink); } static GstStaticPadTemplate * @@ -329,35 +353,94 @@ gst_base_sink_buffer_alloc (GstBaseSink * sink, guint64 offset, guint size, return NULL; } -/* STREAM_LOCK should be held */ -GstFlowReturn +/* with PREROLL_LOCK */ +static void +gst_basesink_preroll_queue_push (GstBaseSink * basesink, GstPad * pad, + GstBuffer * buffer) +{ + if (basesink->preroll_queue->length == 0) { + GstBaseSinkClass *bclass = GST_BASESINK_GET_CLASS (basesink); + + if (bclass->preroll) + bclass->preroll (basesink, buffer); + } + + if (basesink->preroll_queue->length < basesink->preroll_queue_max_len) { + DEBUG ("push %p %p\n", basesink, buffer); + g_queue_push_tail (basesink->preroll_queue, buffer); + } else { + /* block until the state changes, or we get a flush, or something */ + DEBUG ("block %p %p\n", basesink, buffer); + GST_DEBUG ("element %s waiting to finish preroll", + GST_ELEMENT_NAME (basesink)); + basesink->need_preroll = FALSE; + basesink->have_preroll = TRUE; + GST_PREROLL_WAIT (pad); + GST_DEBUG ("done preroll"); + basesink->have_preroll = FALSE; + } +} + +/* with PREROLL_LOCK */ +static void +gst_basesink_preroll_queue_empty (GstBaseSink * basesink, GstPad * pad) +{ + GstBuffer *buf; + GQueue *q = basesink->preroll_queue; + + if (q) { + DEBUG ("empty queue\n"); + while ((buf = g_queue_pop_head (q))) { + DEBUG ("pop %p\n", buf); + gst_basesink_handle_buffer (basesink, buf); + } + DEBUG ("queue len %p %d\n", basesink, q->length); + } +} + +/* with PREROLL_LOCK */ +static void +gst_basesink_preroll_queue_flush (GstBaseSink * basesink) +{ + GstBuffer *buf; + GQueue *q = basesink->preroll_queue; + + DEBUG ("flush %p\n", basesink); + if (q) { + while ((buf = g_queue_pop_head (q))) { + DEBUG ("pop %p\n", buf); + gst_buffer_unref (buf); + } + } +} + +typedef enum +{ + PREROLL_QUEUEING, + PREROLL_PLAYING, + PREROLL_FLUSHING, + PREROLL_ERROR +} PrerollReturn; + +/* with STREAM_LOCK */ +PrerollReturn gst_basesink_finish_preroll (GstBaseSink * basesink, GstPad * pad, GstBuffer * buffer) { gboolean usable; - GstBaseSinkClass *bclass; + DEBUG ("finish preroll %p <\n", basesink); /* lock order is important */ GST_STATE_LOCK (basesink); GST_PREROLL_LOCK (pad); + DEBUG ("finish preroll %p >\n", basesink); if (!basesink->need_preroll) goto no_preroll; - bclass = GST_BASESINK_GET_CLASS (basesink); - - if (bclass->preroll) - bclass->preroll (basesink, buffer); - gst_element_commit_state (GST_ELEMENT (basesink)); GST_STATE_UNLOCK (basesink); - GST_DEBUG ("element %s waiting to finish preroll", - GST_ELEMENT_NAME (basesink)); - basesink->need_preroll = FALSE; - basesink->have_preroll = TRUE; - GST_PREROLL_WAIT (pad); - GST_DEBUG ("done preroll"); - basesink->have_preroll = FALSE; + gst_basesink_preroll_queue_push (basesink, pad, buffer); GST_LOCK (pad); usable = !GST_RPAD_IS_FLUSHING (pad) && GST_RPAD_IS_ACTIVE (pad); @@ -365,26 +448,40 @@ gst_basesink_finish_preroll (GstBaseSink * basesink, GstPad * pad, if (!usable) goto unusable; + if (basesink->need_preroll) + goto still_queueing; + GST_DEBUG ("done preroll"); + gst_basesink_preroll_queue_empty (basesink, pad); + GST_PREROLL_UNLOCK (pad); - return GST_FLOW_OK; + + return PREROLL_PLAYING; no_preroll: { + /* maybe it was another sink that blocked in preroll, need to check for + buffers to drain */ + if (basesink->preroll_queue->length) + gst_basesink_preroll_queue_empty (basesink, pad); GST_PREROLL_UNLOCK (pad); GST_STATE_UNLOCK (basesink); - return GST_FLOW_OK; + return PREROLL_PLAYING; } unusable: { GST_DEBUG ("pad is flushing"); GST_PREROLL_UNLOCK (pad); - return GST_FLOW_UNEXPECTED; + return PREROLL_FLUSHING; + } +still_queueing: + { + GST_PREROLL_UNLOCK (pad); + return PREROLL_QUEUEING; } } - static gboolean gst_basesink_event (GstPad * pad, GstEvent * event) { @@ -396,38 +493,40 @@ gst_basesink_event (GstPad * pad, GstEvent * event) bclass = GST_BASESINK_GET_CLASS (basesink); + DEBUG ("event %p\n", basesink); + if (bclass->event) bclass->event (basesink, event); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_EOS: { - GstFlowReturn ret; + gboolean need_eos; GST_STREAM_LOCK (pad); - ret = gst_basesink_finish_preroll (basesink, pad, NULL); - if (ret == GST_FLOW_OK) { - gboolean need_eos; - GST_LOCK (basesink); - need_eos = basesink->eos = TRUE; - if (basesink->clock) { - /* wait for last buffer to finish if we have a valid end time */ - if (GST_CLOCK_TIME_IS_VALID (basesink->end_time)) { - basesink->clock_id = gst_clock_new_single_shot_id (basesink->clock, - basesink->end_time + GST_ELEMENT (basesink)->base_time); - GST_UNLOCK (basesink); + GST_PREROLL_LOCK (pad); + gst_basesink_preroll_queue_empty (basesink, pad); + GST_PREROLL_UNLOCK (pad); - gst_clock_id_wait (basesink->clock_id, NULL); + GST_LOCK (basesink); + need_eos = basesink->eos = TRUE; + if (basesink->clock) { + /* wait for last buffer to finish if we have a valid end time */ + if (GST_CLOCK_TIME_IS_VALID (basesink->end_time)) { + basesink->clock_id = gst_clock_new_single_shot_id (basesink->clock, + basesink->end_time + GST_ELEMENT (basesink)->base_time); + GST_UNLOCK (basesink); - GST_LOCK (basesink); - if (basesink->clock_id) { - gst_clock_id_unref (basesink->clock_id); - basesink->clock_id = NULL; - } - basesink->end_time = GST_CLOCK_TIME_NONE; - need_eos = basesink->eos; + gst_clock_id_wait (basesink->clock_id, NULL); + + GST_LOCK (basesink); + if (basesink->clock_id) { + gst_clock_id_unref (basesink->clock_id); + basesink->clock_id = NULL; } + basesink->end_time = GST_CLOCK_TIME_NONE; + need_eos = basesink->eos; } GST_UNLOCK (basesink); @@ -462,6 +561,7 @@ gst_basesink_event (GstPad * pad, GstEvent * event) /* unlock from a possible state change/preroll */ GST_PREROLL_LOCK (pad); basesink->need_preroll = TRUE; + gst_basesink_preroll_queue_flush (basesink); GST_PREROLL_SIGNAL (pad); GST_PREROLL_UNLOCK (pad); } @@ -528,29 +628,47 @@ gst_basesink_do_sync (GstBaseSink * basesink, GstBuffer * buffer) } } -static GstFlowReturn -gst_basesink_chain_unlocked (GstPad * pad, GstBuffer * buf) +static inline void +gst_basesink_handle_buffer (GstBaseSink * basesink, GstBuffer * buf) { - GstBaseSink *basesink; - GstFlowReturn result = GST_FLOW_OK; GstBaseSinkClass *bclass; - basesink = GST_BASESINK (GST_OBJECT_PARENT (pad)); - - result = gst_basesink_finish_preroll (basesink, pad, buf); - if (result != GST_FLOW_OK) - goto exit; - gst_basesink_do_sync (basesink, buf); bclass = GST_BASESINK_GET_CLASS (basesink); if (bclass->render) bclass->render (basesink, buf); -exit: + DEBUG ("unref %p %p\n", basesink, buf); gst_buffer_unref (buf); +} - return result; +static GstFlowReturn +gst_basesink_chain_unlocked (GstPad * pad, GstBuffer * buf) +{ + GstBaseSink *basesink; + PrerollReturn result; + + basesink = GST_BASESINK (GST_OBJECT_PARENT (pad)); + + DEBUG ("chain_unlocked %p\n", basesink); + + result = gst_basesink_finish_preroll (basesink, pad, buf); + + DEBUG ("chain_unlocked %p after\n", basesink); + + switch (result) { + case PREROLL_QUEUEING: + return GST_FLOW_OK; + case PREROLL_PLAYING: + gst_basesink_handle_buffer (basesink, buf); + return GST_FLOW_OK; + case PREROLL_FLUSHING: + return GST_FLOW_UNEXPECTED; + default: + g_assert_not_reached (); + return GST_FLOW_UNEXPECTED; + } } static GstFlowReturn @@ -591,13 +709,14 @@ gst_basesink_loop (GstPad * pad) if (result != GST_FLOW_OK) goto paused; -exit: + /* default */ GST_STREAM_UNLOCK (pad); return; paused: gst_task_pause (GST_RPAD_TASK (pad)); - goto exit; + GST_STREAM_UNLOCK (pad); + return; } static gboolean @@ -665,6 +784,8 @@ gst_basesink_change_state (GstElement * element) GstBaseSink *basesink = GST_BASESINK (element); GstElementState transition = GST_STATE_TRANSITION (element); + DEBUG ("state change > %p %x\n", basesink, transition); + switch (transition) { case GST_STATE_NULL_TO_READY: break; @@ -673,6 +794,7 @@ gst_basesink_change_state (GstElement * element) * is no data flow in READY so we cqn safely assume we need to preroll. */ basesink->offset = 0; GST_PREROLL_LOCK (basesink->sinkpad); + basesink->preroll_queue = g_queue_new (); basesink->need_preroll = TRUE; basesink->have_preroll = FALSE; GST_PREROLL_UNLOCK (basesink->sinkpad); @@ -715,6 +837,22 @@ gst_basesink_change_state (GstElement * element) break; } case GST_STATE_PAUSED_TO_READY: + /* flush out the data thread if it's locked in finish_preroll */ + GST_PREROLL_LOCK (basesink->sinkpad); + + gst_basesink_preroll_queue_flush (basesink); + g_queue_free (basesink->preroll_queue); + basesink->preroll_queue = NULL; + + if (basesink->have_preroll) + GST_PREROLL_SIGNAL (basesink->sinkpad); + + basesink->need_preroll = FALSE; + basesink->have_preroll = FALSE; + GST_PREROLL_UNLOCK (basesink->sinkpad); + /* make sure the element is finished processing */ + GST_STREAM_LOCK (basesink->sinkpad); + GST_STREAM_UNLOCK (basesink->sinkpad); break; case GST_STATE_READY_TO_NULL: break; @@ -723,5 +861,6 @@ gst_basesink_change_state (GstElement * element) } GST_ELEMENT_CLASS (parent_class)->change_state (element); + DEBUG ("state change < %p %x\n", basesink, transition); return ret; } diff --git a/libs/gst/base/gstbasesink.h b/libs/gst/base/gstbasesink.h index b65d62a2d7..3c64f78ed3 100644 --- a/libs/gst/base/gstbasesink.h +++ b/libs/gst/base/gstbasesink.h @@ -47,6 +47,9 @@ struct _GstBaseSink { GstPad *sinkpad; GstActivateMode pad_mode; + GQueue *preroll_queue; /* with PREROLL_LOCK */ + gint preroll_queue_max_len; /* with PREROLL_LOCK */ + guint64 offset; gboolean has_loop; gboolean has_chain; diff --git a/tests/check/gst/gstobject.c b/tests/check/gst/gstobject.c index 06339cf49b..52f92e9ba0 100644 --- a/tests/check/gst/gstobject.c +++ b/tests/check/gst/gstobject.c @@ -98,7 +98,9 @@ START_TEST (test_fake_object_name) object = g_object_new (gst_fake_object_get_type (), NULL); name = gst_object_get_name (object); - fail_if (name != NULL, "Newly created object has a name"); + fail_if (name == NULL, "Newly created object has no name"); + fail_if (strncmp (name, "fakeobject", 10) != 0, + "Random name %s does not start with Gst", name); /* give a random name by setting with NULL; * GstFakeObject class -> fakeobject%d */