make %u in all request pad templates
[platform/upstream/gst-plugins-good.git] / gst / matroska / matroska-mux.c
index ee4b45c..d48d56c 100644 (file)
 #endif
 
 #include <math.h>
+#include <stdio.h>
 #include <string.h>
 
+#include <gst/riff/riff-media.h>
+#include <gst/tag/tag.h>
+
 #include "matroska-mux.h"
 #include "matroska-ids.h"
 
@@ -58,11 +62,18 @@ enum
 {
   ARG_0,
   ARG_WRITING_APP,
-  ARG_MATROSKA_VERSION
+  ARG_DOCTYPE_VERSION,
+  ARG_MIN_INDEX_INTERVAL,
+  ARG_STREAMABLE
 };
 
-#define  DEFAULT_MATROSKA_VERSION        1
+#define  DEFAULT_DOCTYPE_VERSION         2
 #define  DEFAULT_WRITING_APP             "GStreamer Matroska muxer"
+#define  DEFAULT_MIN_INDEX_INTERVAL      0
+#define  DEFAULT_STREAMABLE              FALSE
+
+/* WAVEFORMATEX is gst_riff_strf_auds + an extra guint16 extension size */
+#define WAVEFORMATEX_SIZE  (2 + sizeof (gst_riff_strf_auds))
 
 static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
     GST_PAD_SRC,
@@ -79,19 +90,19 @@ static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
   "width = (int) [ 16, 4096 ], " \
   "height = (int) [ 16, 4096 ] "
 
-/* FIXME: 
+/* FIXME:
  * * require codec data, etc as needed
  */
 
 static GstStaticPadTemplate videosink_templ =
-    GST_STATIC_PAD_TEMPLATE ("video_%d",
+    GST_STATIC_PAD_TEMPLATE ("video_%u",
     GST_PAD_SINK,
     GST_PAD_REQUEST,
     GST_STATIC_CAPS ("video/mpeg, "
         "mpegversion = (int) { 1, 2, 4 }, "
         "systemstream = (boolean) false, "
         COMMON_VIDEO_CAPS "; "
-        "video/x-h264, "
+        "video/x-h264, stream-format=avc, alignment=au, "
         COMMON_VIDEO_CAPS "; "
         "video/x-divx, "
         COMMON_VIDEO_CAPS "; "
@@ -113,9 +124,12 @@ static GstStaticPadTemplate videosink_templ =
         "video/x-pn-realvideo, "
         "rmversion = (int) [1, 4], "
         COMMON_VIDEO_CAPS "; "
+        "video/x-vp8, "
+        COMMON_VIDEO_CAPS "; "
         "video/x-raw-yuv, "
         "format = (fourcc) { YUY2, I420, YV12, UYVY, AYUV }, "
-        COMMON_VIDEO_CAPS)
+        COMMON_VIDEO_CAPS "; "
+        "video/x-wmv, " "wmvversion = (int) [ 1, 3 ], " COMMON_VIDEO_CAPS)
     );
 
 #define COMMON_AUDIO_CAPS \
@@ -126,18 +140,23 @@ static GstStaticPadTemplate videosink_templ =
  * * require codec data, etc as needed
  */
 static GstStaticPadTemplate audiosink_templ =
-    GST_STATIC_PAD_TEMPLATE ("audio_%d",
+    GST_STATIC_PAD_TEMPLATE ("audio_%u",
     GST_PAD_SINK,
     GST_PAD_REQUEST,
     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 }, "
         COMMON_AUDIO_CAPS "; "
         "audio/x-ac3, "
         COMMON_AUDIO_CAPS "; "
+        "audio/x-eac3, "
+        COMMON_AUDIO_CAPS "; "
+        "audio/x-dts, "
+        COMMON_AUDIO_CAPS "; "
         "audio/x-vorbis, "
         COMMON_AUDIO_CAPS "; "
         "audio/x-flac, "
@@ -175,14 +194,21 @@ static GstStaticPadTemplate audiosink_templ =
         "width = (int) { 8, 16, 24 }, "
         "channels = (int) { 1, 2 }, " "rate = (int) [ 8000, 96000 ]; "
         "audio/x-pn-realaudio, "
-        "raversion = (int) { 1, 2, 8 }, " COMMON_AUDIO_CAPS ";")
+        "raversion = (int) { 1, 2, 8 }, " COMMON_AUDIO_CAPS "; "
+        "audio/x-wma, " "wmaversion = (int) [ 1, 3 ], "
+        "block_align = (int) [ 0, 65535 ], bitrate = (int) [ 0, 524288 ], "
+        COMMON_AUDIO_CAPS ";"
+        "audio/x-alaw, "
+        "channels = (int) {1, 2}, " "rate = (int) [ 8000, 192000 ]; "
+        "audio/x-mulaw, "
+        "channels = (int) {1, 2}, " "rate = (int) [ 8000, 192000 ]")
     );
 
 static GstStaticPadTemplate subtitlesink_templ =
-GST_STATIC_PAD_TEMPLATE ("subtitle_%d",
+GST_STATIC_PAD_TEMPLATE ("subtitle_%u",
     GST_PAD_SINK,
     GST_PAD_REQUEST,
-    GST_STATIC_CAPS_ANY);
+    GST_STATIC_CAPS ("subtitle/x-kate"));
 
 static GArray *used_uids;
 G_LOCK_DEFINE_STATIC (used_uids);
@@ -228,8 +254,13 @@ static gboolean vorbis_streamheader_to_codecdata (const GValue * streamheader,
     GstMatroskaTrackContext * context);
 static gboolean speex_streamheader_to_codecdata (const GValue * streamheader,
     GstMatroskaTrackContext * context);
+static gboolean kate_streamheader_to_codecdata (const GValue * streamheader,
+    GstMatroskaTrackContext * context);
 static gboolean flac_streamheader_to_codecdata (const GValue * streamheader,
     GstMatroskaTrackContext * context);
+static void
+gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag,
+    gpointer data);
 
 static void
 gst_matroska_mux_add_interfaces (GType type)
@@ -242,23 +273,6 @@ gst_matroska_mux_add_interfaces (GType type)
 static void
 gst_matroska_mux_base_init (gpointer g_class)
 {
-  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
-
-  gst_element_class_add_pad_template (element_class,
-      gst_static_pad_template_get (&videosink_templ));
-  gst_element_class_add_pad_template (element_class,
-      gst_static_pad_template_get (&audiosink_templ));
-  gst_element_class_add_pad_template (element_class,
-      gst_static_pad_template_get (&subtitlesink_templ));
-  gst_element_class_add_pad_template (element_class,
-      gst_static_pad_template_get (&src_templ));
-  gst_element_class_set_details_simple (element_class, "Matroska muxer",
-      "Codec/Muxer",
-      "Muxes video/audio/subtitle streams into a matroska stream",
-      "Ronald Bultje <rbultje@ronald.bitfreak.net>");
-
-  GST_DEBUG_CATEGORY_INIT (matroskamux_debug, "matroskamux", 0,
-      "Matroska muxer");
 }
 
 static void
@@ -270,6 +284,22 @@ gst_matroska_mux_class_init (GstMatroskaMuxClass * klass)
   gobject_class = (GObjectClass *) klass;
   gstelement_class = (GstElementClass *) klass;
 
+  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 (&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",
+      "Codec/Muxer",
+      "Muxes video/audio/subtitle streams into a matroska stream",
+      "GStreamer maintainers <gstreamer-devel@lists.sourceforge.net>");
+
+  GST_DEBUG_CATEGORY_INIT (matroskamux_debug, "matroskamux", 0,
+      "Matroska muxer");
+
   gobject_class->finalize = gst_matroska_mux_finalize;
 
   gobject_class->get_property = gst_matroska_mux_get_property;
