baseparse: Allow chaining of subclass event handlers
[platform/upstream/gst-plugins-good.git] / gst / audioparsers / gstbaseparse.c
index 8e2a2ae..3bea83b 100644 (file)
 
 #include "gstbaseparse.h"
 
+#define MIN_FRAMES_TO_POST_BITRATE 10
+
 GST_DEBUG_CATEGORY_STATIC (gst_base_parse_debug);
 #define GST_CAT_DEFAULT gst_base_parse_debug
 
@@ -228,22 +230,23 @@ 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;
 };
 
-struct _GstBaseParseClassPrivate
-{
-  gpointer _padding;
-};
-
 static GstElementClass *parent_class = NULL;
 
-static void gst_base_parse_base_init (gpointer g_class);
-static void gst_base_parse_base_finalize (gpointer g_class);
 static void gst_base_parse_class_init (GstBaseParseClass * klass);
 static void gst_base_parse_init (GstBaseParse * parse,
     GstBaseParseClass * klass);
@@ -256,8 +259,8 @@ gst_base_parse_get_type (void)
   if (!base_parse_type) {
     static const GTypeInfo base_parse_info = {
       sizeof (GstBaseParseClass),
-      (GBaseInitFunc) gst_base_parse_base_init,
-      (GBaseFinalizeFunc) gst_base_parse_base_finalize,
+      (GBaseInitFunc) NULL,
+      (GBaseFinalizeFunc) NULL,
       (GClassInitFunc) gst_base_parse_class_init,
       NULL,
       NULL,
@@ -267,7 +270,7 @@ gst_base_parse_get_type (void)
     };
 
     base_parse_type = g_type_register_static (GST_TYPE_ELEMENT,
-        "GstBaseParse", &base_parse_info, G_TYPE_FLAG_ABSTRACT);
+        "GstAudioBaseParseBad", &base_parse_info, G_TYPE_FLAG_ABSTRACT);
   }
   return base_parse_type;
 }
@@ -281,6 +284,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);
@@ -310,30 +314,8 @@ gboolean gst_base_parse_convert (GstBaseParse * parse, GstFormat src_format,
 
 static void gst_base_parse_drain (GstBaseParse * parse);
 
-static void
-gst_base_parse_base_init (gpointer g_class)
-{
-  GstBaseParseClass *klass = GST_BASE_PARSE_CLASS (g_class);
-  GstBaseParseClassPrivate *priv;
-
-  GST_DEBUG_CATEGORY_INIT (gst_base_parse_debug, "baseparse", 0,
-      "baseparse element");
-
-  /* TODO: Remove this once GObject supports class private data */
-  priv = g_slice_new0 (GstBaseParseClassPrivate);
-  if (klass->priv)
-    memcpy (priv, klass->priv, sizeof (GstBaseParseClassPrivate));
-  klass->priv = priv;
-}
-
-static void
-gst_base_parse_base_finalize (gpointer g_class)
-{
-  GstBaseParseClass *klass = GST_BASE_PARSE_CLASS (g_class);
-
-  g_slice_free (GstBaseParseClassPrivate, klass->priv);
-  klass->priv = NULL;
-}
+static void gst_base_parse_post_bitrates (GstBaseParse * parse,
+    gboolean post_min, gboolean post_avg, gboolean post_max);
 
 static void
 gst_base_parse_finalize (GObject * object)
@@ -379,10 +361,12 @@ gst_base_parse_class_init (GstBaseParseClass * klass)
   /* Default handlers */
   klass->check_valid_frame = gst_base_parse_check_frame;
   klass->parse_frame = gst_base_parse_parse_frame;
-  klass->event = gst_base_parse_sink_eventfunc;
   klass->src_event = gst_base_parse_src_eventfunc;
   klass->is_seekable = gst_base_parse_is_seekable;
   klass->convert = gst_base_parse_convert;
+
+  GST_DEBUG_CATEGORY_INIT (gst_base_parse_debug, "baseparse", 0,
+      "baseparse element");
 }
 
 static void
@@ -545,15 +529,28 @@ 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;
   } else {
 
+    if (GST_EVENT_TYPE (event) == GST_EVENT_EOS &&
+        parse->priv->framecount < MIN_FRAMES_TO_POST_BITRATE)
+      /* We've not posted bitrate tags yet - do so now */
+      gst_base_parse_post_bitrates (parse, TRUE, TRUE, TRUE);
+
     if (bclass->event)
       handled = bclass->event (parse, event);
 
     if (!handled)
+      handled = gst_base_parse_sink_eventfunc (parse, event);
+
+    if (!handled)
       ret = gst_pad_event_default (pad, event);
   }
 
@@ -701,6 +698,8 @@ gst_base_parse_src_event (GstPad * pad, GstEvent * event)
 
   if (!handled)
     ret = gst_pad_event_default (pad, event);
+  else
+    gst_event_unref (event);
 
   gst_object_unref (parse);
   return ret;
