misc: chain up to collectpads event handler
[platform/upstream/gst-plugins-good.git] / gst / matroska / matroska-mux.c
index 55fd051..4cb87da 100644 (file)
@@ -2,6 +2,7 @@
  * (c) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
  * (c) 2005 Michal Benes <michal.benes@xeris.cz>
  * (c) 2008 Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ * (c) 2011 Mark Nauwelaerts <mark.nauwelaerts@collabora.co.uk>
  *
  * matroska-mux.c: matroska file/stream muxer
  *
@@ -56,6 +57,8 @@
 #include "matroska-mux.h"
 #include "matroska-ids.h"
 
+#define GST_MATROSKA_MUX_CHAPLANG "und"
+
 GST_DEBUG_CATEGORY_STATIC (matroskamux_debug);
 #define GST_CAT_DEFAULT matroskamux_debug
 
@@ -147,10 +150,10 @@ static GstStaticPadTemplate audiosink_templ =
     GST_STATIC_CAPS ("audio/mpeg, "
         "mpegversion = (int) 1, "
         "layer = (int) [ 1, 3 ], "
-        "stream-format = (string) { raw }, "
         COMMON_AUDIO_CAPS "; "
         "audio/mpeg, "
         "mpegversion = (int) { 2, 4 }, "
+        "stream-format = (string) raw, "
         COMMON_AUDIO_CAPS "; "
         "audio/x-ac3, "
         COMMON_AUDIO_CAPS "; "
@@ -166,6 +169,7 @@ static GstStaticPadTemplate audiosink_templ =
         COMMON_AUDIO_CAPS "; "
         "audio/x-raw, "
         "format = (string) { U8, S16BE, S16LE, S24BE, S24LE, S32BE, S32LE, F32LE, F64LE }, "
+        "layout = (string) interleaved, "
         COMMON_AUDIO_CAPS ";"
         "audio/x-tta, "
         "width = (int) { 8, 16, 24 }, "
@@ -182,24 +186,32 @@ static GstStaticPadTemplate audiosink_templ =
     );
 
 static GstStaticPadTemplate subtitlesink_templ =
-GST_STATIC_PAD_TEMPLATE ("subtitle_%u",
+    GST_STATIC_PAD_TEMPLATE ("subtitle_%d",
     GST_PAD_SINK,
     GST_PAD_REQUEST,
-    GST_STATIC_CAPS ("subtitle/x-kate"));
+    GST_STATIC_CAPS ("subtitle/x-kate; "
+        "text/plain; application/x-ssa; application/x-ass; "
+        "application/x-usf; video/x-dvd-subpicture; "
+        "application/x-subtitle-unknown")
+    );
 
 static GArray *used_uids;
 G_LOCK_DEFINE_STATIC (used_uids);
 
 #define parent_class gst_matroska_mux_parent_class
 G_DEFINE_TYPE_WITH_CODE (GstMatroskaMux, gst_matroska_mux, GST_TYPE_ELEMENT,
-    G_IMPLEMENT_INTERFACE (GST_TYPE_TAG_SETTER, NULL));
+    G_IMPLEMENT_INTERFACE (GST_TYPE_TAG_SETTER, NULL)
+    G_IMPLEMENT_INTERFACE (GST_TYPE_TOC_SETTER, NULL)
+    );
 
 /* Matroska muxer destructor */
 static void gst_matroska_mux_finalize (GObject * object);
 
 /* Pads collected callback */
-static GstFlowReturn
-gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data);
+static GstFlowReturn gst_matroska_mux_handle_buffer (GstCollectPads2 * pads,
+    GstCollectData2 * data, GstBuffer * buf, gpointer user_data);
+static gboolean gst_matroska_mux_handle_sink_event (GstCollectPads2 * pads,
+    GstCollectData2 * data, GstEvent * event, gpointer user_data);
 
 /* pad functions */
 static gboolean gst_matroska_mux_handle_src_event (GstPad * pad,
@@ -255,7 +267,7 @@ gst_matroska_mux_class_init (GstMatroskaMuxClass * klass)
       gst_static_pad_template_get (&subtitlesink_templ));
   gst_element_class_add_pad_template (gstelement_class,
       gst_static_pad_template_get (&src_templ));
-  gst_element_class_set_details_simple (gstelement_class, "Matroska muxer",
+  gst_element_class_set_static_metadata (gstelement_class, "Matroska muxer",
       "Codec/Muxer",
       "Muxes video/audio/subtitle streams into a matroska stream",
       "GStreamer maintainers <gstreamer-devel@lists.sourceforge.net>");
@@ -414,10 +426,13 @@ gst_matroska_mux_init (GstMatroskaMux * mux)
   gst_pad_set_event_function (mux->srcpad, gst_matroska_mux_handle_src_event);
   gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad);
 
-  mux->collect = gst_collect_pads_new ();
-  gst_collect_pads_set_function (mux->collect,
-      (GstCollectPadsFunction) GST_DEBUG_FUNCPTR (gst_matroska_mux_collected),
-      mux);
+  mux->collect = gst_collect_pads2_new ();
+  gst_collect_pads2_set_clip_function (mux->collect,
+      GST_DEBUG_FUNCPTR (gst_collect_pads2_clip_running_time), mux);
+  gst_collect_pads2_set_buffer_function (mux->collect,
+      GST_DEBUG_FUNCPTR (gst_matroska_mux_handle_buffer), mux);
+  gst_collect_pads2_set_event_function (mux->collect,
+      GST_DEBUG_FUNCPTR (gst_matroska_mux_handle_sink_event), mux);
 
   mux->ebml_write = gst_ebml_write_new (mux->srcpad);
   mux->doctype = GST_MATROSKA_DOCTYPE_MATROSKA;
@@ -534,12 +549,6 @@ gst_matroska_pad_reset (GstMatroskaPad * collect_pad, gboolean full)
     collect_pad->track = NULL;
   }
 
-  /* free cached buffer */
-  if (collect_pad->buffer != NULL) {
-    gst_buffer_unref (collect_pad->buffer);
-    collect_pad->buffer = NULL;
-  }
-
   if (!full && type != 0) {
     GstMatroskaTrackContext *context;
 
@@ -567,7 +576,6 @@ gst_matroska_pad_reset (GstMatroskaPad * collect_pad, gboolean full)
     /* TODO: check default values for the context */
     context->flags = GST_MATROSKA_TRACK_ENABLED | GST_MATROSKA_TRACK_DEFAULT;
     collect_pad->track = context;
-    collect_pad->buffer = NULL;
     collect_pad->duration = 0;
     collect_pad->start_ts = GST_CLOCK_TIME_NONE;
     collect_pad->end_ts = GST_CLOCK_TIME_NONE;
@@ -634,6 +642,13 @@ gst_matroska_mux_reset (GstElement * element)
 
   /* reset tags */
   gst_tag_setter_reset_tags (GST_TAG_SETTER (mux));
+
+  mux->tags_pos = 0;
+
+  /* reset chapters */
+  gst_toc_setter_reset_toc (GST_TOC_SETTER (mux));
+
+  mux->chapters_pos = 0;
 }
 
 /**
@@ -664,6 +679,55 @@ gst_matroska_mux_handle_src_event (GstPad * pad, GstObject * parent,
   return gst_pad_event_default (pad, parent, event);
 }
 
+
+static void
+gst_matroska_mux_free_codec_priv (GstMatroskaTrackContext * context)
+{
+  if (context->codec_priv != NULL) {
+    g_free (context->codec_priv);
+    context->codec_priv = NULL;
+    context->codec_priv_size = 0;
+  }
+}
+
+static void
+gst_matroska_mux_build_vobsub_private (GstMatroskaTrackContext * context,
+    const guint * clut)
+{
+  gchar *clutv[17];
+  gchar *sclut;
+  gint i;
+  guint32 col;
+  gdouble y, u, v;
+  guint8 r, g, b;
+
+  /* produce comma-separated list in hex format */
+  for (i = 0; i < 16; ++i) {
+    col = clut[i];
+    /* replicate vobsub's slightly off RGB conversion calculation */
+    y = (((col >> 16) & 0xff) - 16) * 255 / 219;
+    u = ((col >> 8) & 0xff) - 128;
+    v = (col & 0xff) - 128;
+    r = CLAMP (1.0 * y + 1.4022 * u, 0, 255);
+    g = CLAMP (1.0 * y - 0.3456 * u - 0.7145 * v, 0, 255);
+    b = CLAMP (1.0 * y + 1.7710 * v, 0, 255);
+    clutv[i] = g_strdup_printf ("%02x%02x%02x", r, g, b);
+  }
+  clutv[i] = NULL;
+  sclut = g_strjoinv (",", clutv);
+
+  /* build codec private; only palette for now */
+  gst_matroska_mux_free_codec_priv (context);
+  context->codec_priv = (guint8 *) g_strdup_printf ("palette: %s", sclut);
+  /* include terminating 0 */
+  context->codec_priv_size = strlen ((gchar *) context->codec_priv) + 1;
+  g_free (sclut);
+  for (i = 0; i < 16; ++i) {
+    g_free (clutv[i]);
+  }
+}
+
+
 /**
  * gst_matroska_mux_handle_sink_event:
  * @pad: Pad which received the event.
@@ -674,15 +738,22 @@ gst_matroska_mux_handle_src_event (GstPad * pad, GstObject * parent,
  * Returns: #TRUE on success.
  */
 static gboolean
