baseparse: Fix integer overflow in bitrate calculation
[platform/upstream/gstreamer.git] / libs / gst / base / gstbaseparse.c
index 395b43b..99283b8 100644 (file)
@@ -23,6 +23,7 @@
 
 /**
  * SECTION:gstbaseparse
+ * @title: GstBaseParse
  * @short_description: Base class for stream parsers
  * @see_also: #GstBaseTransform
  *
  * into separate audio/video/whatever frames.
  *
  * It provides for:
- * <itemizedlist>
- *   <listitem><para>provides one sink pad and one source pad</para></listitem>
- *   <listitem><para>handles state changes</para></listitem>
- *   <listitem><para>can operate in pull mode or push mode</para></listitem>
- *   <listitem><para>handles seeking in both modes</para></listitem>
- *   <listitem><para>handles events (SEGMENT/EOS/FLUSH)</para></listitem>
- *   <listitem><para>
- *        handles queries (POSITION/DURATION/SEEKING/FORMAT/CONVERT)
- *   </para></listitem>
- *   <listitem><para>handles flushing</para></listitem>
- * </itemizedlist>
+ *
+ *   * provides one sink pad and one source pad
+ *   * handles state changes
+ *   * can operate in pull mode or push mode
+ *   * handles seeking in both modes
+ *   * handles events (SEGMENT/EOS/FLUSH)
+ *   * handles queries (POSITION/DURATION/SEEKING/FORMAT/CONVERT)
+ *   * handles flushing
  *
  * The purpose of this base class is to provide the basic functionality of
  * a parser and share a lot of rather complex code.
  *
- * Description of the parsing mechanism:
- * <orderedlist>
- * <listitem>
- *   <itemizedlist><title>Set-up phase</title>
- *   <listitem><para>
- *     #GstBaseParse calls @start to inform subclass that data processing is
- *     about to start now.
- *   </para></listitem>
- *   <listitem><para>
- *     #GstBaseParse class calls @set_sink_caps to inform the subclass about
- *     incoming sinkpad caps. Subclass could already set the srcpad caps
- *     accordingly, but this might be delayed until calling
- *     gst_base_parse_finish_frame() with a non-queued frame.
- *   </para></listitem>
- *   <listitem><para>
- *      At least at this point subclass needs to tell the #GstBaseParse class
- *      how big data chunks it wants to receive (min_frame_size). It can do
- *      this with gst_base_parse_set_min_frame_size().
- *   </para></listitem>
- *   <listitem><para>
- *      #GstBaseParse class sets up appropriate data passing mode (pull/push)
- *      and starts to process the data.
- *   </para></listitem>
- *   </itemizedlist>
- * </listitem>
- * <listitem>
- *   <itemizedlist>
- *   <title>Parsing phase</title>
- *     <listitem><para>
- *       #GstBaseParse gathers at least min_frame_size bytes of data either
- *       by pulling it from upstream or collecting buffers in an internal
- *       #GstAdapter.
- *     </para></listitem>
- *     <listitem><para>
- *       A buffer of (at least) min_frame_size bytes is passed to subclass with
- *       @handle_frame. Subclass checks the contents and can optionally
- *       return GST_FLOW_OK along with an amount of data to be skipped to find
- *       a valid frame (which will result in a subsequent DISCONT).
- *       If, otherwise, the buffer does not hold a complete frame,
- *       @handle_frame can merely return and will be called again when additional
- *       data is available.  In push mode this amounts to an
- *       additional input buffer (thus minimal additional latency), in pull mode
- *       this amounts to some arbitrary reasonable buffer size increase.
- *       Of course, gst_base_parse_set_min_frame_size() could also be used if a
- *       very specific known amount of additional data is required.
- *       If, however, the buffer holds a complete valid frame, it can pass
- *       the size of this frame to gst_base_parse_finish_frame().
- *       If acting as a converter, it can also merely indicate consumed input data
- *       while simultaneously providing custom output data.
- *       Note that baseclass performs some processing (such as tracking
- *       overall consumed data rate versus duration) for each finished frame,
- *       but other state is only updated upon each call to @handle_frame
- *       (such as tracking upstream input timestamp).
- *       </para><para>
- *       Subclass is also responsible for setting the buffer metadata
- *       (e.g. buffer timestamp and duration, or keyframe if applicable).
- *       (although the latter can also be done by #GstBaseParse if it is
- *       appropriately configured, see below).  Frame is provided with
- *       timestamp derived from upstream (as much as generally possible),
- *       duration obtained from configuration (see below), and offset
- *       if meaningful (in pull mode).
- *       </para><para>
- *       Note that @check_valid_frame might receive any small
- *       amount of input data when leftover data is being drained (e.g. at EOS).
- *     </para></listitem>
- *     <listitem><para>
- *       As part of finish frame processing,
- *       just prior to actually pushing the buffer in question,
- *       it is passed to @pre_push_frame which gives subclass yet one
- *       last chance to examine buffer metadata, or to send some custom (tag)
- *       events, or to perform custom (segment) filtering.
- *     </para></listitem>
- *     <listitem><para>
- *       During the parsing process #GstBaseParseClass will handle both srcpad
- *       and sinkpad events. They will be passed to subclass if @event or
- *       @src_event callbacks have been provided.
- *     </para></listitem>
- *   </itemizedlist>
- * </listitem>
- * <listitem>
- *   <itemizedlist><title>Shutdown phase</title>
- *   <listitem><para>
- *     #GstBaseParse class calls @stop to inform the subclass that data
- *     parsing will be stopped.
- *   </para></listitem>
- *   </itemizedlist>
- * </listitem>
- * </orderedlist>
+ * # Description of the parsing mechanism:
+ *
+ * ## Set-up phase
+ *
+ *  * #GstBaseParse calls @start to inform subclass that data processing is
+ *    about to start now.
+ *
+ *  * #GstBaseParse class calls @set_sink_caps to inform the subclass about
+ *    incoming sinkpad caps. Subclass could already set the srcpad caps
+ *    accordingly, but this might be delayed until calling
+ *    gst_base_parse_finish_frame() with a non-queued frame.
+ *
+ *  * At least at this point subclass needs to tell the #GstBaseParse class
+ *    how big data chunks it wants to receive (min_frame_size). It can do
+ *    this with gst_base_parse_set_min_frame_size().
+ *
+ *  * #GstBaseParse class sets up appropriate data passing mode (pull/push)
+ *    and starts to process the data.
+ *
+ * ## Parsing phase
+ *
+ *  * #GstBaseParse gathers at least min_frame_size bytes of data either
+ *    by pulling it from upstream or collecting buffers in an internal
+ *    #GstAdapter.
+ *
+ *  * A buffer of (at least) min_frame_size bytes is passed to subclass with
+ *    @handle_frame. Subclass checks the contents and can optionally
+ *    return GST_FLOW_OK along with an amount of data to be skipped to find
+ *    a valid frame (which will result in a subsequent DISCONT).
+ *    If, otherwise, the buffer does not hold a complete frame,
+ *    @handle_frame can merely return and will be called again when additional
+ *    data is available.  In push mode this amounts to an
+ *    additional input buffer (thus minimal additional latency), in pull mode
+ *    this amounts to some arbitrary reasonable buffer size increase.
+ *    Of course, gst_base_parse_set_min_frame_size() could also be used if a
+ *    very specific known amount of additional data is required.
+ *    If, however, the buffer holds a complete valid frame, it can pass
+ *    the size of this frame to gst_base_parse_finish_frame().
+ *    If acting as a converter, it can also merely indicate consumed input data
+ *    while simultaneously providing custom output data.
+ *    Note that baseclass performs some processing (such as tracking
+ *    overall consumed data rate versus duration) for each finished frame,
+ *    but other state is only updated upon each call to @handle_frame
+ *    (such as tracking upstream input timestamp).
+ *
+ *    Subclass is also responsible for setting the buffer metadata
+ *    (e.g. buffer timestamp and duration, or keyframe if applicable).
+ *    (although the latter can also be done by #GstBaseParse if it is
+ *    appropriately configured, see below).  Frame is provided with
+ *    timestamp derived from upstream (as much as generally possible),
+ *    duration obtained from configuration (see below), and offset
+ *    if meaningful (in pull mode).
+ *
+ *    Note that @check_valid_frame might receive any small
+ *    amount of input data when leftover data is being drained (e.g. at EOS).
+ *
+ *  * As part of finish frame processing,
+ *    just prior to actually pushing the buffer in question,
+ *    it is passed to @pre_push_frame which gives subclass yet one
+ *    last chance to examine buffer metadata, or to send some custom (tag)
+ *    events, or to perform custom (segment) filtering.
+ *
+ *  * During the parsing process #GstBaseParseClass will handle both srcpad
+ *    and sinkpad events. They will be passed to subclass if @event or
+ *    @src_event callbacks have been provided.
+ *
+ * ## Shutdown phase
+ *
+ * * #GstBaseParse class calls @stop to inform the subclass that data
+ *   parsing will be stopped.
  *
  * Subclass is responsible for providing pad template caps for
  * source and sink pads. The pads need to be named "sink" and "src". It also
  * and end of data processing.
  *
  * Things that subclass need to take care of:
- * <itemizedlist>
- *   <listitem><para>Provide pad templates</para></listitem>
- *   <listitem><para>
- *      Fixate the source pad caps when appropriate
- *   </para></listitem>
- *   <listitem><para>
- *      Inform base class how big data chunks should be retrieved. This is
- *      done with gst_base_parse_set_min_frame_size() function.
- *   </para></listitem>
- *   <listitem><para>
- *      Examine data chunks passed to subclass with @handle_frame and pass
- *      proper frame(s) to gst_base_parse_finish_frame(), and setting src pad
- *      caps and timestamps on frame.
- *   </para></listitem>
- *   <listitem><para>Provide conversion functions</para></listitem>
- *   <listitem><para>
- *      Update the duration information with gst_base_parse_set_duration()
- *   </para></listitem>
- *   <listitem><para>
- *      Optionally passthrough using gst_base_parse_set_passthrough()
- *   </para></listitem>
- *   <listitem><para>
- *      Configure various baseparse parameters using
- *      gst_base_parse_set_average_bitrate(), gst_base_parse_set_syncable()
- *      and gst_base_parse_set_frame_rate().
- *   </para></listitem>
- *   <listitem><para>
- *      In particular, if subclass is unable to determine a duration, but
- *      parsing (or specs) yields a frames per seconds rate, then this can be
- *      provided to #GstBaseParse to enable it to cater for
- *      buffer time metadata (which will be taken from upstream as much as
- *      possible). Internally keeping track of frame durations and respective
- *      sizes that have been pushed provides #GstBaseParse with an estimated
- *      bitrate. A default @convert (used if not overridden) will then use these
- *      rates to perform obvious conversions.  These rates are also used to
- *      update (estimated) duration at regular frame intervals.
- *   </para></listitem>
- * </itemizedlist>
+ *
+ * * Provide pad templates
+ * * Fixate the source pad caps when appropriate
+ * * Inform base class how big data chunks should be retrieved. This is
+ *   done with gst_base_parse_set_min_frame_size() function.
+ * * Examine data chunks passed to subclass with @handle_frame and pass
+ *   proper frame(s) to gst_base_parse_finish_frame(), and setting src pad
+ *   caps and timestamps on frame.
+ * * Provide conversion functions
+ * * Update the duration information with gst_base_parse_set_duration()
+ * * Optionally passthrough using gst_base_parse_set_passthrough()
+ * * Configure various baseparse parameters using
+ *   gst_base_parse_set_average_bitrate(), gst_base_parse_set_syncable()
+ *   and gst_base_parse_set_frame_rate().
+ *
+ * * In particular, if subclass is unable to determine a duration, but
+ *   parsing (or specs) yields a frames per seconds rate, then this can be
+ *   provided to #GstBaseParse to enable it to cater for
+ *   buffer time metadata (which will be taken from upstream as much as
+ *   possible). Internally keeping track of frame durations and respective
+ *   sizes that have been pushed provides #GstBaseParse with an estimated
+ *   bitrate. A default @convert (used if not overridden) will then use these
+ *   rates to perform obvious conversions.  These rates are also used to
+ *   update (estimated) duration at regular frame intervals.
  *
  */
 
 #define MIN_FRAMES_TO_POST_BITRATE 10
 #define TARGET_DIFFERENCE          (20 * GST_SECOND)
 #define MAX_INDEX_ENTRIES          4096
+#define UPDATE_THRESHOLD           2
+
+#define ABSDIFF(a,b) (((a) > (b)) ? ((a) - (b)) : ((b) - (a)))
 
 GST_DEBUG_CATEGORY_STATIC (gst_base_parse_debug);
 #define GST_CAT_DEFAULT gst_base_parse_debug
@@ -270,6 +239,7 @@ struct _GstBaseParsePrivate
   GstClockTime next_dts;
   GstClockTime prev_pts;
   GstClockTime prev_dts;
+  gboolean prev_dts_from_pts;
   GstClockTime frame_duration;
   gboolean seen_keyframe;
   gboolean is_video;
@@ -286,6 +256,7 @@ struct _GstBaseParsePrivate
   gboolean post_min_bitrate;
   gboolean post_avg_bitrate;
   gboolean post_max_bitrate;
+
   guint min_bitrate;
   guint avg_bitrate;
   guint max_bitrate;
@@ -359,6 +330,12 @@ struct _GstBaseParsePrivate
 
   /* When we need to skip more data than we have currently */
   guint skip;
+
+  /* Tag handling (stream tags only, global tags are passed through as-is) */
+  GstTagList *upstream_tags;
+  GstTagList *parser_tags;
+  GstTagMergeMode parser_tags_merge_mode;
+  gboolean tags_changed;
 };
 
 typedef struct _GstBaseParseSeek
@@ -432,7 +409,8 @@ static gboolean gst_base_parse_sink_activate_mode (GstPad * pad,
     GstObject * parent, GstPadMode mode, 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 void gst_base_parse_set_upstream_tags (GstBaseParse * parse,
+    GstTagList * taglist);
 
 static void gst_base_parse_set_property (GObject * object, guint prop_id,
     const GValue * value, GParamSpec * pspec);
@@ -467,11 +445,6 @@ static gboolean gst_base_parse_sink_query_default (GstBaseParse * parse,
 static gboolean gst_base_parse_src_query_default (GstBaseParse * parse,
     GstQuery * query);
 
-static void gst_base_parse_drain (GstBaseParse * parse);
-
-static void gst_base_parse_post_bitrates (GstBaseParse * parse,
-    gboolean post_min, gboolean post_avg, gboolean post_max);
-
 static gint64 gst_base_parse_find_offset (GstBaseParse * parse,
     GstClockTime time, gboolean before, GstClockTime * _ts);
 static GstFlowReturn gst_base_parse_locate_time (GstBaseParse * parse,
@@ -644,6 +617,10 @@ gst_base_parse_init (GstBaseParse * parse, GstBaseParseClass * bclass)
   GST_DEBUG_OBJECT (parse, "init ok");
 
   GST_OBJECT_FLAG_SET (parse, GST_ELEMENT_FLAG_INDEXABLE);
+
+  parse->priv->upstream_tags = NULL;
+  parse->priv->parser_tags = NULL;
+  parse->priv->parser_tags_merge_mode = GST_TAG_MERGE_APPEND;
 }
 
 static void
@@ -678,7 +655,7 @@ gst_base_parse_get_property (GObject * object, guint prop_id, GValue * value,
   }
 }
 
-static GstBaseParseFrame *
+GstBaseParseFrame *
 gst_base_parse_frame_copy (GstBaseParseFrame * frame)
 {
   GstBaseParseFrame *copy;
@@ -779,6 +756,8 @@ gst_base_parse_update_frame (GstBaseParse * parse, GstBaseParseFrame * frame)
   if (G_UNLIKELY (parse->priv->discont)) {
     GST_DEBUG_OBJECT (parse, "marking DISCONT");
     GST_BUFFER_FLAG_SET (frame->buffer, GST_BUFFER_FLAG_DISCONT);
+  } else {
+    GST_BUFFER_FLAG_UNSET (frame->buffer, GST_BUFFER_FLAG_DISCONT);
   }
 
   if (parse->priv->prev_offset != parse->priv->offset || parse->priv->new_frame) {
@@ -823,9 +802,6 @@ gst_base_parse_reset (GstBaseParse * parse)
   parse->priv->pts_interpolate = TRUE;
   parse->priv->infer_ts = TRUE;
   parse->priv->has_timing_info = 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->avg_bitrate = 0;
@@ -867,6 +843,14 @@ gst_base_parse_reset (GstBaseParse * parse)
   if (parse->priv->adapter)
     gst_adapter_clear (parse->priv->adapter);
 
+  gst_base_parse_set_upstream_tags (parse, NULL);
+
+  if (parse->priv->parser_tags) {
+    gst_tag_list_unref (parse->priv->parser_tags);
+    parse->priv->parser_tags = NULL;
+  }
+  parse->priv->parser_tags_merge_mode = GST_TAG_MERGE_APPEND;
+
   parse->priv->new_frame = TRUE;
 
   parse->priv->first_buffer = TRUE;
@@ -878,6 +862,86 @@ gst_base_parse_reset (GstBaseParse * parse)
   GST_OBJECT_UNLOCK (parse);
 }
 
+static gboolean
+gst_base_parse_check_bitrate_tag (GstBaseParse * parse, const gchar * tag)
+{
+  gboolean got_tag = FALSE;
+  guint n = 0;
+
+  if (parse->priv->upstream_tags != NULL)
+    got_tag = gst_tag_list_get_uint (parse->priv->upstream_tags, tag, &n);
+
+  if (!got_tag && parse->priv->parser_tags != NULL)
+    got_tag = gst_tag_list_get_uint (parse->priv->parser_tags, tag, &n);
+
+  return got_tag;
+}
+
+/* check if upstream or subclass tags contain bitrates already */
+static void
+gst_base_parse_check_bitrate_tags (GstBaseParse * parse)
+{
+  parse->priv->post_min_bitrate =
+      !gst_base_parse_check_bitrate_tag (parse, GST_TAG_MINIMUM_BITRATE);
+  parse->priv->post_avg_bitrate =
+      !gst_base_parse_check_bitrate_tag (parse, GST_TAG_BITRATE);
+  parse->priv->post_max_bitrate =
+      !gst_base_parse_check_bitrate_tag (parse, GST_TAG_MAXIMUM_BITRATE);
+}
+
+/* Queues new tag event with the current combined state of the stream tags
+ * (i.e. upstream tags merged with subclass tags and current baseparse tags) */
+static void
+gst_base_parse_queue_tag_event_update (GstBaseParse * parse)
+{
+  GstTagList *merged_tags;
+
+  GST_LOG_OBJECT (parse, "upstream : %" GST_PTR_FORMAT,
+      parse->priv->upstream_tags);
+  GST_LOG_OBJECT (parse, "parser   : %" GST_PTR_FORMAT,
+      parse->priv->parser_tags);
+  GST_LOG_OBJECT (parse, "mode     : %d", parse->priv->parser_tags_merge_mode);
+
+  merged_tags =
+      gst_tag_list_merge (parse->priv->upstream_tags, parse->priv->parser_tags,
+      parse->priv->parser_tags_merge_mode);
+
+  GST_DEBUG_OBJECT (parse, "merged   : %" GST_PTR_FORMAT, merged_tags);
+
+  if (merged_tags == NULL)
+    return;
+
+  if (gst_tag_list_is_empty (merged_tags)) {
+    gst_tag_list_unref (merged_tags);
+    return;
+  }
+
+  if (parse->priv->framecount >= MIN_FRAMES_TO_POST_BITRATE) {
+    /* only add bitrate tags to non-empty taglists for now, and only if neither
+     * upstream tags nor the subclass sets the bitrate tag in question already */
+    if (parse->priv->min_bitrate != G_MAXUINT && parse->priv->post_min_bitrate) {
+      GST_LOG_OBJECT (parse, "adding min bitrate %u", parse->priv->min_bitrate);
+      gst_tag_list_add (merged_tags, GST_TAG_MERGE_KEEP,
+          GST_TAG_MINIMUM_BITRATE, parse->priv->min_bitrate, NULL);
+    }
+    if (parse->priv->max_bitrate != 0 && parse->priv->post_max_bitrate) {
+      GST_LOG_OBJECT (parse, "adding max bitrate %u", parse->priv->max_bitrate);
+      gst_tag_list_add (merged_tags, GST_TAG_MERGE_KEEP,
+          GST_TAG_MAXIMUM_BITRATE, parse->priv->max_bitrate, NULL);
+    }
+    if (parse->priv->avg_bitrate != 0 && parse->priv->post_avg_bitrate) {
+      parse->priv->posted_avg_bitrate = parse->priv->avg_bitrate;
+      GST_LOG_OBJECT (parse, "adding avg bitrate %u", parse->priv->avg_bitrate);
+      gst_tag_list_add (merged_tags, GST_TAG_MERGE_KEEP,
+          GST_TAG_BITRATE, parse->priv->avg_bitrate, NULL);
+    }
+  }
+
+  parse->priv->pending_events =
+      g_list_prepend (parse->priv->pending_events,
+      gst_event_new_tag (merged_tags));
+}
+
 /* gst_base_parse_parse_frame:
  * @parse: #GstBaseParse.
  * @buffer: #GstBuffer.
@@ -958,6 +1022,84 @@ gst_base_parse_convert (GstBaseParse * parse,
   return ret;
 }
 
+static gboolean
+update_upstream_provided (GQuark field_id, const GValue * value,
+    gpointer user_data)
+{
+  GstCaps *default_caps = user_data;
+  gint i;
+  gint caps_size;
+
+  caps_size = gst_caps_get_size (default_caps);
+  for (i = 0; i < caps_size; i++) {
+    GstStructure *structure = gst_caps_get_structure (default_caps, i);
+    if (gst_structure_id_has_field (structure, field_id))
+      gst_structure_id_set_value (structure, field_id, value);
+  }
+
+  return TRUE;
+}
+
+static GstCaps *
+gst_base_parse_negotiate_default_caps (GstBaseParse * parse)
+{
+  GstCaps *caps, *templcaps;
+  GstCaps *sinkcaps = NULL;
+  GstCaps *default_caps = NULL;
+  GstStructure *structure;
+
+  templcaps = gst_pad_get_pad_template_caps (GST_BASE_PARSE_SRC_PAD (parse));
+  caps = gst_pad_peer_query_caps (GST_BASE_PARSE_SRC_PAD (parse), templcaps);
+  if (caps)
+    gst_caps_unref (templcaps);
+  else
+    caps = templcaps;
+  templcaps = NULL;
+
+  if (!caps || gst_caps_is_empty (caps) || gst_caps_is_any (caps)) {
+    goto caps_error;
+  }
+
+  GST_LOG_OBJECT (parse, "peer caps  %" GST_PTR_FORMAT, caps);
+
+  /* before fixating, try to use whatever upstream provided */
+  default_caps = gst_caps_copy (caps);
+  sinkcaps = gst_pad_get_current_caps (GST_BASE_PARSE_SINK_PAD (parse));
+
+  GST_LOG_OBJECT (parse, "current caps %" GST_PTR_FORMAT " for sinkpad",
+      sinkcaps);
+
+  if (sinkcaps) {
+    structure = gst_caps_get_structure (sinkcaps, 0);
+    gst_structure_foreach (structure, update_upstream_provided, default_caps);
+  }
+
+  default_caps = gst_caps_fixate (default_caps);
+
+  if (!default_caps) {
+    GST_WARNING_OBJECT (parse, "Failed to create default caps !");
+    goto caps_error;
+  }
+
+  GST_INFO_OBJECT (parse,
+      "Chose default caps %" GST_PTR_FORMAT " for initial gap", default_caps);
+
+  if (sinkcaps)
+    gst_caps_unref (sinkcaps);
+  gst_caps_unref (caps);
+
+  return default_caps;
+
+caps_error:
+  {
+    if (caps)
+      gst_caps_unref (caps);
+    if (sinkcaps)
+      gst_caps_unref (sinkcaps);
+    return NULL;
+  }
+}
+
 /* gst_base_parse_sink_event:
  * @pad: #GstPad that received the event.
  * @event: #GstEvent to be handled.
@@ -978,7 +1120,6 @@ gst_base_parse_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
   return ret;
 }
 
-
 /* gst_base_parse_sink_event_default:
  * @parse: #GstBaseParse.
  * @event: #GstEvent to be handled.
@@ -1135,6 +1276,7 @@ gst_base_parse_sink_event_default (GstBaseParse * parse, GstEvent * event)
       parse->priv->last_dts = GST_CLOCK_TIME_NONE;
       parse->priv->prev_pts = GST_CLOCK_TIME_NONE;
       parse->priv->prev_dts = GST_CLOCK_TIME_NONE;
+      parse->priv->prev_dts_from_pts = FALSE;
       parse->priv->discont = TRUE;
       parse->priv->seen_keyframe = FALSE;
       parse->priv->skip = 0;
@@ -1186,14 +1328,16 @@ gst_base_parse_sink_event_default (GstBaseParse * parse, GstEvent * event)
         GST_ELEMENT_ERROR (parse, STREAM, WRONG_TYPE,
             ("No valid frames found before end of stream"), (NULL));
       }
-      /* newsegment and other serialized events before eos */
-      gst_base_parse_push_pending_events (parse);
 
       if (!parse->priv->saw_gaps
           && 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);
+        gst_base_parse_queue_tag_event_update (parse);
       }
+
+      /* newsegment and other serialized events before eos */
+      gst_base_parse_push_pending_events (parse);
+
       forward_immediate = TRUE;
       break;
     case GST_EVENT_CUSTOM_DOWNSTREAM:{
@@ -1224,6 +1368,47 @@ gst_base_parse_sink_event_default (GstBaseParse * parse, GstEvent * event)
     {
       GST_DEBUG_OBJECT (parse, "draining current data due to gap event");
 
+      /* Ensure we have caps before forwarding the event */
+      if (!gst_pad_has_current_caps (GST_BASE_PARSE_SRC_PAD (parse))) {
+        GstCaps *default_caps = NULL;
+        if ((default_caps = gst_base_parse_negotiate_default_caps (parse))) {
+          GList *l;
+          GstEvent *caps_event = gst_event_new_caps (default_caps);
+
+          GST_DEBUG_OBJECT (parse,
+              "Store caps event to pending list for initial pre-rolling");
+
+          /* Events are in decreasing order. Go down the list until we
+           * find the first pre-CAPS event and insert our CAPS event there.
+           *
+           * There should be a SEGMENT event already, which is > CAPS */
+          for (l = parse->priv->pending_events; l; l = l->next) {
+            GstEvent *e = l->data;
+
+            if (GST_EVENT_TYPE (e) < GST_EVENT_CAPS) {
+              parse->priv->pending_events =
+                  g_list_insert_before (parse->priv->pending_events, l,
+                  caps_event);
+              break;
+            }
+          }
+          /* No pending event that is < CAPS, so we have to add it at the very
+           * end of the list */
+          if (!l) {
+            parse->priv->pending_events =
+                g_list_append (parse->priv->pending_events, caps_event);
+          }
+          gst_caps_unref (default_caps);
+        } else {
+          gst_event_unref (event);
+          event = NULL;
+          ret = FALSE;
+          GST_ELEMENT_ERROR (parse, STREAM, FORMAT, (NULL),
+              ("Parser output not negotiated before GAP event."));
+          break;
+        }
+      }
+
       gst_base_parse_push_pending_events (parse);
 
       if (parse->segment.rate > 0.0)
@@ -1235,15 +1420,32 @@ gst_base_parse_sink_event_default (GstBaseParse * parse, GstEvent * event)
       break;
     }
     case GST_EVENT_TAG:
-      /* See if any bitrate tags were posted */
-      gst_base_parse_handle_tag (parse, event);
-      break;
+    {
+      GstTagList *tags = NULL;
+
+      gst_event_parse_tag (event, &tags);
+
+      /* We only care about stream tags here, global tags we just forward */
+      if (gst_tag_list_get_scope (tags) != GST_TAG_SCOPE_STREAM)
+        break;
 
+      gst_base_parse_set_upstream_tags (parse, tags);
+      gst_base_parse_queue_tag_event_update (parse);
+      parse->priv->tags_changed = FALSE;
+      gst_event_unref (event);
+      event = NULL;
+      ret = TRUE;
+      break;
+    }
     case GST_EVENT_STREAM_START:
+    {
       if (parse->priv->pad_mode != GST_PAD_MODE_PULL)
         forward_immediate = TRUE;
-      break;
 
+      gst_base_parse_set_upstream_tags (parse, NULL);
+      parse->priv->tags_changed = TRUE;
+      break;
+    }
     default:
       break;
   }
@@ -1476,6 +1678,14 @@ gst_base_parse_convert_default (GstBaseParse * parse,
     return TRUE;
   }
 
+  if (parse->priv->upstream_format != GST_FORMAT_BYTES) {
+    /* don't do byte format conversions if we're not really parsing
+     * a raw elementary stream, since we don't really have BYTES
+     * position / duration info */
+    if (src_format == GST_FORMAT_BYTES || dest_format == GST_FORMAT_BYTES)
+      goto no_slaved_conversions;
+  }
+
   /* need at least some frames */
   if (!parse->priv->framecount)
     goto no_framecount;
@@ -1539,7 +1749,12 @@ no_duration_bytes:
         G_GUINT64_FORMAT, duration, bytes);
     return FALSE;
   }
-
+no_slaved_conversions:
+  {
+    GST_DEBUG_OBJECT (parse,
+        "Can't do format conversions when upstream format is not BYTES");
+    return FALSE;
+  }
 }
 
 static void
@@ -1570,45 +1785,6 @@ gst_base_parse_update_duration (GstBaseParse * parse)
   }
 }
 
-static void
-gst_base_parse_post_bitrates (GstBaseParse * parse, gboolean post_min,
-    gboolean post_avg, gboolean post_max)
-{
-  GstTagList *taglist = NULL;
-
-  if (post_min && parse->priv->post_min_bitrate) {
-    taglist = gst_tag_list_new_empty ();
-
-    gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
-        GST_TAG_MINIMUM_BITRATE, parse->priv->min_bitrate, NULL);
-  }
-
-  if (post_avg && parse->priv->post_avg_bitrate) {
-    if (taglist == NULL)
-      taglist = gst_tag_list_new_empty ();
-
-    parse->priv->posted_avg_bitrate = parse->priv->avg_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) {
-    if (taglist == NULL)
-      taglist = gst_tag_list_new_empty ();
-
-    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);
-
-  if (taglist != NULL) {
-    gst_pad_push_event (parse->srcpad, gst_event_new_tag (taglist));
-  }
-}
-
 /* gst_base_parse_update_bitrates:
  * @parse: #GstBaseParse.
  * @buffer: Current frame as a #GstBuffer
@@ -1619,12 +1795,10 @@ gst_base_parse_post_bitrates (GstBaseParse * parse, gboolean post_min,
 static void
 gst_base_parse_update_bitrates (GstBaseParse * parse, GstBaseParseFrame * frame)
 {
-  /* Only update the tag on a 10 kbps delta */
-  static const gint update_threshold = 10000;
-
   guint64 data_len, frame_dur;
-  gint overhead, frame_bitrate, old_avg_bitrate;
-  gboolean update_min = FALSE, update_avg = FALSE, update_max = FALSE;
+  gint overhead;
+  guint frame_bitrate;
+  guint64 frame_bitrate64;
   GstBuffer *buffer = frame->buffer;
 
   overhead = frame->overhead;
@@ -1637,11 +1811,17 @@ gst_base_parse_update_bitrates (GstBaseParse * parse, GstBaseParseFrame * frame)
   /* duration should be valid by now,
    * either set by subclass or maybe based on fps settings */
   if (GST_BUFFER_DURATION_IS_VALID (buffer) && parse->priv->acc_duration != 0) {
+    guint64 avg_bitrate;
+
     /* 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;
+    avg_bitrate = gst_util_uint64_scale (GST_SECOND,
+        8 * parse->priv->data_bytecount, parse->priv->acc_duration);
+
+    if (avg_bitrate > G_MAXUINT)
+      return;
 
+    parse->priv->avg_bitrate = avg_bitrate;
   } else {
     /* No way to figure out frame duration (is this even possible?) */
     return;
@@ -1651,48 +1831,54 @@ gst_base_parse_update_bitrates (GstBaseParse * parse, GstBaseParseFrame * frame)
   if (parse->priv->bitrate) {
     parse->priv->avg_bitrate = parse->priv->bitrate;
     /* spread this (confirmed) info ASAP */
-    if (parse->priv->posted_avg_bitrate != parse->priv->avg_bitrate)
-      gst_base_parse_post_bitrates (parse, FALSE, TRUE, FALSE);
+    if (parse->priv->post_avg_bitrate &&
+        parse->priv->posted_avg_bitrate != parse->priv->avg_bitrate)
+      parse->priv->tags_changed = TRUE;
   }
 
-  if (frame_dur)
-    frame_bitrate = (8 * data_len * GST_SECOND) / frame_dur;
-  else
+  if (!frame_dur)
+    return;
+
+  frame_bitrate64 = gst_util_uint64_scale (GST_SECOND, 8 * data_len, frame_dur);
+
+  if (frame_bitrate64 > G_MAXUINT)
     return;
 
+  frame_bitrate = (guint) frame_bitrate64;
+
   GST_LOG_OBJECT (parse, "frame bitrate %u, avg bitrate %u", frame_bitrate,
       parse->priv->avg_bitrate);
 
-  if (parse->priv->framecount < MIN_FRAMES_TO_POST_BITRATE) {
-    goto exit;
-  } else if (parse->priv->framecount == MIN_FRAMES_TO_POST_BITRATE) {
-    /* always post all at threshold time */
-    update_min = update_max = update_avg = TRUE;
-  }
+  if (parse->priv->framecount < MIN_FRAMES_TO_POST_BITRATE)
+    return;
+
+  if (parse->priv->framecount == MIN_FRAMES_TO_POST_BITRATE &&
+      (parse->priv->post_min_bitrate || parse->priv->post_avg_bitrate
+          || parse->priv->post_max_bitrate))
+    parse->priv->tags_changed = TRUE;
 
   if (G_LIKELY (parse->priv->framecount >= MIN_FRAMES_TO_POST_BITRATE)) {
     if (frame_bitrate < parse->priv->min_bitrate) {
       parse->priv->min_bitrate = frame_bitrate;
-      update_min = TRUE;
+      if (parse->priv->post_min_bitrate)
+        parse->priv->tags_changed = TRUE;
     }
 
     if (frame_bitrate > parse->priv->max_bitrate) {
       parse->priv->max_bitrate = frame_bitrate;
-      update_max = TRUE;
+      if (parse->priv->post_max_bitrate)
+        parse->priv->tags_changed = TRUE;
     }
 
-    old_avg_bitrate = parse->priv->posted_avg_bitrate;
-    if ((gint) (old_avg_bitrate - parse->priv->avg_bitrate) > update_threshold
-        || (gint) (parse->priv->avg_bitrate - old_avg_bitrate) >
-        update_threshold)
-      update_avg = TRUE;
+    /* Only update the tag on a 2% change */
+    if (parse->priv->post_avg_bitrate && parse->priv->avg_bitrate) {
+      guint64 diffprev = gst_util_uint64_scale_int (100,
+          ABSDIFF (parse->priv->avg_bitrate, parse->priv->posted_avg_bitrate),
+          parse->priv->avg_bitrate);
+      if (diffprev >= UPDATE_THRESHOLD)
+        parse->priv->tags_changed = TRUE;
+    }
   }
-
-  if ((update_min || update_avg || update_max))
-    gst_base_parse_post_bitrates (parse, update_min, update_avg, update_max);
-
-exit:
-  return;
 }
 
 /**
@@ -2031,6 +2217,12 @@ gst_base_parse_handle_buffer (GstBaseParse * parse, GstBuffer * buffer,
     gst_adapter_clear (parse->priv->adapter);
   }
 
+  if (*skip == 0 && *flushed == 0) {
+    /* Carry over discont if we need more data */
+    if (GST_BUFFER_IS_DISCONT (frame->buffer))
+      parse->priv->discont = TRUE;
+  }
+
   gst_base_parse_frame_free (frame);
 
   return ret;
@@ -2228,6 +2420,11 @@ gst_base_parse_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame)
     gst_base_parse_check_media (parse);
   }
 
+  if (parse->priv->tags_changed) {
+    gst_base_parse_queue_tag_event_update (parse);
+    parse->priv->tags_changed = FALSE;
+  }
+
   /* Push pending events, including SEGMENT events */
   gst_base_parse_push_pending_events (parse);
 
@@ -2275,6 +2472,14 @@ gst_base_parse_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame)
     frame->flags |= GST_BASE_PARSE_FRAME_FLAG_CLIP;
   }
 
+  /* Push pending events, if there are any new ones
+   * like tags added by pre_push_frame */
+  if (parse->priv->tags_changed) {
+    gst_base_parse_queue_tag_event_update (parse);
+    parse->priv->tags_changed = FALSE;
+  }
+  gst_base_parse_push_pending_events (parse);
+
   /* take final ownership of frame buffer */
   if (frame->out_buffer) {
     buffer = frame->out_buffer;
@@ -2319,6 +2524,8 @@ gst_base_parse_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame)
 
   if (ret == GST_BASE_PARSE_FLOW_DROPPED) {
     GST_LOG_OBJECT (parse, "frame (%" G_GSIZE_FORMAT " bytes) dropped", size);
+    if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DISCONT))
+      parse->priv->discont = TRUE;
     gst_buffer_unref (buffer);
     ret = GST_FLOW_OK;
   } else if (ret == GST_FLOW_OK) {
@@ -2375,8 +2582,9 @@ gst_base_parse_push_frame (GstBaseParse * parse, GstBaseParseFrame * frame)
   }
 
   /* Update current running segment position */
-  if (ret == GST_FLOW_OK && last_stop != GST_CLOCK_TIME_NONE &&
-      parse->segment.position < last_stop)
+  if ((ret == GST_FLOW_OK || ret == GST_FLOW_NOT_LINKED)
+      && last_stop != GST_CLOCK_TIME_NONE
+      && parse->segment.position < last_stop)
     parse->segment.position = last_stop;
 
   return ret;
@@ -2488,13 +2696,17 @@ exit:
   return ret;
 }
 
-/* gst_base_parse_drain:
+/**
+ * gst_base_parse_drain:
+ * @parse: a #GstBaseParse
  *
  * Drains the adapter until it is empty. It decreases the min_frame_size to
  * match the current adapter size and calls chain method until the adapter
  * is emptied or chain returns with error.
+ *
+ * Since: 1.12
  */
-static void
+void
 gst_base_parse_drain (GstBaseParse * parse)
 {
   guint avail;
@@ -2552,6 +2764,12 @@ gst_base_parse_send_buffers (GstBaseParse * parse)
     if (first) {
       GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_DISCONT);
       first = FALSE;
+    } else {
+      /* likewise, subsequent buffers should never have DISCONT
+       * according to the "reverse fragment protocol", or such would
+       * confuse a downstream decoder
+       * (could be DISCONT due to aggregating upstream fragments by parsing) */
+      GST_BUFFER_FLAG_UNSET (buf, GST_BUFFER_FLAG_DISCONT);
     }
 
     /* iterate output queue an push downstream */
@@ -2589,6 +2807,7 @@ gst_base_parse_start_fragment (GstBaseParse * parse)
   parse->priv->prev_pts = GST_CLOCK_TIME_NONE;
   parse->priv->next_dts = GST_CLOCK_TIME_NONE;
   parse->priv->prev_dts = GST_CLOCK_TIME_NONE;
+  parse->priv->prev_dts_from_pts = FALSE;
   /* prevent it hanging around stop all the time */
   parse->segment.position = GST_CLOCK_TIME_NONE;
   /* mark next run */
@@ -2959,6 +3178,7 @@ gst_base_parse_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
   /* Stop either when adapter is empty or we are flushing */
   while (!parse->priv->flushing) {
     gint flush = 0;
+    gboolean updated_prev_pts = FALSE;
 
     /* note: if subclass indicates MAX fsize,
      * this will not likely be available anyway ... */
@@ -2983,11 +3203,15 @@ gst_base_parse_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
      * but interpolate in between */
     pts = gst_adapter_prev_pts (parse->priv->adapter, NULL);
     dts = gst_adapter_prev_dts (parse->priv->adapter, NULL);
-    if (GST_CLOCK_TIME_IS_VALID (pts) && (parse->priv->prev_pts != pts))
+    if (GST_CLOCK_TIME_IS_VALID (pts) && (parse->priv->prev_pts != pts)) {
       parse->priv->prev_pts = parse->priv->next_pts = pts;
+      updated_prev_pts = TRUE;
+    }
 
-    if (GST_CLOCK_TIME_IS_VALID (dts) && (parse->priv->prev_dts != dts))
+    if (GST_CLOCK_TIME_IS_VALID (dts) && (parse->priv->prev_dts != dts)) {
       parse->priv->prev_dts = parse->priv->next_dts = dts;
+      parse->priv->prev_dts_from_pts = FALSE;
+    }
 
     /* we can mess with, erm interpolate, timestamps,
      * and incoming stuff has PTS but no DTS seen so far,
@@ -2995,9 +3219,12 @@ gst_base_parse_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
     if (parse->priv->infer_ts &&
         parse->priv->pts_interpolate &&
         !GST_CLOCK_TIME_IS_VALID (dts) &&
-        !GST_CLOCK_TIME_IS_VALID (parse->priv->prev_dts) &&
-        GST_CLOCK_TIME_IS_VALID (pts))
-      parse->priv->next_dts = pts;
+        (!GST_CLOCK_TIME_IS_VALID (parse->priv->prev_dts)
+            || (parse->priv->prev_dts_from_pts && updated_prev_pts))
+        && GST_CLOCK_TIME_IS_VALID (pts)) {
+      parse->priv->prev_dts = parse->priv->next_dts = pts;
+      parse->priv->prev_dts_from_pts = TRUE;
+    }
 
     /* always pass all available data */
     tmpbuf = gst_adapter_get_buffer (parse->priv->adapter, av);
@@ -3025,7 +3252,8 @@ gst_base_parse_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
       ret = old_ret;
       goto done;
     }
-    old_ret = ret;
+    if (old_ret == GST_FLOW_OK)
+      old_ret = ret;
   }
 
 done:
@@ -3380,8 +3608,7 @@ pause:
       /* for fatal errors we post an error message, wrong-state is
        * not fatal because it happens due to flushes and only means
        * that we should stop now. */
-      GST_ELEMENT_ERROR (parse, STREAM, FAILED, (NULL),
-          ("streaming stopped, reason %s", gst_flow_get_name (ret)));
+      GST_ELEMENT_FLOW_ERROR (parse, ret);
       push_eos = TRUE;
     }
     if (push_eos) {
@@ -3430,6 +3657,8 @@ gst_base_parse_sink_activate (GstPad * sinkpad, GstObject * parent)
     goto baseparse_push;
 
   parse->priv->push_stream_start = TRUE;
+  /* In pull mode, upstream is BYTES */
+  parse->priv->upstream_format = GST_FORMAT_BYTES;
 
   return gst_pad_start_task (sinkpad, (GstTaskFunction) gst_base_parse_loop,
       sinkpad, NULL);
@@ -3468,6 +3697,7 @@ gst_base_parse_activate (GstBaseParse * parse, gboolean active)
       result = klass->stop (parse);
 
     parse->priv->pad_mode = GST_PAD_MODE_NONE;
+    parse->priv->upstream_format = GST_FORMAT_UNDEFINED;
   }
   GST_DEBUG_OBJECT (parse, "activate return: %d", result);
   return result;
@@ -3643,7 +3873,11 @@ gst_base_parse_set_frame_rate (GstBaseParse * parse, guint fps_num,
         gst_util_uint64_scale (GST_SECOND, fps_den * lead_out, fps_num);
     /* aim for about 1.5s to estimate duration */
     if (parse->priv->update_interval < 0) {
-      parse->priv->update_interval = fps_num * 3 / (fps_den * 2);
+      guint64 interval = gst_util_uint64_scale (fps_num, 3,
+          G_GUINT64_CONSTANT (2) * fps_den);
+
+      parse->priv->update_interval = MIN (interval, G_MAXINT);
+
       GST_LOG_OBJECT (parse, "estimated update interval to %d frames",
           parse->priv->update_interval);
     }
@@ -3822,7 +4056,10 @@ gst_base_parse_src_query_default (GstBaseParse * parse, GstQuery * query)
       if (!res) {
         /* Fall back on interpreting segment */
         GST_OBJECT_LOCK (parse);
-        if (format == GST_FORMAT_BYTES) {
+        /* Only reply BYTES if upstream is in BYTES already, otherwise
+         * we're not in charge */
+        if (format == GST_FORMAT_BYTES
+            && parse->priv->upstream_format == GST_FORMAT_BYTES) {
           dest_value = parse->priv->offset;
           res = TRUE;
         } else if (format == parse->segment.format &&
@@ -3832,9 +4069,10 @@ gst_base_parse_src_query_default (GstBaseParse * parse, GstQuery * query)
           res = TRUE;
         }
         GST_OBJECT_UNLOCK (parse);
-        if (!res) {
+        if (!res && parse->priv->upstream_format == GST_FORMAT_BYTES) {
           /* no precise result, upstream no idea either, then best estimate */
-          /* priv->offset is updated in both PUSH/PULL modes */
+          /* priv->offset is updated in both PUSH/PULL modes, *iff* we're
+           * in charge of things */
           res = gst_base_parse_convert (parse,
               GST_FORMAT_BYTES, parse->priv->offset, format, &dest_value);
         }
@@ -3884,8 +4122,8 @@ gst_base_parse_src_query_default (GstBaseParse * parse, GstQuery * query)
           if (!gst_base_parse_get_duration (parse, GST_FORMAT_TIME, &duration)
               || duration == -1) {
             /* seekable if we still have a chance to get duration later on */
-            seekable =
-                parse->priv->upstream_seekable && parse->priv->update_interval;
+            seekable = parse->priv->upstream_seekable &&
+                (parse->priv->update_interval > 0);
           } else {
             seekable = parse->priv->upstream_seekable;
             GST_LOG_OBJECT (parse, "already determine upstream seekabled: %d",
@@ -4392,10 +4630,6 @@ gst_base_parse_handle_seek (GstBaseParse * parse, GstEvent * event)
       gst_pad_push_event (parse->srcpad, gst_event_ref (fevent));
       gst_pad_push_event (parse->sinkpad, fevent);
       gst_base_parse_clear_queues (parse);
-    } else {
-      /* keep track of our position */
-      seeksegment.base = gst_segment_to_running_time (&seeksegment,
-          seeksegment.format, parse->segment.position);
     }
 
     memcpy (&parse->segment, &seeksegment, sizeof (GstSegment));
@@ -4529,33 +4763,23 @@ convert_failed:
   }
 }
 
-/* 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)
+gst_base_parse_set_upstream_tags (GstBaseParse * parse, GstTagList * taglist)
 {
-  GstTagList *taglist = NULL;
-  guint tmp;
-
-  gst_event_parse_tag (event, &taglist);
-
-  /* We only care about stream tags here */
-  if (gst_tag_list_get_scope (taglist) != GST_TAG_SCOPE_STREAM)
+  if (taglist == parse->priv->upstream_tags)
     return;
 
-  if (gst_tag_list_get_uint (taglist, GST_TAG_MINIMUM_BITRATE, &tmp)) {
-    GST_DEBUG_OBJECT (parse, "upstream min bitrate %d", tmp);
-    parse->priv->post_min_bitrate = FALSE;
-  }
-  if (gst_tag_list_get_uint (taglist, GST_TAG_BITRATE, &tmp)) {
-    GST_DEBUG_OBJECT (parse, "upstream avg bitrate %d", tmp);
-    parse->priv->post_avg_bitrate = FALSE;
-  }
-  if (gst_tag_list_get_uint (taglist, GST_TAG_MAXIMUM_BITRATE, &tmp)) {
-    GST_DEBUG_OBJECT (parse, "upstream max bitrate %d", tmp);
-    parse->priv->post_max_bitrate = FALSE;
+  if (parse->priv->upstream_tags) {
+    gst_tag_list_unref (parse->priv->upstream_tags);
+    parse->priv->upstream_tags = NULL;
   }
+
+  GST_INFO_OBJECT (parse, "upstream tags: %" GST_PTR_FORMAT, taglist);
+
+  if (taglist != NULL)
+    parse->priv->upstream_tags = gst_tag_list_ref (taglist);
+
+  gst_base_parse_check_bitrate_tags (parse);
 }
 
 #if 0
@@ -4674,6 +4898,55 @@ gst_base_parse_set_ts_at_offset (GstBaseParse * parse, gsize offset)
   if (GST_CLOCK_TIME_IS_VALID (pts) && (parse->priv->prev_pts != pts))
     parse->priv->prev_pts = parse->priv->next_pts = pts;
 
-  if (GST_CLOCK_TIME_IS_VALID (dts) && (parse->priv->prev_dts != dts))
+  if (GST_CLOCK_TIME_IS_VALID (dts) && (parse->priv->prev_dts != dts)) {
     parse->priv->prev_dts = parse->priv->next_dts = dts;
+    parse->priv->prev_dts_from_pts = FALSE;
+  }
+}
+
+/**
+ * gst_base_parse_merge_tags:
+ * @parse: a #GstBaseParse
+ * @tags: (allow-none): a #GstTagList to merge, or NULL to unset
+ *     previously-set tags
+ * @mode: the #GstTagMergeMode to use, usually #GST_TAG_MERGE_REPLACE
+ *
+ * Sets the parser subclass's tags and how they should be merged with any
+ * upstream stream tags. This will override any tags previously-set
+ * with gst_base_parse_merge_tags().
+ *
+ * Note that this is provided for convenience, and the subclass is
+ * not required to use this and can still do tag handling on its own.
+ *
+ * Since: 1.6
+ */
+void
+gst_base_parse_merge_tags (GstBaseParse * parse, GstTagList * tags,
+    GstTagMergeMode mode)
+{
+  g_return_if_fail (GST_IS_BASE_PARSE (parse));
+  g_return_if_fail (tags == NULL || GST_IS_TAG_LIST (tags));
+  g_return_if_fail (tags == NULL || mode != GST_TAG_MERGE_UNDEFINED);
+
+  GST_OBJECT_LOCK (parse);
+
+  if (tags != parse->priv->parser_tags) {
+    if (parse->priv->parser_tags) {
+      gst_tag_list_unref (parse->priv->parser_tags);
+      parse->priv->parser_tags = NULL;
+      parse->priv->parser_tags_merge_mode = GST_TAG_MERGE_APPEND;
+    }
+    if (tags) {
+      parse->priv->parser_tags = gst_tag_list_ref (tags);
+      parse->priv->parser_tags_merge_mode = mode;
+    }
+
+    GST_DEBUG_OBJECT (parse, "setting parser tags to %" GST_PTR_FORMAT
+        " (mode %d)", tags, parse->priv->parser_tags_merge_mode);
+
+    gst_base_parse_check_bitrate_tags (parse);
+    parse->priv->tags_changed = TRUE;
+  }
+
+  GST_OBJECT_UNLOCK (parse);
 }