* all: Added matroska writing. Patch by Mike Matsnev.
authorLaurent Aimar <fenrir@videolan.org>
Fri, 22 Jul 2005 15:51:10 +0000 (15:51 +0000)
committerLaurent Aimar <fenrir@videolan.org>
Fri, 22 Jul 2005 15:51:10 +0000 (15:51 +0000)
git-svn-id: svn://svn.videolan.org/x264/trunk@280 df754926-b1dd-0310-bc7b-ec298dee348c

Makefile
matroska.c [new file with mode: 0755]
matroska.h [new file with mode: 0755]
x264.c

index b80570e..561aef8 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -59,8 +59,8 @@ libx264.a: .depend $(OBJS) $(OBJASM)
        ar rc libx264.a $(OBJS) $(OBJASM)
        ranlib libx264.a
 
-x264$(EXE): libx264.a x264.o
-       $(CC) -o $@ x264.o libx264.a $(LDFLAGS)
+x264$(EXE): libx264.a x264.o matroska.o
+       $(CC) -o $@ x264.o matroska.o libx264.a $(LDFLAGS)
 
 x264vfw.dll: libx264.a $(wildcard vfw/*.c vfw/*.h)
        make -C vfw/build/cygwin
@@ -74,7 +74,7 @@ checkasm: testing/checkasm.o libx264.a
 .depend: config.mak config.h
        rm -f .depend
 # Hacky - because gcc 2.9x doesn't have -MT
-       $(foreach SRC, $(SRCS) x264.c, ( echo -n "`dirname $(SRC)`/" && $(CC) $(CFLAGS) $(SRC) -MM -g0 ) 1>> .depend;)
+       $(foreach SRC, $(SRCS) x264.c matroska.c, ( echo -n "`dirname $(SRC)`/" && $(CC) $(CFLAGS) $(SRC) -MM -g0 ) 1>> .depend;)
 
 config.h: $(wildcard .svn/entries */.svn/entries */*/.svn/entries)
        ./version.sh
@@ -85,7 +85,7 @@ include .depend
 endif
 
 clean:
-       rm -f $(OBJS) $(OBJASM) config.h *.a x264.o x264 x264.exe .depend TAGS
+       rm -f $(OBJS) $(OBJASM) config.h *.a x264.o matroska.o x264 x264.exe .depend TAGS
        rm -rf vfw/build/cygwin/bin
 
 distclean: clean
diff --git a/matroska.c b/matroska.c
new file mode 100755 (executable)
index 0000000..95add2d
--- /dev/null
@@ -0,0 +1,525 @@
+/*****************************************************************************
+ * matroska.c:
+ *****************************************************************************
+ * Copyright (C) 2005 x264 project
+ * $Id: $
+ *
+ * Authors: Mike Matsnev
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ *****************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#else
+#include <inttypes.h>
+#endif
+
+#include "matroska.h"
+
+#define        CLSIZE    1048576
+
+struct mk_Context {
+  struct mk_Context *next, **prev, *parent;
+  struct mk_Writer  *owner;
+  unsigned         id;
+
+  void             *data;
+  unsigned         d_cur, d_max;
+};
+
+typedef struct mk_Context mk_Context;
+
+struct mk_Writer {
+  FILE               *fp;
+
+  unsigned           duration_ptr;
+
+  mk_Context         *root, *cluster, *frame;
+  mk_Context         *freelist;
+  mk_Context         *actlist;
+
+  int64_t            def_duration;
+  int64_t            timescale;
+  int64_t            cluster_tc_scaled;
+  int64_t            frame_tc, prev_frame_tc_scaled, max_frame_tc;
+
+  char               wrote_header, in_frame, keyframe;
+};
+
+static mk_Context *mk_createContext(mk_Writer *w, mk_Context *parent, unsigned id) {
+  mk_Context  *c;
+
+  if (w->freelist) {
+    c = w->freelist;
+    w->freelist = w->freelist->next;
+  } else {
+    c = malloc(sizeof(*c));
+    memset(c, 0, sizeof(*c));
+  }
+
+  if (c == NULL)
+    return NULL;
+
+  c->parent = parent;
+  c->owner = w;
+  c->id = id;
+
+  if (c->owner->actlist)
+    c->owner->actlist->prev = &c->next;
+  c->next = c->owner->actlist;
+  c->prev = &c->owner->actlist;
+
+  return c;
+}
+
+static int       mk_appendContextData(mk_Context *c, const void *data, unsigned size) {
+  unsigned  ns = c->d_cur + size;
+
+  if (ns > c->d_max) {
+    void      *dp;
+    unsigned  dn = c->d_max ? c->d_max << 1 : 16;
+    while (ns > dn)
+      dn <<= 1;
+
+    dp = realloc(c->data, dn);
+    if (dp == NULL)
+      return -1;
+
+    c->data = dp;
+    c->d_max = dn;
+  }
+
+  memcpy((char*)c->data + c->d_cur, data, size);
+
+  c->d_cur = ns;
+
+  return 0;
+}
+
+static void      mk_writeID(mk_Context *c, unsigned id) {
+  unsigned char          c_id[4] = { id >> 24, id >> 16, id >> 8, id };
+
+  if (c_id[0])
+    mk_appendContextData(c, c_id, 4);
+  else if (c_id[1])
+    mk_appendContextData(c, c_id+1, 3);
+  else if (c_id[2])
+    mk_appendContextData(c, c_id+2, 2);
+  else
+    mk_appendContextData(c, c_id+3, 1);
+}
+
+static void      mk_writeSize(mk_Context *c, unsigned size) {
+  unsigned char          c_size[5] = { 0x08, size >> 24, size >> 16, size >> 8, size };
+
+  if (size < 0x7f) {
+    c_size[4] |= 0x80;
+    mk_appendContextData(c, c_size+4, 1);
+  } else if (size < 0x3fff) {
+    c_size[3] |= 0x40;
+    mk_appendContextData(c, c_size+3, 2);
+  } else if (size < 0x1fffff) {
+    c_size[2] |= 0x20;
+    mk_appendContextData(c, c_size+2, 3);
+  } else if (size < 0x0fffffff) {
+    c_size[1] |= 0x10;
+    mk_appendContextData(c, c_size+1, 4);
+  } else
+    mk_appendContextData(c, c_size, 5);
+}
+
+static void      mk_flushContextID(mk_Context *c) {
+  unsigned char        ff = 0xff;
+
+  if (c->id == 0)
+    return;
+
+  mk_writeID(c->parent, c->id);
+  mk_appendContextData(c->parent, &ff, 1);
+
+  c->id = 0;
+}
+
+static void      mk_flushContextData(mk_Context *c) {
+  if (c->d_cur == 0)
+    return;
+
+  if (c->parent)
+    mk_appendContextData(c->parent, c->data, c->d_cur);
+  else
+    fwrite(c->data, c->d_cur, 1, c->owner->fp);
+
+  c->d_cur = 0;
+}
+
+static unsigned          mk_closeContext(mk_Context *c, unsigned off) {
+  if (c->id) {
+    mk_writeID(c->parent, c->id);
+    mk_writeSize(c->parent, c->d_cur);
+  }
+
+  if (c->parent)
+    off += c->parent->d_cur;
+
+  mk_flushContextData(c);
+
+  if (c->next)
+    c->next->prev = c->prev;
+  *(c->prev) = c->next;
+  c->next = c->owner->freelist;
+  c->owner->freelist = c;
+
+  return off;
+}
+
+static void      mk_destroyContexts(mk_Writer *w) {
+  mk_Context  *cur, *next;
+
+  for (cur = w->freelist; cur; cur = next) {
+    next = cur->next;
+    free(cur->data);
+    free(cur);
+  }
+
+  for (cur = w->actlist; cur; cur = next) {
+    next = cur->next;
+    free(cur->data);
+    free(cur);
+  }
+
+  w->freelist = w->actlist = w->root = NULL;
+}
+
+static void      mk_writeStr(mk_Context *c, unsigned id, const char *str) {
+  size_t  len = strlen(str);
+
+  mk_writeID(c, id);
+  mk_writeSize(c, len);
+  mk_appendContextData(c, str, len);
+}
+
+static void      mk_writeBin(mk_Context *c, unsigned id, const void *data, unsigned size) {
+  mk_writeID(c, id);
+  mk_writeSize(c, size);
+  mk_appendContextData(c, data, size);
+}
+
+static void      mk_writeUInt(mk_Context *c, unsigned id, uint64_t ui) {
+  unsigned char          c_ui[8] = { ui >> 56, ui >> 48, ui >> 40, ui >> 32, ui >> 24, ui >> 16, ui >> 8, ui };
+  unsigned       i = 0;
+
+  mk_writeID(c, id);
+  while (i < 7 && c_ui[i] == 0)
+    ++i;
+  mk_writeSize(c, 8 - i);
+  mk_appendContextData(c, c_ui+i, 8 - i);
+}
+
+static void      mk_writeSInt(mk_Context *c, unsigned id, int64_t si) {
+  unsigned char          c_si[8] = { si >> 56, si >> 48, si >> 40, si >> 32, si >> 24, si >> 16, si >> 8, si };
+  unsigned       i = 0;
+
+  mk_writeID(c, id);
+  if (si < 0)
+    while (i < 7 && c_si[i] == 0xff && c_si[i+1] & 0x80)
+      ++i;
+  else
+    while (i < 7 && c_si[i] == 0 && !(c_si[i+1] & 0x80))
+      ++i;
+  mk_writeSize(c, 8 - i);
+  mk_appendContextData(c, c_si+i, 8 - i);
+}
+
+static void      mk_writeFloatRaw(mk_Context *c, float f) {
+  union {
+    float f;
+    unsigned u;
+  } u;
+  unsigned char        c_f[4];
+
+  u.f = f;
+  c_f[0] = u.u >> 24;
+  c_f[1] = u.u >> 16;
+  c_f[2] = u.u >> 8;
+  c_f[3] = u.u;
+
+  mk_appendContextData(c, c_f, 4);
+}
+
+static void      mk_writeFloat(mk_Context *c, unsigned id, float f) {
+  mk_writeID(c, id);
+  mk_writeSize(c, 4);
+  mk_writeFloatRaw(c, f);
+}
+
+static unsigned          mk_ebmlSizeSize(unsigned s) {
+  if (s < 0x7f)
+    return 1;
+  if (s < 0x3fff)
+    return 2;
+  if (s < 0x1fffff)
+    return 3;
+  if (s < 0x0fffffff)
+    return 4;
+  return 5;
+}
+
+static unsigned          mk_ebmlSIntSize(int64_t si) {
+  unsigned char          c_si[8] = { si >> 56, si >> 48, si >> 40, si >> 32, si >> 24, si >> 16, si >> 8, si };
+  unsigned       i = 0;
+
+  if (si < 0)
+    while (i < 7 && c_si[i] == 0xff && c_si[i+1] & 0x80)
+      ++i;
+  else
+    while (i < 7 && c_si[i] == 0 && !(c_si[i+1] & 0x80))
+      ++i;
+
+  return 8 - i;
+}
+
+mk_Writer *mk_createWriter(const char *filename) {
+  mk_Writer *w = malloc(sizeof(*w));
+  if (w == NULL)
+    return NULL;
+
+  memset(w, 0, sizeof(*w));
+
+  w->root = mk_createContext(w, NULL, 0);
+  if (w->root == NULL) {
+    free(w);
+    return NULL;
+  }
+
+  w->fp = fopen(filename, "wb");
+  if (w->fp == NULL) {
+    mk_destroyContexts(w);
+    free(w);
+    return NULL;
+  }
+
+  w->timescale = 1000000;
+
+  return w;
+}
+
+int      mk_writeHeader(mk_Writer *w, const char *writingApp,
+                        const char *codecID,
+                        const void *codecPrivate, unsigned codecPrivateSize,
+                        int64_t default_frame_duration,
+                        int64_t timescale,
+                        unsigned width, unsigned height,
+                        unsigned d_width, unsigned d_height)
+{
+  mk_Context  *c, *ti, *v;
+
+  if (w->wrote_header)
+    return -1;
+
+  w->timescale = timescale;
+  w->def_duration = default_frame_duration;
+
+  if ((c = mk_createContext(w, w->root, 0x1a45dfa3)) == NULL) // EBML
+    return -1;
+  mk_writeUInt(c, 0x4286, 1); // EBMLVersion
+  mk_writeUInt(c, 0x42f7, 1); // EBMLReadVersion
+  mk_writeUInt(c, 0x42f2, 4); // EBMLMaxIDLength
+  mk_writeUInt(c, 0x42f3, 8); // EBMLMaxSizeLength
+  mk_writeStr(c, 0x4282, "matroska"); // DocType
+  mk_writeUInt(c, 0x4287, 1); // DocTypeVersion
+  mk_writeUInt(c, 0x4285, 1); // DocTypeReadversion
+  mk_closeContext(c, 0);
+
+  if ((c = mk_createContext(w, w->root, 0x18538067)) == NULL) // Segment
+    return -1;
+  mk_flushContextID(c);
+  mk_closeContext(c, 0);
+
+  if ((c = mk_createContext(w, w->root, 0x1549a966)) == NULL) // SegmentInfo
+    return -1;
+  mk_writeStr(c, 0x4d80, "Haali Matroska Writer b0");
+  mk_writeStr(c, 0x5741, writingApp);
+  mk_writeUInt(c, 0x2ad7b1, w->timescale);
+  mk_writeFloat(c, 0x4489, 0);
+  w->duration_ptr = mk_closeContext(c, c->d_cur - 4);
+
+  if ((c = mk_createContext(w, w->root, 0x1654ae6b)) == NULL) // tracks
+    return -1;
+  if ((ti = mk_createContext(w, c, 0xae)) == NULL) // TrackEntry
+    return -1;
+  mk_writeUInt(ti, 0xd7, 1); // TrackNumber
+  mk_writeUInt(ti, 0x73c5, 1); // TrackUID
+  mk_writeUInt(ti, 0x83, 1); // TrackType
+  mk_writeUInt(ti, 0x9c, 0); // FlagLacing
+  mk_writeStr(ti, 0x86, codecID); // CodecID
+  if (codecPrivateSize)
+    mk_writeBin(ti, 0x63a2, codecPrivate, codecPrivateSize); // CodecPrivate
+  if (default_frame_duration)
+    mk_writeUInt(ti, 0x23e383, default_frame_duration); // DefaultDuration
+
+  if ((v = mk_createContext(w, ti, 0xe0)) == NULL) // Video
+    return -1;
+  mk_writeUInt(v, 0xb0, width);
+  mk_writeUInt(v, 0xba, height);
+  mk_writeUInt(v, 0x54b0, d_width);
+  mk_writeUInt(v, 0x54ba, d_height);
+  mk_closeContext(v, 0);
+
+  mk_closeContext(ti, 0);
+
+  mk_closeContext(c, 0);
+
+  mk_flushContextData(w->root);
+
+  w->wrote_header = 1;
+
+  return 0;
+}
+
+static void mk_closeCluster(mk_Writer *w) {
+  if (w->cluster == NULL)
+    return;
+  mk_closeContext(w->cluster, 0);
+  w->cluster = NULL;
+  mk_flushContextData(w->root);
+}
+
+int      mk_flushFrame(mk_Writer *w)
+{
+  int64_t      delta, ref = 0;
+  unsigned     fsize, bgsize;
+  unsigned char        c_delta_flags[3];
+
+  if (!w->in_frame)
+    return 0;
+
+  delta = w->frame_tc/w->timescale - w->cluster_tc_scaled;
+  if (delta > 32767ll || delta < -32768ll)
+    mk_closeCluster(w);
+
+  if (w->cluster == NULL) {
+    w->cluster_tc_scaled = w->frame_tc / w->timescale;
+    w->cluster = mk_createContext(w, w->root, 0x1f43b675); // Cluster
+    if (w->cluster == NULL)
+      return -1;
+
+    mk_writeUInt(w->cluster, 0xe7, w->cluster_tc_scaled); // Timecode
+
+    delta = 0;
+  }
+
+  fsize = w->frame ? w->frame->d_cur : 0;
+  bgsize = fsize + 4 + mk_ebmlSizeSize(fsize + 4) + 1;
+  if (!w->keyframe) {
+    ref = w->prev_frame_tc_scaled - w->cluster_tc_scaled - delta;
+    bgsize += 1 + 1 + mk_ebmlSIntSize(ref);
+  }
+
+  mk_writeID(w->cluster, 0xa0); // BlockGroup
+  mk_writeSize(w->cluster, bgsize);
+  mk_writeID(w->cluster, 0xa1); // Block
+  mk_writeSize(w->cluster, fsize + 4);
+  mk_writeSize(w->cluster, 1); // track number
+
+  c_delta_flags[0] = delta >> 8;
+  c_delta_flags[1] = delta;
+  c_delta_flags[2] = 0;
+  mk_appendContextData(w->cluster, c_delta_flags, 3);
+  if (w->frame) {
+    mk_appendContextData(w->cluster, w->frame->data, w->frame->d_cur);
+    w->frame->d_cur = 0;
+  }
+  if (!w->keyframe)
+    mk_writeSInt(w->cluster, 0xfb, ref); // ReferenceBlock
+
+  w->in_frame = 0;
+  w->prev_frame_tc_scaled = w->cluster_tc_scaled + delta;
+
+  if (w->cluster->d_cur > CLSIZE)
+    mk_closeCluster(w);
+
+  return 0;
+}
+
+int      mk_startFrame(mk_Writer *w) {
+  if (mk_flushFrame(w) < 0)
+    return -1;
+
+  w->in_frame = 1;
+  w->keyframe = 0;
+
+  return 0;
+}
+
+int      mk_setFrameFlags(mk_Writer *w,int64_t timestamp, int keyframe) {
+  if (!w->in_frame)
+    return -1;
+
+  w->frame_tc = timestamp;
+  w->keyframe = keyframe != 0;
+
+  if (w->max_frame_tc < timestamp)
+    w->max_frame_tc = timestamp;
+
+  return 0;
+}
+
+int      mk_addFrameData(mk_Writer *w, const void *data, unsigned size) {
+  if (!w->in_frame)
+    return -1;
+
+  if (w->frame == NULL)
+    if ((w->frame = mk_createContext(w, NULL, 0)) == NULL)
+      return -1;
+
+  return mk_appendContextData(w->frame, data, size);
+}
+
+void     mk_close(mk_Writer *w) {
+  mk_flushFrame(w);
+  mk_closeCluster(w);
+  if (w->wrote_header) {
+    fseek(w->fp, w->duration_ptr, SEEK_SET);
+    mk_writeFloatRaw(w->root, (float)((double)(w->max_frame_tc+w->def_duration) / w->timescale));
+    mk_flushContextData(w->root);
+  }
+  mk_destroyContexts(w);
+  fclose(w->fp);
+  free(w);
+}
+
+#ifdef TESTING
+int   main(int argc, char **argv) {
+  char   f[4] = { 1, 2, 3, 4 };
+  mk_Writer *w = mk_createWriter("foo.mkv");
+  mk_writeHeader(w, "mktest", "V_MPEG/ISO/AVC", NULL, 0, 40000000, 352, 288, 3, 4);
+  mk_startFrame(w);
+  mk_setFrameFlags(w, 0, 1);
+  mk_addFrameData(w, f, 1);
+  mk_startFrame(w);
+  mk_setFrameFlags(w, 40000000, 0);
+  mk_addFrameData(w, f, 2);
+  mk_startFrame(w);
+  mk_setFrameFlags(w, 80000000, 0);
+  mk_addFrameData(w, f, 4);
+  mk_close(w);
+  return 0;
+}
+#endif
diff --git a/matroska.h b/matroska.h
new file mode 100755 (executable)
index 0000000..61a6223
--- /dev/null
@@ -0,0 +1,44 @@
+/*****************************************************************************
+ * matroska.c:
+ *****************************************************************************
+ * Copyright (C) 2005 x264 project
+ * $Id: $
+ *
+ * Authors: Mike Matsnev
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
+ *****************************************************************************/
+
+#ifndef _MATROSKA_H
+#define _MATROSKA_H 1
+
+typedef struct mk_Writer mk_Writer;
+
+mk_Writer *mk_createWriter( const char *filename );
+
+int  mk_writeHeader( mk_Writer *w, const char *writingApp,
+                     const char *codecID,
+                     const void *codecPrivate, unsigned codecPrivateSize,
+                     int64_t default_frame_duration,
+                     int64_t timescale,
+                     unsigned width, unsigned height,
+                     unsigned d_width, unsigned d_height );
+
+int  mk_startFrame( mk_Writer *w );
+int  mk_addFrameData( mk_Writer *w, const void *data, unsigned size );
+int  mk_setFrameFlags( mk_Writer *w, int64_t timestamp, int keyframe );
+void mk_close( mk_Writer *w );
+
+#endif
diff --git a/x264.c b/x264.c
index f0ba4b8..d580f48 100644 (file)
--- a/x264.c
+++ b/x264.c
 #include <gpac/m4_author.h>
 #endif
 
+
 #include "common/common.h"
 #include "x264.h"
+
 #ifndef _MSC_VER
 #include "config.h"
 #endif
 
+#include "matroska.h"
+
 #define DATA_MAX 3000000
 uint8_t data[DATA_MAX];
 
@@ -114,6 +118,12 @@ static int set_eop_mp4( hnd_t handle, x264_picture_t *p_picture );
 static int close_file_mp4( hnd_t handle );
 #endif
 
+static int open_file_mkv( char *psz_filename, hnd_t *p_handle );
+static int set_param_mkv( hnd_t handle, x264_param_t *p_param );
+static int write_nalu_mkv( hnd_t handle, uint8_t *p_nal, int i_size );
+static int set_eop_mkv( hnd_t handle, x264_picture_t *p_picture );
+static int close_file_mkv( hnd_t handle );
+
 static void Help( x264_param_t *defaults );
 static int  Parse( int argc, char **argv, x264_param_t *param, cli_opt_t *opt );
 static int  Encode( x264_param_t *param, cli_opt_t *opt );
@@ -157,6 +167,7 @@ static void Help( x264_param_t *defaults )
              "  or AVI or Avisynth if compiled with AVIS support (%s).\n"
              "Outfile type is selected by filename:\n"
              " .264 -> Raw bytestream\n"
+             " .mkv -> Matroska\n"
              " .mp4 -> MP4 if compiled with GPAC support (%s)\n"
              "\n"
              "Options:\n"
@@ -570,6 +581,14 @@ static int  Parse( int argc, char **argv,
                     return -1;
 #endif
                 }
+        else if( !strncasecmp(optarg + strlen(optarg) - 4, ".mkv", 4) )
+        {
+            p_open_outfile = open_file_mkv;
+                    p_write_nalu = write_nalu_mkv;
+                    p_set_outfile_param = set_param_mkv;
+                    p_set_eop = set_eop_mkv;
+                    p_close_outfile = close_file_mkv;
+        }
                 if( !strcmp(optarg, "-") )
                     opt->hout = stdout;
                 else if( p_open_outfile( optarg, &opt->hout ) )
@@ -1573,3 +1592,230 @@ static int set_eop_mp4( hnd_t handle, x264_picture_t *p_picture )
 }
 
 #endif