-gst_matroska_mux_handle_sink_event (GstPad * pad, GstObject * parent,
-    GstEvent * event)
+gst_matroska_mux_handle_sink_event (GstCollectPads2 * pads,
+    GstCollectData2 * data, GstEvent * event, gpointer user_data)
 {
-  GstMatroskaTrackContext *context;
   GstMatroskaPad *collect_pad;
-  GstMatroskaMux *mux = GST_MATROSKA_MUX (parent);
+  GstMatroskaTrackContext *context;
+  GstMatroskaMux *mux;
+  GstPad *pad;
   GstTagList *list;
   gboolean ret = TRUE;
 
+  mux = GST_MATROSKA_MUX (user_data);
+  collect_pad = (GstMatroskaPad *) data;
+  pad = data->pad;
+  context = collect_pad->track;
+  g_assert (context);
+
   switch (GST_EVENT_TYPE (event)) {
     case GST_EVENT_CAPS:{
       GstCaps *caps;
@@ -701,11 +772,6 @@ gst_matroska_mux_handle_sink_event (GstPad * pad, GstObject * parent,
       GST_DEBUG_OBJECT (mux, "received tag event");
       gst_event_parse_tag (event, &list);
 
-      collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
-      g_assert (collect_pad);
-      context = collect_pad->track;
-      g_assert (context);
-
       /* Matroska wants ISO 639-2B code, taglist most likely contains 639-1 */
       if (gst_tag_list_get_string (list, GST_TAG_LANGUAGE_CODE, &lang)) {
         const gchar *lang_code;
@@ -727,17 +793,31 @@ gst_matroska_mux_handle_sink_event (GstPad * pad, GstObject * parent,
       gst_event_unref (event);
       /* handled this, don't want collectpads to forward it downstream */
       event = NULL;
+      ret = TRUE;
       break;
     }
-    case GST_EVENT_SEGMENT:{
-      const GstSegment *segment;
+    case GST_EVENT_TOC:{
+      GstToc *toc;
 
-      gst_event_parse_segment (event, &segment);
-      if (segment->format != GST_FORMAT_TIME) {
-        ret = FALSE;
-        gst_event_unref (event);
-        event = NULL;
+      if (mux->chapters_pos > 0)
+        break;
+
+      GST_DEBUG_OBJECT (mux, "received toc event");
+      gst_event_parse_toc (event, &toc, NULL);
+
+      if (toc != NULL) {
+        if (gst_toc_setter_get_toc (GST_TOC_SETTER (mux)) != NULL) {
+          gst_toc_setter_reset_toc (GST_TOC_SETTER (mux));
+          GST_INFO_OBJECT (pad, "Replacing TOC with a new one");
+        }
+
+        gst_toc_setter_set_toc (GST_TOC_SETTER (mux), toc);
+        gst_toc_free (toc);
       }
+
+      gst_event_unref (event);
+      /* handled this, don't want collectpads to forward it downstream */
+      event = NULL;
       break;
     }
     case GST_EVENT_CUSTOM_DOWNSTREAM:{
@@ -748,20 +828,53 @@ gst_matroska_mux_handle_sink_event (GstPad * pad, GstObject * parent,
         gst_event_replace (&mux->force_key_unit_event, NULL);
         mux->force_key_unit_event = event;
         event = NULL;
+      } else if (gst_structure_has_name (structure, "application/x-gst-dvd") &&
+          !strcmp ("dvd-spu-clut-change",
+              gst_structure_get_string (structure, "event"))) {
+        gchar name[16];
+        gint i, value;
+        guint clut[16];
+
+        GST_DEBUG_OBJECT (pad, "New DVD colour table received");
+        if (context->type != GST_MATROSKA_TRACK_TYPE_SUBTITLE) {
+          GST_DEBUG_OBJECT (pad, "... discarding");
+          break;
+        }
+        /* first transform event data into table form */
+        for (i = 0; i < 16; i++) {
+          g_snprintf (name, sizeof (name), "clut%02d", i);
+          if (!gst_structure_get_int (structure, name, &value)) {
+            GST_ERROR_OBJECT (mux, "dvd-spu-clut-change event did not "
+                "contain %s field", name);
+            break;
+          }
+          clut[i] = value;
+        }
+
+        /* transform into private data for stream; text form */
+        gst_matroska_mux_build_vobsub_private (context, clut);
       }
-      break;
     }
+      /* fall through */
     default:
       break;
   }
 
-  /* now GstCollectPads can take care of the rest, e.g. EOS */
-  if (event)
-    ret = mux->collect_event (pad, parent, event);
+  if (event != NULL)
+    return gst_collect_pads2_event_default (pads, data, event, FALSE);
 
   return ret;
 }
 
+static void
+gst_matroska_mux_set_codec_id (GstMatroskaTrackContext * context,
+    const char *id)
+{
+  g_assert (context && id);
+  if (context->codec_id)
+    g_free (context->codec_id);
+  context->codec_id = g_strdup (id);
+}
 
 /**
  * gst_matroska_mux_video_pad_setcaps:
@@ -871,12 +984,13 @@ skip_details:
   /* find type */
   if (!strcmp (mimetype, "video/x-raw")) {
     const gchar *fstr;
-    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED);
+    gst_matroska_mux_set_codec_id (context,
+        GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED);
     fstr = gst_structure_get_string (structure, "format");
     if (fstr && strlen (fstr) == 4)
       videocontext->fourcc = GST_STR_FOURCC (fstr);
   } else if (!strcmp (mimetype, "image/jpeg")) {
-    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MJPEG);
+    gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_VIDEO_MJPEG);
   } else if (!strcmp (mimetype, "video/x-xvid") /* MS/VfW compatibility cases */
       ||!strcmp (mimetype, "video/x-huffyuv")
       || !strcmp (mimetype, "video/x-divx")
@@ -969,18 +1083,15 @@ skip_details:
           (guint8 *) bih + sizeof (gst_riff_strf_vids), -1);
     }
 
-    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC);
+    gst_matroska_mux_set_codec_id (context,
+        GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC);
+    gst_matroska_mux_free_codec_priv (context);
     context->codec_priv = (gpointer) bih;
     context->codec_priv_size = size;
   } else if (!strcmp (mimetype, "video/x-h264")) {
-    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC);
-
-    if (context->codec_priv != NULL) {
-      g_free (context->codec_priv);
-      context->codec_priv = NULL;
-      context->codec_priv_size = 0;
-    }
-
+    gst_matroska_mux_set_codec_id (context,
+        GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC);
+    gst_matroska_mux_free_codec_priv (context);
     /* Create avcC header */
     if (codec_buf != NULL) {
       context->codec_priv_size = gst_buffer_get_size (codec_buf);
@@ -990,13 +1101,9 @@ skip_details:
   } else if (!strcmp (mimetype, "video/x-theora")) {
     const GValue *streamheader;
 
-    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_THEORA);
+    gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_VIDEO_THEORA);
 
-    if (context->codec_priv != NULL) {
-      g_free (context->codec_priv);
-      context->codec_priv = NULL;
-      context->codec_priv_size = 0;
-    }
+    gst_matroska_mux_free_codec_priv (context);
 
     streamheader = gst_structure_get_value (structure, "streamheader");
     if (!theora_streamheader_to_codecdata (streamheader, context)) {
@@ -1005,22 +1112,25 @@ skip_details:
       goto refuse_caps;
     }
   } else if (!strcmp (mimetype, "video/x-dirac")) {
-    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_DIRAC);
+    gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_VIDEO_DIRAC);
   } else if (!strcmp (mimetype, "video/x-vp8")) {
-    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VP8);
+    gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_VIDEO_VP8);
   } else if (!strcmp (mimetype, "video/mpeg")) {
     gint mpegversion;
 
     gst_structure_get_int (structure, "mpegversion", &mpegversion);
     switch (mpegversion) {
       case 1:
-        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG1);
+        gst_matroska_mux_set_codec_id (context,
+            GST_MATROSKA_CODEC_ID_VIDEO_MPEG1);
         break;
       case 2:
-        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG2);
+        gst_matroska_mux_set_codec_id (context,
+            GST_MATROSKA_CODEC_ID_VIDEO_MPEG2);
         break;
       case 4:
-        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP);
+        gst_matroska_mux_set_codec_id (context,
+            GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP);
         break;
       default:
         goto refuse_caps;
@@ -1028,6 +1138,7 @@ skip_details:
 
     /* global headers may be in codec data */
     if (codec_buf != NULL) {
+      gst_matroska_mux_free_codec_priv (context);
       context->codec_priv_size = gst_buffer_get_size (codec_buf);
       context->codec_priv = g_malloc0 (context->codec_priv_size);
       gst_buffer_extract (codec_buf, 0, context->codec_priv, -1);
@@ -1043,16 +1154,20 @@ skip_details:
     gst_structure_get_int (structure, "rmversion", &rmversion);
     switch (rmversion) {
       case 1:
-        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO1);
+        gst_matroska_mux_set_codec_id (context,
+            GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO1);
         break;
       case 2:
-        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO2);
+        gst_matroska_mux_set_codec_id (context,
+            GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO2);
         break;
       case 3:
-        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO3);
+        gst_matroska_mux_set_codec_id (context,
+            GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO3);
         break;
       case 4:
-        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4);
+        gst_matroska_mux_set_codec_id (context,
+            GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4);
         break;
       default:
         goto refuse_caps;
@@ -1070,6 +1185,7 @@ skip_details:
 
       gst_buffer_extract (codec_data_buf, 0, priv_data, -1);
 
+      gst_matroska_mux_free_codec_priv (context);
       context->codec_priv = priv_data;
       context->codec_priv_size = priv_data_size;
     }
@@ -1153,6 +1269,7 @@ xiphN_streamheader_to_codecdata (const GValue * streamheader,
     offset += gst_buffer_get_size (buf[i]);
   }
 
+  gst_matroska_mux_free_codec_priv (context);
   context->codec_priv = priv_data;
   context->codec_priv_size = priv_data_size;
 
@@ -1201,14 +1318,15 @@ vorbis_streamheader_to_codecdata (const GValue * streamheader,
   } else {
     if (gst_buffer_memcmp (buf0, 1, "vorbis", 6) == 0) {
       GstMatroskaTrackAudioContext *audiocontext;
-      guint8 *data, *hdr;
+      GstMapInfo map;
+      guint8 *hdr;
 
-      data = gst_buffer_map (buf0, NULL, NULL, GST_MAP_READ);
-      hdr = data + 1 + 6 + 4;
+      gst_buffer_map (buf0, &map, GST_MAP_READ);
+      hdr = map.data + 1 + 6 + 4;
       audiocontext = (GstMatroskaTrackAudioContext *) context;
       audiocontext->channels = GST_READ_UINT8 (hdr);
       audiocontext->samplerate = GST_READ_UINT32_LE (hdr + 1);
-      gst_buffer_unmap (buf0, data, -1);
+      gst_buffer_unmap (buf0, &map);
     }
   }
 
@@ -1234,10 +1352,11 @@ theora_streamheader_to_codecdata (const GValue * streamheader,
   } else {
     GstMatroskaTrackVideoContext *videocontext;
     guint fps_num, fps_denom, par_num, par_denom;
-    guint8 *data, *hdr;
+    GstMapInfo map;
+    guint8 *hdr;
 
-    data = gst_buffer_map (buf0, NULL, NULL, GST_MAP_READ);
-    hdr = data + 1 + 6 + 3 + 2 + 2;
+    gst_buffer_map (buf0, &map, GST_MAP_READ);
+    hdr = map.data + 1 + 6 + 3 + 2 + 2;
 
     videocontext = (GstMatroskaTrackVideoContext *) context;
     videocontext->pixel_width = GST_READ_UINT32_BE (hdr) >> 8;
@@ -1269,7 +1388,7 @@ theora_streamheader_to_codecdata (const GValue * streamheader,
     }
     hdr += 3 + 3;
 
-    gst_buffer_unmap (buf0, data, -1);
+    gst_buffer_unmap (buf0, &map);
   }
 
   if (buf0)
@@ -1337,6 +1456,7 @@ flac_streamheader_to_codecdata (const GValue * streamheader,
     return FALSE;
   }
 
+  gst_matroska_mux_free_codec_priv (context);
   context->codec_priv_size = gst_buffer_get_size (buffer) - 9;
   context->codec_priv = g_malloc (context->codec_priv_size);
   gst_buffer_extract (buffer, 9, context->codec_priv, -1);
@@ -1346,9 +1466,7 @@ flac_streamheader_to_codecdata (const GValue * streamheader,
     bufval = &g_array_index (bufarr, GValue, i);
 
     if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
-      g_free (context->codec_priv);
-      context->codec_priv = NULL;
-      context->codec_priv_size = 0;
+      gst_matroska_mux_free_codec_priv (context);
       GST_WARNING ("streamheaders array does not contain GstBuffers");
       return FALSE;
     }
@@ -1403,6 +1521,7 @@ speex_streamheader_to_codecdata (const GValue * streamheader,
     return FALSE;
   }
 
+  gst_matroska_mux_free_codec_priv (context);
   context->codec_priv_size = gst_buffer_get_size (buffer);
   context->codec_priv = g_malloc (context->codec_priv_size);
   gst_buffer_extract (buffer, 0, context->codec_priv, -1);
@@ -1410,9 +1529,7 @@ speex_streamheader_to_codecdata (const GValue * streamheader,
   bufval = &g_array_index (bufarr, GValue, 1);
 
   if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
-    g_free (context->codec_priv);
-    context->codec_priv = NULL;
-    context->codec_priv_size = 0;
+    gst_matroska_mux_free_codec_priv (context);
     GST_WARNING ("streamheaders array does not contain GstBuffers");
     return FALSE;
   }
@@ -1550,13 +1667,16 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
 
         switch (layer) {
           case 1:
-            context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1);
+            gst_matroska_mux_set_codec_id (context,
+                GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L1);
             break;
           case 2:
-            context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2);
+            gst_matroska_mux_set_codec_id (context,
+                GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L2);
             break;
           case 3:
-            context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3);
+            gst_matroska_mux_set_codec_id (context,
+                GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3);
             break;
           default:
             goto refuse_caps;
@@ -1619,14 +1739,16 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
           goto refuse_caps;
         }
         if (GST_AUDIO_INFO_IS_BIG_ENDIAN (&info))
-          context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE);
+          gst_matroska_mux_set_codec_id (context,
+              GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_BE);
         else
-          context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE);
+          gst_matroska_mux_set_codec_id (context,
+              GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE);
         break;
-
       case GST_AUDIO_FORMAT_F32LE:
       case GST_AUDIO_FORMAT_F64LE:
-        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT);
+        gst_matroska_mux_set_codec_id (context,
+            GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT);
         break;
 
       default:
@@ -1635,17 +1757,12 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
     }
 
     audiocontext->bitdepth = GST_AUDIO_INFO_WIDTH (&info);
-
   } else if (!strcmp (mimetype, "audio/x-vorbis")) {
     const GValue *streamheader;
 
-    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_VORBIS);
+    gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_VORBIS);
 
