flvmux: Release pads via GstAggregator
[platform/upstream/gstreamer.git] / gst / flv / gstflvmux.c
index 8119e9d..69e8864 100644 (file)
@@ -1,6 +1,9 @@
 /* GStreamer
  *
  * Copyright (c) 2008,2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ * Copyright (c) 2008-2017 Collabora Ltd
+ *  @author: Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ *  @author: Vincent Penquerc'h <vincent.penquerch@collabora.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
  *
  * You should have received a copy of the GNU Library General Public
  * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
  */
 
 /**
  * SECTION:element-flvmux
+ * @title: flvmux
  *
  * flvmux muxes different streams into an FLV file.
  *
- * <refsect2>
- * <title>Example launch line</title>
+ * ## Example launch line
  * |[
- * gst-launch -v filesrc location=/path/to/audio ! decodebin2 ! queue ! flvmux name=m ! filesink location=file.flv   filesrc location=/path/to/video ! decodebin2 ! queue ! m.
- * ]| This pipeline muxes an audio and video file into a single FLV file.
- * </refsect2>
+ * gst-launch-1.0 -v flvmux name=mux ! filesink location=test.flv  audiotestsrc samplesperbuffer=44100 num-buffers=10 ! faac ! mux.  videotestsrc num-buffers=250 ! video/x-raw,framerate=25/1 ! x264enc ! mux.
+ * ]| This pipeline encodes a test audio and video stream and muxes both into an FLV file.
+ *
  */
 
 #ifdef HAVE_CONFIG_H
@@ -49,11 +52,16 @@ GST_DEBUG_CATEGORY_STATIC (flvmux_debug);
 enum
 {
   PROP_0,
-  PROP_STREAMABLE
+  PROP_STREAMABLE,
+  PROP_METADATACREATOR,
+  PROP_ENCODER,
+  PROP_SKIP_BACKWARDS_STREAMS,
 };
 
 #define DEFAULT_STREAMABLE FALSE
 #define MAX_INDEX_ENTRIES 128
+#define DEFAULT_METADATACREATOR "GStreamer " PACKAGE_VERSION " FLV muxer"
+#define DEFAULT_SKIP_BACKWARDS_STREAMS FALSE
 
 static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
     GST_PAD_SRC,
@@ -76,47 +84,130 @@ static GstStaticPadTemplate audiosink_templ = GST_STATIC_PAD_TEMPLATE ("audio",
     GST_STATIC_CAPS
     ("audio/x-adpcm, layout = (string) swf, channels = (int) { 1, 2 }, rate = (int) { 5512, 11025, 22050, 44100 }; "
         "audio/mpeg, mpegversion = (int) 1, layer = (int) 3, channels = (int) { 1, 2 }, rate = (int) { 5512, 8000, 11025, 22050, 44100 }, parsed = (boolean) TRUE; "
-        "audio/mpeg, mpegversion = (int) 2, framed = (boolean) TRUE; "
-        "audio/mpeg, mpegversion = (int) 4, stream-format = (string) raw, framed = (boolean) TRUE; "
+        "audio/mpeg, mpegversion = (int) { 4, 2 }, stream-format = (string) raw; "
         "audio/x-nellymoser, channels = (int) { 1, 2 }, rate = (int) { 5512, 8000, 11025, 16000, 22050, 44100 }; "
         "audio/x-raw, format = (string) { U8, S16LE}, layout = (string) interleaved, channels = (int) { 1, 2 }, rate = (int) { 5512, 11025, 22050, 44100 }; "
-        "audio/x-alaw, channels = (int) { 1, 2 }, rate = (int) { 5512, 11025, 22050, 44100 }; "
-        "audio/x-mulaw, channels = (int) { 1, 2 }, rate = (int) { 5512, 11025, 22050, 44100 }; "
-        "audio/x-speex, channels = (int) { 1, 2 }, rate = (int) { 5512, 11025, 22050, 44100 };")
+        "audio/x-alaw, channels = (int) { 1, 2 }, rate = (int) 8000; "
+        "audio/x-mulaw, channels = (int) { 1, 2 }, rate = (int) 8000; "
+        "audio/x-speex, channels = (int) 1, rate = (int) 16000;")
     );
 
+G_DEFINE_TYPE (GstFlvMuxPad, gst_flv_mux_pad, GST_TYPE_AGGREGATOR_PAD);
+
 #define gst_flv_mux_parent_class parent_class
