commit 02752f1c6cceae2b1772a75639c9278fd9673adb
Author: Duncan Palmer <dpalmer@digisoft.tv>
Date:   Wed Feb 18 16:43:59 2015 +1000

    DDTWO-1727 hlsdemux: bitrate calculation protoype
    
    Conflicts:
    	ext/hls/gsthlsdemux.c
    	ext/hls/gsthlsdemux.h

diff --git a/ext/hls/gsthlsdemux.c b/ext/hls/gsthlsdemux.c
index ff07a44..6477955 100644
--- a/ext/hls/gsthlsdemux.c
+++ b/ext/hls/gsthlsdemux.c
@@ -194,7 +194,7 @@ gst_hls_demux_class_init (GstHLSDemuxClass * klass)
       g_param_spec_float ("bitrate-limit",
           "Bitrate limit in %",
           "Limit of the available bitrate to use when switching to alternates.",
-          0, 1, DEFAULT_BITRATE_LIMIT,
+          0, 2, DEFAULT_BITRATE_LIMIT,
           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   g_object_class_install_property (gobject_class, PROP_CONNECTION_SPEED,
@@ -243,6 +243,7 @@ gst_hls_demux_init (GstHLSDemux * demux)
   /* Properties */
   demux->bitrate_limit = DEFAULT_BITRATE_LIMIT;
   demux->connection_speed = DEFAULT_CONNECTION_SPEED;
+  demux->min_buffer_size = 256 * 1024;  /* @todo make this a property */
 
   g_mutex_init (&demux->download_lock);
   g_cond_init (&demux->download_cond);
@@ -799,6 +800,8 @@ _src_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
   GstHLSDemux *demux = (GstHLSDemux *) GST_PAD_PARENT (srcpad);
   GstFlowReturn ret;
   GstCaps *caps;
+  gboolean new_buffer = FALSE;
+  gint64 time;
 
   /* Is it encrypted? */
   if (demux->current_key) {
@@ -877,31 +880,56 @@ _src_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
     return GST_FLOW_OK;
   }
 
-  if (demux->starting_fragment) {
-    GST_LOG_OBJECT (demux, "set buffer pts=%" GST_TIME_FORMAT,
-        GST_TIME_ARGS (demux->current_timestamp));
-    GST_BUFFER_PTS (buffer) = demux->current_timestamp;
+  /* Buffer up before pushing downstream, to allow us to accurately calculate
+   * bitrate. */
+  if (G_UNLIKELY (!demux->buffer)) {
+    demux->buffer = gst_buffer_new ();
+    new_buffer = TRUE;
+  }
+  {
+    gsize before = gst_buffer_get_size (demux->buffer);
+    gsize after;
 
-    if (demux->segment.rate < 0)
-      /* Set DISCONT flag for every first buffer in reverse playback mode
-       * as each fragment for its own has to be reversed */
-      demux->discont = TRUE;
-    demux->starting_fragment = FALSE;
-    demux->segment.position = GST_BUFFER_PTS (buffer);
-  } else {
-    GST_BUFFER_PTS (buffer) = GST_CLOCK_TIME_NONE;
+    demux->buffer = gst_buffer_append (demux->buffer, buffer);
+
+    after = gst_buffer_get_size (demux->buffer);
+
+    if (before < (demux->min_buffer_size / 2)
+        && after >= (demux->min_buffer_size / 2)) {
+      GST_DEBUG_OBJECT (demux, "setting start time. before: %u after: %u",
+          before, after);
+      demux->download_start_time = g_get_monotonic_time ();
+    }
   }
 
-  GST_BUFFER_DURATION (buffer) = GST_CLOCK_TIME_NONE;
-  GST_BUFFER_DTS (buffer) = GST_CLOCK_TIME_NONE;
+  if (new_buffer) {
+    if (demux->starting_fragment) {
+      GST_LOG_OBJECT (demux, "set buffer pts=%" GST_TIME_FORMAT,
+          GST_TIME_ARGS (demux->current_timestamp));
+      GST_BUFFER_PTS (demux->buffer) = demux->current_timestamp;
+
+      if (demux->segment.rate < 0)
+        /* Set DISCONT flag for every first buffer in reverse playback mode
+         * as each fragment for its own has to be reversed */
+        demux->discont = TRUE;
+      demux->starting_fragment = FALSE;
+      demux->segment.position = GST_BUFFER_PTS (demux->buffer);
+    } else {
+      GST_BUFFER_PTS (demux->buffer) = GST_CLOCK_TIME_NONE;
+    }
+
+    GST_BUFFER_DURATION (demux->buffer) = GST_CLOCK_TIME_NONE;
+    GST_BUFFER_DTS (demux->buffer) = GST_CLOCK_TIME_NONE;
+  }
 
   /* We actually need to do this every time we switch bitrate */
   if (G_UNLIKELY (demux->do_typefind)) {
-    caps = gst_type_find_helper_for_buffer (NULL, buffer, NULL);
+    caps = gst_type_find_helper_for_buffer (NULL, demux->buffer, NULL);
     if (G_UNLIKELY (!caps)) {
       GST_ELEMENT_ERROR (demux, STREAM, TYPE_NOT_FOUND,
           ("Could not determine type of stream"), (NULL));
-      gst_buffer_unref (buffer);
+      gst_buffer_unref (demux->buffer);
+      demux->buffer = NULL;
       demux->last_ret = GST_FLOW_NOT_NEGOTIATED;
       return GST_FLOW_NOT_NEGOTIATED;
     }
@@ -916,15 +944,18 @@ _src_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
     gst_caps_unref (caps);
   }
 
-  if (demux->discont) {
-    GST_DEBUG_OBJECT (demux, "Marking fragment as discontinuous");
-    GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
-    demux->discont = FALSE;
-  } else {
-    GST_BUFFER_FLAG_UNSET (buffer, GST_BUFFER_FLAG_DISCONT);
-  }
+  if (new_buffer) {
+    if (demux->discont) {
+      GST_DEBUG_OBJECT (demux, "Marking fragment as discontinuous");
+      GST_BUFFER_FLAG_SET (demux->buffer, GST_BUFFER_FLAG_DISCONT);
+      demux->discont = FALSE;
+    } else {
+      GST_BUFFER_FLAG_UNSET (demux->buffer, GST_BUFFER_FLAG_DISCONT);
+    }
 
-  demux->starting_fragment = FALSE;
+    /* @todo - this is set to FALSE earlier. Why is it here? */
+    demux->starting_fragment = FALSE;
+  }
 
   if (demux->need_segment) {
     /* And send a newsegment */
@@ -934,28 +965,43 @@ _src_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
     demux->need_segment = FALSE;
   }
 
-  /* accumulate time and size to get this chunk */
-  demux->download_total_time +=
-      g_get_monotonic_time () - demux->download_start_time;
-  demux->download_total_bytes += gst_buffer_get_size (buffer);
+  if (gst_buffer_get_size (demux->buffer) >= demux->min_buffer_size) {
 
-  ret = gst_proxy_pad_chain_default (pad, parent, buffer);
-  demux->download_start_time = g_get_monotonic_time ();
+    /* accumulate time and size to get this chunk */
+    demux->download_total_time +=
+        g_get_monotonic_time () - demux->download_start_time;
+    time = g_get_monotonic_time ();
+    demux->download_total_bytes +=
+        gst_buffer_get_size (demux->buffer) - demux->min_buffer_size / 2;
 
-  if (ret != GST_FLOW_OK) {
-    if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS) {
-      GST_ELEMENT_ERROR (demux, STREAM, FAILED, (NULL),
-          ("stream stopped, reason %s", gst_flow_get_name (ret)));
-      gst_pad_push_event (demux->srcpad, gst_event_new_eos ());
-    } else {
-      GST_DEBUG_OBJECT (demux, "stream stopped, reason %s",
-          gst_flow_get_name (ret));
+    GST_DEBUG_OBJECT (demux, "push");
+    ret = gst_proxy_pad_chain_default (pad, parent, demux->buffer);
+    demux->buffer = NULL;
+
+    demux->push_time += g_get_monotonic_time () - time;
+    demux->download_start_time = g_get_monotonic_time ();
+
+
+    if (ret != GST_FLOW_OK) {
+      if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_EOS) {
+        GST_ELEMENT_ERROR (demux, STREAM, FAILED, (NULL),
+            ("stream stopped, reason %s", gst_flow_get_name (ret)));
+        gst_pad_push_event (demux->srcpad, gst_event_new_eos ());
+      } else {
+        GST_DEBUG_OBJECT (demux, "stream stopped, reason %s",
+            gst_flow_get_name (ret));
+      }
+      gst_hls_demux_pause_tasks (demux);
     }
-    gst_hls_demux_pause_tasks (demux);
+
+    /* avoid having the source handle the same error again */
+    demux->last_ret = ret;
+  } else {
+    GST_DEBUG_OBJECT (demux, "buffer size: %u",
+        (unsigned int) (gst_buffer_get_size (demux->buffer)));
   }
 
-  /* avoid having the source handle the same error again */
-  demux->last_ret = ret;
+
   ret = GST_FLOW_OK;
 
   return ret;
@@ -997,18 +1043,35 @@ _src_event (GstPad * pad, GstObject * parent, GstEvent * event)
 
           gst_buffer_resize (demux->pending_buffer, 0, unpadded_size);
 
+
+          if (G_UNLIKELY (!demux->buffer))
+            demux->buffer = gst_buffer_new ();
+          demux->buffer =
+              gst_buffer_append (demux->buffer, demux->pending_buffer);
+          demux->pending_buffer = NULL;
+        }
+
+        /* Push out the last of our buffered data */
+        if (demux->buffer) {
+#if 0
           demux->download_total_time +=
               g_get_monotonic_time () - demux->download_start_time;
-          demux->download_total_bytes +=
-              gst_buffer_get_size (demux->pending_buffer);
-          demux->last_ret = gst_pad_push (demux->srcpad, demux->pending_buffer);
+          demux->download_total_bytes += gst_buffer_get_size (demux->buffer);
+#endif
 
-          demux->pending_buffer = NULL;
+          GST_DEBUG_OBJECT (demux, "last push");
+          demux->last_ret = gst_pad_push (demux->srcpad, demux->buffer);
+
+          demux->buffer = NULL;
         }
       } else {
         if (demux->pending_buffer)
           gst_buffer_unref (demux->pending_buffer);
         demux->pending_buffer = NULL;
+        if (demux->buffer) {
+          gst_buffer_unref (demux->buffer);
+          demux->buffer = NULL;
+        }
       }
 
       GST_DEBUG_OBJECT (demux, "Fragment download finished");
@@ -1258,6 +1321,8 @@ gst_hls_demux_stream_loop (GstHLSDemux * demux)
   gst_hls_demux_switch_playlist (demux);
   demux->download_total_bytes = 0;
   demux->download_total_time = 0;
+  demux->push_time = 0;
+
 
   GST_DEBUG_OBJECT (demux, "Finished pushing fragment");
 
@@ -1615,8 +1680,8 @@ retry:
 
     GST_M3U8_CLIENT_LOCK (demux->client);
     last_sequence =
-        GST_M3U8_MEDIA_FILE (g_list_last (demux->client->current->
-            files)->data)->sequence;
+        GST_M3U8_MEDIA_FILE (g_list_last (demux->client->current->files)->
+        data)->sequence;
 
     if (demux->client->sequence >= last_sequence - 3) {
       GST_DEBUG_OBJECT (demux, "Sequence is beyond playlist. Moving back to %u",
@@ -1720,8 +1785,8 @@ retry_failover_protection:
     gst_m3u8_client_set_current (demux->client, previous_variant->data);
     /*  Try a lower bitrate (or stop if we just tried the lowest) */
     if (GST_M3U8 (previous_variant->data)->iframe && new_bandwidth ==
-        GST_M3U8 (g_list_first (demux->client->main->iframe_lists)->data)->
-        bandwidth)
+        GST_M3U8 (g_list_first (demux->client->main->iframe_lists)->
+            data)->bandwidth)
       return FALSE;
     else if (!GST_M3U8 (previous_variant->data)->iframe && new_bandwidth ==
         GST_M3U8 (g_list_first (demux->client->main->lists)->data)->bandwidth)
@@ -1752,6 +1817,14 @@ gst_hls_demux_switch_playlist (GstHLSDemux * demux)
       (guint) demux->download_total_bytes,
       GST_TIME_ARGS (demux->download_total_time * GST_USECOND), (gint) bitrate);
 
+  GST_DEBUG_OBJECT (demux,
+      "fragment: %" GST_TIME_FORMAT " download: %" GST_TIME_FORMAT " push: %"
+      GST_TIME_FORMAT,
+      GST_TIME_ARGS ((g_get_monotonic_time () -
+              demux->fragment_start_time) * GST_USECOND),
+      GST_TIME_ARGS (demux->download_total_time * GST_USECOND),
+      GST_TIME_ARGS (demux->push_time * GST_USECOND));
+
   /* Take old rate into account too */
   if (demux->current_download_rate != -1)
     bitrate = (demux->current_download_rate + bitrate * 3) / 4;
@@ -2048,6 +2121,7 @@ gst_hls_demux_get_next_fragment (GstHLSDemux * demux,
       gst_pad_push_event (demux->src_srcpad, gst_event_new_flush_start ());
       gst_pad_push_event (demux->src_srcpad, gst_event_new_flush_stop (TRUE));
 
+      demux->fragment_start_time = g_get_monotonic_time ();
       demux->download_start_time = g_get_monotonic_time ();
       gst_element_sync_state_with_parent (demux->src);
 
diff --git a/ext/hls/gsthlsdemux.h b/ext/hls/gsthlsdemux.h
index f594ced..5d4c4b2 100644
--- a/ext/hls/gsthlsdemux.h
+++ b/ext/hls/gsthlsdemux.h
@@ -81,6 +81,7 @@ struct _GstHLSDemux
   /* Properties */
   gfloat bitrate_limit;         /* limit of the available bitrate to use */
   guint connection_speed;       /* Network connection speed in kbps (0 = unknown) */
+  gsize min_buffer_size;
 
   /* Streaming task */
   GstTask *stream_task;
@@ -115,6 +116,7 @@ struct _GstHLSDemux
   /* fragment download tooling */
   GstElement *src;
   GstPad *src_srcpad; /* handy link to src's src pad */
+  GstBuffer *buffer;
   GMutex fragment_download_lock;
   GCond fragment_download_cond;
   GstClockTime current_timestamp;
@@ -127,6 +129,9 @@ struct _GstHLSDemux
   GstFlowReturn last_ret;
   GError *last_error;
 
+  gint64 fragment_start_time;
+  gint64 push_time;
+
   /* decryption tooling */
 #ifdef HAVE_NETTLE
   struct CBC_CTX (struct aes_ctx, AES_BLOCK_SIZE) aes_ctx;