@@ -278,11 +308,23 @@ gst_matroska_mux_class_init (GstMatroskaMuxClass * klass)
   g_object_class_install_property (gobject_class, ARG_WRITING_APP,
       g_param_spec_string ("writing-app", "Writing application.",
           "The name the application that creates the matroska file.",
-          NULL, G_PARAM_READWRITE));
-  g_object_class_install_property (gobject_class, ARG_MATROSKA_VERSION,
-      g_param_spec_int ("version", "Matroska version",
-          "This parameter determines what matroska features can be used.",
-          1, 2, DEFAULT_MATROSKA_VERSION, G_PARAM_READWRITE));
+          NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, ARG_DOCTYPE_VERSION,
+      g_param_spec_int ("version", "DocType version",
+          "This parameter determines what Matroska features can be used.",
+          1, 2, DEFAULT_DOCTYPE_VERSION,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, ARG_MIN_INDEX_INTERVAL,
+      g_param_spec_int64 ("min-index-interval", "Minimum time between index "
+          "entries", "An index entry is created every so many nanoseconds.",
+          0, G_MAXINT64, DEFAULT_MIN_INDEX_INTERVAL,
+          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (gobject_class, ARG_STREAMABLE,
+      g_param_spec_boolean ("streamable", "Determines whether output should "
+          "be 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 | G_PARAM_STATIC_STRINGS));
 
   gstelement_class->change_state =
       GST_DEBUG_FUNCPTR (gst_matroska_mux_change_state);
@@ -303,7 +345,12 @@ gst_matroska_mux_class_init (GstMatroskaMuxClass * klass)
 static void
 gst_matroska_mux_init (GstMatroskaMux * mux, GstMatroskaMuxClass * g_class)
 {
-  mux->srcpad = gst_pad_new_from_static_template (&src_templ, "src");
+  GstPadTemplate *templ;
+
+  templ =
+      gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "src");
+  mux->srcpad = gst_pad_new_from_template (templ, "src");
+
   gst_pad_set_event_function (mux->srcpad, gst_matroska_mux_handle_src_event);
   gst_element_add_pad (GST_ELEMENT (mux), mux->srcpad);
 
@@ -313,10 +360,13 @@ gst_matroska_mux_init (GstMatroskaMux * mux, GstMatroskaMuxClass * g_class)
       mux);
 
   mux->ebml_write = gst_ebml_write_new (mux->srcpad);
+  mux->doctype = GST_MATROSKA_DOCTYPE_MATROSKA;
 
   /* property defaults */
-  mux->matroska_version = DEFAULT_MATROSKA_VERSION;
+  mux->doctype_version = DEFAULT_DOCTYPE_VERSION;
   mux->writing_app = g_strdup (DEFAULT_WRITING_APP);
+  mux->min_index_interval = DEFAULT_MIN_INDEX_INTERVAL;
+  mux->streamable = DEFAULT_STREAMABLE;
 
   /* initialize internal variables */
   mux->index = NULL;
@@ -341,6 +391,8 @@ gst_matroska_mux_finalize (GObject * object)
 {
   GstMatroskaMux *mux = GST_MATROSKA_MUX (object);
 
+  gst_event_replace (&mux->force_key_unit_event, NULL);
+
   gst_object_unref (mux->collect);
   gst_object_unref (mux->ebml_write);
   if (mux->writing_app)
@@ -447,7 +499,7 @@ gst_matroska_pad_reset (GstMatroskaPad * collect_pad, gboolean full)
         break;
       default:
         g_assert_not_reached ();
-        break;
+        return;
     }
 
     context->type = type;
@@ -497,10 +549,8 @@ gst_matroska_mux_reset (GstElement * element)
 
   for (walk = mux->collect->data; walk; walk = g_slist_next (walk)) {
     GstMatroskaPad *collect_pad;
-    GstPad *thepad;
 
     collect_pad = (GstMatroskaPad *) walk->data;
-    thepad = collect_pad->collect.pad;
 
     /* reset collect pad to pristine state */
     gst_matroska_pad_reset (collect_pad, FALSE);
@@ -513,12 +563,14 @@ gst_matroska_mux_reset (GstElement * element)
 
   /* reset timers */
   mux->time_scale = GST_MSECOND;
+  mux->max_cluster_duration = G_MAXINT16 * mux->time_scale;
   mux->duration = 0;
 
   /* reset cluster */
   mux->cluster = 0;
   mux->cluster_time = 0;
   mux->cluster_pos = 0;
+  mux->prev_cluster_size = 0;
 
   /* reset tags */
   gst_tag_setter_reset_tags (GST_TAG_SETTER (mux));
@@ -529,7 +581,7 @@ gst_matroska_mux_reset (GstElement * element)
  * @pad: Pad which received the event.
  * @event: Received event.
  *
- * handle events - copied from oggmux without understanding 
+ * handle events - copied from oggmux without understanding
  *
  * Returns: #TRUE on success.
  */
@@ -572,7 +624,9 @@ gst_matroska_mux_handle_sink_event (GstPad * pad, GstEvent * event)
   mux = GST_MATROSKA_MUX (gst_pad_get_parent (pad));
 
   switch (GST_EVENT_TYPE (event)) {
-    case GST_EVENT_TAG:
+    case GST_EVENT_TAG:{
+      gchar *lang = NULL;
+
       GST_DEBUG_OBJECT (mux, "received tag event");
       gst_event_parse_tag (event, &list);
 
@@ -580,26 +634,61 @@ gst_matroska_mux_handle_sink_event (GstPad * pad, GstEvent * event)
       g_assert (collect_pad);
       context = collect_pad->track;
       g_assert (context);
-      /* FIXME ?
-       * strictly speaking, the incoming language code may only be 639-1, so not
-       * 639-2 according to matroska specs, but it will have to do for now */
-      gst_tag_list_get_string (list, GST_TAG_LANGUAGE_CODE, &context->language);
 
+      /* 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;
+
+        lang_code = gst_tag_get_language_code_iso_639_2B (lang);
+        if (lang_code) {
+          GST_INFO_OBJECT (pad, "Setting language to '%s'", lang_code);
+          context->language = g_strdup (lang_code);
+        } else {
+          GST_WARNING_OBJECT (pad, "Did not get language code for '%s'", lang);
+        }
+        g_free (lang);
+      }
+
+      /* FIXME: what about stream-specific tags? */
       gst_tag_setter_merge_tags (GST_TAG_SETTER (mux), list,
           gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (mux)));
-      break;
-    case GST_EVENT_NEWSEGMENT:
-      /* We don't support NEWSEGMENT events */
-      ret = FALSE;
+
       gst_event_unref (event);
+      /* handled this, don't want collectpads to forward it downstream */
+      event = NULL;
+      break;
+    }
+    case GST_EVENT_NEWSEGMENT:{
+      GstFormat format;
+
+      gst_event_parse_new_segment (event, NULL, NULL, &format, NULL, NULL,
+          NULL);
+      if (format != GST_FORMAT_TIME) {
+        ret = FALSE;
+        gst_event_unref (event);
+        event = NULL;
+      }
       break;
+    }
+    case GST_EVENT_CUSTOM_DOWNSTREAM:{
+      const GstStructure *structure;
+
+      structure = gst_event_get_structure (event);
+      if (gst_structure_has_name (structure, "GstForceKeyUnit")) {
+        gst_event_replace (&mux->force_key_unit_event, NULL);
+        mux->force_key_unit_event = event;
+        event = NULL;
+      }
+      break;
+    }
     default:
       break;
   }
 
   /* now GstCollectPads can take care of the rest, e.g. EOS */
-  if (ret)
+  if (event)
     ret = mux->collect_event (pad, event);
+
   gst_object_unref (mux);
 
   return ret;
@@ -628,6 +717,7 @@ gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps)
   const GstBuffer *codec_buf = NULL;
   gint width, height, pixel_width, pixel_height;
   gint fps_d, fps_n;
+  gboolean interlaced = FALSE;
 
   mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
 
@@ -644,14 +734,21 @@ gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps)
 
   mimetype = gst_structure_get_name (structure);
 
+  if (gst_structure_get_boolean (structure, "interlaced", &interlaced)
+      && interlaced)
+    context->flags |= GST_MATROSKA_VIDEOTRACK_INTERLACED;
+
   if (!strcmp (mimetype, "video/x-theora")) {
     /* we'll extract the details later from the theora identification header */
     goto skip_details;
   }
 
   /* get general properties */
-  gst_structure_get_int (structure, "width", &width);
-  gst_structure_get_int (structure, "height", &height);
+  /* spec says it is mandatory */
+  if (!gst_structure_get_int (structure, "width", &width) ||
+      !gst_structure_get_int (structure, "height", &height))
+    goto refuse_caps;
+
   videocontext->pixel_width = width;
   videocontext->pixel_height = height;
   if (gst_structure_get_fraction (structure, "framerate", &fps_n, &fps_d)
@@ -699,20 +796,16 @@ skip_details:
   if (!strcmp (mimetype, "video/x-raw-yuv")) {
     context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_UNCOMPRESSED);
     gst_structure_get_fourcc (structure, "format", &videocontext->fourcc);
-
-    return TRUE;
-  } else if (!strcmp (mimetype, "image/jpeg")) {
-    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MJPEG);
-
-    return TRUE;
   } else if (!strcmp (mimetype, "video/x-xvid") /* MS/VfW compatibility cases */
       ||!strcmp (mimetype, "video/x-huffyuv")
       || !strcmp (mimetype, "video/x-divx")
       || !strcmp (mimetype, "video/x-dv")
       || !strcmp (mimetype, "video/x-h263")
-      || !strcmp (mimetype, "video/x-msmpeg")) {
-    BITMAPINFOHEADER *bih;
-    gint size = sizeof (BITMAPINFOHEADER);
+      || !strcmp (mimetype, "video/x-msmpeg")
+      || !strcmp (mimetype, "video/x-wmv")
+      || !strcmp (mimetype, "image/jpeg")) {
+    gst_riff_strf_vids *bih;
+    gint size = sizeof (gst_riff_strf_vids);
     guint32 fourcc = 0;
 
     if (!strcmp (mimetype, "video/x-xvid"))
@@ -753,35 +846,49 @@ skip_details:
           goto msmpeg43;
           break;
       }