-    if (context->codec_priv != NULL) {
-      g_free (context->codec_priv);
-      context->codec_priv = NULL;
-      context->codec_priv_size = 0;
-    }
+    gst_matroska_mux_free_codec_priv (context);
 
     streamheader = gst_structure_get_value (structure, "streamheader");
     if (!vorbis_streamheader_to_codecdata (streamheader, context)) {
@@ -1656,12 +1773,9 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
   } else if (!strcmp (mimetype, "audio/x-flac")) {
     const GValue *streamheader;
 
-    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_FLAC);
-    if (context->codec_priv != NULL) {
-      g_free (context->codec_priv);
-      context->codec_priv = NULL;
-      context->codec_priv_size = 0;
-    }
+    gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_FLAC);
+
+    gst_matroska_mux_free_codec_priv (context);
 
     streamheader = gst_structure_get_value (structure, "streamheader");
     if (!flac_streamheader_to_codecdata (streamheader, context)) {
@@ -1672,12 +1786,8 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
   } else if (!strcmp (mimetype, "audio/x-speex")) {
     const GValue *streamheader;
 
-    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_SPEEX);
-    if (context->codec_priv != NULL) {
-      g_free (context->codec_priv);
-      context->codec_priv = NULL;
-      context->codec_priv_size = 0;
-    }
+    gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_SPEEX);
+    gst_matroska_mux_free_codec_priv (context);
 
     streamheader = gst_structure_get_value (structure, "streamheader");
     if (!speex_streamheader_to_codecdata (streamheader, context)) {
@@ -1686,11 +1796,11 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
       goto refuse_caps;
     }
   } else if (!strcmp (mimetype, "audio/x-ac3")) {
-    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_AC3);
+    gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_AC3);
   } else if (!strcmp (mimetype, "audio/x-eac3")) {
-    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_EAC3);
+    gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_EAC3);
   } else if (!strcmp (mimetype, "audio/x-dts")) {
-    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_DTS);
+    gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_DTS);
   } else if (!strcmp (mimetype, "audio/x-tta")) {
     gint width;
 
@@ -1699,7 +1809,7 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
 
     gst_structure_get_int (structure, "width", &width);
     audiocontext->bitdepth = width;
-    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_TTA);
+    gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_TTA);
 
   } else if (!strcmp (mimetype, "audio/x-pn-realaudio")) {
     gint raversion;
@@ -1708,13 +1818,16 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
     gst_structure_get_int (structure, "raversion", &raversion);
     switch (raversion) {
       case 1:
-        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_14_4);
+        gst_matroska_mux_set_codec_id (context,
+            GST_MATROSKA_CODEC_ID_AUDIO_REAL_14_4);
         break;
       case 2:
-        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_28_8);
+        gst_matroska_mux_set_codec_id (context,
+            GST_MATROSKA_CODEC_ID_AUDIO_REAL_28_8);
         break;
       case 8:
-        context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_COOK);
+        gst_matroska_mux_set_codec_id (context,
+            GST_MATROSKA_CODEC_ID_AUDIO_REAL_COOK);
         break;
       default:
         goto refuse_caps;
@@ -1732,6 +1845,8 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
 
       gst_buffer_extract (codec_data_buf, 0, priv_data, -1);
 
+      gst_matroska_mux_free_codec_priv (context);
+
       context->codec_priv = priv_data;
       context->codec_priv_size = priv_data_size;
     }
@@ -1815,7 +1930,8 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
           (guint8 *) codec_priv + WAVEFORMATEX_SIZE, -1);
     }
 
-    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_ACM);
+    gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_AUDIO_ACM);
+    gst_matroska_mux_free_codec_priv (context);
     context->codec_priv = (gpointer) codec_priv;
     context->codec_priv_size = codec_priv_size;
   }
@@ -1831,6 +1947,10 @@ refuse_caps:
   }
 }
 
+/* we probably don't have the data at start,
+ * so have to reserve (a maximum) space to write this at the end.
+ * bit spacy, but some formats can hold quite some */
+#define SUBTITLE_MAX_CODEC_PRIVATE   2048       /* must be > 128 */
 
 /**
  * gst_matroska_mux_subtitle_pad_setcaps:
@@ -1844,11 +1964,6 @@ refuse_caps:
 static gboolean
 gst_matroska_mux_subtitle_pad_setcaps (GstPad * pad, GstCaps * caps)
 {
-  /* FIXME:
-   * Consider this as boilerplate code for now. There is
-   * no single subtitle creation element in GStreamer,
-   * neither do I know how subtitling works at all. */
-
   /* There is now (at least) one such alement (kateenc), and I'm going
      to handle it here and claim it works when it can be piped back
      through GStreamer and VLC */
@@ -1859,6 +1974,9 @@ gst_matroska_mux_subtitle_pad_setcaps (GstPad * pad, GstCaps * caps)
   GstMatroskaPad *collect_pad;
   const gchar *mimetype;
   GstStructure *structure;
+  const GValue *value = NULL;
+  GstBuffer *buf = NULL;
+  gboolean ret = TRUE;
 
   mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
 
@@ -1878,29 +1996,71 @@ gst_matroska_mux_subtitle_pad_setcaps (GstPad * pad, GstCaps * caps)
   scontext->invalid_utf8 = 0;
   context->default_duration = 0;
 
-  /* TODO: - other format than Kate */
-
   if (!strcmp (mimetype, "subtitle/x-kate")) {
     const GValue *streamheader;
 
-    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_SUBTITLE_KATE);
+    gst_matroska_mux_set_codec_id (context,
+        GST_MATROSKA_CODEC_ID_SUBTITLE_KATE);
 
-    if (context->codec_priv != NULL) {
-      g_free (context->codec_priv);
-      context->codec_priv = NULL;
-      context->codec_priv_size = 0;
-    }
+    gst_matroska_mux_free_codec_priv (context);
 
     streamheader = gst_structure_get_value (structure, "streamheader");
     if (!kate_streamheader_to_codecdata (streamheader, context)) {
       GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
           ("kate stream headers missing or malformed"));
-      return FALSE;
+      ret = FALSE;
+      goto exit;
+    }
+  } else if (!strcmp (mimetype, "text/plain")) {
+    gst_matroska_mux_set_codec_id (context,
+        GST_MATROSKA_CODEC_ID_SUBTITLE_UTF8);
+  } else if (!strcmp (mimetype, "application/x-ssa")) {
+    gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_SUBTITLE_SSA);
+  } else if (!strcmp (mimetype, "application/x-ass")) {
+    gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_SUBTITLE_ASS);
+  } else if (!strcmp (mimetype, "application/x-usf")) {
+    gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_SUBTITLE_USF);
+  } else if (!strcmp (mimetype, "video/x-dvd-subpicture")) {
+    gst_matroska_mux_set_codec_id (context,
+        GST_MATROSKA_CODEC_ID_SUBTITLE_VOBSUB);
+  } else {
+    ret = FALSE;
+    goto exit;
+  }
+
+  /* maybe some private data, e.g. vobsub */
+  value = gst_structure_get_value (structure, "codec_data");
+  if (value)
+    buf = gst_value_get_buffer (value);
+  if (buf != NULL) {
+    GstMapInfo map;
+    guint8 *priv_data = NULL;
+
+    gst_buffer_map (buf, &map, GST_MAP_READ);
+
+    if (map.size > SUBTITLE_MAX_CODEC_PRIVATE) {
+      GST_WARNING_OBJECT (mux, "pad %" GST_PTR_FORMAT " subtitle private data"
+          " exceeded maximum (%d); discarding", pad,
+          SUBTITLE_MAX_CODEC_PRIVATE);
+      gst_buffer_unmap (buf, &map);
+      return TRUE;
     }
-    return TRUE;
+
+    gst_matroska_mux_free_codec_priv (context);
+
+    priv_data = g_malloc0 (map.size);
+    memcpy (priv_data, map.data, map.size);
+    context->codec_priv = priv_data;
+    context->codec_priv_size = map.size;
+    gst_buffer_unmap (buf, &map);
   }
 
-  return FALSE;
+  GST_DEBUG_OBJECT (pad, "codec_id %s, codec data size %" G_GSIZE_FORMAT,
+      GST_STR_NULL (context->codec_id), context->codec_priv_size);
+
+exit:
+
+  return ret;
 }
 
 