@@ -729,7 +728,6 @@ gst_base_parse_src_eventfunc (GstBaseParse * parse, GstEvent * event)
     {
       if (bclass->is_seekable (parse)) {
         handled = gst_base_parse_handle_seek (parse, event);
-        gst_event_unref (event);
       }
       break;
     }
@@ -870,6 +868,99 @@ gst_base_parse_update_duration (GstBaseParse * aacparse)
   }
 }
 
+static void
+gst_base_parse_post_bitrates (GstBaseParse * parse, gboolean post_min,
+    gboolean post_avg, gboolean post_max)
+{
+  GstTagList *taglist = gst_tag_list_new ();
+
+  if (post_min && parse->priv->post_min_bitrate)
+    gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
+        GST_TAG_MINIMUM_BITRATE, parse->priv->min_bitrate, NULL);
+
+  if (post_avg && parse->priv->post_min_bitrate)
+    gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE, GST_TAG_BITRATE,
+        parse->priv->avg_bitrate, NULL);
+
+  if (post_max && parse->priv->post_max_bitrate)
+    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_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 = TRUE;
+  }
+
+  if (frame_bitrate > parse->priv->max_bitrate) {
+    parse->priv->max_bitrate = frame_bitrate;
+    update_max = TRUE;
+  }
+
+  if (old_avg_bitrate / update_threshold !=
+      parse->priv->avg_bitrate / update_threshold)
+    update_avg = TRUE;
+
+  if (parse->priv->framecount >= MIN_FRAMES_TO_POST_BITRATE &&
+      (update_min || update_avg || update_max))
+    gst_base_parse_post_bitrates (parse, update_min, update_avg, update_max);
+}
+
 /**
  * gst_base_parse_handle_and_push_buffer:
  * @parse: #GstBaseParse.
@@ -945,6 +1036,7 @@ GstFlowReturn
 gst_base_parse_push_buffer (GstBaseParse * parse, GstBuffer * buffer)
 {
   GstFlowReturn ret = GST_FLOW_OK;
+  GstClockTime last_start = GST_CLOCK_TIME_NONE;
   GstClockTime last_stop = GST_CLOCK_TIME_NONE;
 
   GST_LOG_OBJECT (parse,
@@ -966,16 +1058,32 @@ 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))
-    last_stop += GST_BUFFER_DURATION (buffer);
+    last_start = last_stop = GST_BUFFER_TIMESTAMP (buffer);
+  if (last_start != GST_CLOCK_TIME_NONE
+      && GST_BUFFER_DURATION_IS_VALID (buffer))
+    last_stop = last_start + GST_BUFFER_DURATION (buffer);
 
   /* should have caps by now */
   g_return_val_if_fail (GST_PAD_CAPS (parse->srcpad), GST_FLOW_ERROR);
 
   gst_buffer_set_caps (buffer, GST_PAD_CAPS (parse->srcpad));
 
