manual: add example for effect switching
authorWim Taymans <wim.taymans@collabora.co.uk>
Mon, 8 Oct 2012 08:39:30 +0000 (10:39 +0200)
committerWim Taymans <wim.taymans@collabora.co.uk>
Mon, 8 Oct 2012 08:39:30 +0000 (10:39 +0200)
docs/manual/advanced-dataaccess.xml
tests/examples/manual/Makefile.am

index 28fc56c..b57f25b 100644 (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>
index 6e79e67..a2db29b 100644 (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 $@ $<