@@ -1927,6 +2087,8 @@ gst_matroska_mux_request_new_pad (GstElement * element,
   GstMatroskaCapsFunc capsfunc = NULL;
   GstMatroskaTrackContext *context = NULL;
   gint pad_id;
+  gboolean locked = TRUE;
+  gchar *id = NULL;
 
   if (templ == gst_element_class_get_pad_template (klass, "audio_%u")) {
     /* don't mix named and unnamed pads, if the pad already exists we fail when
@@ -1970,6 +2132,9 @@ gst_matroska_mux_request_new_pad (GstElement * element,
         g_new0 (GstMatroskaTrackSubtitleContext, 1);
     context->type = GST_MATROSKA_TRACK_TYPE_SUBTITLE;
     context->name = g_strdup ("Subtitle");
+    /* setcaps may only provide proper one a lot later */
+    id = g_strdup ("S_SUB_UNKNOWN");
+    locked = FALSE;
   } else {
     GST_WARNING_OBJECT (mux, "This is not our template!");
     return NULL;
@@ -1981,24 +2146,13 @@ gst_matroska_mux_request_new_pad (GstElement * element,
 
   gst_matroskamux_pad_init (newpad);
   collect_pad = (GstMatroskaPad *)
-      gst_collect_pads_add_pad (mux->collect, GST_PAD (newpad),
-      sizeof (GstMatroskaPad),
-      (GstCollectDataDestroyNotify) gst_matroska_pad_free);
+      gst_collect_pads2_add_pad_full (mux->collect, GST_PAD (newpad),
+      sizeof (GstMatroskamuxPad),
+      (GstCollectData2DestroyNotify) gst_matroska_pad_free, locked);
 
   collect_pad->track = context;
   gst_matroska_pad_reset (collect_pad, FALSE);
-
-  /* FIXME: hacked way to override/extend the event function of
-   * GstCollectPads; because it sets its own event function giving the
-   * element no access to events.
-   * TODO GstCollectPads should really give its 'users' a clean chance to
-   * properly handle events that are not meant for collectpads itself.
-   * Perhaps a callback or so, though rejected (?) in #340060.
-   * This would allow (clean) transcoding of info from demuxer/streams
-   * to another muxer */
-  mux->collect_event = (GstPadEventFunction) GST_PAD_EVENTFUNC (newpad);
-  gst_pad_set_event_function (GST_PAD (newpad),
-      GST_DEBUG_FUNCPTR (gst_matroska_mux_handle_sink_event));
+  collect_pad->track->codec_id = id;
 
   collect_pad->capsfunc = capsfunc;
   gst_pad_set_active (GST_PAD (newpad), TRUE);
@@ -2036,7 +2190,7 @@ gst_matroska_mux_release_pad (GstElement * element, GstPad * pad)
   mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
 
   for (walk = mux->collect->data; walk; walk = g_slist_next (walk)) {
-    GstCollectData *cdata = (GstCollectData *) walk->data;
+    GstCollectData2 *cdata = (GstCollectData2 *) walk->data;
     GstMatroskaPad *collect_pad = (GstMatroskaPad *) cdata;
 
     if (cdata->pad == pad) {
@@ -2057,7 +2211,7 @@ gst_matroska_mux_release_pad (GstElement * element, GstPad * pad)
     }
   }
 
-  gst_collect_pads_remove_pad (mux->collect, pad);
+  gst_collect_pads2_remove_pad (mux->collect, pad);
   if (gst_element_remove_pad (element, pad))
     mux->num_streams--;
 }
@@ -2094,6 +2248,14 @@ gst_matroska_mux_track_header (GstMatroskaMux * mux,
         context->language);
   }
 
+  /* FIXME: until we have a nice way of getting the codecname
+   * out of the caps, I'm not going to enable this. Too much
+   * (useless, double, boring) work... */
+  /* TODO: Use value from tags if any */
+  /*gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_CODECNAME,
+     context->codec_name); */
+  gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKNAME, context->name);
+
   /* type-specific stuff */
   switch (context->type) {
     case GST_MATROSKA_TRACK_TYPE_VIDEO:{
@@ -2144,6 +2306,24 @@ gst_matroska_mux_track_header (GstMatroskaMux * mux,
       break;
     }
 
+      /* this is what we write for now and must be filled
+       * and remainder void'ed later on */
+#define SUBTITLE_DUMMY_SIZE   (1 + 1 + 14 + 1 + 2 + SUBTITLE_MAX_CODEC_PRIVATE)
+
+    case GST_MATROSKA_TRACK_TYPE_SUBTITLE:{
+      gpointer buf;
+
+      context->pos = ebml->pos;
+      /* CodecID is mandatory ... */
+      gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_CODECID, "S_SUB_UNKNOWN");
+      /* reserve space */
+      buf = g_malloc0 (SUBTITLE_MAX_CODEC_PRIVATE);
+      gst_ebml_write_binary (ebml, GST_EBML_ID_VOID, buf,
+          SUBTITLE_MAX_CODEC_PRIVATE);
+      g_free (buf);
+      /* real data has to be written at finish */
+      return;
+    }
     default:
       /* doesn't need type-specific data */
       break;
@@ -2153,15 +2333,112 @@ gst_matroska_mux_track_header (GstMatroskaMux * mux,
   if (context->codec_priv)
     gst_ebml_write_binary (ebml, GST_MATROSKA_ID_CODECPRIVATE,
         context->codec_priv, context->codec_priv_size);
-  /* FIXME: until we have a nice way of getting the codecname
-   * out of the caps, I'm not going to enable this. Too much
-   * (useless, double, boring) work... */
-  /* TODO: Use value from tags if any */
-  /*gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_CODECNAME,
-     context->codec_name); */
-  gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_TRACKNAME, context->name);
 }
 
+static void
+gst_matroska_mux_write_chapter_title (const gchar * title, GstEbmlWrite * ebml)
+{
+  guint64 title_master;
+
+  title_master =
+      gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CHAPTERDISPLAY);
+
+  gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_CHAPSTRING, title);
+  gst_ebml_write_ascii (ebml, GST_MATROSKA_ID_CHAPLANGUAGE,
+      GST_MATROSKA_MUX_CHAPLANG);
+
+  gst_ebml_write_master_finish (ebml, title_master);
+}
+
+static void
+gst_matroska_mux_write_chapter (GstMatroskaMux * mux, GstTocEntry * edition,
+    GstTocEntry * entry, GstEbmlWrite * ebml, guint64 * master_chapters,
+    guint64 * master_edition)
+{
+  guint64 uid, master_chapteratom;
+  GList *cur;
+  GstTocEntry *cur_entry;
+  guint count, i;
+  gchar *title;
+  gint64 start, stop;
+
+  if (G_UNLIKELY (master_chapters != NULL && *master_chapters == 0))
+    *master_chapters =
+        gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CHAPTERS);
+
+  if (G_UNLIKELY (master_edition != NULL && *master_edition == 0)) {
+    /* create uid for the parent */
+    uid = gst_matroska_mux_create_uid ();
+    g_free (edition->uid);
+    edition->uid = g_strdup_printf ("%" G_GUINT64_FORMAT, uid);
+
+    *master_edition =
+        gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_EDITIONENTRY);
+
+    gst_ebml_write_uint (ebml, GST_MATROSKA_ID_EDITIONUID, uid);
+    gst_ebml_write_uint (ebml, GST_MATROSKA_ID_EDITIONFLAGHIDDEN, 0);
+    gst_ebml_write_uint (ebml, GST_MATROSKA_ID_EDITIONFLAGDEFAULT, 0);
+    gst_ebml_write_uint (ebml, GST_MATROSKA_ID_EDITIONFLAGORDERED, 0);
+  }
+
+  uid = gst_matroska_mux_create_uid ();
+  gst_toc_entry_get_start_stop (entry, &start, &stop);
+
+  master_chapteratom =
+      gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CHAPTERATOM);
+  g_free (entry->uid);
+  entry->uid = g_strdup_printf ("%" G_GUINT64_FORMAT, uid);
+  gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CHAPTERUID, uid);
+  gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CHAPTERTIMESTART, start);
+  gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CHAPTERTIMESTOP, stop);
+  gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CHAPTERFLAGHIDDEN, 0);
+  gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CHAPTERFLAGENABLED, 1);
+
+  cur = entry->subentries;
+  while (cur != NULL) {
+    cur_entry = cur->data;
+    gst_matroska_mux_write_chapter (mux, NULL, cur_entry, ebml, NULL, NULL);
+
+    cur = cur->next;
+  }
+
+  if (G_LIKELY (entry->tags != NULL)) {
+    count = gst_tag_list_get_tag_size (entry->tags, GST_TAG_TITLE);
+
+    for (i = 0; i < count; ++i) {
+      gst_tag_list_get_string_index (entry->tags, GST_TAG_TITLE, i, &title);
+      gst_matroska_mux_write_chapter_title (title, ebml);
+      g_free (title);
+    }
+
+    /* remove title tag */
+    if (G_LIKELY (count > 0))
+      gst_tag_list_remove_tag (entry->tags, GST_TAG_TITLE);
+  }
+
+  gst_ebml_write_master_finish (ebml, master_chapteratom);
+}
+
+static void
+gst_matroska_mux_write_chapter_edition (GstMatroskaMux * mux,
+    GstTocEntry * entry, GstEbmlWrite * ebml, guint64 * master_chapters)
+{
+  guint64 master_edition = 0;
+  GList *cur;
+  GstTocEntry *subentry;
+
+  cur = entry->subentries;
+  while (cur != NULL) {
+    subentry = cur->data;
+    gst_matroska_mux_write_chapter (mux, entry, subentry, ebml, master_chapters,
+        &master_edition);
+
+    cur = cur->next;
+  }
+
+  if (G_LIKELY (master_edition != 0))
+    gst_ebml_write_master_finish (ebml, master_edition);
+}
 
 /**
  * gst_matroska_mux_start:
@@ -2176,6 +2453,7 @@ gst_matroska_mux_start (GstMatroskaMux * mux)
   const gchar *doctype;
   guint32 seekhead_id[] = { GST_MATROSKA_ID_SEGMENTINFO,
     GST_MATROSKA_ID_TRACKS,
+    GST_MATROSKA_ID_CHAPTERS,
     GST_MATROSKA_ID_CUES,
     GST_MATROSKA_ID_TAGS,
     0
@@ -2188,6 +2466,30 @@ gst_matroska_mux_start (GstMatroskaMux * mux)
   guint32 segment_uid[4];
   GTimeVal time = { 0, 0 };
 
+  /* 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 (!strcmp (mux->doctype, GST_MATROSKA_DOCTYPE_WEBM)) {
     ebml->caps = gst_caps_new_empty_simple ("video/webm");
   } else {
@@ -2229,7 +2531,7 @@ gst_matroska_mux_start (GstMatroskaMux * mux)
     if (tags != NULL && !gst_tag_list_is_empty (tags)) {
       guint64 master_tags, master_tag;
 
-      GST_DEBUG ("Writing tags");
+      GST_DEBUG_OBJECT (mux, "Writing tags");
 
       /* TODO: maybe limit via the TARGETS id by looking at the source pad */
       mux->tags_pos = ebml->pos;