+    } else if (!strcmp (mimetype, "video/x-wmv")) {
+      gint wmvversion;
+      guint32 format;
+      if (gst_structure_get_fourcc (structure, "format", &format)) {
+        fourcc = format;
+      } else if (gst_structure_get_int (structure, "wmvversion", &wmvversion)) {
+        if (wmvversion == 2) {
+          fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '2');
+        } else if (wmvversion == 1) {
+          fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '1');
+        } else if (wmvversion == 3) {
+          fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '3');
+        }
+      }
+    } else if (!strcmp (mimetype, "image/jpeg")) {
+      fourcc = GST_MAKE_FOURCC ('M', 'J', 'P', 'G');
     }
 
     if (!fourcc)
-      return FALSE;
-
-    bih = g_new0 (BITMAPINFOHEADER, 1);
-    GST_WRITE_UINT32_LE (&bih->bi_size, size);
-    GST_WRITE_UINT32_LE (&bih->bi_width, videocontext->pixel_width);
-    GST_WRITE_UINT32_LE (&bih->bi_height, videocontext->pixel_height);
-    GST_WRITE_UINT32_LE (&bih->bi_compression, fourcc);
-    GST_WRITE_UINT16_LE (&bih->bi_planes, (guint16) 1);
-    GST_WRITE_UINT16_LE (&bih->bi_bit_count, (guint16) 24);
-    GST_WRITE_UINT32_LE (&bih->bi_size_image, videocontext->pixel_width *
+      goto refuse_caps;
+
+    bih = g_new0 (gst_riff_strf_vids, 1);
+    GST_WRITE_UINT32_LE (&bih->size, size);
+    GST_WRITE_UINT32_LE (&bih->width, videocontext->pixel_width);
+    GST_WRITE_UINT32_LE (&bih->height, videocontext->pixel_height);
+    GST_WRITE_UINT32_LE (&bih->compression, fourcc);
+    GST_WRITE_UINT16_LE (&bih->planes, (guint16) 1);
+    GST_WRITE_UINT16_LE (&bih->bit_cnt, (guint16) 24);
+    GST_WRITE_UINT32_LE (&bih->image_size, videocontext->pixel_width *
         videocontext->pixel_height * 3);
 
     /* process codec private/initialization data, if any */
     if (codec_buf) {
       size += GST_BUFFER_SIZE (codec_buf);
       bih = g_realloc (bih, size);
-      GST_WRITE_UINT32_LE (&bih->bi_size, size);
-      memcpy ((guint8 *) bih + sizeof (BITMAPINFOHEADER),
+      GST_WRITE_UINT32_LE (&bih->size, size);
+      memcpy ((guint8 *) bih + sizeof (gst_riff_strf_vids),
           GST_BUFFER_DATA (codec_buf), GST_BUFFER_SIZE (codec_buf));
     }
 
     context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VFW_FOURCC);
     context->codec_priv = (gpointer) bih;
     context->codec_priv_size = size;
-
-    return TRUE;
   } else if (!strcmp (mimetype, "video/x-h264")) {
     context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC);
 
@@ -798,8 +905,6 @@ skip_details:
       memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf),
           context->codec_priv_size);
     }
-
-    return TRUE;
   } else if (!strcmp (mimetype, "video/x-theora")) {
     const GValue *streamheader;
 
@@ -815,13 +920,12 @@ skip_details:
     if (!theora_streamheader_to_codecdata (streamheader, context)) {
       GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
           ("theora stream headers missing or malformed"));
-      return FALSE;
+      goto refuse_caps;
     }
-    return TRUE;
   } else if (!strcmp (mimetype, "video/x-dirac")) {
     context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_DIRAC);
-
-    return TRUE;
+  } else if (!strcmp (mimetype, "video/x-vp8")) {
+    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_VP8);
   } else if (!strcmp (mimetype, "video/mpeg")) {
     gint mpegversion;
 
@@ -837,7 +941,7 @@ skip_details:
         context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_ASP);
         break;
       default:
-        return FALSE;
+        goto refuse_caps;
     }
 
     /* global headers may be in codec data */
@@ -847,14 +951,10 @@ skip_details:
       memcpy (context->codec_priv, GST_BUFFER_DATA (codec_buf),
           context->codec_priv_size);
     }
-
-    return TRUE;
   } else if (!strcmp (mimetype, "video/x-msmpeg")) {
   msmpeg43:
     /* can only make it here if preceding case verified it was version 3 */
     context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_MSMPEG4V3);
-
-    return TRUE;
   } else if (!strcmp (mimetype, "video/x-pn-realvideo")) {
     gint rmversion;
     const GValue *mdpr_data;
@@ -874,7 +974,7 @@ skip_details:
         context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_VIDEO_REALVIDEO4);
         break;
       default:
-        return FALSE;
+        goto refuse_caps;
     }
 
     mdpr_data = gst_structure_get_value (structure, "mdpr_data");
@@ -892,21 +992,29 @@ skip_details:
       context->codec_priv = priv_data;
       context->codec_priv_size = priv_data_size;
     }
-
-    return TRUE;
   }
 
-  return FALSE;
+  return TRUE;
+
+  /* ERRORS */
+refuse_caps:
+  {
+    GST_WARNING_OBJECT (mux, "pad %s refused caps %" GST_PTR_FORMAT,
+        GST_PAD_NAME (pad), caps);
+    return FALSE;
+  }
 }
 
+/* N > 0 to expect a particular number of headers, negative if the
+   number of headers is variable */
 static gboolean
