diff --git a/docs/manual/advanced-buffering.xml b/docs/manual/advanced-buffering.xml index 91fcb0a9dd..6d1660fafe 100644 --- a/docs/manual/advanced-buffering.xml +++ b/docs/manual/advanced-buffering.xml @@ -58,12 +58,12 @@ 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; @@ -256,14 +256,195 @@ Buffering strategies - WRITEME + What follows are some ideas for implementing different buffering + strategies based on the buffering messages and buffering query. - - 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. - - + + No-rebuffer strategy + + 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. + + + 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 + ). + + + This is what the code would look like: + + + + + +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 (&argc, &argv); + loop = g_main_loop_new (NULL, FALSE); + + /* make sure we have a URI */ + if (argc != 2) { + g_print ("Usage: %s <URI>\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; +} +]]> + + + + 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. + + + diff --git a/tests/examples/manual/.gitignore b/tests/examples/manual/.gitignore index 4cf9593d4f..1f16c7d065 100644 --- a/tests/examples/manual/.gitignore +++ b/tests/examples/manual/.gitignore @@ -22,12 +22,15 @@ queue threads bin decodebin +dynamic elementcreate elementfactory elementlink ghostpad pad playbin +playsink +norebuffer probe query fakesrc diff --git a/tests/examples/manual/Makefile.am b/tests/examples/manual/Makefile.am index 396c100368..dec8a3fc1c 100644 --- a/tests/examples/manual/Makefile.am +++ b/tests/examples/manual/Makefile.am @@ -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 $@ $<