+
+
+/* -- mkv muxing support ------------------------------------------------- */
+typedef struct
+{
+    mk_Writer *w;
+
+    uint8_t   *sps, *pps;
+    int       sps_len, pps_len;
+
+    int       width, height, d_width, d_height;
+
+    int64_t   frame_duration;
+    int       fps_num;
+
+    int       b_header_written;
+    char      b_writing_frame;
+} mkv_t;
+
+static int write_header_mkv( mkv_t *p_mkv )
+{
+    int       ret;
+    uint8_t   *avcC;
+    int  avcC_len;
+
+    if( p_mkv->sps == NULL || p_mkv->pps == NULL ||
+        p_mkv->width == 0 || p_mkv->height == 0 ||
+        p_mkv->d_width == 0 || p_mkv->d_height == 0)
+        return -1;
+
+    avcC_len = 5 + 1 + 2 + p_mkv->sps_len + 1 + 2 + p_mkv->pps_len;
+    avcC = malloc(avcC_len);
+    if (avcC == NULL)
+        return -1;
+
+    avcC[0] = 1;
+    avcC[1] = p_mkv->sps[1];
+    avcC[2] = p_mkv->sps[2];
+    avcC[3] = p_mkv->sps[3];
+    avcC[4] = 0xfe; // nalu size length is three bytes
+    avcC[5] = 0xe1; // one sps
+
+    avcC[6] = p_mkv->sps_len >> 8;
+    avcC[7] = p_mkv->sps_len;
+
+    memcpy(avcC+8, p_mkv->sps, p_mkv->sps_len);
+
+    avcC[8+p_mkv->sps_len] = 1; // one pps
+    avcC[9+p_mkv->sps_len] = p_mkv->pps_len >> 8;
+    avcC[10+p_mkv->sps_len] = p_mkv->pps_len;
+
+    memcpy( avcC+11+p_mkv->sps_len, p_mkv->pps, p_mkv->pps_len );
+
+    ret = mk_writeHeader( p_mkv->w, "x264", "V_MPEG4/ISO/AVC",
+                          avcC, avcC_len, p_mkv->frame_duration, 50000,
+                          p_mkv->width, p_mkv->height,
+                          p_mkv->d_width, p_mkv->d_height );
+
+    free( avcC );
+
+    p_mkv->b_header_written = 1;
+
+    return ret;
+}
+
+static int open_file_mkv( char *psz_filename, hnd_t *p_handle )
+{
+    mkv_t *p_mkv;
+
+    *p_handle = NULL;
+
+    p_mkv  = malloc(sizeof(*p_mkv));
+    if (p_mkv == NULL)
+        return -1;
+
+    memset(p_mkv, 0, sizeof(*p_mkv));
+
+    p_mkv->w = mk_createWriter(psz_filename);
+    if (p_mkv->w == NULL)
+    {
+        free(p_mkv);
+        return -1;
+    }
+
+    *p_handle = p_mkv;
+
+    return 0;
+}
+
+static int set_param_mkv( hnd_t handle, x264_param_t *p_param )
+{
+    mkv_t   *p_mkv = handle;
+    int64_t dw, dh;
+
+    if( p_param->i_fps_num > 0 )
+    {
+        p_mkv->frame_duration = (int64_t)p_param->i_fps_den *
+                                (int64_t)1000000000 / p_param->i_fps_num;
+        p_mkv->fps_num = p_param->i_fps_num;
+    }
+    else
+    {
+        p_mkv->frame_duration = 0;
+        p_mkv->fps_num = 1;
+    }
+
+    p_mkv->width = p_param->i_width;
+    p_mkv->height = p_param->i_height;
+
+    if( p_param->vui.i_sar_width && p_param->vui.i_sar_height )
+    {
+        dw = (int64_t)p_param->i_width  * p_param->vui.i_sar_width;
+        dh = (int64_t)p_param->i_height * p_param->vui.i_sar_height;
+    }
+    else
+    {
+        dw = p_param->i_width;
+        dh = p_param->i_height;
+    }
+
+    if( dw > 0 && dh > 0 )
+    {
+        for (;;)
+        {
+            int64_t c = dw % dh;
+            if( c == 0 )
+              break;
+            dw = dh;
+            dh = c;
+        }
+    }
+
+    p_mkv->d_width = (int)dw;
+    p_mkv->d_height = (int)dh;
+
+    return 0;
+}
+
+static int write_nalu_mkv( hnd_t handle, uint8_t *p_nalu, int i_size )
+{
+    mkv_t *p_mkv = handle;
+    uint8_t type = p_nalu[4] & 0x1f;
+    uint8_t dsize[3];
+    int psize;
+
+    switch( type )
+    {
+    // sps
+    case 0x07:
+        if( !p_mkv->sps )
+        {
+            p_mkv->sps = malloc(i_size - 4);
+            if (p_mkv->sps == NULL)
+                return -1;
+            p_mkv->sps_len = i_size - 4;
+            memcpy(p_mkv->sps, p_nalu + 4, i_size - 4);
+        }
+        break;
+
+    // pps
+    case 0x08:
+        if( !p_mkv->pps )
+        {
+            p_mkv->pps = malloc(i_size - 4);
+            if (p_mkv->pps == NULL)
+                return -1;
+            p_mkv->pps_len = i_size - 4;
+            memcpy(p_mkv->pps, p_nalu + 4, i_size - 4);
+        }
+        break;
+
+    // slice, sei
+    case 0x1:
+    case 0x5:
+    case 0x6:
+        if (!p_mkv->b_writing_frame)
+        {
+            p_mkv->b_writing_frame = 1;
+            mk_startFrame(p_mkv->w);
+        }
+        psize = i_size - 4 ;
+        dsize[0] = psize >> 16;
+        dsize[1] = psize >> 8;
+        dsize[2] = psize;
+        mk_addFrameData(p_mkv->w, dsize, 3);
+        mk_addFrameData(p_mkv->w, p_nalu + 4, i_size - 4);
+        break;
+
+    default:
+        break;
+    }
+
+    if( !p_mkv->b_header_written && p_mkv->pps && p_mkv->sps &&
+        !write_header_mkv(p_mkv) )
+        return -1;
+
+    return i_size;
+}
+
+static int set_eop_mkv( hnd_t handle, x264_picture_t *p_picture )
+{
+    mkv_t *p_mkv = handle;
+    int64_t i_stamp = (int64_t)p_picture->i_pts * (int64_t)1000000000 /
+                      p_mkv->fps_num;
+
+    p_mkv->b_writing_frame = 0;
+
+    return mk_setFrameFlags( p_mkv->w, i_stamp,
+                             p_picture->i_type == X264_TYPE_IDR );
+}
+
+static int close_file_mkv( hnd_t handle )
+{
+    mkv_t *p_mkv = handle;
+
+    if( p_mkv->sps )
+        free( p_mkv->sps );
+    if( p_mkv->pps )
+        free( p_mkv->pps );
+
+    mk_close(p_mkv->w);
+
+    free( p_mkv );
+
+    return 0;
+}
+