-G_DEFINE_TYPE_WITH_CODE (GstFlvMux, gst_flv_mux, GST_TYPE_ELEMENT,
+G_DEFINE_TYPE_WITH_CODE (GstFlvMux, gst_flv_mux, GST_TYPE_AGGREGATOR,
     G_IMPLEMENT_INTERFACE (GST_TYPE_TAG_SETTER, NULL));
 
-static void gst_flv_mux_finalize (GObject * object);
 static GstFlowReturn
-gst_flv_mux_handle_buffer (GstCollectPads2 * pads, GstCollectData2 * cdata,
-    GstBuffer * buf, gpointer user_data);
+gst_flv_mux_aggregate (GstAggregator * aggregator, gboolean timeout);
 static gboolean
-gst_flv_mux_handle_sink_event (GstCollectPads2 * pads, GstCollectData2 * data,
-    GstEvent * event, gpointer user_data);
-
-static gboolean gst_flv_mux_handle_src_event (GstPad * pad, GstObject * parent,
+gst_flv_mux_sink_event (GstAggregator * aggregator, GstAggregatorPad * pad,
     GstEvent * event);
-static GstPad *gst_flv_mux_request_new_pad (GstElement * element,
+
+static GstAggregatorPad *gst_flv_mux_create_new_pad (GstAggregator * agg,
     GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps);
 static void gst_flv_mux_release_pad (GstElement * element, GstPad * pad);
 
-static gboolean gst_flv_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps);
-static gboolean gst_flv_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps);
+static gboolean gst_flv_mux_video_pad_setcaps (GstFlvMuxPad * pad,
+    GstCaps * caps);
+static gboolean gst_flv_mux_audio_pad_setcaps (GstFlvMuxPad * pad,
+    GstCaps * caps);
 
 static void gst_flv_mux_get_property (GObject * object,
     guint prop_id, GValue * value, GParamSpec * pspec);
 static void gst_flv_mux_set_property (GObject * object,
     guint prop_id, const GValue * value, GParamSpec * pspec);
-
-static GstStateChangeReturn
-gst_flv_mux_change_state (GstElement * element, GstStateChange transition);
+static void gst_flv_mux_finalize (GObject * object);
 
 static void gst_flv_mux_reset (GstElement * element);
-static void gst_flv_mux_reset_pad (GstFlvMux * mux, GstFlvPad * pad,
-    gboolean video);
+static void gst_flv_mux_reset_pad (GstFlvMuxPad * pad);
+
+static void gst_flv_mux_pad_finalize (GObject * object);
+
+static gboolean gst_flv_mux_start (GstAggregator * aggregator);
+static GstFlowReturn gst_flv_mux_flush (GstAggregator * aggregator);
+static GstClockTime gst_flv_mux_get_next_time (GstAggregator * aggregator);
+static GstFlowReturn gst_flv_mux_write_eos (GstFlvMux * mux);
+static GstFlowReturn gst_flv_mux_write_header (GstFlvMux * mux);
+static GstFlowReturn gst_flv_mux_rewrite_header (GstFlvMux * mux);
+static gboolean gst_flv_mux_are_all_pads_eos (GstFlvMux * mux);
+static GstFlowReturn gst_flv_mux_update_src_caps (GstAggregator * aggregator,
+    GstCaps * caps, GstCaps ** ret);
+static GstClockTime gst_flv_mux_query_upstream_duration (GstFlvMux * mux);
+static GstClockTime gst_flv_mux_segment_to_running_time (const GstSegment *
+    segment, GstClockTime t);
+
+static GstFlowReturn
+gst_flv_mux_pad_flush (GstAggregatorPad * pad, GstAggregator * aggregator)
+{
+  GstFlvMuxPad *flvpad = GST_FLV_MUX_PAD (pad);
+
+  flvpad->last_timestamp = GST_CLOCK_TIME_NONE;
+  flvpad->pts = GST_CLOCK_TIME_NONE;
+  flvpad->dts = GST_CLOCK_TIME_NONE;
+
+  return GST_FLOW_OK;
+}
+
+static gboolean
+gst_flv_mux_skip_buffer (GstAggregatorPad * apad, GstAggregator * aggregator,
+    GstBuffer * buffer)
+{
+  GstFlvMuxPad *fpad = GST_FLV_MUX_PAD_CAST (apad);
+  GstFlvMux *mux = GST_FLV_MUX_CAST (aggregator);
+  GstClockTime t;
+
+  if (!mux->skip_backwards_streams)
+    return FALSE;
+
+  if (fpad->drop_deltas) {
+    if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)) {
+      GST_INFO_OBJECT (fpad, "Waiting for keyframe, dropping %" GST_PTR_FORMAT,
+          buffer);
+      return TRUE;
+    } else {
+      /* drop-deltas is set and the buffer isn't delta, drop flag */
+      fpad->drop_deltas = FALSE;
+    }
+  }
+
+  if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS_OR_PTS (buffer))) {
+    t = gst_flv_mux_segment_to_running_time (&apad->segment,
+        GST_BUFFER_DTS_OR_PTS (buffer));
+
+    if (t < (GST_MSECOND * mux->last_dts)) {
+      GST_WARNING_OBJECT (fpad,
+          "Timestamp %" GST_TIME_FORMAT " going backwards from last used %"
+          GST_TIME_FORMAT ", dropping %" GST_PTR_FORMAT,
+          GST_TIME_ARGS (t), GST_TIME_ARGS (GST_MSECOND * mux->last_dts),
+          buffer);
+      /* Look for non-delta buffer */
+      fpad->drop_deltas = TRUE;
+      return TRUE;
+    }
+  }
+
+  return FALSE;
+}
+
+static void
+gst_flv_mux_pad_class_init (GstFlvMuxPadClass * klass)
+{
+  GstAggregatorPadClass *aggregatorpad_class = (GstAggregatorPadClass *) klass;
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+
+  gobject_class->finalize = gst_flv_mux_pad_finalize;
+
+  aggregatorpad_class->flush = GST_DEBUG_FUNCPTR (gst_flv_mux_pad_flush);
+  aggregatorpad_class->skip_buffer =
+      GST_DEBUG_FUNCPTR (gst_flv_mux_skip_buffer);
+}
+
+static void
+gst_flv_mux_pad_init (GstFlvMuxPad * pad)
+{
+  gst_flv_mux_reset_pad (pad);
+}
 
 typedef struct
 {
@@ -136,7 +227,7 @@ _gst_buffer_new_wrapped (gpointer mem, gsize size, GFreeFunc free_func)
   GstBuffer *buf;
 
   buf = gst_buffer_new ();
-  gst_buffer_take_memory (buf, -1,
+  gst_buffer_append_memory (buf,
       gst_memory_new_wrapped (free_func ? 0 : GST_MEMORY_FLAG_READONLY,
           mem, size, 0, size, mem, free_func));
 
@@ -158,11 +249,13 @@ gst_flv_mux_class_init (GstFlvMuxClass * klass)
 {
   GObjectClass *gobject_class;
   GstElementClass *gstelement_class;
+  GstAggregatorClass *gstaggregator_class;
 
   GST_DEBUG_CATEGORY_INIT (flvmux_debug, "flvmux", 0, "FLV muxer");
 
   gobject_class = (GObjectClass *) klass;
   gstelement_class = (GstElementClass *) klass;
+  gstaggregator_class = (GstAggregatorClass *) klass;
 
   gobject_class->get_property = gst_flv_mux_get_property;
   gobject_class->set_property = gst_flv_mux_set_property;
@@ -175,54 +268,69 @@ gst_flv_mux_class_init (GstFlvMuxClass * klass)
    *
    * If True, the output will be streaming friendly. (ie without indexes and
    * duration)
-   *
-   * Since: 0.10.24
-   **/
+   */
   g_object_class_install_property (gobject_class, PROP_STREAMABLE,
       g_param_spec_boolean ("streamable", "streamable",
           "If set to true, the output should be as if it is to be streamed "
           "and hence no indexes written or duration written.",
           DEFAULT_STREAMABLE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
-  gstelement_class->change_state = GST_DEBUG_FUNCPTR (gst_flv_mux_change_state);
-  gstelement_class->request_new_pad =
-      GST_DEBUG_FUNCPTR (gst_flv_mux_request_new_pad);
+  g_object_class_install_property (gobject_class, PROP_METADATACREATOR,
+      g_param_spec_string ("metadatacreator", "metadatacreator",
+          "The value of metadatacreator in the meta packet.",
+          NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_ENCODER,
+      g_param_spec_string ("encoder", "encoder",
+          "The value of encoder in the meta packet.",
+          NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, PROP_SKIP_BACKWARDS_STREAMS,
+      g_param_spec_boolean ("skip-backwards-streams", "Skip backwards streams",
+          "If set to true, streams that go backwards related to the other stream "
+          "will have buffers dropped until they reach the correct timestamp",
+          DEFAULT_SKIP_BACKWARDS_STREAMS,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  gstaggregator_class->create_new_pad =
+      GST_DEBUG_FUNCPTR (gst_flv_mux_create_new_pad);
   gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_flv_mux_release_pad);
 
-  gst_element_class_add_pad_template (gstelement_class,
-      gst_static_pad_template_get (&videosink_templ));
-  gst_element_class_add_pad_template (gstelement_class,
-      gst_static_pad_template_get (&audiosink_templ));
-  gst_element_class_add_pad_template (gstelement_class,
-      gst_static_pad_template_get (&src_templ));
-  gst_element_class_set_details_simple (gstelement_class, "FLV muxer",
+  gstaggregator_class->start = GST_DEBUG_FUNCPTR (gst_flv_mux_start);
+  gstaggregator_class->aggregate = GST_DEBUG_FUNCPTR (gst_flv_mux_aggregate);
+  gstaggregator_class->sink_event = GST_DEBUG_FUNCPTR (gst_flv_mux_sink_event);
+  gstaggregator_class->flush = GST_DEBUG_FUNCPTR (gst_flv_mux_flush);
+  gstaggregator_class->get_next_time =
+      GST_DEBUG_FUNCPTR (gst_flv_mux_get_next_time);
+  gstaggregator_class->update_src_caps =
+      GST_DEBUG_FUNCPTR (gst_flv_mux_update_src_caps);
+
+  gst_element_class_add_static_pad_template_with_gtype (gstelement_class,
+      &videosink_templ, GST_TYPE_FLV_MUX_PAD);
+  gst_element_class_add_static_pad_template_with_gtype (gstelement_class,
+      &audiosink_templ, GST_TYPE_FLV_MUX_PAD);
+  gst_element_class_add_static_pad_template_with_gtype (gstelement_class,
+      &src_templ, GST_TYPE_AGGREGATOR_PAD);
+  gst_element_class_set_static_metadata (gstelement_class, "FLV muxer",
       "Codec/Muxer",
       "Muxes video/audio streams into a FLV stream",
       "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
 
   GST_DEBUG_CATEGORY_INIT (flvmux_debug, "flvmux", 0, "FLV muxer");
+
+  gst_type_mark_as_plugin_api (GST_TYPE_FLV_MUX_PAD, 0);
 }
 
 static void
 gst_flv_mux_init (GstFlvMux * mux)
 {
-  mux->srcpad = gst_pad_new_from_static_template (&src_templ, "src");
-  gst_pad_set_event_function (mux->srcpad, gst_flv_mux_handle_src_event);
-  gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad);
+  mux->srcpad = GST_AGGREGATOR_CAST (mux)->srcpad;
 
   /* property */
   mux->streamable = DEFAULT_STREAMABLE;
+  mux->metadatacreator = g_strdup (DEFAULT_METADATACREATOR);
+  mux->encoder = g_strdup (DEFAULT_METADATACREATOR);
 
   mux->new_tags = FALSE;
 
-  mux->collect = gst_collect_pads2_new ();
-  gst_collect_pads2_set_buffer_function (mux->collect,
-      GST_DEBUG_FUNCPTR (gst_flv_mux_handle_buffer), mux);
-  gst_collect_pads2_set_event_function (mux->collect,
-      GST_DEBUG_FUNCPTR (gst_flv_mux_handle_sink_event), mux);
-  gst_collect_pads2_set_clip_function (mux->collect,
-      GST_DEBUG_FUNCPTR (gst_collect_pads2_clip_running_time), mux);
-
   gst_flv_mux_reset (GST_ELEMENT (mux));
 }
 
@@ -231,83 +339,99 @@ gst_flv_mux_finalize (GObject * object)
 {
   GstFlvMux *mux = GST_FLV_MUX (object);
 
-  gst_object_unref (mux->collect);
+  gst_flv_mux_reset (GST_ELEMENT (object));
+  g_free (mux->metadatacreator);
+  g_free (mux->encoder);
 
-  G_OBJECT_CLASS (parent_class)->finalize (object);
+  G_OBJECT_CLASS (gst_flv_mux_parent_class)->finalize (object);
 }
 
 static void
-gst_flv_mux_reset (GstElement * element)
+gst_flv_mux_pad_finalize (GObject * object)
 {
-  GstFlvMux *mux = GST_FLV_MUX (element);
-  GSList *sl;
+  GstFlvMuxPad *pad = GST_FLV_MUX_PAD (object);
 
-  for (sl = mux->collect->data; sl != NULL; sl = g_slist_next (sl)) {
-    GstFlvPad *cpad = (GstFlvPad *) sl->data;
+  gst_flv_mux_reset_pad (pad);
 
-    gst_flv_mux_reset_pad (mux, cpad, cpad->video);
-  }
+  G_OBJECT_CLASS (gst_flv_mux_pad_parent_class)->finalize (object);
+}
+
+static GstFlowReturn
+gst_flv_mux_flush (GstAggregator * aggregator)
+{
+  /* TODO: What is the right behaviour on flush? Should we just ignore it ?
+   * This still needs to be defined. */
+
+  gst_flv_mux_reset (GST_ELEMENT (aggregator));
+  return GST_FLOW_OK;
+}
+
+static gboolean
+gst_flv_mux_start (GstAggregator * aggregator)
+{
+  gst_flv_mux_reset (GST_ELEMENT (aggregator));
+  return TRUE;
+}
+
+static void
+gst_flv_mux_reset (GstElement * element)
+{
+  GstFlvMux *mux = GST_FLV_MUX (element);
 
   g_list_foreach (mux->index, (GFunc) gst_flv_mux_index_entry_free, NULL);
   g_list_free (mux->index);
   mux->index = NULL;
   mux->byte_count = 0;
 
-  mux->have_audio = mux->have_video = FALSE;
   mux->duration = GST_CLOCK_TIME_NONE;
   mux->new_tags = FALSE;
+  mux->first_timestamp = GST_CLOCK_TIME_NONE;
+  mux->last_dts = 0;
 
   mux->state = GST_FLV_MUX_STATE_HEADER;
+  mux->sent_header = FALSE;
 
   /* tags */
   gst_tag_setter_reset_tags (GST_TAG_SETTER (mux));
 }
 
-static gboolean
-gst_flv_mux_handle_src_event (GstPad * pad, GstObject * parent,
-    GstEvent * event)
+/* Extract per-codec relevant tags for
+ * insertion into the metadata later - ie bitrate,
+ * but maybe others in the future */
+static void
+gst_flv_mux_store_codec_tags (GstFlvMux * mux,
+    GstFlvMuxPad * flvpad, GstTagList * list)
 {
-  GstEventType type;
-
-  type = event ? GST_EVENT_TYPE (event) : GST_EVENT_UNKNOWN;
-
-  switch (type) {
-    case GST_EVENT_SEEK:
-      /* disable seeking for now */
-      return FALSE;
-    default:
-      break;
+  /* Look for a bitrate as either nominal or actual bitrate tag */
+  if (gst_tag_list_get_uint (list, GST_TAG_NOMINAL_BITRATE, &flvpad->bitrate)
+      || gst_tag_list_get_uint (list, GST_TAG_BITRATE, &flvpad->bitrate)) {
+    GST_DEBUG_OBJECT (mux, "Stored bitrate for pad %" GST_PTR_FORMAT " = %u",
+        flvpad, flvpad->bitrate);
   }
-
-  return gst_pad_event_default (pad, parent, event);
 }
 
 static gboolean
-gst_flv_mux_handle_sink_event (GstCollectPads2 * pads, GstCollectData2 * data,
-    GstEvent * event, gpointer user_data)
+gst_flv_mux_sink_event (GstAggregator * aggregator, GstAggregatorPad * pad,
+    GstEvent * event)
 {
-  GstFlvMux *mux = GST_FLV_MUX (user_data);
-  gboolean ret = FALSE;
+  GstFlvMux *mux = GST_FLV_MUX (aggregator);
+  GstFlvMuxPad *flvpad = (GstFlvMuxPad *) pad;
+  gboolean ret = TRUE;
 
   switch (GST_EVENT_TYPE (event)) {
     case GST_EVENT_CAPS:
     {
       GstCaps *caps;
-      GstFlvPad *flvpad;
 
       gst_event_parse_caps (event, &caps);
 
-      /* find stream data */
-      flvpad = (GstFlvPad *) data;
-      g_assert (flvpad);
-
-      if (flvpad->video) {
-        ret = gst_flv_mux_video_pad_setcaps (data->pad, caps);
+      if (mux->video_pad == flvpad) {
+        ret = gst_flv_mux_video_pad_setcaps (flvpad, caps);
+      } else if (mux->audio_pad == flvpad) {
+        ret = gst_flv_mux_audio_pad_setcaps (flvpad, caps);
       } else {
-        ret = gst_flv_mux_audio_pad_setcaps (data->pad, caps);
+        g_assert_not_reached ();
       }
-      /* and eat */
-      gst_event_unref (event);
       break;
     }
     case GST_EVENT_TAG:{
@@ -317,44 +441,47 @@ gst_flv_mux_handle_sink_event (GstCollectPads2 * pads, GstCollectData2 * data,
 
       gst_event_parse_tag (event, &list);
       gst_tag_setter_merge_tags (setter, list, mode);
+      gst_flv_mux_store_codec_tags (mux, flvpad, list);
       mux->new_tags = TRUE;
       ret = TRUE;
-      gst_event_unref (event);
       break;
     }
-    case GST_EVENT_EOS:
-    case GST_EVENT_SEGMENT:
-      gst_event_unref (event);
-      ret = TRUE;
-      break;
     default:
-      ret = gst_pad_event_default (data->pad, GST_OBJECT (mux), event);
       break;
   }
 
-  return ret;
+  if (!ret)
+    return FALSE;
+
+  return GST_AGGREGATOR_CLASS (parent_class)->sink_event (aggregator, pad,
+      event);;
 }
 
 static gboolean
-gst_flv_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps)
+gst_flv_mux_video_pad_setcaps (GstFlvMuxPad * pad, GstCaps * caps)
 {
   GstFlvMux *mux = GST_FLV_MUX (gst_pad_get_parent (pad));
-  GstFlvPad *cpad = (GstFlvPad *) gst_pad_get_element_private (pad);
   gboolean ret = TRUE;
   GstStructure *s;
+  guint old_codec;
+  GstBuffer *old_codec_data = NULL;
+
+  old_codec = pad->codec;
+  if (pad->codec_data)
+    old_codec_data = gst_buffer_ref (pad->codec_data);
 
   s = gst_caps_get_structure (caps, 0);
 
   if (strcmp (gst_structure_get_name (s), "video/x-flash-video") == 0) {
-    cpad->video_codec = 2;
+    pad->codec = 2;
   } else if (strcmp (gst_structure_get_name (s), "video/x-flash-screen") == 0) {
-    cpad->video_codec = 3;
+    pad->codec = 3;
   } else if (strcmp (gst_structure_get_name (s), "video/x-vp6-flash") == 0) {
-    cpad->video_codec = 4;
+    pad->codec = 4;
   } else if (strcmp (gst_structure_get_name (s), "video/x-vp6-alpha") == 0) {
-    cpad->video_codec = 5;
+    pad->codec = 5;
   } else if (strcmp (gst_structure_get_name (s), "video/x-h264") == 0) {
-    cpad->video_codec = 7;
+    pad->codec = 7;
   } else {
     ret = FALSE;
   }
@@ -363,28 +490,63 @@ gst_flv_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps)
     const GValue *val = gst_structure_get_value (s, "codec_data");
 
     if (val)
-      cpad->video_codec_data = gst_buffer_ref (gst_value_get_buffer (val));
+      gst_buffer_replace (&pad->codec_data, gst_value_get_buffer (val));
+    else if (!val && pad->codec_data)
+      gst_buffer_unref (pad->codec_data);
   }
 
+  if (ret && mux->streamable && mux->state != GST_FLV_MUX_STATE_HEADER) {
+    if (old_codec != pad->codec) {
+      pad->info_changed = TRUE;
+    }
+
+    if (old_codec_data && pad->codec_data) {
+      GstMapInfo map;
+
+      gst_buffer_map (old_codec_data, &map, GST_MAP_READ);
+      if (map.size != gst_buffer_get_size (pad->codec_data) ||
+          gst_buffer_memcmp (pad->codec_data, 0, map.data, map.size))
+        pad->info_changed = TRUE;
+
+      gst_buffer_unmap (old_codec_data, &map);
+    } else if (!old_codec_data && pad->codec_data) {
+      pad->info_changed = TRUE;
+    }
+
+    if (pad->info_changed)
+      mux->state = GST_FLV_MUX_STATE_HEADER;
+  }
+
+  if (old_codec_data)
+    gst_buffer_unref (old_codec_data);
+
   gst_object_unref (mux);
 
   return ret;
 }
 
 static gboolean
-gst_flv_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
+gst_flv_mux_audio_pad_setcaps (GstFlvMuxPad * pad, GstCaps * caps)
 {
   GstFlvMux *mux = GST_FLV_MUX (gst_pad_get_parent (pad));
-  GstFlvPad *cpad = (GstFlvPad *) gst_pad_get_element_private (pad);
   gboolean ret = TRUE;
   GstStructure *s;
+  guint old_codec, old_rate, old_width, old_channels;
+  GstBuffer *old_codec_data = NULL;
+
+  old_codec = pad->codec;
+  old_rate = pad->rate;
+  old_width = pad->width;
+  old_channels = pad->channels;
+  if (pad->codec_data)
+    old_codec_data = gst_buffer_ref (pad->codec_data);
 
   s = gst_caps_get_structure (caps, 0);
 
   if (strcmp (gst_structure_get_name (s), "audio/x-adpcm") == 0) {
     const gchar *layout = gst_structure_get_string (s, "layout");
     if (layout && strcmp (layout, "swf") == 0) {
-      cpad->audio_codec = 1;
+      pad->codec = 1;
     } else {
       ret = FALSE;
     }
@@ -399,14 +561,14 @@ gst_flv_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
           gint rate;
 
           if (gst_structure_get_int (s, "rate", &rate) && rate == 8000)
-            cpad->audio_codec = 14;
+            pad->codec = 14;
           else
-            cpad->audio_codec = 2;
+            pad->codec = 2;
         } else {
           ret = FALSE;
         }
       } else if (mpegversion == 4 || mpegversion == 2) {
-        cpad->audio_codec = 10;
+        pad->codec = 10;
       } else {
         ret = FALSE;
       }
@@ -419,34 +581,34 @@ gst_flv_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
     if (gst_structure_get_int (s, "rate", &rate)
         && gst_structure_get_int (s, "channels", &channels)) {
       if (channels == 1 && rate == 16000)
-        cpad->audio_codec = 4;
+        pad->codec = 4;
       else if (channels == 1 && rate == 8000)
-        cpad->audio_codec = 5;
+        pad->codec = 5;
       else
-        cpad->audio_codec = 6;
+        pad->codec = 6;
     } else {
-      cpad->audio_codec = 6;
+      pad->codec = 6;
     }
   } else if (strcmp (gst_structure_get_name (s), "audio/x-raw") == 0) {
     GstAudioInfo info;
 
     if (gst_audio_info_from_caps (&info, caps)) {
-      cpad->audio_codec = 3;
+      pad->codec = 3;
 
       if (GST_AUDIO_INFO_WIDTH (&info) == 8)
-        cpad->width = 0;
+        pad->width = 0;
       else if (GST_AUDIO_INFO_WIDTH (&info) == 16)
-        cpad->width = 1;
+        pad->width = 1;
       else
         ret = FALSE;
     } else
       ret = FALSE;
   } else if (strcmp (gst_structure_get_name (s), "audio/x-alaw") == 0) {
-    cpad->audio_codec = 7;
+    pad->codec = 7;
   } else if (strcmp (gst_structure_get_name (s), "audio/x-mulaw") == 0) {
-    cpad->audio_codec = 8;
+    pad->codec = 8;
   } else if (strcmp (gst_structure_get_name (s), "audio/x-speex") == 0) {
-    cpad->audio_codec = 11;
+    pad->codec = 11;
   } else {
     ret = FALSE;
   }
@@ -455,116 +617,141 @@ gst_flv_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
     gint rate, channels;
 
     if (gst_structure_get_int (s, "rate", &rate)) {
-      if (cpad->audio_codec == 10)
-        cpad->rate = 3;
+      if (pad->codec == 10)
+        pad->rate = 3;
       else if (rate == 5512)
-        cpad->rate = 0;
+        pad->rate = 0;
       else if (rate == 11025)
-        cpad->rate = 1;
+        pad->rate = 1;
       else if (rate == 22050)
-        cpad->rate = 2;
+        pad->rate = 2;
       else if (rate == 44100)
-        cpad->rate = 3;
-      else if (rate == 8000 && (cpad->audio_codec == 5
-              || cpad->audio_codec == 14))
-        cpad->rate = 0;
-      else if (rate == 16000 && cpad->audio_codec == 4)
-        cpad->rate = 0;
+        pad->rate = 3;
+      else if (rate == 8000 && (pad->codec == 5 || pad->codec == 14
+              || pad->codec == 7 || pad->codec == 8))
+        pad->rate = 0;
+      else if (rate == 16000 && (pad->codec == 4 || pad->codec == 11))
+        pad->rate = 0;
       else
         ret = FALSE;
-    } else if (cpad->audio_codec == 10) {
-      cpad->rate = 3;
+    } else if (pad->codec == 10) {
+      pad->rate = 3;
     } else {
       ret = FALSE;
     }
 
     if (gst_structure_get_int (s, "channels", &channels)) {
-      if (cpad->audio_codec == 4 || cpad->audio_codec == 5
-          || cpad->audio_codec == 6)
-        cpad->channels = 0;
-      else if (cpad->audio_codec == 10)
-        cpad->channels = 1;
+      if (pad->codec == 4 || pad->codec == 5
+          || pad->codec == 6 || pad->codec == 11)
+        pad->channels = 0;
+      else if (pad->codec == 10)
+        pad->channels = 1;
       else if (channels == 1)
-        cpad->channels = 0;
+        pad->channels = 0;
       else if (channels == 2)
-        cpad->channels = 1;
+        pad->channels = 1;
       else
         ret = FALSE;
-    } else if (cpad->audio_codec == 4 || cpad->audio_codec == 5
-        || cpad->audio_codec == 6) {
-      cpad->channels = 0;
-    } else if (cpad->audio_codec == 10) {
-      cpad->channels = 1;
+    } else if (pad->codec == 4 || pad->codec == 5 || pad->codec == 6) {
+      pad->channels = 0;
+    } else if (pad->codec == 10) {
+      pad->channels = 1;
     } else {
       ret = FALSE;
     }
 
-    if (cpad->audio_codec != 3)
-      cpad->width = 1;
+    if (pad->codec != 3)
+      pad->width = 1;
   }
 
   if (ret && gst_structure_has_field (s, "codec_data")) {
     const GValue *val = gst_structure_get_value (s, "codec_data");
 
     if (val)
-      cpad->audio_codec_data = gst_buffer_ref (gst_value_get_buffer (val));
+      gst_buffer_replace (&pad->codec_data, gst_value_get_buffer (val));
+    else if (!val && pad->codec_data)
+      gst_buffer_unref (pad->codec_data);
+  }
+
+  if (ret && mux->streamable && mux->state != GST_FLV_MUX_STATE_HEADER) {
+    if (old_codec != pad->codec || old_rate != pad->rate ||
+        old_width != pad->width || old_channels != pad->channels) {
+      pad->info_changed = TRUE;
+    }
+
+    if (old_codec_data && pad->codec_data) {
+      GstMapInfo map;
+
+      gst_buffer_map (old_codec_data, &map, GST_MAP_READ);
+      if (map.size != gst_buffer_get_size (pad->codec_data) ||
+          gst_buffer_memcmp (pad->codec_data, 0, map.data, map.size))
+        pad->info_changed = TRUE;
+
+      gst_buffer_unmap (old_codec_data, &map);
+    } else if (!old_codec_data && pad->codec_data) {
+      pad->info_changed = TRUE;
+    }
+
+    if (pad->info_changed)
+      mux->state = GST_FLV_MUX_STATE_HEADER;
   }
 
+  if (old_codec_data)
+    gst_buffer_unref (old_codec_data);
+
   gst_object_unref (mux);
 
   return ret;
 }
 
 static void
-gst_flv_mux_reset_pad (GstFlvMux * mux, GstFlvPad * cpad, gboolean video)
+gst_flv_mux_reset_pad (GstFlvMuxPad * pad)
 {
-  cpad->video = video;
-
-  if (cpad->audio_codec_data)
-    gst_buffer_unref (cpad->audio_codec_data);
-  cpad->audio_codec_data = NULL;
-  cpad->audio_codec = G_MAXUINT;
-  cpad->rate = G_MAXUINT;
-  cpad->width = G_MAXUINT;
-  cpad->channels = G_MAXUINT;
-
-  if (cpad->video_codec_data)
-    gst_buffer_unref (cpad->video_codec_data);
-  cpad->video_codec_data = NULL;
-  cpad->video_codec = G_MAXUINT;
-  cpad->last_timestamp = 0;
+  GST_DEBUG_OBJECT (pad, "resetting pad");
+
+  if (pad->codec_data)
+    gst_buffer_unref (pad->codec_data);
+  pad->codec_data = NULL;
+  pad->codec = G_MAXUINT;
+  pad->rate = G_MAXUINT;
+  pad->width = G_MAXUINT;
+  pad->channels = G_MAXUINT;
+  pad->info_changed = FALSE;
+  pad->drop_deltas = FALSE;
+
+  gst_flv_mux_pad_flush (GST_AGGREGATOR_PAD_CAST (pad), NULL);
 }
 
-static GstPad *
-gst_flv_mux_request_new_pad (GstElement * element,
+static GstAggregatorPad *
+gst_flv_mux_create_new_pad (GstAggregator * agg,
     GstPadTemplate * templ, const gchar * req_name, const GstCaps * caps)
 {
-  GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
-  GstFlvMux *mux = GST_FLV_MUX (element);
-  GstFlvPad *cpad;
-  GstPad *pad = NULL;
+  GstElementClass *klass = GST_ELEMENT_GET_CLASS (agg);
+  GstAggregatorPad *aggpad;
+  GstFlvMux *mux = GST_FLV_MUX (agg);
+  GstFlvMuxPad *pad = NULL;
   const gchar *name = NULL;
   gboolean video;
 
-  if (mux->state != GST_FLV_MUX_STATE_HEADER) {
-    GST_WARNING_OBJECT (mux, "Can't request pads after writing header");
+  if (mux->state != GST_FLV_MUX_STATE_HEADER && !mux->streamable) {
+    GST_ELEMENT_WARNING (mux, STREAM, MUX,
+        ("Requested a late stream in a non-streamable file"),
+        ("Stream added after file started and therefore won't be playable"));
     return NULL;
   }
 
   if (templ == gst_element_class_get_pad_template (klass, "audio")) {
-    if (mux->have_audio) {
+    if (mux->audio_pad) {
       GST_WARNING_OBJECT (mux, "Already have an audio pad");
       return NULL;
     }
-    mux->have_audio = TRUE;
     name = "audio";
     video = FALSE;
   } else if (templ == gst_element_class_get_pad_template (klass, "video")) {
-    if (mux->have_video) {
+    if (mux->video_pad) {
       GST_WARNING_OBJECT (mux, "Already have a video pad");
       return NULL;
     }
-    mux->have_video = TRUE;
     name = "video";
     video = TRUE;
   } else {
@@ -572,39 +759,59 @@ gst_flv_mux_request_new_pad (GstElement * element,
     return NULL;
   }
 
-  pad = gst_pad_new_from_template (templ, name);
-  cpad = (GstFlvPad *)
-      gst_collect_pads2_add_pad (mux->collect, pad, sizeof (GstFlvPad));
+  aggpad =
+      GST_AGGREGATOR_CLASS (gst_flv_mux_parent_class)->create_new_pad (agg,
+      templ, name, caps);
+  if (aggpad == NULL)
+    return NULL;
+
+  pad = GST_FLV_MUX_PAD (aggpad);
 
-  cpad->audio_codec_data = NULL;
-  cpad->video_codec_data = NULL;
-  gst_flv_mux_reset_pad (mux, cpad, video);
+  gst_flv_mux_reset_pad (pad);
 
-  gst_pad_set_active (pad, TRUE);
-  gst_element_add_pad (element, pad);
+  if (video)
+    mux->video_pad = pad;
+  else
+    mux->audio_pad = pad;
 
-  return pad;
+  return aggpad;
 }
 
 static void
 gst_flv_mux_release_pad (GstElement * element, GstPad * pad)
 {
-  GstFlvMux *mux = GST_FLV_MUX (GST_PAD_PARENT (pad));
-  GstFlvPad *cpad = (GstFlvPad *) gst_pad_get_element_private (pad);
+  GstFlvMux *mux = GST_FLV_MUX (element);
+  GstFlvMuxPad *flvpad = GST_FLV_MUX_PAD (gst_object_ref (pad));
 
-  gst_flv_mux_reset_pad (mux, cpad, cpad->video);
-  gst_collect_pads2_remove_pad (mux->collect, pad);
-  gst_element_remove_pad (element, pad);
+  GST_ELEMENT_CLASS (gst_flv_mux_parent_class)->release_pad (element, pad);
+
+  gst_flv_mux_reset_pad (flvpad);
+
+  if (flvpad == mux->video_pad) {
+    mux->video_pad = NULL;
+  } else if (flvpad == mux->audio_pad) {
+    mux->audio_pad = NULL;
+  } else {
+    GST_WARNING_OBJECT (pad, "Pad is not known audio or video pad");
+  }
+
+  gst_object_unref (flvpad);
 }
 
 static GstFlowReturn
 gst_flv_mux_push (GstFlvMux * mux, GstBuffer * buffer)
 {
+  GstAggregator *agg = GST_AGGREGATOR (mux);
+  GstAggregatorPad *srcpad = GST_AGGREGATOR_PAD (agg->srcpad);
+
+  if (GST_BUFFER_PTS_IS_VALID (buffer))
+    srcpad->segment.position = GST_BUFFER_PTS (buffer);
+
   /* pushing the buffer that rewrites the header will make it no longer be the
    * total output size in bytes, but it doesn't matter at that point */
   mux->byte_count += gst_buffer_get_size (buffer);
 
-  return gst_pad_push (mux->srcpad, buffer);
+  return gst_aggregator_finish_buffer (GST_AGGREGATOR_CAST (mux), buffer);
 }
 
 static GstBuffer *
@@ -612,6 +819,8 @@ gst_flv_mux_create_header (GstFlvMux * mux)
 {
   GstBuffer *header;
   guint8 *data;
+  gboolean have_audio;
+  gboolean have_video;
 
   _gst_buffer_new_and_alloc (9 + 4, &header, &data);
 
@@ -620,7 +829,10 @@ gst_flv_mux_create_header (GstFlvMux * mux)
   data[2] = 'V';
   data[3] = 0x01;               /* Version */
 
-  data[4] = (mux->have_audio << 2) | mux->have_video;   /* flags */
+  have_audio = (mux->audio_pad && mux->audio_pad->codec != G_MAXUINT);
+  have_video = (mux->video_pad && mux->video_pad->codec != G_MAXUINT);
+
+  data[4] = (have_audio << 2) | have_video;     /* flags */
   GST_WRITE_UINT32_BE (data + 5, 9);    /* data offset */
   GST_WRITE_UINT32_BE (data + 9, 0);    /* previous tag size */
 
@@ -676,17 +888,33 @@ gst_flv_mux_create_number_script_value (const gchar * name, gdouble value)
 }
 
 static GstBuffer *
-gst_flv_mux_create_metadata (GstFlvMux * mux, gboolean full)
+gst_flv_mux_create_metadata (GstFlvMux * mux)
 {
   const GstTagList *tags;
   GstBuffer *script_tag, *tmp;
   GstMapInfo map;
+  guint64 dts;
   guint8 *data;
   gint i, n_tags, tags_written = 0;
 
   tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (mux));
 
-  GST_DEBUG_OBJECT (mux, "tags = %" GST_PTR_FORMAT, tags);
+  dts = mux->last_dts;
+
+  /* Timestamp must start at zero */
+  if (GST_CLOCK_TIME_IS_VALID (mux->first_timestamp)) {
+    dts -= mux->first_timestamp / GST_MSECOND;
+  }
+
+  GST_DEBUG_OBJECT (mux,
+      "Creating metadata, dts %" G_GUINT64_FORMAT ", tags = %" GST_PTR_FORMAT,
+      dts, tags);
+
+  if (dts > G_MAXUINT32) {
+    GST_LOG_OBJECT (mux,
+        "Detected rollover, timestamp will be truncated (previous:%"
+        G_GUINT64_FORMAT ", new:%u)", dts, (guint32) dts);
+  }
 
   /* FIXME perhaps some bytewriter'ing here ... */
 
@@ -700,7 +928,8 @@ gst_flv_mux_create_metadata (GstFlvMux * mux, gboolean full)
   data[3] = 0;
 
   /* Timestamp */
-  data[4] = data[5] = data[6] = data[7] = 0;
+  GST_WRITE_UINT24_BE (data + 4, dts);
+  data[7] = (((guint) dts) >> 24) & 0xff;
 
   /* Stream ID */
   data[8] = data[9] = data[10] = 0;
@@ -711,43 +940,38 @@ gst_flv_mux_create_metadata (GstFlvMux * mux, gboolean full)
   data[2] = 10;                 /* length 10 */
   memcpy (&data[3], "onMetaData", 10);
 
-  script_tag = gst_buffer_join (script_tag, tmp);
+  script_tag = gst_buffer_append (script_tag, tmp);
 
-  n_tags = (tags) ? gst_structure_n_fields ((GstStructure *) tags) : 0;
+  n_tags = (tags) ? gst_tag_list_n_tags (tags) : 0;
   _gst_buffer_new_and_alloc (5, &tmp, &data);
   data[0] = 8;                  /* ECMA array */
   GST_WRITE_UINT32_BE (data + 1, n_tags);
-  script_tag = gst_buffer_join (script_tag, tmp);
-
-  if (!full)
-    goto tags;
+  script_tag = gst_buffer_append (script_tag, tmp);
 
   /* Some players expect the 'duration' to be always set. Fill it out later,
      after querying the pads or after getting EOS */
   if (!mux->streamable) {
     tmp = gst_flv_mux_create_number_script_value ("duration", 86400);
-    script_tag = gst_buffer_join (script_tag, tmp);
+    script_tag = gst_buffer_append (script_tag, tmp);
     tags_written++;
 
     /* Sometimes the information about the total file size is useful for the
        player. It will be filled later, after getting EOS */
     tmp = gst_flv_mux_create_number_script_value ("filesize", 0);
-    script_tag = gst_buffer_join (script_tag, tmp);
+    script_tag = gst_buffer_append (script_tag, tmp);
     tags_written++;
 
     /* Preallocate space for the index to be written at EOS */
     tmp = gst_flv_mux_preallocate_index (mux);
-    script_tag = gst_buffer_join (script_tag, tmp);
+    script_tag = gst_buffer_append (script_tag, tmp);
   } else {
     GST_DEBUG_OBJECT (mux, "not preallocating index, streamable mode");
   }
 
-tags:
   for (i = 0; tags && i < n_tags; i++) {
-    const gchar *tag_name =
-        gst_structure_nth_field_name ((const GstStructure *) tags, i);
+    const gchar *tag_name = gst_tag_list_nth_tag_name (tags, i);
     if (!strcmp (tag_name, GST_TAG_DURATION)) {
-      guint64 dur;
+      GstClockTime dur;
 
       if (!gst_tag_list_get_uint64 (tags, GST_TAG_DURATION, &dur))
         continue;
@@ -774,31 +998,15 @@ tags:
       data[3 + strlen (t)] = (strlen (s) >> 8) & 0xff;
       data[4 + strlen (t)] = (strlen (s)) & 0xff;
       memcpy (&data[5 + strlen (t)], s, strlen (s));
-      script_tag = gst_buffer_join (script_tag, tmp);
+      script_tag = gst_buffer_append (script_tag, tmp);
 
       g_free (s);
       tags_written++;
     }
   }
 
-  if (!full)
-    goto end;
-
-  if (mux->duration == GST_CLOCK_TIME_NONE) {
-    GSList *l;
-    guint64 dur;
-
-    for (l = mux->collect->data; l; l = l->next) {
-      GstCollectData2 *cdata = l->data;
-
-      if (gst_pad_peer_query_duration (cdata->pad, GST_FORMAT_TIME,
-              (gint64 *) & dur) && dur != GST_CLOCK_TIME_NONE) {
-        if (mux->duration == GST_CLOCK_TIME_NONE)
-          mux->duration = dur;
-        else
-          mux->duration = MAX (dur, mux->duration);
-      }
-    }
+  if (!mux->streamable && mux->duration == GST_CLOCK_TIME_NONE) {
+    mux->duration = gst_flv_mux_query_upstream_duration (mux);
   }
 
   if (!mux->streamable && mux->duration != GST_CLOCK_TIME_NONE) {
@@ -814,34 +1022,25 @@ tags:
     gst_buffer_unmap (script_tag, &map);
   }
 
-  if (mux->have_video) {
-    GstPad *video_pad = NULL;
-    GstFlvPad *cpad;
-    GSList *l = mux->collect->data;
+  if (mux->video_pad && mux->video_pad->codec != G_MAXUINT) {
+    GstCaps *caps = NULL;
 
-    for (; l; l = l->next) {
-      cpad = l->data;
-      if (cpad && cpad->video) {
-        video_pad = cpad->collect.pad;
-        break;
-      }
-    }
+    if (mux->video_pad)
+      caps = gst_pad_get_current_caps (GST_PAD (mux->video_pad));
 
-    if (video_pad && gst_pad_has_current_caps (video_pad)) {
-      GstCaps *caps;
+    if (caps != NULL) {
       GstStructure *s;
       gint size;
       gint num, den;
 
       GST_DEBUG_OBJECT (mux, "putting videocodecid %d in the metadata",
-          cpad->video_codec);
+          mux->video_pad->codec);
 
       tmp = gst_flv_mux_create_number_script_value ("videocodecid",
-          cpad->video_codec);
-      script_tag = gst_buffer_join (script_tag, tmp);
+          mux->video_pad->codec);
+      script_tag = gst_buffer_append (script_tag, tmp);
       tags_written++;
 
-      caps = gst_pad_get_current_caps (video_pad);
       s = gst_caps_get_structure (caps, 0);
       gst_caps_unref (caps);
 
@@ -849,7 +1048,7 @@ tags:
         GST_DEBUG_OBJECT (mux, "putting width %d in the metadata", size);
 
         tmp = gst_flv_mux_create_number_script_value ("width", size);
-        script_tag = gst_buffer_join (script_tag, tmp);
+        script_tag = gst_buffer_append (script_tag, tmp);
         tags_written++;
       }
 
@@ -857,7 +1056,7 @@ tags:
         GST_DEBUG_OBJECT (mux, "putting height %d in the metadata", size);
 
         tmp = gst_flv_mux_create_number_script_value ("height", size);
-        script_tag = gst_buffer_join (script_tag, tmp);
+        script_tag = gst_buffer_append (script_tag, tmp);
         tags_written++;
       }
 
@@ -868,14 +1067,14 @@ tags:
         GST_DEBUG_OBJECT (mux, "putting AspectRatioX %f in the metadata", d);
 
         tmp = gst_flv_mux_create_number_script_value ("AspectRatioX", d);
-        script_tag = gst_buffer_join (script_tag, tmp);
+        script_tag = gst_buffer_append (script_tag, tmp);
         tags_written++;
 
         d = den;
         GST_DEBUG_OBJECT (mux, "putting AspectRatioY %f in the metadata", d);
 
         tmp = gst_flv_mux_create_number_script_value ("AspectRatioY", d);
-        script_tag = gst_buffer_join (script_tag, tmp);
+        script_tag = gst_buffer_append (script_tag, tmp);
         tags_written++;
       }
 
@@ -886,56 +1085,63 @@ tags:
         GST_DEBUG_OBJECT (mux, "putting framerate %f in the metadata", d);
 
         tmp = gst_flv_mux_create_number_script_value ("framerate", d);
-        script_tag = gst_buffer_join (script_tag, tmp);
+        script_tag = gst_buffer_append (script_tag, tmp);
         tags_written++;
       }
-    }
-  }
-
-  if (mux->have_audio) {
-    GstPad *audio_pad = NULL;
-    GstFlvPad *cpad;
-    GSList *l = mux->collect->data;
-
-    for (; l; l = l->next) {
-      cpad = l->data;
-      if (cpad && !cpad->video) {
-        audio_pad = cpad->collect.pad;
-        break;
-      }
-    }
 
-    if (audio_pad) {
-      GST_DEBUG_OBJECT (mux, "putting audiocodecid %d in the metadata",
-          cpad->audio_codec);
-
-      tmp = gst_flv_mux_create_number_script_value ("audiocodecid",
-          cpad->audio_codec);
-      script_tag = gst_buffer_join (script_tag, tmp);
+      GST_DEBUG_OBJECT (mux, "putting videodatarate %u KB/s in the metadata",
+          mux->video_pad->bitrate / 1024);
+      tmp = gst_flv_mux_create_number_script_value ("videodatarate",
+          mux->video_pad->bitrate / 1024);
+      script_tag = gst_buffer_append (script_tag, tmp);
       tags_written++;
     }
   }
 
-  {
-    const gchar *s = "GStreamer FLV muxer";
-
-    _gst_buffer_new_and_alloc (2 + 15 + 1 + 2 + strlen (s), &tmp, &data);
-    data[0] = 0;                /* 15 bytes name */
-    data[1] = 15;
-    memcpy (&data[2], "metadatacreator", 15);
-    data[17] = 2;               /* string */
-    data[18] = (strlen (s) >> 8) & 0xff;
-    data[19] = (strlen (s)) & 0xff;
-    memcpy (&data[20], s, strlen (s));
-    script_tag = gst_buffer_join (script_tag, tmp);
+  if (mux->audio_pad && mux->audio_pad->codec != G_MAXUINT) {
+    GST_DEBUG_OBJECT (mux, "putting audiocodecid %d in the metadata",
+        mux->audio_pad->codec);
+
+    tmp = gst_flv_mux_create_number_script_value ("audiocodecid",
+        mux->audio_pad->codec);
+    script_tag = gst_buffer_append (script_tag, tmp);
+    tags_written++;
 
+    GST_DEBUG_OBJECT (mux, "putting audiodatarate %u KB/s in the metadata",
+        mux->audio_pad->bitrate / 1024);
+    tmp = gst_flv_mux_create_number_script_value ("audiodatarate",
+        mux->audio_pad->bitrate / 1024);
+    script_tag = gst_buffer_append (script_tag, tmp);
     tags_written++;
   }
 
+  _gst_buffer_new_and_alloc (2 + 15 + 1 + 2 + strlen (mux->metadatacreator),
+      &tmp, &data);
+  data[0] = 0;                  /* 15 bytes name */
+  data[1] = 15;
+  memcpy (&data[2], "metadatacreator", 15);
+  data[17] = 2;                 /* string */
+  data[18] = (strlen (mux->metadatacreator) >> 8) & 0xff;
+  data[19] = (strlen (mux->metadatacreator)) & 0xff;
+  memcpy (&data[20], mux->metadatacreator, strlen (mux->metadatacreator));
+  script_tag = gst_buffer_append (script_tag, tmp);
+  tags_written++;
+
+  _gst_buffer_new_and_alloc (2 + 7 + 1 + 2 + strlen (mux->encoder),
+      &tmp, &data);
+  data[0] = 0;                  /* 7 bytes name */
+  data[1] = 7;
+  memcpy (&data[2], "encoder", 7);
+  data[9] = 2;                  /* string */
+  data[10] = (strlen (mux->encoder) >> 8) & 0xff;
+  data[11] = (strlen (mux->encoder)) & 0xff;
+  memcpy (&data[12], mux->encoder, strlen (mux->encoder));
+  script_tag = gst_buffer_append (script_tag, tmp);
+  tags_written++;
+
   {
-    GTimeVal tv = { 0, };
     time_t secs;
-    struct tm *tm;
+    struct tm tm;
     gchar *s;
     static const gchar *weekdays[] = {
       "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
@@ -945,13 +1151,16 @@ tags:
       "Aug", "Sep", "Oct", "Nov", "Dec"
     };
 
-    g_get_current_time (&tv);
-    secs = tv.tv_sec;
-    tm = gmtime (&secs);
+    secs = g_get_real_time () / G_USEC_PER_SEC;
+#ifdef HAVE_GMTIME_R
+    gmtime_r (&secs, &tm);
+#else
+    tm = *gmtime (&secs);
+#endif
 
-    s = g_strdup_printf ("%s %s %d %d:%d:%d %d", weekdays[tm->tm_wday],
-        months[tm->tm_mon], tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec,
-        tm->tm_year + 1900);
+    s = g_strdup_printf ("%s %s %d %02d:%02d:%02d %d", weekdays[tm.tm_wday],
+        months[tm.tm_mon], tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
+        tm.tm_year + 1900);
 
     _gst_buffer_new_and_alloc (2 + 12 + 1 + 2 + strlen (s), &tmp, &data);
     data[0] = 0;                /* 12 bytes name */
@@ -961,14 +1170,12 @@ tags:
     data[15] = (strlen (s) >> 8) & 0xff;
     data[16] = (strlen (s)) & 0xff;
     memcpy (&data[17], s, strlen (s));
-    script_tag = gst_buffer_join (script_tag, tmp);
+    script_tag = gst_buffer_append (script_tag, tmp);
 
     g_free (s);
     tags_written++;
   }
 
-end:
-
   if (!tags_written) {
     gst_buffer_unref (script_tag);
     script_tag = NULL;
@@ -979,12 +1186,11 @@ end:
   data[0] = 0;                  /* 0 byte size */
   data[1] = 0;
   data[2] = 9;                  /* end marker */
-  script_tag = gst_buffer_join (script_tag, tmp);
-  tags_written++;
+  script_tag = gst_buffer_append (script_tag, tmp);
 
   _gst_buffer_new_and_alloc (4, &tmp, &data);
   GST_WRITE_UINT32_BE (data, gst_buffer_get_size (script_tag));
-  script_tag = gst_buffer_join (script_tag, tmp);
+  script_tag = gst_buffer_append (script_tag, tmp);
 
   gst_buffer_map (script_tag, &map, GST_MAP_WRITE);
   map.data[1] = ((gst_buffer_get_size (script_tag) - 11 - 4) >> 16) & 0xff;
@@ -1000,31 +1206,89 @@ exit:
 
 static GstBuffer *
 gst_flv_mux_buffer_to_tag_internal (GstFlvMux * mux, GstBuffer * buffer,
-    GstFlvPad * cpad, gboolean is_codec_data)
+    GstFlvMuxPad * pad, gboolean is_codec_data)
 {
   GstBuffer *tag;
   GstMapInfo map;
   guint size;
-  guint32 timestamp =
-      (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) ? GST_BUFFER_TIMESTAMP (buffer) /
-      GST_MSECOND : cpad->last_timestamp / GST_MSECOND;
-  guint8 *data, *bdata;
-  gsize bsize;
+  guint64 pts, dts, cts;
+  guint8 *data, *bdata = NULL;
+  gsize bsize = 0;
+
+  if (GST_CLOCK_TIME_IS_VALID (pad->dts)) {
+    pts = pad->pts / GST_MSECOND;
+    dts = pad->dts / GST_MSECOND;
+    GST_LOG_OBJECT (mux,
+        "Pad %s: Created dts %" GST_TIME_FORMAT ", pts %" GST_TIME_FORMAT
+        " from rounding %" GST_TIME_FORMAT ", %" GST_TIME_FORMAT,
+        GST_PAD_NAME (pad), GST_TIME_ARGS (dts * GST_MSECOND),
+        GST_TIME_ARGS (pts * GST_MSECOND), GST_TIME_ARGS (pad->dts),
+        GST_TIME_ARGS (pad->pts));
+  } else if (GST_CLOCK_TIME_IS_VALID (pad->last_timestamp)) {
+    pts = dts = pad->last_timestamp / GST_MSECOND;
+    GST_DEBUG_OBJECT (mux,
+        "Pad %s: Created dts and pts %" GST_TIME_FORMAT
+        " from rounding last pad timestamp %" GST_TIME_FORMAT,
+        GST_PAD_NAME (pad), GST_TIME_ARGS (pts * GST_MSECOND),
+        GST_TIME_ARGS (pad->last_timestamp));
+  } else {
+    pts = dts = mux->last_dts;
+    GST_DEBUG_OBJECT (mux,
+        "Pad %s: Created dts and pts %" GST_TIME_FORMAT
+        " from last mux timestamp",
+        GST_PAD_NAME (pad), GST_TIME_ARGS (pts * GST_MSECOND));
+  }
+
+  /* We prevent backwards timestamps because they confuse librtmp,
+   * it expects timestamps to go forward not only inside one stream, but
+   * also between the audio & video streams.
+   */
+  if (dts < mux->last_dts) {
+    GST_WARNING_OBJECT (pad, "Got backwards dts! (%" GST_TIME_FORMAT
+        " < %" GST_TIME_FORMAT ")", GST_TIME_ARGS (dts * GST_MSECOND),
+        GST_TIME_ARGS (mux->last_dts * GST_MSECOND));
+    dts = mux->last_dts;
+  }
+  mux->last_dts = dts;
+
+  /* Be safe in case TS are buggy */
+  if (pts > dts)
+    cts = pts - dts;
+  else
+    cts = 0;
+
+  /* Timestamp must start at zero */
+  if (GST_CLOCK_TIME_IS_VALID (mux->first_timestamp)) {
+    dts -= mux->first_timestamp / GST_MSECOND;
+    pts = dts + cts;
+  }
 
-  gst_buffer_map (buffer, &map, GST_MAP_READ);
-  bdata = map.data;
-  bsize = map.size;
+  GST_LOG_OBJECT (mux,
+      "got pts %" G_GUINT64_FORMAT " dts %" G_GUINT64_FORMAT " cts %"
+      G_GUINT64_FORMAT, pts, dts, cts);
+
+  if (dts > G_MAXUINT32) {
+    GST_LOG_OBJECT (mux,
+        "Detected rollover, timestamp will be truncated (previous:%"
+        G_GUINT64_FORMAT ", new:%u)", dts, (guint32) dts);
+  }
+
+  if (buffer != NULL) {
+    gst_buffer_map (buffer, &map, GST_MAP_READ);
+    bdata = map.data;
+    bsize = map.size;
+  }
 
   size = 11;
-  if (cpad->video) {
+  if (mux->video_pad == pad) {
     size += 1;
-    if (cpad->video_codec == 7)
+    if (pad->codec == 7)
       size += 4 + bsize;
     else
       size += bsize;
   } else {
     size += 1;
-    if (cpad->audio_codec == 10)
+    if (pad->codec == 10)
       size += 1 + bsize;
     else
       size += bsize;
@@ -1032,49 +1296,55 @@ gst_flv_mux_buffer_to_tag_internal (GstFlvMux * mux, GstBuffer * buffer,
   size += 4;
 
   _gst_buffer_new_and_alloc (size, &tag, &data);
-  GST_BUFFER_TIMESTAMP (tag) = timestamp * GST_MSECOND;
   memset (data, 0, size);
 
-  data[0] = (cpad->video) ? 9 : 8;
+  data[0] = (mux->video_pad == pad) ? 9 : 8;
 
   data[1] = ((size - 11 - 4) >> 16) & 0xff;
   data[2] = ((size - 11 - 4) >> 8) & 0xff;
   data[3] = ((size - 11 - 4) >> 0) & 0xff;
 
-  /* wrap the timestamp every G_MAXINT32 miliseconds */
-  timestamp &= 0x7fffffff;
-  data[4] = (timestamp >> 16) & 0xff;
-  data[5] = (timestamp >> 8) & 0xff;
-  data[6] = (timestamp >> 0) & 0xff;
-  data[7] = (timestamp >> 24) & 0xff;
+  GST_WRITE_UINT24_BE (data + 4, dts);
+  data[7] = (((guint) dts) >> 24) & 0xff;
 
   data[8] = data[9] = data[10] = 0;
 
-  if (cpad->video) {
-    if (GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT))
+  if (mux->video_pad == pad) {
+    if (buffer && GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT))
       data[11] |= 2 << 4;
     else
       data[11] |= 1 << 4;
 
-    data[11] |= cpad->video_codec & 0x0f;
-
-    if (cpad->video_codec == 7) {
-      data[12] = is_codec_data ? 0 : 1;
-
-      /* FIXME: what to do about composition time */
-      data[13] = data[14] = data[15] = 0;
+    data[11] |= pad->codec & 0x0f;
 
+    if (pad->codec == 7) {
+      if (is_codec_data) {
+        data[12] = 0;
+        GST_WRITE_UINT24_BE (data + 13, 0);
+      } else if (bsize == 0) {
+        /* AVC end of sequence */
+        data[12] = 2;
+        GST_WRITE_UINT24_BE (data + 13, 0);
+      } else {
+        /* ACV NALU */
+        data[12] = 1;
+        GST_WRITE_UINT24_BE (data + 13, cts);
+      }
       memcpy (data + 11 + 1 + 4, bdata, bsize);
     } else {
       memcpy (data + 11 + 1, bdata, bsize);
     }
   } else {
-    data[11] |= (cpad->audio_codec << 4) & 0xf0;
-    data[11] |= (cpad->rate << 2) & 0x0c;
-    data[11] |= (cpad->width << 1) & 0x02;
-    data[11] |= (cpad->channels << 0) & 0x01;
+    data[11] |= (pad->codec << 4) & 0xf0;
+    data[11] |= (pad->rate << 2) & 0x0c;
+    data[11] |= (pad->width << 1) & 0x02;
+    data[11] |= (pad->channels << 0) & 0x01;
 
-    if (cpad->audio_codec == 10) {
+    GST_LOG_OBJECT (mux, "Creating byte %02x with "
+        "codec:%d, rate:%d, width:%d, channels:%d",
+        data[11], pad->codec, pad->rate, pad->width, pad->channels);
+
+    if (pad->codec == 10) {
       data[12] = is_codec_data ? 0 : 1;
 
       memcpy (data + 11 + 1 + 1, bdata, bsize);
@@ -1083,39 +1353,66 @@ gst_flv_mux_buffer_to_tag_internal (GstFlvMux * mux, GstBuffer * buffer,
     }
   }
 
-  gst_buffer_unmap (buffer, &map);
+  if (buffer)
+    gst_buffer_unmap (buffer, &map);
 
   GST_WRITE_UINT32_BE (data + size - 4, size - 4);
 
-  GST_BUFFER_TIMESTAMP (tag) = GST_BUFFER_TIMESTAMP (buffer);
-  GST_BUFFER_DURATION (tag) = GST_BUFFER_DURATION (buffer);
-  GST_BUFFER_OFFSET (tag) = GST_BUFFER_OFFSET (buffer);
-  GST_BUFFER_OFFSET_END (tag) = GST_BUFFER_OFFSET_END (buffer);
+  GST_BUFFER_PTS (tag) = GST_CLOCK_TIME_NONE;
+  GST_BUFFER_DTS (tag) = GST_CLOCK_TIME_NONE;
+  GST_BUFFER_DURATION (tag) = GST_CLOCK_TIME_NONE;
+
+  if (buffer) {
+    /* if we are streamable we copy over timestamps and offsets,
+       if not just copy the offsets */
+    if (mux->streamable) {
+      GstClockTime timestamp = GST_CLOCK_TIME_NONE;
+
+      if (gst_segment_to_running_time_full (&GST_AGGREGATOR_PAD (pad)->segment,
+              GST_FORMAT_TIME, GST_BUFFER_DTS_OR_PTS (buffer),
+              &timestamp) == 1) {
+        GST_BUFFER_PTS (tag) = timestamp;
+        GST_BUFFER_DURATION (tag) = GST_BUFFER_DURATION (buffer);
+      }
+      GST_BUFFER_OFFSET (tag) = GST_BUFFER_OFFSET_NONE;
+      GST_BUFFER_OFFSET_END (tag) = GST_BUFFER_OFFSET_NONE;
+    } else {
+      GST_BUFFER_OFFSET (tag) = GST_BUFFER_OFFSET (buffer);
+      GST_BUFFER_OFFSET_END (tag) = GST_BUFFER_OFFSET_END (buffer);
+    }
 
-  /* mark the buffer if it's an audio buffer and there's also video being muxed
-   * or it's a video interframe */
-  if ((mux->have_video && !cpad->video) ||
-      GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT))
+    /* mark the buffer if it's an audio buffer and there's also video being muxed
+     * or it's a video interframe */
+    if (mux->video_pad == pad &&
+        GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT))
+      GST_BUFFER_FLAG_SET (tag, GST_BUFFER_FLAG_DELTA_UNIT);
+  } else {
     GST_BUFFER_FLAG_SET (tag, GST_BUFFER_FLAG_DELTA_UNIT);
-
-  GST_BUFFER_OFFSET (tag) = GST_BUFFER_OFFSET_END (tag) =
-      GST_BUFFER_OFFSET_NONE;
+    GST_BUFFER_OFFSET (tag) = GST_BUFFER_OFFSET_END (tag) =
+        GST_BUFFER_OFFSET_NONE;
+  }
 
   return tag;
 }
 
 static inline GstBuffer *
 gst_flv_mux_buffer_to_tag (GstFlvMux * mux, GstBuffer * buffer,
-    GstFlvPad * cpad)
+    GstFlvMuxPad * pad)
 {
-  return gst_flv_mux_buffer_to_tag_internal (mux, buffer, cpad, FALSE);
+  return gst_flv_mux_buffer_to_tag_internal (mux, buffer, pad, FALSE);
 }
 
 static inline GstBuffer *
 gst_flv_mux_codec_data_buffer_to_tag (GstFlvMux * mux, GstBuffer * buffer,
-    GstFlvPad * cpad)
+    GstFlvMuxPad * pad)
 {
-  return gst_flv_mux_buffer_to_tag_internal (mux, buffer, cpad, TRUE);
+  return gst_flv_mux_buffer_to_tag_internal (mux, buffer, pad, TRUE);
+}
+
+static inline GstBuffer *
+gst_flv_mux_eos_to_tag (GstFlvMux * mux, GstFlvMuxPad * pad)
+{
+  return gst_flv_mux_buffer_to_tag_internal (mux, NULL, pad, FALSE);
 }
 
 static void
@@ -1133,67 +1430,45 @@ gst_flv_mux_put_buffer_in_streamheader (GValue * streamheader,
   g_value_unset (&value);
 }
 
-static GstFlowReturn
-gst_flv_mux_write_header (GstFlvMux * mux)
+static GstCaps *
+gst_flv_mux_prepare_src_caps (GstFlvMux * mux, GstBuffer ** header_buf,
+    GstBuffer ** metadata_buf, GstBuffer ** video_codec_data_buf,
+    GstBuffer ** audio_codec_data_buf)
 {
   GstBuffer *header, *metadata;
   GstBuffer *video_codec_data, *audio_codec_data;
   GstCaps *caps;
   GstStructure *structure;
   GValue streamheader = { 0 };
-  GSList *l;
-  GstFlowReturn ret;
-
-  /* if not streaming, check if downstream is seekable */
-  if (!mux->streamable) {
-    gboolean seekable;
-    GstQuery *query;
-
-    query = gst_query_new_seeking (GST_FORMAT_BYTES);
-    if (gst_pad_peer_query (mux->srcpad, query)) {
-      gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL);
-      GST_INFO_OBJECT (mux, "downstream is %sseekable", seekable ? "" : "not ");
-    } else {
-      /* have to assume seeking is supported if query not handled downstream */
-      GST_WARNING_OBJECT (mux, "downstream did not handle seeking query");
-      seekable = FALSE;
-    }
-    if (!seekable) {
-      mux->streamable = TRUE;
-      g_object_notify (G_OBJECT (mux), "streamable");
-      GST_WARNING_OBJECT (mux, "downstream is not seekable, but "
-          "streamable=false. Will ignore that and create streamable output "
-          "instead");
-    }
-  }
+  GList *l;
 
   header = gst_flv_mux_create_header (mux);
-  metadata = gst_flv_mux_create_metadata (mux, TRUE);
+  metadata = gst_flv_mux_create_metadata (mux);
   video_codec_data = NULL;
   audio_codec_data = NULL;
 
-  for (l = mux->collect->data; l != NULL; l = l->next) {
-    GstFlvPad *cpad = l->data;
+  GST_OBJECT_LOCK (mux);
+  for (l = GST_ELEMENT_CAST (mux)->sinkpads; l != NULL; l = l->next) {
+    GstFlvMuxPad *pad = l->data;
 
     /* Get H.264 and AAC codec data, if present */
-    if (cpad && cpad->video && cpad->video_codec == 7) {
-      if (cpad->video_codec_data == NULL)
+    if (pad && mux->video_pad == pad && pad->codec == 7) {
+      if (pad->codec_data == NULL)
         GST_WARNING_OBJECT (mux, "Codec data for video stream not found, "
             "output might not be playable");
       else
         video_codec_data =
-            gst_flv_mux_codec_data_buffer_to_tag (mux, cpad->video_codec_data,
-            cpad);
-    } else if (cpad && !cpad->video && cpad->audio_codec == 10) {
-      if (cpad->audio_codec_data == NULL)
+            gst_flv_mux_codec_data_buffer_to_tag (mux, pad->codec_data, pad);
+    } else if (pad && mux->audio_pad == pad && pad->codec == 10) {
+      if (pad->codec_data == NULL)
         GST_WARNING_OBJECT (mux, "Codec data for audio stream not found, "
             "output might not be playable");
       else
         audio_codec_data =
-            gst_flv_mux_codec_data_buffer_to_tag (mux, cpad->audio_codec_data,
-            cpad);
+            gst_flv_mux_codec_data_buffer_to_tag (mux, pad->codec_data, pad);
     }
   }
+  GST_OBJECT_UNLOCK (mux);
 
   /* mark buffers that will go in the streamheader */
   GST_BUFFER_FLAG_SET (header, GST_BUFFER_FLAG_HEADER);
@@ -1223,72 +1498,185 @@ gst_flv_mux_write_header (GstFlvMux * mux)
   gst_structure_set_value (structure, "streamheader", &streamheader);
   g_value_unset (&streamheader);
 
-  if (!gst_pad_has_current_caps (mux->srcpad))
-    gst_pad_set_caps (mux->srcpad, caps);
+  if (header_buf) {
+    *header_buf = header;
+  } else {
+    gst_buffer_unref (header);
+  }
+
+  if (metadata_buf) {
+    *metadata_buf = metadata;
+  } else {
+    gst_buffer_unref (metadata);
+  }
+
+  if (video_codec_data_buf) {
+    *video_codec_data_buf = video_codec_data;
+  } else if (video_codec_data) {
+    gst_buffer_unref (video_codec_data);
+  }
+
+  if (audio_codec_data_buf) {
+    *audio_codec_data_buf = audio_codec_data;
+  } else if (audio_codec_data) {
+    gst_buffer_unref (audio_codec_data);
+  }
+
+  return caps;
+}
+
+static GstFlowReturn
+gst_flv_mux_write_header (GstFlvMux * mux)
+{
+  GstBuffer *header, *metadata;
+  GstBuffer *video_codec_data, *audio_codec_data;
+  GstCaps *caps;
+  GstFlowReturn ret;
+
+  header = metadata = video_codec_data = audio_codec_data = NULL;
+
+  /* if not streaming, check if downstream is seekable */
+  if (!mux->streamable) {
+    gboolean seekable;
+    GstQuery *query;
+
+    query = gst_query_new_seeking (GST_FORMAT_BYTES);
+    if (gst_pad_peer_query (mux->srcpad, query)) {
+      gst_query_parse_seeking (query, NULL, &seekable, NULL, NULL);
+      GST_INFO_OBJECT (mux, "downstream is %sseekable", seekable ? "" : "not ");
+    } else {
+      /* have to assume seeking is supported if query not handled downstream */
+      GST_WARNING_OBJECT (mux, "downstream did not handle seeking query");
+      seekable = FALSE;
+    }
+    if (!seekable) {
+      mux->streamable = TRUE;
+      g_object_notify (G_OBJECT (mux), "streamable");
+      GST_WARNING_OBJECT (mux, "downstream is not seekable, but "
+          "streamable=false. Will ignore that and create streamable output "
+          "instead");
+    }
+    gst_query_unref (query);
+  }
+
+  if (mux->streamable && mux->sent_header) {
+    GstBuffer **video_codec_data_p = NULL, **audio_codec_data_p = NULL;
+
+    if (mux->video_pad && mux->video_pad->info_changed)
+      video_codec_data_p = &video_codec_data;
+    if (mux->audio_pad && mux->audio_pad->info_changed)
+      audio_codec_data_p = &audio_codec_data;
+
+    caps = gst_flv_mux_prepare_src_caps (mux,
+        NULL, NULL, video_codec_data_p, audio_codec_data_p);
+  } else {
+    caps = gst_flv_mux_prepare_src_caps (mux,
+        &header, &metadata, &video_codec_data, &audio_codec_data);
+  }
+
+  gst_aggregator_set_src_caps (GST_AGGREGATOR_CAST (mux), caps);
 
   gst_caps_unref (caps);
 
   /* push the header buffer, the metadata and the codec info, if any */
-  ret = gst_flv_mux_push (mux, header);
-  if (ret != GST_FLOW_OK)
-    return ret;
-  ret = gst_flv_mux_push (mux, metadata);
-  if (ret != GST_FLOW_OK)
-    return ret;
+  if (header != NULL) {
+    ret = gst_flv_mux_push (mux, header);
+    if (ret != GST_FLOW_OK)
+      goto failure_header;
+    mux->sent_header = TRUE;
+  }
+  if (metadata != NULL) {
+    ret = gst_flv_mux_push (mux, metadata);
+    if (ret != GST_FLOW_OK)
+      goto failure_metadata;
+    mux->new_tags = FALSE;
+  }
   if (video_codec_data != NULL) {
     ret = gst_flv_mux_push (mux, video_codec_data);
     if (ret != GST_FLOW_OK)
-      return ret;
+      goto failure_video_codec_data;
+    mux->video_pad->info_changed = FALSE;
   }
   if (audio_codec_data != NULL) {
     ret = gst_flv_mux_push (mux, audio_codec_data);
     if (ret != GST_FLOW_OK)
-      return ret;
+      goto failure_audio_codec_data;
+    mux->audio_pad->info_changed = FALSE;
   }
   return GST_FLOW_OK;
+
+failure_header:
+  gst_buffer_unref (metadata);
+
+failure_metadata:
+  if (video_codec_data != NULL)
+    gst_buffer_unref (video_codec_data);
+
+failure_video_codec_data:
+  if (audio_codec_data != NULL)
+    gst_buffer_unref (audio_codec_data);
+
+failure_audio_codec_data:
+  return ret;
+}
+
+static GstClockTime
+gst_flv_mux_segment_to_running_time (const GstSegment * segment, GstClockTime t)
+{
+  /* we can get a dts before the segment, if dts < pts and pts is inside
+   * the segment, so we consider early times as 0 */
+  if (t < segment->start)
+    return 0;
+  return gst_segment_to_running_time (segment, GST_FORMAT_TIME, t);
 }
 
 static void
-gst_flv_mux_update_index (GstFlvMux * mux, GstBuffer * buffer, GstFlvPad * cpad)
+gst_flv_mux_update_index (GstFlvMux * mux, GstBuffer * buffer,
+    GstFlvMuxPad * pad)
 {
   /*
    * Add the tag byte offset and to the index if it's a valid seek point, which
    * means it's either a video keyframe or if there is no video pad (in that
    * case every FLV tag is a valid seek point)
    */
-  if (mux->have_video &&
-      (!cpad->video ||
-          GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT)))
+  if (mux->video_pad == pad &&
+      GST_BUFFER_FLAG_IS_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT))
     return;
 
-  if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) {
+  if (GST_BUFFER_PTS_IS_VALID (buffer)) {
     GstFlvMuxIndexEntry *entry = g_slice_new (GstFlvMuxIndexEntry);
+    GstClockTime pts =
+        gst_flv_mux_segment_to_running_time (&GST_AGGREGATOR_PAD
+        (pad)->segment, GST_BUFFER_PTS (buffer));
     entry->position = mux->byte_count;
-    entry->time =
-        gst_guint64_to_gdouble (GST_BUFFER_TIMESTAMP (buffer)) / GST_SECOND;
+    entry->time = gst_guint64_to_gdouble (pts) / GST_SECOND;
     mux->index = g_list_prepend (mux->index, entry);
   }
 }
 
 static GstFlowReturn
-gst_flv_mux_write_buffer (GstFlvMux * mux, GstFlvPad * cpad, GstBuffer * buffer)
+gst_flv_mux_write_buffer (GstFlvMux * mux, GstFlvMuxPad * pad,
+    GstBuffer * buffer)
 {
   GstBuffer *tag;
   GstFlowReturn ret;
+  GstClockTime dts =
+      gst_flv_mux_segment_to_running_time (&GST_AGGREGATOR_PAD (pad)->segment,
+      GST_BUFFER_DTS (buffer));
 
   /* clipping function arranged for running_time */
 
   if (!mux->streamable)
-    gst_flv_mux_update_index (mux, buffer, cpad);
+    gst_flv_mux_update_index (mux, buffer, pad);
 
-  tag = gst_flv_mux_buffer_to_tag (mux, buffer, cpad);
+  tag = gst_flv_mux_buffer_to_tag (mux, buffer, pad);
 
   gst_buffer_unref (buffer);
 
   ret = gst_flv_mux_push (mux, tag);
 
-  if (ret == GST_FLOW_OK && GST_BUFFER_TIMESTAMP_IS_VALID (tag))
-    cpad->last_timestamp = GST_BUFFER_TIMESTAMP (tag);
+  if (ret == GST_FLOW_OK && GST_CLOCK_TIME_IS_VALID (dts))
+    pad->last_timestamp = dts;
 
   return ret;
 }
@@ -1296,26 +1684,92 @@ gst_flv_mux_write_buffer (GstFlvMux * mux, GstFlvPad * cpad, GstBuffer * buffer)
 static guint64
 gst_flv_mux_determine_duration (GstFlvMux * mux)
 {
-  GSList *l;
+  GList *l;
   GstClockTime duration = GST_CLOCK_TIME_NONE;
 
   GST_DEBUG_OBJECT (mux, "trying to determine the duration "
       "from pad timestamps");
 
-  for (l = mux->collect->data; l != NULL; l = l->next) {
-    GstFlvPad *cpad = l->data;
+  GST_OBJECT_LOCK (mux);
+  for (l = GST_ELEMENT_CAST (mux)->sinkpads; l != NULL; l = l->next) {
+    GstFlvMuxPad *pad = GST_FLV_MUX_PAD (l->data);
 
-    if (cpad && (cpad->last_timestamp != GST_CLOCK_TIME_NONE)) {
+    if (pad && (pad->last_timestamp != GST_CLOCK_TIME_NONE)) {
       if (duration == GST_CLOCK_TIME_NONE)
-        duration = cpad->last_timestamp;
+        duration = pad->last_timestamp;
       else
-        duration = MAX (duration, cpad->last_timestamp);
+        duration = MAX (duration, pad->last_timestamp);
     }
   }
+  GST_OBJECT_UNLOCK (mux);
 
   return duration;
 }
 
+struct DurationData
+{
+  GstClockTime duration;
+};
+
+static gboolean
+duration_query_cb (GstElement * element, GstPad * pad,
+    struct DurationData *data)
+{
+  GstClockTime dur;
+
+  if (gst_pad_peer_query_duration (GST_PAD (pad), GST_FORMAT_TIME,
+          (gint64 *) & dur) && dur != GST_CLOCK_TIME_NONE) {
+    if (data->duration == GST_CLOCK_TIME_NONE)
+      data->duration = dur;
+    else
+      data->duration = MAX (dur, data->duration);
+  }
+
+  return TRUE;
+}
+
+static GstClockTime
+gst_flv_mux_query_upstream_duration (GstFlvMux * mux)
+{
+  struct DurationData cb_data = { GST_CLOCK_TIME_NONE };
+
+  gst_element_foreach_sink_pad (GST_ELEMENT (mux),
+      (GstElementForeachPadFunc) (duration_query_cb), &cb_data);
+
+  return cb_data.duration;
+}
+
+static gboolean
+gst_flv_mux_are_all_pads_eos (GstFlvMux * mux)
+{
+  GList *l;
+
+  GST_OBJECT_LOCK (mux);
+  for (l = GST_ELEMENT_CAST (mux)->sinkpads; l; l = l->next) {
+    GstFlvMuxPad *pad = GST_FLV_MUX_PAD (l->data);
+
+    if (!gst_aggregator_pad_is_eos (GST_AGGREGATOR_PAD (pad))) {
+      GST_OBJECT_UNLOCK (mux);
+      return FALSE;
+    }
+  }
+  GST_OBJECT_UNLOCK (mux);
+  return TRUE;
+}
+
+static GstFlowReturn
+gst_flv_mux_write_eos (GstFlvMux * mux)
+{
+  GstBuffer *tag;
+
+  if (mux->video_pad == NULL)
+    return GST_FLOW_OK;
+
+  tag = gst_flv_mux_eos_to_tag (mux, mux->video_pad);
+
+  return gst_flv_mux_push (mux, tag);
+}
+
 static GstFlowReturn
 gst_flv_mux_rewrite_header (GstFlvMux * mux)
 {
@@ -1362,7 +1816,7 @@ gst_flv_mux_rewrite_header (GstFlvMux * mux)
   GST_DEBUG_OBJECT (mux, "putting total filesize %f in the metadata", d);
 
   tmp = gst_flv_mux_create_number_script_value ("filesize", d);
-  rewrite = gst_buffer_join (rewrite, tmp);
+  rewrite = gst_buffer_append (rewrite, tmp);
 
   if (!mux->index) {
     /* no index, so push buffer and return */
@@ -1446,75 +1900,198 @@ gst_flv_mux_rewrite_header (GstFlvMux * mux)
     GST_DEBUG_OBJECT (mux, "Remaining filler size is %d bytes",
         remaining_filler_size);
     GST_WRITE_UINT16_BE (data + 12, remaining_filler_size);
-    index = gst_buffer_join (index, tmp);
+    index = gst_buffer_append (index, tmp);
   }
 
-  rewrite = gst_buffer_join (rewrite, index);
+  rewrite = gst_buffer_append (rewrite, index);
 
   return gst_flv_mux_push (mux, rewrite);
 }
 
+/* Returns NULL, or a reference to the pad with the
+ * buffer with lowest running time */
+static GstFlvMuxPad *
+gst_flv_mux_find_best_pad (GstAggregator * aggregator, GstClockTime * ts,
+    gboolean timeout)
+{
+  GstFlvMuxPad *best = NULL;
+  GstClockTime best_ts = GST_CLOCK_TIME_NONE;
+  GstIterator *pads;
+  GValue padptr = { 0, };
+  gboolean done = FALSE;
+
+  pads = gst_element_iterate_sink_pads (GST_ELEMENT (aggregator));
+
+  while (!done) {
+    switch (gst_iterator_next (pads, &padptr)) {
+      case GST_ITERATOR_OK:{
+        GstAggregatorPad *apad = g_value_get_object (&padptr);
+        GstClockTime t = GST_CLOCK_TIME_NONE;
+        GstBuffer *buffer;
+
+        buffer = gst_aggregator_pad_peek_buffer (apad);
+        if (!buffer) {
+          if (!timeout && !GST_PAD_IS_EOS (apad)) {
+            gst_object_replace ((GstObject **) & best, NULL);
+            best_ts = GST_CLOCK_TIME_NONE;
+            done = TRUE;
+          }
+          break;
+        }
+
+        if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS_OR_PTS (buffer))) {
+          t = gst_flv_mux_segment_to_running_time (&apad->segment,
+              GST_BUFFER_DTS_OR_PTS (buffer));
+        }
+
+        if (!GST_CLOCK_TIME_IS_VALID (best_ts) ||
+            (GST_CLOCK_TIME_IS_VALID (t) && t < best_ts)) {
+          gst_object_replace ((GstObject **) & best, GST_OBJECT (apad));
+          best_ts = t;
+        }
+        gst_buffer_unref (buffer);
+        break;
+      }
+      case GST_ITERATOR_DONE:
+        done = TRUE;
+        break;
+      case GST_ITERATOR_RESYNC:
+        gst_iterator_resync (pads);
+        /* Clear the best pad and start again. It might have disappeared */
+        gst_object_replace ((GstObject **) & best, NULL);
+        best_ts = GST_CLOCK_TIME_NONE;
+        break;
+      case GST_ITERATOR_ERROR:
+        /* This can't happen if the parameters to gst_iterator_next() are valid */
+        g_assert_not_reached ();
+        break;
+    }
+    g_value_reset (&padptr);
+  }
+  g_value_unset (&padptr);
+  gst_iterator_free (pads);
+
+  if (best) {
+    GST_DEBUG_OBJECT (aggregator,
+        "Best pad found with TS %" GST_TIME_FORMAT ": %" GST_PTR_FORMAT,
+        GST_TIME_ARGS (best_ts), best);
+  } else {
+    GST_DEBUG_OBJECT (aggregator, "Best pad not found");
+  }
+
+  if (ts)
+    *ts = best_ts;
+  return best;
+}
+
 static GstFlowReturn
-gst_flv_mux_handle_buffer (GstCollectPads2 * pads, GstCollectData2 * cdata,
-    GstBuffer * buffer, gpointer user_data)
+gst_flv_mux_aggregate (GstAggregator * aggregator, gboolean timeout)
 {
-  GstFlvMux *mux = GST_FLV_MUX (user_data);
-  GstFlvPad *best;
-  GstClockTime best_time;
+  GstFlvMux *mux = GST_FLV_MUX (aggregator);
+  GstFlvMuxPad *best;
+  GstClockTime best_time = GST_CLOCK_TIME_NONE;
   GstFlowReturn ret;
+  GstClockTime ts;
+  GstBuffer *buffer = NULL;
 
   if (mux->state == GST_FLV_MUX_STATE_HEADER) {
-    GstSegment segment;
-
-    if (mux->collect->data == NULL) {
+    if (GST_ELEMENT_CAST (mux)->sinkpads == NULL) {
       GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
           ("No input streams configured"));
       return GST_FLOW_ERROR;
     }
 
-    gst_segment_init (&segment, GST_FORMAT_BYTES);
-    if (gst_pad_push_event (mux->srcpad, gst_event_new_segment (&segment)))
-      ret = gst_flv_mux_write_header (mux);
-    else
-      ret = GST_FLOW_ERROR;
+    best = gst_flv_mux_find_best_pad (aggregator, &ts, timeout);
+    if (!best) {
+      if (!gst_flv_mux_are_all_pads_eos (mux))
+        return GST_AGGREGATOR_FLOW_NEED_DATA;
+      else
+        return GST_FLOW_OK;
+    }
 
-    if (ret != GST_FLOW_OK)
+    ret = gst_flv_mux_write_header (mux);
+    if (ret != GST_FLOW_OK) {
+      gst_object_unref (best);
       return ret;
+    }
+
     mux->state = GST_FLV_MUX_STATE_DATA;
+
+    if (!mux->streamable || mux->first_timestamp == GST_CLOCK_TIME_NONE) {
+      if (best && GST_CLOCK_TIME_IS_VALID (ts))
+        mux->first_timestamp = ts;
+      else
+        mux->first_timestamp = 0;
+    }
+  } else {
+    best = gst_flv_mux_find_best_pad (aggregator, &ts, timeout);
+  }
+
+  if (best) {
+    buffer = gst_aggregator_pad_pop_buffer (GST_AGGREGATOR_PAD (best));
+    if (!buffer) {
+      /* We might have gotten a flush event after we picked the pad */
+      gst_object_unref (best);
+      return GST_AGGREGATOR_FLOW_NEED_DATA;
+    }
   }
 
-  if (mux->new_tags) {
-    GstBuffer *buf = gst_flv_mux_create_metadata (mux, FALSE);
+  if (mux->new_tags && mux->streamable) {
+    GstBuffer *buf = gst_flv_mux_create_metadata (mux);
     if (buf)
       gst_flv_mux_push (mux, buf);
     mux->new_tags = FALSE;
   }
 
-  best = (GstFlvPad *) cdata;
   if (best) {
-    g_assert (buffer);
-    best_time = GST_BUFFER_TIMESTAMP (buffer);
+    best->dts =
+        gst_flv_mux_segment_to_running_time (&GST_AGGREGATOR_PAD
+        (best)->segment, GST_BUFFER_DTS_OR_PTS (buffer));
+
+    if (GST_CLOCK_TIME_IS_VALID (best->dts))
+      best_time = best->dts - mux->first_timestamp;
+
+    if (GST_BUFFER_PTS_IS_VALID (buffer))
+      best->pts =
+          gst_flv_mux_segment_to_running_time (&GST_AGGREGATOR_PAD
+          (best)->segment, GST_BUFFER_PTS (buffer));
+    else
+      best->pts = best->dts;
+
+    GST_LOG_OBJECT (best,
+        "got buffer PTS %" GST_TIME_FORMAT " DTS %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (best->pts), GST_TIME_ARGS (best->dts));
   } else {
-    best_time = GST_CLOCK_TIME_NONE;
+    if (!gst_flv_mux_are_all_pads_eos (mux))
+      return GST_AGGREGATOR_FLOW_NEED_DATA;
+    best_time = GST_CLOCK_STIME_NONE;
   }
 
   /* The FLV timestamp is an int32 field. For non-live streams error out if a
      bigger timestamp is seen, for live the timestamp will get wrapped in
      gst_flv_mux_buffer_to_tag */
-  if (!mux->streamable && GST_CLOCK_TIME_IS_VALID (best_time)
+  if (!mux->streamable && (GST_CLOCK_TIME_IS_VALID (best_time))
       && best_time / GST_MSECOND > G_MAXINT32) {
     GST_WARNING_OBJECT (mux, "Timestamp larger than FLV supports - EOS");
-    gst_buffer_unref (buffer);
-    buffer = NULL;
+    if (buffer) {
+      gst_buffer_unref (buffer);
+      buffer = NULL;
+    }
+    gst_object_unref (best);
     best = NULL;
   }
 
   if (best) {
-    return gst_flv_mux_write_buffer (mux, best, buffer);
+    GstFlowReturn ret = gst_flv_mux_write_buffer (mux, best, buffer);
+    gst_object_unref (best);
+    return ret;
   } else {
-    gst_flv_mux_rewrite_header (mux);
-    gst_pad_push_event (mux->srcpad, gst_event_new_eos ());
-    return GST_FLOW_EOS;
+    if (gst_flv_mux_are_all_pads_eos (mux)) {
+      gst_flv_mux_write_eos (mux);
+      gst_flv_mux_rewrite_header (mux);
+      return GST_FLOW_EOS;
+    }
+    return GST_FLOW_OK;
   }
 }
 
@@ -1528,6 +2105,15 @@ gst_flv_mux_get_property (GObject * object,
     case PROP_STREAMABLE:
       g_value_set_boolean (value, mux->streamable);
       break;
+    case PROP_METADATACREATOR:
+      g_value_set_string (value, mux->metadatacreator);
+      break;
+    case PROP_ENCODER:
+      g_value_set_string (value, mux->encoder);
+      break;
+    case PROP_SKIP_BACKWARDS_STREAMS:
+      g_value_set_boolean (value, mux->skip_backwards_streams);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -1550,46 +2136,65 @@ gst_flv_mux_set_property (GObject * object,
         gst_tag_setter_set_tag_merge_mode (GST_TAG_SETTER (mux),
             GST_TAG_MERGE_KEEP);
       break;
+    case PROP_METADATACREATOR:
+      g_free (mux->metadatacreator);
+      if (!g_value_get_string (value)) {
+        GST_WARNING_OBJECT (mux, "metadatacreator property can not be NULL");
+        mux->metadatacreator = g_strdup (DEFAULT_METADATACREATOR);
+      } else {
+        mux->metadatacreator = g_value_dup_string (value);
+      }
+      break;
+    case PROP_ENCODER:
+      g_free (mux->encoder);
+      if (!g_value_get_string (value)) {
+        GST_WARNING_OBJECT (mux, "encoder property can not be NULL");
+        mux->encoder = g_strdup (DEFAULT_METADATACREATOR);
+      } else {
+        mux->encoder = g_value_dup_string (value);
+      }
+      break;
+    case PROP_SKIP_BACKWARDS_STREAMS:
+      mux->skip_backwards_streams = g_value_get_boolean (value);
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
   }
 }
 
-static GstStateChangeReturn
-gst_flv_mux_change_state (GstElement * element, GstStateChange transition)
+static GstClockTime
+gst_flv_mux_get_next_time (GstAggregator * aggregator)
 {
-  GstStateChangeReturn ret;
-  GstFlvMux *mux = GST_FLV_MUX (element);
-
-  switch (transition) {
-    case GST_STATE_CHANGE_NULL_TO_READY:
-      break;
-    case GST_STATE_CHANGE_READY_TO_PAUSED:
-      gst_collect_pads2_start (mux->collect);
-      break;
-    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
-      break;
-    case GST_STATE_CHANGE_PAUSED_TO_READY:
-      gst_collect_pads2_stop (mux->collect);
-      break;
-    default:
-      break;
-  }
+  GstFlvMux *mux = GST_FLV_MUX (aggregator);
+  GstAggregatorPad *agg_audio_pad = GST_AGGREGATOR_PAD_CAST (mux->audio_pad);
+  GstAggregatorPad *agg_video_pad = GST_AGGREGATOR_PAD_CAST (mux->video_pad);
+
+  GST_OBJECT_LOCK (aggregator);
+  if (mux->state == GST_FLV_MUX_STATE_HEADER &&
+      ((mux->audio_pad && mux->audio_pad->codec == G_MAXUINT) ||
+          (mux->video_pad && mux->video_pad->codec == G_MAXUINT)))
+    goto wait_for_data;
+
+  if (!((agg_audio_pad && gst_aggregator_pad_has_buffer (agg_audio_pad)) ||
+          (agg_video_pad && gst_aggregator_pad_has_buffer (agg_video_pad))))
+    goto wait_for_data;
+  GST_OBJECT_UNLOCK (aggregator);
+
+  return gst_aggregator_simple_get_next_time (aggregator);
+
+wait_for_data:
+  GST_OBJECT_UNLOCK (aggregator);
+  return GST_CLOCK_TIME_NONE;
+}
 
-  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+static GstFlowReturn
+gst_flv_mux_update_src_caps (GstAggregator * aggregator,
+    GstCaps * caps, GstCaps ** ret)
+{
+  GstFlvMux *mux = GST_FLV_MUX (aggregator);
 
-  switch (transition) {
-    case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
-      break;
-    case GST_STATE_CHANGE_PAUSED_TO_READY:
-      gst_flv_mux_reset (GST_ELEMENT (mux));
-      break;
-    case GST_STATE_CHANGE_READY_TO_NULL:
-      break;
-    default:
-      break;
-  }
+  *ret = gst_flv_mux_prepare_src_caps (mux, NULL, NULL, NULL, NULL);
 
-  return ret;
+  return GST_FLOW_OK;
 }