diff options
author | Sebastian Dröge <sebastian.droege@collabora.co.uk> | 2010-07-04 16:52:14 +0200 |
---|---|---|
committer | Sebastian Dröge <sebastian.droege@collabora.co.uk> | 2010-07-07 10:17:33 +0200 |
commit | 52cc8b19ee1252b3f190fd1315b8b6dce884664e (patch) | |
tree | b8ac6bf4fad9461e2060f995ca95c4e1658efcc9 | |
parent | f12a99c35560ac2a36354dbd5bec700d7505d4c9 (diff) |
gconf: First try of supporting element switching during playback in switchsinkgconf-wip
-rw-r--r-- | ext/gconf/gstswitchsink.c | 227 | ||||
-rw-r--r-- | ext/gconf/gstswitchsink.h | 12 |
2 files changed, 224 insertions, 15 deletions
diff --git a/ext/gconf/gstswitchsink.c b/ext/gconf/gstswitchsink.c index 1fccf683f..e93bd055a 100644 --- a/ext/gconf/gstswitchsink.c +++ b/ext/gconf/gstswitchsink.c @@ -2,6 +2,7 @@ * Copyright (c) 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net> * Copyright (c) 2006 Jürg Billeter <j@bitron.ch> * Copyright (c) 2007 Jan Schmidt <thaytan@noraisin.net> + * Copyright (c) 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -33,11 +34,11 @@ GST_DEBUG_CATEGORY_STATIC (switch_debug); static void gst_switch_sink_dispose (GObject * object); static GstStateChangeReturn gst_switch_sink_change_state (GstElement * element, GstStateChange transition); +static gboolean gst_switch_sink_commit_new_kid (GstSwitchSink * sink); -enum -{ - PROP_0 -}; +static gboolean gst_switch_sink_event (GstPad * pad, GstEvent * event); +static GstFlowReturn gst_switch_sink_bufferalloc (GstPad * pad, guint64 offset, + guint size, GstCaps * caps, GstBuffer ** buf); GST_BOILERPLATE (GstSwitchSink, gst_switch_sink, GstBin, GST_TYPE_BIN); @@ -72,6 +73,11 @@ gst_switch_sink_class_init (GstSwitchSinkClass * klass) static gboolean gst_switch_sink_reset (GstSwitchSink * sink) { + if (sink->blocking) { + gst_pad_set_blocked (sink->pad, FALSE); + sink->blocking = FALSE; + } + /* this will install fakesink if no other child has been set, * otherwise we rely on the subclass to know when to unset its * custom kid */ @@ -90,6 +96,12 @@ gst_switch_sink_init (GstSwitchSink * sink, GstSwitchSinkClass * g_class) templ = gst_element_class_get_pad_template (eklass, "sink"); sink->pad = gst_ghost_pad_new_no_target_from_template ("sink", templ); + sink->ghostpad_event_func = GST_PAD_EVENTFUNC (sink->pad); + gst_pad_set_event_function (sink->pad, + GST_DEBUG_FUNCPTR (gst_switch_sink_event)); + sink->ghostpad_bufferalloc_func = GST_PAD_BUFFERALLOCFUNC (sink->pad); + gst_pad_set_bufferalloc_function (sink->pad, + GST_DEBUG_FUNCPTR (gst_switch_sink_bufferalloc)); gst_element_add_pad (GST_ELEMENT (sink), sink->pad); gst_switch_sink_reset (sink); @@ -117,6 +129,30 @@ gst_switch_sink_dispose (GObject * object) GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object)); } +static void +gst_switch_sink_reset_old_kids (GstElement * old_kid, GstSwitchSink * sink) +{ + GST_DEBUG_OBJECT (sink, "Resetting old kid: %s", GST_ELEMENT_NAME (old_kid)); + gst_element_set_state (old_kid, GST_STATE_NULL); + gst_object_unref (old_kid); +} + +static void +gst_switch_sink_pad_blocked (GstPad * pad, gboolean blocked, + GstSwitchSink * sink) +{ + if (!blocked) { + GST_DEBUG_OBJECT (sink, "Unblocked pad"); + sink->blocking = FALSE; + return; + } + + sink->blocking = TRUE; + + GST_DEBUG_OBJECT (sink, "Blocked pad"); + gst_switch_sink_commit_new_kid (sink); +} + static gboolean gst_switch_sink_commit_new_kid (GstSwitchSink * sink) { @@ -164,7 +200,8 @@ gst_switch_sink_commit_new_kid (GstSwitchSink * sink) gst_element_set_bus (new_kid, bus); gst_object_unref (bus); - if (gst_element_set_state (new_kid, kid_state) == GST_STATE_CHANGE_FAILURE) { + if (gst_element_set_state (new_kid, + GST_STATE_READY) == GST_STATE_CHANGE_FAILURE) { GstMessage *msg; /* check if child posted an error message and if so re-post it on our bus @@ -195,11 +232,16 @@ gst_switch_sink_commit_new_kid (GstSwitchSink * sink) /* kill old element */ if (old_kid) { GST_DEBUG_OBJECT (sink, "Removing old kid %" GST_PTR_FORMAT, old_kid); - gst_element_set_state (old_kid, GST_STATE_NULL); gst_bin_remove (GST_BIN (sink), old_kid); - gst_object_unref (old_kid); /* Don't lose the SINK flag */ GST_OBJECT_FLAG_SET (sink, GST_ELEMENT_IS_SINK); + + if (!sink->blocking) { + gst_element_set_state (old_kid, GST_STATE_NULL); + gst_object_unref (old_kid); + } else { + g_thread_pool_push (sink->thread_pool, old_kid, NULL); + } } /* re-attach ghostpad */ @@ -209,7 +251,38 @@ gst_switch_sink_commit_new_kid (GstSwitchSink * sink) gst_object_unref (targetpad); GST_DEBUG_OBJECT (sink, "done changing child of switchsink"); - /* FIXME: Push new-segment info and pre-roll buffer(s) into the kid */ + if (gst_element_set_state (new_kid, kid_state) == GST_STATE_CHANGE_FAILURE) { + GST_ELEMENT_ERROR (sink, CORE, STATE_CHANGE, (NULL), + ("Failed to set state on new child.")); + GST_OBJECT_LOCK (sink); + gst_object_unref (sink->kid); + sink->kid = NULL; + GST_OBJECT_UNLOCK (sink); + return FALSE; + } + + if (sink->segment.format != GST_FORMAT_UNDEFINED) { + GstEvent *event; + GstSegment *segment = &sink->segment; + + event = gst_event_new_new_segment_full (FALSE, segment->rate, + segment->applied_rate, segment->format, 0, segment->accum, 0); + + gst_pad_send_event (sink->pad, event); + + event = gst_event_new_new_segment_full (FALSE, segment->rate, + segment->applied_rate, segment->format, + segment->start, segment->stop, segment->time); + + gst_pad_send_event (sink->pad, event); + } + + /* Unblock the target pad if necessary */ + if (sink->blocking) { + gst_pad_set_blocked_async_full (sink->pad, FALSE, + (GstPadBlockCallback) gst_switch_sink_pad_blocked, + gst_object_ref (sink), (GDestroyNotify) gst_object_unref); + } return TRUE; } @@ -234,14 +307,14 @@ gst_switch_sink_set_child (GstSwitchSink * sink, GstElement * new_kid) if (new_kid) gst_object_unref (new_kid); - /* Sometime, it would be lovely to allow sink changes even when - * already running, but this involves sending an appropriate new-segment - * and possibly prerolling etc */ - /* FIXME: Block the pad and replace the kid when it completes */ if (cur > GST_STATE_READY || next == GST_STATE_PAUSED) { - GST_DEBUG_OBJECT (sink, - "Switch-sink is already running. Ignoring change of child."); - gst_object_unref (new_kid); + GST_DEBUG_OBJECT (sink, "Already running, switching child after pad-block"); + if (!sink->blocking) { + sink->blocking = TRUE; + gst_pad_set_blocked_async_full (sink->pad, TRUE, + (GstPadBlockCallback) gst_switch_sink_pad_blocked, + gst_object_ref (sink), (GDestroyNotify) gst_object_unref); + } return TRUE; } @@ -254,6 +327,18 @@ gst_switch_sink_change_state (GstElement * element, GstStateChange transition) GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; GstSwitchSink *sink = GST_SWITCH_SINK (element); + switch (transition) { + case GST_STATE_CHANGE_NULL_TO_READY: + sink->thread_pool = + g_thread_pool_new ((GFunc) gst_switch_sink_reset_old_kids, sink, 1, + FALSE, NULL); + case GST_STATE_CHANGE_READY_TO_PAUSED: + gst_segment_init (&sink->segment, GST_FORMAT_UNDEFINED); + break; + default: + break; + } + ret = GST_CALL_PARENT_WITH_DEFAULT (GST_ELEMENT_CLASS, change_state, (element, transition), GST_STATE_CHANGE_SUCCESS); @@ -261,6 +346,11 @@ gst_switch_sink_change_state (GstElement * element, GstStateChange transition) case GST_STATE_CHANGE_READY_TO_NULL: if (!gst_switch_sink_reset (sink)) ret = GST_STATE_CHANGE_FAILURE; + + if (sink->thread_pool) { + g_thread_pool_free (sink->thread_pool, FALSE, TRUE); + sink->thread_pool = NULL; + } break; default: break; @@ -268,3 +358,110 @@ gst_switch_sink_change_state (GstElement * element, GstStateChange transition) return ret; } + +static gboolean +gst_switch_sink_event (GstPad * pad, GstEvent * event) +{ + GstSwitchSink *sink = GST_SWITCH_SINK (gst_pad_get_parent (pad)); + gboolean ret; + + if (GST_EVENT_TYPE (event) == GST_EVENT_FLUSH_STOP) { + GST_DEBUG_OBJECT (sink, "Resetting segment because of flush-stop event"); + gst_segment_init (&sink->segment, GST_FORMAT_UNDEFINED); + } + + ret = sink->ghostpad_event_func (pad, gst_event_ref (event)); + + if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) { + gboolean update; + gdouble rate, applied_rate; + GstFormat format; + gint64 start, stop, position; + + GST_DEBUG_OBJECT (sink, "Newsegment event: %" GST_PTR_FORMAT, + event->structure); + gst_event_parse_new_segment_full (event, &update, &rate, &applied_rate, + &format, &start, &stop, &position); + + if (sink->segment.format != GST_FORMAT_UNDEFINED + && format != sink->segment.format) { + GST_ERROR_OBJECT (sink, + "Newsegment event in different format: %s (expected %s)", + gst_format_get_name (format), + gst_format_get_name (sink->segment.format)); + gst_object_unref (event); + gst_object_unref (sink); + return FALSE; + } + + GST_DEBUG_OBJECT (pad, "Old segment: %" GST_SEGMENT_FORMAT, &sink->segment); + gst_segment_set_newsegment_full (&sink->segment, update, rate, + applied_rate, format, start, stop, position); + GST_DEBUG_OBJECT (sink, "New video segment: %" GST_SEGMENT_FORMAT, + &sink->segment); + } + + gst_event_unref (event); + gst_object_unref (sink); + + return ret; +} + +static GstFlowReturn +gst_switch_sink_bufferalloc (GstPad * pad, guint64 offset, guint size, + GstCaps * caps, GstBuffer ** buf) +{ + GstSwitchSink *sink = GST_SWITCH_SINK (gst_pad_get_parent (pad)); + GstFlowReturn ret; + GstPad *target; + + target = gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (pad)); + if (target) { + if (GST_PAD_CAPS (target) != caps + && !gst_caps_is_equal (caps, GST_PAD_CAPS (target))) { + GST_DEBUG_OBJECT (sink, + "Have new caps: %" GST_PTR_FORMAT " -> %" GST_PTR_FORMAT, + GST_PAD_CAPS (target), caps); + + if (!gst_pad_accept_caps (target, caps)) { + GST_DEBUG_OBJECT (sink, + "New target did not accept caps, trying to get possible caps"); + caps = gst_pad_get_caps (target); + + if (caps) { + GST_DEBUG_OBJECT (sink, "Sink has caps %" GST_PTR_FORMAT, caps); + if (!gst_caps_is_fixed (caps)) { + gst_caps_truncate (caps); + gst_pad_fixate_caps (target, caps); + + if (!gst_caps_is_fixed (caps)) { + GST_ERROR_OBJECT (sink, "Unable to fixate caps"); + gst_caps_unref (caps); + ret = GST_FLOW_NOT_NEGOTIATED; + *buf = NULL; + goto done; + } + + GST_DEBUG_OBJECT (sink, "Caps fixated to %" GST_PTR_FORMAT, caps); + } + + ret = GST_FLOW_OK; + *buf = gst_buffer_new (); + gst_buffer_set_caps (*buf, caps); + gst_caps_unref (caps); + } + + if (!*buf) + ret = GST_FLOW_NOT_NEGOTIATED; + goto done; + } + } + gst_object_unref (target); + } + + ret = sink->ghostpad_bufferalloc_func (pad, offset, size, caps, buf); + +done: + gst_object_unref (sink); + return ret; +} diff --git a/ext/gconf/gstswitchsink.h b/ext/gconf/gstswitchsink.h index 556e75536..f78167432 100644 --- a/ext/gconf/gstswitchsink.h +++ b/ext/gconf/gstswitchsink.h @@ -1,6 +1,7 @@ /* GStreamer * Copyright (c) 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net> * Copyright (c) 2007 Jan Schmidt <thaytan@mad.scientist.com> + * Copyright (c) 2010 Sebastian Dröge <sebastian.droege@collabora.co.uk> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public @@ -47,6 +48,17 @@ typedef struct _GstSwitchSink { /* If a custom child has been set... */ gboolean have_kid; + + /* Track incoming segment info for switchover */ + GstSegment segment; + GstPadEventFunction ghostpad_event_func; + GstPadBufferAllocFunction ghostpad_bufferalloc_func; + + /* If the pad is blocking/blocked */ + gboolean blocking; + + /* Thread pool for resetting the old kids to NULL */ + GThreadPool *thread_pool; } GstSwitchSink; typedef struct _GstSwitchSinkClass { |