Simple MKV splitter
I finished off this task which was left a couple of weeks ago. This splitter, virtually a primitive parser, draws recoginizable tracks out from MKV files, esp. helpful to get the subtitles. Reference: http://www.matroska.org/
The code is as follows:
#include <stdio.h>
typedef enum
{
/* Table 1 */
k_EMBLVersion = 0x4286,
k_EMBLReadVersion = 0x42f7,
k_EMBLMaxIdLength = 0x42f2,
k_EMBLMaxSizeLength = 0x42f3,
k_DocType = 0x4282,
k_DocTypeVersion = 0x4287,
k_DocTypeReadVersion = 0x4285,
/* Table 2 */
k_SegmentInfo = 0x1549a966,
k_SeekHead = 0x114d9b74,
k_Cluster = 0x1f43b675,
k_Tracks = 0x1654ae6b,
k_Cues = 0x1c53bb6b,
k_Attachments = 0x1941a469,
k_Chapters = 0x1043a770,
k_Tags = 0x1254c367,
/* Table 3 */
k_SegmentUID = 0x73a4,
k_SegmentFileName = 0x7384,
k_PrevUID = 0x3cb923,
k_PrevFileName = 0x3c83ab,
k_NextUID = 0x3eb923,
k_NextFileName = 0x3e83bb,
k_TimeCodeScale = 0x2ad7b1,
k_Duration = 0x4489,
k_Title = 0x7ba9,
k_MuxingApp = 0x4d80,
k_DateUTC = 0x4461,
/* Table 4 */
k_Seek = 0x4dbb,
/* Table 5 */
k_SeekId = 0x53ab,
k_SeekPosition = 0x53ac,
/* Table 6 */
k_TrackEntry = 0xae,
/* Table 7 */
k_TrackNumber = 0xd7,
k_TrackUID = 0x73c5,
k_TrackType = 0x83,
k_FlagEnabled = 0xb9,
k_FlagDefault = 0x88,
k_FlagForced = 0x55aa,
k_FlagLacing = 0x9c,
k_MinCache = 0x6de7,
k_MaxCache = 0x6df8,
k_DefaultDuration = 0x23e383,
k_TrackTimeCodeScale = 0x23314f,
k_Name = 0x536e,
k_Language = 0x22b59c,
k_CodecId = 0x86,
k_CodecPrivate = 0x63a2,
k_CodecName = 0x258688,
k_AttachmentLink = 0x7446,
k_Video = 0xe0,
k_Audio = 0xe1,
k_ContentEncodings = 0x6d80,
/* Table 8 */
k_PixelWidth = 0xb0,
k_PixelHeight = 0xba,
k_PixelCropBottom = 0x54aa,
k_PixelCropTop = 0x54bb,
k_PixelCropLeft = 0x54cc,
k_PixelCropRight = 0x54dd,
k_DisplayWidth = 0x54b0,
k_DisplayHeight = 0x54ba,
k_DisplayUnit = 0x54b2,
/* Table 9 */
k_SampleFrequency = 0xb5,
k_OutputSamplingFrequency = 0x78b5,
k_Channels = 0x9f,
k_BitDepth = 0x6264,
/* Table 10 */
k_ContentEncoding = 0x6240,
/* Table 11 */
k_ContentEncodingOrder = 0x5031,
k_ContentEncodingScope = 0x5032,
k_ContentEncodingType = 0x5033,
k_ContentCompression = 0x5034,
k_ContentEncryption = 0x5035,
/* Table 12 */
k_ContentCompAlgo = 0x4254,
k_ContentCompSettings = 0x4255,
/* Table 16 */
k_TimeCode = 0xe7,
k_Position = 0xa7,
k_PrevSize = 0xab,
k_BlockGroup = 0xa0,
k_SimpleBlock = 0xa3,
/* Table 17 */
k_Block = 0xa1,
k_ReferenceBlock = 0xfb,
k_BlockDuration = 0x9b,
/* Table 18 */
k_CuePoint = 0xbb,
/* Table 19 */
k_CueTime = 0xb3,
k_CueTrackPositions = 0xb7,
/* Table 20 */
k_CueTrack = 0xf7,
k_CueClusterPosition = 0xf1,
k_CueBlockNumber = 0x5378,
/* Table 21 */
k_EditionEntry = 0x45b9,
/* Table 22: */
k_EditionUID = 0x45bc,
k_EditionFlagHidden = 0x45bd,
k_EditionFlagDefault = 0x45db,
k_EditionFlagOrdered = 0x45dd,
k_ChapterAtom = 0xb6,
/* Table 23 */
k_ChapterUID = 0x73c4,
k_ChapterTimeStart = 0x91,
k_ChapterTimeEnd = 0x92,
k_ChapterFlagHidden = 0x98,
k_ChapterFlagEnabled = 0x4598,
k_ChapterSegmentUID = 0x6e67,
k_ChapterSegmentEditionUID = 0x6ebc,
k_ChapterTracks = 0x8f,
k_ChapterDisplay = 0x80,
/* Table 24 */
k_ChapterTrackNumber = 0x89,
/* Table 25 */
k_ChapString = 0x85,
k_ChapLanguage = 0x437c,
k_ChapCountry = 0x437e,
/* Table 26 */
k_AttachedFile = 0x61a7,
/* Table 27 */
k_FileDescription = 0x467e,
k_FileName = 0x466e,
k_FileMimeType = 0x4660,
k_FileData = 0x465c,
k_FileUID = 0x46ae,
/* Table 28 */
k_Tag = 0x7373,
/* Table 29 */
k_Targets = 0x63c0,
k_SimpleTag = 0x67c8,
/* Table 30 */
k_TargetTypeValue = 0x68ca,
k_TargetType = 0x63ca,
k_TargetUID = 0x63c5,
k_TargetEditionUID = 0x63c9,
k_TargetChapterUID = 0x63c4,
/* Table 31 */
k_TagName = 0x45a3,
k_TagLanguage = 0x447a,
k_TagOriginal = 0x4484,
k_TagString = 0x4487,
k_TagBinary = 0x4485,
} ElemId;
typedef unsigned char uint8_t;
typedef unsigned char sint8_t;
typedef unsigned char uint16_t;
typedef unsigned char sint16_t;
typedef unsigned long uint32_t;
typedef signed long sint32_t;
typedef int (*mkv_srng_proc)(void *, int start, int *end);
typedef int (*mkv_read_proc)(void *, int size, uint8_t *buf);
typedef int (*mkv_seek_proc)(void *, int offset);
typedef struct _mkvparser_t
{
void * read_info;
mkv_read_proc read;
mkv_seek_proc seek;
mkv_srng_proc set_range;
} mkvparser_t;
typedef enum _track_type_t
{
k_VideoTrack = 0x01,
k_AudioTrack = 0x02,
k_ComplexTrack = 0x03,
k_LogoTrack = 0x10,
k_SubtitleTrack = 0x11,
k_ButtonTrack = 0x12,
k_ControlTrack = 0x20,
} track_type_t;
#define CHECK_READ(size, buf) if(parser->read(parser->read_info, size, buf) != size){return -1;}
typedef struct _uint64_t
{
uint32_t hi;
uint32_t lo;
} uint64_t;
typedef struct _sint64_t
{
sint32_t hi;
uint32_t lo;
} sint64_t;
typedef struct _mkv_elem_t
{
uint32_t id; /* elem id */
uint8_t id_len; /* number of bytes occupied by elem id */
uint8_t size_buf_len; /* number of bytes occupied by size */
uint32_t data_size; /* size of data */
uint32_t data_addr; /* absolute offset of the data from the file beginning */
} mkv_elem_t;
//////////////////////////////
// util
int read_sint16 (mkvparser_t *parser, sint16_t *val)
{
uint8_t buf[2];
uint16_t tmp;
if (parser->read(parser->read_info, 2, buf) != 2)
{
return -1;
}
tmp = buf[0]<<8;
tmp |= buf[1];
*val = (sint16_t)tmp;
return 2;
}
int read_int_withlen (mkvparser_t *parser, int len, uint32_t *val)
{
uint8_t buf[4];
uint32_t tmp = 0;
int i;
len = len < 4? len : 4;
len = parser->read(parser->read_info, len, buf);
for (i = 0; i < len; i++)
{
tmp <<= 8;
tmp |= buf[i];
}
*val = tmp;
return len;
}
int read_int8 (mkvparser_t *parser, uint8_t *val)
{
if (parser->read(parser->read_info, 1, val) != 1)
{
return -1;
}
return 1;
}
int read_vint (mkvparser_t *parser, uint64_t *val)
{
int i;
int code_len;
uint8_t id_buf[4];
CHECK_READ(1, id_buf);
if (id_buf[0] & 0x80)
{ /* class A */
code_len = 1;
id_buf[0] &= 0x7f;
}
else if (id_buf[0] & 0x40)
{ /* class B */
CHECK_READ(1, id_buf + 1);
code_len = 2;
id_buf[0] &= 0x3f;
}
else if (id_buf[0] & 0x20)
{ /* class C */
CHECK_READ(2, id_buf + 1);
code_len = 3;
id_buf[0] &= 0x1f;
}
else if (id_buf[0] & 0x10)
{ /* class D */
CHECK_READ(3, id_buf + 1);
code_len = 4;
id_buf[0] &= 0x0f;
}
else if (id_buf[0] & 0x08)
{ /* class B */
CHECK_READ(4, id_buf + 1);
code_len = 5;
id_buf[0] &= 0x07;
}
else if (id_buf[0] & 0x04)
{ /* class C */
CHECK_READ(5, id_buf + 1);
code_len = 6;
id_buf[0] &= 0x03;
}
else if (id_buf[0] & 0x02)
{ /* class D */
CHECK_READ(6, id_buf + 1);
code_len = 7;
id_buf[0] &= 0x01;
}
else if (id_buf[0] & 0x01)
{ /* class D */
CHECK_READ(7, id_buf + 1);
code_len = 8;
id_buf[0] = 0;
}
else
{ /* error */
return -2;
}
val->hi = 0;
val->lo = 0;
i = 0;
if (code_len > 4)
{
for ( ; i < code_len - 4; i++)
{
val->hi <<= 8;
val->hi |= id_buf[i];
}
}
for (; i < code_len; i++)
{
val->lo <<= 8;
val->lo |= id_buf[i];
}
return code_len;
}
int read_svint (mkvparser_t *parser, sint64_t *val)
{
static sint32_t vsint_subtr_lo[] = {0x3f, 0x1fff, 0xfffff, 0x7ffffff};
static sint32_t vsint_subtr_hi[] = {0x3, 0x1ff, 0xffff, 0x7fffff};
int code_len = read_vint(parser, (uint64_t *)val);
if (code_len <= 0)
{
return code_len;
}
if (code_len < 4)
{
val->lo -= vsint_subtr_lo[code_len];
if (val->lo & 0x80000000)
{
val->hi = -1;
}
}
else
{
val->hi -= vsint_subtr_hi[code_len - 4];
val->lo -= 0xffffffff;
if (val->lo != 0)
{
val->hi--;
}
}
return code_len;
}
//////////////////////////////
// elem
char * get_elem_name (mkv_elem_t *elem)
{
switch (elem->id)
{
/* Table 1 */
case k_EMBLVersion: return "EMBLVersion";
case k_EMBLReadVersion: return "EMBLReadVersion";
case k_EMBLMaxIdLength: return "EMBLMaxIdLength";
case k_EMBLMaxSizeLength: return "EMBLMaxSizeLength";
case k_DocType: return "DocType";
case k_DocTypeVersion: return "DocTypeVersion";
case k_DocTypeReadVersion: return "DocTypeReadVersion";
/* Table 2 */
case k_SegmentInfo: return "SegmentInfo";
case k_SeekHead: return "SeekHead";
case k_Cluster: return "Cluster";
case k_Tracks: return "Tracks";
case k_Cues: return "Cues";
case k_Attachments: return "Attachments";
case k_Chapters: return "Chapters";
case k_Tags: return "Tags";
/* Table 3 */
case k_SegmentUID: return "SegmentUID";
case k_SegmentFileName: return "SegmentFileName";
case k_PrevUID: return "PrevUID";
case k_PrevFileName: return "PrevFileName";
case k_NextUID: return "NextUID";
case k_NextFileName: return "NextFileName";
case k_TimeCodeScale: return "TimeCodeScale";
case k_Duration: return "Duration";
case k_Title: return "Title";
case k_MuxingApp: return "MuxingApp";
case k_DateUTC: return "DateUTC";
/* Table 4 */
case k_Seek: return "Seek";
/* Table 5 */
case k_SeekId: return "SeekId";
case k_SeekPosition: return "SeekPosition";
/* Table 6 */
case k_TrackEntry: return "TrackEntry";
/* Table 7 */
case k_TrackNumber: return "TrackNumber";
case k_TrackUID: return "TrackUID";
case k_TrackType: return "TrackType";
case k_FlagEnabled: return "FlagEnabled";
case k_FlagDefault: return "FlagDefault";
case k_FlagForced: return "FlagForced";
case k_FlagLacing: return "FlagLacing";
case k_MinCache: return "MinCache";
case k_MaxCache: return "MaxCache";
case k_DefaultDuration: return "DefaultDuration";
case k_TrackTimeCodeScale: return "TrackTimeCodeScale";
case k_Name: return "Name";
case k_Language: return "Language";
case k_CodecId: return "CodecId";
case k_CodecPrivate: return "CodecPrivate";
case k_CodecName: return "CodecName";
case k_AttachmentLink: return "AttachmentLink";
case k_Video: return "Video";
case k_Audio: return "Audio";
case k_ContentEncodings: return "ContentEncodings";
/* Table 8 */
case k_PixelWidth: return "PixelWidth";
case k_PixelHeight: return "PixelHeight";
case k_PixelCropBottom: return "PixelCropBottom";
case k_PixelCropTop: return "PixelCropTop";
case k_PixelCropLeft: return "PixelCropLeft";
case k_PixelCropRight: return "PixelCropRight";
case k_DisplayWidth: return "DisplayWidth";
case k_DisplayHeight: return "DisplayHeight";
case k_DisplayUnit: return "DisplayUnit";
/* Table 9 */
case k_SampleFrequency: return "SampleFrequency";
case k_OutputSamplingFrequency: return "OutputSamplingFrequency";
case k_Channels: return "Channels";
case k_BitDepth: return "BitDepth";
/* Table 10 */
case k_ContentEncoding: return "ContentEncoding";
/* Table 11 */
case k_ContentEncodingOrder: return "ContentEncodingOrder";
case k_ContentEncodingScope: return "ContentEncodingScope";
case k_ContentEncodingType: return "ContentEncodingType";
case k_ContentCompression: return "ContentCompression";
case k_ContentEncryption: return "ContentEncryption";
/* Table 12 */
case k_ContentCompAlgo: return "ContentCompAlgo";
case k_ContentCompSettings: return "ContentCompSettings";
/* Table 16 */
case k_TimeCode: return "TimeCode";
case k_Position: return "Position";
case k_PrevSize: return "PrevSize";
case k_BlockGroup: return "BlockGroup";
case k_SimpleBlock: return "SimpleBlock";
/* Table 17 */
case k_Block: return "Block";
case k_ReferenceBlock: return "ReferenceBlock";
case k_BlockDuration: return "BlockDuration";
/* Table 18 */
case k_CuePoint: return "CuePoint";
/* Table 19 */
case k_CueTime: return "CueTime";
case k_CueTrackPositions: return "CueTrackPositions";
/* Table 20 */
case k_CueTrack: return "CueTrack";
case k_CueClusterPosition: return "CueClusterPosition";
case k_CueBlockNumber: return "CueBlockNumber";
/* Table 21 */
case k_EditionEntry: return "EditionEntry";
/* Table 22: */
case k_EditionUID: return "EditionUID";
case k_EditionFlagHidden: return "EditionFlagHidden";
case k_EditionFlagDefault: return "EditionFlagDefault";
case k_EditionFlagOrdered: return "EditionFlagOrdered";
case k_ChapterAtom: return "ChapterAtom";
/* Table 23 */
case k_ChapterUID: return "ChapterUID";
case k_ChapterTimeStart: return "ChapterTimeStart";
case k_ChapterTimeEnd: return "ChapterTimeEnd";
case k_ChapterFlagHidden: return "ChapterFlagHidden";
case k_ChapterFlagEnabled: return "ChapterFlagEnabled";
case k_ChapterSegmentUID: return "ChapterSegmentUID";
case k_ChapterSegmentEditionUID: return "ChapterSegmentEditionUID";
case k_ChapterTracks: return "ChapterTracks";
case k_ChapterDisplay: return "ChapterDisplay";
/* Table 24 */
case k_ChapterTrackNumber: return "ChapterTrackNumber";
/* Table 25 */
case k_ChapString: return "ChapString";
case k_ChapLanguage: return "ChapLanguage";
case k_ChapCountry: return "ChapCountry";
/* Table 26 */
case k_AttachedFile: return "AttachedFile";
/* Table 27 */
case k_FileDescription: return "FileDescription";
case k_FileName: return "FileName";
case k_FileMimeType: return "FileMimeType";
case k_FileData: return "FileData";
case k_FileUID: return "FileUID";
/* Table 28 */
case k_Tag: return "Tag";
/* Table 29 */
case k_Targets: return "Targets";
case k_SimpleTag: return "SimpleTag";
/* Table 30 */
case k_TargetTypeValue: return "TargetTypeValue";
case k_TargetType: return "TargetType";
case k_TargetUID: return "TargetUID";
case k_TargetEditionUID: return "TargetEditionUID";
case k_TargetChapterUID: return "TargetChapterUID";
/* Table 31 */
case k_TagName: return "TagName";
case k_TagLanguage: return "TagLanguage";
case k_TagOriginal: return "TagOriginal";
case k_TagString: return "TagString";
case k_TagBinary: return "TagBinary";
default: return 0;
}
}
char * get_track_type_name (track_type_t type)
{
switch (type)
{
case k_VideoTrack: return "Video";
case k_AudioTrack: return "Audio";
case k_ComplexTrack: return "Complex";
case k_LogoTrack: return "Logo";
case k_SubtitleTrack: return "Subtitle";
case k_ButtonTrack: return "Button";
case k_ControlTrack: return "Control";
default: return "Unknown";
}
}
//////////////////////////////
// mkvparser
#define MAX_TRACKS 16
typedef struct _mkv_track_t
{
track_type_t type;
uint32_t language;
uint32_t tcodescale;
uint32_t block_tcode;
uint32_t timecode_scale;
FILE *fout;
int err;
} mkv_track_t;
typedef struct _mkv_t
{
uint32_t cluster_tcode;
mkv_track_t tracks[MAX_TRACKS];
uint32_t trackidx;
} mkv_t;
static char * get_indent (int stackdepth)
{
static char indent[1024];
static int old = 0;
if (indent[0] != ' ' && indent[1] != ' ')
{
int i;
for (i = 0; i < 64; i++)
{
indent[i] = ' ';
}
indent[0] = 0;
}
if (old != stackdepth)
{
indent[old*2] = ' ';
old = stackdepth;
indent[old*2] = 0;
}
return indent;
}
static int mkv_get_elem (mkvparser_t *parser, int start, int end, mkv_elem_t *elem)
{
uint8_t local_buf[8];
uint8_t id_buf[4];
int size_buf_len = 0;
int i;
/* parse Element ID */
CHECK_READ(1, id_buf);
if (id_buf[0] & 0x80)
{ /* class A */
elem->id_len = 1;
}
else if (id_buf[0] & 0x40)
{ /* class B */
CHECK_READ(1, id_buf + 1);
elem->id_len = 2;
}
else if (id_buf[0] & 0x20)
{ /* class C */
CHECK_READ(2, id_buf + 1);
elem->id_len = 3;
}
else if (id_buf[0] & 0x10)
{ /* class D */
CHECK_READ(3, id_buf + 1);
elem->id_len = 4;
}
else
{
return -2; /* syntax error */
}
/* parse size */
CHECK_READ(1, local_buf);
if (local_buf[0] & 0x80)
{
size_buf_len = 1;
}
else if (local_buf[0] & 0x40)
{
CHECK_READ(1, local_buf + 1);
size_buf_len = 2;
}
else if (local_buf[0] & 0x20)
{
CHECK_READ(2, local_buf + 1);
size_buf_len = 3;
}
else if (local_buf[0] & 0x10)
{
CHECK_READ(3, local_buf + 1);
size_buf_len = 4;
}
else if (local_buf[0] & 0x08)
{
CHECK_READ(4, local_buf + 1);
size_buf_len = 5;
}
else if (local_buf[0] & 0x04)
{
CHECK_READ(5, local_buf + 1);
size_buf_len = 6;
}
else if (local_buf[0] & 0x02)
{
CHECK_READ(6, local_buf + 1);
size_buf_len = 7;
}
else if (local_buf[0] & 0x01)
{
CHECK_READ(7, local_buf + 1);
size_buf_len = 8;
}
else
{
return -2; /* syntax error */
}
elem->data_size = local_buf[0] & (0xff >> size_buf_len);
for (i = 1; i < size_buf_len; i++)
{
elem->data_size <<= 8;
elem->data_size += local_buf[i];
}
elem->data_addr = start + elem->id_len + size_buf_len;
elem->size_buf_len = size_buf_len;
if (elem->data_addr + elem->data_size > end)
{ /* invalid */
return -1;
}
// form the id
elem->id = 0;
for (i = 0; i < elem->id_len; i++)
{
elem->id <<= 8;
elem->id |= id_buf[i];
}
return 0; /* success */
}
static void elem_checkin (mkv_elem_t *elem, int stackdepth)
{
char *indent = get_indent(stackdepth);
char *elem_name = get_elem_name(elem);
if (elem_name)
{
printf("%s%s () { 0x%08x /n", indent, elem_name, elem->data_addr - elem->id_len - elem->size_buf_len);
}
else
{
printf("%s[%X] () { 0x%08x /n", indent, elem->id, elem->data_addr - elem->id_len - elem->size_buf_len);
}
}
static void elem_checkout (mkv_elem_t *elem, int stackdepth)
{
char *indent = get_indent(stackdepth);
char *elem_name = get_elem_name(elem);
if (elem_name)
{
printf("%s} %s () /n", indent, elem_name);
}
else
{
printf("%s} [%X] () /n", indent, elem->id);
}
}
static void mkv_init (mkv_t *mkv)
{
memset(mkv, 0, sizeof(mkv_t));
mkv->cluster_tcode = 0;
}
void mkvparser_init (mkvparser_t *parser, mkv_read_proc read, mkv_seek_proc seek,
mkv_srng_proc srng, void *read_info)
{
parser->read = read;
parser->seek = seek;
parser->set_range = srng;
parser->read_info = read_info;
}
void mkvparser_general_parse (mkvparser_t *parser)
{
#define STACK_SIZE 32
int depth = 0;
int start, end;
int top_end;
int file_end;
int res;
int deeper = 1;
int i;
mkv_elem_t *cur, *top;
mkv_elem_t stack[STACK_SIZE];
uint8_t buf[16];
// mkv syntax related
mkv_t mkv;
mkv_init(&mkv);
start = 0, end = -1;
if (parser->set_range(parser->read_info, start, &end) != 0)
{
return;
}
file_end = end;
while (1)
{
if (deeper && depth < STACK_SIZE)
{
cur = stack + depth;
res = mkv_get_elem(parser, start, end, cur);
}
else
{ // no deeper or stack overflow to be concealed
res = -1;
}
if (res == 0)
{ // elem found
start = cur->data_addr;
end = cur->data_addr + cur->data_size;
/* validate `end' */
top = stack + depth - 1;
top_end = top->data_addr + top->data_size;
if (end > top_end)
{ // invalid stream
end = top_end;
}
if (parser->set_range(parser->read_info, cur->data_addr, &end) != 0)
{
return;
}
cur->data_size = end - cur->data_addr; // correct mandatorily
elem_checkin(cur, depth);
// Process the Tag
{
char *indent = get_indent(depth + 1);
if (cur->id == k_Block)
{ // Block
int bytesgone = 0;
int readsize = 0;
union
{
uint64_t u64;
sint16_t s16;
} val;
uint8_t lacing;
readsize = read_vint(parser, &val.u64);
if (bytesgone != -1 && readsize > 0) { bytesgone += readsize; }
printf("%sTrackNumber = %d/n", indent, val.u64.lo);
mkv.trackidx = val.u64.lo - 1;
if (mkv.trackidx < 0 || mkv.trackidx >= MAX_TRACKS)
{
mkv.trackidx = -1;
}
val.s16 = 0;
readsize = read_sint16(parser, &val.s16);
if (bytesgone != -1 && readsize > 0) { bytesgone += readsize; }
if (mkv.trackidx >= 0)
{
printf("%sTrackType = %s/n", indent, get_track_type_name(mkv.tracks[mkv.trackidx].type));
mkv.tracks[mkv.trackidx].block_tcode = mkv.cluster_tcode + val.s16;
printf("%sTimeCode = %d * %f/n", indent,
mkv.tracks[mkv.trackidx].block_tcode,
mkv.tracks[mkv.trackidx].timecode_scale);
}
readsize = read_int8(parser, &lacing);
if (bytesgone != -1 && readsize > 0) { bytesgone += readsize; }
if (lacing != 0)
{
printf("%sFlags = 0x%04x/n", indent, lacing);
}
if (lacing & 0x06) //lacing
{
printf("%sLacing not supported yet/n");
}
else if (mkv.trackidx >= 0)
{
#define OUTBUFSIZE 4096
unsigned char outbuf[OUTBUFSIZE];
int rem = cur->data_size - bytesgone;
printf("outputing track %d with size %d/n", mkv.trackidx + 1, rem);
FILE *f = mkv.tracks[mkv.trackidx].fout;
if (f) while (rem > 0)
{
int outsize = rem<OUTBUFSIZE? rem : OUTBUFSIZE;
rem -= outsize;
readsize = parser->read(parser->read_info, outsize, outbuf);
if (readsize < outsize)
{ // fatal error
mkv.tracks[mkv.trackidx].err = 1;
break;
}
else
{
fwrite(outbuf, 1, outsize, f);
}
}
if (mkv.tracks[mkv.trackidx].type == k_SubtitleTrack)
{
char retchar[] = {0xd, 0xa};
fwrite(retchar, 1, 2, f);
}
}
deeper = 0;
}
else if (cur->id == k_ReferenceBlock)
{
uint32_t tcode;
if (cur->data_size <= 4 && read_int_withlen(parser, cur->data_size, &tcode) > 0)
{
if (mkv.trackidx >= 0)
{
printf("%sTimeCode = %d * %d/n", indent,
mkv.tracks[mkv.trackidx].block_tcode + tcode,
mkv.tracks[mkv.trackidx].timecode_scale);
}
}
}
else if (cur->id == k_TrackNumber || cur->id == k_TrackType || cur->id == k_Language)
{ // TrackName, TrackType, Language
if (cur->id == k_TrackNumber)
{
mkv.trackidx = -1;
if (cur->data_size <= 4)
{
uint32_t trackidx;
if (read_int_withlen(parser, cur->data_size, &trackidx) > 0
&& trackidx <= MAX_TRACKS)
{
mkv.trackidx = trackidx - 1;
}
}
}
else if (cur->id == k_TrackType && cur->data_size <= 4)
{
uint32_t type;
if (read_int_withlen(parser, cur->data_size, &type) > 0)
{
printf("%sTrackType = %s/n", indent, get_track_type_name(type));
if (mkv.trackidx >= 0)
{
mkv.tracks[mkv.trackidx].type = type;
char fnbuf[32];
sprintf(fnbuf, "track%02d.dat", mkv.trackidx + 1);
mkv.tracks[mkv.trackidx].fout = fopen(fnbuf, "wb");
}
}
}
else
{
int read_size = (16<cur->data_size)?16:cur->data_size;
parser->read(parser->read_info, read_size, buf);
printf("%s", indent);
for (i = 0; i < read_size; i++)
{
printf("%02x ", buf[i]);
}
printf("/n");
}
deeper = 0;
}
else if (cur->id == k_TimeCode)
{ // cluster timecode
if (cur->data_size <= 4)
{
uint32_t tcode;
if (read_int_withlen(parser, cur->data_size, &tcode) > 0)
{
mkv.cluster_tcode = tcode;
printf("%sTimeCode = %d/n", indent, tcode);
}
}
deeper = 0;
}
else if (cur->id == k_TrackTimeCodeScale)
{
uint32_t tcscale;
printf("tcodescale datasize = %d/n", cur->data_size);
if (cur->data_size <= 4 && read_int_withlen(parser, cur->data_size, &tcscale) > 0)
{
printf("%sTrackTimeCodeScale = %d/n", indent, tcscale);
if (mkv.trackidx >= 0)
{
*(uint32_t*)(&mkv.tracks[mkv.trackidx].timecode_scale)
= tcscale; // it's a floating number!
}
}
deeper = 0;
}
}
depth++;
}
else
{ // no more sub-element in current element
// move to the end
--depth;
if (depth < 0)
{
break;
}
// move on to the next element on the same level
cur = stack + depth;
start = cur->data_addr + cur->data_size;
if (depth > 0)
{
top = stack + depth - 1;
end = top->data_addr + top->data_size;
}
else
{
end = file_end;
}
parser->set_range(parser->read_info, start, &end);
// TODO:
// ...
deeper = 1;
elem_checkout(cur, depth);
}
}
for (i = 0; i < MAX_TRACKS; i++)
{
if (mkv.tracks[i].fout)
{
fclose(mkv.tracks[i].fout);
if (mkv.tracks[i].err)
{ // clear the file
char fnbuf[32];
sprintf(fnbuf, "track%02d.dat", mkv.trackidx + 1);
mkv.tracks[i].fout = fopen(fnbuf, "wb");
fclose(mkv.tracks[i].fout);
}
}
}
}
//////////////////////////////
// main
typedef struct _mkv_read_info
{
FILE *fIn;
int end;
int file_end;
} mkv_read_info;
void mkv_file_init (void *info, FILE *fIn)
{
mkv_read_info *mfr_info = (mkv_read_info *)info;
mfr_info->fIn = fIn;
fseek(fIn, 0, SEEK_END);
mfr_info->file_end = ftell(fIn);
fseek(fIn, 0, SEEK_SET);
}
int mkv_file_read (void *info, int size, uint8_t *buf)
{
mkv_read_info *mfr_info = (mkv_read_info *)info;
FILE *fIn = mfr_info->fIn;
int start = ftell(fIn);
int end = mfr_info->end;
if (end >= 0 && start + size > end)
{
size = end - start;
}
if (size <= 0)
{
return 0;
}
return fread(buf, 1, size, fIn);
}
int mkv_file_seek (void *info, int offset)
{
mkv_read_info *mfr_info = (mkv_read_info *)info;
FILE *fIn = mfr_info->fIn;
int end = mfr_info->end;
if (end >= 0 && offset > end)
{
return 0;
}
fseek(fIn, offset, SEEK_SET);
return (offset == ftell(fIn));
}
int mkv_file_setrange (void *info, int start, int *end)
{
mkv_read_info *mfr_info = (mkv_read_info *)info;
if (!mkv_file_seek(mfr_info, start))
{
return -1;
}
if (*end > mfr_info->file_end || *end <= 0)
{
*end = mfr_info->file_end;
}
mfr_info->end = *end;
return 0;
}
int main (void)
{
FILE *fIn = fopen("in.mkv", "rb");
mkv_read_info rinfo;
mkvparser_t parser;
if (!fIn)
{
goto bail;
}
mkv_file_init(&rinfo, fIn);
mkvparser_init(&parser, mkv_file_read, mkv_file_seek, mkv_file_setrange, &rinfo);
mkvparser_general_parse(&parser);
fclose(fIn);
bail:
return 0;
}
The code is as follows:
#include <stdio.h>
typedef enum
{
/* Table 1 */
k_EMBLVersion = 0x4286,
k_EMBLReadVersion = 0x42f7,
k_EMBLMaxIdLength = 0x42f2,
k_EMBLMaxSizeLength = 0x42f3,
k_DocType = 0x4282,
k_DocTypeVersion = 0x4287,
k_DocTypeReadVersion = 0x4285,
/* Table 2 */
k_SegmentInfo = 0x1549a966,
k_SeekHead = 0x114d9b74,
k_Cluster = 0x1f43b675,
k_Tracks = 0x1654ae6b,
k_Cues = 0x1c53bb6b,
k_Attachments = 0x1941a469,
k_Chapters = 0x1043a770,
k_Tags = 0x1254c367,
/* Table 3 */
k_SegmentUID = 0x73a4,
k_SegmentFileName = 0x7384,
k_PrevUID = 0x3cb923,
k_PrevFileName = 0x3c83ab,
k_NextUID = 0x3eb923,
k_NextFileName = 0x3e83bb,
k_TimeCodeScale = 0x2ad7b1,
k_Duration = 0x4489,
k_Title = 0x7ba9,
k_MuxingApp = 0x4d80,
k_DateUTC = 0x4461,
/* Table 4 */
k_Seek = 0x4dbb,
/* Table 5 */
k_SeekId = 0x53ab,
k_SeekPosition = 0x53ac,
/* Table 6 */
k_TrackEntry = 0xae,
/* Table 7 */
k_TrackNumber = 0xd7,
k_TrackUID = 0x73c5,
k_TrackType = 0x83,
k_FlagEnabled = 0xb9,
k_FlagDefault = 0x88,
k_FlagForced = 0x55aa,
k_FlagLacing = 0x9c,
k_MinCache = 0x6de7,
k_MaxCache = 0x6df8,
k_DefaultDuration = 0x23e383,
k_TrackTimeCodeScale = 0x23314f,
k_Name = 0x536e,
k_Language = 0x22b59c,
k_CodecId = 0x86,
k_CodecPrivate = 0x63a2,
k_CodecName = 0x258688,
k_AttachmentLink = 0x7446,
k_Video = 0xe0,
k_Audio = 0xe1,
k_ContentEncodings = 0x6d80,
/* Table 8 */
k_PixelWidth = 0xb0,
k_PixelHeight = 0xba,
k_PixelCropBottom = 0x54aa,
k_PixelCropTop = 0x54bb,
k_PixelCropLeft = 0x54cc,
k_PixelCropRight = 0x54dd,
k_DisplayWidth = 0x54b0,
k_DisplayHeight = 0x54ba,
k_DisplayUnit = 0x54b2,
/* Table 9 */
k_SampleFrequency = 0xb5,
k_OutputSamplingFrequency = 0x78b5,
k_Channels = 0x9f,
k_BitDepth = 0x6264,
/* Table 10 */
k_ContentEncoding = 0x6240,
/* Table 11 */
k_ContentEncodingOrder = 0x5031,
k_ContentEncodingScope = 0x5032,
k_ContentEncodingType = 0x5033,
k_ContentCompression = 0x5034,
k_ContentEncryption = 0x5035,
/* Table 12 */
k_ContentCompAlgo = 0x4254,
k_ContentCompSettings = 0x4255,
/* Table 16 */
k_TimeCode = 0xe7,
k_Position = 0xa7,
k_PrevSize = 0xab,
k_BlockGroup = 0xa0,
k_SimpleBlock = 0xa3,
/* Table 17 */
k_Block = 0xa1,
k_ReferenceBlock = 0xfb,
k_BlockDuration = 0x9b,
/* Table 18 */
k_CuePoint = 0xbb,
/* Table 19 */
k_CueTime = 0xb3,
k_CueTrackPositions = 0xb7,
/* Table 20 */
k_CueTrack = 0xf7,
k_CueClusterPosition = 0xf1,
k_CueBlockNumber = 0x5378,
/* Table 21 */
k_EditionEntry = 0x45b9,
/* Table 22: */
k_EditionUID = 0x45bc,
k_EditionFlagHidden = 0x45bd,
k_EditionFlagDefault = 0x45db,
k_EditionFlagOrdered = 0x45dd,
k_ChapterAtom = 0xb6,
/* Table 23 */
k_ChapterUID = 0x73c4,
k_ChapterTimeStart = 0x91,
k_ChapterTimeEnd = 0x92,
k_ChapterFlagHidden = 0x98,
k_ChapterFlagEnabled = 0x4598,
k_ChapterSegmentUID = 0x6e67,
k_ChapterSegmentEditionUID = 0x6ebc,
k_ChapterTracks = 0x8f,
k_ChapterDisplay = 0x80,
/* Table 24 */
k_ChapterTrackNumber = 0x89,
/* Table 25 */
k_ChapString = 0x85,
k_ChapLanguage = 0x437c,
k_ChapCountry = 0x437e,
/* Table 26 */
k_AttachedFile = 0x61a7,
/* Table 27 */
k_FileDescription = 0x467e,
k_FileName = 0x466e,
k_FileMimeType = 0x4660,
k_FileData = 0x465c,
k_FileUID = 0x46ae,
/* Table 28 */
k_Tag = 0x7373,
/* Table 29 */
k_Targets = 0x63c0,
k_SimpleTag = 0x67c8,
/* Table 30 */
k_TargetTypeValue = 0x68ca,
k_TargetType = 0x63ca,
k_TargetUID = 0x63c5,
k_TargetEditionUID = 0x63c9,
k_TargetChapterUID = 0x63c4,
/* Table 31 */
k_TagName = 0x45a3,
k_TagLanguage = 0x447a,
k_TagOriginal = 0x4484,
k_TagString = 0x4487,
k_TagBinary = 0x4485,
} ElemId;
typedef unsigned char uint8_t;
typedef unsigned char sint8_t;
typedef unsigned char uint16_t;
typedef unsigned char sint16_t;
typedef unsigned long uint32_t;
typedef signed long sint32_t;
typedef int (*mkv_srng_proc)(void *, int start, int *end);
typedef int (*mkv_read_proc)(void *, int size, uint8_t *buf);
typedef int (*mkv_seek_proc)(void *, int offset);
typedef struct _mkvparser_t
{
void * read_info;
mkv_read_proc read;
mkv_seek_proc seek;
mkv_srng_proc set_range;
} mkvparser_t;
typedef enum _track_type_t
{
k_VideoTrack = 0x01,
k_AudioTrack = 0x02,
k_ComplexTrack = 0x03,
k_LogoTrack = 0x10,
k_SubtitleTrack = 0x11,
k_ButtonTrack = 0x12,
k_ControlTrack = 0x20,
} track_type_t;
#define CHECK_READ(size, buf) if(parser->read(parser->read_info, size, buf) != size){return -1;}
typedef struct _uint64_t
{
uint32_t hi;
uint32_t lo;
} uint64_t;
typedef struct _sint64_t
{
sint32_t hi;
uint32_t lo;
} sint64_t;
typedef struct _mkv_elem_t
{
uint32_t id; /* elem id */
uint8_t id_len; /* number of bytes occupied by elem id */
uint8_t size_buf_len; /* number of bytes occupied by size */
uint32_t data_size; /* size of data */
uint32_t data_addr; /* absolute offset of the data from the file beginning */
} mkv_elem_t;
//////////////////////////////
// util
int read_sint16 (mkvparser_t *parser, sint16_t *val)
{
uint8_t buf[2];
uint16_t tmp;
if (parser->read(parser->read_info, 2, buf) != 2)
{
return -1;
}
tmp = buf[0]<<8;
tmp |= buf[1];
*val = (sint16_t)tmp;
return 2;
}
int read_int_withlen (mkvparser_t *parser, int len, uint32_t *val)
{
uint8_t buf[4];
uint32_t tmp = 0;
int i;
len = len < 4? len : 4;
len = parser->read(parser->read_info, len, buf);
for (i = 0; i < len; i++)
{
tmp <<= 8;
tmp |= buf[i];
}
*val = tmp;
return len;
}
int read_int8 (mkvparser_t *parser, uint8_t *val)
{
if (parser->read(parser->read_info, 1, val) != 1)
{
return -1;
}
return 1;
}
int read_vint (mkvparser_t *parser, uint64_t *val)
{
int i;
int code_len;
uint8_t id_buf[4];
CHECK_READ(1, id_buf);
if (id_buf[0] & 0x80)
{ /* class A */
code_len = 1;
id_buf[0] &= 0x7f;
}
else if (id_buf[0] & 0x40)
{ /* class B */
CHECK_READ(1, id_buf + 1);
code_len = 2;
id_buf[0] &= 0x3f;
}
else if (id_buf[0] & 0x20)
{ /* class C */
CHECK_READ(2, id_buf + 1);
code_len = 3;
id_buf[0] &= 0x1f;
}
else if (id_buf[0] & 0x10)
{ /* class D */
CHECK_READ(3, id_buf + 1);
code_len = 4;
id_buf[0] &= 0x0f;
}
else if (id_buf[0] & 0x08)
{ /* class B */
CHECK_READ(4, id_buf + 1);
code_len = 5;
id_buf[0] &= 0x07;
}
else if (id_buf[0] & 0x04)
{ /* class C */
CHECK_READ(5, id_buf + 1);
code_len = 6;
id_buf[0] &= 0x03;
}
else if (id_buf[0] & 0x02)
{ /* class D */
CHECK_READ(6, id_buf + 1);
code_len = 7;
id_buf[0] &= 0x01;
}
else if (id_buf[0] & 0x01)
{ /* class D */
CHECK_READ(7, id_buf + 1);
code_len = 8;
id_buf[0] = 0;
}
else
{ /* error */
return -2;
}
val->hi = 0;
val->lo = 0;
i = 0;
if (code_len > 4)
{
for ( ; i < code_len - 4; i++)
{
val->hi <<= 8;
val->hi |= id_buf[i];
}
}
for (; i < code_len; i++)
{
val->lo <<= 8;
val->lo |= id_buf[i];
}
return code_len;
}
int read_svint (mkvparser_t *parser, sint64_t *val)
{
static sint32_t vsint_subtr_lo[] = {0x3f, 0x1fff, 0xfffff, 0x7ffffff};
static sint32_t vsint_subtr_hi[] = {0x3, 0x1ff, 0xffff, 0x7fffff};
int code_len = read_vint(parser, (uint64_t *)val);
if (code_len <= 0)
{
return code_len;
}
if (code_len < 4)
{
val->lo -= vsint_subtr_lo[code_len];
if (val->lo & 0x80000000)
{
val->hi = -1;
}
}
else
{
val->hi -= vsint_subtr_hi[code_len - 4];
val->lo -= 0xffffffff;
if (val->lo != 0)
{
val->hi--;
}
}
return code_len;
}
//////////////////////////////
// elem
char * get_elem_name (mkv_elem_t *elem)
{
switch (elem->id)
{
/* Table 1 */
case k_EMBLVersion: return "EMBLVersion";
case k_EMBLReadVersion: return "EMBLReadVersion";
case k_EMBLMaxIdLength: return "EMBLMaxIdLength";
case k_EMBLMaxSizeLength: return "EMBLMaxSizeLength";
case k_DocType: return "DocType";
case k_DocTypeVersion: return "DocTypeVersion";
case k_DocTypeReadVersion: return "DocTypeReadVersion";
/* Table 2 */
case k_SegmentInfo: return "SegmentInfo";
case k_SeekHead: return "SeekHead";
case k_Cluster: return "Cluster";
case k_Tracks: return "Tracks";
case k_Cues: return "Cues";
case k_Attachments: return "Attachments";
case k_Chapters: return "Chapters";
case k_Tags: return "Tags";
/* Table 3 */
case k_SegmentUID: return "SegmentUID";
case k_SegmentFileName: return "SegmentFileName";
case k_PrevUID: return "PrevUID";
case k_PrevFileName: return "PrevFileName";
case k_NextUID: return "NextUID";
case k_NextFileName: return "NextFileName";
case k_TimeCodeScale: return "TimeCodeScale";
case k_Duration: return "Duration";
case k_Title: return "Title";
case k_MuxingApp: return "MuxingApp";
case k_DateUTC: return "DateUTC";
/* Table 4 */
case k_Seek: return "Seek";
/* Table 5 */
case k_SeekId: return "SeekId";
case k_SeekPosition: return "SeekPosition";
/* Table 6 */
case k_TrackEntry: return "TrackEntry";
/* Table 7 */
case k_TrackNumber: return "TrackNumber";
case k_TrackUID: return "TrackUID";
case k_TrackType: return "TrackType";
case k_FlagEnabled: return "FlagEnabled";
case k_FlagDefault: return "FlagDefault";
case k_FlagForced: return "FlagForced";
case k_FlagLacing: return "FlagLacing";
case k_MinCache: return "MinCache";
case k_MaxCache: return "MaxCache";
case k_DefaultDuration: return "DefaultDuration";
case k_TrackTimeCodeScale: return "TrackTimeCodeScale";
case k_Name: return "Name";
case k_Language: return "Language";
case k_CodecId: return "CodecId";
case k_CodecPrivate: return "CodecPrivate";
case k_CodecName: return "CodecName";
case k_AttachmentLink: return "AttachmentLink";
case k_Video: return "Video";
case k_Audio: return "Audio";
case k_ContentEncodings: return "ContentEncodings";
/* Table 8 */
case k_PixelWidth: return "PixelWidth";
case k_PixelHeight: return "PixelHeight";
case k_PixelCropBottom: return "PixelCropBottom";
case k_PixelCropTop: return "PixelCropTop";
case k_PixelCropLeft: return "PixelCropLeft";
case k_PixelCropRight: return "PixelCropRight";
case k_DisplayWidth: return "DisplayWidth";
case k_DisplayHeight: return "DisplayHeight";
case k_DisplayUnit: return "DisplayUnit";
/* Table 9 */
case k_SampleFrequency: return "SampleFrequency";
case k_OutputSamplingFrequency: return "OutputSamplingFrequency";
case k_Channels: return "Channels";
case k_BitDepth: return "BitDepth";
/* Table 10 */
case k_ContentEncoding: return "ContentEncoding";
/* Table 11 */
case k_ContentEncodingOrder: return "ContentEncodingOrder";
case k_ContentEncodingScope: return "ContentEncodingScope";
case k_ContentEncodingType: return "ContentEncodingType";
case k_ContentCompression: return "ContentCompression";
case k_ContentEncryption: return "ContentEncryption";
/* Table 12 */
case k_ContentCompAlgo: return "ContentCompAlgo";
case k_ContentCompSettings: return "ContentCompSettings";
/* Table 16 */
case k_TimeCode: return "TimeCode";
case k_Position: return "Position";
case k_PrevSize: return "PrevSize";
case k_BlockGroup: return "BlockGroup";
case k_SimpleBlock: return "SimpleBlock";
/* Table 17 */
case k_Block: return "Block";
case k_ReferenceBlock: return "ReferenceBlock";
case k_BlockDuration: return "BlockDuration";
/* Table 18 */
case k_CuePoint: return "CuePoint";
/* Table 19 */
case k_CueTime: return "CueTime";
case k_CueTrackPositions: return "CueTrackPositions";
/* Table 20 */
case k_CueTrack: return "CueTrack";
case k_CueClusterPosition: return "CueClusterPosition";
case k_CueBlockNumber: return "CueBlockNumber";
/* Table 21 */
case k_EditionEntry: return "EditionEntry";
/* Table 22: */
case k_EditionUID: return "EditionUID";
case k_EditionFlagHidden: return "EditionFlagHidden";
case k_EditionFlagDefault: return "EditionFlagDefault";
case k_EditionFlagOrdered: return "EditionFlagOrdered";
case k_ChapterAtom: return "ChapterAtom";
/* Table 23 */
case k_ChapterUID: return "ChapterUID";
case k_ChapterTimeStart: return "ChapterTimeStart";
case k_ChapterTimeEnd: return "ChapterTimeEnd";
case k_ChapterFlagHidden: return "ChapterFlagHidden";
case k_ChapterFlagEnabled: return "ChapterFlagEnabled";
case k_ChapterSegmentUID: return "ChapterSegmentUID";
case k_ChapterSegmentEditionUID: return "ChapterSegmentEditionUID";
case k_ChapterTracks: return "ChapterTracks";
case k_ChapterDisplay: return "ChapterDisplay";
/* Table 24 */
case k_ChapterTrackNumber: return "ChapterTrackNumber";
/* Table 25 */
case k_ChapString: return "ChapString";
case k_ChapLanguage: return "ChapLanguage";
case k_ChapCountry: return "ChapCountry";
/* Table 26 */
case k_AttachedFile: return "AttachedFile";
/* Table 27 */
case k_FileDescription: return "FileDescription";
case k_FileName: return "FileName";
case k_FileMimeType: return "FileMimeType";
case k_FileData: return "FileData";
case k_FileUID: return "FileUID";
/* Table 28 */
case k_Tag: return "Tag";
/* Table 29 */
case k_Targets: return "Targets";
case k_SimpleTag: return "SimpleTag";
/* Table 30 */
case k_TargetTypeValue: return "TargetTypeValue";
case k_TargetType: return "TargetType";
case k_TargetUID: return "TargetUID";
case k_TargetEditionUID: return "TargetEditionUID";
case k_TargetChapterUID: return "TargetChapterUID";
/* Table 31 */
case k_TagName: return "TagName";
case k_TagLanguage: return "TagLanguage";
case k_TagOriginal: return "TagOriginal";
case k_TagString: return "TagString";
case k_TagBinary: return "TagBinary";
default: return 0;
}
}
char * get_track_type_name (track_type_t type)
{
switch (type)
{
case k_VideoTrack: return "Video";
case k_AudioTrack: return "Audio";
case k_ComplexTrack: return "Complex";
case k_LogoTrack: return "Logo";
case k_SubtitleTrack: return "Subtitle";
case k_ButtonTrack: return "Button";
case k_ControlTrack: return "Control";
default: return "Unknown";
}
}
//////////////////////////////
// mkvparser
#define MAX_TRACKS 16
typedef struct _mkv_track_t
{
track_type_t type;
uint32_t language;
uint32_t tcodescale;
uint32_t block_tcode;
uint32_t timecode_scale;
FILE *fout;
int err;
} mkv_track_t;
typedef struct _mkv_t
{
uint32_t cluster_tcode;
mkv_track_t tracks[MAX_TRACKS];
uint32_t trackidx;
} mkv_t;
static char * get_indent (int stackdepth)
{
static char indent[1024];
static int old = 0;
if (indent[0] != ' ' && indent[1] != ' ')
{
int i;
for (i = 0; i < 64; i++)
{
indent[i] = ' ';
}
indent[0] = 0;
}
if (old != stackdepth)
{
indent[old*2] = ' ';
old = stackdepth;
indent[old*2] = 0;
}
return indent;
}
static int mkv_get_elem (mkvparser_t *parser, int start, int end, mkv_elem_t *elem)
{
uint8_t local_buf[8];
uint8_t id_buf[4];
int size_buf_len = 0;
int i;
/* parse Element ID */
CHECK_READ(1, id_buf);
if (id_buf[0] & 0x80)
{ /* class A */
elem->id_len = 1;
}
else if (id_buf[0] & 0x40)
{ /* class B */
CHECK_READ(1, id_buf + 1);
elem->id_len = 2;
}
else if (id_buf[0] & 0x20)
{ /* class C */
CHECK_READ(2, id_buf + 1);
elem->id_len = 3;
}
else if (id_buf[0] & 0x10)
{ /* class D */
CHECK_READ(3, id_buf + 1);
elem->id_len = 4;
}
else
{
return -2; /* syntax error */
}
/* parse size */
CHECK_READ(1, local_buf);
if (local_buf[0] & 0x80)
{
size_buf_len = 1;
}
else if (local_buf[0] & 0x40)
{
CHECK_READ(1, local_buf + 1);
size_buf_len = 2;
}
else if (local_buf[0] & 0x20)
{
CHECK_READ(2, local_buf + 1);
size_buf_len = 3;
}
else if (local_buf[0] & 0x10)
{
CHECK_READ(3, local_buf + 1);
size_buf_len = 4;
}
else if (local_buf[0] & 0x08)
{
CHECK_READ(4, local_buf + 1);
size_buf_len = 5;
}
else if (local_buf[0] & 0x04)
{
CHECK_READ(5, local_buf + 1);
size_buf_len = 6;
}
else if (local_buf[0] & 0x02)
{
CHECK_READ(6, local_buf + 1);
size_buf_len = 7;
}
else if (local_buf[0] & 0x01)
{
CHECK_READ(7, local_buf + 1);
size_buf_len = 8;
}
else
{
return -2; /* syntax error */
}
elem->data_size = local_buf[0] & (0xff >> size_buf_len);
for (i = 1; i < size_buf_len; i++)
{
elem->data_size <<= 8;
elem->data_size += local_buf[i];
}
elem->data_addr = start + elem->id_len + size_buf_len;
elem->size_buf_len = size_buf_len;
if (elem->data_addr + elem->data_size > end)
{ /* invalid */
return -1;
}
// form the id
elem->id = 0;
for (i = 0; i < elem->id_len; i++)
{
elem->id <<= 8;
elem->id |= id_buf[i];
}
return 0; /* success */
}
static void elem_checkin (mkv_elem_t *elem, int stackdepth)
{
char *indent = get_indent(stackdepth);
char *elem_name = get_elem_name(elem);
if (elem_name)
{
printf("%s%s () { 0x%08x /n", indent, elem_name, elem->data_addr - elem->id_len - elem->size_buf_len);
}
else
{
printf("%s[%X] () { 0x%08x /n", indent, elem->id, elem->data_addr - elem->id_len - elem->size_buf_len);
}
}
static void elem_checkout (mkv_elem_t *elem, int stackdepth)
{
char *indent = get_indent(stackdepth);
char *elem_name = get_elem_name(elem);
if (elem_name)
{
printf("%s} %s () /n", indent, elem_name);
}
else
{
printf("%s} [%X] () /n", indent, elem->id);
}
}
static void mkv_init (mkv_t *mkv)
{
memset(mkv, 0, sizeof(mkv_t));
mkv->cluster_tcode = 0;
}
void mkvparser_init (mkvparser_t *parser, mkv_read_proc read, mkv_seek_proc seek,
mkv_srng_proc srng, void *read_info)
{
parser->read = read;
parser->seek = seek;
parser->set_range = srng;
parser->read_info = read_info;
}
void mkvparser_general_parse (mkvparser_t *parser)
{
#define STACK_SIZE 32
int depth = 0;
int start, end;
int top_end;
int file_end;
int res;
int deeper = 1;
int i;
mkv_elem_t *cur, *top;
mkv_elem_t stack[STACK_SIZE];
uint8_t buf[16];
// mkv syntax related
mkv_t mkv;
mkv_init(&mkv);
start = 0, end = -1;
if (parser->set_range(parser->read_info, start, &end) != 0)
{
return;
}
file_end = end;
while (1)
{
if (deeper && depth < STACK_SIZE)
{
cur = stack + depth;
res = mkv_get_elem(parser, start, end, cur);
}
else
{ // no deeper or stack overflow to be concealed
res = -1;
}
if (res == 0)
{ // elem found
start = cur->data_addr;
end = cur->data_addr + cur->data_size;
/* validate `end' */
top = stack + depth - 1;
top_end = top->data_addr + top->data_size;
if (end > top_end)
{ // invalid stream
end = top_end;
}
if (parser->set_range(parser->read_info, cur->data_addr, &end) != 0)
{
return;
}
cur->data_size = end - cur->data_addr; // correct mandatorily
elem_checkin(cur, depth);
// Process the Tag
{
char *indent = get_indent(depth + 1);
if (cur->id == k_Block)
{ // Block
int bytesgone = 0;
int readsize = 0;
union
{
uint64_t u64;
sint16_t s16;
} val;
uint8_t lacing;
readsize = read_vint(parser, &val.u64);
if (bytesgone != -1 && readsize > 0) { bytesgone += readsize; }
printf("%sTrackNumber = %d/n", indent, val.u64.lo);
mkv.trackidx = val.u64.lo - 1;
if (mkv.trackidx < 0 || mkv.trackidx >= MAX_TRACKS)
{
mkv.trackidx = -1;
}
val.s16 = 0;
readsize = read_sint16(parser, &val.s16);
if (bytesgone != -1 && readsize > 0) { bytesgone += readsize; }
if (mkv.trackidx >= 0)
{
printf("%sTrackType = %s/n", indent, get_track_type_name(mkv.tracks[mkv.trackidx].type));
mkv.tracks[mkv.trackidx].block_tcode = mkv.cluster_tcode + val.s16;
printf("%sTimeCode = %d * %f/n", indent,
mkv.tracks[mkv.trackidx].block_tcode,
mkv.tracks[mkv.trackidx].timecode_scale);
}
readsize = read_int8(parser, &lacing);
if (bytesgone != -1 && readsize > 0) { bytesgone += readsize; }
if (lacing != 0)
{
printf("%sFlags = 0x%04x/n", indent, lacing);
}
if (lacing & 0x06) //lacing
{
printf("%sLacing not supported yet/n");
}
else if (mkv.trackidx >= 0)
{
#define OUTBUFSIZE 4096
unsigned char outbuf[OUTBUFSIZE];
int rem = cur->data_size - bytesgone;
printf("outputing track %d with size %d/n", mkv.trackidx + 1, rem);
FILE *f = mkv.tracks[mkv.trackidx].fout;
if (f) while (rem > 0)
{
int outsize = rem<OUTBUFSIZE? rem : OUTBUFSIZE;
rem -= outsize;
readsize = parser->read(parser->read_info, outsize, outbuf);
if (readsize < outsize)
{ // fatal error
mkv.tracks[mkv.trackidx].err = 1;
break;
}
else
{
fwrite(outbuf, 1, outsize, f);
}
}
if (mkv.tracks[mkv.trackidx].type == k_SubtitleTrack)
{
char retchar[] = {0xd, 0xa};
fwrite(retchar, 1, 2, f);
}
}
deeper = 0;
}
else if (cur->id == k_ReferenceBlock)
{
uint32_t tcode;
if (cur->data_size <= 4 && read_int_withlen(parser, cur->data_size, &tcode) > 0)
{
if (mkv.trackidx >= 0)
{
printf("%sTimeCode = %d * %d/n", indent,
mkv.tracks[mkv.trackidx].block_tcode + tcode,
mkv.tracks[mkv.trackidx].timecode_scale);
}
}
}
else if (cur->id == k_TrackNumber || cur->id == k_TrackType || cur->id == k_Language)
{ // TrackName, TrackType, Language
if (cur->id == k_TrackNumber)
{
mkv.trackidx = -1;
if (cur->data_size <= 4)
{
uint32_t trackidx;
if (read_int_withlen(parser, cur->data_size, &trackidx) > 0
&& trackidx <= MAX_TRACKS)
{
mkv.trackidx = trackidx - 1;
}
}
}
else if (cur->id == k_TrackType && cur->data_size <= 4)
{
uint32_t type;
if (read_int_withlen(parser, cur->data_size, &type) > 0)
{
printf("%sTrackType = %s/n", indent, get_track_type_name(type));
if (mkv.trackidx >= 0)
{
mkv.tracks[mkv.trackidx].type = type;
char fnbuf[32];
sprintf(fnbuf, "track%02d.dat", mkv.trackidx + 1);
mkv.tracks[mkv.trackidx].fout = fopen(fnbuf, "wb");
}
}
}
else
{
int read_size = (16<cur->data_size)?16:cur->data_size;
parser->read(parser->read_info, read_size, buf);
printf("%s", indent);
for (i = 0; i < read_size; i++)
{
printf("%02x ", buf[i]);
}
printf("/n");
}
deeper = 0;
}
else if (cur->id == k_TimeCode)
{ // cluster timecode
if (cur->data_size <= 4)
{
uint32_t tcode;
if (read_int_withlen(parser, cur->data_size, &tcode) > 0)
{
mkv.cluster_tcode = tcode;
printf("%sTimeCode = %d/n", indent, tcode);
}
}
deeper = 0;
}
else if (cur->id == k_TrackTimeCodeScale)
{
uint32_t tcscale;
printf("tcodescale datasize = %d/n", cur->data_size);
if (cur->data_size <= 4 && read_int_withlen(parser, cur->data_size, &tcscale) > 0)
{
printf("%sTrackTimeCodeScale = %d/n", indent, tcscale);
if (mkv.trackidx >= 0)
{
*(uint32_t*)(&mkv.tracks[mkv.trackidx].timecode_scale)
= tcscale; // it's a floating number!
}
}
deeper = 0;
}
}
depth++;
}
else
{ // no more sub-element in current element
// move to the end
--depth;
if (depth < 0)
{
break;
}
// move on to the next element on the same level
cur = stack + depth;
start = cur->data_addr + cur->data_size;
if (depth > 0)
{
top = stack + depth - 1;
end = top->data_addr + top->data_size;
}
else
{
end = file_end;
}
parser->set_range(parser->read_info, start, &end);
// TODO:
// ...
deeper = 1;
elem_checkout(cur, depth);
}
}
for (i = 0; i < MAX_TRACKS; i++)
{
if (mkv.tracks[i].fout)
{
fclose(mkv.tracks[i].fout);
if (mkv.tracks[i].err)
{ // clear the file
char fnbuf[32];
sprintf(fnbuf, "track%02d.dat", mkv.trackidx + 1);
mkv.tracks[i].fout = fopen(fnbuf, "wb");
fclose(mkv.tracks[i].fout);
}
}
}
}
//////////////////////////////
// main
typedef struct _mkv_read_info
{
FILE *fIn;
int end;
int file_end;
} mkv_read_info;
void mkv_file_init (void *info, FILE *fIn)
{
mkv_read_info *mfr_info = (mkv_read_info *)info;
mfr_info->fIn = fIn;
fseek(fIn, 0, SEEK_END);
mfr_info->file_end = ftell(fIn);
fseek(fIn, 0, SEEK_SET);
}
int mkv_file_read (void *info, int size, uint8_t *buf)
{
mkv_read_info *mfr_info = (mkv_read_info *)info;
FILE *fIn = mfr_info->fIn;
int start = ftell(fIn);
int end = mfr_info->end;
if (end >= 0 && start + size > end)
{
size = end - start;
}
if (size <= 0)
{
return 0;
}
return fread(buf, 1, size, fIn);
}
int mkv_file_seek (void *info, int offset)
{
mkv_read_info *mfr_info = (mkv_read_info *)info;
FILE *fIn = mfr_info->fIn;
int end = mfr_info->end;
if (end >= 0 && offset > end)
{
return 0;
}
fseek(fIn, offset, SEEK_SET);
return (offset == ftell(fIn));
}
int mkv_file_setrange (void *info, int start, int *end)
{
mkv_read_info *mfr_info = (mkv_read_info *)info;
if (!mkv_file_seek(mfr_info, start))
{
return -1;
}
if (*end > mfr_info->file_end || *end <= 0)
{
*end = mfr_info->file_end;
}
mfr_info->end = *end;
return 0;
}
int main (void)
{
FILE *fIn = fopen("in.mkv", "rb");
mkv_read_info rinfo;
mkvparser_t parser;
if (!fIn)
{
goto bail;
}
mkv_file_init(&rinfo, fIn);
mkvparser_init(&parser, mkv_file_read, mkv_file_seek, mkv_file_setrange, &rinfo);
mkvparser_general_parse(&parser);
fclose(fIn);
bail:
return 0;
}
enjoy every minute of an appless, googless and oracless life