audioparsers: Add bitrate calculation to baseparse
authorArun Raghavan <arun.raghavan@collabora.co.uk>
Thu, 25 Mar 2010 11:22:58 +0000 (11:22 +0000)
committerTim-Philipp Müller <tim.muller@collabora.co.uk>
Fri, 8 Apr 2011 17:07:04 +0000 (18:07 +0100)
This makes baseparse keep a running average of the stream bitrate, as
well as the minimum and maximum bitrates. Subclasses can override a
vfunc to make sure that per-frame overhead from the container is not
accounted for in the bitrate calculation.

We take care not to override the bitrate, minimum-bitrate, and
maximum-bitrate tags if they have been posted upstream. We also
rate-limit the emission of bitrate so that it is only triggered by a
change of >10 kbps.

gst/audioparsers/gstbaseparse.c
gst/audioparsers/gstbaseparse.h

index 4c9468c..3416bf6 100644 (file)
@@ -228,8 +228,16 @@ struct _GstBaseParsePrivate
 
   guint64 framecount;
   guint64 bytecount;
+  guint64 data_bytecount;
   guint64 acc_duration;
 
+  gboolean post_min_bitrate;
+  gboolean post_avg_bitrate;
+  gboolean post_max_bitrate;
+  guint min_bitrate;
+  guint avg_bitrate;
+  guint max_bitrate;
+
   GList *pending_events;
 
   GstBuffer *cache;
@@ -281,6 +289,7 @@ static gboolean gst_base_parse_sink_activate_pull (GstPad * pad,
     gboolean active);
 static gboolean gst_base_parse_handle_seek (GstBaseParse * parse,
     GstEvent * event);
+static void gst_base_parse_handle_tag (GstBaseParse * parse, GstEvent * event);
 
 static gboolean gst_base_parse_src_event (GstPad * pad, GstEvent * event);
 static gboolean gst_base_parse_sink_event (GstPad * pad, GstEvent * event);
