summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Dröge <sebastian.droege@collabora.co.uk>2010-07-04 16:52:14 +0200
committerSebastian Dröge <sebastian.droege@collabora.co.uk>2010-07-07 10:17:33 +0200
commit52cc8b19ee1252b3f190fd1315b8b6dce884664e (patch)
treeb8ac6bf4fad9461e2060f995ca95c4e1658efcc9
parentf12a99c35560ac2a36354dbd5bec700d7505d4c9 (diff)
gconf: First try of supporting element switching during playback in switchsinkgconf-wip
-rw-r--r--ext/gconf/gstswitchsink.c227
-rw-r--r--ext/gconf/gstswitchsink.h12
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 {