-xiph3_streamheader_to_codecdata (const GValue * streamheader,
-    GstMatroskaTrackContext * context, GstBuffer ** p_buf0)
+xiphN_streamheader_to_codecdata (const GValue * streamheader,
+    GstMatroskaTrackContext * context, GstBuffer ** p_buf0, int N)
 {
-  GstBuffer *buf[3];
+  GstBuffer **buf = NULL;
   GArray *bufarr;
   guint8 *priv_data;
-  guint i, offset, priv_data_size;
+  guint bufi, i, offset, priv_data_size;
 
   if (streamheader == NULL)
     goto no_stream_headers;
@@ -915,44 +1023,51 @@ xiph3_streamheader_to_codecdata (const GValue * streamheader,
     goto wrong_type;
 
   bufarr = g_value_peek_pointer (streamheader);
-  if (bufarr->len != 3)
+  if (bufarr->len <= 0 || bufarr->len > 255)    /* at least one header, and count stored in a byte */
+    goto wrong_count;
+  if (N > 0 && bufarr->len != N)
     goto wrong_count;
 
   context->xiph_headers_to_skip = bufarr->len;
 
-  for (i = 0; i < 3; i++) {
+  buf = (GstBuffer **) g_malloc0 (sizeof (GstBuffer *) * bufarr->len);
+  for (i = 0; i < bufarr->len; i++) {
     GValue *bufval = &g_array_index (bufarr, GValue, i);
 
-    if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER)
+    if (G_VALUE_TYPE (bufval) != GST_TYPE_BUFFER) {
+      g_free (buf);
       goto wrong_content_type;
+    }
 
     buf[i] = g_value_peek_pointer (bufval);
   }
 
   priv_data_size = 1;
-  priv_data_size += GST_BUFFER_SIZE (buf[0]) / 0xff + 1;
-  priv_data_size += GST_BUFFER_SIZE (buf[1]) / 0xff + 1;
+  if (bufarr->len > 0) {
+    for (i = 0; i < bufarr->len - 1; i++) {
+      priv_data_size += GST_BUFFER_SIZE (buf[i]) / 0xff + 1;
+    }
+  }
 
-  for (i = 0; i < 3; ++i) {
+  for (i = 0; i < bufarr->len; ++i) {
     priv_data_size += GST_BUFFER_SIZE (buf[i]);
   }
 
   priv_data = g_malloc0 (priv_data_size);
 
-  priv_data[0] = 2;
+  priv_data[0] = bufarr->len - 1;
   offset = 1;
 
-  for (i = 0; i < GST_BUFFER_SIZE (buf[0]) / 0xff; ++i) {
-    priv_data[offset++] = 0xff;
-  }
-  priv_data[offset++] = GST_BUFFER_SIZE (buf[0]) % 0xff;
-
-  for (i = 0; i < GST_BUFFER_SIZE (buf[1]) / 0xff; ++i) {
-    priv_data[offset++] = 0xff;
+  if (bufarr->len > 0) {
+    for (bufi = 0; bufi < bufarr->len - 1; bufi++) {
+      for (i = 0; i < GST_BUFFER_SIZE (buf[bufi]) / 0xff; ++i) {
+        priv_data[offset++] = 0xff;
+      }
+      priv_data[offset++] = GST_BUFFER_SIZE (buf[bufi]) % 0xff;
+    }
   }
-  priv_data[offset++] = GST_BUFFER_SIZE (buf[1]) % 0xff;
 
-  for (i = 0; i < 3; ++i) {
+  for (i = 0; i < bufarr->len; ++i) {
     memcpy (priv_data + offset, GST_BUFFER_DATA (buf[i]),
         GST_BUFFER_SIZE (buf[i]));
     offset += GST_BUFFER_SIZE (buf[i]);
@@ -964,6 +1079,8 @@ xiph3_streamheader_to_codecdata (const GValue * streamheader,
   if (p_buf0)
     *p_buf0 = gst_buffer_ref (buf[0]);
 
+  g_free (buf);
+
   return TRUE;
 
 /* ERRORS */
@@ -980,7 +1097,7 @@ wrong_type:
   }
 wrong_count:
   {
-    GST_WARNING ("got %u streamheaders, not 3 as expected", bufarr->len);
+    GST_WARNING ("got %u streamheaders, not %d as expected", bufarr->len, N);
     return FALSE;
   }
 wrong_content_type:
@@ -996,7 +1113,7 @@ vorbis_streamheader_to_codecdata (const GValue * streamheader,
 {
   GstBuffer *buf0 = NULL;
 
-  if (!xiph3_streamheader_to_codecdata (streamheader, context, &buf0))
+  if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3))
     return FALSE;
 
   if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 4) {
@@ -1025,7 +1142,7 @@ theora_streamheader_to_codecdata (const GValue * streamheader,
 {
   GstBuffer *buf0 = NULL;
 
-  if (!xiph3_streamheader_to_codecdata (streamheader, context, &buf0))
+  if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, 3))
     return FALSE;
 
   if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 1 + 6 + 26) {
@@ -1077,6 +1194,27 @@ theora_streamheader_to_codecdata (const GValue * streamheader,
 }
 
 static gboolean
+kate_streamheader_to_codecdata (const GValue * streamheader,
+    GstMatroskaTrackContext * context)
+{
+  GstBuffer *buf0 = NULL;
+
+  if (!xiphN_streamheader_to_codecdata (streamheader, context, &buf0, -1))
+    return FALSE;
+
+  if (buf0 == NULL || GST_BUFFER_SIZE (buf0) < 64) {    /* Kate ID header is 64 bytes */
+    GST_WARNING ("First kate header too small, ignoring");
+  } else if (memcmp (GST_BUFFER_DATA (buf0), "\200kate\0\0\0", 8) != 0) {
+    GST_WARNING ("First header not a kate identification header, ignoring");
+  }
+
+  if (buf0)
+    gst_buffer_unref (buf0);
+
+  return TRUE;
+}
+
+static gboolean
 flac_streamheader_to_codecdata (const GValue * streamheader,
     GstMatroskaTrackContext * context)
 {
@@ -1207,10 +1345,10 @@ speex_streamheader_to_codecdata (const GValue * streamheader,
   return TRUE;
 }
 
-static gchar *
+static const gchar *
 aac_codec_data_to_codec_id (const GstBuffer * buf)
 {
-  gchar *result;
+  const gchar *result;
   gint profile;
 
   /* default to MAIN */
@@ -1262,6 +1400,9 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
   const gchar *mimetype;
   gint samplerate = 0, channels = 0;
   GstStructure *structure;
+  const GValue *codec_data = NULL;
+  const GstBuffer *buf = NULL;
+  const gchar *stream_format = NULL;
 
   mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
 
@@ -1285,6 +1426,10 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
   audiocontext->bitdepth = 0;
   context->default_duration = 0;
 
+  codec_data = gst_structure_get_value (structure, "codec_data");
+  if (codec_data)
+    buf = gst_value_get_buffer (codec_data);
+
   /* TODO: - check if we handle all codecs by the spec, i.e. codec private
    *         data and other settings
    *       - add new formats
@@ -1292,12 +1437,6 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
 
   if (!strcmp (mimetype, "audio/mpeg")) {
     gint mpegversion = 0;
-    const GValue *codec_data;
-    const GstBuffer *buf = NULL;
-
-    codec_data = gst_structure_get_value (structure, "codec_data");
-    if (codec_data)
-      buf = gst_value_get_buffer (codec_data);
 
     gst_structure_get_int (structure, "mpegversion", &mpegversion);
     switch (mpegversion) {
@@ -1337,35 +1476,43 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
             context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_MPEG1_L3);
             break;
           default:
-            return FALSE;
+            goto refuse_caps;
         }
         break;
       }
       case 2:
-        if (buf) {
-          context->codec_id =
-              g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG2 "%s",
-              aac_codec_data_to_codec_id (buf));
+      case 4:
+        stream_format = gst_structure_get_string (structure, "stream-format");
+        /* check this is raw aac */
+        if (stream_format) {
+          if (strcmp (stream_format, "raw") != 0) {
+            GST_WARNING_OBJECT (mux, "AAC stream-format must be 'raw', not %s",
+                stream_format);
+          }
         } else {
-          GST_DEBUG_OBJECT (mux, "no AAC codec_data; not packetized");
-          return FALSE;
+          GST_WARNING_OBJECT (mux, "AAC stream-format not specified, "
+              "assuming 'raw'");
         }
-        break;
-      case 4:
+
         if (buf) {
-          context->codec_id =
-              g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG4 "%s",
-              aac_codec_data_to_codec_id (buf));
+          if (mpegversion == 2)
+            context->codec_id =
+                g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG2 "%s",
+                aac_codec_data_to_codec_id (buf));
+          else if (mpegversion == 4)
+            context->codec_id =
+                g_strdup_printf (GST_MATROSKA_CODEC_ID_AUDIO_AAC_MPEG4 "%s",
+                aac_codec_data_to_codec_id (buf));
+          else
+            g_assert_not_reached ();
         } else {
           GST_DEBUG_OBJECT (mux, "no AAC codec_data; not packetized");
-          return FALSE;
+          goto refuse_caps;
         }
         break;
       default:
-        return FALSE;
+        goto refuse_caps;
     }
-
-    return TRUE;
   } else if (!strcmp (mimetype, "audio/x-raw-int")) {
     gint width, depth;
     gint endianness = G_LITTLE_ENDIAN;
@@ -1375,24 +1522,24 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
         !gst_structure_get_int (structure, "depth", &depth) ||
         !gst_structure_get_boolean (structure, "signed", &signedness)) {
       GST_DEBUG_OBJECT (mux, "broken caps, width/depth/signed field missing");
-      return FALSE;
+      goto refuse_caps;
     }
 
     if (depth > 8 &&
         !gst_structure_get_int (structure, "endianness", &endianness)) {
       GST_DEBUG_OBJECT (mux, "broken caps, no endianness specified");
-      return FALSE;
+      goto refuse_caps;
     }
 
     if (width != depth) {
       GST_DEBUG_OBJECT (mux, "width must be same as depth!");
-      return FALSE;
+      goto refuse_caps;
     }
 
     /* FIXME: where is this spec'ed out? (tpm) */
     if ((width == 8 && signedness) || (width >= 16 && !signedness)) {
       GST_DEBUG_OBJECT (mux, "8-bit PCM must be unsigned, 16-bit PCM signed");
-      return FALSE;
+      goto refuse_caps;
     }
 
     audiocontext->bitdepth = depth;
@@ -1401,19 +1548,17 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
     else
       context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_INT_LE);
 
-    return TRUE;
   } else if (!strcmp (mimetype, "audio/x-raw-float")) {
     gint width;
 
     if (!gst_structure_get_int (structure, "width", &width)) {
       GST_DEBUG_OBJECT (mux, "broken caps, width field missing");
-      return FALSE;
+      goto refuse_caps;
     }
 
     audiocontext->bitdepth = width;
     context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_PCM_FLOAT);
 
-    return TRUE;
   } else if (!strcmp (mimetype, "audio/x-vorbis")) {
     const GValue *streamheader;
 
@@ -1429,9 +1574,8 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
     if (!vorbis_streamheader_to_codecdata (streamheader, context)) {
       GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
           ("vorbis stream headers missing or malformed"));
-      return FALSE;
+      goto refuse_caps;
     }
-    return TRUE;
   } else if (!strcmp (mimetype, "audio/x-flac")) {
     const GValue *streamheader;
 
@@ -1446,9 +1590,8 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
     if (!flac_streamheader_to_codecdata (streamheader, context)) {
       GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
           ("flac stream headers missing or malformed"));
-      return FALSE;
+      goto refuse_caps;
     }
-    return TRUE;
   } else if (!strcmp (mimetype, "audio/x-speex")) {
     const GValue *streamheader;
 
@@ -1463,13 +1606,14 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
     if (!speex_streamheader_to_codecdata (streamheader, context)) {
       GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL),
           ("speex stream headers missing or malformed"));
-      return FALSE;
+      goto refuse_caps;
     }
-    return TRUE;
   } else if (!strcmp (mimetype, "audio/x-ac3")) {
     context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_AC3);
-
-    return TRUE;
+  } else if (!strcmp (mimetype, "audio/x-eac3")) {
+    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_EAC3);
+  } else if (!strcmp (mimetype, "audio/x-dts")) {
+    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_DTS);
   } else if (!strcmp (mimetype, "audio/x-tta")) {
     gint width;
 
@@ -1480,7 +1624,6 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
     audiocontext->bitdepth = width;
     context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_TTA);
 