+  /* segment times are typically estimates,
+   * actual frame data might lead subclass to different timestamps,
+   * so override segment start from what is supplied there */
+  if (G_UNLIKELY (parse->pending_segment && !parse->priv->passthrough &&
+          GST_CLOCK_TIME_IS_VALID (last_start))) {
+    gst_event_unref (parse->pending_segment);
+    /* stop time possibly lost this way,
+     * but unlikely and not really supported */
+    parse->pending_segment =
+        gst_event_new_new_segment (FALSE, parse->segment.rate,
+        parse->segment.format, last_start, -1, last_start);
+  }
+
   /* and should then also be linked downstream, so safe to send some events */
   if (parse->priv->pad_mode == GST_ACTIVATE_PULL) {
     if (G_UNLIKELY (parse->close_segment)) {
@@ -1236,8 +1344,8 @@ gst_base_parse_pull_range (GstBaseParse * parse, guint size,
   /* Caching here actually makes much less difference than one would expect.
    * We do it mainly to avoid pulling buffers of 1 byte all the time */
   if (parse->priv->cache) {
-    guint64 cache_offset = GST_BUFFER_OFFSET (parse->priv->cache);
-    guint cache_size = GST_BUFFER_SIZE (parse->priv->cache);
+    gint64 cache_offset = GST_BUFFER_OFFSET (parse->priv->cache);
+    gint cache_size = GST_BUFFER_SIZE (parse->priv->cache);
 
     if (cache_offset <= parse->priv->offset &&
         (parse->priv->offset + size) <= (cache_offset + cache_size)) {
@@ -1387,7 +1495,9 @@ gst_base_parse_loop (GstPad * pad)
 
   if (ret != GST_FLOW_OK) {
     GST_DEBUG_OBJECT (parse, "flow: %s", gst_flow_get_name (ret));
-    if (GST_FLOW_IS_FATAL (ret)) {
+    if (ret == GST_FLOW_UNEXPECTED) {
+      gst_pad_push_event (parse->srcpad, gst_event_new_eos ());
+    } else if (ret == GST_FLOW_NOT_LINKED || ret < GST_FLOW_UNEXPECTED) {
       GST_ELEMENT_ERROR (parse, STREAM, FAILED, (NULL),
           ("streaming task paused, reason: %s", gst_flow_get_name (ret)));
       gst_pad_push_event (parse->srcpad, gst_event_new_eos ());
@@ -1480,6 +1590,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);
@@ -1698,7 +1814,7 @@ gst_base_parse_set_frame_props (GstBaseParse * parse, guint fps_num,
  * gst_base_transform_get_sync:
  * @parse: the #GstBaseParse to query
  *
- * Returns TRUE if parser is considered 'in sync'.  That is, frames have been
+ * Returns: TRUE if parser is considered 'in sync'.  That is, frames have been
  * continuously successfully parsed and pushed.
  */
 gboolean
@@ -1721,7 +1837,7 @@ gst_base_parse_get_sync (GstBaseParse * parse)
  * gst_base_transform_get_drain:
  * @parse: the #GstBaseParse to query
  *
- * Returns TRUE if parser is currently 'draining'.  That is, leftover data
+ * Returns: TRUE if parser is currently 'draining'.  That is, leftover data
  * (e.g. in FLUSH or EOS situation) is being parsed.
  */
 gboolean
@@ -1935,6 +2051,11 @@ gst_base_parse_handle_seek (GstBaseParse * parse, GstEvent * event)
   gst_event_parse_seek (event, &rate, &format, &flags,
       &cur_type, &cur, &stop_type, &stop);
 
+  GST_DEBUG_OBJECT (parse, "seek to format %s, "
+      "start type %d at %" GST_TIME_FORMAT ", end type %d at %"
+      GST_TIME_FORMAT, gst_format_get_name (format),
+      cur_type, GST_TIME_ARGS (cur), stop_type, GST_TIME_ARGS (stop));
+
   /* no negative rates yet */
   if (rate < 0.0)
     goto negative_rate;
@@ -1946,20 +2067,42 @@ gst_base_parse_handle_seek (GstBaseParse * parse, GstEvent * event)
    * it directly or fail. For TIME, try upstream, but do it ourselves if
    * it fails upstream */
   if (format != GST_FORMAT_TIME) {
-    return gst_pad_push_event (parse->sinkpad, event);
+    /* default action delegates to upstream */
+    return FALSE;
   } else {
     gst_event_ref (event);
     if (gst_pad_push_event (parse->sinkpad, event)) {
-      gst_event_unref (event);
       return TRUE;
     }
   }
 
+  /* too much estimating going on to support this sensibly,
+   * and no eos/end-of-segment loop handling either ... */
+  if ((stop_type == GST_SEEK_TYPE_SET && stop != GST_CLOCK_TIME_NONE) ||
+      (stop_type != GST_SEEK_TYPE_NONE && stop_type != GST_SEEK_TYPE_SET) ||
+      (flags & GST_SEEK_FLAG_SEGMENT))
+    goto wrong_type;
+  stop = -1;
+
   /* get flush flag */
   flush = flags & GST_SEEK_FLAG_FLUSH;
 
+  /* copy segment, we need this because we still need the old
+   * segment when we close the current segment. */
+  memcpy (&seeksegment, &parse->segment, sizeof (GstSegment));
+
+  GST_DEBUG_OBJECT (parse, "configuring seek");
+  gst_segment_set_seek (&seeksegment, rate, format, flags,
+      cur_type, cur, stop_type, stop, &update);
+
+  /* figure out the last position we need to play. If it's configured (stop !=
+   * -1), use that, else we play until the total duration of the file */
+  if ((stop = seeksegment.stop) == -1)
+    stop = seeksegment.duration;
+
   dstformat = GST_FORMAT_BYTES;
-  if (!gst_pad_query_convert (parse->srcpad, format, cur, &dstformat, &seekpos)) {
+  if (!gst_pad_query_convert (parse->srcpad, format, seeksegment.last_stop,
+          &dstformat, &seekpos)) {
     GST_DEBUG_OBJECT (parse, "conversion failed");
     return FALSE;
   }
@@ -1991,19 +2134,7 @@ gst_base_parse_handle_seek (GstBaseParse * parse, GstEvent * event)
     GST_DEBUG_OBJECT (parse, "stopped streaming at %" G_GINT64_FORMAT,
         last_stop);
 
-    /* copy segment, we need this because we still need the old
-     * segment when we close the current segment. */
-    memcpy (&seeksegment, &parse->segment, sizeof (GstSegment));
-
-    GST_DEBUG_OBJECT (parse, "configuring seek");
-    gst_segment_set_seek (&seeksegment, rate, format, flags,
-        cur_type, cur, stop_type, stop, &update);
-
-    /* figure out the last position we need to play. If it's configured (stop !=
-     * -1), use that, else we play until the total duration of the file */
-    if ((stop = seeksegment.stop) == -1)
-      stop = seeksegment.duration;
-
+    /* now commit to new position */
     parse->priv->offset = seekpos;
 
     /* prepare for streaming again */
@@ -2090,6 +2221,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: