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 $@ $<