From 106568ff4ad4f29afa431b9ab8e8a63d650aee2f Mon Sep 17 00:00:00 2001 From: Hendrik Leppkes Date: Wed, 25 Apr 2012 16:23:37 +0200 Subject: [PATCH] Add Matroska demuxer based on Haalis MatroskaParser --- libavformat/Makefile | 4 + libavformat/MatroskaParser.c | 3385 +++++++++++++++++++++++++++++++ libavformat/MatroskaParser.h | 409 ++++ libavformat/allformats.c | 1 + libavformat/matroskadec_haali.c | 683 +++++++ 5 files changed, 4482 insertions(+) create mode 100644 libavformat/MatroskaParser.c create mode 100644 libavformat/MatroskaParser.h create mode 100644 libavformat/matroskadec_haali.c diff --git a/libavformat/Makefile b/libavformat/Makefile index 44aa485029..a678987a71 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -350,6 +350,10 @@ OBJS-$(CONFIG_MATROSKA_MUXER) += matroskaenc.o matroska.o \ flacenc_header.o avlanguage.o \ vorbiscomment.o wv.o dovi_isom.o OBJS-$(CONFIG_MCA_DEMUXER) += mca.o +OBJS-$(CONFIG_MATROSKA_HAALI_DEMUXER) += matroskadec_haali.o matroska.o \ + MatroskaParser.o \ + isom.o rmsipr.o flac_picture.o \ + oggparsevorbis.o vorbiscomment.o OBJS-$(CONFIG_MCC_DEMUXER) += mccdec.o subtitles.o OBJS-$(CONFIG_MD5_MUXER) += hashenc.o OBJS-$(CONFIG_MGSTS_DEMUXER) += mgsts.o diff --git a/libavformat/MatroskaParser.c b/libavformat/MatroskaParser.c new file mode 100644 index 0000000000..57eb0428a9 --- /dev/null +++ b/libavformat/MatroskaParser.c @@ -0,0 +1,3385 @@ +/* + * Copyright (c) 2004-2008 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.75 2010/08/14 08:38:41 mike Exp $ + * + */ + +#include +#include +#include +#include +#include + +#if defined(_WIN32) && defined(_MSC_VER) +// MS names some functions differently +#define alloca _alloca +#define inline __inline + +#include +#elif defined(__MINGW32__) +#define alloca __builtin_alloca +#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 WEBM_DOCTYPE "webm" + +#define MAX_STRING_LEN 1023 +#define QSEGSIZE 512 +#define MAX_TRACKS 64 +#define MAX_READAHEAD (256*1024) + +#define MAXCLUSTER (64*1048576) +#define MAXFRAME (4*1048576) + +#if defined(_WIN32) && defined(_MSC_VER) +#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; +} + +static void mystrlcpy(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; + qe->Data = NULL; + + for (i=0;iQFreeList = qe; + } + + qe = mf->QFreeList; + mf->QFreeList = qe->next; + + return qe; +} + +static inline void QFree(MatroskaFile *mf,struct QueueEntry *qe) { + mf->cache->memfree(mf->cache, qe->Data); + qe->Data = NULL; + 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 0; // XXX should errorjmp()? + + 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 readSizeUnspec(MatroskaFile *mf) { + int m; + ulonglong v = readVLUIntImp(mf,&m); + + // see if it's unspecified + if (v == (MAXU64 >> (57-m*7))) + return MAXU64; + + return v; +} + +static ulonglong readSize(MatroskaFile *mf) { + ulonglong v = readSizeUnspec(mf); + + // see if it's unspecified + if (v == MAXU64) + 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 FOREACH2(f,tl,clid) \ + { \ + ulonglong tmplen = (tl); \ + { \ + ulonglong start = filepos(f); \ + ulonglong cur,len; \ + int id; \ + for (;;) { \ + cur = filepos(mf); \ + if (tmplen != MAXU64 && cur == start + tmplen) \ + break; \ + id = readID(f); \ + if (id==EOF) \ + errorjmp(mf,"Unexpected EOF while reading EBML container"); \ + len = id == clid ? readSizeUnspec(mf) : readSize(mf); \ + switch (id) { + +#define FOREACH(f,tl) FOREACH2(f,tl,EOF) + +#define RESTART() (tmplen=len),(start=cur) + +#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) != 0 && strcmp(buf,WEBM_DOCTYPE) != 0) + 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); + mf->Seg.DateUTCValid = 1; + 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) { + int seenTimecode = 0, seenBlock = 0; + longlong tc; + ulonglong clstart = filepos(mf); + + mf->seen.Cluster = 1; + mf->firstTimecode = 0; + + FOREACH2(mf,toplen,0x1f43b675) + case 0xe7: // Timecode + tc = readUInt(mf,(unsigned)len); + if (!seenTimecode) { + seenTimecode = 1; + mf->firstTimecode += tc; + } + + if (seenBlock) { +out: + if (toplen != MAXU64) + skipbytes(mf,clstart + toplen - filepos(mf)); + else if (len != MAXU64) + skipbytes(mf,cur + len - filepos(mf)); + return; + } + break; + case 0xa3: // BlockEx + readVLUInt(mf); // track number + tc = readSInt(mf, 2); + if (!seenBlock) { + seenBlock = 1; + mf->firstTimecode += tc; + } + + if (seenTimecode) + goto out; + break; + case 0xa0: // BlockGroup + FOREACH(mf,len) + case 0xa1: // Block + readVLUInt(mf); // track number + tc = readSInt(mf,2); + if (!seenBlock) { + seenBlock = 1; + mf->firstTimecode += tc; + } + + if (seenTimecode) + goto out; + ENDFOR(mf); + break; + case 0x1f43b675: + return; + 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); + ti->AV.Video.DisplayUnit = (unsigned char)v; + break; + case 0x54b3: // AspectRatioType + v = readUInt(mf,(unsigned)len); + 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.Forced = 0; + 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 0x55aa: // Forced + t.Forced = 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 + STRGETA(mf,t.CodecID,len); + break; + case 0x63a2: // CodecPrivate + if (cp) + errorjmp(mf,"Duplicate CodecPrivate"); + if (len>2097152) // 2MB + 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; + } + + // handle compressed CodecPrivate + // header removal compression + if (t.CompEnabled && (CompScope & 2)) { + if (t.CompMethod == COMP_PREPEND && cslen > 0) { + cp = mf->cache->memrealloc(mf->cache, cp, cplen + cslen); + memmove(cp+cslen, cp, cplen); + memcpy(cp, cs, cslen); + cplen += cslen; + } +#ifdef MATROSKA_COMPRESSION_SUPPORT + // zlib compression + else if (t.CompMethod == COMP_ZLIB && cplen > 0) { + z_stream zs; + Bytef tmp[64], *ncp; + int code; + uLong ncplen; + + memset(&zs,0,sizeof(zs)); + if (inflateInit(&zs) != Z_OK) + errorjmp(mf, "inflateInit failed"); + + zs.next_in = (Bytef *)cp; + zs.avail_in = (uInt)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 = (Bytef *)cp; + zs.avail_in = (uInt)cplen; + zs.next_out = ncp; + zs.avail_out = ncplen; + + if (inflate(&zs, Z_FINISH) != Z_STREAM_END) + errorjmp(mf, "inflate failed"); + + inflateEnd(&zs); + + cp = (char *)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 = (unsigned)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; + jmp_buf jb; + + memcpy(&jb,&mf->jb,sizeof(jb)); + + if (setjmp(mf->jb)) + mf->flags &= ~MPF_ERROR; + else { + // we want to read data until we find a seekhead or a trackinfo + FOREACH2(mf,toplen,0x1f43b675) + 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); + } + } while (mf->pSeekHead && nSeekHeads < 10); + 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) { + if (len != MAXU64) + 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(); + } + + memcpy(&mf->jb,&jb,sizeof(jb)); + + 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; + void *add_data; + + FOREACH(mf, toplen) + case 0xa6: // BlockMore + have_add = 0; + add_data = NULL; + FOREACH(mf, len) + case 0xee: // BlockAddId + add_id = readUInt(mf, (unsigned)len); + break; + case 0xa5: // BlockAddition + add_pos = filepos(mf); + add_len = len; + add_data = mf->cache->memrealloc(mf->cache,add_data,add_len); + readbytes(mf, add_data, 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->Data = (char *)add_data; + qe->flags = FRAME_UNKNOWN_START | FRAME_UNKNOWN_END | + (((unsigned)add_id << FRAME_STREAM_SHIFT) & FRAME_STREAM_MASK); + + QPut(&mf->Queues[track],qe); + } else if(add_data) { + mf->cache->memfree(mf->cache,add_data); + } + break; + ENDFOR(mf); +} + +static void parseBlockGroup(MatroskaFile *mf,ulonglong toplen,ulonglong timecode, int blockex) { + ulonglong v; + ulonglong duration = 0; + ulonglong dpos; + 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 & (ULL(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 = (unsigned char)!(c & 0x80); + + gap = (unsigned char)(c & 0x1); + lacing = (unsigned char)((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->Data = (char *)mf->cache->memalloc(mf->cache,qe->Length); + readbytes(mf, qe->Data, qe->Length); + 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) { + ulonglong defd = mf->Tracks[tracknum]->DefaultDuration; + v = qf->Start; + + 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 += defd; + duration -= defd; + 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) { + for (qe = qf; nframes > 0; --nframes, qe = qe->next) { + qe->Start = v; + v += defd; + 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; + mf->cache->memfree(mf->cache, qe->Data); + qe->Data = NULL; + 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 = readSizeUnspec(mf); + if (toplen < MAXCLUSTER || toplen == MAXU64) { + // 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 = cid == 0x1f43b675 ? readSizeUnspec(mf) : readSize(mf); + + if (cid == 0x1f43b675) { // Cluster + unsigned char have_timecode = 0; + + FOREACH2(mf,toplen,0x1f43b675) + case 0x1f43b675: + RESTART(); + break; + 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 + ulonglong stmask = 0; + unsigned 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 |= ULL(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,ulonglong mask) { + unsigned i,j; + int ret = 0; + + for (;;) { + j = 0; + + for (i=0;inTracks;++i) + if (mf->Queues[i].head && !(mask & (ULL(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 & (ULL(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 = readSizeUnspec(mf); + if (size == MAXU64) + break; + + 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 = ~(ULL(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); + + 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 = readSizeUnspec(mf); + if (id == 0x18538067) // Segment + break; + if (len == MAXU64) + errorjmp(mf,"No segments found in the file"); + 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) { + mystrlcpy(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 + mystrlcpy(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]->Data); + 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_GetCues(MatroskaFile *mf,Cue **cue,unsigned *count) { + *cue = mf->Cues; + *count = mf->nCues; +} + +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; + ulonglong mask,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 | MKVF_SEEK_TO_PREV_KEYFRAME_STRICT)) { + // 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; + + 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])); + } + + // We've drained the queue, so the frame at head is the next one past the requered point. + // In strict mode we are done, but when seeking is not strict we use the head frame + // if it's not an audio track (we accept preroll within a frame for audio), and the head frame + // is a keyframe + if (!(flags & MKVF_SEEK_TO_PREV_KEYFRAME_STRICT)) + 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 & (ULL(1)<0 && (mf->Tracks[n]->Type == TT_VIDEO || mf->Tracks[n]->Type == TT_AUDIO)) + { + // 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; + + // no timecodes for ignored streams + for (n=0;nnTracks;++n) + if (mf->trackMask & (ULL(1)<trackMask;;) { + 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 |= ULL(1)<Tracks[n]->Type == TT_VIDEO || mf->Tracks[n]->Type == TT_AUDIO)) { + ++z; + } + + if (z==mf->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,ulonglong mask) { + unsigned int i; + + if (mf->flags & MPF_ERROR) + return; + + mf->trackMask = mask; + + for (i=0;inTracks;++i) + if (mask & (ULL(1)<Queues[i]); +} + +int mkv_ReadFrame(MatroskaFile *mf, + ulonglong mask,unsigned int *track, + ulonglong *StartTime,ulonglong *EndTime, + ulonglong *FilePos,unsigned int *FrameSize, + char **FrameData,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 & (ULL(1)<Queues[i].head) { + j = i; + ++i; + break; + } + + for (;inTracks;++i) + if (!(mask & (ULL(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; + *FrameData = (char *)mf->cache->memalloc(mf->cache, qe->Length); + memcpy(*FrameData, qe->Data, 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 */ + unsigned frame_size; + char *frame_buffer; + + /* 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) { + mystrlcpy(errormsg, "No such track.", msgsize); + return NULL; + } + + if (!ti->CompEnabled) { + mystrlcpy(errormsg, "Track is not compressed.", msgsize); + return NULL; + } + + if (ti->CompMethod != COMP_ZLIB) { + mystrlcpy(errormsg, "Unsupported compression method.", msgsize); + return NULL; + } + + cs = mf->cache->memalloc(mf->cache,sizeof(*cs)); + if (cs == NULL) { + mystrlcpy(errormsg, "Ouf of memory.", msgsize); + return NULL; + } + + memset(&cs->zs,0,sizeof(cs->zs)); + code = inflateInit(&cs->zs); + if (code != Z_OK) { + mystrlcpy(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 */ unsigned size, + /* in */ char *frame_data) +{ + cs->zs.avail_in = 0; + inflateReset(&cs->zs); + cs->frame_size = size; + cs->frame_buffer = frame_data; + 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 = (Bytef *)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; + + cs->zs.next_in = (Bytef *)cs->frame_buffer; + cs->zs.avail_in = todo; + + cs->frame_size = 0; + } + + /* try to decode more data */ + code = inflate(&cs->zs,Z_NO_FLUSH); + if (code != Z_OK && code != Z_STREAM_END) { + mystrlcpy(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/libavformat/MatroskaParser.h b/libavformat/MatroskaParser.h new file mode 100644 index 0000000000..47e5524e99 --- /dev/null +++ b/libavformat/MatroskaParser.h @@ -0,0 +1,409 @@ +/* + * Copyright (c) 2004-2008 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.22 2008/04/29 21:03:09 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; + + unsigned int Enabled:1; + unsigned int Default:1; + unsigned int Forced: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; + + 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; + longlong DateUTC; + char DateUTCValid; +}; + +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]; + + unsigned int Hidden:1; + unsigned int Enabled:1; + + // Editions + unsigned int Default:1; + unsigned int Ordered:1; +}; + +typedef struct Chapter Chapter; + +struct Cue { + ulonglong Time; + ulonglong Position; + ulonglong Block; + unsigned char Track; +}; + +typedef struct Cue Cue; + +#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 void mkv_GetCues(MatroskaFile *mf, Cue **cue, 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 +#define MKVF_SEEK_TO_PREV_KEYFRAME_STRICT 2 + +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 */ ulonglong 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 */ ulonglong 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 */ char **FrameData, + /* 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 */ unsigned size, + /* in */ char *frame_data); + +/* 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/libavformat/allformats.c b/libavformat/allformats.c index e15d0fa6d7..3446576955 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -265,6 +265,7 @@ extern const FFInputFormat ff_mca_demuxer; extern const FFInputFormat ff_mcc_demuxer; extern const FFOutputFormat ff_md5_muxer; extern const FFInputFormat ff_matroska_demuxer; +extern const FFInputFormat ff_matroska_haali_demuxer; extern const FFOutputFormat ff_matroska_muxer; extern const FFOutputFormat ff_matroska_audio_muxer; extern const FFInputFormat ff_mgsts_demuxer; diff --git a/libavformat/matroskadec_haali.c b/libavformat/matroskadec_haali.c new file mode 100644 index 0000000000..288483ffc8 --- /dev/null +++ b/libavformat/matroskadec_haali.c @@ -0,0 +1,683 @@ +/* + * Matroska Demuxer based on Haalis MatroskaParser Library + * Copyright (c) 2011-2012 Hendrik Leppkes + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include "MatroskaParser.h" + +#include "avformat.h" +#include "internal.h" +#include "avio_internal.h" +/* For ff_codec_get_id(). */ +#include "riff.h" +#include "isom.h" +#include "rm.h" +#include "matroska.h" +#include "libavcodec/mpeg4audio.h" +#include "libavutil/intfloat.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/avstring.h" +#include "libavutil/dict.h" +#if CONFIG_ZLIB +#include +#endif + +#define IO_BUFFER_SIZE 32768 + +static const char *matroska_doctypes[] = { "matroska", "webm" }; + +typedef struct AVIOStream { + InputStream base; + AVFormatContext *ctx; +} AVIOStream; + +typedef struct MatroskaTrack { + TrackInfo *info; + CompressedStream *cs; + AVStream *stream; + int ms_compat; +} MatroskaTrack; + +typedef struct MatroskaDemuxContext { + AVIOStream *iostream; + MatroskaFile *matroska; + AVFormatContext *ctx; + + int num_tracks; + MatroskaTrack *tracks; + + ulonglong mask; + + char CSBuffer[4096]; + char *Buffer; + unsigned BufferSize; +} MatroskaDemuxContext; + +static int aviostream_read(struct AVIOStream *cc,ulonglong pos,void *buffer,int count) +{ + int ret; + int64_t ret64, cur_pos; + + if (count == 0) + return 0; + + cur_pos = avio_tell(cc->ctx->pb); + if (cur_pos != pos) { + /* Seek to the desired position */ + ret64 = avio_seek(cc->ctx->pb, pos, SEEK_SET); + if(ret64 < 0) { + av_log(cc->ctx, AV_LOG_ERROR, "aviostream_scan(): Seek to %"PRIu64" failed with code %"PRId64, pos, ret64); + return -1; + } + } + + /* Read the requested number of bytes */ + ret = avio_read(cc->ctx->pb, (unsigned char *)buffer, count); + if (ret == AVERROR_EOF) { + return 0; + } else if (ret < 0) { + return -1; + } + return ret; +} + +static longlong aviostream_scan(struct AVIOStream *cc,ulonglong start,unsigned signature) +{ + int64_t ret64, cur_pos; + unsigned cmp = 0; + + cur_pos = avio_tell(cc->ctx->pb); + if (cur_pos != start) { + /* Seek to the desired position */ + ret64 = avio_seek(cc->ctx->pb, start, SEEK_SET); + if(ret64 < 0) { + av_log(cc->ctx, AV_LOG_ERROR, "aviostream_scan(): Seek to %"PRIu64" failed with code %"PRId64, start, ret64); + return -1; + } + } + + /* Scan for the byte signature, until EOF was found */ + while(!cc->ctx->pb->eof_reached) { + cmp = ((cmp << 8) | avio_r8(cc->ctx->pb)) & 0xffffffff; + if (cmp == signature) + return avio_tell(cc->ctx->pb) - 4; + } + + return -1; +} + +static unsigned aviostream_getcachesize(struct AVIOStream *cc) +{ + return cc->ctx->pb->max_packet_size ? cc->ctx->pb->max_packet_size : IO_BUFFER_SIZE; +} + +static const char *aviostream_geterror(struct AVIOStream *cc) +{ + return "avio error"; +} + +static void *aviostream_memalloc(struct AVIOStream *cc, size_t size) +{ + return av_malloc(size); +} + +static void *aviostream_memrealloc(struct AVIOStream *cc, void *mem, size_t newsize) +{ + return av_realloc(mem, newsize); +} + +static void aviostream_memfree(struct AVIOStream *cc, void *mem) +{ + av_free(mem); +} + +static int aviostream_progress(struct AVIOStream *cc, ulonglong cur, ulonglong max) +{ + return 1; +} + +static longlong aviostream_getfilesize(struct AVIOStream *cc) +{ + return avio_size(cc->ctx->pb); +} + +/* Taken vanilla from ffmpeg */ +static int mkv_probe(AVProbeData *p) +{ + uint64_t total = 0; + int len_mask = 0x80, size = 1, n = 1, i; + + /* EBML header? */ + if (AV_RB32(p->buf) != EBML_ID_HEADER) + return 0; + + /* length of header */ + total = p->buf[4]; + while (size <= 8 && !(total & len_mask)) { + size++; + len_mask >>= 1; + } + if (size > 8) + return 0; + total &= (len_mask - 1); + while (n < size) + total = (total << 8) | p->buf[4 + n++]; + + /* Does the probe data contain the whole header? */ + if (p->buf_size < 4 + size + total) + return 0; + + /* The header should contain a known document type. For now, + * we don't parse the whole header but simply check for the + * availability of that array of characters inside the header. + * Not fully fool-proof, but good enough. */ + for (i = 0; i < FF_ARRAY_ELEMS(matroska_doctypes); i++) { + size_t probelen = strlen(matroska_doctypes[i]); + if (total < probelen) + continue; + for (n = 4+size; n <= 4+size+total-probelen; n++) + if (!memcmp(p->buf+n, matroska_doctypes[i], probelen)) + return AVPROBE_SCORE_MAX; + } + + // probably valid EBML header but no recognized doctype + return AVPROBE_SCORE_MAX/2; +} + +static int matroska_aac_profile(char *codec_id) +{ + static const char * const aac_profiles[] = { "MAIN", "LC", "SSR" }; + int profile; + + for (profile=0; profileUID && chapter->Enabled) { + AVChapter *avchap = avpriv_new_chapter(s, (int)chapter->UID, (AVRational){1, 1000000000}, chapter->Start, chapter->End, chapter->Display ? chapter->Display->String : NULL); + + if (level > 0 && chapter->Display && chapter->Display->String) { + char *title = (char *)av_mallocz(level + strlen(chapter->Display->String) + 2); + memset(title, '+', level); + title[level] = ' '; + memcpy(&title[level+1], chapter->Display->String, strlen(chapter->Display->String)); + av_dict_set(&avchap->metadata, "title", title, 0); + } + } + if (chapter->nChildren > 0) { + for (i = 0; i < chapter->nChildren; i++) { + mkv_process_chapter(s, chapter->Children + i, level + 1); + } + } +} + +static ulonglong mkv_get_track_mask(MatroskaDemuxContext *ctx) +{ + int i; + ulonglong mask = 0; + for (i = 0; i < ctx->num_tracks; i++) { + if (!ctx->tracks[i].stream || ctx->tracks[i].stream->discard == AVDISCARD_ALL) + mask |= (1ull << i); + } + return mask; +} + +static int mkv_read_header(AVFormatContext *s) +{ + MatroskaDemuxContext *ctx = (MatroskaDemuxContext *)s->priv_data; + char ErrorMessage[256]; + int i, j, num_tracks; + SegmentInfo *segment; + Chapter *chapters = NULL; + Attachment *attachments = NULL; + Cue *cues = NULL; + unsigned int count, u; + + AVIOStream *iostream = (AVIOStream *)av_mallocz(sizeof(AVIOStream)); + iostream->base.read = (int (*)(InputStream *,ulonglong,void *,int))aviostream_read; + iostream->base.scan = (longlong (*)(InputStream *,ulonglong,unsigned int))aviostream_scan; + iostream->base.getcachesize = (unsigned (*)(InputStream *cc))aviostream_getcachesize; + iostream->base.geterror = (const char *(*)(InputStream *))aviostream_geterror; + iostream->base.memalloc = (void *(*)(InputStream *,size_t))aviostream_memalloc; + iostream->base.memrealloc = (void *(*)(InputStream *,void *,size_t))aviostream_memrealloc; + iostream->base.memfree = (void (*)(InputStream *,void *))aviostream_memfree; + iostream->base.progress = (int (*)(InputStream *,ulonglong,ulonglong))aviostream_progress; + iostream->base.getfilesize = (longlong (*)(InputStream *))aviostream_getfilesize; + iostream->ctx = s; + ctx->iostream = iostream; + + av_log(s, AV_LOG_DEBUG, "Opening MKV file"); + ctx->matroska = mkv_OpenEx(&iostream->base, 0, 0, ErrorMessage, sizeof(ErrorMessage)); + if (!ctx->matroska) { + av_log(s, AV_LOG_ERROR, "mkv_OpenEx returned error: %s", ErrorMessage); + return -1; + } + + /* Gather information */ + segment = mkv_GetFileInfo(ctx->matroska); + + if (segment->Duration) + s->duration = segment->Duration / (1000000000 / AV_TIME_BASE); + av_dict_set(&s->metadata, "title", segment->Title, 0); + + /* Tracks */ + ctx->num_tracks = num_tracks = mkv_GetNumTracks(ctx->matroska); + ctx->tracks = (MatroskaTrack *)av_mallocz(sizeof(MatroskaTrack) * num_tracks); + for(i = 0; i < num_tracks; i++) { + MatroskaTrack *track = &ctx->tracks[i]; + TrackInfo *info = mkv_GetTrackInfo(ctx->matroska, i); + enum AVCodecID codec_id = AV_CODEC_ID_NONE; + AVStream *st; + uint8_t *extradata = NULL; + int extradata_size = 0; + int extradata_offset = 0; + uint32_t fourcc = 0; + AVIOContext b; + + track->info = info; + + if (info->Type != TT_VIDEO && info->Type != TT_AUDIO && info->Type != TT_SUB) { + av_log(s, AV_LOG_ERROR, "Unknown or unsupported track type: %d", info->Type); + continue; + } + + if (info->CodecID == NULL) + continue; + + av_log(s, AV_LOG_DEBUG, "Track %d, type: %d, codec_id: %s", i, info->Type, info->CodecID); + + if (info->CompEnabled && info->CompMethod == COMP_ZLIB) { + av_log(s, AV_LOG_DEBUG, "Track is ZLIB compressed"); + track->cs = cs_Create(ctx->matroska, i, ErrorMessage, sizeof(ErrorMessage)); + if (!track->cs) { + av_log(s, AV_LOG_ERROR, "Creating compressed stream failed: %s", ErrorMessage); + continue; + } + } + + for(j=0; ff_mkv_codec_tags[j].id != AV_CODEC_ID_NONE; j++){ + if(!strncmp(ff_mkv_codec_tags[j].str, info->CodecID, strlen(ff_mkv_codec_tags[j].str))){ + codec_id = ff_mkv_codec_tags[j].id; + break; + } + } + + st = track->stream = avformat_new_stream(s, NULL); + if (st == NULL) + return AVERROR(ENOMEM); + + if (!strcmp(info->CodecID, "V_MS/VFW/FOURCC") && info->CodecPrivateSize >= 40 && info->CodecPrivate != NULL) { + track->ms_compat = 1; + fourcc = AV_RL32((uint8_t *)info->CodecPrivate + 16); + codec_id = ff_codec_get_id(ff_codec_bmp_tags, fourcc); + extradata_offset = 40; + } else if (!strcmp(info->CodecID, "A_MS/ACM") && info->CodecPrivateSize >= 14 && info->CodecPrivate != NULL) { + int ret; + ffio_init_context(&b, (uint8_t *)info->CodecPrivate, info->CodecPrivateSize, 0, NULL, NULL, NULL, NULL); + ret = ff_get_wav_header(s, &b, st->codec, info->CodecPrivateSize, 0); + if (ret < 0) + return ret; + codec_id = st->codec->codec_id; + extradata_offset = FFMIN(info->CodecPrivateSize, 18); + } else if (!strcmp(info->CodecID, "V_QUICKTIME") && (info->CodecPrivateSize >= 86) && (info->CodecPrivate != NULL)) { + fourcc = AV_RL32(info->CodecPrivate); + codec_id = ff_codec_get_id(ff_codec_movvideo_tags, fourcc); + } else if (codec_id == AV_CODEC_ID_ALAC && info->CodecPrivateSize) { + /* Only ALAC's magic cookie is stored in Matroska's track headers. + Create the "atom size", "tag", and "tag version" fields the + decoder expects manually. */ + extradata_size = 12 + info->CodecPrivateSize; + extradata = av_mallocz(extradata_size + FF_INPUT_BUFFER_PADDING_SIZE); + if (extradata == NULL) + return AVERROR(ENOMEM); + AV_WB32(extradata, extradata_size); + memcpy(&extradata[4], "alac", 4); + AV_WB32(&extradata[8], 0); + memcpy(&extradata[12], info->CodecPrivate, info->CodecPrivateSize); + } else if (codec_id == AV_CODEC_ID_PCM_S16BE) { + switch (info->AV.Audio.BitDepth) { + case 8: codec_id = AV_CODEC_ID_PCM_U8; break; + case 24: codec_id = AV_CODEC_ID_PCM_S24BE; break; + case 32: codec_id = AV_CODEC_ID_PCM_S32BE; break; + } + } else if (codec_id == AV_CODEC_ID_PCM_S16LE) { + switch (info->AV.Audio.BitDepth) { + case 8: codec_id = AV_CODEC_ID_PCM_U8; break; + case 24: codec_id = AV_CODEC_ID_PCM_S24LE; break; + case 32: codec_id = AV_CODEC_ID_PCM_S32LE; break; + } + } else if (codec_id == AV_CODEC_ID_PCM_F32LE && info->AV.Audio.BitDepth == 64) { + codec_id = AV_CODEC_ID_PCM_F64LE; + } else if (codec_id == AV_CODEC_ID_AAC && !info->CodecPrivateSize) { + int profile = matroska_aac_profile(info->CodecID); + int sri = matroska_aac_sri(mkv_TruncFloat(info->AV.Audio.SamplingFreq)); + extradata = (uint8_t *)av_malloc(5 + FF_INPUT_BUFFER_PADDING_SIZE); + if (extradata == NULL) + return AVERROR(ENOMEM); + extradata[0] = (profile << 3) | ((sri&0x0E) >> 1); + extradata[1] = ((sri&0x01) << 7) | (info->AV.Audio.Channels<<3); + if (strstr(info->CodecID, "SBR")) { + sri = matroska_aac_sri(mkv_TruncFloat(info->AV.Audio.OutputSamplingFreq)); + extradata[2] = 0x56; + extradata[3] = 0xE5; + extradata[4] = 0x80 | (sri<<3); + extradata_size = 5; + } else + extradata_size = 2; + } else if (codec_id == AV_CODEC_ID_TTA) { + extradata_size = 30; + extradata = (uint8_t *)av_mallocz(extradata_size + FF_INPUT_BUFFER_PADDING_SIZE); + if (extradata == NULL) + return AVERROR(ENOMEM); + ffio_init_context(&b, extradata, extradata_size, 1, NULL, NULL, NULL, NULL); + avio_write(&b, "TTA1", 4); + avio_wl16(&b, 1); + avio_wl16(&b, info->AV.Audio.Channels); + avio_wl16(&b, info->AV.Audio.BitDepth); + avio_wl32(&b, mkv_TruncFloat(info->AV.Audio.OutputSamplingFreq)); + avio_wl32(&b, s->duration * info->AV.Audio.OutputSamplingFreq); + } + info->CodecPrivateSize -= extradata_offset; + + if (codec_id == AV_CODEC_ID_NONE) + av_log(s, AV_LOG_VERBOSE, "Unknown/unsupported CodecID: %s", info->CodecID); + + avpriv_set_pts_info(st, 64, 1, 1000*1000*1000); /* 64 bit pts in ns */ + + st->codec->codec_id = codec_id; + st->start_time = 0; + if (strlen(info->Language) == 0) /* default english language if none is set */ + av_dict_set(&st->metadata, "language", "eng", 0); + else if (strcmp(info->Language, "und")) + av_dict_set(&st->metadata, "language", info->Language, 0); + av_dict_set(&st->metadata, "title", info->Name, 0); + + if (info->Default) + st->disposition |= AV_DISPOSITION_DEFAULT; + if (info->Forced) + st->disposition |= AV_DISPOSITION_FORCED; + + if (!st->codec->extradata) { + if(extradata){ + st->codec->extradata = extradata; + st->codec->extradata_size = extradata_size; + } else if(info->CodecPrivate && info->CodecPrivateSize > 0){ + st->codec->extradata = (uint8_t *)av_mallocz(info->CodecPrivateSize + FF_INPUT_BUFFER_PADDING_SIZE); + if(st->codec->extradata == NULL) + return AVERROR(ENOMEM); + st->codec->extradata_size = info->CodecPrivateSize; + memcpy(st->codec->extradata, (uint8_t *)info->CodecPrivate + extradata_offset, info->CodecPrivateSize); + } + } + + if (info->Type == TT_VIDEO) { + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + st->codec->codec_tag = fourcc; + st->codec->width = info->AV.Video.PixelWidth; + st->codec->height = info->AV.Video.PixelHeight; + av_reduce(&st->sample_aspect_ratio.num, &st->sample_aspect_ratio.den, + st->codec->height * info->AV.Video.DisplayWidth, + st->codec-> width * info->AV.Video.DisplayHeight, + 1 << 30); + if (st->codec->codec_id != AV_CODEC_ID_H264) + st->need_parsing = AVSTREAM_PARSE_HEADERS; + if (info->DefaultDuration) { + av_reduce(&st->r_frame_rate.num, &st->r_frame_rate.den, + 1000000000, info->DefaultDuration, 100000); + st->avg_frame_rate = st->r_frame_rate; + } + + /* export stereo mode flag as metadata tag */ + /* if (track->video.stereo_mode && track->video.stereo_mode < MATROSKA_VIDEO_STEREO_MODE_COUNT) + av_dict_set(&st->metadata, "stereo_mode", matroska_video_stereo_mode[track->video.stereo_mode], 0); + + // if we have virtual track, mark the real tracks + for (j=0; j < track->operation.combine_planes.nb_elem; j++) { + char buf[32]; + if (planes[j].type >= MATROSKA_VIDEO_STEREO_PLANE_COUNT) + continue; + snprintf(buf, sizeof(buf), "%s_%d", matroska_video_stereo_plane[planes[j].type], i); + for (k=0; k < matroska->tracks.nb_elem; k++) + if (planes[j].uid == tracks[k].uid) { + av_dict_set(&s->streams[k]->metadata, "stereo_mode", buf, 0); + break; + } + } */ + } else if (info->Type == TT_AUDIO) { + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->sample_rate = (unsigned int)info->AV.Audio.OutputSamplingFreq; + st->codec->channels = info->AV.Audio.Channels; + if (st->codec->codec_id != AV_CODEC_ID_AAC && st->codec->codec_id != AV_CODEC_ID_MLP && st->codec->codec_id != AV_CODEC_ID_TRUEHD) + st->need_parsing = AVSTREAM_PARSE_HEADERS; + } else if (info->Type == TT_SUB) { + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + } + } + + /* chapter start at level -1 because they are always wrapped in a edition entry */ + mkv_GetChapters(ctx->matroska, &chapters, &count); + for (u = 0; u < count; u++) { + mkv_process_chapter(s, chapters + u, -1); + } + + mkv_GetAttachments(ctx->matroska, &attachments, &count); + for (u = 0; u < count; u++) { + Attachment *attach = &attachments[u]; + + if (!(attach->Name && attach->MimeType && attach->Length > 0)) { + av_log(s, AV_LOG_VERBOSE, "Incomplete attachment, skipping"); + } else { + AVStream *st = avformat_new_stream(s, NULL); + if (st == NULL) + break; + av_dict_set(&st->metadata, "filename", attach->Name, 0); + av_dict_set(&st->metadata, "mimetype", attach->MimeType, 0); + st->codec->codec_id = AV_CODEC_ID_NONE; + st->codec->codec_type = AVMEDIA_TYPE_ATTACHMENT; + + st->codec->extradata = (uint8_t *)av_malloc((size_t)attach->Length + FF_INPUT_BUFFER_PADDING_SIZE); + if(st->codec->extradata == NULL) + break; + st->codec->extradata_size = (int)attach->Length; + aviostream_read(ctx->iostream, attach->Position, st->codec->extradata, st->codec->extradata_size); + + for (i=0; ff_mkv_mime_tags[i].id != AV_CODEC_ID_NONE; i++) { + if (!strncmp(ff_mkv_mime_tags[i].str, attach->MimeType, strlen(ff_mkv_mime_tags[i].str))) { + st->codec->codec_id = ff_mkv_mime_tags[i].id; + break; + } + } + } + } + + /* convert Cue entries into av index entries */ + mkv_GetCues(ctx->matroska, &cues, &count); + for (u = 0; u < count; u++) { + for(i = 0; i < num_tracks; i++) { + av_add_index_entry(ctx->tracks[i].stream, -1, cues[u].Time, 0, 0, AVINDEX_KEYFRAME); + } + } + + return 0; +} + +static int mkv_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + MatroskaDemuxContext *ctx = (MatroskaDemuxContext *)s->priv_data; + + int ret; + unsigned int size, offset = 0, flags, track_num; + ulonglong start_time, end_time, pos; + MatroskaTrack *track; + char *frame_data = NULL; + + ulonglong mask = 0; + if (!(s->flags & AVFMT_FLAG_NETWORK)) { + mask = mkv_get_track_mask(ctx); + if (mask != ctx->mask) { + mkv_SetTrackMask(ctx->matroska, mask); + ctx->mask = mask; + } + } + +again: + av_freep(&frame_data); + ret = mkv_ReadFrame(ctx->matroska, mask, &track_num, &start_time, &end_time, &pos, &size, &frame_data, &flags); + if (ret < 0) + return AVERROR_EOF; + + track = &ctx->tracks[track_num]; + if (!track->stream || track->stream->discard == AVDISCARD_ALL) + goto again; + + /* zlib compression */ + if (track->cs) { + unsigned int frame_size = 0; + cs_NextFrame(track->cs, size, frame_data); + for(;;) { + ret = cs_ReadData(track->cs, ctx->CSBuffer, sizeof(ctx->CSBuffer)); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "cs_ReadData failed"); + ret = AVERROR(EIO); + goto done; + } else if (ret == 0) { + size = frame_size; + break; + } + if (ctx->BufferSize < (frame_size + ret)) { + ctx->BufferSize = (frame_size + ret) * 2; + ctx->Buffer = (char *)av_realloc(ctx->Buffer, ctx->BufferSize); + } + memcpy(ctx->Buffer + frame_size, ctx->CSBuffer, ret); + frame_size += ret; + } + av_new_packet(pkt, frame_size); + memcpy(pkt->data, ctx->Buffer, frame_size); + } else { + /* header removal compression */ + if (track->info->CompEnabled && track->info->CompMethod == COMP_PREPEND && track->info->CompMethodPrivateSize > 0) { + offset = track->info->CompMethodPrivateSize; + } + + av_new_packet(pkt, size+offset); + memcpy(pkt->data+offset, frame_data, size); + if (offset > 0) + memcpy(pkt->data, track->info->CompMethodPrivate, offset); + } + + if (!(flags & FRAME_UNKNOWN_START)) { + if (track->ms_compat) + pkt->dts = start_time; + else + pkt->pts = start_time; + + if (track->info->Type == TT_SUB) + pkt->convergence_duration = end_time - start_time; + else + pkt->duration = (int)(end_time - start_time); + } + + pkt->flags = (flags & FRAME_KF) ? AV_PKT_FLAG_KEY : 0; + pkt->pos = pos; + pkt->size = size+offset; + pkt->stream_index = track->stream->index; + + ret = 0; +done: + av_freep(&frame_data); + return ret; +} + +static int mkv_read_close(AVFormatContext *s) +{ + MatroskaDemuxContext *ctx = (MatroskaDemuxContext *)s->priv_data; + int i; + + mkv_Close(ctx->matroska); + av_freep(&ctx->iostream); + + for (i = 0; i < ctx->num_tracks; i++) { + av_freep(&ctx->tracks[i].cs); + } + av_freep(&ctx->tracks); + + + return 0; +} + +static int mkv_read_seek(AVFormatContext *s, int stream_index, int64_t timestamp, int flags) +{ + MatroskaDemuxContext *ctx = (MatroskaDemuxContext *)s->priv_data; + int mkvflags = (!(flags & AVSEEK_FLAG_ANY) && !(s->flags & AVFMT_FLAG_NETWORK)) ? MKVF_SEEK_TO_PREV_KEYFRAME : 0; + int i; + int64_t cur_dts; + AVStream *st = ctx->tracks[stream_index].stream; + + /* check if we're seeking to a index entry directly, and if so, disable the keyframe logic */ + for (i = 0; i < st->nb_index_entries; i++) { + if (st->index_entries[i].timestamp == timestamp) { + mkvflags &= ~MKVF_SEEK_TO_PREV_KEYFRAME; + break; + } + } + + /* update track mask */ + if (!(s->flags & AVFMT_FLAG_NETWORK)) + mkv_SetTrackMask(ctx->matroska, mkv_get_track_mask(ctx)); + + /* perform seek */ + mkv_Seek(ctx->matroska, timestamp, mkvflags); + + /* Update current timestamp */ + cur_dts = mkv_GetLowestQTimecode(ctx->matroska); + av_log(s, AV_LOG_DEBUG, "mkv_read_seek: requested: %"PRId64", achieved: %"PRId64, timestamp, cur_dts); + if (cur_dts == -1) + cur_dts = timestamp; + + ff_update_cur_dts(s, ctx->tracks[stream_index].stream, cur_dts); + + return 0; +} + +FFInputFormat ff_matroska_haali_demuxer = { + .p.name = "matroska", + .p.long_name = NULL_IF_CONFIG_SMALL("Matroska/WebM"), + .priv_data_size = sizeof(MatroskaDemuxContext), + .read_probe = mkv_probe, + .read_header = mkv_read_header, + .read_packet = mkv_read_packet, + .read_close = mkv_read_close, + .read_seek = mkv_read_seek, +};