@@ -2303,7 +2605,7 @@ gst_matroska_mux_start (GstMatroskaMux * mux)
       child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TRACKENTRY);
       gst_matroska_mux_track_header (mux, collect_pad->track);
       gst_ebml_write_master_finish (ebml, child);
-      /* some remaing pad/track setup */
+      /* some remaining pad/track setup */
       collect_pad->default_duration_scaled =
           gst_util_uint64_scale (collect_pad->track->default_duration,
           1, mux->time_scale);
@@ -2311,6 +2613,68 @@ gst_matroska_mux_start (GstMatroskaMux * mux)
   }
   gst_ebml_write_master_finish (ebml, master);
 
+  /* chapters */
+  if (gst_toc_setter_get_toc (GST_TOC_SETTER (mux)) != NULL && !mux->streamable) {
+    guint64 master_chapters = 0;
+    GstTocEntry *toc_entry;
+    const GstToc *toc;
+    GList *cur, *to_write = NULL;
+    gint64 start, stop;
+
+    GST_DEBUG ("Writing chapters");
+
+    toc = gst_toc_setter_get_toc (GST_TOC_SETTER (mux));
+
+    /* check whether we have editions or chapters at the root level */
+    toc_entry = toc->entries->data;
+
+    if (toc_entry->type != GST_TOC_ENTRY_TYPE_EDITION) {
+      toc_entry = gst_toc_entry_new (GST_TOC_ENTRY_TYPE_EDITION, "");
+      gst_toc_entry_set_start_stop (toc_entry, -1, -1);
+
+      /* aggregate all chapters without root edition */
+      cur = toc->entries;
+      while (cur != NULL) {
+        toc_entry->subentries =
+            g_list_prepend (toc_entry->subentries, cur->data);
+        cur = cur->next;
+      }
+
+      gst_toc_entry_get_start_stop (((GstTocEntry *) toc_entry->
+              subentries->data), &start, NULL);
+      toc_entry->subentries = g_list_reverse (toc_entry->subentries);
+      gst_toc_entry_get_start_stop (((GstTocEntry *) toc_entry->
+              subentries->data), NULL, &stop);
+      gst_toc_entry_set_start_stop (toc_entry, start, stop);
+
+      to_write = g_list_append (to_write, toc_entry);
+    } else {
+      toc_entry = NULL;
+      to_write = toc->entries;
+    }
+
+    /* finally write chapters */
+    mux->chapters_pos = ebml->pos;
+
+    cur = to_write;
+    while (cur != NULL) {
+      gst_matroska_mux_write_chapter_edition (mux, cur->data, ebml,
+          &master_chapters);
+      cur = cur->next;
+    }
+
+    /* close master element if any edition was written */
+    if (G_LIKELY (master_chapters != 0))
+      gst_ebml_write_master_finish (ebml, master_chapters);
+
+    if (toc_entry != NULL) {
+      g_list_free (toc_entry->subentries);
+      toc_entry->subentries = NULL;
+      gst_toc_entry_free (toc_entry);
+      g_list_free (to_write);
+    }
+  }
+
   /* lastly, flush the cache */
   gst_ebml_write_flush_cache (ebml, FALSE, 0);
 }
@@ -2374,6 +2738,44 @@ gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag,
   }
 }
 
+static void
+gst_matroska_mux_write_toc_entry_tags (GstMatroskaMux * mux,
+    const GstTocEntry * entry, guint64 * master_tags)
+{
+  guint64 master_tag, master_targets;
+  GstEbmlWrite *ebml;
+  GList *cur;
+
+  ebml = mux->ebml_write;
+
+  if (G_UNLIKELY (entry->tags != NULL && !gst_tag_list_is_empty (entry->tags))) {
+    if (*master_tags == 0) {
+      mux->tags_pos = ebml->pos;
+      *master_tags = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAGS);
+    }
+
+    master_tag = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAG);
+    master_targets =
+        gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TARGETS);
+
+    if (entry->type == GST_TOC_ENTRY_TYPE_EDITION)
+      gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TARGETEDITIONUID,
+          g_ascii_strtoull (entry->uid, NULL, 10));
+    else
+      gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TARGETCHAPTERUID,
+          g_ascii_strtoull (entry->uid, NULL, 10));
+
+    gst_ebml_write_master_finish (ebml, master_targets);
+    gst_tag_list_foreach (entry->tags, gst_matroska_mux_write_simple_tag, ebml);
+    gst_ebml_write_master_finish (ebml, master_tag);
+  }
+
+  cur = entry->subentries;
+  while (cur != NULL) {
+    gst_matroska_mux_write_toc_entry_tags (mux, cur->data, master_tags);
+    cur = cur->next;
+  }
+}
 
 /**
  * gst_matroska_mux_finish:
@@ -2427,22 +2829,45 @@ gst_matroska_mux_finish (GstMatroskaMux * mux)
   /* tags */
   tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (mux));
 
-  if (tags != NULL && !gst_tag_list_is_empty (tags)) {
-    guint64 master_tags, master_tag;
+  if ((tags != NULL && !gst_tag_list_is_empty (tags))
+      || gst_toc_setter_get_toc (GST_TOC_SETTER (mux)) != NULL) {
+    guint64 master_tags = 0, master_tag;
+    GList *cur;
+    const GstToc *toc;
 
-    GST_DEBUG ("Writing tags");
+    GST_DEBUG_OBJECT (mux, "Writing tags");
 
-    /* TODO: maybe limit via the TARGETS id by looking at the source pad */
-    mux->tags_pos = ebml->pos;
-    master_tags = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAGS);
-    master_tag = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAG);
-    gst_tag_list_foreach (tags, gst_matroska_mux_write_simple_tag, ebml);
-    gst_ebml_write_master_finish (ebml, master_tag);
-    gst_ebml_write_master_finish (ebml, master_tags);
+    toc = gst_toc_setter_get_toc (GST_TOC_SETTER (mux));
+
+    if (tags != NULL) {
+      /* TODO: maybe limit via the TARGETS id by looking at the source pad */
+      mux->tags_pos = ebml->pos;
+      master_tags = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAGS);
+      master_tag = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_TAG);
+
+      if (tags != NULL)
+        gst_tag_list_foreach (tags, gst_matroska_mux_write_simple_tag, ebml);
+      if (toc != NULL)
+        gst_tag_list_foreach (toc->tags, gst_matroska_mux_write_simple_tag,
+            ebml);
+
+      gst_ebml_write_master_finish (ebml, master_tag);
+    }
+
+    if (toc != NULL) {
+      cur = toc->entries;
+      while (cur != NULL) {
+        gst_matroska_mux_write_toc_entry_tags (mux, cur->data, &master_tags);
+        cur = cur->next;
+      }
+    }
+
+    if (master_tags != 0)
+      gst_ebml_write_master_finish (ebml, master_tags);
   }
 
   /* update seekhead. We know that:
-   * - a seekhead contains 4 entries.
+   * - a seekhead contains 5 entries.
    * - order of entries is as above.
    * - a seekhead has a 4-byte header + 8-byte length
    * - each entry is 2-byte master, 2-byte ID pointer,
@@ -2455,9 +2880,10 @@ gst_matroska_mux_finish (GstMatroskaMux * mux)
       mux->info_pos - mux->segment_master);
   gst_ebml_replace_uint (ebml, mux->seekhead_pos + 60,
       mux->tracks_pos - mux->segment_master);
-  if (mux->index != NULL) {
+  if (gst_toc_setter_get_toc (GST_TOC_SETTER (mux)) != NULL
+      && mux->chapters_pos > 0) {
     gst_ebml_replace_uint (ebml, mux->seekhead_pos + 88,
-        mux->cues_pos - mux->segment_master);
+        mux->chapters_pos - mux->segment_master);
   } else {
     /* void'ify */
     guint64 my_pos = ebml->pos;