-    return TRUE;
   } else if (!strcmp (mimetype, "audio/x-pn-realaudio")) {
     gint raversion;
     const GValue *mdpr_data;
@@ -1497,7 +1640,7 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
         context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_REAL_COOK);
         break;
       default:
-        return FALSE;
+        goto refuse_caps;
     }
 
     mdpr_data = gst_structure_get_value (structure, "mdpr_data");
@@ -1516,10 +1659,99 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
       context->codec_priv_size = priv_data_size;
     }
 
-    return TRUE;
+  } else if (!strcmp (mimetype, "audio/x-wma")
+      || !strcmp (mimetype, "audio/x-alaw")
+      || !strcmp (mimetype, "audio/x-mulaw")) {
+    guint8 *codec_priv;
+    guint codec_priv_size;
+    guint16 format = 0;
+    gint block_align;
+    gint bitrate;
+
+    if (samplerate == 0 || channels == 0) {
+      GST_WARNING_OBJECT (mux, "Missing channels/samplerate on caps");
+      goto refuse_caps;
+    }
+
+    if (!strcmp (mimetype, "audio/x-wma")) {
+      gint wmaversion;
+      gint depth;
+
+      if (!gst_structure_get_int (structure, "wmaversion", &wmaversion)
+          || !gst_structure_get_int (structure, "block_align", &block_align)
+          || !gst_structure_get_int (structure, "bitrate", &bitrate)) {
+        GST_WARNING_OBJECT (mux, "Missing wmaversion/block_align/bitrate"
+            " on WMA caps");
+        goto refuse_caps;
+      }
+
+      switch (wmaversion) {
+        case 1:
+          format = GST_RIFF_WAVE_FORMAT_WMAV1;
+          break;
+        case 2:
+          format = GST_RIFF_WAVE_FORMAT_WMAV2;
+          break;
+        case 3:
+          format = GST_RIFF_WAVE_FORMAT_WMAV3;
+          break;
+        default:
+          GST_WARNING_OBJECT (mux, "Unexpected WMA version: %d", wmaversion);
+          goto refuse_caps;
+      }
+
+      if (gst_structure_get_int (structure, "depth", &depth))
+        audiocontext->bitdepth = depth;
+    } else if (!strcmp (mimetype, "audio/x-alaw")
+        || !strcmp (mimetype, "audio/x-mulaw")) {
+      audiocontext->bitdepth = 8;
+      if (!strcmp (mimetype, "audio/x-alaw"))
+        format = GST_RIFF_WAVE_FORMAT_ALAW;
+      else
+        format = GST_RIFF_WAVE_FORMAT_MULAW;
+
+      block_align = channels;
+      bitrate = block_align * samplerate;
+    }
+    g_assert (format != 0);
+
+    codec_priv_size = WAVEFORMATEX_SIZE;
+    if (buf)
+      codec_priv_size += GST_BUFFER_SIZE (buf);
+
+    /* serialize waveformatex structure */
+    codec_priv = g_malloc0 (codec_priv_size);
+    GST_WRITE_UINT16_LE (codec_priv, format);
+    GST_WRITE_UINT16_LE (codec_priv + 2, channels);
+    GST_WRITE_UINT32_LE (codec_priv + 4, samplerate);
+    GST_WRITE_UINT32_LE (codec_priv + 8, bitrate / 8);
+    GST_WRITE_UINT16_LE (codec_priv + 12, block_align);
+    GST_WRITE_UINT16_LE (codec_priv + 14, 0);
+    if (buf)
+      GST_WRITE_UINT16_LE (codec_priv + 16, GST_BUFFER_SIZE (buf));
+    else
+      GST_WRITE_UINT16_LE (codec_priv + 16, 0);
+
+    /* process codec private/initialization data, if any */
+    if (buf) {
+      memcpy ((guint8 *) codec_priv + WAVEFORMATEX_SIZE,
+          GST_BUFFER_DATA (buf), GST_BUFFER_SIZE (buf));
+    }
+
+    context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_ACM);
+    context->codec_priv = (gpointer) codec_priv;
+    context->codec_priv_size = codec_priv_size;
   }
 
-  return FALSE;
+  return TRUE;
+
+  /* ERRORS */
+refuse_caps:
+  {
+    GST_WARNING_OBJECT (mux, "pad %s refused caps %" GST_PTR_FORMAT,
+        GST_PAD_NAME (pad), caps);
+    return FALSE;
+  }
 }
 
 
@@ -1540,6 +1772,57 @@ gst_matroska_mux_subtitle_pad_setcaps (GstPad * pad, GstCaps * caps)
    * 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 */
+
+  GstMatroskaTrackContext *context = NULL;
+  GstMatroskaTrackSubtitleContext *scontext;
+  GstMatroskaMux *mux;
+  GstMatroskaPad *collect_pad;
+  const gchar *mimetype;
+  GstStructure *structure;
+
+  mux = GST_MATROSKA_MUX (GST_PAD_PARENT (pad));
+
+  /* find context */
+  collect_pad = (GstMatroskaPad *) gst_pad_get_element_private (pad);
+  g_assert (collect_pad);
+  context = collect_pad->track;
+  g_assert (context);
+  g_assert (context->type == GST_MATROSKA_TRACK_TYPE_SUBTITLE);
+  scontext = (GstMatroskaTrackSubtitleContext *) context;
+
+  structure = gst_caps_get_structure (caps, 0);
+  mimetype = gst_structure_get_name (structure);
+
+  /* general setup */
+  scontext->check_utf8 = 1;
+  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);
+
+    if (context->codec_priv != NULL) {
+      g_free (context->codec_priv);
+      context->codec_priv = NULL;
+      context->codec_priv_size = 0;
+    }
+
+    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;
+    }
+    return TRUE;
+  }
+
   return FALSE;
 }
 
@@ -1556,32 +1839,55 @@ gst_matroska_mux_subtitle_pad_setcaps (GstPad * pad, GstCaps * caps)
  */
 static GstPad *
 gst_matroska_mux_request_new_pad (GstElement * element,
-    GstPadTemplate * templ, const gchar * pad_name)
+    GstPadTemplate * templ, const gchar * req_name)
 {
   GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
   GstMatroskaMux *mux = GST_MATROSKA_MUX (element);
   GstMatroskaPad *collect_pad;
   GstPad *newpad = NULL;
   gchar *name = NULL;
+  const gchar *pad_name = NULL;
   GstPadSetCapsFunction setcapsfunc = NULL;
   GstMatroskaTrackContext *context = NULL;
+  gint pad_id;
 
-  if (templ == gst_element_class_get_pad_template (klass, "audio_%d")) {
-    name = g_strdup_printf ("audio_%d", mux->num_a_streams++);
+  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
+     * trying to add it */
+    if (req_name != NULL && sscanf (req_name, "audio_%u", &pad_id) == 1) {
+      pad_name = req_name;
+    } else {
+      name = g_strdup_printf ("audio_%u", mux->num_a_streams++);
+      pad_name = name;
+    }
     setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_audio_pad_setcaps);
     context = (GstMatroskaTrackContext *)
         g_new0 (GstMatroskaTrackAudioContext, 1);
     context->type = GST_MATROSKA_TRACK_TYPE_AUDIO;
     context->name = g_strdup ("Audio");
-  } else if (templ == gst_element_class_get_pad_template (klass, "video_%d")) {
-    name = g_strdup_printf ("video_%d", mux->num_v_streams++);
+  } else if (templ == gst_element_class_get_pad_template (klass, "video_%u")) {
+    /* don't mix named and unnamed pads, if the pad already exists we fail when
+     * trying to add it */
+    if (req_name != NULL && sscanf (req_name, "video_%u", &pad_id) == 1) {
+      pad_name = req_name;
+    } else {
+      name = g_strdup_printf ("video_%u", mux->num_v_streams++);
+      pad_name = name;
+    }
     setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_video_pad_setcaps);
     context = (GstMatroskaTrackContext *)
         g_new0 (GstMatroskaTrackVideoContext, 1);
     context->type = GST_MATROSKA_TRACK_TYPE_VIDEO;
     context->name = g_strdup ("Video");
-  } else if (templ == gst_element_class_get_pad_template (klass, "subtitle_%d")) {
-    name = g_strdup_printf ("subtitle_%d", mux->num_t_streams++);
+  } else if (templ == gst_element_class_get_pad_template (klass, "subtitle_%u")) {
+    /* don't mix named and unnamed pads, if the pad already exists we fail when
+     * trying to add it */
+    if (req_name != NULL && sscanf (req_name, "subtitle_%u", &pad_id) == 1) {
+      pad_name = req_name;
+    } else {
+      name = g_strdup_printf ("subtitle_%u", mux->num_t_streams++);
+      pad_name = name;
+    }
     setcapsfunc = GST_DEBUG_FUNCPTR (gst_matroska_mux_subtitle_pad_setcaps);
     context = (GstMatroskaTrackContext *)
         g_new0 (GstMatroskaTrackSubtitleContext, 1);
@@ -1592,7 +1898,7 @@ gst_matroska_mux_request_new_pad (GstElement * element,
     return NULL;
   }
 
-  newpad = gst_pad_new_from_template (templ, name);
+  newpad = gst_pad_new_from_template (templ, pad_name);
   g_free (name);
   collect_pad = (GstMatroskaPad *)
       gst_collect_pads_add_pad_full (mux->collect, newpad,
@@ -1616,10 +1922,22 @@ gst_matroska_mux_request_new_pad (GstElement * element,
 
   gst_pad_set_setcaps_function (newpad, setcapsfunc);
   gst_pad_set_active (newpad, TRUE);
-  gst_element_add_pad (element, newpad);
+  if (!gst_element_add_pad (element, newpad))
+    goto pad_add_failed;
+
   mux->num_streams++;
 
+  GST_DEBUG_OBJECT (newpad, "Added new request pad");
+
   return newpad;
+
+  /* ERROR cases */
+pad_add_failed:
+  {
+    GST_WARNING_OBJECT (mux, "Adding the new pad '%s' failed", pad_name);
+    gst_object_unref (newpad);
+    return NULL;
+  }
 }
 
 /**
@@ -1775,6 +2093,7 @@ static void
 gst_matroska_mux_start (GstMatroskaMux * mux)
 {
   GstEbmlWrite *ebml = mux->ebml_write;
+  const gchar *doctype;
   guint32 seekhead_id[] = { GST_MATROSKA_ID_SEGMENTINFO,
     GST_MATROSKA_ID_TRACKS,
     GST_MATROSKA_ID_CUES,
@@ -1789,27 +2108,58 @@ gst_matroska_mux_start (GstMatroskaMux * mux)
   guint32 segment_uid[4];
   GTimeVal time = { 0, 0 };
 
+  if (!strcmp (mux->doctype, GST_MATROSKA_DOCTYPE_WEBM)) {
+    ebml->caps = gst_caps_new_simple ("video/webm", NULL);
+  } else {
+    ebml->caps = gst_caps_new_simple ("video/x-matroska", NULL);
+  }
   /* we start with a EBML header */
-  gst_ebml_write_header (ebml, "matroska", mux->matroska_version);
+  doctype = mux->doctype;
+  GST_INFO_OBJECT (ebml, "DocType: %s, Version: %d",
+      doctype, mux->doctype_version);
+  gst_ebml_write_header (ebml, doctype, mux->doctype_version);
+
+  /* the rest of the header is cached */
+  gst_ebml_write_set_cache (ebml, 0x1000);
 
   /* start a segment */
   mux->segment_pos =
       gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENT);
   mux->segment_master = ebml->pos;
 
-  /* the rest of the header is cached */
-  gst_ebml_write_set_cache (ebml, 0x1000);
+  if (!mux->streamable) {
+    /* seekhead (table of contents) - we set the positions later */
+    mux->seekhead_pos = ebml->pos;
+    master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKHEAD);
+    for (i = 0; seekhead_id[i] != 0; i++) {
+      child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKENTRY);
+      gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKID, seekhead_id[i]);
+      gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKPOSITION, -1);
+      gst_ebml_write_master_finish (ebml, child);
+    }
+    gst_ebml_write_master_finish (ebml, master);
+  }
+
+  if (mux->streamable) {
+    const GstTagList *tags;
 
-  /* seekhead (table of contents) - we set the positions later */
-  mux->seekhead_pos = ebml->pos;
-  master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKHEAD);
-  for (i = 0; seekhead_id[i] != 0; i++) {
-    child = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEEKENTRY);
-    gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKID, seekhead_id[i]);
-    gst_ebml_write_uint (ebml, GST_MATROSKA_ID_SEEKPOSITION, -1);
-    gst_ebml_write_master_finish (ebml, child);
+    /* 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;
+
+      GST_DEBUG ("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);
+    }
   }
-  gst_ebml_write_master_finish (ebml, master);
 
   /* segment info */
   mux->info_pos = ebml->pos;
@@ -1822,30 +2172,31 @@ gst_matroska_mux_start (GstMatroskaMux * mux)
   gst_ebml_write_uint (ebml, GST_MATROSKA_ID_TIMECODESCALE, mux->time_scale);
   mux->duration_pos = ebml->pos;
   /* get duration */
-  for (collected = mux->collect->data; collected;
-      collected = g_slist_next (collected)) {
-    GstMatroskaPad *collect_pad;
-    GstFormat format = GST_FORMAT_TIME;
-    GstPad *thepad;
-    gint64 trackduration;
-
-    collect_pad = (GstMatroskaPad *) collected->data;
-    thepad = collect_pad->collect.pad;
-
-    /* Query the total length of the track. */
-    GST_DEBUG_OBJECT (thepad, "querying peer duration");
-    if (gst_pad_query_peer_duration (thepad, &format, &trackduration)) {
-      GST_DEBUG_OBJECT (thepad, "duration: %" GST_TIME_FORMAT,
-          GST_TIME_ARGS (trackduration));
-      if (trackduration != GST_CLOCK_TIME_NONE && trackduration > duration) {
-        duration = (GstClockTime) trackduration;
+  if (!mux->streamable) {
+    for (collected = mux->collect->data; collected;
+        collected = g_slist_next (collected)) {
+      GstMatroskaPad *collect_pad;
+      GstFormat format = GST_FORMAT_TIME;
+      GstPad *thepad;
+      gint64 trackduration;
+
+      collect_pad = (GstMatroskaPad *) collected->data;
+      thepad = collect_pad->collect.pad;
+
+      /* Query the total length of the track. */
+      GST_DEBUG_OBJECT (thepad, "querying peer duration");
+      if (gst_pad_query_peer_duration (thepad, &format, &trackduration)) {
+        GST_DEBUG_OBJECT (thepad, "duration: %" GST_TIME_FORMAT,
+            GST_TIME_ARGS (trackduration));
+        if (trackduration != GST_CLOCK_TIME_NONE && trackduration > duration) {
+          duration = (GstClockTime) trackduration;
+        }
       }
     }
+    gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION,
+        gst_guint64_to_gdouble (duration) /
+        gst_guint64_to_gdouble (mux->time_scale));
   }
-  gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION,
-      gst_guint64_to_gdouble (duration) /
-      gst_guint64_to_gdouble (mux->time_scale));
-
   gst_ebml_write_utf8 (ebml, GST_MATROSKA_ID_MUXINGAPP,
       "GStreamer plugin version " PACKAGE_VERSION);
   if (mux->writing_app && mux->writing_app[0]) {
@@ -1873,12 +2224,16 @@ 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 */
+      collect_pad->default_duration_scaled =
+          gst_util_uint64_scale (collect_pad->track->default_duration,
+          1, mux->time_scale);
     }
   }
   gst_ebml_write_master_finish (ebml, master);
 
   /* lastly, flush the cache */
-  gst_ebml_write_flush_cache (ebml);
+  gst_ebml_write_flush_cache (ebml, FALSE, 0);
 }
 
 static void
@@ -1886,15 +2241,15 @@ gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag,
     gpointer data)
 {
   /* TODO: more sensible tag mappings */
-  struct
+  static const struct
   {
-    gchar *matroska_tagname;
-    gchar *gstreamer_tagname;
+    const gchar *matroska_tagname;
+    const gchar *gstreamer_tagname;
   }
   tag_conv[] = {
     {
     GST_MATROSKA_TAG_ID_TITLE, GST_TAG_TITLE}, {
-    GST_MATROSKA_TAG_ID_AUTHOR, GST_TAG_ARTIST}, {
+    GST_MATROSKA_TAG_ID_ARTIST, GST_TAG_ARTIST}, {
     GST_MATROSKA_TAG_ID_ALBUM, GST_TAG_ALBUM}, {
     GST_MATROSKA_TAG_ID_COMMENTS, GST_TAG_COMMENT}, {
     GST_MATROSKA_TAG_ID_BITSPS, GST_TAG_BITRATE}, {
@@ -1987,7 +2342,7 @@ gst_matroska_mux_finish (GstMatroskaMux * mux)
     }
 
     gst_ebml_write_master_finish (ebml, master);
-    gst_ebml_write_flush_cache (ebml);
+    gst_ebml_write_flush_cache (ebml, FALSE, GST_CLOCK_TIME_NONE);
   }
 
   /* tags */
@@ -2055,7 +2410,8 @@ gst_matroska_mux_finish (GstMatroskaMux * mux)
 
     collect_pad = (GstMatroskaPad *) collected->data;
 
-    GST_DEBUG_OBJECT (mux, "Pad %" GST_PTR_FORMAT " start ts %" GST_TIME_FORMAT
+    GST_DEBUG_OBJECT (mux,
+        "Pad %" GST_PTR_FORMAT " start ts %" GST_TIME_FORMAT
         " end ts %" GST_TIME_FORMAT, collect_pad,
         GST_TIME_ARGS (collect_pad->start_ts),
         GST_TIME_ARGS (collect_pad->end_ts));
@@ -2066,7 +2422,8 @@ gst_matroska_mux_finish (GstMatroskaMux * mux)
           GST_CLOCK_DIFF (collect_pad->start_ts, collect_pad->end_ts);
       if (collect_pad->duration < min_duration)
         collect_pad->duration = min_duration;
-      GST_DEBUG_OBJECT (collect_pad, "final track duration: %" GST_TIME_FORMAT,
+      GST_DEBUG_OBJECT (collect_pad,
+          "final track duration: %" GST_TIME_FORMAT,
           GST_TIME_ARGS (collect_pad->duration));
     }
 
@@ -2091,7 +2448,7 @@ gst_matroska_mux_finish (GstMatroskaMux * mux)
     gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 8);
     gst_ebml_write_seek (ebml, my_pos);
   }
-
+  GST_DEBUG_OBJECT (mux, "finishing segment");
   /* finish segment - this also writes element length */
   gst_ebml_write_master_finish (ebml, mux->segment_pos);
 }
@@ -2102,7 +2459,7 @@ gst_matroska_mux_finish (GstMatroskaMux * mux)
  * @mux: #GstMatroskaMux
  * @popped: True if at least one buffer was popped from #GstCollectPads
  *
- * Find a pad with the oldest data 
+ * Find a pad with the oldest data
  * (data from this pad should be written first).
  *
  * Returns: Selected pad.
@@ -2124,8 +2481,33 @@ gst_matroska_mux_best_pad (GstMatroskaMux * mux, gboolean * popped)
       collect_pad->buffer = gst_collect_pads_pop (mux->collect,
           (GstCollectData *) collect_pad);
 
-      if (collect_pad->buffer != NULL)
+      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_metadata_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 */
@@ -2149,10 +2531,10 @@ gst_matroska_mux_best_pad (GstMatroskaMux * mux, gboolean * popped)
  * @flags: Buffer flags.
  *
  * Create a buffer containing buffer header.
- * 
+ *
  * Returns: New buffer.
  */
-GstBuffer *
+static GstBuffer *
 gst_matroska_mux_create_buffer_header (GstMatroskaTrackContext * track,
     gint16 relative_timestamp, int flags)
 {
@@ -2170,6 +2552,10 @@ gst_matroska_mux_create_buffer_header (GstMatroskaTrackContext * track,
   return hdr;
 }
 
+#define DIRAC_PARSE_CODE_SEQUENCE_HEADER 0x00
+#define DIRAC_PARSE_CODE_END_OF_SEQUENCE 0x10
+#define DIRAC_PARSE_CODE_IS_PICTURE(x) ((x & 0x08) != 0)
+
 static GstBuffer *
 gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux,
     GstMatroskaPad * collect_pad, GstBuffer * buf)
@@ -2181,33 +2567,37 @@ gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux,
   guint8 parse_code;
   guint32 next_parse_offset;
   GstBuffer *ret = NULL;
-  gboolean is_picture = FALSE;
+  gboolean is_muxing_unit = FALSE;
 
   if (GST_BUFFER_SIZE (buf) < 13) {
     gst_buffer_unref (buf);
     return ret;
   }
 
-  /* Check if this buffer contains a picture packet */
+  /* Check if this buffer contains a picture or end-of-sequence packet */
   while (size >= 13) {
-    if (GST_READ_UINT32_BE (data) != 0x42424344) {
+    if (GST_READ_UINT32_BE (data) != 0x42424344 /* 'BBCD' */ ) {
       gst_buffer_unref (buf);
       return ret;
     }
 
     parse_code = GST_READ_UINT8 (data + 4);
-    if (parse_code == 0x00) {
+    if (parse_code == DIRAC_PARSE_CODE_SEQUENCE_HEADER) {
       if (ctx->dirac_unit) {
         gst_buffer_unref (ctx->dirac_unit);
         ctx->dirac_unit = NULL;
       }
-    } else if (parse_code & 0x08) {
-      is_picture = TRUE;
+    } else if (DIRAC_PARSE_CODE_IS_PICTURE (parse_code) ||
+        parse_code == DIRAC_PARSE_CODE_END_OF_SEQUENCE) {
+      is_muxing_unit = TRUE;
       break;
     }
 
     next_parse_offset = GST_READ_UINT32_BE (data + 5);
 
+    if (G_UNLIKELY (next_parse_offset == 0 || next_parse_offset > size))
+      break;
+
     data += next_parse_offset;
     size -= next_parse_offset;
   }
@@ -2217,7 +2607,7 @@ gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux,
   else
     ctx->dirac_unit = gst_buffer_ref (buf);
 
-  if (is_picture) {
+  if (is_muxing_unit) {
     ret = gst_buffer_make_metadata_writable (ctx->dirac_unit);
     ctx->dirac_unit = NULL;
     gst_buffer_copy_metadata (ret, buf,
@@ -2232,6 +2622,36 @@ gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux,
   return ret;
 }
 
+static void
+gst_matroska_mux_stop_streamheader (GstMatroskaMux * mux)
+{
+  GstCaps *caps;
+  GstStructure *s;
+  GValue streamheader = { 0 };
+  GValue bufval = { 0 };
+  GstBuffer *streamheader_buffer;
+  GstEbmlWrite *ebml = mux->ebml_write;
+
+  streamheader_buffer = gst_ebml_stop_streamheader (ebml);
+  if (!strcmp (mux->doctype, GST_MATROSKA_DOCTYPE_WEBM)) {
+    caps = gst_caps_new_simple ("video/webm", NULL);
+  } else {
+    caps = gst_caps_new_simple ("video/x-matroska", NULL);
+  }
+  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_value_set_buffer (&bufval, streamheader_buffer);
+  gst_value_array_append_value (&streamheader, &bufval);
+  g_value_unset (&bufval);
+  gst_structure_set_value (s, "streamheader", &streamheader);
+  g_value_unset (&streamheader);
+  gst_caps_replace (&ebml->caps, caps);
+  gst_buffer_unref (streamheader_buffer);
+  gst_caps_unref (caps);
+}
+
 /**
  * gst_matroska_mux_write_data:
  * @mux: #GstMatroskaMux
@@ -2246,7 +2666,7 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
 {
   GstEbmlWrite *ebml = mux->ebml_write;
   GstBuffer *buf, *hdr;
-  guint64 cluster, blockgroup;
+  guint64 blockgroup;
   gboolean write_duration;
   gint16 relative_timestamp;
   gint64 relative_timestamp64;
@@ -2296,117 +2716,149 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
   }
 
   if (mux->cluster) {
-    /* start a new cluster every two seconds or at keyframe */
-    if (mux->cluster_time + GST_SECOND * 2 < GST_BUFFER_TIMESTAMP (buf)
-        || is_video_keyframe) {
+    /* start a new cluster at every keyframe, at every GstForceKeyUnit event,
+     * or when we may be reaching the limit of the relative timestamp */
+    if (mux->cluster_time +
+        mux->max_cluster_duration < GST_BUFFER_TIMESTAMP (buf)
+        || is_video_keyframe || mux->force_key_unit_event) {
+      if (!mux->streamable)
+        gst_ebml_write_master_finish (ebml, mux->cluster);
+
+      /* Forward the GstForceKeyUnit event after finishing the cluster */
+      if (mux->force_key_unit_event) {
+        gst_pad_push_event (mux->srcpad, mux->force_key_unit_event);
+        mux->force_key_unit_event = NULL;
+      }
 
-      gst_ebml_write_master_finish (ebml, mux->cluster);
+      mux->prev_cluster_size = ebml->pos - mux->cluster_pos;
       mux->cluster_pos = ebml->pos;
+      gst_ebml_write_set_cache (ebml, 0x20);
       mux->cluster =
           gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER);
       gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE,
-          GST_BUFFER_TIMESTAMP (buf) / mux->time_scale);
+          gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (buf), 1,
+              mux->time_scale));
+      GST_LOG_OBJECT (mux, "cluster timestamp %" G_GUINT64_FORMAT,
+          gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (buf), 1,
+              mux->time_scale));
+      gst_ebml_write_flush_cache (ebml, TRUE, GST_BUFFER_TIMESTAMP (buf));
       mux->cluster_time = GST_BUFFER_TIMESTAMP (buf);
+      gst_ebml_write_uint (ebml, GST_MATROSKA_ID_PREVSIZE,
+          mux->prev_cluster_size);
     }
   } else {
     /* first cluster */
 
     mux->cluster_pos = ebml->pos;
+    gst_ebml_write_set_cache (ebml, 0x20);
     mux->cluster = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER);
     gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE,
-        GST_BUFFER_TIMESTAMP (buf) / mux->time_scale);
+        gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (buf), 1, mux->time_scale));
+    gst_ebml_write_flush_cache (ebml, TRUE, GST_BUFFER_TIMESTAMP (buf));
     mux->cluster_time = GST_BUFFER_TIMESTAMP (buf);
   }
-  cluster = mux->cluster;
 
   /* update duration of this track */
   if (GST_BUFFER_DURATION_IS_VALID (buf))
     collect_pad->duration += GST_BUFFER_DURATION (buf);
 
-  /* We currently write an index entry for each keyframe in a
-   * video track or one entry for each cluster in an audio track
-   * for audio only files. This can be largely improved, such as doing
-   * one for each keyframe or each second (for all-keyframe
-   * streams), only the *first* video track. But that'll come later... */
+  /* We currently write index entries for all video tracks or for the audio
+   * track in a single-track audio file.  This could be improved by keeping the
+   * index only for the *first* video track. */
 
   /* TODO: index is useful for every track, should contain the number of
-   * the block in the cluster which contains the timestamp
+   * the block in the cluster which contains the timestamp, should also work
+   * for files with multiple audio tracks.
    */
-  if (is_video_keyframe) {
-    GstMatroskaIndex *idx;
-
-    if (mux->num_indexes % 32 == 0) {
-      mux->index = g_renew (GstMatroskaIndex, mux->index,
-          mux->num_indexes + 32);
+  if (!mux->streamable &&
+      (is_video_keyframe ||
+          ((collect_pad->track->type == GST_MATROSKA_TRACK_TYPE_AUDIO) &&
+              (mux->num_streams == 1)))) {
+    gint last_idx = -1;
+
+    if (mux->min_index_interval != 0) {
+      for (last_idx = mux->num_indexes - 1; last_idx >= 0; last_idx--) {
+        if (mux->index[last_idx].track == collect_pad->track->num)
+          break;
+      }
     }
-    idx = &mux->index[mux->num_indexes++];
 
-    idx->pos = mux->cluster_pos;
-    idx->time = GST_BUFFER_TIMESTAMP (buf);
-    idx->track = collect_pad->track->num;
-  } else if ((collect_pad->track->type == GST_MATROSKA_TRACK_TYPE_AUDIO) &&
-      (mux->num_streams == 1)) {
-    GstMatroskaIndex *idx;
+    if (last_idx < 0 || mux->min_index_interval == 0 ||
+        (GST_CLOCK_DIFF (mux->index[last_idx].time, GST_BUFFER_TIMESTAMP (buf))
+            >= mux->min_index_interval)) {
+      GstMatroskaIndex *idx;
 
-    if (mux->num_indexes % 32 == 0) {
-      mux->index = g_renew (GstMatroskaIndex, mux->index,
-          mux->num_indexes + 32);
-    }
-    idx = &mux->index[mux->num_indexes++];
+      if (mux->num_indexes % 32 == 0) {
+        mux->index = g_renew (GstMatroskaIndex, mux->index,
+            mux->num_indexes + 32);
+      }
+      idx = &mux->index[mux->num_indexes++];
 
-    idx->pos = mux->cluster_pos;
-    idx->time = GST_BUFFER_TIMESTAMP (buf);
-    idx->track = collect_pad->track->num;
+      idx->pos = mux->cluster_pos;
+      idx->time = GST_BUFFER_TIMESTAMP (buf);
+      idx->track = collect_pad->track->num;
+    }
   }
 
   /* Check if the duration differs from the default duration. */
   write_duration = FALSE;
-  block_duration = GST_BUFFER_DURATION (buf);
+  block_duration = 0;
   if (GST_BUFFER_DURATION_IS_VALID (buf)) {
-    if (block_duration != collect_pad->track->default_duration) {
+    block_duration = gst_util_uint64_scale (GST_BUFFER_DURATION (buf),
+        1, mux->time_scale);
+
+    /* small difference should be ok. */
+    if (block_duration > collect_pad->default_duration_scaled + 1 ||
+        block_duration < collect_pad->default_duration_scaled - 1) {
       write_duration = TRUE;
     }
   }
 
-  /* write the block, for matroska v2 use SimpleBlock if possible
+  /* write the block, for doctype v2 use SimpleBlock if possible
    * one slice (*breath*).
    * FIXME: Need to do correct lacing! */
   relative_timestamp64 = GST_BUFFER_TIMESTAMP (buf) - mux->cluster_time;
   if (relative_timestamp64 >= 0) {
     /* round the timestamp */
-    relative_timestamp64 += mux->time_scale / 2;
+    relative_timestamp64 += gst_util_uint64_scale (mux->time_scale, 1, 2);
   } else {
     /* round the timestamp */
-    relative_timestamp64 -= mux->time_scale / 2;
+    relative_timestamp64 -= gst_util_uint64_scale (mux->time_scale, 1, 2);
   }
-  relative_timestamp = relative_timestamp64 / (gint64) mux->time_scale;
-  if (mux->matroska_version > 1 && !write_duration) {
+  relative_timestamp = gst_util_uint64_scale (relative_timestamp64, 1,
+      mux->time_scale);
+  if (mux->doctype_version > 1 && !write_duration) {
     int flags =
         GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_DELTA_UNIT) ? 0 : 0x80;
 
     hdr =
         gst_matroska_mux_create_buffer_header (collect_pad->track,
         relative_timestamp, flags);
+    gst_ebml_write_set_cache (ebml, 0x40);
     gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_SIMPLEBLOCK,
         GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr));
     gst_ebml_write_buffer (ebml, hdr);
+    gst_ebml_write_flush_cache (ebml, FALSE, GST_BUFFER_TIMESTAMP (buf));
     gst_ebml_write_buffer (ebml, buf);
 
     return gst_ebml_last_write_result (ebml);
   } else {
+    gst_ebml_write_set_cache (ebml, GST_BUFFER_SIZE (buf) * 2);
+    /* write and call order slightly unnatural,
+     * but avoids seek and minizes pushing */
     blockgroup = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_BLOCKGROUP);
     hdr =
         gst_matroska_mux_create_buffer_header (collect_pad->track,
         relative_timestamp, 0);
+    if (write_duration)
+      gst_ebml_write_uint (ebml, GST_MATROSKA_ID_BLOCKDURATION, block_duration);
     gst_ebml_write_buffer_header (ebml, GST_MATROSKA_ID_BLOCK,
         GST_BUFFER_SIZE (buf) + GST_BUFFER_SIZE (hdr));
     gst_ebml_write_buffer (ebml, hdr);
+    gst_ebml_write_master_finish_full (ebml, blockgroup, GST_BUFFER_SIZE (buf));
+    gst_ebml_write_flush_cache (ebml, FALSE, GST_BUFFER_TIMESTAMP (buf));
     gst_ebml_write_buffer (ebml, buf);
-    if (write_duration) {
-      gst_ebml_write_uint (ebml, GST_MATROSKA_ID_BLOCKDURATION,
-          block_duration / mux->time_scale);
-    }
-    gst_ebml_write_master_finish (ebml, blockgroup);
+
     return gst_ebml_last_write_result (ebml);
   }
 }
@@ -2425,9 +2877,10 @@ static GstFlowReturn
 gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data)
 {
   GstMatroskaMux *mux = GST_MATROSKA_MUX (user_data);
+  GstEbmlWrite *ebml = mux->ebml_write;
   GstMatroskaPad *best;
   gboolean popped;
-  GstFlowReturn ret;
+  GstFlowReturn ret = GST_FLOW_OK;
 
   GST_DEBUG_OBJECT (mux, "Collected pads");
 
@@ -2439,7 +2892,9 @@ gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data)
       return GST_FLOW_ERROR;
     }
     mux->state = GST_MATROSKA_MUX_STATE_HEADER;
+    gst_ebml_start_streamheader (ebml);
     gst_matroska_mux_start (mux);
+    gst_matroska_mux_stop_streamheader (mux);
     mux->state = GST_MATROSKA_MUX_STATE_DATA;
   }
 
@@ -2449,8 +2904,15 @@ gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_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...");
-      gst_matroska_mux_finish (mux);
+      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;
@@ -2552,8 +3014,14 @@ gst_matroska_mux_set_property (GObject * object,
       g_free (mux->writing_app);
       mux->writing_app = g_value_dup_string (value);
       break;
-    case ARG_MATROSKA_VERSION:
-      mux->matroska_version = g_value_get_int (value);
+    case ARG_DOCTYPE_VERSION:
+      mux->doctype_version = g_value_get_int (value);
+      break;
+    case ARG_MIN_INDEX_INTERVAL:
+      mux->min_index_interval = g_value_get_int64 (value);
+      break;
+    case ARG_STREAMABLE:
+      mux->streamable = g_value_get_boolean (value);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -2574,18 +3042,17 @@ gst_matroska_mux_get_property (GObject * object,
     case ARG_WRITING_APP:
       g_value_set_string (value, mux->writing_app);
       break;
-    case ARG_MATROSKA_VERSION:
-      g_value_set_int (value, mux->matroska_version);
+    case ARG_DOCTYPE_VERSION:
+      g_value_set_int (value, mux->doctype_version);
+      break;
+    case ARG_MIN_INDEX_INTERVAL:
+      g_value_set_int64 (value, mux->min_index_interval);
+      break;
+    case ARG_STREAMABLE:
+      g_value_set_boolean (value, mux->streamable);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
   }
 }
-
-gboolean
-gst_matroska_mux_plugin_init (GstPlugin * plugin)
-{
-  return gst_element_register (plugin, "matroskamux",
-      GST_RANK_NONE, GST_TYPE_MATROSKA_MUX);
-}