manual: add example for effect switching

This commit is contained in:
Wim Taymans 2012-10-08 10:39:30 +02:00
parent dc2080fb78
commit 402f0166c1
2 changed files with 311 additions and 1 deletions

View file

@ -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 <xref linkend="section-dynamic-changing"/>.
</para>
</listitem>
</itemizedlist>
@ -1262,7 +1263,311 @@ main (int argc, char **argv)
<sect2 id="section-dynamic-changing">
<title>Changing elements in a pipeline</title>
<para>
WRITEME
In the next example we look at the following chain of elements:
</para>
<programlisting>
- ----. .----------. .---- -
element1 | | element2 | | element3
src -> sink src -> sink
- ----' '----------' '---- -
</programlisting>
<para>
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.
</para>
<para>
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:
</para>
<itemizedlist>
<listitem>
<para>
Block element1's source pad with a blocking pad probe. When the
pad is blocked, the probe callback will be called.
</para>
</listitem>
<listitem>
<para>
Inside the block callback nothing is flowing between element1
and element2 and nothing will flow until unblocked.
</para>
</listitem>
<listitem>
<para>
Unlink element1 and element2.
</para>
</listitem>
<listitem>
<para>
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:
</para>
<itemizedlist>
<listitem>
<para>
Put an event probe on element2's source pad.
</para>
</listitem>
<listitem>
<para>
Send EOS to element2's sinkpad. This makes sure the all the
data inside element2 is forced out.
</para>
</listitem>
<listitem>
<para>
Wait for the EOS event to appear on element2's source pad.
When the EOS is received, drop it and remove the event
probe.
</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para>
Unlink element2 and element3. You can now also remove element2
from the pipeline and set the state to NULL.
</para>
</listitem>
<listitem>
<para>
Add element4 to the pipeline, if not already added. Link element4
and element3. Link element1 and element4.
</para>
</listitem>
<listitem>
<para>
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.
</para>
</listitem>
<listitem>
<para>
Unblock element1's source pad probe. This will let new data into
element4 and continue streaming.
</para>
</listitem>
</itemizedlist>
<para>
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.
</para>
<para>
Let show you how this works with an example. This example changes the
video effect on a simple pipeline every second.
</para>
<programlisting>
<!-- example-begin effectswitch.c -->
<![CDATA[
#include <gst/gst.h>
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;
}
]]>
<!-- example-end effectswitch.c -->
</programlisting>
<para>
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.
</para>
</sect2>
</sect1>

View file

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