From 2216528544798e30debf6af1a9835c77d5d315aa Mon Sep 17 00:00:00 2001 From: Rodrigo Braz Monteiro Date: Thu, 23 Feb 2006 01:44:48 +0000 Subject: [PATCH] Matroska parsing implemented Originally committed to SVN as r120. --- core/MatroskaParser.c | 3309 ++++++++++++++++++++++++++++++++++++++++ core/MatroskaParser.h | 398 +++++ core/mkv_wrap.cpp | 296 ++++ core/mkv_wrap.h | 94 ++ core/vfr.cpp | 18 + core/vfr.h | 4 +- core/video_display.cpp | 24 +- 7 files changed, 4138 insertions(+), 5 deletions(-) create mode 100644 core/MatroskaParser.c create mode 100644 core/MatroskaParser.h create mode 100644 core/mkv_wrap.cpp create mode 100644 core/mkv_wrap.h diff --git a/core/MatroskaParser.c b/core/MatroskaParser.c new file mode 100644 index 000000000..61850a78a --- /dev/null +++ b/core/MatroskaParser.c @@ -0,0 +1,3309 @@ +/* + * Copyright (c) 2004-2006 Mike Matsnev. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice immediately at the beginning of the file, without modification, + * this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Absolutely no warranty of function or purpose is made by the author + * Mike Matsnev. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id: MatroskaParser.c,v 1.59.2.4 2006/01/13 01:44:45 mike Exp $ + * + */ + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +// MS names some functions differently +#define alloca _alloca +#define inline __inline + +#include +#endif + +#ifndef EVCBUG +#define EVCBUG +#endif + +#include "MatroskaParser.h" + +#ifdef MATROSKA_COMPRESSION_SUPPORT +#include +#endif + +#define EBML_VERSION 1 +#define EBML_MAX_ID_LENGTH 4 +#define EBML_MAX_SIZE_LENGTH 8 +#define MATROSKA_VERSION 2 +#define MATROSKA_DOCTYPE "matroska" + +#define MAX_STRING_LEN 1023 +#define QSEGSIZE 512 +#define MAX_TRACKS 32 +#define MAX_READAHEAD (256*1024) + +#define MAXCLUSTER (64*1048576) +#define MAXFRAME (4*1048576) + +#ifdef WIN32 +#define LL(x) x##i64 +#define ULL(x) x##ui64 +#else +#define LL(x) x##ll +#define ULL(x) x##ull +#endif + +#define MAXU64 ULL(0xffffffffffffffff) +#define ONE ULL(1) + +// compatibility +static char *mystrdup(struct InputStream *is,const char *src) { + size_t len; + char *dst; + + if (src==NULL) + return NULL; + + len = strlen(src); + dst = is->memalloc(is,len+1); + if (dst==NULL) + return NULL; + + memcpy(dst,src,len+1); + + return dst; +} + +#ifdef _WIN32 +static void strlcpy(char *dst,const char *src,unsigned size) { + unsigned i; + + for (i=0;i+1 0) + *dest = '\0'; + return; + } + + while (*fmt && dest < de) + switch (state) { + case 0: + if (*fmt == '%') { + ++fmt; + state = 1; + width = zero = neg = ll = 0; + } else + *dest++ = *fmt++; + break; + case 1: + if (*fmt == '-') { + neg = 1; + ++fmt; + state = 2; + break; + } + if (*fmt == '0') + zero = 1; + state = 2; + case 2: + if (*fmt >= '0' && *fmt <= '9') { + width = width * 10 + *fmt++ - '0'; + break; + } + state = 3; + case 3: + if (*fmt == 'l') { + ++ll; + ++fmt; + break; + } + state = 4; + case 4: + switch (*fmt) { + case 's': + myvsnprintf_string(&dest,de,va_arg(ap,const char *)); + break; + case 'd': + switch (ll) { + case 0: + myvsnprintf_int(&dest,de,width,zero,neg,10,'a',va_arg(ap,int)); + break; + case 1: + myvsnprintf_int(&dest,de,width,zero,neg,10,'a',va_arg(ap,long)); + break; + case 2: + myvsnprintf_int(&dest,de,width,zero,neg,10,'a',va_arg(ap,longlong)); + break; + } + break; + case 'u': + switch (ll) { + case 0: + myvsnprintf_uint(&dest,de,width,zero,neg,10,'a',va_arg(ap,unsigned int)); + break; + case 1: + myvsnprintf_uint(&dest,de,width,zero,neg,10,'a',va_arg(ap,unsigned long)); + break; + case 2: + myvsnprintf_uint(&dest,de,width,zero,neg,10,'a',va_arg(ap,ulonglong)); + break; + } + break; + case 'x': + switch (ll) { + case 0: + myvsnprintf_uint(&dest,de,width,zero,neg,16,'a',va_arg(ap,unsigned int)); + break; + case 1: + myvsnprintf_uint(&dest,de,width,zero,neg,16,'a',va_arg(ap,unsigned long)); + break; + case 2: + myvsnprintf_uint(&dest,de,width,zero,neg,16,'a',va_arg(ap,ulonglong)); + break; + } + break; + case 'X': + switch (ll) { + case 0: + myvsnprintf_uint(&dest,de,width,zero,neg,16,'A',va_arg(ap,unsigned int)); + break; + case 1: + myvsnprintf_uint(&dest,de,width,zero,neg,16,'A',va_arg(ap,unsigned long)); + break; + case 2: + myvsnprintf_uint(&dest,de,width,zero,neg,16,'A',va_arg(ap,ulonglong)); + break; + } + break; + default: + break; + } + ++fmt; + state = 0; + break; + default: + state = 0; + break; + } + *dest = '\0'; +} + +static void errorjmp(MatroskaFile *mf,const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + myvsnprintf(mf->errmsg,sizeof(mf->errmsg),fmt,ap); + va_end(ap); + + mf->flags |= MPF_ERROR; + + longjmp(mf->jb,1); +} + +/////////////////////////////////////////////////////////////////////////// +// arrays +static void *ArrayAlloc(MatroskaFile *mf,void **base, + unsigned *cur,unsigned *max,unsigned elem_size) +{ + if (*cur>=*max) { + void *np; + unsigned newsize = *max * 2; + if (newsize==0) + newsize = 1; + + np = mf->cache->memrealloc(mf->cache,*base,newsize*elem_size); + if (np==NULL) + errorjmp(mf,"Out of memory in ArrayAlloc"); + + *base = np; + *max = newsize; + } + + return (char*)*base + elem_size * (*cur)++; +} + +static void ArrayReleaseMemory(MatroskaFile *mf,void **base, + unsigned cur,unsigned *max,unsigned elem_size) +{ + if (cur<*max) { + void *np = mf->cache->memrealloc(mf->cache,*base,cur*elem_size); + *base = np; + *max = cur; + } +} + + +#define ASGET(f,s,name) ArrayAlloc((f),(void**)&(s)->name,&(s)->n##name,&(s)->n##name##Size,sizeof(*((s)->name))) +#define AGET(f,name) ArrayAlloc((f),(void**)&(f)->name,&(f)->n##name,&(f)->n##name##Size,sizeof(*((f)->name))) +#define ARELEASE(f,s,name) ArrayReleaseMemory((f),(void**)&(s)->name,(s)->n##name,&(s)->n##name##Size,sizeof(*((s)->name))) + +/////////////////////////////////////////////////////////////////////////// +// queues +static struct QueueEntry *QPut(struct Queue *q,struct QueueEntry *qe) { + if (q->tail) + q->tail->next = qe; + qe->next = NULL; + q->tail = qe; + if (q->head==NULL) + q->head = qe; + + return qe; +} + +static struct QueueEntry *QGet(struct Queue *q) { + struct QueueEntry *qe = q->head; + if (qe == NULL) + return NULL; + q->head = qe->next; + if (q->tail == qe) + q->tail = NULL; + return qe; +} + +static struct QueueEntry *QAlloc(MatroskaFile *mf) { + struct QueueEntry *qe,**qep; + if (mf->QFreeList == NULL) { + unsigned i; + + qep = AGET(mf,QBlocks); + + *qep = mf->cache->memalloc(mf->cache,QSEGSIZE * sizeof(*qe)); + if (*qep == NULL) + errorjmp(mf,"Ouf of memory"); + + qe = *qep; + + for (i=0;iQFreeList = qe; + } + + qe = mf->QFreeList; + mf->QFreeList = qe->next; + + return qe; +} + +static inline void QFree(MatroskaFile *mf,struct QueueEntry *qe) { + qe->next = mf->QFreeList; + mf->QFreeList = qe; +} + +// fill the buffer at current position +static void fillbuf(MatroskaFile *mf) { + int rd; + + // advance buffer pointers + mf->bufbase += mf->buflen; + mf->buflen = mf->bufpos = 0; + + // get the relevant page + rd = mf->cache->read(mf->cache, mf->bufbase, mf->inbuf, IBSZ); + if (rd<0) + errorjmp(mf,"I/O Error: %s",mf->cache->geterror(mf->cache)); + + mf->buflen = rd; +} + +// fill the buffer and return next char +static int nextbuf(MatroskaFile *mf) { + fillbuf(mf); + + if (mf->bufpos < mf->buflen) + return (unsigned char)(mf->inbuf[mf->bufpos++]); + + return EOF; +} + +static inline int readch(MatroskaFile *mf) { + return mf->bufpos < mf->buflen ? (unsigned char)(mf->inbuf[mf->bufpos++]) : nextbuf(mf); +} + +static inline ulonglong filepos(MatroskaFile *mf) { + return mf->bufbase + mf->bufpos; +} + +static void readbytes(MatroskaFile *mf,void *buffer,int len) { + char *cp = buffer; + int nb = mf->buflen - mf->bufpos; + + if (nb > len) + nb = len; + + memcpy(cp, mf->inbuf + mf->bufpos, nb); + mf->bufpos += nb; + len -= nb; + cp += nb; + + if (len>0) { + mf->bufbase += mf->buflen; + mf->bufpos = mf->buflen = 0; + + nb = mf->cache->read(mf->cache, mf->bufbase, cp, len); + if (nb<0) + errorjmp(mf,"I/O Error: %s",mf->cache->geterror(mf->cache)); + if (nb != len) + errorjmp(mf,"Short read: got %d bytes of %d",nb,len); + mf->bufbase += len; + } +} + +static void skipbytes(MatroskaFile *mf,ulonglong len) { + int nb = mf->buflen - mf->bufpos; + + if (nb > len) + nb = (int)len; + + mf->bufpos += nb; + len -= nb; + + if (len>0) { + mf->bufbase += mf->buflen; + mf->bufpos = mf->buflen = 0; + + mf->bufbase += len; + } +} + +static void seek(MatroskaFile *mf,ulonglong pos) { + // see if pos is inside buffer + if (pos>=mf->bufbase && posbufbase+mf->buflen) + mf->bufpos = (unsigned)(pos - mf->bufbase); + else { + // invalidate buffer and set pointer + mf->bufbase = pos; + mf->buflen = mf->bufpos = 0; + } +} + +/////////////////////////////////////////////////////////////////////////// +// floating point +static inline MKFLOAT mkfi(int i) { +#ifdef MATROSKA_INTEGER_ONLY + MKFLOAT f; + f.v = (longlong)i << 32; + return f; +#else + return i; +#endif +} + +static inline longlong mul3(MKFLOAT scale,longlong tc) { +#ifdef MATROSKA_INTEGER_ONLY + // x1 x0 + // y1 y0 + // -------------- + // x0*y0 + // x1*y0 + // x0*y1 + // x1*y1 + // -------------- + // .. r1 r0 .. + // + // r = ((x0*y0) >> 32) + (x1*y0) + (x0*y1) + ((x1*y1) << 32) + unsigned x0,x1,y0,y1; + ulonglong p; + char sign = 0; + + if (scale.v < 0) + sign = !sign, scale.v = -scale.v; + if (tc < 0) + sign = !sign, tc = -tc; + + x0 = (unsigned)scale.v; + x1 = (unsigned)((ulonglong)scale.v >> 32); + y0 = (unsigned)tc; + y1 = (unsigned)((ulonglong)tc >> 32); + + p = (ulonglong)x0*y0 >> 32; + p += (ulonglong)x0*y1; + p += (ulonglong)x1*y0; + p += (ulonglong)(x1*y1) << 32; + + return p; +#else + return (longlong)(scale * tc); +#endif +} + +/////////////////////////////////////////////////////////////////////////// +// EBML support +static int readID(MatroskaFile *mf) { + int c1,c2,c3,c4; + + c1 = readch(mf); + if (c1 == EOF) + return EOF; + + if (c1 & 0x80) + return c1; + + if ((c1 & 0xf0) == 0) + errorjmp(mf,"Invalid first byte of EBML ID: %02X",c1); + + c2 = readch(mf); + if (c2 == EOF) +fail: + errorjmp(mf,"Got EOF while reading EBML ID"); + + if ((c1 & 0xc0) == 0x40) + return (c1<<8) | c2; + + c3 = readch(mf); + if (c3 == EOF) + goto fail; + + if ((c1 & 0xe0) == 0x20) + return (c1<<16) | (c2<<8) | c3; + + c4 = readch(mf); + if (c4 == EOF) + goto fail; + + if ((c1 & 0xf0) == 0x10) + return (c1<<24) | (c2<<16) | (c3<<8) | c4; + + return 0; // NOT REACHED +} + +static ulonglong readVLUIntImp(MatroskaFile *mf,int *mask) { + int c,d,m; + ulonglong v = 0; + + c = readch(mf); + if (c == EOF) + return EOF; + + if (c == 0) + errorjmp(mf,"Invalid first byte of EBML integer: 0"); + + for (m=0;;++m) { + if (c & (0x80 >> m)) { + c &= 0x7f >> m; + if (mask) + *mask = m; + return v | ((ulonglong)c << m*8); + } + d = readch(mf); + if (d == EOF) + errorjmp(mf,"Got EOF while reading EBML unsigned integer"); + v = (v<<8) | d; + } + // NOT REACHED +} + +static inline ulonglong readVLUInt(MatroskaFile *mf) { + return readVLUIntImp(mf,NULL); +} + +static ulonglong readSize(MatroskaFile *mf) { + int m; + ulonglong v = readVLUIntImp(mf,&m); + + // see if it's unspecified + if (v == (MAXU64 >> (57-m*7))) + errorjmp(mf,"Unspecified element size is not supported here."); + + return v; +} + +static inline longlong readVLSInt(MatroskaFile *mf) { + static longlong bias[8] = { (ONE<<6)-1, (ONE<<13)-1, (ONE<<20)-1, (ONE<<27)-1, + (ONE<<34)-1, (ONE<<41)-1, (ONE<<48)-1, (ONE<<55)-1 }; + + int m; + longlong v = readVLUIntImp(mf,&m); + + return v - bias[m]; +} + +static ulonglong readUInt(MatroskaFile *mf,unsigned int len) { + int c; + unsigned int m = len; + ulonglong v = 0; + + if (len==0) + return v; + if (len>8) + errorjmp(mf,"Unsupported integer size in readUInt: %u",len); + + do { + c = readch(mf); + if (c == EOF) + errorjmp(mf,"Got EOF while reading EBML unsigned integer"); + v = (v<<8) | c; + } while (--m); + + return v; +} + +static inline longlong readSInt(MatroskaFile *mf,unsigned int len) { + longlong v = readUInt(mf,(unsigned)len); + int s = 64 - (len<<3); + return (v << s) >> s; +} + +static MKFLOAT readFloat(MatroskaFile *mf,unsigned int len) { +#ifdef MATROSKA_INTEGER_ONLY + MKFLOAT f; + int shift; +#else + union { + unsigned int ui; + ulonglong ull; + float f; + double d; + } u; +#endif + + if (len!=4 && len!=8) + errorjmp(mf,"Invalid float size in readFloat: %u",len); + +#ifdef MATROSKA_INTEGER_ONLY + if (len == 4) { + unsigned ui = (unsigned)readUInt(mf,(unsigned)len); + f.v = (ui & 0x7fffff) | 0x800000; + if (ui & 0x80000000) + f.v = -f.v; + shift = (ui >> 23) & 0xff; + if (shift == 0) // assume 0 +zero: + shift = 0, f.v = 0; + else if (shift == 255) +inf: + if (ui & 0x80000000) + f.v = LL(0x8000000000000000); + else + f.v = LL(0x7fffffffffffffff); + else { + shift += -127 + 9; + if (shift > 39) + goto inf; +shift: + if (shift < 0) + f.v = f.v >> -shift; + else if (shift > 0) + f.v = f.v << shift; + } + } else if (len == 8) { + ulonglong ui = readUInt(mf,(unsigned)len); + f.v = (ui & LL(0xfffffffffffff)) | LL(0x10000000000000); + if (ui & 0x80000000) + f.v = -f.v; + shift = (int)((ui >> 52) & 0x7ff); + if (shift == 0) // assume 0 + goto zero; + else if (shift == 2047) + goto inf; + else { + shift += -1023 - 20; + if (shift > 10) + goto inf; + goto shift; + } + } + + return f; +#else + if (len==4) { + u.ui = (unsigned int)readUInt(mf,(unsigned)len); + return u.f; + } + + if (len==8) { + u.ull = readUInt(mf,(unsigned)len); + return u.d; + } + + return 0; +#endif +} + +static void readString(MatroskaFile *mf,ulonglong len,char *buffer,int buflen) { + int nread; + + if (buflen<1) + errorjmp(mf,"Invalid buffer size in readString: %d",buflen); + + nread = buflen - 1; + + if (nread > len) + nread = (int)len; + + readbytes(mf,buffer,nread); + len -= nread; + + if (len>0) + skipbytes(mf,len); + + buffer[nread] = '\0'; +} + +static void readLangCC(MatroskaFile *mf, ulonglong len, char lcc[4]) { + unsigned todo = len > 3 ? 3 : (int)len; + + lcc[0] = lcc[1] = lcc[2] = lcc[3] = 0; + readbytes(mf, lcc, todo); + skipbytes(mf, len - todo); +} + +/////////////////////////////////////////////////////////////////////////// +// file parser +#define FOREACH(f,tl) \ + { \ + ulonglong tmplen = (tl); \ + { \ + ulonglong start = filepos(f); \ + ulonglong cur,len; \ + int id; \ + for (;;) { \ + cur = filepos(mf); \ + if (cur == start + tmplen) \ + break; \ + id = readID(f); \ + if (id==EOF) \ + errorjmp(mf,"Unexpected EOF while reading EBML container"); \ + len = readSize(mf); \ + switch (id) { + +#define ENDFOR1(f) \ + default: \ + skipbytes(f,len); \ + break; \ + } +#define ENDFOR2() \ + } \ + } \ + } + +#define ENDFOR(f) ENDFOR1(f) ENDFOR2() + +#define myalloca(f,c) alloca(c) +#define STRGETF(f,v,len,func) \ + { \ + char *TmpVal; \ + unsigned TmpLen = (len)>MAX_STRING_LEN ? MAX_STRING_LEN : (unsigned)(len); \ + TmpVal = func(f->cache,TmpLen+1); \ + if (TmpVal == NULL) \ + errorjmp(mf,"Out of memory"); \ + readString(f,len,TmpVal,TmpLen+1); \ + (v) = TmpVal; \ + } + +#define STRGETA(f,v,len) STRGETF(f,v,len,myalloca) +#define STRGETM(f,v,len) STRGETF(f,v,len,f->cache->memalloc) + +static int IsWritingApp(MatroskaFile *mf,const char *str) { + const char *cp = mf->Seg.WritingApp; + if (!cp) + return 0; + + while (*str && *str++==*cp++) ; + + return !*str; +} + +static void parseEBML(MatroskaFile *mf,ulonglong toplen) { + ulonglong v; + char buf[32]; + + FOREACH(mf,toplen) + case 0x4286: // Version + v = readUInt(mf,(unsigned)len); + break; + case 0x42f7: // ReadVersion + v = readUInt(mf,(unsigned)len); + if (v > EBML_VERSION) + errorjmp(mf,"File requires version %d EBML parser",(int)v); + break; + case 0x42f2: // MaxIDLength + v = readUInt(mf,(unsigned)len); + if (v > EBML_MAX_ID_LENGTH) + errorjmp(mf,"File has identifiers longer than %d",(int)v); + break; + case 0x42f3: // MaxSizeLength + v = readUInt(mf,(unsigned)len); + if (v > EBML_MAX_SIZE_LENGTH) + errorjmp(mf,"File has integers longer than %d",(int)v); + break; + case 0x4282: // DocType + readString(mf,len,buf,sizeof(buf)); + if (strcmp(buf,MATROSKA_DOCTYPE)) + errorjmp(mf,"Unsupported DocType: %s",buf); + break; + case 0x4287: // DocTypeVersion + v = readUInt(mf,(unsigned)len); + break; + case 0x4285: // DocTypeReadVersion + v = readUInt(mf,(unsigned)len); + if (v > MATROSKA_VERSION) + errorjmp(mf,"File requires version %d Matroska parser",(int)v); + break; + ENDFOR(mf); +} + +static void parseSeekEntry(MatroskaFile *mf,ulonglong toplen) { + int seekid = 0; + ulonglong pos = (ulonglong)-1; + + FOREACH(mf,toplen) + case 0x53ab: // SeekID + if (len>EBML_MAX_ID_LENGTH) + errorjmp(mf,"Invalid ID size in parseSeekEntry: %d\n",(int)len); + seekid = (int)readUInt(mf,(unsigned)len); + break; + case 0x53ac: // SeekPos + pos = readUInt(mf,(unsigned)len); + break; + ENDFOR(mf); + + if (pos == (ulonglong)-1) + errorjmp(mf,"Invalid element position in parseSeekEntry"); + + pos += mf->pSegment; + switch (seekid) { + case 0x114d9b74: // next SeekHead + if (mf->pSeekHead) + errorjmp(mf,"SeekHead contains more than one SeekHead pointer"); + mf->pSeekHead = pos; + break; + case 0x1549a966: // SegmentInfo + mf->pSegmentInfo = pos; + break; + case 0x1f43b675: // Cluster + if (!mf->pCluster) + mf->pCluster = pos; + break; + case 0x1654ae6b: // Tracks + mf->pTracks = pos; + break; + case 0x1c53bb6b: // Cues + mf->pCues = pos; + break; + case 0x1941a469: // Attachments + mf->pAttachments = pos; + break; + case 0x1043a770: // Chapters + mf->pChapters = pos; + break; + case 0x1254c367: // tags + mf->pTags = pos; + break; + } +} + +static void parseSeekHead(MatroskaFile *mf,ulonglong toplen) { + FOREACH(mf,toplen) + case 0x4dbb: + parseSeekEntry(mf,len); + break; + ENDFOR(mf); +} + +static void parseSegmentInfo(MatroskaFile *mf,ulonglong toplen) { + MKFLOAT duration = mkfi(0); + + if (mf->seen.SegmentInfo) { + skipbytes(mf,toplen); + return; + } + + mf->seen.SegmentInfo = 1; + mf->Seg.TimecodeScale = 1000000; // Default value + + FOREACH(mf,toplen) + case 0x73a4: // SegmentUID + if (len!=sizeof(mf->Seg.UID)) + errorjmp(mf,"SegmentUID size is not %d bytes",mf->Seg.UID); + readbytes(mf,mf->Seg.UID,sizeof(mf->Seg.UID)); + break; + case 0x7384: // SegmentFilename + STRGETM(mf,mf->Seg.Filename,len); + break; + case 0x3cb923: // PrevUID + if (len!=sizeof(mf->Seg.PrevUID)) + errorjmp(mf,"PrevUID size is not %d bytes",mf->Seg.PrevUID); + readbytes(mf,mf->Seg.PrevUID,sizeof(mf->Seg.PrevUID)); + break; + case 0x3c83ab: // PrevFilename + STRGETM(mf,mf->Seg.PrevFilename,len); + break; + case 0x3eb923: // NextUID + if (len!=sizeof(mf->Seg.NextUID)) + errorjmp(mf,"NextUID size is not %d bytes",mf->Seg.NextUID); + readbytes(mf,mf->Seg.NextUID,sizeof(mf->Seg.NextUID)); + break; + case 0x3e83bb: // NextFilename + STRGETM(mf,mf->Seg.NextFilename,len); + break; + case 0x2ad7b1: // TimecodeScale + mf->Seg.TimecodeScale = readUInt(mf,(unsigned)len); + if (mf->Seg.TimecodeScale == 0) + errorjmp(mf,"Segment timecode scale is zero"); + break; + case 0x4489: // Duration + duration = readFloat(mf,(unsigned)len); + break; + case 0x4461: // DateUTC + mf->Seg.DateUTC = readUInt(mf,(unsigned)len); + break; + case 0x7ba9: // Title + STRGETM(mf,mf->Seg.Title,len); + break; + case 0x4d80: // MuxingApp + STRGETM(mf,mf->Seg.MuxingApp,len); + break; + case 0x5741: // WritingApp + STRGETM(mf,mf->Seg.WritingApp,len); + break; + ENDFOR(mf); + + mf->Seg.Duration = mul3(duration,mf->Seg.TimecodeScale); +} + +static void parseFirstCluster(MatroskaFile *mf,ulonglong toplen) { + mf->seen.Cluster = 1; + mf->firstTimecode = 0; + + FOREACH(mf,toplen) + case 0xe7: // Timecode + mf->firstTimecode += readUInt(mf,(unsigned)len); + break; + case 0xa3: // BlockEx + readVLUInt(mf); // track number + mf->firstTimecode += readSInt(mf, 2); + + skipbytes(mf,start + toplen - filepos(mf)); + return; + case 0xa0: // BlockGroup + FOREACH(mf,len) + case 0xa1: // Block + readVLUInt(mf); // track number + mf->firstTimecode += readSInt(mf,2); + + skipbytes(mf,start + toplen - filepos(mf)); + return; + ENDFOR(mf); + break; + ENDFOR(mf); +} + +static void parseVideoInfo(MatroskaFile *mf,ulonglong toplen,struct TrackInfo *ti) { + ulonglong v; + char dW = 0, dH = 0; + + FOREACH(mf,toplen) + case 0x9a: // FlagInterlaced + ti->AV.Video.Interlaced = readUInt(mf,(unsigned)len)!=0; + break; + case 0x53b8: // StereoMode + v = readUInt(mf,(unsigned)len); + if (v>3) + errorjmp(mf,"Invalid stereo mode"); + ti->AV.Video.StereoMode = (unsigned char)v; + break; + case 0xb0: // PixelWidth + v = readUInt(mf,(unsigned)len); + if (v>0xffffffff) + errorjmp(mf,"PixelWidth is too large"); + ti->AV.Video.PixelWidth = (unsigned)v; + if (!dW) + ti->AV.Video.DisplayWidth = ti->AV.Video.PixelWidth; + break; + case 0xba: // PixelHeight + v = readUInt(mf,(unsigned)len); + if (v>0xffffffff) + errorjmp(mf,"PixelHeight is too large"); + ti->AV.Video.PixelHeight = (unsigned)v; + if (!dH) + ti->AV.Video.DisplayHeight = ti->AV.Video.PixelHeight; + break; + case 0x54b0: // DisplayWidth + v = readUInt(mf,(unsigned)len); + if (v>0xffffffff) + errorjmp(mf,"DisplayWidth is too large"); + ti->AV.Video.DisplayWidth = (unsigned)v; + dW = 1; + break; + case 0x54ba: // DisplayHeight + v = readUInt(mf,(unsigned)len); + if (v>0xffffffff) + errorjmp(mf,"DisplayHeight is too large"); + ti->AV.Video.DisplayHeight = (unsigned)v; + dH = 1; + break; + case 0x54b2: // DisplayUnit + v = readUInt(mf,(unsigned)len); + if (v>2) + errorjmp(mf,"Invalid DisplayUnit: %d",(int)v); + ti->AV.Video.DisplayUnit = (unsigned char)v; + break; + case 0x54b3: // AspectRatioType + v = readUInt(mf,(unsigned)len); + if (v>2) + errorjmp(mf,"Invalid AspectRatioType: %d",(int)v); + ti->AV.Video.AspectRatioType = (unsigned char)v; + break; + case 0x54aa: // PixelCropBottom + v = readUInt(mf,(unsigned)len); + if (v>0xffffffff) + errorjmp(mf,"PixelCropBottom is too large"); + ti->AV.Video.CropB = (unsigned)v; + break; + case 0x54bb: // PixelCropTop + v = readUInt(mf,(unsigned)len); + if (v>0xffffffff) + errorjmp(mf,"PixelCropTop is too large"); + ti->AV.Video.CropT = (unsigned)v; + break; + case 0x54cc: // PixelCropLeft + v = readUInt(mf,(unsigned)len); + if (v>0xffffffff) + errorjmp(mf,"PixelCropLeft is too large"); + ti->AV.Video.CropL = (unsigned)v; + break; + case 0x54dd: // PixelCropRight + v = readUInt(mf,(unsigned)len); + if (v>0xffffffff) + errorjmp(mf,"PixelCropRight is too large"); + ti->AV.Video.CropR = (unsigned)v; + break; + case 0x2eb524: // ColourSpace + ti->AV.Video.ColourSpace = (unsigned)readUInt(mf,4); + break; + case 0x2fb523: // GammaValue + ti->AV.Video.GammaValue = readFloat(mf,(unsigned)len); + break; + ENDFOR(mf); +} + +static void parseAudioInfo(MatroskaFile *mf,ulonglong toplen,struct TrackInfo *ti) { + ulonglong v; + + FOREACH(mf,toplen) + case 0xb5: // SamplingFrequency + ti->AV.Audio.SamplingFreq = readFloat(mf,(unsigned)len); + break; + case 0x78b5: // OutputSamplingFrequency + ti->AV.Audio.OutputSamplingFreq = readFloat(mf,(unsigned)len); + break; + case 0x9f: // Channels + v = readUInt(mf,(unsigned)len); + if (v<1 || v>255) + errorjmp(mf,"Invalid Channels value"); + ti->AV.Audio.Channels = (unsigned char)v; + break; + case 0x7d7b: // ChannelPositions + skipbytes(mf,len); + break; + case 0x6264: // BitDepth + v = readUInt(mf,(unsigned)len); +#if 0 + if ((v<1 || v>255) && !IsWritingApp(mf,"AVI-Mux GUI")) + errorjmp(mf,"Invalid BitDepth: %d",(int)v); +#endif + ti->AV.Audio.BitDepth = (unsigned char)v; + break; + ENDFOR(mf); + + if (ti->AV.Audio.Channels == 0) + ti->AV.Audio.Channels = 1; + if (mkv_TruncFloat(ti->AV.Audio.SamplingFreq) == 0) + ti->AV.Audio.SamplingFreq = mkfi(8000); + if (mkv_TruncFloat(ti->AV.Audio.OutputSamplingFreq)==0) + ti->AV.Audio.OutputSamplingFreq = ti->AV.Audio.SamplingFreq; +} + +static void CopyStr(char **src,char **dst) { + size_t l; + + if (!*src) + return; + + l = strlen(*src)+1; + memcpy(*dst,*src,l); + *src = *dst; + *dst += l; +} + +static void parseTrackEntry(MatroskaFile *mf,ulonglong toplen) { + struct TrackInfo t,*tp,**tpp; + ulonglong v; + char *cp = NULL, *cs = NULL; + size_t cplen = 0, cslen = 0, cpadd = 0; + unsigned CompScope, num_comp = 0; + + if (mf->nTracks >= MAX_TRACKS) + errorjmp(mf,"Too many tracks."); + + // clear track info + memset(&t,0,sizeof(t)); + + // fill default values + t.Enabled = 1; + t.Default = 1; + t.Lacing = 1; + t.TimecodeScale = mkfi(1); + t.DecodeAll = 1; + + FOREACH(mf,toplen) + case 0xd7: // TrackNumber + v = readUInt(mf,(unsigned)len); + if (v>255) + errorjmp(mf,"Track number is >255 (%d)",(int)v); + t.Number = (unsigned char)v; + break; + case 0x73c5: // TrackUID + t.UID = readUInt(mf,(unsigned)len); + break; + case 0x83: // TrackType + v = readUInt(mf,(unsigned)len); + if (v<1 || v>254) + errorjmp(mf,"Invalid track type: %d",(int)v); + t.Type = (unsigned char)v; + break; + case 0xb9: // Enabled + t.Enabled = readUInt(mf,(unsigned)len)!=0; + break; + case 0x88: // Default + t.Default = readUInt(mf,(unsigned)len)!=0; + break; + case 0x9c: // Lacing + t.Lacing = readUInt(mf,(unsigned)len)!=0; + break; + case 0x6de7: // MinCache + v = readUInt(mf,(unsigned)len); + if (v > 0xffffffff) + errorjmp(mf,"MinCache is too large"); + t.MinCache = (unsigned)v; + break; + case 0x6df8: // MaxCache + v = readUInt(mf,(unsigned)len); + if (v > 0xffffffff) + errorjmp(mf,"MaxCache is too large"); + t.MaxCache = (unsigned)v; + break; + case 0x23e383: // DefaultDuration + t.DefaultDuration = readUInt(mf,(unsigned)len); + break; + case 0x23314f: // TrackTimecodeScale + t.TimecodeScale = readFloat(mf,(unsigned)len); + break; + case 0x55ee: // MaxBlockAdditionID + t.MaxBlockAdditionID = (unsigned)readUInt(mf,(unsigned)len); + break; + case 0x536e: // Name + if (t.Name) + errorjmp(mf,"Duplicate Track Name"); + STRGETA(mf,t.Name,len); + break; + case 0x22b59c: // Language + readLangCC(mf, len, t.Language); + break; + case 0x86: // CodecID + if (t.CodecID) + errorjmp(mf,"Duplicate CodecID"); + STRGETA(mf,t.CodecID,len); + break; + case 0x63a2: // CodecPrivate + if (cp) + errorjmp(mf,"Duplicate CodecPrivate"); + if (len>262144) // 256KB + errorjmp(mf,"CodecPrivate is too large: %d",(int)len); + cplen = (unsigned)len; + cp = alloca(cplen); + readbytes(mf,cp,(int)cplen); + break; + case 0x258688: // CodecName + skipbytes(mf,len); + break; + case 0x3a9697: // CodecSettings + skipbytes(mf,len); + break; + case 0x3b4040: // CodecInfoURL + skipbytes(mf,len); + break; + case 0x26b240: // CodecDownloadURL + skipbytes(mf,len); + break; + case 0xaa: // CodecDecodeAll + t.DecodeAll = readUInt(mf,(unsigned)len)!=0; + break; + case 0x6fab: // TrackOverlay + v = readUInt(mf,(unsigned)len); + if (v>255) + errorjmp(mf,"Track number in TrackOverlay is too large: %d",(int)v); + t.TrackOverlay = (unsigned char)v; + break; + case 0xe0: // VideoInfo + parseVideoInfo(mf,len,&t); + break; + case 0xe1: // AudioInfo + parseAudioInfo(mf,len,&t); + break; + case 0x6d80: // ContentEncodings + FOREACH(mf,len) + case 0x6240: // ContentEncoding + // fill in defaults + t.CompEnabled = 1; + t.CompMethod = COMP_ZLIB; + CompScope = 1; + if (++num_comp > 1) + return; // only one compression layer supported + FOREACH(mf,len) + case 0x5031: // ContentEncodingOrder + readUInt(mf,(unsigned)len); + break; + case 0x5032: // ContentEncodingScope + CompScope = (unsigned)readUInt(mf,(unsigned)len); + break; + case 0x5033: // ContentEncodingType + if (readUInt(mf,(unsigned)len) != 0) + return; // encryption is not supported + break; + case 0x5034: // ContentCompression + FOREACH(mf,len) + case 0x4254: // ContentCompAlgo + v = readUInt(mf,(unsigned)len); + t.CompEnabled = 1; + switch (v) { + case 0: // Zlib + t.CompMethod = COMP_ZLIB; + break; + case 3: // prepend fixed data + t.CompMethod = COMP_PREPEND; + break; + default: + return; // unsupported compression, skip track + } + break; + case 0x4255: // ContentCompSettings + if (len > 256) + return; + cslen = (unsigned)len; + cs = alloca(cslen); + readbytes(mf, cs, (int)cslen); + break; + ENDFOR(mf); + break; + // TODO Implement Encryption/Signatures + ENDFOR(mf); + break; + ENDFOR(mf); + break; + ENDFOR(mf); + + // validate track info + if (!t.CodecID) + errorjmp(mf,"Track has no Codec ID"); + + if (t.UID != 0) { + unsigned i; + for (i = 0; i < mf->nTracks; ++i) + if (mf->Tracks[i]->UID == t.UID) // duplicate track entry + return; + } + +#ifdef MATROSKA_COMPRESSION_SUPPORT + // handle compressed CodecPrivate + if (t.CompEnabled && t.CompMethod == COMP_ZLIB && (CompScope & 2) && cplen > 0) { + z_stream zs; + char tmp[64], *ncp; + int code; + uLong ncplen; + + memset(&zs,0,sizeof(zs)); + if (inflateInit(&zs) != Z_OK) + errorjmp(mf, "inflateInit failed"); + + zs.next_in = cp; + zs.avail_in = cplen; + + do { + zs.next_out = tmp; + zs.avail_out = sizeof(tmp); + + code = inflate(&zs, Z_NO_FLUSH); + } while (code == Z_OK); + + if (code != Z_STREAM_END) + errorjmp(mf, "invalid compressed data in CodecPrivate"); + + ncplen = zs.total_out; + ncp = alloca(ncplen); + + inflateReset(&zs); + + zs.next_in = cp; + zs.avail_in = cplen; + zs.next_out = ncp; + zs.avail_out = ncplen; + + if (inflate(&zs, Z_FINISH) != Z_STREAM_END) + errorjmp(mf, "inflate failed"); + + inflateEnd(&zs); + + cp = ncp; + cplen = ncplen; + } +#endif + + if (t.CompEnabled && !(CompScope & 1)) { + t.CompEnabled = 0; + cslen = 0; + } + + // allocate new track + tpp = AGET(mf,Tracks); + + // copy strings + if (t.Name) + cpadd += strlen(t.Name)+1; + if (t.CodecID) + cpadd += strlen(t.CodecID)+1; + + tp = mf->cache->memalloc(mf->cache,sizeof(*tp) + cplen + cslen + cpadd); + if (tp == NULL) + errorjmp(mf,"Out of memory"); + + memcpy(tp,&t,sizeof(*tp)); + if (cplen) { + tp->CodecPrivate = tp+1; + tp->CodecPrivateSize = (unsigned)cplen; + memcpy(tp->CodecPrivate,cp,cplen); + } + if (cslen) { + tp->CompMethodPrivate = (char *)(tp+1) + cplen; + tp->CompMethodPrivateSize = cslen; + memcpy(tp->CompMethodPrivate, cs, cslen); + } + + cp = (char*)(tp+1) + cplen + cslen; + CopyStr(&tp->Name,&cp); + CopyStr(&tp->CodecID,&cp); + + // set default language + if (!tp->Language[0]) + memcpy(tp->Language, "eng", 4); + + *tpp = tp; +} + +static void parseTracks(MatroskaFile *mf,ulonglong toplen) { + mf->seen.Tracks = 1; + FOREACH(mf,toplen) + case 0xae: // TrackEntry + parseTrackEntry(mf,len); + break; + ENDFOR(mf); +} + +static void addCue(MatroskaFile *mf,ulonglong pos,ulonglong timecode) { + struct Cue *cc = AGET(mf,Cues); + cc->Time = timecode; + cc->Position = pos; + cc->Track = 0; + cc->Block = 0; +} + +static void fixupCues(MatroskaFile *mf) { + // adjust cues, shift cues if file does not start at 0 + unsigned i; + longlong adjust = mf->firstTimecode * mf->Seg.TimecodeScale; + + for (i=0;inCues;++i) { + mf->Cues[i].Time *= mf->Seg.TimecodeScale; + mf->Cues[i].Time -= adjust; + } +} + +static void parseCues(MatroskaFile *mf,ulonglong toplen) { + jmp_buf jb; + ulonglong v; + struct Cue cc; + unsigned i,j,k; + + mf->seen.Cues = 1; + mf->nCues = 0; + cc.Block = 0; + + memcpy(&jb,&mf->jb,sizeof(jb)); + + if (setjmp(mf->jb)) { + memcpy(&mf->jb,&jb,sizeof(jb)); + mf->nCues = 0; + mf->seen.Cues = 0; + return; + } + + FOREACH(mf,toplen) + case 0xbb: // CuePoint + FOREACH(mf,len) + case 0xb3: // CueTime + cc.Time = readUInt(mf,(unsigned)len); + break; + case 0xb7: // CueTrackPositions + FOREACH(mf,len) + case 0xf7: // CueTrack + v = readUInt(mf,(unsigned)len); + if (v>255) + errorjmp(mf,"CueTrack points to an invalid track: %d",(int)v); + cc.Track = (unsigned char)v; + break; + case 0xf1: // CueClusterPosition + cc.Position = readUInt(mf,(unsigned)len); + break; + case 0x5378: // CueBlockNumber + cc.Block = readUInt(mf,(unsigned)len); + break; + case 0xea: // CodecState + readUInt(mf,(unsigned)len); + break; + case 0xdb: // CueReference + FOREACH(mf,len) + case 0x96: // CueRefTime + readUInt(mf,(unsigned)len); + break; + case 0x97: // CueRefCluster + readUInt(mf,(unsigned)len); + break; + case 0x535f: // CueRefNumber + readUInt(mf,(unsigned)len); + break; + case 0xeb: // CueRefCodecState + readUInt(mf,(unsigned)len); + break; + ENDFOR(mf); + break; + ENDFOR(mf); + break; + ENDFOR(mf); + + if (mf->nCues == 0 && mf->pCluster - mf->pSegment != cc.Position) + addCue(mf,mf->pCluster - mf->pSegment,mf->firstTimecode); + + memcpy(AGET(mf,Cues),&cc,sizeof(cc)); + break; + ENDFOR(mf); + + memcpy(&mf->jb,&jb,sizeof(jb)); + + ARELEASE(mf,mf,Cues); + + // bubble sort the cues and fuck the losers that write unordered cues + if (mf->nCues > 0) + for (i = mf->nCues - 1, k = 1; i > 0 && k > 0; --i) + for (j = k = 0; j < i; ++j) + if (mf->Cues[j].Time > mf->Cues[j+1].Time) { + struct Cue tmp = mf->Cues[j+1]; + mf->Cues[j+1] = mf->Cues[j]; + mf->Cues[j] = tmp; + ++k; + } +} + +static void parseAttachment(MatroskaFile *mf,ulonglong toplen) { + struct Attachment a,*pa; + + memset(&a,0,sizeof(a)); + FOREACH(mf,toplen) + case 0x467e: // Description + STRGETA(mf,a.Description,len); + break; + case 0x466e: // Name + STRGETA(mf,a.Name,len); + break; + case 0x4660: // MimeType + STRGETA(mf,a.MimeType,len); + break; + case 0x46ae: // UID + a.UID = readUInt(mf,(unsigned)len); + break; + case 0x465c: // Data + a.Position = filepos(mf); + a.Length = len; + skipbytes(mf,len); + break; + ENDFOR(mf); + + if (!a.Position) + return; + + pa = AGET(mf,Attachments); + memcpy(pa,&a,sizeof(a)); + + if (a.Description) + pa->Description = mystrdup(mf->cache,a.Description); + if (a.Name) + pa->Name = mystrdup(mf->cache,a.Name); + if (a.MimeType) + pa->MimeType = mystrdup(mf->cache,a.MimeType); +} + +static void parseAttachments(MatroskaFile *mf,ulonglong toplen) { + mf->seen.Attachments = 1; + + FOREACH(mf,toplen) + case 0x61a7: // AttachedFile + parseAttachment(mf,len); + break; + ENDFOR(mf); +} + +static void parseChapter(MatroskaFile *mf,ulonglong toplen,struct Chapter *parent) { + struct ChapterDisplay *disp; + struct ChapterProcess *proc; + struct ChapterCommand *cmd; + struct Chapter *ch = ASGET(mf,parent,Children); + + memset(ch,0,sizeof(*ch)); + + ch->Enabled = 1; + + FOREACH(mf,toplen) + case 0x73c4: // ChapterUID + ch->UID = readUInt(mf,(unsigned)len); + break; + case 0x6e67: // ChapterSegmentUID + if (len != sizeof(ch->SegmentUID)) + skipbytes(mf, len); + else + readbytes(mf, ch->SegmentUID, sizeof(ch->SegmentUID)); + break; + case 0x91: // ChapterTimeStart + ch->Start = readUInt(mf,(unsigned)len); + break; + case 0x92: // ChapterTimeEnd + ch->End = readUInt(mf,(unsigned)len); + break; + case 0x98: // ChapterFlagHidden + ch->Hidden = readUInt(mf,(unsigned)len)!=0; + break; + case 0x4598: // ChapterFlagEnabled + ch->Enabled = readUInt(mf,(unsigned)len)!=0; + break; + case 0x8f: // ChapterTrack + FOREACH(mf,len) + case 0x89: // ChapterTrackNumber + *(ulonglong*)(ASGET(mf,ch,Tracks)) = readUInt(mf,(unsigned)len); + break; + ENDFOR(mf); + break; + case 0x80: // ChapterDisplay + disp = NULL; + + FOREACH(mf,len) + case 0x85: // ChapterString + if (disp==NULL) { + disp = ASGET(mf,ch,Display); + memset(disp, 0, sizeof(*disp)); + } + if (disp->String) + skipbytes(mf,len); // Ignore duplicate string + else + STRGETM(mf,disp->String,len); + break; + case 0x437c: // ChapterLanguage + if (disp==NULL) { + disp = ASGET(mf,ch,Display); + memset(disp, 0, sizeof(*disp)); + } + readLangCC(mf, len, disp->Language); + break; + case 0x437e: // ChapterCountry + if (disp==NULL) { + disp = ASGET(mf,ch,Display); + memset(disp, 0, sizeof(*disp)); + } + readLangCC(mf, len, disp->Country); + break; + ENDFOR(mf); + + if (disp && !disp->String) + --ch->nDisplay; + break; + case 0x6944: // ChapProcess + proc = NULL; + + FOREACH(mf,len) + case 0x6955: // ChapProcessCodecID + if (proc == NULL) { + proc = ASGET(mf, ch, Process); + memset(proc, 0, sizeof(*proc)); + } + proc->CodecID = (unsigned)readUInt(mf,(unsigned)len); + break; + case 0x450d: // ChapProcessPrivate + if (proc == NULL) { + proc = ASGET(mf, ch, Process); + memset(proc, 0, sizeof(*proc)); + } + if (proc->CodecPrivate) + skipbytes(mf, len); + else { + proc->CodecPrivateLength = (unsigned)len; + STRGETM(mf,proc->CodecPrivate,len); + } + break; + case 0x6911: // ChapProcessCommand + if (proc == NULL) { + proc = ASGET(mf, ch, Process); + memset(proc, 0, sizeof(*proc)); + } + + cmd = NULL; + + FOREACH(mf,len) + case 0x6922: // ChapterCommandTime + if (cmd == NULL) { + cmd = ASGET(mf,proc,Commands); + memset(cmd, 0, sizeof(*cmd)); + } + cmd->Time = (unsigned)readUInt(mf,(unsigned)len); + break; + case 0x6933: // ChapterCommandString + if (cmd == NULL) { + cmd = ASGET(mf,proc,Commands); + memset(cmd, 0, sizeof(*cmd)); + } + if (cmd->Command) + skipbytes(mf,len); + else { + cmd->CommandLength = (unsigned)len; + STRGETM(mf,cmd->Command,len); + } + break; + ENDFOR(mf); + + if (cmd && !cmd->Command) + --proc->nCommands; + break; + ENDFOR(mf); + + if (proc && !proc->nCommands) + --ch->nProcess; + break; + case 0xb6: // Nested ChapterAtom + parseChapter(mf,len,ch); + break; + ENDFOR(mf); + + ARELEASE(mf,ch,Tracks); + ARELEASE(mf,ch,Display); + ARELEASE(mf,ch,Children); +} + +static void parseChapters(MatroskaFile *mf,ulonglong toplen) { + struct Chapter *ch; + + mf->seen.Chapters = 1; + + FOREACH(mf,toplen) + case 0x45b9: // EditionEntry + ch = AGET(mf,Chapters); + memset(ch, 0, sizeof(*ch)); + FOREACH(mf,len) + case 0x45bc: // EditionUID + ch->UID = readUInt(mf,(unsigned)len); + break; + case 0x45bd: // EditionFlagHidden + ch->Hidden = readUInt(mf,(unsigned)len)!=0; + break; + case 0x45db: // EditionFlagDefault + ch->Default = readUInt(mf,(unsigned)len)!=0; + break; + case 0x45dd: // EditionFlagOrdered + ch->Ordered = readUInt(mf,(unsigned)len)!=0; + break; + case 0xb6: // ChapterAtom + parseChapter(mf,len,ch); + break; + ENDFOR(mf); + break; + ENDFOR(mf); +} + +static void parseTags(MatroskaFile *mf,ulonglong toplen) { + struct Tag *tag; + struct Target *target; + struct SimpleTag *st; + + mf->seen.Tags = 1; + + FOREACH(mf,toplen) + case 0x7373: // Tag + tag = AGET(mf,Tags); + memset(tag,0,sizeof(*tag)); + + FOREACH(mf,len) + case 0x63c0: // Targets + FOREACH(mf,len) + case 0x63c5: // TrackUID + target = ASGET(mf,tag,Targets); + target->UID = readUInt(mf,(unsigned)len); + target->Type = TARGET_TRACK; + break; + case 0x63c4: // ChapterUID + target = ASGET(mf,tag,Targets); + target->UID = readUInt(mf,(unsigned)len); + target->Type = TARGET_CHAPTER; + break; + case 0x63c6: // AttachmentUID + target = ASGET(mf,tag,Targets); + target->UID = readUInt(mf,(unsigned)len); + target->Type = TARGET_ATTACHMENT; + break; + case 0x63c9: // EditionUID + target = ASGET(mf,tag,Targets); + target->UID = readUInt(mf,(unsigned)len); + target->Type = TARGET_EDITION; + break; + ENDFOR(mf); + break; + case 0x67c8: // SimpleTag + st = ASGET(mf,tag,SimpleTags); + memset(st,0,sizeof(*st)); + + FOREACH(mf,len) + case 0x45a3: // TagName + if (st->Name) + skipbytes(mf,len); + else + STRGETM(mf,st->Name,len); + break; + case 0x4487: // TagString + if (st->Value) + skipbytes(mf,len); + else + STRGETM(mf,st->Value,len); + break; + case 0x447a: // TagLanguage + readLangCC(mf, len, st->Language); + break; + case 0x4484: // TagDefault + st->Default = readUInt(mf,(unsigned)len)!=0; + break; + ENDFOR(mf); + + if (!st->Name || !st->Value) { + mf->cache->memfree(mf->cache,st->Name); + mf->cache->memfree(mf->cache,st->Value); + --tag->nSimpleTags; + } + break; + ENDFOR(mf); + break; + ENDFOR(mf); +} + +static void parseContainer(MatroskaFile *mf) { + ulonglong len; + int id = readID(mf); + if (id==EOF) + errorjmp(mf,"Unexpected EOF in parseContainer"); + + len = readSize(mf); + + switch (id) { + case 0x1549a966: // SegmentInfo + parseSegmentInfo(mf,len); + break; + case 0x1f43b675: // Cluster + parseFirstCluster(mf,len); + break; + case 0x1654ae6b: // Tracks + parseTracks(mf,len); + break; + case 0x1c53bb6b: // Cues + parseCues(mf,len); + break; + case 0x1941a469: // Attachments + parseAttachments(mf,len); + break; + case 0x1043a770: // Chapters + parseChapters(mf,len); + break; + case 0x1254c367: // Tags + parseTags(mf,len); + break; + } +} + +static void parseContainerPos(MatroskaFile *mf,ulonglong pos) { + seek(mf,pos); + parseContainer(mf); +} + +static void parsePointers(MatroskaFile *mf) { + jmp_buf jb; + + if (mf->pSegmentInfo && !mf->seen.SegmentInfo) + parseContainerPos(mf,mf->pSegmentInfo); + if (mf->pCluster && !mf->seen.Cluster) + parseContainerPos(mf,mf->pCluster); + if (mf->pTracks && !mf->seen.Tracks) + parseContainerPos(mf,mf->pTracks); + + memcpy(&jb,&mf->jb,sizeof(jb)); + + if (setjmp(mf->jb)) + mf->flags &= ~MPF_ERROR; // ignore errors + else { + if (mf->pCues && !mf->seen.Cues) + parseContainerPos(mf,mf->pCues); + if (mf->pAttachments && !mf->seen.Attachments) + parseContainerPos(mf,mf->pAttachments); + if (mf->pChapters && !mf->seen.Chapters) + parseContainerPos(mf,mf->pChapters); + if (mf->pTags && !mf->seen.Tags) + parseContainerPos(mf,mf->pTags); + } + + memcpy(&mf->jb,&jb,sizeof(jb)); +} + +static void parseSegment(MatroskaFile *mf,ulonglong toplen) { + ulonglong nextpos; + unsigned nSeekHeads = 0, dontstop = 0; + + // we want to read data until we find a seekhead or a trackinfo + FOREACH(mf,toplen) + case 0x114d9b74: // SeekHead + if (mf->flags & MKVF_AVOID_SEEKS) { + skipbytes(mf,len); + break; + } + + nextpos = filepos(mf) + len; + do { + mf->pSeekHead = 0; + parseSeekHead(mf,len); + ++nSeekHeads; + if (mf->pSeekHead) { // this is possibly a chained SeekHead + seek(mf,mf->pSeekHead); + id = readID(mf); + if (id==EOF) // chained SeekHead points to EOF? + break; + if (id != 0x114d9b74) // chained SeekHead doesnt point to a SeekHead? + break; + len = readSize(mf); + } else if (mf->pSegmentInfo && mf->pTracks && mf->pCues && mf->pCluster) { // we have pointers to all key elements + // XXX EVIL HACK + // Some software doesnt index tags via SeekHead, so we continue + // reading the segment after the second SeekHead + if (mf->pTags || nSeekHeads<2 || filepos(mf)>=start+toplen) { + parsePointers(mf); + return; + } + // reset nextpos pointer to current position + nextpos = filepos(mf); + dontstop = 1; + } + } while (mf->pSeekHead); + seek(mf,nextpos); // resume reading segment + break; + case 0x1549a966: // SegmentInfo + mf->pSegmentInfo = cur; + parseSegmentInfo(mf,len); + break; + case 0x1f43b675: // Cluster + if (!mf->pCluster) + mf->pCluster = cur; + if (mf->seen.Cluster) + skipbytes(mf,len); + else + parseFirstCluster(mf,len); + break; + case 0x1654ae6b: // Tracks + mf->pTracks = cur; + parseTracks(mf,len); + break; + case 0x1c53bb6b: // Cues + mf->pCues = cur; + parseCues(mf,len); + break; + case 0x1941a469: // Attachments + mf->pAttachments = cur; + parseAttachments(mf,len); + break; + case 0x1043a770: // Chapters + mf->pChapters = cur; + parseChapters(mf,len); + break; + case 0x1254c367: // Tags + mf->pTags = cur; + parseTags(mf,len); + break; + ENDFOR1(mf); + // if we have pointers to all key elements + if (!dontstop && mf->pSegmentInfo && mf->pTracks && mf->pCluster) + break; + ENDFOR2(); + parsePointers(mf); +} + +static void parseBlockAdditions(MatroskaFile *mf, ulonglong toplen, ulonglong timecode, unsigned track) { + ulonglong add_id = 1, add_pos, add_len; + unsigned char have_add; + + FOREACH(mf, toplen) + case 0xa6: // BlockMore + have_add = 0; + FOREACH(mf, len) + case 0xee: // BlockAddId + add_id = readUInt(mf, (unsigned)len); + break; + case 0xa5: // BlockAddition + add_pos = filepos(mf); + add_len = len; + skipbytes(mf, len); + ++have_add; + break; + ENDFOR(mf); + if (have_add == 1 && id > 0 && id < 255) { + struct QueueEntry *qe = QAlloc(mf); + qe->Start = qe->End = timecode; + qe->Position = add_pos; + qe->Length = (unsigned)add_len; + qe->flags = FRAME_UNKNOWN_START | FRAME_UNKNOWN_END | + (((unsigned)add_id << FRAME_STREAM_SHIFT) & FRAME_STREAM_MASK); + + QPut(&mf->Queues[track],qe); + } + break; + ENDFOR(mf); +} + +static void parseBlockGroup(MatroskaFile *mf,ulonglong toplen,ulonglong timecode, int blockex) { + ulonglong v; + ulonglong duration = 0; + ulonglong dpos; + unsigned add_id = 0; + struct QueueEntry *qe,*qf = NULL; + unsigned char have_duration = 0, have_block = 0; + unsigned char gap = 0; + unsigned char lacing = 0; + unsigned char ref = 0; + unsigned char trackid; + unsigned tracknum = 0; + int c; + unsigned nframes = 0,i; + unsigned *sizes; + signed short block_timecode; + + if (blockex) + goto blockex; + + FOREACH(mf,toplen) + case 0xfb: // ReferenceBlock + readSInt(mf,(unsigned)len); + ref = 1; + break; +blockex: + cur = start = filepos(mf); + len = tmplen = toplen; + case 0xa1: // Block + have_block = 1; + + dpos = filepos(mf); + + v = readVLUInt(mf); + if (v>255) + errorjmp(mf,"Invalid track number in Block: %d",(int)v); + trackid = (unsigned char)v; + + for (tracknum=0;tracknumnTracks;++tracknum) + if (mf->Tracks[tracknum]->Number == trackid) { + if (mf->trackMask & (1<Tracks[tracknum]->TimecodeScale, + (timecode - mf->firstTimecode + block_timecode) * mf->Seg.TimecodeScale); + + c = readch(mf); + if (c==EOF) + errorjmp(mf,"Unexpected EOF while reading Block flags"); + + if (blockex) + ref = !(c & 0x80); + + gap = c & 0x1; + lacing = (c >> 1) & 3; + + if (lacing) { + c = readch(mf); + if (c == EOF) + errorjmp(mf,"Unexpected EOF while reading lacing data"); + nframes = c+1; + } else + nframes = 1; + sizes = alloca(nframes*sizeof(*sizes)); + + switch (lacing) { + case 0: // No lacing + sizes[0] = (unsigned)(len - filepos(mf) + dpos); + break; + case 1: // Xiph lacing + sizes[nframes-1] = 0; + for (i=0;i1) + sizes[nframes-1] = (unsigned)(len - filepos(mf) + dpos) - sizes[0] - sizes[nframes-1]; + break; + case 2: // Fixed lacing + sizes[0] = (unsigned)(len - filepos(mf) + dpos)/nframes; + for (i=1;iStart = timecode; + qe->End = timecode; + qe->Position = v; + qe->Length = sizes[i]; + qe->flags = FRAME_UNKNOWN_END | FRAME_KF; + if (i == nframes-1 && gap) + qe->flags |= FRAME_GAP; + if (i > 0) + qe->flags |= FRAME_UNKNOWN_START; + + QPut(&mf->Queues[tracknum],qe); + + v += sizes[i]; + } + + // we want to still load these bytes into cache + for (v = filepos(mf) & ~0x3fff; v < len + dpos; v += 0x4000) + mf->cache->read(mf->cache,v,NULL,0); // touch page + + skipbytes(mf,len - filepos(mf) + dpos); + + if (blockex) + goto out; + break; + case 0x9b: // BlockDuration + duration = readUInt(mf,(unsigned)len); + have_duration = 1; + break; + case 0x75a1: // BlockAdditions + if (nframes > 0) // have some frames + parseBlockAdditions(mf, len, timecode, tracknum); + else + skipbytes(mf, len); + break; + ENDFOR(mf); + +out: + if (!have_block) + errorjmp(mf,"Found a BlockGroup without Block"); + + if (nframes > 1) { + if (have_duration) { + duration = mul3(mf->Tracks[tracknum]->TimecodeScale, + duration * mf->Seg.TimecodeScale); + + for (qe = qf; nframes > 1; --nframes, qe = qe->next) { + qe->Start = v; + v += dpos; + duration -= dpos; + qe->End = v; +#if 0 + qe->flags &= ~(FRAME_UNKNOWN_START|FRAME_UNKNOWN_END); +#endif + } + qe->Start = v; + qe->End = v + duration; + qe->flags &= ~FRAME_UNKNOWN_END; + } else if (mf->Tracks[tracknum]->DefaultDuration) { + dpos = mf->Tracks[tracknum]->DefaultDuration; + v = qf->Start; + for (qe = qf; nframes > 0; --nframes, qe = qe->next) { + qe->Start = v; + v += dpos; + qe->End = v; + qe->flags &= ~(FRAME_UNKNOWN_START|FRAME_UNKNOWN_END); + } + } + } else if (nframes == 1) { + if (have_duration) { + qf->End = qf->Start + mul3(mf->Tracks[tracknum]->TimecodeScale, + duration * mf->Seg.TimecodeScale); + qf->flags &= ~FRAME_UNKNOWN_END; + } else if (mf->Tracks[tracknum]->DefaultDuration) { + qf->End = qf->Start + mf->Tracks[tracknum]->DefaultDuration; + qf->flags &= ~FRAME_UNKNOWN_END; + } + } + + if (ref) + while (qf) { + qf->flags &= ~FRAME_KF; + qf = qf->next; + } +} + +static void ClearQueue(MatroskaFile *mf,struct Queue *q) { + struct QueueEntry *qe,*qn; + + for (qe=q->head;qe;qe=qn) { + qn = qe->next; + qe->next = mf->QFreeList; + mf->QFreeList = qe; + } + + q->head = NULL; + q->tail = NULL; +} + +static void EmptyQueues(MatroskaFile *mf) { + unsigned i; + + for (i=0;inTracks;++i) + ClearQueue(mf,&mf->Queues[i]); +} + +static int readMoreBlocks(MatroskaFile *mf) { + ulonglong toplen, cstop; + longlong cp; + int cid, ret = 0; + jmp_buf jb; + volatile unsigned retries = 0; + + if (mf->readPosition >= mf->pSegmentTop) + return EOF; + + memcpy(&jb,&mf->jb,sizeof(jb)); + + if (setjmp(mf->jb)) { // something evil happened here, try to resync + // always advance read position no matter what so + // we don't get caught in an endless loop + mf->readPosition = filepos(mf); + + ret = EOF; + + if (++retries > 3) // don't try too hard + goto ex; + + for (;;) { + if (filepos(mf) >= mf->pSegmentTop) + goto ex; + + cp = mf->cache->scan(mf->cache,filepos(mf),0x1f43b675); // cluster + + if (cp < 0 || (ulonglong)cp >= mf->pSegmentTop) + goto ex; + + seek(mf,cp); + + cid = readID(mf); + if (cid == EOF) + goto ex; + if (cid == 0x1f43b675) { + toplen = readSize(mf); + if (toplen < MAXCLUSTER) { + // reset error flags + mf->flags &= ~MPF_ERROR; + ret = RBRESYNC; + break; + } + } + } + + mf->readPosition = cp; + } + + cstop = mf->cache->getcachesize(mf->cache)>>1; + if (cstop > MAX_READAHEAD) + cstop = MAX_READAHEAD; + cstop += mf->readPosition; + + seek(mf,mf->readPosition); + + while (filepos(mf) < mf->pSegmentTop) { + cid = readID(mf); + if (cid == EOF) { + ret = EOF; + break; + } + toplen = readSize(mf); + + if (cid == 0x1f43b675) { // Cluster + unsigned char have_timecode = 0; + + FOREACH(mf,toplen) + case 0xe7: // Timecode + mf->tcCluster = readUInt(mf,(unsigned)len); + have_timecode = 1; + break; + case 0xa7: // Position + readUInt(mf,(unsigned)len); + break; + case 0xab: // PrevSize + readUInt(mf,(unsigned)len); + break; + case 0x5854: { // SilentTracks + unsigned stmask = 0, i, trk; + FOREACH(mf, len) + case 0x58d7: // SilentTrackNumber + trk = (unsigned)readUInt(mf, (unsigned)len); + for (i = 0; i < mf->nTracks; ++i) + if (mf->Tracks[i]->Number == trk) { + stmask |= 1 << i; + break; + } + break; + ENDFOR(mf); + // TODO pass stmask to reading app + break; } + case 0xa0: // BlockGroup + if (!have_timecode) + errorjmp(mf,"Found BlockGroup before cluster TimeCode"); + parseBlockGroup(mf,len,mf->tcCluster, 0); + goto out; + case 0xa3: // BlockEx + if (!have_timecode) + errorjmp(mf,"Found BlockGroup before cluster TimeCode"); + parseBlockGroup(mf, len, mf->tcCluster, 1); + goto out; + ENDFOR(mf); +out:; + } else { + if (toplen > MAXFRAME) + errorjmp(mf,"Element in a cluster is too large around %llu, %X [%u]",filepos(mf),cid,(unsigned)toplen); + if (cid == 0xa0) // BlockGroup + parseBlockGroup(mf,toplen,mf->tcCluster, 0); + else if (cid == 0xa3) // BlockEx + parseBlockGroup(mf, toplen, mf->tcCluster, 1); + else + skipbytes(mf,toplen); + } + + if ((mf->readPosition = filepos(mf)) > cstop) + break; + } + + mf->readPosition = filepos(mf); + +ex: + memcpy(&mf->jb,&jb,sizeof(jb)); + + return ret; +} + +// this is almost the same as readMoreBlocks, except it ensures +// there are no partial frames queued, however empty queues are ok +static int fillQueues(MatroskaFile *mf,unsigned int mask) { + unsigned i,j; + int ret = 0; + + for (;;) { + j = 0; + + for (i=0;inTracks;++i) + if (mf->Queues[i].head && !(mask & (1<0) // have at least some frames + return ret; + + if ((ret = readMoreBlocks(mf)) < 0) { + j = 0; + for (i=0;inTracks;++i) + if (mf->Queues[i].head && !(mask & (1<pCluster; + ulonglong step = 10*1024*1024; + ulonglong size, tc, isize; + longlong next_cluster; + int id, have_tc, bad; + struct Cue *cue; + + if (pos >= mf->pSegmentTop) + return; + + if (pos + step * 10 > mf->pSegmentTop) + step = (mf->pSegmentTop - pos) / 10; + if (step == 0) + step = 1; + + memcpy(&jb,&mf->jb,sizeof(jb)); + + // remove all cues + mf->nCues = 0; + + bad = 0; + + while (pos < mf->pSegmentTop) { + if (!mf->cache->progress(mf->cache,pos,mf->pSegmentTop)) + break; + + if (++bad > 50) { + pos += step; + bad = 0; + continue; + } + + // find next cluster header + next_cluster = mf->cache->scan(mf->cache,pos,0x1f43b675); // cluster + if (next_cluster < 0 || (ulonglong)next_cluster >= mf->pSegmentTop) + break; + + pos = next_cluster + 4; // prevent endless loops + + if (setjmp(mf->jb)) // something evil happened while reindexing + continue; + + seek(mf,next_cluster); + + id = readID(mf); + if (id == EOF) + break; + if (id != 0x1f43b675) // shouldn't happen + continue; + + size = readVLUInt(mf); + if (size >= MAXCLUSTER || size < 1024) + continue; + + have_tc = 0; + size += filepos(mf); + + while (filepos(mf) < (ulonglong)next_cluster + 1024) { + id = readID(mf); + if (id == EOF) + break; + + isize = readVLUInt(mf); + + if (id == 0xe7) { // cluster timecode + tc = readUInt(mf,(unsigned)isize); + have_tc = 1; + break; + } + + skipbytes(mf,isize); + } + + if (!have_tc) + continue; + + seek(mf,size); + id = readID(mf); + + if (id == EOF) + break; + + if (id != 0x1f43b675) // cluster + continue; + + // good cluster, remember it + cue = AGET(mf,Cues); + cue->Time = tc; + cue->Position = next_cluster - mf->pSegment; + cue->Block = 0; + cue->Track = 0; + + // advance to the next point + pos = next_cluster + step; + if (pos < size) + pos = size; + + bad = 0; + } + + fixupCues(mf); + + if (mf->nCues == 0) { + cue = AGET(mf,Cues); + cue->Time = mf->firstTimecode; + cue->Position = mf->pCluster - mf->pSegment; + cue->Block = 0; + cue->Track = 0; + } + + mf->cache->progress(mf->cache,0,0); + + memcpy(&mf->jb,&jb,sizeof(jb)); +} + +static void fixupChapter(ulonglong adj, struct Chapter *ch) { + unsigned i; + + if (ch->Start != 0) + ch->Start -= adj; + if (ch->End != 0) + ch->End -= adj; + + for (i=0;inChildren;++i) + fixupChapter(adj,&ch->Children[i]); +} + +static longlong findLastTimecode(MatroskaFile *mf) { + ulonglong nd = 0; + unsigned n,vtrack; + + if (mf->nTracks == 0) + return -1; + + for (n=vtrack=0;nnTracks;++n) + if (mf->Tracks[n]->Type == TT_VIDEO) { + vtrack = n; + goto ok; + } + + return -1; +ok: + + EmptyQueues(mf); + + if (mf->nCues == 0) { + mf->readPosition = mf->pCluster + 13000000 > mf->pSegmentTop ? mf->pCluster : mf->pSegmentTop - 13000000; + mf->tcCluster = 0; + } else { + mf->readPosition = mf->Cues[mf->nCues - 1].Position + mf->pSegment; + mf->tcCluster = mf->Cues[mf->nCues - 1].Time / mf->Seg.TimecodeScale; + } + mf->trackMask = ~(1 << vtrack); + + do + while (mf->Queues[vtrack].head) + { + ulonglong tc = mf->Queues[vtrack].head->flags & FRAME_UNKNOWN_END ? + mf->Queues[vtrack].head->Start : mf->Queues[vtrack].head->End; + if (nd < tc) + nd = tc; + QFree(mf,QGet(&mf->Queues[vtrack])); + } + while (fillQueues(mf,0) != EOF); + + mf->trackMask = 0; + + EmptyQueues(mf); + + // there may have been an error, but at this point we will ignore it + if (mf->flags & MPF_ERROR) { + mf->flags &= ~MPF_ERROR; + if (nd == 0) + return -1; + } + + return nd; +} + +static void parseFile(MatroskaFile *mf) { + ulonglong len = filepos(mf), adjust; + unsigned i; + int id = readID(mf); + int m; + + if (id==EOF) + errorjmp(mf,"Unexpected EOF at start of file"); + + // files with multiple concatenated segments can have only + // one EBML prolog + if (len > 0 && id == 0x18538067) + goto segment; + + if (id!=0x1a45dfa3) + errorjmp(mf,"First element in file is not EBML"); + + parseEBML(mf,readSize(mf)); + + // next we need to find the first segment + for (;;) { + id = readID(mf); + if (id==EOF) + errorjmp(mf,"No segments found in the file"); +segment: + len = readVLUIntImp(mf,&m); + // see if it's unspecified + if (len == (MAXU64 >> (57-m*7))) + len = MAXU64; + if (id == 0x18538067) // Segment + break; + skipbytes(mf,len); + } + + // found it + mf->pSegment = filepos(mf); + if (len == MAXU64) { + mf->pSegmentTop = MAXU64; + if (mf->cache->getfilesize) { + longlong seglen = mf->cache->getfilesize(mf->cache); + if (seglen > 0) + mf->pSegmentTop = seglen; + } + } else + mf->pSegmentTop = mf->pSegment + len; + parseSegment(mf,len); + + // check if we got all data + if (!mf->seen.SegmentInfo) + errorjmp(mf,"Couldn't find SegmentInfo"); + if (!mf->seen.Cluster) + mf->pCluster = mf->pSegmentTop; + + adjust = mf->firstTimecode * mf->Seg.TimecodeScale; + + for (i=0;inChapters;++i) + fixupChapter(adjust, &mf->Chapters[i]); + + fixupCues(mf); + + // release extra memory + ARELEASE(mf,mf,Tracks); + + // initialize reader + mf->Queues = mf->cache->memalloc(mf->cache,mf->nTracks * sizeof(*mf->Queues)); + if (mf->Queues == NULL) + errorjmp(mf, "Ouf of memory"); + memset(mf->Queues, 0, mf->nTracks * sizeof(*mf->Queues)); + + // try to detect real duration + if (!(mf->flags & MKVF_AVOID_SEEKS)) { + longlong nd = findLastTimecode(mf); + if (nd > 0) + mf->Seg.Duration = nd; + } + + // move to first frame + mf->readPosition = mf->pCluster; + mf->tcCluster = mf->firstTimecode; +} + +static void DeleteChapter(MatroskaFile *mf,struct Chapter *ch) { + unsigned i,j; + + for (i=0;inDisplay;++i) + mf->cache->memfree(mf->cache,ch->Display[i].String); + mf->cache->memfree(mf->cache,ch->Display); + mf->cache->memfree(mf->cache,ch->Tracks); + + for (i=0;inProcess;++i) { + for (j=0;jProcess[i].nCommands;++j) + mf->cache->memfree(mf->cache,ch->Process[i].Commands[j].Command); + mf->cache->memfree(mf->cache,ch->Process[i].Commands); + mf->cache->memfree(mf->cache,ch->Process[i].CodecPrivate); + } + mf->cache->memfree(mf->cache,ch->Process); + + for (i=0;inChildren;++i) + DeleteChapter(mf,&ch->Children[i]); + mf->cache->memfree(mf->cache,ch->Children); +} + +/////////////////////////////////////////////////////////////////////////// +// public interface +MatroskaFile *mkv_OpenEx(InputStream *io, + ulonglong base, + unsigned flags, + char *err_msg,unsigned msgsize) +{ + MatroskaFile *mf = io->memalloc(io,sizeof(*mf)); + if (mf == NULL) { + strlcpy(err_msg,"Out of memory",msgsize); + return NULL; + } + + memset(mf,0,sizeof(*mf)); + + mf->cache = io; + mf->flags = flags; + io->progress(io,0,0); + + if (setjmp(mf->jb)==0) { + seek(mf,base); + parseFile(mf); + } else { // parser error + strlcpy(err_msg,mf->errmsg,msgsize); + mkv_Close(mf); + return NULL; + } + + return mf; +} + +MatroskaFile *mkv_Open(InputStream *io, + char *err_msg,unsigned msgsize) +{ + return mkv_OpenEx(io,0,0,err_msg,msgsize); +} + +void mkv_Close(MatroskaFile *mf) { + unsigned i,j; + + if (mf==NULL) + return; + + for (i=0;inTracks;++i) + mf->cache->memfree(mf->cache,mf->Tracks[i]); + mf->cache->memfree(mf->cache,mf->Tracks); + + for (i=0;inQBlocks;++i) + mf->cache->memfree(mf->cache,mf->QBlocks[i]); + mf->cache->memfree(mf->cache,mf->QBlocks); + + mf->cache->memfree(mf->cache,mf->Queues); + + mf->cache->memfree(mf->cache,mf->Seg.Title); + mf->cache->memfree(mf->cache,mf->Seg.MuxingApp); + mf->cache->memfree(mf->cache,mf->Seg.WritingApp); + mf->cache->memfree(mf->cache,mf->Seg.Filename); + mf->cache->memfree(mf->cache,mf->Seg.NextFilename); + mf->cache->memfree(mf->cache,mf->Seg.PrevFilename); + + mf->cache->memfree(mf->cache,mf->Cues); + + for (i=0;inAttachments;++i) { + mf->cache->memfree(mf->cache,mf->Attachments[i].Description); + mf->cache->memfree(mf->cache,mf->Attachments[i].Name); + mf->cache->memfree(mf->cache,mf->Attachments[i].MimeType); + } + mf->cache->memfree(mf->cache,mf->Attachments); + + for (i=0;inChapters;++i) + DeleteChapter(mf,&mf->Chapters[i]); + mf->cache->memfree(mf->cache,mf->Chapters); + + for (i=0;inTags;++i) { + for (j=0;jTags[i].nSimpleTags;++j) { + mf->cache->memfree(mf->cache,mf->Tags[i].SimpleTags[j].Name); + mf->cache->memfree(mf->cache,mf->Tags[i].SimpleTags[j].Value); + } + mf->cache->memfree(mf->cache,mf->Tags[i].Targets); + mf->cache->memfree(mf->cache,mf->Tags[i].SimpleTags); + } + mf->cache->memfree(mf->cache,mf->Tags); + + mf->cache->memfree(mf->cache,mf); +} + +const char *mkv_GetLastError(MatroskaFile *mf) { + return mf->errmsg[0] ? mf->errmsg : NULL; +} + +SegmentInfo *mkv_GetFileInfo(MatroskaFile *mf) { + return &mf->Seg; +} + +unsigned int mkv_GetNumTracks(MatroskaFile *mf) { + return mf->nTracks; +} + +TrackInfo *mkv_GetTrackInfo(MatroskaFile *mf,unsigned track) { + if (track>mf->nTracks) + return NULL; + + return mf->Tracks[track]; +} + +void mkv_GetAttachments(MatroskaFile *mf,Attachment **at,unsigned *count) { + *at = mf->Attachments; + *count = mf->nAttachments; +} + +void mkv_GetChapters(MatroskaFile *mf,Chapter **ch,unsigned *count) { + *ch = mf->Chapters; + *count = mf->nChapters; +} + +void mkv_GetTags(MatroskaFile *mf,Tag **tag,unsigned *count) { + *tag = mf->Tags; + *count = mf->nTags; +} + +ulonglong mkv_GetSegmentTop(MatroskaFile *mf) { + return mf->pSegmentTop; +} + +#define IS_DELTA(f) (!((f)->flags & FRAME_KF) || ((f)->flags & FRAME_UNKNOWN_START)) + +void mkv_Seek(MatroskaFile *mf,ulonglong timecode,unsigned flags) { + int i,j,m,ret; + unsigned n,z,mask; + ulonglong m_kftime[MAX_TRACKS]; + unsigned char m_seendf[MAX_TRACKS]; + + if (mf->flags & MKVF_AVOID_SEEKS) + return; + + if (timecode == 0) { + EmptyQueues(mf); + mf->readPosition = mf->pCluster; + mf->tcCluster = mf->firstTimecode; + mf->flags &= ~MPF_ERROR; + + return; + } + + if (mf->nCues==0) + reindex(mf); + + if (mf->nCues==0) + return; + + mf->flags &= ~MPF_ERROR; + + i = 0; + j = mf->nCues - 1; + + for (;;) { + if (i>j) { + j = j>=0 ? j : 0; + + if (setjmp(mf->jb)!=0) + return; + + mkv_SetTrackMask(mf,mf->trackMask); + + if (flags & MKVF_SEEK_TO_PREV_KEYFRAME) { + // we do this in two stages + // a. find the last keyframes before the require position + // b. seek to them + + // pass 1 + for (;;) { + for (n=0;nnTracks;++n) { + m_kftime[n] = MAXU64; + m_seendf[n] = 0; + } + + EmptyQueues(mf); + + mf->readPosition = mf->Cues[j].Position + mf->pSegment; + mf->tcCluster = mf->Cues[j].Time / mf->Seg.TimecodeScale; + + for (;;) { + if ((ret = fillQueues(mf,0)) < 0 || ret == RBRESYNC) + return; + + // drain queues until we get to the required timecode + for (n=0;nnTracks;++n) { + if (mf->Queues[n].head && mf->Queues[n].head->StartQueues[n].head)) + m_seendf[n] = 1; + else + m_kftime[n] = mf->Queues[n].head->Start; + } + + while (mf->Queues[n].head && mf->Queues[n].head->StartQueues[n].head)) + m_seendf[n] = 1; + else + m_kftime[n] = mf->Queues[n].head->Start; + QFree(mf,QGet(&mf->Queues[n])); + } + + if (mf->Queues[n].head && (mf->Tracks[n]->Type != TT_AUDIO || mf->Queues[n].head->Start<=timecode)) + if (!IS_DELTA(mf->Queues[n].head)) + m_kftime[n] = mf->Queues[n].head->Start; + } + + for (n=0;nnTracks;++n) + if (mf->Queues[n].head && mf->Queues[n].head->Start>=timecode) + goto found; + } +found: + + for (n=0;nnTracks;++n) + if (!(mf->trackMask & (1<0) + { + // we need to restart the search from prev cue + --j; + goto again; + } + + break; +again:; + } + } else + for (n=0;nnTracks;++n) + m_kftime[n] = timecode; + + // now seek to this timecode + EmptyQueues(mf); + + mf->readPosition = mf->Cues[j].Position + mf->pSegment; + mf->tcCluster = mf->Cues[j].Time / mf->Seg.TimecodeScale; + + for (mask=0;;) { + if ((ret = fillQueues(mf,mask)) < 0 || ret == RBRESYNC) + return; + + // drain queues until we get to the required timecode + for (n=0;nnTracks;++n) { + struct QueueEntry *qe; + for (qe = mf->Queues[n].head;qe && qe->StartQueues[n].head) + QFree(mf,QGet(&mf->Queues[n])); + } + + for (n=z=0;nnTracks;++n) + if (m_kftime[n]==MAXU64 || (mf->Queues[n].head && mf->Queues[n].head->Start>=m_kftime[n])) { + ++z; + mask |= 1<nTracks) + return; + } + } + + m = (i+j)>>1; + + if (timecode < mf->Cues[m].Time) + j = m-1; + else + i = m+1; + } +} + +void mkv_SkipToKeyframe(MatroskaFile *mf) { + unsigned n,wait; + ulonglong ht; + + if (setjmp(mf->jb)!=0) + return; + + // remove delta frames from queues + do { + wait = 0; + + if (fillQueues(mf,0)<0) + return; + + for (n=0;nnTracks;++n) + if (mf->Queues[n].head && !(mf->Queues[n].head->flags & FRAME_KF)) { + ++wait; + QFree(mf,QGet(&mf->Queues[n])); + } + } while (wait); + + // find highest queued time + for (n=0,ht=0;nnTracks;++n) + if (mf->Queues[n].head && htQueues[n].head->Start) + ht = mf->Queues[n].head->Start; + + // ensure the time difference is less than 100ms + do { + wait = 0; + + if (fillQueues(mf,0)<0) + return; + + for (n=0;nnTracks;++n) + while (mf->Queues[n].head && mf->Queues[n].head->next && + (mf->Queues[n].head->next->flags & FRAME_KF) && + ht - mf->Queues[n].head->Start > 100000000) + { + ++wait; + QFree(mf,QGet(&mf->Queues[n])); + } + + } while (wait); +} + +ulonglong mkv_GetLowestQTimecode(MatroskaFile *mf) { + unsigned n,seen; + ulonglong t; + + // find the lowest queued timecode + for (n=seen=0,t=0;nnTracks;++n) + if (mf->Queues[n].head && (!seen || t > mf->Queues[n].head->Start)) + t = mf->Queues[n].head->Start, seen=1; + + return seen ? t : (ulonglong)LL(-1); +} + +int mkv_TruncFloat(MKFLOAT f) { +#ifdef MATROSKA_INTEGER_ONLY + return (int)(f.v >> 32); +#else + return (int)f; +#endif +} + +#define FTRACK 0xffffffff + +void mkv_SetTrackMask(MatroskaFile *mf,unsigned int mask) { + unsigned int i; + + if (mf->flags & MPF_ERROR) + return; + + mf->trackMask = mask; + + for (i=0;inTracks;++i) + if (mask & (1<Queues[i]); +} + +int mkv_ReadFrame(MatroskaFile *mf, + unsigned int mask,unsigned int *track, + ulonglong *StartTime,ulonglong *EndTime, + ulonglong *FilePos,unsigned int *FrameSize, + unsigned int *FrameFlags) +{ + unsigned int i,j; + struct QueueEntry *qe; + + if (setjmp(mf->jb)!=0) + return -1; + + do { + // extract required frame, use block with the lowest timecode + for (j=FTRACK,i=0;inTracks;++i) + if (!(mask & (1<Queues[i].head) { + j = i; + ++i; + break; + } + + for (;inTracks;++i) + if (!(mask & (1<Queues[i].head && + mf->Queues[j].head->Start > mf->Queues[i].head->Start) + j = i; + + if (j != FTRACK) { + qe = QGet(&mf->Queues[j]); + + *track = j; + *StartTime = qe->Start; + *EndTime = qe->End; + *FilePos = qe->Position; + *FrameSize = qe->Length; + *FrameFlags = qe->flags; + + QFree(mf,qe); + + return 0; + } + + if (mf->flags & MPF_ERROR) + return -1; + + } while (fillQueues(mf,mask)>=0); + + return EOF; +} + +#ifdef MATROSKA_COMPRESSION_SUPPORT +/************************************************************************* + * Compressed streams support + ************************************************************************/ +struct CompressedStream { + MatroskaFile *mf; + z_stream zs; + + /* current compressed frame */ + ulonglong frame_pos; + unsigned frame_size; + char frame_buffer[2048]; + + /* decoded data buffer */ + char decoded_buffer[2048]; + unsigned decoded_ptr; + unsigned decoded_size; + + /* error handling */ + char errmsg[128]; +}; + +CompressedStream *cs_Create(/* in */ MatroskaFile *mf, + /* in */ unsigned tracknum, + /* out */ char *errormsg, + /* in */ unsigned msgsize) +{ + CompressedStream *cs; + TrackInfo *ti; + int code; + + ti = mkv_GetTrackInfo(mf, tracknum); + if (ti == NULL) { + strlcpy(errormsg, "No such track.", msgsize); + return NULL; + } + + if (!ti->CompEnabled) { + strlcpy(errormsg, "Track is not compressed.", msgsize); + return NULL; + } + + if (ti->CompMethod != COMP_ZLIB) { + strlcpy(errormsg, "Unsupported compression method.", msgsize); + return NULL; + } + + cs = mf->cache->memalloc(mf->cache,sizeof(*cs)); + if (cs == NULL) { + strlcpy(errormsg, "Ouf of memory.", msgsize); + return NULL; + } + + memset(&cs->zs,0,sizeof(cs->zs)); + code = inflateInit(&cs->zs); + if (code != Z_OK) { + strlcpy(errormsg, "ZLib error.", msgsize); + mf->cache->memfree(mf->cache,cs); + return NULL; + } + + cs->frame_size = 0; + cs->decoded_ptr = cs->decoded_size = 0; + cs->mf = mf; + + return cs; +} + +void cs_Destroy(/* in */ CompressedStream *cs) { + if (cs == NULL) + return; + + inflateEnd(&cs->zs); + cs->mf->cache->memfree(cs->mf->cache,cs); +} + +/* advance to the next frame in matroska stream, you need to pass values returned + * by mkv_ReadFrame */ +void cs_NextFrame(/* in */ CompressedStream *cs, + /* in */ ulonglong pos, + /* in */ unsigned size) +{ + cs->zs.avail_in = 0; + inflateReset(&cs->zs); + cs->frame_pos = pos; + cs->frame_size = size; + cs->decoded_ptr = cs->decoded_size = 0; +} + +/* read and decode more data from current frame, return number of bytes decoded, + * 0 on end of frame, or -1 on error */ +int cs_ReadData(CompressedStream *cs,char *buffer,unsigned bufsize) +{ + char *cp = buffer; + unsigned rd = 0; + unsigned todo; + int code; + + do { + /* try to copy data from decoded buffer */ + if (cs->decoded_ptr < cs->decoded_size) { + todo = cs->decoded_size - cs->decoded_ptr;; + if (todo > bufsize - rd) + todo = bufsize - rd; + + memcpy(cp, cs->decoded_buffer + cs->decoded_ptr, todo); + + rd += todo; + cp += todo; + cs->decoded_ptr += todo; + } else { + /* setup output buffer */ + cs->zs.next_out = cs->decoded_buffer; + cs->zs.avail_out = sizeof(cs->decoded_buffer); + + /* try to read more data */ + if (cs->zs.avail_in == 0 && cs->frame_size > 0) { + todo = cs->frame_size; + if (todo > sizeof(cs->frame_buffer)) + todo = sizeof(cs->frame_buffer); + + if (cs->mf->cache->read(cs->mf->cache, cs->frame_pos, cs->frame_buffer, todo) != (int)todo) { + strlcpy(cs->errmsg, "File read failed", sizeof(cs->errmsg)); + return -1; + } + + cs->zs.next_in = cs->frame_buffer; + cs->zs.avail_in = todo; + + cs->frame_pos += todo; + cs->frame_size -= todo; + } + + /* try to decode more data */ + code = inflate(&cs->zs,Z_NO_FLUSH); + if (code != Z_OK && code != Z_STREAM_END) { + strlcpy(cs->errmsg, "ZLib error.", sizeof(cs->errmsg)); + return -1; + } + + /* handle decoded data */ + if (cs->zs.avail_out == sizeof(cs->decoded_buffer)) /* EOF */ + break; + + cs->decoded_ptr = 0; + cs->decoded_size = sizeof(cs->decoded_buffer) - cs->zs.avail_out; + } + } while (rd < bufsize); + + return rd; +} + +/* return error message for the last error */ +const char *cs_GetLastError(CompressedStream *cs) +{ + if (!cs->errmsg[0]) + return NULL; + return cs->errmsg; +} +#endif diff --git a/core/MatroskaParser.h b/core/MatroskaParser.h new file mode 100644 index 000000000..29e3988c3 --- /dev/null +++ b/core/MatroskaParser.h @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2004-2006 Mike Matsnev. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice immediately at the beginning of the file, without modification, + * this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Absolutely no warranty of function or purpose is made by the author + * Mike Matsnev. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id: MatroskaParser.h,v 1.18.2.6 2006/01/13 01:44:45 mike Exp $ + * + */ + +#ifndef MATROSKA_PARSER_H +#define MATROSKA_PARSER_H + +/* Random notes: + * + * The parser does not process frame data in any way and does not read it into + * the queue. The app should read it via mkv_ReadData if it is interested. + * + * The code here is 64-bit clean and was tested on FreeBSD/sparc 64-bit big endian + * system + */ + +#ifdef MPDLLBUILD +#define X __declspec(dllexport) +#else +#ifdef MPDLL +#define X __declspec(dllimport) +#pragma comment(lib,"MatroskaParser") +#else +#define X +#endif +#endif + +#define MATROSKA_COMPRESSION_SUPPORT +#define MATROSKA_INTEGER_ONLY + +#ifdef __cplusplus +extern "C" { +#endif + +/* 64-bit integers */ +#ifdef _WIN32_WCE +typedef signed __int64 longlong; +typedef unsigned __int64 ulonglong; +#else +typedef signed long long longlong; +typedef unsigned long long ulonglong; +#endif + +/* MKFLOATing point */ +#ifdef MATROSKA_INTEGER_ONLY +typedef struct { + longlong v; +} MKFLOAT; +#else +typedef double MKFLOAT; +#endif + +/* generic I/O */ +struct InputStream { + /* read bytes from stream */ + int (*read)(struct InputStream *cc,ulonglong pos,void *buffer,int count); + /* scan for a four byte signature, bytes must be nonzero */ + longlong (*scan)(struct InputStream *cc,ulonglong start,unsigned signature); + /* get cache size, this is used to cap readahead */ + unsigned (*getcachesize)(struct InputStream *cc); + /* fetch last error message */ + const char *(*geterror)(struct InputStream *cc); + /* memory allocation */ + void *(*memalloc)(struct InputStream *cc,size_t size); + void *(*memrealloc)(struct InputStream *cc,void *mem,size_t newsize); + void (*memfree)(struct InputStream *cc,void *mem); + /* zero return causes parser to abort open */ + int (*progress)(struct InputStream *cc,ulonglong cur,ulonglong max); + /* get file size, optional, can be NULL or return -1 if filesize is unknown */ + longlong (*getfilesize)(struct InputStream *cc); +}; + +typedef struct InputStream InputStream; + +/* matroska file */ +struct MatroskaFile; /* opaque */ + +typedef struct MatroskaFile MatroskaFile; + +#define COMP_ZLIB 0 +#define COMP_BZIP 1 +#define COMP_LZO1X 2 +#define COMP_PREPEND 3 + +#define TT_VIDEO 1 +#define TT_AUDIO 2 +#define TT_SUB 17 + +struct TrackInfo { + unsigned char Number; + unsigned char Type; + unsigned char TrackOverlay; + ulonglong UID; + ulonglong MinCache; + ulonglong MaxCache; + ulonglong DefaultDuration; + MKFLOAT TimecodeScale; + void *CodecPrivate; + unsigned CodecPrivateSize; + unsigned CompMethod; + void *CompMethodPrivate; + unsigned CompMethodPrivateSize; + unsigned MaxBlockAdditionID; + struct { + unsigned int Enabled:1; + unsigned int Default:1; + unsigned int Lacing:1; + unsigned int DecodeAll:1; + unsigned int CompEnabled:1; + }; + + union { + struct { + unsigned char StereoMode; + unsigned char DisplayUnit; + unsigned char AspectRatioType; + unsigned int PixelWidth; + unsigned int PixelHeight; + unsigned int DisplayWidth; + unsigned int DisplayHeight; + unsigned int CropL, CropT, CropR, CropB; + unsigned int ColourSpace; + MKFLOAT GammaValue; + struct { + unsigned int Interlaced:1; + }; + } Video; + struct { + MKFLOAT SamplingFreq; + MKFLOAT OutputSamplingFreq; + unsigned char Channels; + unsigned char BitDepth; + } Audio; + } AV; + + /* various strings */ + char *Name; + char Language[4]; + char *CodecID; +}; + +typedef struct TrackInfo TrackInfo; + +struct SegmentInfo { + char UID[16]; + char PrevUID[16]; + char NextUID[16]; + char *Filename; + char *PrevFilename; + char *NextFilename; + char *Title; + char *MuxingApp; + char *WritingApp; + ulonglong TimecodeScale; + ulonglong Duration; + ulonglong DateUTC; +}; + +typedef struct SegmentInfo SegmentInfo; + +struct Attachment { + ulonglong Position; + ulonglong Length; + ulonglong UID; + char *Name; + char *Description; + char *MimeType; +}; + +typedef struct Attachment Attachment; + +struct ChapterDisplay { + char *String; + char Language[4]; + char Country[4]; +}; + +struct ChapterCommand { + unsigned Time; + unsigned CommandLength; + void *Command; +}; + +struct ChapterProcess { + unsigned CodecID; + unsigned CodecPrivateLength; + void *CodecPrivate; + unsigned nCommands,nCommandsSize; + struct ChapterCommand *Commands; +}; + +struct Chapter { + ulonglong UID; + ulonglong Start; + ulonglong End; + + unsigned nTracks,nTracksSize; + ulonglong *Tracks; + unsigned nDisplay,nDisplaySize; + struct ChapterDisplay *Display; + unsigned nChildren,nChildrenSize; + struct Chapter *Children; + unsigned nProcess,nProcessSize; + struct ChapterProcess *Process; + + char SegmentUID[16]; + + struct { + unsigned int Hidden:1; + unsigned int Enabled:1; + + // Editions + unsigned int Default:1; + unsigned int Ordered:1; + }; +}; + +typedef struct Chapter Chapter; + +#define TARGET_TRACK 0 +#define TARGET_CHAPTER 1 +#define TARGET_ATTACHMENT 2 +#define TARGET_EDITION 3 +struct Target { + ulonglong UID; + unsigned Type; +}; + +struct SimpleTag { + char *Name; + char *Value; + char Language[4]; + unsigned Default:1; +}; + +struct Tag { + unsigned nTargets,nTargetsSize; + struct Target *Targets; + + unsigned nSimpleTags,nSimpleTagsSize; + struct SimpleTag *SimpleTags; +}; + +typedef struct Tag Tag; + +/* Open a matroska file + * io pointer is recorded inside MatroskaFile + */ +X MatroskaFile *mkv_Open(/* in */ InputStream *io, + /* out */ char *err_msg, + /* in */ unsigned msgsize); + +#define MKVF_AVOID_SEEKS 1 /* use sequential reading only */ + +X MatroskaFile *mkv_OpenEx(/* in */ InputStream *io, + /* in */ ulonglong base, + /* in */ unsigned flags, + /* out */ char *err_msg, + /* in */ unsigned msgsize); + +/* Close and deallocate mf + * NULL pointer is ok and is simply ignored + */ +X void mkv_Close(/* in */ MatroskaFile *mf); + +/* Fetch the error message of the last failed operation */ +X const char *mkv_GetLastError(/* in */ MatroskaFile *mf); + +/* Get file information */ +X SegmentInfo *mkv_GetFileInfo(/* in */ MatroskaFile *mf); + +/* Get track information */ +X unsigned int mkv_GetNumTracks(/* in */ MatroskaFile *mf); +X TrackInfo *mkv_GetTrackInfo(/* in */ MatroskaFile *mf,/* in */ unsigned track); + +/* chapters, tags and attachments */ +X void mkv_GetAttachments(/* in */ MatroskaFile *mf, + /* out */ Attachment **at, + /* out */ unsigned *count); +X void mkv_GetChapters(/* in */ MatroskaFile *mf, + /* out */ Chapter **ch, + /* out */ unsigned *count); +X void mkv_GetTags(/* in */ MatroskaFile *mf, + /* out */ Tag **tag, + /* out */ unsigned *count); + +X ulonglong mkv_GetSegmentTop(MatroskaFile *mf); + +/* Seek to specified timecode, + * if timecode is past end of file, + * all tracks are set to return EOF + * on next read + */ +#define MKVF_SEEK_TO_PREV_KEYFRAME 1 + +X void mkv_Seek(/* in */ MatroskaFile *mf, + /* in */ ulonglong timecode /* in ns */, + /* in */ unsigned flags); + +X void mkv_SkipToKeyframe(MatroskaFile *mf); + +X ulonglong mkv_GetLowestQTimecode(MatroskaFile *mf); + +X int mkv_TruncFloat(MKFLOAT f); + +/************************************************************************* + * reading data, pull model + */ + +/* frame flags */ +#define FRAME_UNKNOWN_START 0x00000001 +#define FRAME_UNKNOWN_END 0x00000002 +#define FRAME_KF 0x00000004 +#define FRAME_GAP 0x00800000 +#define FRAME_STREAM_MASK 0xff000000 +#define FRAME_STREAM_SHIFT 24 + +/* This sets the masking flags for the parser, + * masked tracks [with 1s in their bit positions] + * will be ignored when reading file data. + * This call discards all parsed and queued frames + */ +X void mkv_SetTrackMask(/* in */ MatroskaFile *mf,/* in */ unsigned int mask); + +/* Read one frame from the queue. + * mask specifies what tracks to ignore. + * Returns -1 if there are no more frames in the specified + * set of tracks, 0 on success + */ +X int mkv_ReadFrame(/* in */ MatroskaFile *mf, + /* in */ unsigned int mask, + /* out */ unsigned int *track, + /* out */ ulonglong *StartTime /* in ns */, + /* out */ ulonglong *EndTime /* in ns */, + /* out */ ulonglong *FilePos /* in bytes from start of file */, + /* out */ unsigned int *FrameSize /* in bytes */, + /* out */ unsigned int *FrameFlags); + +#ifdef MATROSKA_COMPRESSION_SUPPORT +/* Compressed streams support */ +struct CompressedStream; + +typedef struct CompressedStream CompressedStream; + +X CompressedStream *cs_Create(/* in */ MatroskaFile *mf, + /* in */ unsigned tracknum, + /* out */ char *errormsg, + /* in */ unsigned msgsize); +X void cs_Destroy(/* in */ CompressedStream *cs); + +/* advance to the next frame in matroska stream, you need to pass values returned + * by mkv_ReadFrame */ +X void cs_NextFrame(/* in */ CompressedStream *cs, + /* in */ ulonglong pos, + /* in */ unsigned size); + +/* read and decode more data from current frame, return number of bytes decoded, + * 0 on end of frame, or -1 on error */ +X int cs_ReadData(CompressedStream *cs,char *buffer,unsigned bufsize); + +/* return error message for the last error */ +X const char *cs_GetLastError(CompressedStream *cs); +#endif + +#ifdef __cplusplus +} +#endif + +#undef X + +#endif diff --git a/core/mkv_wrap.cpp b/core/mkv_wrap.cpp new file mode 100644 index 000000000..214289cd7 --- /dev/null +++ b/core/mkv_wrap.cpp @@ -0,0 +1,296 @@ +// Copyright (c) 2004-2006, Rodrigo Braz Monteiro, Mike Matsnev +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +/////////// +// Headers +#include +#include "mkv_wrap.h" + + +/////////// +// Defines +#define CACHESIZE 65536 + + +/////////////// +// Constructor +MatroskaWrapper::MatroskaWrapper() { + file = NULL; +} + + +////////////// +// Destructor +MatroskaWrapper::~MatroskaWrapper() { + Close(); +} + + +///////////// +// Open file +void MatroskaWrapper::Open(wxString filename) { + // Make sure it's closed first + Close(); + + // Open + char err[2048]; + input = new MkvStdIO(filename); + if (input->fp) { + file = mkv_Open(input,err,sizeof(err)); + + // Failed parsing + if (!file) { + delete input; + throw wxString(_T("MatroskaParser error: ") + wxString(err,wxConvUTF8)).c_str(); + } + + Parse(); + } + + // Failed opening + else { + delete input; + throw _T("Unable to open Matroska file for parsing."); + } +} + + +////////////// +// Close file +void MatroskaWrapper::Close() { + if (file) { + mkv_Close(file); + file = NULL; + fclose(input->fp); + delete input; + } +} + + +//////////////////// +// Return keyframes +wxArrayInt MatroskaWrapper::GetKeyFrames() { + return keyFrames; +} + + +/////////////////////// +// Comparison operator +bool operator < (MkvFrame &t1, MkvFrame &t2) { + return t1.time < t2.time; +} + + +////////////////// +// Actually parse +void MatroskaWrapper::Parse() { + // Clear keyframes and timecodes + keyFrames.Clear(); + timecodes.clear(); + std::list frames; + + // Get info + int tracks = mkv_GetNumTracks(file); + TrackInfo *trackInfo; + SegmentInfo *segInfo = mkv_GetFileInfo(file); + + // Parse tracks + for (int track=0;trackType == 1) { + // Variables + ulonglong startTime, endTime, filePos; + unsigned int rt, frameSize, frameFlags; + CompressedStream *cs = NULL; + + // Timecode scale + __int64 timecodeScale = mkv_TruncFloat(trackInfo->TimecodeScale) * segInfo->TimecodeScale; + + // Mask other tracks away + mkv_SetTrackMask(file, ~(1 << track)); + + // Read frames + int frameN = 0; + while (mkv_ReadFrame(file,0,&rt,&startTime,&endTime,&filePos,&frameSize,&frameFlags) == 0) { + frames.push_back(MkvFrame((frameFlags & FRAME_KF) != 0,double(startTime) / timecodeScale)); + frameN++; + } + + break; + } + } + + // Process timecodes and keyframes + frames.sort(); + MkvFrame curFrame(false,0); + int i = 0; + for (std::list::iterator cur=frames.begin();cur!=frames.end();cur++) { + curFrame = *cur; + if (curFrame.isKey) keyFrames.Add(i); + timecodes.push_back(curFrame.time); + i++; + } +} + + +/////////////////////////// +// Set target to timecodes +void MatroskaWrapper::SetToTimecodes(FrameRate &target) { + // Enough frames? + int frames = timecodes.size(); + if (frames <= 1) return; + + // Sort + //std::sort::iterator>(timecodes.begin(),timecodes.end()); + + // Check if it's CFR + bool isCFR = true; + double estimateCFR = timecodes.back() / timecodes.size()-1; + double curTime = 0; + for (int i=0;i 1)) { + isCFR = false; + break; + } + curTime += estimateCFR; + } + + // Constant framerate + if (isCFR) { + if (abs(estimateCFR - 23.976) < 0.01) estimateCFR = 23.976; + if (abs(estimateCFR - 29.97) < 0.01) estimateCFR = 29.97; + target.SetCFR(estimateCFR); + } + + // Variable framerate + else { + std::vector times; + for (int i=0;ifp, pos, SEEK_SET)) { + st->error = errno; + return -1; + } + rd = fread(buffer, 1, count, st->fp); + if (rd == 0) { + if (feof(st->fp)) + return 0; + st->error = errno; + return -1; + } + return rd; +} + +/* scan for a signature sig(big-endian) starting at file position pos + * return position of the first byte of signature or -1 if error/not found + */ +longlong StdIoScan(InputStream *_st, ulonglong start, unsigned signature) { + MkvStdIO *st = (MkvStdIO *) _st; + int c; + unsigned cmp = 0; + FILE *fp = st->fp; + + if (fseek(fp, start, SEEK_SET)) + return -1; + + while ((c = getc(fp)) != EOF) { + cmp = ((cmp << 8) | c) & 0xffffffff; + if (cmp == signature) + return ftell(fp) - 4; + } + + return -1; +} + +/* return cache size, this is used to limit readahead */ +unsigned StdIoGetCacheSize(InputStream *_st) { + return CACHESIZE; +} + +/* return last error message */ +const char *StdIoGetLastError(InputStream *_st) { + MkvStdIO *st = (MkvStdIO *) _st; + return strerror(st->error); +} + +/* memory allocation, this is done via stdlib */ +void *StdIoMalloc(InputStream *_st, size_t size) { + return malloc(size); +} + +void *StdIoRealloc(InputStream *_st, void *mem, size_t size) { + return realloc(mem,size); +} + +void StdIoFree(InputStream *_st, void *mem) { + free(mem); +} + +int StdIoProgress(InputStream *_st, ulonglong cur, ulonglong max) { + return 1; +} + +MkvStdIO::MkvStdIO(wxString filename) { + read = StdIoRead; + scan = StdIoScan; + getcachesize = StdIoGetCacheSize; + geterror = StdIoGetLastError; + memalloc = StdIoMalloc; + memrealloc = StdIoRealloc; + memfree = StdIoFree; + progress = StdIoProgress; + fp = fopen(filename.mb_str(),"rb"); + if (fp) { + setvbuf(fp, NULL, _IOFBF, CACHESIZE); + } +} diff --git a/core/mkv_wrap.h b/core/mkv_wrap.h new file mode 100644 index 000000000..1033ab0a4 --- /dev/null +++ b/core/mkv_wrap.h @@ -0,0 +1,94 @@ +// Copyright (c) 2006, Rodrigo Braz Monteiro +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +#pragma once + + +/////////// +// Headers +#include +#include +#include +#include "MatroskaParser.h" +#include "vfr.h" + + +///////////////////////////// +// STD IO for MatroskaParser +class MkvStdIO : public InputStream { +public: + MkvStdIO(wxString filename); + FILE *fp; + int error; +}; + + +////////////////// +// MkvFrame class +class MkvFrame { +public: + double time; + bool isKey; + + MkvFrame(bool keyframe,double timecode) { + isKey = keyframe; + time = timecode; + } +}; + +bool operator < (MkvFrame &t1, MkvFrame &t2); + + +////////////////////////// +// Matroska wrapper class +class MatroskaWrapper { +private: + MatroskaFile *file; + MkvStdIO *input; + wxArrayInt keyFrames; + std::vector timecodes; + + void Parse(); + +public: + MatroskaWrapper(); + ~MatroskaWrapper(); + + void Open(wxString filename); + void Close(); + void SetToTimecodes(FrameRate &target); + wxArrayInt GetKeyFrames(); +}; diff --git a/core/vfr.cpp b/core/vfr.cpp index 1f78d25cf..ec98d6ebd 100644 --- a/core/vfr.cpp +++ b/core/vfr.cpp @@ -314,6 +314,24 @@ void FrameRate::SetCFR(double fps,bool ifunset) { AverageFrameRate = fps; } + +/////////////// +// Sets to VFR +void FrameRate::SetVFR(std::vector newTimes) { + // Prepare + Unload(); + loaded = true; + vfrFile = _T(""); + FrameRateType = VFR; + + // Set new VFR + AverageFrameRate = newTimes.back() / (newTimes.size()-1); + Frame = newTimes; + last_time = newTimes.back(); + last_frame = newTimes.size()-1; +} + + ///////////////////////////// // Get correct frame at time // returns the adjusted time for end frames when start=false diff --git a/core/vfr.h b/core/vfr.h index 2b5e2ed8c..22e0df29f 100644 --- a/core/vfr.h +++ b/core/vfr.h @@ -70,16 +70,18 @@ private: void Clear(); void CalcAverage(); + public: FrameRate(); ~FrameRate(); - wxString vfrFile; bool loaded; ASS_FrameRateType FrameRateType; void SetCFR(double fps,bool ifunset=false); + void SetVFR(std::vector times); + void Load(wxString file); void Unload(); int GetFrameAtTime(int ms); diff --git a/core/video_display.cpp b/core/video_display.cpp index 750584781..4f0a7044f 100644 --- a/core/video_display.cpp +++ b/core/video_display.cpp @@ -43,6 +43,7 @@ #include "ass_dialogue.h" #include "subs_grid.h" #include "vfw_wrap.h" +#include "mkv_wrap.h" #include "options.h" #include "subs_edit_box.h" #include "audio_display.h" @@ -159,10 +160,25 @@ void VideoDisplay::SetVideo(const wxString &filename) { provider = new VideoProvider(filename,GetTempWorkFile(),zoomValue,usedDirectshow,true); // Set keyframes - if (filename.Right(4).Lower() == _T(".avi")) - KeyFrames = VFWWrapper::GetKeyFrames(filename); - else - KeyFrames.Clear(); + wxString ext = filename.Right(4).Lower(); + if (ext == _T(".avi")) KeyFrames = VFWWrapper::GetKeyFrames(filename); + else if (ext == _T(".mkv")) { + // Parse mkv + MatroskaWrapper mkvwrap; + mkvwrap.Open(filename); + + // Get keyframes + KeyFrames = mkvwrap.GetKeyFrames(); + + // Ask to override timecodes + int override = wxYES; + if (VFR_Output.FrameRateType == VFR) override = wxMessageBox(_T("You already have timecodes loaded. Replace them with the timecodes from the Matroska file?"),_T("Replace timecodes?"),wxYES_NO | wxICON_QUESTION); + if (override == wxYES) mkvwrap.SetToTimecodes(VFR_Output); + + // Close mkv + mkvwrap.Close(); + } + else KeyFrames.Clear(); UpdateSize();