@@ -2466,9 +2892,9 @@ gst_matroska_mux_finish (GstMatroskaMux * mux)
     gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
     gst_ebml_write_seek (ebml, my_pos);
   }
-  if (tags != NULL) {
+  if (mux->index != NULL) {
     gst_ebml_replace_uint (ebml, mux->seekhead_pos + 116,
-        mux->tags_pos - mux->segment_master);
+        mux->cues_pos - mux->segment_master);
   } else {
     /* void'ify */
     guint64 my_pos = ebml->pos;
@@ -2478,16 +2904,35 @@ gst_matroska_mux_finish (GstMatroskaMux * mux)
     gst_ebml_write_seek (ebml, my_pos);
   }
 
-  /* update duration */
-  /* first get the overall duration */
-  /* a released track may have left a duration in here */
+  if (tags != NULL) {
+    gst_ebml_replace_uint (ebml, mux->seekhead_pos + 144,
+        mux->tags_pos - mux->segment_master);
+  } else {
+    /* void'ify */
+    guint64 my_pos = ebml->pos;
+
+    gst_ebml_write_seek (ebml, mux->seekhead_pos + 124);
+    gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
+    gst_ebml_write_seek (ebml, my_pos);
+  }
+
+  /* loop tracks:
+   * - first get the overall duration
+   *   (a released track may have left a duration in here)
+   * - write some track header data for subtitles
+   */
   duration = mux->duration;
+  pos = ebml->pos;
   for (collected = mux->collect->data; collected;
       collected = g_slist_next (collected)) {
     GstMatroskaPad *collect_pad;
     GstClockTime min_duration;  /* observed minimum duration */
+    GstMatroskaTrackContext *context;
+    gint voidleft = 0, fill = 0;
+    gpointer codec_id;
 
     collect_pad = (GstMatroskaPad *) collected->data;
+    context = collect_pad->track;
 
     GST_DEBUG_OBJECT (mux,
         "Pad %" GST_PTR_FORMAT " start ts %" GST_TIME_FORMAT
@@ -2509,7 +2954,41 @@ gst_matroska_mux_finish (GstMatroskaMux * mux)
     if (GST_CLOCK_TIME_IS_VALID (collect_pad->duration) &&
         duration < collect_pad->duration)
       duration = collect_pad->duration;
-  }
+
+    if (context->type != GST_MATROSKA_TRACK_TYPE_SUBTITLE || !context->pos)
+      continue;
+
+  again:
+    /* write subtitle type and possible private data */
+    gst_ebml_write_seek (ebml, context->pos);
+    /* complex way to write ascii to account for extra filling */
+    codec_id = g_malloc0 (strlen (context->codec_id) + 1 + fill);
+    strcpy (codec_id, context->codec_id);
+    gst_ebml_write_binary (ebml, GST_MATROSKA_ID_CODECID,
+        codec_id, strlen (context->codec_id) + 1 + fill);
+    g_free (codec_id);
+    if (context->codec_priv)
+      gst_ebml_write_binary (ebml, GST_MATROSKA_ID_CODECPRIVATE,
+          context->codec_priv, context->codec_priv_size);
+    voidleft = SUBTITLE_DUMMY_SIZE - (ebml->pos - context->pos);
+    /* void'ify; sigh, variable sized length field */
+    if (voidleft == 1) {
+      fill = 1;
+      goto again;
+    } else if (voidleft && voidleft <= 128)
+      gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, voidleft - 2);
+    else if (voidleft >= 130)
+      gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, voidleft - 3);
+    else if (voidleft == 129) {
+      gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 64);
+      gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 63);
+    }
+  }
+
+  /* seek back (optional, but do anyway) */
+  gst_ebml_write_seek (ebml, pos);
+
+  /* update duration */
   if (duration != 0) {
     GST_DEBUG_OBJECT (mux, "final total duration: %" GST_TIME_FORMAT,
         GST_TIME_ARGS (duration));
@@ -2532,77 +3011,6 @@ gst_matroska_mux_finish (GstMatroskaMux * mux)
   gst_ebml_write_master_finish (ebml, mux->segment_pos);
 }
 
-
-/**
- * gst_matroska_mux_best_pad:
- * @mux: #GstMatroskaMux
- * @popped: True if at least one buffer was popped from #GstCollectPads
- *
- * Find a pad with the oldest data
- * (data from this pad should be written first).
- *
- * Returns: Selected pad.
- */
-static GstMatroskaPad *
-gst_matroska_mux_best_pad (GstMatroskaMux * mux, gboolean * popped)
-{
-  GSList *collected;
-  GstMatroskaPad *best = NULL;
-
-  *popped = FALSE;
-  for (collected = mux->collect->data; collected;
-      collected = g_slist_next (collected)) {
-    GstMatroskaPad *collect_pad;
-
-    collect_pad = (GstMatroskaPad *) collected->data;
-    /* fetch a new buffer if needed */
-    if (collect_pad->buffer == NULL) {
-      collect_pad->buffer = gst_collect_pads_pop (mux->collect,
-          (GstCollectData *) collect_pad);
-
-      if (collect_pad->buffer != NULL) {
-        GstClockTime time;
-
-        *popped = TRUE;
-        /* convert to running time */
-        time = GST_BUFFER_TIMESTAMP (collect_pad->buffer);
-        /* invalid should pass */
-        if (G_LIKELY (GST_CLOCK_TIME_IS_VALID (time))) {
-          time = gst_segment_to_running_time (&collect_pad->collect.segment,
-              GST_FORMAT_TIME, time);
-          if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (time))) {
-            GST_DEBUG_OBJECT (mux, "clipping buffer on pad %s outside segment",
-                GST_PAD_NAME (collect_pad->collect.pad));
-            gst_buffer_unref (collect_pad->buffer);
-            collect_pad->buffer = NULL;
-            return NULL;
-          } else {
-            GST_LOG_OBJECT (mux, "buffer ts %" GST_TIME_FORMAT " -> %"
-                GST_TIME_FORMAT " running time",
-                GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (collect_pad->buffer)),
-                GST_TIME_ARGS (time));
-            collect_pad->buffer =
-                gst_buffer_make_writable (collect_pad->buffer);
-            GST_BUFFER_TIMESTAMP (collect_pad->buffer) = time;
-          }
-        }
-      }
-    }
-
-    /* if we have a buffer check if it is better then the current best one */
-    if (collect_pad->buffer != NULL) {
-      if (best == NULL || !GST_BUFFER_TIMESTAMP_IS_VALID (collect_pad->buffer)
-          || (GST_BUFFER_TIMESTAMP_IS_VALID (best->buffer)
-              && GST_BUFFER_TIMESTAMP (collect_pad->buffer) <
-              GST_BUFFER_TIMESTAMP (best->buffer))) {
-        best = collect_pad;
-      }
-    }
-  }
-
-  return best;
-}
-
 /**
  * gst_matroska_mux_buffer_header:
  * @track: Track context.
@@ -2642,18 +3050,20 @@ gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux,
 {
   GstMatroskaTrackVideoContext *ctx =
       (GstMatroskaTrackVideoContext *) collect_pad->track;
-  guint8 *buf_data, *data;
+  GstMapInfo map;
+  guint8 *data;
   gsize size;
   guint8 parse_code;
   guint32 next_parse_offset;
   GstBuffer *ret = NULL;
   gboolean is_muxing_unit = FALSE;
 
-  buf_data = gst_buffer_map (buf, &size, NULL, GST_MAP_READ);
-  data = buf_data;
+  gst_buffer_map (buf, &map, GST_MAP_READ);
+  data = map.data;
+  size = map.size;
 
   if (size < 13) {
-    gst_buffer_unmap (buf, buf_data, -1);
+    gst_buffer_unmap (buf, &map);
     gst_buffer_unref (buf);
     return ret;
   }
@@ -2661,7 +3071,7 @@ gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux,
   /* Check if this buffer contains a picture or end-of-sequence packet */
   while (size >= 13) {
     if (GST_READ_UINT32_BE (data) != 0x42424344 /* 'BBCD' */ ) {
-      gst_buffer_unmap (buf, buf_data, -1);
+      gst_buffer_unmap (buf, &map);
       gst_buffer_unref (buf);
       return ret;
     }
@@ -2688,11 +3098,11 @@ gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux,
   }
 
   if (ctx->dirac_unit)
