make %u in all request pad templates
[platform/upstream/gst-plugins-good.git] / gst / matroska / matroska-mux.c
index 265eb18..d48d56c 100644 (file)
@@ -46,6 +46,7 @@
 #endif
 
 #include <math.h>
+#include <stdio.h>
 #include <string.h>
 
 #include <gst/riff/riff-media.h>
@@ -89,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 "; "
@@ -139,7 +140,7 @@ 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, "
@@ -152,6 +153,10 @@ static GstStaticPadTemplate audiosink_templ =
         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, "
@@ -192,14 +197,18 @@ static GstStaticPadTemplate audiosink_templ =
         "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)
+        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);
@@ -249,6 +258,9 @@ 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)
@@ -296,20 +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));
+          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));
+          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));
+          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));
+          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);
@@ -376,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)
@@ -482,7 +499,7 @@ gst_matroska_pad_reset (GstMatroskaPad * collect_pad, gboolean full)
         break;
       default:
         g_assert_not_reached ();
-        break;
+        return;
     }
 
     context->type = type;
@@ -564,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.
  */
@@ -606,7 +623,6 @@ gst_matroska_mux_handle_sink_event (GstPad * pad, GstEvent * event)
 
   mux = GST_MATROSKA_MUX (gst_pad_get_parent (pad));
 
-  /* FIXME: aren't we either leaking events here or doing a wrong unref? */
   switch (GST_EVENT_TYPE (event)) {
     case GST_EVENT_TAG:{
       gchar *lang = NULL;
@@ -633,22 +649,46 @@ gst_matroska_mux_handle_sink_event (GstPad * pad, GstEvent * event)
         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)));
+
+      gst_event_unref (event);
+      /* handled this, don't want collectpads to forward it downstream */
+      event = NULL;
       break;
     }
-    case GST_EVENT_NEWSEGMENT:
-      /* We don't support NEWSEGMENT events */
-      ret = FALSE;
-      gst_event_unref (event);
+    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;
@@ -756,15 +796,14 @@ 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);
-  } else if (!strcmp (mimetype, "image/jpeg")) {
-    context->codec_id = g_strdup (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")
       || !strcmp (mimetype, "video/x-dv")
       || !strcmp (mimetype, "video/x-h263")
       || !strcmp (mimetype, "video/x-msmpeg")
-      || !strcmp (mimetype, "video/x-wmv")) {
+      || !strcmp (mimetype, "video/x-wmv")
+      || !strcmp (mimetype, "image/jpeg")) {
     gst_riff_strf_vids *bih;
     gint size = sizeof (gst_riff_strf_vids);
     guint32 fourcc = 0;
@@ -821,6 +860,8 @@ skip_details:
           fourcc = GST_MAKE_FOURCC ('W', 'M', 'V', '3');
         }
       }
+    } else if (!strcmp (mimetype, "image/jpeg")) {
+      fourcc = GST_MAKE_FOURCC ('M', 'J', 'P', 'G');
     }
 
     if (!fourcc)
@@ -1569,6 +1610,10 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
     }
   } else if (!strcmp (mimetype, "audio/x-ac3")) {
     context->codec_id = g_strdup (GST_MATROSKA_CODEC_ID_AUDIO_AC3);
+  } 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;
 
@@ -1614,41 +1659,61 @@ gst_matroska_mux_audio_pad_setcaps (GstPad * pad, GstCaps * caps)
       context->codec_priv_size = priv_data_size;
     }
 
-  } else if (!strcmp (mimetype, "audio/x-wma")) {
+  } 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;
+    guint16 format = 0;
     gint block_align;
     gint bitrate;
-    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)
-        || samplerate == 0 || channels == 0) {
-      GST_WARNING_OBJECT (mux, "Missing wmaversion/block_align/bitrate/"
-          "channels/rate on WMA caps");
+
+    if (samplerate == 0 || channels == 0) {
+      GST_WARNING_OBJECT (mux, "Missing channels/samplerate on 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);
+    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;
-    }
+      }
 
-    if (gst_structure_get_int (structure, "depth", &depth))
-      audiocontext->bitdepth = depth;
+      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)
@@ -1774,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);
@@ -1810,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,
@@ -1834,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;
+  }
 }
 
 /**
@@ -2009,9 +2109,9 @@ gst_matroska_mux_start (GstMatroskaMux * mux)
   GTimeVal time = { 0, 0 };
 
   if (!strcmp (mux->doctype, GST_MATROSKA_DOCTYPE_WEBM)) {
-    ebml->caps = gst_caps_from_string ("video/webm");
+    ebml->caps = gst_caps_new_simple ("video/webm", NULL);
   } else {
-    ebml->caps = gst_caps_from_string ("video/x-matroska");
+    ebml->caps = gst_caps_new_simple ("video/x-matroska", NULL);
   }
   /* we start with a EBML header */
   doctype = mux->doctype;
@@ -2040,6 +2140,27 @@ gst_matroska_mux_start (GstMatroskaMux * mux)
     gst_ebml_write_master_finish (ebml, master);
   }
 
+  if (mux->streamable) {
+    const GstTagList *tags;
+
+    /* 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);
+    }
+  }
+
   /* segment info */
   mux->info_pos = ebml->pos;
   master = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_SEGMENTINFO);
@@ -2103,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, FALSE);
+  gst_ebml_write_flush_cache (ebml, FALSE, 0);
 }
 
 static void
@@ -2116,7 +2241,7 @@ gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag,
     gpointer data)
 {
   /* TODO: more sensible tag mappings */
-  struct
+  static const struct
   {
     const gchar *matroska_tagname;
     const gchar *gstreamer_tagname;
@@ -2124,7 +2249,7 @@ gst_matroska_mux_write_simple_tag (const GstTagList * list, const gchar * tag,
   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}, {
@@ -2192,7 +2317,7 @@ gst_matroska_mux_finish (GstMatroskaMux * mux)
   }
 
   /* cues */
-  if (mux->index != NULL && !mux->streamable) {
+  if (mux->index != NULL) {
     guint n;
     guint64 master, pointentry_master, trackpos_master;
 
@@ -2217,13 +2342,13 @@ gst_matroska_mux_finish (GstMatroskaMux * mux)
     }
 
     gst_ebml_write_master_finish (ebml, master);
-    gst_ebml_write_flush_cache (ebml, FALSE);
+    gst_ebml_write_flush_cache (ebml, FALSE, GST_CLOCK_TIME_NONE);
   }
 
   /* tags */
   tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (mux));
 
-  if (tags != NULL && !gst_tag_list_is_empty (tags) && !mux->streamable) {
+  if (tags != NULL && !gst_tag_list_is_empty (tags)) {
     guint64 master_tags, master_tag;
 
     GST_DEBUG ("Writing tags");
@@ -2247,84 +2372,81 @@ gst_matroska_mux_finish (GstMatroskaMux * mux)
    *     length pointer starts at 20.
    * - all entries are local to the segment (so pos - segment_master).
    * - so each entry is at 12 + 20 + num * 28. */
-  if (!mux->streamable) {
-    GST_DEBUG_OBJECT (mux, "not streamable");
-    gst_ebml_replace_uint (ebml, mux->seekhead_pos + 32,
-        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) {
-      gst_ebml_replace_uint (ebml, mux->seekhead_pos + 88,
-          mux->cues_pos - mux->segment_master);
-    } else {
-      /* void'ify */
-      guint64 my_pos = ebml->pos;
-
-      gst_ebml_write_seek (ebml, mux->seekhead_pos + 68);
-      gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
-      gst_ebml_write_seek (ebml, my_pos);
-    }
-    if (tags != NULL) {
-      gst_ebml_replace_uint (ebml, mux->seekhead_pos + 116,
-          mux->tags_pos - mux->segment_master);
-    } else {
-      /* void'ify */
-      guint64 my_pos = ebml->pos;
-
-      gst_ebml_write_seek (ebml, mux->seekhead_pos + 96);
-      gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
-      gst_ebml_write_seek (ebml, my_pos);
-    }
+  gst_ebml_replace_uint (ebml, mux->seekhead_pos + 32,
+      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) {
+    gst_ebml_replace_uint (ebml, mux->seekhead_pos + 88,
+        mux->cues_pos - mux->segment_master);
+  } else {
+    /* void'ify */
+    guint64 my_pos = ebml->pos;
 
-    /* update duration */
-    /* first get the overall duration */
-    /* a released track may have left a duration in here */
-    duration = mux->duration;
-    for (collected = mux->collect->data; collected;
-        collected = g_slist_next (collected)) {
-      GstMatroskaPad *collect_pad;
-      GstClockTime min_duration;        /* observed minimum duration */
+    gst_ebml_write_seek (ebml, mux->seekhead_pos + 68);
+    gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
+    gst_ebml_write_seek (ebml, my_pos);
+  }
+  if (tags != NULL) {
+    gst_ebml_replace_uint (ebml, mux->seekhead_pos + 116,
+        mux->tags_pos - mux->segment_master);
+  } else {
+    /* void'ify */
+    guint64 my_pos = ebml->pos;
 
-      collect_pad = (GstMatroskaPad *) collected->data;
+    gst_ebml_write_seek (ebml, mux->seekhead_pos + 96);
+    gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 26);
+    gst_ebml_write_seek (ebml, my_pos);
+  }
 
-      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));
+  /* update duration */
+  /* first get the overall duration */
+  /* a released track may have left a duration in here */
+  duration = mux->duration;
+  for (collected = mux->collect->data; collected;
+      collected = g_slist_next (collected)) {
+    GstMatroskaPad *collect_pad;
+    GstClockTime min_duration;  /* observed minimum duration */
 
-      if (GST_CLOCK_TIME_IS_VALID (collect_pad->start_ts) &&
-          GST_CLOCK_TIME_IS_VALID (collect_pad->end_ts)) {
-        min_duration =
-            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_TIME_ARGS (collect_pad->duration));
-      }
+    collect_pad = (GstMatroskaPad *) collected->data;
 
-      if (GST_CLOCK_TIME_IS_VALID (collect_pad->duration) &&
-          duration < collect_pad->duration)
-        duration = collect_pad->duration;
-    }
-    if (duration != 0) {
-      GST_DEBUG_OBJECT (mux, "final total duration: %" GST_TIME_FORMAT,
-          GST_TIME_ARGS (duration));
-      pos = mux->ebml_write->pos;
-      gst_ebml_write_seek (ebml, mux->duration_pos);
-      gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION,
-          gst_guint64_to_gdouble (duration) /
-          gst_guint64_to_gdouble (mux->time_scale));
-      gst_ebml_write_seek (ebml, pos);
-    } else {
-      /* void'ify */
-      guint64 my_pos = ebml->pos;
+    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));
+
+    if (GST_CLOCK_TIME_IS_VALID (collect_pad->start_ts) &&
+        GST_CLOCK_TIME_IS_VALID (collect_pad->end_ts)) {
+      min_duration =
+          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_TIME_ARGS (collect_pad->duration));
+    }
+
+    if (GST_CLOCK_TIME_IS_VALID (collect_pad->duration) &&
+        duration < collect_pad->duration)
+      duration = collect_pad->duration;
+  }
+  if (duration != 0) {
+    GST_DEBUG_OBJECT (mux, "final total duration: %" GST_TIME_FORMAT,
+        GST_TIME_ARGS (duration));
+    pos = mux->ebml_write->pos;
+    gst_ebml_write_seek (ebml, mux->duration_pos);
+    gst_ebml_write_float (ebml, GST_MATROSKA_ID_DURATION,
+        gst_guint64_to_gdouble (duration) /
+        gst_guint64_to_gdouble (mux->time_scale));
+    gst_ebml_write_seek (ebml, pos);
+  } else {
+    /* void'ify */
+    guint64 my_pos = ebml->pos;
 
-      gst_ebml_write_seek (ebml, mux->duration_pos);
-      gst_ebml_write_buffer_header (ebml, GST_EBML_ID_VOID, 8);
-      gst_ebml_write_seek (ebml, my_pos);
-    }
+    gst_ebml_write_seek (ebml, mux->duration_pos);
+    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 */
@@ -2337,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.
@@ -2359,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 */
@@ -2384,7 +2531,7 @@ gst_matroska_mux_best_pad (GstMatroskaMux * mux, gboolean * popped)
  * @flags: Buffer flags.
  *
  * Create a buffer containing buffer header.
- * 
+ *
  * Returns: New buffer.
  */
 static GstBuffer *
@@ -2448,7 +2595,7 @@ gst_matroska_mux_handle_dirac_packet (GstMatroskaMux * mux,
 
     next_parse_offset = GST_READ_UINT32_BE (data + 5);
 
-    if (G_UNLIKELY (next_parse_offset == 0))
+    if (G_UNLIKELY (next_parse_offset == 0 || next_parse_offset > size))
       break;
 
     data += next_parse_offset;
@@ -2487,20 +2634,22 @@ gst_matroska_mux_stop_streamheader (GstMatroskaMux * mux)
 
   streamheader_buffer = gst_ebml_stop_streamheader (ebml);
   if (!strcmp (mux->doctype, GST_MATROSKA_DOCTYPE_WEBM)) {
-    caps = gst_caps_from_string ("video/webm");
+    caps = gst_caps_new_simple ("video/webm", NULL);
   } else {
-    caps = gst_caps_from_string ("video/x-matroska");
+    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_unref (ebml->caps);
-  ebml->caps = caps;
+  gst_caps_replace (&ebml->caps, caps);
+  gst_buffer_unref (streamheader_buffer);
+  gst_caps_unref (caps);
 }
 
 /**
@@ -2567,13 +2716,20 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
   }
 
   if (mux->cluster) {
-    /* start a new cluster at every keyframe or when we may be reaching the
-     * limit of the relative timestamp */
+    /* 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) {
+        || 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;
+      }
+
       mux->prev_cluster_size = ebml->pos - mux->cluster_pos;
       mux->cluster_pos = ebml->pos;
       gst_ebml_write_set_cache (ebml, 0x20);
@@ -2582,10 +2738,10 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
       gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE,
           gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (buf), 1,
               mux->time_scale));
-      GST_WARNING_OBJECT (mux, "cluster timestamp %" G_GUINT64_FORMAT,
+      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_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);
@@ -2598,7 +2754,7 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
     mux->cluster = gst_ebml_write_master_start (ebml, GST_MATROSKA_ID_CLUSTER);
     gst_ebml_write_uint (ebml, GST_MATROSKA_ID_CLUSTERTIMECODE,
         gst_util_uint64_scale (GST_BUFFER_TIMESTAMP (buf), 1, mux->time_scale));
-    gst_ebml_write_flush_cache (ebml, TRUE);
+    gst_ebml_write_flush_cache (ebml, TRUE, GST_BUFFER_TIMESTAMP (buf));
     mux->cluster_time = GST_BUFFER_TIMESTAMP (buf);
   }
 
@@ -2614,9 +2770,10 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
    * the block in the cluster which contains the timestamp, should also work
    * for files with multiple audio tracks.
    */
-  if (is_video_keyframe ||
-      ((collect_pad->track->type == GST_MATROSKA_TRACK_TYPE_AUDIO) &&
-          (mux->num_streams == 1))) {
+  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) {
@@ -2645,9 +2802,14 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
 
   /* 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;
     }
   }
@@ -2676,7 +2838,7 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
     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_ebml_write_flush_cache (ebml, FALSE, GST_BUFFER_TIMESTAMP (buf));
     gst_ebml_write_buffer (ebml, buf);
 
     return gst_ebml_last_write_result (ebml);
@@ -2688,16 +2850,15 @@ gst_matroska_mux_write_data (GstMatroskaMux * mux, GstMatroskaPad * collect_pad)
     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,
-          gst_util_uint64_scale (block_duration, 1, mux->time_scale));
-    }
+    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_ebml_write_flush_cache (ebml, FALSE, GST_BUFFER_TIMESTAMP (buf));
     gst_ebml_write_buffer (ebml, buf);
+
     return gst_ebml_last_write_result (ebml);
   }
 }
@@ -2719,7 +2880,7 @@ gst_matroska_mux_collected (GstCollectPads * pads, gpointer user_data)
   GstEbmlWrite *ebml = mux->ebml_write;
   GstMatroskaPad *best;
   gboolean popped;
-  GstFlowReturn ret;
+  GstFlowReturn ret = GST_FLOW_OK;
 
   GST_DEBUG_OBJECT (mux, "Collected pads");
 
@@ -2743,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;