baseparse: Fix integer overflow in bitrate calculation
[platform/upstream/gstreamer.git] / libs / gst / base / gstbaseparse.c
index 193bdf8..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;
@@ -760,11 +737,8 @@ gst_base_parse_frame_new (GstBuffer * buffer, GstBaseParseFrameFlags flags,
 }
 
 static inline void
-gst_base_parse_frame_update (GstBaseParse * parse, GstBaseParseFrame * frame,
-    GstBuffer * buf)
+gst_base_parse_update_flags (GstBaseParse * parse)
 {
-  gst_buffer_replace (&frame->buffer, buf);
-
   parse->flags = 0;
 
   /* set flags one by one for clarity */
@@ -776,6 +750,24 @@ gst_base_parse_frame_update (GstBaseParse * parse, GstBaseParseFrame * frame,
     parse->flags |= GST_BASE_PARSE_FLAG_LOST_SYNC;
 }
 
+static inline void
+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) {
+    GST_LOG_OBJECT (parse, "marking as new frame");
+    frame->flags |= GST_BASE_PARSE_FRAME_FLAG_NEW_FRAME;
+  }
+
+  frame->offset = parse->priv->prev_offset = parse->priv->offset;
+}
+
 static void
 gst_base_parse_reset (GstBaseParse * parse)
 {
@@ -810,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;
@@ -854,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;
@@ -865,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.
@@ -945,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.
@@ -965,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.
@@ -1122,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;
@@ -1173,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:{
@@ -1211,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)
@@ -1222,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;
   }
@@ -1463,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;
@@ -1526,84 +1749,39 @@ no_duration_bytes:
         G_GUINT64_FORMAT, duration, bytes);
     return FALSE;
   }
-
-}
-
-static void
-gst_base_parse_update_duration (GstBaseParse * baseparse)
-{
-  GstPad *peer;
-  GstBaseParse *parse;
-
-  parse = GST_BASE_PARSE (baseparse);
-
-  peer = gst_pad_get_peer (parse->sinkpad);
-  if (peer) {
-    gboolean qres = FALSE;
-    gint64 ptot, dest_value;
-
-    qres = gst_pad_query_duration (peer, GST_FORMAT_BYTES, &ptot);
-    gst_object_unref (GST_OBJECT (peer));
-    if (qres) {
-      if (gst_base_parse_convert (parse, GST_FORMAT_BYTES, ptot,
-              GST_FORMAT_TIME, &dest_value)) {
-
-        /* inform if duration changed, but try to avoid spamming */
-        parse->priv->estimated_drift +=
-            dest_value - parse->priv->estimated_duration;
-
-        parse->priv->estimated_duration = dest_value;
-        GST_LOG_OBJECT (parse,
-            "updated estimated duration to %" GST_TIME_FORMAT,
-            GST_TIME_ARGS (dest_value));
-
-        if (parse->priv->estimated_drift > GST_SECOND ||
-            parse->priv->estimated_drift < -GST_SECOND) {
-          gst_element_post_message (GST_ELEMENT (parse),
-              gst_message_new_duration_changed (GST_OBJECT (parse)));
-          parse->priv->estimated_drift = 0;
-        }
-      }
-    }
+no_slaved_conversions:
+  {
+    GST_DEBUG_OBJECT (parse,
+        "Can't do format conversions when upstream format is not BYTES");
+    return FALSE;
   }
 }
 
 static void
-gst_base_parse_post_bitrates (GstBaseParse * parse, gboolean post_min,
-    gboolean post_avg, gboolean post_max)
+gst_base_parse_update_duration (GstBaseParse * parse)
 {
-  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 ();
+  gint64 ptot, dest_value;
 
-    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 ();
+  if (!gst_pad_peer_query_duration (parse->sinkpad, GST_FORMAT_BYTES, &ptot))
+    return;
 
-    gst_tag_list_add (taglist, GST_TAG_MERGE_REPLACE,
-        GST_TAG_MAXIMUM_BITRATE, parse->priv->max_bitrate, NULL);
-  }
+  if (!gst_base_parse_convert (parse, GST_FORMAT_BYTES, ptot,
+          GST_FORMAT_TIME, &dest_value))
+    return;
 
-  GST_DEBUG_OBJECT (parse, "Updated bitrates. Min: %u, Avg: %u, Max: %u",
-      parse->priv->min_bitrate, parse->priv->avg_bitrate,
-      parse->priv->max_bitrate);
+  /* inform if duration changed, but try to avoid spamming */
+  parse->priv->estimated_drift += dest_value - parse->priv->estimated_duration;
 
-  if (taglist != NULL) {
-    gst_pad_push_event (parse->srcpad, gst_event_new_tag (taglist));
+  parse->priv->estimated_duration = dest_value;
+  GST_LOG_OBJECT (parse,
+      "updated estimated duration to %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (dest_value));
+
+  if (parse->priv->estimated_drift > GST_SECOND ||
+      parse->priv->estimated_drift < -GST_SECOND) {
+    gst_element_post_message (GST_ELEMENT (parse),
+        gst_message_new_duration_changed (GST_OBJECT (parse)));
+    parse->priv->estimated_drift = 0;
   }
 }
 
@@ -1617,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;
@@ -1635,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;
@@ -1649,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;
 }
 
 /**
@@ -1920,27 +2108,17 @@ gst_base_parse_prepare_frame (GstBaseParse * parse, GstBuffer * buffer)
       GST_BUFFER_OFFSET (buffer), GST_BUFFER_OFFSET (buffer),
       gst_buffer_get_size (buffer));
 
-  if (parse->priv->discont) {
-    GST_DEBUG_OBJECT (parse, "marking DISCONT");
-    GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DISCONT);
-    parse->priv->discont = FALSE;
-  }
-
   GST_BUFFER_OFFSET (buffer) = parse->priv->offset;
 
-  frame = gst_base_parse_frame_new (buffer, 0, 0);
+  gst_base_parse_update_flags (parse);
 
-  /* also ensure to update state flags */
-  gst_base_parse_frame_update (parse, frame, buffer);
+  frame = gst_base_parse_frame_new (buffer, 0, 0);
   gst_buffer_unref (buffer);
+  gst_base_parse_update_frame (parse, frame);
 
-  if (parse->priv->prev_offset != parse->priv->offset || parse->priv->new_frame) {
-    GST_LOG_OBJECT (parse, "marking as new frame");
-    parse->priv->new_frame = FALSE;
-    frame->flags |= GST_BASE_PARSE_FRAME_FLAG_NEW_FRAME;
-  }
-
-  frame->offset = parse->priv->prev_offset = parse->priv->offset;
+  /* clear flags for next frame */
+  parse->priv->discont = FALSE;
+  parse->priv->new_frame = FALSE;
 
   /* use default handler to provide initial (upstream) metadata */
   gst_base_parse_parse_frame (parse, frame);
@@ -2039,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;
@@ -2236,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);
 
@@ -2283,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;
@@ -2327,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) {
@@ -2383,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;
@@ -2496,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;
@@ -2560,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 */
@@ -2597,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 */
@@ -2810,7 +3021,6 @@ gst_base_parse_chain (GstPad * pad, GstObject * parent, GstBuffer * buffer)
   GstBuffer *tmpbuf = NULL;
   guint fsize = 1;
   gint skip = -1;
-  const guint8 *data;
   guint min_size, av;
   GstClockTime pts, dts;
 
@@ -2968,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 ... */
@@ -2992,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,
@@ -3004,31 +3219,29 @@ 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 */
-    data = gst_adapter_map (parse->priv->adapter, av);
-    /* arrange for actual data to be copied if subclass tries to,
-     * since what is passed is tied to the adapter */
-    tmpbuf = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY |
-        GST_MEMORY_FLAG_NO_SHARE, (gpointer) data, av, 0, av, NULL, NULL);
+    tmpbuf = gst_adapter_get_buffer (parse->priv->adapter, av);
 
     /* already inform subclass what timestamps we have planned,
      * at least if provided by time-based upstream */
     if (parse->priv->upstream_format == GST_FORMAT_TIME) {
+      tmpbuf = gst_buffer_make_writable (tmpbuf);
       GST_BUFFER_PTS (tmpbuf) = parse->priv->next_pts;
       GST_BUFFER_DTS (tmpbuf) = parse->priv->next_dts;
+      GST_BUFFER_DURATION (tmpbuf) = GST_CLOCK_TIME_NONE;
     }
 
     /* keep the adapter mapped, so keep track of what has to be flushed */
     ret = gst_base_parse_handle_buffer (parse, tmpbuf, &skip, &flush);
     tmpbuf = NULL;
 
-    /* probably already implicitly unmapped due to adapter operation,
-     * but for good measure ... */
-    gst_adapter_unmap (parse->priv->adapter);
     if (ret != GST_FLOW_OK && ret != GST_FLOW_NOT_LINKED) {
       goto done;
     }
@@ -3039,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:
@@ -3327,8 +3541,6 @@ gst_base_parse_loop (GstPad * pad)
   }
 
   ret = gst_base_parse_scan_frame (parse, klass);
-  if (ret != GST_FLOW_OK)
-    goto done;
 
   /* eat expected eos signalling past segment in reverse playback */
   if (parse->segment.rate < 0.0 && ret == GST_FLOW_EOS &&
@@ -3338,9 +3550,12 @@ gst_base_parse_loop (GstPad * pad)
     gst_base_parse_finish_fragment (parse, FALSE);
     /* force previous fragment */
     parse->priv->offset = -1;
-    ret = GST_FLOW_OK;
+    goto eos;
   }
 
+  if (ret != GST_FLOW_OK)
+    goto done;
+
 done:
   if (ret == GST_FLOW_EOS)
     goto eos;
@@ -3393,11 +3608,13 @@ 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) {
+      if (parse->priv->estimated_duration <= 0) {
+        gst_base_parse_update_duration (parse);
+      }
       /* Push pending events, including SEGMENT events */
       gst_base_parse_push_pending_events (parse);
 
@@ -3440,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);
@@ -3478,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;
@@ -3653,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);
     }
@@ -3832,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 &&
@@ -3842,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);
         }
@@ -3894,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",
@@ -4294,9 +4522,6 @@ gst_base_parse_handle_seek (GstBaseParse * parse, GstEvent * event)
   if (rate < 0.0 && parse->priv->pad_mode == GST_PAD_MODE_PUSH)
     goto negative_rate;
 
-  if (rate < 0.0 && parse->priv->pad_mode == GST_PAD_MODE_PULL)
-    goto negative_rate_pull_mode;
-
   if (start_type != GST_SEEK_TYPE_SET ||
       (stop_type != GST_SEEK_TYPE_SET && stop_type != GST_SEEK_TYPE_NONE))
     goto wrong_type;
@@ -4322,20 +4547,36 @@ gst_base_parse_handle_seek (GstBaseParse * parse, GstEvent * event)
     GST_DEBUG_OBJECT (parse, "accurate seek possible");
     accurate = TRUE;
   }
+
   if (accurate) {
-    GstClockTime startpos = seeksegment.position;
+    GstClockTime startpos;
+    if (rate >= 0)
+      startpos = seeksegment.position;
+    else
+      startpos = start;
 
     /* accurate requested, so ... seek a bit before target */
     if (startpos < parse->priv->lead_in_ts)
       startpos = 0;
     else
       startpos -= parse->priv->lead_in_ts;
+
+    if (seeksegment.stop == -1 && seeksegment.duration != -1)
+      seeksegment.stop = seeksegment.start + seeksegment.duration;
+
     seekpos = gst_base_parse_find_offset (parse, startpos, TRUE, &start_ts);
     seekstop = gst_base_parse_find_offset (parse, seeksegment.stop, FALSE,
         NULL);
   } else {
-    start_ts = seeksegment.position;
-    if (!gst_base_parse_convert (parse, format, seeksegment.position,
+    if (rate >= 0)
+      start_ts = seeksegment.position;
+    else
+      start_ts = start;
+
+    if (seeksegment.stop == -1 && seeksegment.duration != -1)
+      seeksegment.stop = seeksegment.start + seeksegment.duration;
+
+    if (!gst_base_parse_convert (parse, format, start_ts,
             GST_FORMAT_BYTES, &seekpos))
       goto convert_failed;
     if (!gst_base_parse_convert (parse, format, seeksegment.stop,
@@ -4389,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));
@@ -4499,12 +4736,6 @@ done:
   return res;
 
   /* ERRORS */
-negative_rate_pull_mode:
-  {
-    GST_FIXME_OBJECT (parse, "negative playback in pull mode needs fixing");
-    res = FALSE;
-    goto done;
-  }
 negative_rate:
   {
     GST_DEBUG_OBJECT (parse, "negative playback rates delegated upstream.");
@@ -4532,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
@@ -4677,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);
 }