@@ -545,6 +554,11 @@ gst_base_parse_sink_event (GstPad * pad, GstEvent * event)
       && GST_EVENT_TYPE (event) != GST_EVENT_NEWSEGMENT
       && GST_EVENT_TYPE (event) != GST_EVENT_FLUSH_START
       && GST_EVENT_TYPE (event) != GST_EVENT_FLUSH_STOP) {
+
+    if (GST_EVENT_TYPE (event) == GST_EVENT_TAG)
+      /* See if any bitrate tags were posted */
+      gst_base_parse_handle_tag (parse, event);
+
     parse->priv->pending_events =
         g_list_append (parse->priv->pending_events, event);
     ret = TRUE;
@@ -871,6 +885,91 @@ gst_base_parse_update_duration (GstBaseParse * aacparse)
 }
 
 /**
+ * gst_base_parse_update_bitrates:
+ * @parse: #GstBaseParse.
+ * @buffer: Current frame as a #GstBuffer
+ *
+ * Keeps track of the minimum and maximum bitrates, and also maintains a
+ * running average bitrate of the stream so far.
+ */
+static void
+gst_base_parse_update_bitrates (GstBaseParse * parse, GstBuffer * buffer)
+{
+  /* Only update the tag on a 10 kbps delta */
+  static const gint update_threshold = 10000;
+
+  GstBaseParseClass *klass;
+  guint64 data_len, frame_dur;
+  gint overhead = 0, frame_bitrate, old_avg_bitrate = parse->priv->avg_bitrate;
+  gboolean update_min = FALSE, update_avg = FALSE, update_max = FALSE;
+
+  klass = GST_BASE_PARSE_GET_CLASS (parse);
+
+  if (klass->get_frame_overhead) {
+    overhead = klass->get_frame_overhead (parse, buffer);
+    if (overhead == -1)
+      return;
+  }
+
+  data_len = GST_BUFFER_SIZE (buffer) - overhead;
+  parse->priv->data_bytecount += data_len;
+
+  if (parse->priv->fps_num) {
+    /* Calculate duration of a frame from frame properties */
+    frame_dur = (GST_SECOND * parse->priv->fps_den) / parse->priv->fps_num;
+    parse->priv->avg_bitrate = (8 * parse->priv->data_bytecount * GST_SECOND) /
+        (parse->priv->framecount * frame_dur);
+
+  } else if (GST_BUFFER_DURATION_IS_VALID (buffer)) {
+    /* Calculate duration of a frame from buffer properties */
+    frame_dur = GST_BUFFER_DURATION (buffer);
+    parse->priv->avg_bitrate = (8 * parse->priv->data_bytecount * GST_SECOND) /
+        parse->priv->acc_duration;
+
+  } else {
+    /* No way to figure out frame duration (is this even possible?) */
+    return;
+  }
+
+  frame_bitrate = (8 * data_len * GST_SECOND) / frame_dur;
+
+  if (frame_bitrate < parse->priv->min_bitrate) {
+    parse->priv->min_bitrate = frame_bitrate;
+    update_min = parse->priv->post_min_bitrate;
+  }
+
+  if (frame_bitrate > parse->priv->max_bitrate) {
+    parse->priv->max_bitrate = frame_bitrate;
+    update_max = parse->priv->post_max_bitrate;
+  }
+
+  if (old_avg_bitrate / update_threshold !=
+      parse->priv->avg_bitrate / update_threshold)
+    update_avg = parse->priv->post_avg_bitrate;
+
+  if (update_min || update_avg || update_max) {
+    GstTagList *taglist = gst_tag_list_new ();
+
+    if (update_min)
+      gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, GST_TAG_MINIMUM_BITRATE,
+          parse->priv->min_bitrate, NULL);
+    if (update_avg)
+      gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, GST_TAG_BITRATE,
+          parse->priv->avg_bitrate, NULL);
+    if (update_max)
+      gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, GST_TAG_MAXIMUM_BITRATE,
+          parse->priv->max_bitrate, NULL);
+
+    GST_DEBUG_OBJECT (parse, "Updated bitrates. Min: %u, Avg: %u, Max: %u",
+        parse->priv->min_bitrate, parse->priv->avg_bitrate,
+        parse->priv->max_bitrate);
+
+    gst_element_found_tags_for_pad (GST_ELEMENT (parse), parse->srcpad,
+        taglist);
+  }
+}
+
+/**
  * gst_base_parse_handle_and_push_buffer:
  * @parse: #GstBaseParse.
  * @klass: #GstBaseParseClass.
@@ -966,6 +1065,8 @@ gst_base_parse_push_buffer (GstBaseParse * parse, GstBuffer * buffer)
       (parse->priv->framecount % parse->priv->update_interval) == 0)
     gst_base_parse_update_duration (parse);
 
+  gst_base_parse_update_bitrates (parse, buffer);
+
   if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
     last_stop = GST_BUFFER_TIMESTAMP (buffer);
   if (last_stop != GST_CLOCK_TIME_NONE && GST_BUFFER_DURATION_IS_VALID (buffer))
@@ -1493,6 +1594,12 @@ gst_base_parse_activate (GstBaseParse * parse, gboolean active)
     parse->priv->estimated_duration = -1;
     parse->priv->next_ts = 0;
     parse->priv->passthrough = FALSE;
+    parse->priv->post_min_bitrate = TRUE;
+    parse->priv->post_avg_bitrate = TRUE;
+    parse->priv->post_max_bitrate = TRUE;
+    parse->priv->min_bitrate = G_MAXUINT;
+    parse->priv->max_bitrate = 0;
+    parse->priv->max_bitrate = 0;
 
     if (parse->pending_segment)
       gst_event_unref (parse->pending_segment);
@@ -2109,6 +2216,29 @@ wrong_type:
   }
 }
 
+/**
+ * gst_base_parse_handle_tag:
+ * @parse: #GstBaseParse.
+ * @event: #GstEvent.
+ *
+ * Checks if bitrates are available from upstream tags so that we don't
+ * override them later
+ */
+static void
+gst_base_parse_handle_tag (GstBaseParse * parse, GstEvent * event)
+{
+  GstTagList *taglist = NULL;
+  guint tmp;
+
+  gst_event_parse_tag (event, &taglist);
+
+  if (gst_tag_list_get_uint (taglist, GST_TAG_MINIMUM_BITRATE, &tmp))
+    parse->priv->post_min_bitrate = FALSE;
+  if (gst_tag_list_get_uint (taglist, GST_TAG_BITRATE, &tmp))
+    parse->priv->post_avg_bitrate = FALSE;
+  if (gst_tag_list_get_uint (taglist, GST_TAG_MAXIMUM_BITRATE, &tmp))
+    parse->priv->post_max_bitrate = FALSE;
+}
 
 /**
  * gst_base_parse_sink_setcaps:
index a6a5f54..08620b4 100644 (file)
@@ -184,6 +184,12 @@ struct _GstBaseParse {
  *                  seekability of the stream. Otherwise the element assumes
  *                  that stream is always seekable.
  *
+ * @get_frame_overhead: Finds the metadata overhead for the given frame. This
+ *                      is used to enable more accurate bitrate computations.
+ *                      If NULL, the per-frame overhead is assumed to be 0. If
+ *                      this returns -1, it is assumed that this frame should
+ *                      be skipped in bitrate calculation.
+ *
  * Subclasses can override any of the available virtual methods or not, as
  * needed. At minimum @check_valid_frame and @parse_frame needs to be
  * overridden.
@@ -228,6 +234,9 @@ struct _GstBaseParseClass {
 
   gboolean      (*is_seekable)        (GstBaseParse *parse);
 
+  gint          (*get_frame_overhead) (GstBaseParse *parse,
+                                       GstBuffer *buf);
+
   /*< private >*/
   gpointer       _gst_reserved[GST_PADDING_LARGE];  
   GstBaseParseClassPrivate *priv;