-    ctx->dirac_unit = gst_buffer_join (ctx->dirac_unit, gst_buffer_ref (buf));
+    ctx->dirac_unit = gst_buffer_append (ctx->dirac_unit, gst_buffer_ref (buf));
   else
     ctx->dirac_unit = gst_buffer_ref (buf);
 
-  gst_buffer_unmap (buf, buf_data, -1);
+  gst_buffer_unmap (buf, &map);
 
   if (is_muxing_unit) {
     ret = gst_buffer_make_writable (ctx->dirac_unit);
@@ -2727,7 +3137,7 @@ gst_matroska_mux_stop_streamheader (GstMatroskaMux * mux)
   s = gst_caps_get_structure (caps, 0);
   g_value_init (&streamheader, GST_TYPE_ARRAY);
   g_value_init (&bufval, GST_TYPE_BUFFER);
-  GST_BUFFER_FLAG_SET (streamheader_buffer, GST_BUFFER_FLAG_IN_CAPS);
+  GST_BUFFER_FLAG_SET (streamheader_buffer, GST_BUFFER_FLAG_HEADER);
   gst_value_set_buffer (&bufval, streamheader_buffer);
   gst_value_array_append_value (&streamheader, &bufval);
   g_value_unset (&bufval);
@@ -2748,10 +3158,11 @@ gst_matroska_mux_stop_streamheader (GstMatroskaMux * mux)
  * Returns: Result of the gst_pad_push issued to write the data.
  */
 static GstFlowReturn
-gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
+gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad,
+    GstBuffer * buf)
 {
   GstEbmlWrite *ebml = mux->ebml_write;
-  GstBuffer *buf, *hdr;
+  GstBuffer *hdr;
   guint64 blockgroup;
   gboolean write_duration;
   gint16 relative_timestamp;
@@ -2761,8 +3172,6 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
   GstMatroskamuxPad *pad;
 
   /* write data */
-  buf = collect_pad->buffer;
-  collect_pad->buffer = NULL;
   pad = GST_MATROSKAMUX_PAD_CAST (collect_pad->collect.pad);
 
   /* vorbis/theora headers are retrieved from caps and put in CodecPrivate */
@@ -2952,10 +3361,9 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
   }
 }
 
-
 /**
- * gst_matroska_mux_collected:
- * @pads: #GstCollectPads
+ * gst_matroska_mux_handle_buffer:
+ * @pads: #GstCollectPads2
  * @uuser_data: #GstMatroskaMux
  *
  * Collectpads callback.
@@ -2963,12 +3371,12 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
  * Returns: #GstFlowReturn
  */
 static GstFlowReturn
-gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data)
+gst_matroska_mux_handle_buffer (GstCollectPads2 * pads, GstCollectData2 * data,
+    GstBuffer * buf, gpointer user_data)
 {
   GstMatroskaMux *mux = GST_MATROSKA_MUX (user_data);
   GstEbmlWrite *ebml = mux->ebml_write;
   GstMatroskaPad *best;
-  gboolean popped;
   GstFlowReturn ret = GST_FLOW_OK;
 
   GST_DEBUG_OBJECT (mux, "Collected pads");
@@ -2987,53 +3395,53 @@ gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data)
     mux->state = GST_MATROSKA_MUX_STATE_DATA;
   }
 
-  do {
-    /* which stream to write from? */
-    best = gst_matroska_mux_best_pad (mux, &popped);
+  /* provided with stream to write from */
+  best = (GstMatroskaPad *) data;
 
-    /* if there is no best pad, we have reached EOS */
-    if (best == NULL) {
-      /* buffer popped, but none returned means it was clipped */
-      if (popped)
-        break;
-      GST_DEBUG_OBJECT (mux, "No best pad finishing...");
-      if (!mux->streamable) {
-        gst_matroska_mux_finish (mux);
-      } else {
-        GST_DEBUG_OBJECT (mux, "... but streamable, nothing to finish");
-      }
-      gst_pad_push_event (mux->srcpad, gst_event_new_eos ());
-      ret = GST_FLOW_UNEXPECTED;
-      break;
+  /* if there is no best pad, we have reached EOS */
+  if (best == NULL) {
+    GST_DEBUG_OBJECT (mux, "No best pad finishing...");
+    if (!mux->streamable) {
+      gst_matroska_mux_finish (mux);
+    } else {
+      GST_DEBUG_OBJECT (mux, "... but streamable, nothing to finish");
     }
-    GST_DEBUG_OBJECT (best->collect.pad, "best pad - buffer ts %"
-        GST_TIME_FORMAT " dur %" GST_TIME_FORMAT,
-        GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (best->buffer)),
-        GST_TIME_ARGS (GST_BUFFER_DURATION (best->buffer)));
+    gst_pad_push_event (mux->srcpad, gst_event_new_eos ());
+    ret = GST_FLOW_EOS;
+    goto exit;
+  }
 
-    /* make note of first and last encountered timestamps, so we can calculate
-     * the actual duration later when we send an updated header on eos */
-    if (GST_BUFFER_TIMESTAMP_IS_VALID (best->buffer)) {
-      GstClockTime start_ts = GST_BUFFER_TIMESTAMP (best->buffer);
-      GstClockTime end_ts = start_ts;
+  /* if we have a best stream, should also have a buffer */
+  g_assert (buf);
 
-      if (GST_BUFFER_DURATION_IS_VALID (best->buffer))
-        end_ts += GST_BUFFER_DURATION (best->buffer);
-      else if (best->track->default_duration)
-        end_ts += best->track->default_duration;
+  GST_DEBUG_OBJECT (best->collect.pad, "best pad - buffer ts %"
+      GST_TIME_FORMAT " dur %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buf)),
+      GST_TIME_ARGS (GST_BUFFER_DURATION (buf)));
 
-      if (!GST_CLOCK_TIME_IS_VALID (best->end_ts) || end_ts > best->end_ts)
-        best->end_ts = end_ts;
+  /* make note of first and last encountered timestamps, so we can calculate
+   * the actual duration later when we send an updated header on eos */
+  if (GST_BUFFER_TIMESTAMP_IS_VALID (buf)) {
+    GstClockTime start_ts = GST_BUFFER_TIMESTAMP (buf);
+    GstClockTime end_ts = start_ts;
 
-      if (G_UNLIKELY (best->start_ts == GST_CLOCK_TIME_NONE ||
-              start_ts < best->start_ts))
-        best->start_ts = start_ts;
-    }
+    if (GST_BUFFER_DURATION_IS_VALID (buf))
+      end_ts += GST_BUFFER_DURATION (buf);
+    else if (best->track->default_duration)
+      end_ts += best->track->default_duration;
+
+    if (!GST_CLOCK_TIME_IS_VALID (best->end_ts) || end_ts > best->end_ts)
+      best->end_ts = end_ts;
+
+    if (G_UNLIKELY (best->start_ts == GST_CLOCK_TIME_NONE ||
+            start_ts < best->start_ts))
+      best->start_ts = start_ts;
+  }
 
-    /* write one buffer */
-    ret = gst_matroska_mux_write_data (mux, best);
-  } while (ret == GST_FLOW_OK && !popped);
+  /* write one buffer */
+  ret = gst_matroska_mux_write_data (mux, best, buf);
 
+exit:
   return ret;
 }
 
@@ -3057,12 +3465,12 @@ gst_matroska_mux_change_state (GstElement * element, GstStateChange transition)
     case GST_STATE_CHANGE_NULL_TO_READY:
       break;
     case GST_STATE_CHANGE_READY_TO_PAUSED:
-      gst_collect_pads_start (mux->collect);
+      gst_collect_pads2_start (mux->collect);
       break;
     case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
       break;
     case GST_STATE_CHANGE_PAUSED_TO_READY:
-      gst_collect_pads_stop (mux->collect);
+      gst_collect_pads2_stop (mux->collect);
       break;
     default:
       break;