manual: add example of no-rebuffer buffering strategy
authorWim Taymans <wim.taymans@collabora.co.uk>
Thu, 11 Oct 2012 15:10:17 +0000 (17:10 +0200)
committerWim Taymans <wim.taymans@collabora.co.uk>
Thu, 11 Oct 2012 15:10:17 +0000 (17:10 +0200)
docs/manual/advanced-buffering.xml
tests/examples/manual/.gitignore
tests/examples/manual/Makefile.am

index 91fcb0a..6d1660f 100644 (file)
     case GST_MESSAGE_BUFFERING:{
       gint percent;
 
-      gst_message_parse_buffering (message, &percent);
-
       /* no state management needed for live pipelines */
       if (is_live)
         break;
 
+      gst_message_parse_buffering (message, &percent);
+
       if (percent == 100) {
         /* a 100% message means buffering is done */
         buffering = FALSE;
   <sect1 id="section-buffering-strategies">
     <title>Buffering strategies </title>
     <para>
-      WRITEME
-    </para>
-    <para>
-      The application can use the BUFFERING query to get the estimated
-      download time and match this time to the current/remaining playback time
-      to control when playback should start to have a non-interrupted playback
-      experience.
+      What follows are some ideas for implementing different buffering
+      strategies based on the buffering messages and buffering query.
     </para>
-  </sect1>
 
+    <sect2 id="section-buffering-norebuffer">
+      <title>No-rebuffer strategy </title>
+      <para>
+        We would like to buffer enough data in the pipeline so that playback
+        continues without interruptions. What we need to know to implement
+        this is know the total remaining playback time in the file and the
+        total remaining download time. If the buffering time is less than the
+        playback time, we can start playback without interruptions.
+      </para>
+      <para>
+        We have all this information available with the DURATION, POSITION and
+        BUFFERING queries. We need to periodically execute the buffering query
+        to get the current buffering status. We also need to have a large
+        enough buffer to hold the complete file, worst case. It is best to
+        use this buffering strategy with download buffering (see
+        <xref linkend="section-buffering-download"/>).
+      </para>
+      <para>
+        This is what the code would look like:
+      </para>
+      <programlisting>
+<!-- example-begin norebuffer.c a -->
+<![CDATA[
+#include <gst/gst.h>
+
+GstState target_state;
+static gboolean is_live;
+static gboolean is_buffering;
+
+static gboolean
+buffer_timeout (gpointer data)
+{
+  GstElement *pipeline = data;
+  GstQuery *query;
+  gboolean busy;
+  gint percent;
+  gint64 estimated_total;
+  gint64 position, duration;
+  guint64 play_left;
+
+  query = gst_query_new_buffering (GST_FORMAT_TIME);
+
+  if (!gst_element_query (pipeline, query))
+    return TRUE;
+
+  gst_query_parse_buffering_percent (query, &busy, &percent);
+  gst_query_parse_buffering_range (query, NULL, NULL, NULL, &estimated_total);
+
+  if (estimated_total == -1)
+    estimated_total = 0;
+
+  /* calculate the remaining playback time */
+  if (!gst_element_query_position (pipeline, GST_FORMAT_TIME, &position))
+    position = -1;
+  if (!gst_element_query_duration (pipeline, GST_FORMAT_TIME, &duration))
+    duration = -1;
+
+  if (duration != -1 && position != -1)
+    play_left = GST_TIME_AS_MSECONDS (duration - position);
+  else
+    play_left = 0;
+
+  g_message ("play_left %" G_GUINT64_FORMAT", estimated_total %" G_GUINT64_FORMAT
+      ", percent %d", play_left, estimated_total, percent);
+
+  /* we are buffering or the estimated download time is bigger than the
+   * remaining playback time. We keep buffering. */
+  is_buffering = (busy || estimated_total * 1.1 > play_left);
+
+  if (!is_buffering)
+    gst_element_set_state (pipeline, target_state);
+
+  return is_buffering;
+}
+
+static void
+on_message_buffering (GstBus *bus, GstMessage *message, gpointer user_data)
+{
+  GstElement *pipeline = user_data;
+  gint percent;
+
+  /* no state management needed for live pipelines */
+  if (is_live)
+    return;
+
+  gst_message_parse_buffering (message, &percent);
+
+  if (percent < 100) {
+    /* buffering busy */
+    if (is_buffering == FALSE) {
+      is_buffering = TRUE;
+      if (target_state == GST_STATE_PLAYING) {
+        /* we were not buffering but PLAYING, PAUSE  the pipeline. */
+        gst_element_set_state (pipeline, GST_STATE_PAUSED);
+      }
+    }
+  }
+}
+
+static void
+on_message_async_done (GstBus *bus, GstMessage *message, gpointer user_data)
+{
+  GstElement *pipeline = user_data;
+
+  if (is_buffering == FALSE)
+    gst_element_set_state (pipeline, target_state);
+  else
+    g_timeout_add (500, buffer_timeout, pipeline);
+}
+
+gint
+main (gint   argc,
+      gchar *argv[])
+{
+  GstElement *pipeline;
+  GMainLoop *loop;
+  GstBus *bus;
+  GstStateChangeReturn ret;
+
+  /* init GStreamer */
+  gst_init (&amp;argc, &amp;argv);
+  loop = g_main_loop_new (NULL, FALSE);
+
+  /* make sure we have a URI */
+  if (argc != 2) {
+    g_print ("Usage: %s &lt;URI&gt;\n", argv[0]);
+    return -1;
+  }
+
+  /* set up */
+  pipeline = gst_element_factory_make ("playbin", "pipeline");
+  g_object_set (G_OBJECT (pipeline), "uri", argv[1], NULL);
+  g_object_set (G_OBJECT (pipeline), "flags", 0x697 , NULL);
+
+  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
+  gst_bus_add_signal_watch (bus);
+
+  g_signal_connect (bus, "message::buffering",
+    (GCallback) on_message_buffering, pipeline);
+  g_signal_connect (bus, "message::async-done",
+    (GCallback) on_message_async_done, pipeline);
+  gst_object_unref (bus);
+
+  is_buffering = FALSE;
+  target_state = GST_STATE_PLAYING;
+  ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+
+  switch (ret) {
+    case GST_STATE_CHANGE_SUCCESS:
+      is_live = FALSE;
+      break;
+
+    case GST_STATE_CHANGE_FAILURE:
+      g_warning ("failed to PAUSE");
+      return -1;
+
+    case GST_STATE_CHANGE_NO_PREROLL:
+      is_live = TRUE;
+      break;
+
+    default:
+      break;
+  }
+
+  /* now run */
+  g_main_loop_run (loop);
+
+  /* also clean up */
+  gst_element_set_state (pipeline, GST_STATE_NULL);
+  gst_object_unref (GST_OBJECT (pipeline));
+  g_main_loop_unref (loop);
+
+  return 0;
+}
+]]>
+<!-- example-end norebuffer.c a -->
+      </programlisting>
+      <para>
+        See how we set the pipeline to the PAUSED state first. We will receive
+        buffering messages during the preroll state when buffering is needed.
+        When we are prerolled (on_message_async_done) we see if buffering is
+        going on, if not, we start playback. If buffering was going on, we start
+        a timeout to poll the buffering state. If the estimated time to download
+        is less than the remaining playback time, we start playback.
+      </para>
+    </sect2>
+  </sect1>
 </chapter>
index 4cf9593..1f16c7d 100644 (file)
@@ -22,12 +22,15 @@ queue
 threads
 bin
 decodebin
+dynamic
 elementcreate
 elementfactory
 elementlink
 ghostpad
 pad
 playbin
+playsink
+norebuffer
 probe
 query
 fakesrc
index 396c100..dec8a3f 100644 (file)
@@ -41,6 +41,7 @@ EXAMPLES = \
        dynformat \
        effectswitch \
        testrtpool \
+       norebuffer \
        playbin \
        decodebin \
        playsink 
@@ -61,6 +62,7 @@ BUILT_SOURCES = \
        dynformat.c \
        effectswitch.c \
        testrtpool.c \
+       norebuffer.c \
        playbin.c decodebin.c \
        playsink.c
 
@@ -112,6 +114,9 @@ dynformat.c: $(top_srcdir)/docs/manual/advanced-dataaccess.xml
 effectswitch.c: $(top_srcdir)/docs/manual/advanced-dataaccess.xml
        $(PERL_PATH) $(srcdir)/extract.pl $@ $<
 
+norebuffer.c: $(top_srcdir)/docs/manual/advanced-buffering.xml
+       $(PERL_PATH) $(srcdir)/extract.pl $@ $<
+
 playbin.c decodebin.c playsink.c: $(top_srcdir)/docs/manual/highlevel-playback.xml
        $(PERL_PATH) $(srcdir)/extract.pl $@ $<