diff --git a/docs/manual/advanced-dataaccess.xml b/docs/manual/advanced-dataaccess.xml
index 28fc56ca31..b57f25b91a 100644
--- a/docs/manual/advanced-dataaccess.xml
+++ b/docs/manual/advanced-dataaccess.xml
@@ -1248,6 +1248,7 @@ main (int argc, char **argv)
in the pipeline, it is possible that the pipeline needs to
negotiate a new format and this can fail. Usually you can fix this
by inserting the right converter elements where needed.
+ See also .
@@ -1262,7 +1263,311 @@ main (int argc, char **argv)
Changing elements in a pipeline
- WRITEME
+ In the next example we look at the following chain of elements:
+
+
+ - ----. .----------. .---- -
+ element1 | | element2 | | element3
+ src -> sink src -> sink
+ - ----' '----------' '---- -
+
+
+ We want to change element2 by element4 while the pipeline is in
+ the PLAYING state. Let's say that element2 is a visualization and
+ that you want to switch the visualization in the pipeline.
+
+
+ We can't just unlink element2's sinkpad from element1's source
+ pad because that would leave element1's source pad
+ unlinked and would cause a streaming error in the pipeline when
+ data is pushed on the source pad.
+ The technique is to block the dataflow from element1's source pad
+ before we change element2 by element4 and then resume dataflow
+ as shown in the following steps:
+
+
+
+
+ Block element1's source pad with a blocking pad probe. When the
+ pad is blocked, the probe callback will be called.
+
+
+
+
+ Inside the block callback nothing is flowing between element1
+ and element2 and nothing will flow until unblocked.
+
+
+
+
+ Unlink element1 and element2.
+
+
+
+
+ Make sure data is flushed out of element2. Some elements might
+ internally keep some data, you need to make sure not to lose data
+ by forcing it out of element2. You can do this by pushing EOS into
+ element2, like this:
+
+
+
+
+ Put an event probe on element2's source pad.
+
+
+
+
+ Send EOS to element2's sinkpad. This makes sure the all the
+ data inside element2 is forced out.
+
+
+
+
+ Wait for the EOS event to appear on element2's source pad.
+ When the EOS is received, drop it and remove the event
+ probe.
+
+
+
+
+
+
+ Unlink element2 and element3. You can now also remove element2
+ from the pipeline and set the state to NULL.
+
+
+
+
+ Add element4 to the pipeline, if not already added. Link element4
+ and element3. Link element1 and element4.
+
+
+
+
+ Make sure element4 is in the same state as the rest of the elements
+ in the pipeline. It should be at least in the PAUSED state before
+ it can receive buffers and events.
+
+
+
+
+ Unblock element1's source pad probe. This will let new data into
+ element4 and continue streaming.
+
+
+
+
+ The above algorithm works when the source pad is blocked, i.e. when
+ there is dataflow in the pipeline. If there is no dataflow, there is
+ also no point in changing the element (just yet) so this algorithm can
+ be used in the PAUSED state as well.
+
+
+ Let show you how this works with an example. This example changes the
+ video effect on a simple pipeline every second.
+
+
+
+
+
+static gchar *opt_effects = NULL;
+
+#define DEFAULT_EFFECTS "identity,exclusion,navigationtest," \
+ "agingtv,videoflip,vertigotv,gaussianblur,shagadelictv,edgetv"
+
+static GstPad *blockpad;
+static GstElement *conv_before;
+static GstElement *conv_after;
+static GstElement *cur_effect;
+static GstElement *pipeline;
+
+static GQueue effects = G_QUEUE_INIT;
+
+static GstPadProbeReturn
+event_probe_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+{
+ GMainLoop *loop = user_data;
+ GstElement *next;
+
+ if (GST_EVENT_TYPE (GST_PAD_PROBE_INFO_DATA (info)) != GST_EVENT_EOS)
+ return GST_PAD_PROBE_OK;
+
+ gst_pad_remove_probe (pad, GST_PAD_PROBE_INFO_ID (info));
+
+ /* push current event back into the queue */
+ g_queue_push_tail (&effects, gst_object_ref (cur_effect));
+ /* take next effect from the queue */
+ next = g_queue_pop_head (&effects);
+ if (next == NULL) {
+ GST_DEBUG_OBJECT (pad, "no more effects");
+ g_main_loop_quit (loop);
+ return GST_PAD_PROBE_DROP;
+ }
+
+ g_print ("Switching from '%s' to '%s'..\n", GST_OBJECT_NAME (cur_effect),
+ GST_OBJECT_NAME (next));
+
+ gst_element_set_state (cur_effect, GST_STATE_NULL);
+
+ /* remove unlinks automatically */
+ GST_DEBUG_OBJECT (pipeline, "removing %" GST_PTR_FORMAT, cur_effect);
+ gst_bin_remove (GST_BIN (pipeline), cur_effect);
+
+ GST_DEBUG_OBJECT (pipeline, "adding %" GST_PTR_FORMAT, next);
+ gst_bin_add (GST_BIN (pipeline), next);
+
+ GST_DEBUG_OBJECT (pipeline, "linking..");
+ gst_element_link_many (conv_before, next, conv_after, NULL);
+
+ gst_element_set_state (next, GST_STATE_PLAYING);
+
+ cur_effect = next;
+ GST_DEBUG_OBJECT (pipeline, "done");
+
+ return GST_PAD_PROBE_DROP;
+}
+
+static GstPadProbeReturn
+pad_probe_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+{
+ GstPad *srcpad, *sinkpad;
+
+ GST_DEBUG_OBJECT (pad, "pad is blocked now");
+
+ /* remove the probe first */
+ gst_pad_remove_probe (pad, GST_PAD_PROBE_INFO_ID (info));
+
+ /* install new probe for EOS */
+ srcpad = gst_element_get_static_pad (cur_effect, "src");
+ gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_BLOCK |
+ GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, event_probe_cb, user_data, NULL);
+ gst_object_unref (srcpad);
+
+ /* push EOS into the element, the probe will be fired when the
+ * EOS leaves the effect and it has thus drained all of its data */
+ sinkpad = gst_element_get_static_pad (cur_effect, "sink");
+ gst_pad_send_event (sinkpad, gst_event_new_eos ());
+ gst_object_unref (sinkpad);
+
+ return GST_PAD_PROBE_OK;
+}
+
+static gboolean
+timeout_cb (gpointer user_data)
+{
+ gst_pad_add_probe (blockpad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
+ pad_probe_cb, user_data, NULL);
+
+ return TRUE;
+}
+
+static gboolean
+bus_cb (GstBus * bus, GstMessage * msg, gpointer user_data)
+{
+ GMainLoop *loop = user_data;
+
+ switch (GST_MESSAGE_TYPE (msg)) {
+ case GST_MESSAGE_ERROR:{
+ GError *err = NULL;
+ gchar *dbg;
+
+ gst_message_parse_error (msg, &err, &dbg);
+ gst_object_default_error (msg->src, err, dbg);
+ g_error_free (err);
+ g_free (dbg);
+ g_main_loop_quit (loop);
+ break;
+ }
+ default:
+ break;
+ }
+ return TRUE;
+}
+
+int
+main (int argc, char **argv)
+{
+ GOptionEntry options[] = {
+ {"effects", 'e', 0, G_OPTION_ARG_STRING, &opt_effects,
+ "Effects to use (comma-separated list of element names)", NULL},
+ {NULL}
+ };
+ GOptionContext *ctx;
+ GError *err = NULL;
+ GMainLoop *loop;
+ GstElement *src, *sink;
+ gchar **effect_names, **e;
+
+ ctx = g_option_context_new ("");
+ g_option_context_add_main_entries (ctx, options, NULL);
+ g_option_context_add_group (ctx, gst_init_get_option_group ());
+ if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
+ g_print ("Error initializing: %s\n", err->message);
+ return 1;
+ }
+ g_option_context_free (ctx);
+
+ if (opt_effects != NULL)
+ effect_names = g_strsplit (opt_effects, ",", -1);
+ else
+ effect_names = g_strsplit (DEFAULT_EFFECTS, ",", -1);
+
+ for (e = effect_names; e != NULL && *e != NULL; ++e) {
+ GstElement *el;
+
+ el = gst_element_factory_make (*e, NULL);
+ if (el) {
+ g_print ("Adding effect '%s'\n", *e);
+ g_queue_push_tail (&effects, el);
+ }
+ }
+
+ pipeline = gst_pipeline_new ("pipeline");
+
+ src = gst_element_factory_make ("videotestsrc", NULL);
+ g_object_set (src, "is-live", TRUE, NULL);
+
+ blockpad = gst_element_get_static_pad (src, "src");
+
+ conv_before = gst_element_factory_make ("videoconvert", NULL);
+
+ cur_effect = g_queue_pop_head (&effects);
+
+ conv_after = gst_element_factory_make ("videoconvert", NULL);
+
+ sink = gst_element_factory_make ("ximagesink", NULL);
+
+ gst_bin_add_many (GST_BIN (pipeline), src, conv_before, cur_effect,
+ conv_after, sink, NULL);
+
+ gst_element_link_many (src, conv_before, cur_effect, conv_after,
+ sink, NULL);
+
+ gst_element_set_state (pipeline, GST_STATE_PLAYING);
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ gst_bus_add_watch (GST_ELEMENT_BUS (pipeline), bus_cb, loop);
+
+ g_timeout_add_seconds (1, timeout_cb, loop);
+
+ g_main_loop_run (loop);
+
+ gst_element_set_state (pipeline, GST_STATE_NULL);
+ gst_object_unref (pipeline);
+
+ return 0;
+}
+]]>
+
+
+
+ Note how we added videoconvert elements before and after the effect.
+ This is needed because some elements might operate in different
+ colorspaces than other elements. By inserting the conversion elements
+ you ensure that the right format can be negotiated at any time.
diff --git a/tests/examples/manual/Makefile.am b/tests/examples/manual/Makefile.am
index 6e79e67cd6..a2db29bcd5 100644
--- a/tests/examples/manual/Makefile.am
+++ b/tests/examples/manual/Makefile.am
@@ -40,6 +40,7 @@ EXAMPLES = \
appsrc \
appsink \
dynformat \
+ effectswitch \
playbin \
decodebin
@@ -57,6 +58,7 @@ BUILT_SOURCES = \
appsrc.c \
appsink.c \
dynformat.c \
+ effectswitch.c \
playbin.c decodebin.c
CLEANFILES = core core.* test-registry.* *.gcno *.gcda $(BUILT_SOURCES)
@@ -104,6 +106,9 @@ appsink.c: $(top_srcdir)/docs/manual/advanced-dataaccess.xml
dynformat.c: $(top_srcdir)/docs/manual/advanced-dataaccess.xml
$(PERL_PATH) $(srcdir)/extract.pl $@ $<
+effectswitch.c: $(top_srcdir)/docs/manual/advanced-dataaccess.xml
+ $(PERL_PATH) $(srcdir)/extract.pl $@ $<
+
playbin.c decodebin.c: $(top_srcdir)/docs/manual/highlevel-components.xml
$(PERL_PATH) $(srcdir)/extract.pl $@ $<