diff --git a/FFmpegSource/MatroskaParser.c b/FFmpegSource/MatroskaParser.c new file mode 100644 index 000000000..d6ff7b320 --- /dev/null +++ b/FFmpegSource/MatroskaParser.c @@ -0,0 +1,3311 @@ +/* + * Copyright (c) 2004-2006 Mike Matsnev. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice immediately at the beginning of the file, without modification, + * this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Absolutely no warranty of function or purpose is made by the author + * Mike Matsnev. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id: MatroskaParser.c,v 1.61 2006/10/28 10:39:45 mike Exp $ + * + */ + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +// MS names some functions differently +#define alloca _alloca +#define inline __inline + +#include +#endif + +#ifndef EVCBUG +#define EVCBUG +#endif + +#include "MatroskaParser.h" + +#ifdef MATROSKA_COMPRESSION_SUPPORT +#include "zlib.h" +#endif + +#define EBML_VERSION 1 +#define EBML_MAX_ID_LENGTH 4 +#define EBML_MAX_SIZE_LENGTH 8 +#define MATROSKA_VERSION 2 +#define MATROSKA_DOCTYPE "matroska" + +#define MAX_STRING_LEN 1023 +#define QSEGSIZE 512 +#define MAX_TRACKS 32 +#define MAX_READAHEAD (256*1024) + +#define MAXCLUSTER (64*1048576) +#define MAXFRAME (4*1048576) + +#ifdef WIN32 +#define LL(x) x##i64 +#define ULL(x) x##ui64 +#else +#define LL(x) x##ll +#define ULL(x) x##ull +#endif + +#define MAXU64 ULL(0xffffffffffffffff) +#define ONE ULL(1) + +// compatibility +static char *mystrdup(struct InputStream *is,const char *src) { + size_t len; + char *dst; + + if (src==NULL) + return NULL; + + len = strlen(src); + dst = is->memalloc(is,len+1); + if (dst==NULL) + return NULL; + + memcpy(dst,src,len+1); + + return dst; +} + +#ifdef _WIN32 +static void strlcpy(char *dst,const char *src,unsigned size) { + unsigned i; + + for (i=0;i+1 0) + *dest = '\0'; + return; + } + + while (*fmt && dest < de) + switch (state) { + case 0: + if (*fmt == '%') { + ++fmt; + state = 1; + width = zero = neg = ll = 0; + } else + *dest++ = *fmt++; + break; + case 1: + if (*fmt == '-') { + neg = 1; + ++fmt; + state = 2; + break; + } + if (*fmt == '0') + zero = 1; + state = 2; + case 2: + if (*fmt >= '0' && *fmt <= '9') { + width = width * 10 + *fmt++ - '0'; + break; + } + state = 3; + case 3: + if (*fmt == 'l') { + ++ll; + ++fmt; + break; + } + state = 4; + case 4: + switch (*fmt) { + case 's': + myvsnprintf_string(&dest,de,va_arg(ap,const char *)); + break; + case 'd': + switch (ll) { + case 0: + myvsnprintf_int(&dest,de,width,zero,neg,10,'a',va_arg(ap,int)); + break; + case 1: + myvsnprintf_int(&dest,de,width,zero,neg,10,'a',va_arg(ap,long)); + break; + case 2: + myvsnprintf_int(&dest,de,width,zero,neg,10,'a',va_arg(ap,longlong)); + break; + } + break; + case 'u': + switch (ll) { + case 0: + myvsnprintf_uint(&dest,de,width,zero,neg,10,'a',va_arg(ap,unsigned int)); + break; + case 1: + myvsnprintf_uint(&dest,de,width,zero,neg,10,'a',va_arg(ap,unsigned long)); + break; + case 2: + myvsnprintf_uint(&dest,de,width,zero,neg,10,'a',va_arg(ap,ulonglong)); + break; + } + break; + case 'x': + switch (ll) { + case 0: + myvsnprintf_uint(&dest,de,width,zero,neg,16,'a',va_arg(ap,unsigned int)); + break; + case 1: + myvsnprintf_uint(&dest,de,width,zero,neg,16,'a',va_arg(ap,unsigned long)); + break; + case 2: + myvsnprintf_uint(&dest,de,width,zero,neg,16,'a',va_arg(ap,ulonglong)); + break; + } + break; + case 'X': + switch (ll) { + case 0: + myvsnprintf_uint(&dest,de,width,zero,neg,16,'A',va_arg(ap,unsigned int)); + break; + case 1: + myvsnprintf_uint(&dest,de,width,zero,neg,16,'A',va_arg(ap,unsigned long)); + break; + case 2: + myvsnprintf_uint(&dest,de,width,zero,neg,16,'A',va_arg(ap,ulonglong)); + break; + } + break; + default: + break; + } + ++fmt; + state = 0; + break; + default: + state = 0; + break; + } + *dest = '\0'; +} + +static void errorjmp(MatroskaFile *mf,const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + myvsnprintf(mf->errmsg,sizeof(mf->errmsg),fmt,ap); + va_end(ap); + + mf->flags |= MPF_ERROR; + + longjmp(mf->jb,1); +} + +/////////////////////////////////////////////////////////////////////////// +// arrays +static void *ArrayAlloc(MatroskaFile *mf,void **base, + unsigned *cur,unsigned *max,unsigned elem_size) +{ + if (*cur>=*max) { + void *np; + unsigned newsize = *max * 2; + if (newsize==0) + newsize = 1; + + np = mf->cache->memrealloc(mf->cache,*base,newsize*elem_size); + if (np==NULL) + errorjmp(mf,"Out of memory in ArrayAlloc"); + + *base = np; + *max = newsize; + } + + return (char*)*base + elem_size * (*cur)++; +} + +static void ArrayReleaseMemory(MatroskaFile *mf,void **base, + unsigned cur,unsigned *max,unsigned elem_size) +{ + if (cur<*max) { + void *np = mf->cache->memrealloc(mf->cache,*base,cur*elem_size); + *base = np; + *max = cur; + } +} + + +#define ASGET(f,s,name) ArrayAlloc((f),(void**)&(s)->name,&(s)->n##name,&(s)->n##name##Size,sizeof(*((s)->name))) +#define AGET(f,name) ArrayAlloc((f),(void**)&(f)->name,&(f)->n##name,&(f)->n##name##Size,sizeof(*((f)->name))) +#define ARELEASE(f,s,name) ArrayReleaseMemory((f),(void**)&(s)->name,(s)->n##name,&(s)->n##name##Size,sizeof(*((s)->name))) + +/////////////////////////////////////////////////////////////////////////// +// queues +static struct QueueEntry *QPut(struct Queue *q,struct QueueEntry *qe) { + if (q->tail) + q->tail->next = qe; + qe->next = NULL; + q->tail = qe; + if (q->head==NULL) + q->head = qe; + + return qe; +} + +static struct QueueEntry *QGet(struct Queue *q) { + struct QueueEntry *qe = q->head; + if (qe == NULL) + return NULL; + q->head = qe->next; + if (q->tail == qe) + q->tail = NULL; + return qe; +} + +static struct QueueEntry *QAlloc(MatroskaFile *mf) { + struct QueueEntry *qe,**qep; + if (mf->QFreeList == NULL) { + unsigned i; + + qep = AGET(mf,QBlocks); + + *qep = mf->cache->memalloc(mf->cache,QSEGSIZE * sizeof(*qe)); + if (*qep == NULL) + errorjmp(mf,"Ouf of memory"); + + qe = *qep; + + for (i=0;iQFreeList = qe; + } + + qe = mf->QFreeList; + mf->QFreeList = qe->next; + + return qe; +} + +static inline void QFree(MatroskaFile *mf,struct QueueEntry *qe) { + qe->next = mf->QFreeList; + mf->QFreeList = qe; +} + +// fill the buffer at current position +static void fillbuf(MatroskaFile *mf) { + int rd; + + // advance buffer pointers + mf->bufbase += mf->buflen; + mf->buflen = mf->bufpos = 0; + + // get the relevant page + rd = mf->cache->read(mf->cache, mf->bufbase, mf->inbuf, IBSZ); + if (rd<0) + errorjmp(mf,"I/O Error: %s",mf->cache->geterror(mf->cache)); + + mf->buflen = rd; +} + +// fill the buffer and return next char +static int nextbuf(MatroskaFile *mf) { + fillbuf(mf); + + if (mf->bufpos < mf->buflen) + return (unsigned char)(mf->inbuf[mf->bufpos++]); + + return EOF; +} + +static inline int readch(MatroskaFile *mf) { + return mf->bufpos < mf->buflen ? (unsigned char)(mf->inbuf[mf->bufpos++]) : nextbuf(mf); +} + +static inline ulonglong filepos(MatroskaFile *mf) { + return mf->bufbase + mf->bufpos; +} + +static void readbytes(MatroskaFile *mf,void *buffer,int len) { + char *cp = buffer; + int nb = mf->buflen - mf->bufpos; + + if (nb > len) + nb = len; + + memcpy(cp, mf->inbuf + mf->bufpos, nb); + mf->bufpos += nb; + len -= nb; + cp += nb; + + if (len>0) { + mf->bufbase += mf->buflen; + mf->bufpos = mf->buflen = 0; + + nb = mf->cache->read(mf->cache, mf->bufbase, cp, len); + if (nb<0) + errorjmp(mf,"I/O Error: %s",mf->cache->geterror(mf->cache)); + if (nb != len) + errorjmp(mf,"Short read: got %d bytes of %d",nb,len); + mf->bufbase += len; + } +} + +static void skipbytes(MatroskaFile *mf,ulonglong len) { + int nb = mf->buflen - mf->bufpos; + + if (nb > len) + nb = (int)len; + + mf->bufpos += nb; + len -= nb; + + if (len>0) { + mf->bufbase += mf->buflen; + mf->bufpos = mf->buflen = 0; + + mf->bufbase += len; + } +} + +static void seek(MatroskaFile *mf,ulonglong pos) { + // see if pos is inside buffer + if (pos>=mf->bufbase && posbufbase+mf->buflen) + mf->bufpos = (unsigned)(pos - mf->bufbase); + else { + // invalidate buffer and set pointer + mf->bufbase = pos; + mf->buflen = mf->bufpos = 0; + } +} + +/////////////////////////////////////////////////////////////////////////// +// floating point +static inline MKFLOAT mkfi(int i) { +#ifdef MATROSKA_INTEGER_ONLY + MKFLOAT f; + f.v = (longlong)i << 32; + return f; +#else + return i; +#endif +} + +static inline longlong mul3(MKFLOAT scale,longlong tc) { +#ifdef MATROSKA_INTEGER_ONLY + // x1 x0 + // y1 y0 + // -------------- + // x0*y0 + // x1*y0 + // x0*y1 + // x1*y1 + // -------------- + // .. r1 r0 .. + // + // r = ((x0*y0) >> 32) + (x1*y0) + (x0*y1) + ((x1*y1) << 32) + unsigned x0,x1,y0,y1; + ulonglong p; + char sign = 0; + + if (scale.v < 0) + sign = !sign, scale.v = -scale.v; + if (tc < 0) + sign = !sign, tc = -tc; + + x0 = (unsigned)scale.v; + x1 = (unsigned)((ulonglong)scale.v >> 32); + y0 = (unsigned)tc; + y1 = (unsigned)((ulonglong)tc >> 32); + + p = (ulonglong)x0*y0 >> 32; + p += (ulonglong)x0*y1; + p += (ulonglong)x1*y0; + p += (ulonglong)(x1*y1) << 32; + + return p; +#else + return (longlong)(scale * tc); +#endif +} + +/////////////////////////////////////////////////////////////////////////// +// EBML support +static int readID(MatroskaFile *mf) { + int c1,c2,c3,c4; + + c1 = readch(mf); + if (c1 == EOF) + return EOF; + + if (c1 & 0x80) + return c1; + + if ((c1 & 0xf0) == 0) + errorjmp(mf,"Invalid first byte of EBML ID: %02X",c1); + + c2 = readch(mf); + if (c2 == EOF) +fail: + errorjmp(mf,"Got EOF while reading EBML ID"); + + if ((c1 & 0xc0) == 0x40) + return (c1<<8) | c2; + + c3 = readch(mf); + if (c3 == EOF) + goto fail; + + if ((c1 & 0xe0) == 0x20) + return (c1<<16) | (c2<<8) | c3; + + c4 = readch(mf); + if (c4 == EOF) + goto fail; + + if ((c1 & 0xf0) == 0x10) + return (c1<<24) | (c2<<16) | (c3<<8) | c4; + + return 0; // NOT REACHED +} + +static ulonglong readVLUIntImp(MatroskaFile *mf,int *mask) { + int c,d,m; + ulonglong v = 0; + + c = readch(mf); + if (c == EOF) + return EOF; + + if (c == 0) + errorjmp(mf,"Invalid first byte of EBML integer: 0"); + + for (m=0;;++m) { + if (c & (0x80 >> m)) { + c &= 0x7f >> m; + if (mask) + *mask = m; + return v | ((ulonglong)c << m*8); + } + d = readch(mf); + if (d == EOF) + errorjmp(mf,"Got EOF while reading EBML unsigned integer"); + v = (v<<8) | d; + } + // NOT REACHED +} + +static inline ulonglong readVLUInt(MatroskaFile *mf) { + return readVLUIntImp(mf,NULL); +} + +static ulonglong readSize(MatroskaFile *mf) { + int m; + ulonglong v = readVLUIntImp(mf,&m); + + // see if it's unspecified + if (v == (MAXU64 >> (57-m*7))) + errorjmp(mf,"Unspecified element size is not supported here."); + + return v; +} + +static inline longlong readVLSInt(MatroskaFile *mf) { + static longlong bias[8] = { (ONE<<6)-1, (ONE<<13)-1, (ONE<<20)-1, (ONE<<27)-1, + (ONE<<34)-1, (ONE<<41)-1, (ONE<<48)-1, (ONE<<55)-1 }; + + int m; + longlong v = readVLUIntImp(mf,&m); + + return v - bias[m]; +} + +static ulonglong readUInt(MatroskaFile *mf,unsigned int len) { + int c; + unsigned int m = len; + ulonglong v = 0; + + if (len==0) + return v; + if (len>8) + errorjmp(mf,"Unsupported integer size in readUInt: %u",len); + + do { + c = readch(mf); + if (c == EOF) + errorjmp(mf,"Got EOF while reading EBML unsigned integer"); + v = (v<<8) | c; + } while (--m); + + return v; +} + +static inline longlong readSInt(MatroskaFile *mf,unsigned int len) { + longlong v = readUInt(mf,(unsigned)len); + int s = 64 - (len<<3); + return (v << s) >> s; +} + +static MKFLOAT readFloat(MatroskaFile *mf,unsigned int len) { +#ifdef MATROSKA_INTEGER_ONLY + MKFLOAT f; + int shift; +#else + union { + unsigned int ui; + ulonglong ull; + float f; + double d; + } u; +#endif + + if (len!=4 && len!=8) + errorjmp(mf,"Invalid float size in readFloat: %u",len); + +#ifdef MATROSKA_INTEGER_ONLY + if (len == 4) { + unsigned ui = (unsigned)readUInt(mf,(unsigned)len); + f.v = (ui & 0x7fffff) | 0x800000; + if (ui & 0x80000000) + f.v = -f.v; + shift = (ui >> 23) & 0xff; + if (shift == 0) // assume 0 +zero: + shift = 0, f.v = 0; + else if (shift == 255) +inf: + if (ui & 0x80000000) + f.v = LL(0x8000000000000000); + else + f.v = LL(0x7fffffffffffffff); + else { + shift += -127 + 9; + if (shift > 39) + goto inf; +shift: + if (shift < 0) + f.v = f.v >> -shift; + else if (shift > 0) + f.v = f.v << shift; + } + } else if (len == 8) { + ulonglong ui = readUInt(mf,(unsigned)len); + f.v = (ui & LL(0xfffffffffffff)) | LL(0x10000000000000); + if (ui & 0x80000000) + f.v = -f.v; + shift = (int)((ui >> 52) & 0x7ff); + if (shift == 0) // assume 0 + goto zero; + else if (shift == 2047) + goto inf; + else { + shift += -1023 - 20; + if (shift > 10) + goto inf; + goto shift; + } + } + + return f; +#else + if (len==4) { + u.ui = (unsigned int)readUInt(mf,(unsigned)len); + return u.f; + } + + if (len==8) { + u.ull = readUInt(mf,(unsigned)len); + return u.d; + } + + return 0; +#endif +} + +static void readString(MatroskaFile *mf,ulonglong len,char *buffer,int buflen) { + int nread; + + if (buflen<1) + errorjmp(mf,"Invalid buffer size in readString: %d",buflen); + + nread = buflen - 1; + + if (nread > len) + nread = (int)len; + + readbytes(mf,buffer,nread); + len -= nread; + + if (len>0) + skipbytes(mf,len); + + buffer[nread] = '\0'; +} + +static void readLangCC(MatroskaFile *mf, ulonglong len, char lcc[4]) { + unsigned todo = len > 3 ? 3 : (int)len; + + lcc[0] = lcc[1] = lcc[2] = lcc[3] = 0; + readbytes(mf, lcc, todo); + skipbytes(mf, len - todo); +} + +/////////////////////////////////////////////////////////////////////////// +// file parser +#define FOREACH(f,tl) \ + { \ + ulonglong tmplen = (tl); \ + { \ + ulonglong start = filepos(f); \ + ulonglong cur,len; \ + int id; \ + for (;;) { \ + cur = filepos(mf); \ + if (cur == start + tmplen) \ + break; \ + id = readID(f); \ + if (id==EOF) \ + errorjmp(mf,"Unexpected EOF while reading EBML container"); \ + len = readSize(mf); \ + switch (id) { + +#define ENDFOR1(f) \ + default: \ + skipbytes(f,len); \ + break; \ + } +#define ENDFOR2() \ + } \ + } \ + } + +#define ENDFOR(f) ENDFOR1(f) ENDFOR2() + +#define myalloca(f,c) alloca(c) +#define STRGETF(f,v,len,func) \ + { \ + char *TmpVal; \ + unsigned TmpLen = (len)>MAX_STRING_LEN ? MAX_STRING_LEN : (unsigned)(len); \ + TmpVal = func(f->cache,TmpLen+1); \ + if (TmpVal == NULL) \ + errorjmp(mf,"Out of memory"); \ + readString(f,len,TmpVal,TmpLen+1); \ + (v) = TmpVal; \ + } + +#define STRGETA(f,v,len) STRGETF(f,v,len,myalloca) +#define STRGETM(f,v,len) STRGETF(f,v,len,f->cache->memalloc) + +static int IsWritingApp(MatroskaFile *mf,const char *str) { + const char *cp = mf->Seg.WritingApp; + if (!cp) + return 0; + + while (*str && *str++==*cp++) ; + + return !*str; +} + +static void parseEBML(MatroskaFile *mf,ulonglong toplen) { + ulonglong v; + char buf[32]; + + FOREACH(mf,toplen) + case 0x4286: // Version + v = readUInt(mf,(unsigned)len); + break; + case 0x42f7: // ReadVersion + v = readUInt(mf,(unsigned)len); + if (v > EBML_VERSION) + errorjmp(mf,"File requires version %d EBML parser",(int)v); + break; + case 0x42f2: // MaxIDLength + v = readUInt(mf,(unsigned)len); + if (v > EBML_MAX_ID_LENGTH) + errorjmp(mf,"File has identifiers longer than %d",(int)v); + break; + case 0x42f3: // MaxSizeLength + v = readUInt(mf,(unsigned)len); + if (v > EBML_MAX_SIZE_LENGTH) + errorjmp(mf,"File has integers longer than %d",(int)v); + break; + case 0x4282: // DocType + readString(mf,len,buf,sizeof(buf)); + if (strcmp(buf,MATROSKA_DOCTYPE)) + errorjmp(mf,"Unsupported DocType: %s",buf); + break; + case 0x4287: // DocTypeVersion + v = readUInt(mf,(unsigned)len); + break; + case 0x4285: // DocTypeReadVersion + v = readUInt(mf,(unsigned)len); + if (v > MATROSKA_VERSION) + errorjmp(mf,"File requires version %d Matroska parser",(int)v); + break; + ENDFOR(mf); +} + +static void parseSeekEntry(MatroskaFile *mf,ulonglong toplen) { + int seekid = 0; + ulonglong pos = (ulonglong)-1; + + FOREACH(mf,toplen) + case 0x53ab: // SeekID + if (len>EBML_MAX_ID_LENGTH) + errorjmp(mf,"Invalid ID size in parseSeekEntry: %d\n",(int)len); + seekid = (int)readUInt(mf,(unsigned)len); + break; + case 0x53ac: // SeekPos + pos = readUInt(mf,(unsigned)len); + break; + ENDFOR(mf); + + if (pos == (ulonglong)-1) + errorjmp(mf,"Invalid element position in parseSeekEntry"); + + pos += mf->pSegment; + switch (seekid) { + case 0x114d9b74: // next SeekHead + if (mf->pSeekHead) + errorjmp(mf,"SeekHead contains more than one SeekHead pointer"); + mf->pSeekHead = pos; + break; + case 0x1549a966: // SegmentInfo + mf->pSegmentInfo = pos; + break; + case 0x1f43b675: // Cluster + if (!mf->pCluster) + mf->pCluster = pos; + break; + case 0x1654ae6b: // Tracks + mf->pTracks = pos; + break; + case 0x1c53bb6b: // Cues + mf->pCues = pos; + break; + case 0x1941a469: // Attachments + mf->pAttachments = pos; + break; + case 0x1043a770: // Chapters + mf->pChapters = pos; + break; + case 0x1254c367: // tags + mf->pTags = pos; + break; + } +} + +static void parseSeekHead(MatroskaFile *mf,ulonglong toplen) { + FOREACH(mf,toplen) + case 0x4dbb: + parseSeekEntry(mf,len); + break; + ENDFOR(mf); +} + +static void parseSegmentInfo(MatroskaFile *mf,ulonglong toplen) { + MKFLOAT duration = mkfi(0); + + if (mf->seen.SegmentInfo) { + skipbytes(mf,toplen); + return; + } + + mf->seen.SegmentInfo = 1; + mf->Seg.TimecodeScale = 1000000; // Default value + + FOREACH(mf,toplen) + case 0x73a4: // SegmentUID + if (len!=sizeof(mf->Seg.UID)) + errorjmp(mf,"SegmentUID size is not %d bytes",mf->Seg.UID); + readbytes(mf,mf->Seg.UID,sizeof(mf->Seg.UID)); + break; + case 0x7384: // SegmentFilename + STRGETM(mf,mf->Seg.Filename,len); + break; + case 0x3cb923: // PrevUID + if (len!=sizeof(mf->Seg.PrevUID)) + errorjmp(mf,"PrevUID size is not %d bytes",mf->Seg.PrevUID); + readbytes(mf,mf->Seg.PrevUID,sizeof(mf->Seg.PrevUID)); + break; + case 0x3c83ab: // PrevFilename + STRGETM(mf,mf->Seg.PrevFilename,len); + break; + case 0x3eb923: // NextUID + if (len!=sizeof(mf->Seg.NextUID)) + errorjmp(mf,"NextUID size is not %d bytes",mf->Seg.NextUID); + readbytes(mf,mf->Seg.NextUID,sizeof(mf->Seg.NextUID)); + break; + case 0x3e83bb: // NextFilename + STRGETM(mf,mf->Seg.NextFilename,len); + break; + case 0x2ad7b1: // TimecodeScale + mf->Seg.TimecodeScale = readUInt(mf,(unsigned)len); + if (mf->Seg.TimecodeScale == 0) + errorjmp(mf,"Segment timecode scale is zero"); + break; + case 0x4489: // Duration + duration = readFloat(mf,(unsigned)len); + break; + case 0x4461: // DateUTC + mf->Seg.DateUTC = readUInt(mf,(unsigned)len); + 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) { + mf->seen.Cluster = 1; + mf->firstTimecode = 0; + + FOREACH(mf,toplen) + case 0xe7: // Timecode + mf->firstTimecode += readUInt(mf,(unsigned)len); + break; + case 0xa3: // BlockEx + readVLUInt(mf); // track number + mf->firstTimecode += readSInt(mf, 2); + + skipbytes(mf,start + toplen - filepos(mf)); + return; + case 0xa0: // BlockGroup + FOREACH(mf,len) + case 0xa1: // Block + readVLUInt(mf); // track number + mf->firstTimecode += readSInt(mf,2); + + skipbytes(mf,start + toplen - filepos(mf)); + return; + ENDFOR(mf); + break; + ENDFOR(mf); +} + +static void parseVideoInfo(MatroskaFile *mf,ulonglong toplen,struct TrackInfo *ti) { + ulonglong v; + char dW = 0, dH = 0; + + FOREACH(mf,toplen) + case 0x9a: // FlagInterlaced + ti->AV.Video.Interlaced = readUInt(mf,(unsigned)len)!=0; + break; + case 0x53b8: // StereoMode + v = readUInt(mf,(unsigned)len); + if (v>3) + errorjmp(mf,"Invalid stereo mode"); + ti->AV.Video.StereoMode = (unsigned char)v; + break; + case 0xb0: // PixelWidth + v = readUInt(mf,(unsigned)len); + if (v>0xffffffff) + errorjmp(mf,"PixelWidth is too large"); + ti->AV.Video.PixelWidth = (unsigned)v; + if (!dW) + ti->AV.Video.DisplayWidth = ti->AV.Video.PixelWidth; + break; + case 0xba: // PixelHeight + v = readUInt(mf,(unsigned)len); + if (v>0xffffffff) + errorjmp(mf,"PixelHeight is too large"); + ti->AV.Video.PixelHeight = (unsigned)v; + if (!dH) + ti->AV.Video.DisplayHeight = ti->AV.Video.PixelHeight; + break; + case 0x54b0: // DisplayWidth + v = readUInt(mf,(unsigned)len); + if (v>0xffffffff) + errorjmp(mf,"DisplayWidth is too large"); + ti->AV.Video.DisplayWidth = (unsigned)v; + dW = 1; + break; + case 0x54ba: // DisplayHeight + v = readUInt(mf,(unsigned)len); + if (v>0xffffffff) + errorjmp(mf,"DisplayHeight is too large"); + ti->AV.Video.DisplayHeight = (unsigned)v; + dH = 1; + break; + case 0x54b2: // DisplayUnit + v = readUInt(mf,(unsigned)len); + if (v>2) + errorjmp(mf,"Invalid DisplayUnit: %d",(int)v); + ti->AV.Video.DisplayUnit = (unsigned char)v; + break; + case 0x54b3: // AspectRatioType + v = readUInt(mf,(unsigned)len); + if (v>2) + errorjmp(mf,"Invalid AspectRatioType: %d",(int)v); + ti->AV.Video.AspectRatioType = (unsigned char)v; + break; + case 0x54aa: // PixelCropBottom + v = readUInt(mf,(unsigned)len); + if (v>0xffffffff) + errorjmp(mf,"PixelCropBottom is too large"); + ti->AV.Video.CropB = (unsigned)v; + break; + case 0x54bb: // PixelCropTop + v = readUInt(mf,(unsigned)len); + if (v>0xffffffff) + errorjmp(mf,"PixelCropTop is too large"); + ti->AV.Video.CropT = (unsigned)v; + break; + case 0x54cc: // PixelCropLeft + v = readUInt(mf,(unsigned)len); + if (v>0xffffffff) + errorjmp(mf,"PixelCropLeft is too large"); + ti->AV.Video.CropL = (unsigned)v; + break; + case 0x54dd: // PixelCropRight + v = readUInt(mf,(unsigned)len); + if (v>0xffffffff) + errorjmp(mf,"PixelCropRight is too large"); + ti->AV.Video.CropR = (unsigned)v; + break; + case 0x2eb524: // ColourSpace + ti->AV.Video.ColourSpace = (unsigned)readUInt(mf,4); + break; + case 0x2fb523: // GammaValue + ti->AV.Video.GammaValue = readFloat(mf,(unsigned)len); + break; + ENDFOR(mf); +} + +static void parseAudioInfo(MatroskaFile *mf,ulonglong toplen,struct TrackInfo *ti) { + ulonglong v; + + FOREACH(mf,toplen) + case 0xb5: // SamplingFrequency + ti->AV.Audio.SamplingFreq = readFloat(mf,(unsigned)len); + break; + case 0x78b5: // OutputSamplingFrequency + ti->AV.Audio.OutputSamplingFreq = readFloat(mf,(unsigned)len); + break; + case 0x9f: // Channels + v = readUInt(mf,(unsigned)len); + if (v<1 || v>255) + errorjmp(mf,"Invalid Channels value"); + ti->AV.Audio.Channels = (unsigned char)v; + break; + case 0x7d7b: // ChannelPositions + skipbytes(mf,len); + break; + case 0x6264: // BitDepth + v = readUInt(mf,(unsigned)len); +#if 0 + if ((v<1 || v>255) && !IsWritingApp(mf,"AVI-Mux GUI")) + errorjmp(mf,"Invalid BitDepth: %d",(int)v); +#endif + ti->AV.Audio.BitDepth = (unsigned char)v; + break; + ENDFOR(mf); + + if (ti->AV.Audio.Channels == 0) + ti->AV.Audio.Channels = 1; + if (mkv_TruncFloat(ti->AV.Audio.SamplingFreq) == 0) + ti->AV.Audio.SamplingFreq = mkfi(8000); + if (mkv_TruncFloat(ti->AV.Audio.OutputSamplingFreq)==0) + ti->AV.Audio.OutputSamplingFreq = ti->AV.Audio.SamplingFreq; +} + +static void CopyStr(char **src,char **dst) { + size_t l; + + if (!*src) + return; + + l = strlen(*src)+1; + memcpy(*dst,*src,l); + *src = *dst; + *dst += l; +} + +static void parseTrackEntry(MatroskaFile *mf,ulonglong toplen) { + struct TrackInfo t,*tp,**tpp; + ulonglong v; + char *cp = NULL, *cs = NULL; + size_t cplen = 0, cslen = 0, cpadd = 0; + unsigned CompScope, num_comp = 0; + + if (mf->nTracks >= MAX_TRACKS) + errorjmp(mf,"Too many tracks."); + + // clear track info + memset(&t,0,sizeof(t)); + + // fill default values + t.Enabled = 1; + t.Default = 1; + t.Lacing = 1; + t.TimecodeScale = mkfi(1); + t.DecodeAll = 1; + + FOREACH(mf,toplen) + case 0xd7: // TrackNumber + v = readUInt(mf,(unsigned)len); + if (v>255) + errorjmp(mf,"Track number is >255 (%d)",(int)v); + t.Number = (unsigned char)v; + break; + case 0x73c5: // TrackUID + t.UID = readUInt(mf,(unsigned)len); + break; + case 0x83: // TrackType + v = readUInt(mf,(unsigned)len); + if (v<1 || v>254) + errorjmp(mf,"Invalid track type: %d",(int)v); + t.Type = (unsigned char)v; + break; + case 0xb9: // Enabled + t.Enabled = readUInt(mf,(unsigned)len)!=0; + break; + case 0x88: // Default + t.Default = readUInt(mf,(unsigned)len)!=0; + break; + case 0x9c: // Lacing + t.Lacing = readUInt(mf,(unsigned)len)!=0; + break; + case 0x6de7: // MinCache + v = readUInt(mf,(unsigned)len); + if (v > 0xffffffff) + errorjmp(mf,"MinCache is too large"); + t.MinCache = (unsigned)v; + break; + case 0x6df8: // MaxCache + v = readUInt(mf,(unsigned)len); + if (v > 0xffffffff) + errorjmp(mf,"MaxCache is too large"); + t.MaxCache = (unsigned)v; + break; + case 0x23e383: // DefaultDuration + t.DefaultDuration = readUInt(mf,(unsigned)len); + break; + case 0x23314f: // TrackTimecodeScale + t.TimecodeScale = readFloat(mf,(unsigned)len); + break; + case 0x55ee: // MaxBlockAdditionID + t.MaxBlockAdditionID = (unsigned)readUInt(mf,(unsigned)len); + break; + case 0x536e: // Name + if (t.Name) + errorjmp(mf,"Duplicate Track Name"); + STRGETA(mf,t.Name,len); + break; + case 0x22b59c: // Language + readLangCC(mf, len, t.Language); + break; + case 0x86: // CodecID + if (t.CodecID) + errorjmp(mf,"Duplicate CodecID"); + STRGETA(mf,t.CodecID,len); + break; + case 0x63a2: // CodecPrivate + if (cp) + errorjmp(mf,"Duplicate CodecPrivate"); + if (len>262144) // 256KB + errorjmp(mf,"CodecPrivate is too large: %d",(int)len); + cplen = (unsigned)len; + cp = alloca(cplen); + readbytes(mf,cp,(int)cplen); + break; + case 0x258688: // CodecName + skipbytes(mf,len); + break; + case 0x3a9697: // CodecSettings + skipbytes(mf,len); + break; + case 0x3b4040: // CodecInfoURL + skipbytes(mf,len); + break; + case 0x26b240: // CodecDownloadURL + skipbytes(mf,len); + break; + case 0xaa: // CodecDecodeAll + t.DecodeAll = readUInt(mf,(unsigned)len)!=0; + break; + case 0x6fab: // TrackOverlay + v = readUInt(mf,(unsigned)len); + if (v>255) + errorjmp(mf,"Track number in TrackOverlay is too large: %d",(int)v); + t.TrackOverlay = (unsigned char)v; + break; + case 0xe0: // VideoInfo + parseVideoInfo(mf,len,&t); + break; + case 0xe1: // AudioInfo + parseAudioInfo(mf,len,&t); + break; + case 0x6d80: // ContentEncodings + FOREACH(mf,len) + case 0x6240: // ContentEncoding + // fill in defaults + t.CompEnabled = 1; + t.CompMethod = COMP_ZLIB; + CompScope = 1; + if (++num_comp > 1) + return; // only one compression layer supported + FOREACH(mf,len) + case 0x5031: // ContentEncodingOrder + readUInt(mf,(unsigned)len); + break; + case 0x5032: // ContentEncodingScope + CompScope = (unsigned)readUInt(mf,(unsigned)len); + break; + case 0x5033: // ContentEncodingType + if (readUInt(mf,(unsigned)len) != 0) + return; // encryption is not supported + break; + case 0x5034: // ContentCompression + FOREACH(mf,len) + case 0x4254: // ContentCompAlgo + v = readUInt(mf,(unsigned)len); + t.CompEnabled = 1; + switch (v) { + case 0: // Zlib + t.CompMethod = COMP_ZLIB; + break; + case 3: // prepend fixed data + t.CompMethod = COMP_PREPEND; + break; + default: + return; // unsupported compression, skip track + } + break; + case 0x4255: // ContentCompSettings + if (len > 256) + return; + cslen = (unsigned)len; + cs = alloca(cslen); + readbytes(mf, cs, (int)cslen); + break; + ENDFOR(mf); + break; + // TODO Implement Encryption/Signatures + ENDFOR(mf); + break; + ENDFOR(mf); + break; + ENDFOR(mf); + + // validate track info + if (!t.CodecID) + errorjmp(mf,"Track has no Codec ID"); + + if (t.UID != 0) { + unsigned i; + for (i = 0; i < mf->nTracks; ++i) + if (mf->Tracks[i]->UID == t.UID) // duplicate track entry + return; + } + +#ifdef MATROSKA_COMPRESSION_SUPPORT + // handle compressed CodecPrivate + if (t.CompEnabled && t.CompMethod == COMP_ZLIB && (CompScope & 2) && cplen > 0) { + z_stream zs; + char tmp[64], *ncp; + int code; + uLong ncplen; + + memset(&zs,0,sizeof(zs)); + if (inflateInit(&zs) != Z_OK) + errorjmp(mf, "inflateInit failed"); + + zs.next_in = cp; + zs.avail_in = cplen; + + do { + zs.next_out = tmp; + zs.avail_out = sizeof(tmp); + + code = inflate(&zs, Z_NO_FLUSH); + } while (code == Z_OK); + + if (code != Z_STREAM_END) + errorjmp(mf, "invalid compressed data in CodecPrivate"); + + ncplen = zs.total_out; + ncp = alloca(ncplen); + + inflateReset(&zs); + + zs.next_in = cp; + zs.avail_in = cplen; + zs.next_out = ncp; + zs.avail_out = ncplen; + + if (inflate(&zs, Z_FINISH) != Z_STREAM_END) + errorjmp(mf, "inflate failed"); + + inflateEnd(&zs); + + cp = ncp; + cplen = ncplen; + } +#endif + + if (t.CompEnabled && !(CompScope & 1)) { + t.CompEnabled = 0; + cslen = 0; + } + + // allocate new track + tpp = AGET(mf,Tracks); + + // copy strings + if (t.Name) + cpadd += strlen(t.Name)+1; + if (t.CodecID) + cpadd += strlen(t.CodecID)+1; + + tp = mf->cache->memalloc(mf->cache,sizeof(*tp) + cplen + cslen + cpadd); + if (tp == NULL) + errorjmp(mf,"Out of memory"); + + memcpy(tp,&t,sizeof(*tp)); + if (cplen) { + tp->CodecPrivate = tp+1; + tp->CodecPrivateSize = (unsigned)cplen; + memcpy(tp->CodecPrivate,cp,cplen); + } + if (cslen) { + tp->CompMethodPrivate = (char *)(tp+1) + cplen; + tp->CompMethodPrivateSize = cslen; + memcpy(tp->CompMethodPrivate, cs, cslen); + } + + cp = (char*)(tp+1) + cplen + cslen; + CopyStr(&tp->Name,&cp); + CopyStr(&tp->CodecID,&cp); + + // set default language + if (!tp->Language[0]) + memcpy(tp->Language, "eng", 4); + + *tpp = tp; +} + +static void parseTracks(MatroskaFile *mf,ulonglong toplen) { + mf->seen.Tracks = 1; + FOREACH(mf,toplen) + case 0xae: // TrackEntry + parseTrackEntry(mf,len); + break; + ENDFOR(mf); +} + +static void addCue(MatroskaFile *mf,ulonglong pos,ulonglong timecode) { + struct Cue *cc = AGET(mf,Cues); + cc->Time = timecode; + cc->Position = pos; + cc->Track = 0; + cc->Block = 0; +} + +static void fixupCues(MatroskaFile *mf) { + // adjust cues, shift cues if file does not start at 0 + unsigned i; + longlong adjust = mf->firstTimecode * mf->Seg.TimecodeScale; + + for (i=0;inCues;++i) { + mf->Cues[i].Time *= mf->Seg.TimecodeScale; + mf->Cues[i].Time -= adjust; + } +} + +static void parseCues(MatroskaFile *mf,ulonglong toplen) { + jmp_buf jb; + ulonglong v; + struct Cue cc; + unsigned i,j,k; + + mf->seen.Cues = 1; + mf->nCues = 0; + cc.Block = 0; + + memcpy(&jb,&mf->jb,sizeof(jb)); + + if (setjmp(mf->jb)) { + memcpy(&mf->jb,&jb,sizeof(jb)); + mf->nCues = 0; + mf->seen.Cues = 0; + return; + } + + FOREACH(mf,toplen) + case 0xbb: // CuePoint + FOREACH(mf,len) + case 0xb3: // CueTime + cc.Time = readUInt(mf,(unsigned)len); + break; + case 0xb7: // CueTrackPositions + FOREACH(mf,len) + case 0xf7: // CueTrack + v = readUInt(mf,(unsigned)len); + if (v>255) + errorjmp(mf,"CueTrack points to an invalid track: %d",(int)v); + cc.Track = (unsigned char)v; + break; + case 0xf1: // CueClusterPosition + cc.Position = readUInt(mf,(unsigned)len); + break; + case 0x5378: // CueBlockNumber + cc.Block = readUInt(mf,(unsigned)len); + break; + case 0xea: // CodecState + readUInt(mf,(unsigned)len); + break; + case 0xdb: // CueReference + FOREACH(mf,len) + case 0x96: // CueRefTime + readUInt(mf,(unsigned)len); + break; + case 0x97: // CueRefCluster + readUInt(mf,(unsigned)len); + break; + case 0x535f: // CueRefNumber + readUInt(mf,(unsigned)len); + break; + case 0xeb: // CueRefCodecState + readUInt(mf,(unsigned)len); + break; + ENDFOR(mf); + break; + ENDFOR(mf); + break; + ENDFOR(mf); + + if (mf->nCues == 0 && mf->pCluster - mf->pSegment != cc.Position) + addCue(mf,mf->pCluster - mf->pSegment,mf->firstTimecode); + + memcpy(AGET(mf,Cues),&cc,sizeof(cc)); + break; + ENDFOR(mf); + + memcpy(&mf->jb,&jb,sizeof(jb)); + + ARELEASE(mf,mf,Cues); + + // bubble sort the cues and fuck the losers that write unordered cues + if (mf->nCues > 0) + for (i = mf->nCues - 1, k = 1; i > 0 && k > 0; --i) + for (j = k = 0; j < i; ++j) + if (mf->Cues[j].Time > mf->Cues[j+1].Time) { + struct Cue tmp = mf->Cues[j+1]; + mf->Cues[j+1] = mf->Cues[j]; + mf->Cues[j] = tmp; + ++k; + } +} + +static void parseAttachment(MatroskaFile *mf,ulonglong toplen) { + struct Attachment a,*pa; + + memset(&a,0,sizeof(a)); + FOREACH(mf,toplen) + case 0x467e: // Description + STRGETA(mf,a.Description,len); + break; + case 0x466e: // Name + STRGETA(mf,a.Name,len); + break; + case 0x4660: // MimeType + STRGETA(mf,a.MimeType,len); + break; + case 0x46ae: // UID + a.UID = readUInt(mf,(unsigned)len); + break; + case 0x465c: // Data + a.Position = filepos(mf); + a.Length = len; + skipbytes(mf,len); + break; + ENDFOR(mf); + + if (!a.Position) + return; + + pa = AGET(mf,Attachments); + memcpy(pa,&a,sizeof(a)); + + if (a.Description) + pa->Description = mystrdup(mf->cache,a.Description); + if (a.Name) + pa->Name = mystrdup(mf->cache,a.Name); + if (a.MimeType) + pa->MimeType = mystrdup(mf->cache,a.MimeType); +} + +static void parseAttachments(MatroskaFile *mf,ulonglong toplen) { + mf->seen.Attachments = 1; + + FOREACH(mf,toplen) + case 0x61a7: // AttachedFile + parseAttachment(mf,len); + break; + ENDFOR(mf); +} + +static void parseChapter(MatroskaFile *mf,ulonglong toplen,struct Chapter *parent) { + struct ChapterDisplay *disp; + struct ChapterProcess *proc; + struct ChapterCommand *cmd; + struct Chapter *ch = ASGET(mf,parent,Children); + + memset(ch,0,sizeof(*ch)); + + ch->Enabled = 1; + + FOREACH(mf,toplen) + case 0x73c4: // ChapterUID + ch->UID = readUInt(mf,(unsigned)len); + break; + case 0x6e67: // ChapterSegmentUID + if (len != sizeof(ch->SegmentUID)) + skipbytes(mf, len); + else + readbytes(mf, ch->SegmentUID, sizeof(ch->SegmentUID)); + break; + case 0x91: // ChapterTimeStart + ch->Start = readUInt(mf,(unsigned)len); + break; + case 0x92: // ChapterTimeEnd + ch->End = readUInt(mf,(unsigned)len); + break; + case 0x98: // ChapterFlagHidden + ch->Hidden = readUInt(mf,(unsigned)len)!=0; + break; + case 0x4598: // ChapterFlagEnabled + ch->Enabled = readUInt(mf,(unsigned)len)!=0; + break; + case 0x8f: // ChapterTrack + FOREACH(mf,len) + case 0x89: // ChapterTrackNumber + *(ulonglong*)(ASGET(mf,ch,Tracks)) = readUInt(mf,(unsigned)len); + break; + ENDFOR(mf); + break; + case 0x80: // ChapterDisplay + disp = NULL; + + FOREACH(mf,len) + case 0x85: // ChapterString + if (disp==NULL) { + disp = ASGET(mf,ch,Display); + memset(disp, 0, sizeof(*disp)); + } + if (disp->String) + skipbytes(mf,len); // Ignore duplicate string + else + STRGETM(mf,disp->String,len); + break; + case 0x437c: // ChapterLanguage + if (disp==NULL) { + disp = ASGET(mf,ch,Display); + memset(disp, 0, sizeof(*disp)); + } + readLangCC(mf, len, disp->Language); + break; + case 0x437e: // ChapterCountry + if (disp==NULL) { + disp = ASGET(mf,ch,Display); + memset(disp, 0, sizeof(*disp)); + } + readLangCC(mf, len, disp->Country); + break; + ENDFOR(mf); + + if (disp && !disp->String) + --ch->nDisplay; + break; + case 0x6944: // ChapProcess + proc = NULL; + + FOREACH(mf,len) + case 0x6955: // ChapProcessCodecID + if (proc == NULL) { + proc = ASGET(mf, ch, Process); + memset(proc, 0, sizeof(*proc)); + } + proc->CodecID = (unsigned)readUInt(mf,(unsigned)len); + break; + case 0x450d: // ChapProcessPrivate + if (proc == NULL) { + proc = ASGET(mf, ch, Process); + memset(proc, 0, sizeof(*proc)); + } + if (proc->CodecPrivate) + skipbytes(mf, len); + else { + proc->CodecPrivateLength = (unsigned)len; + STRGETM(mf,proc->CodecPrivate,len); + } + break; + case 0x6911: // ChapProcessCommand + if (proc == NULL) { + proc = ASGET(mf, ch, Process); + memset(proc, 0, sizeof(*proc)); + } + + cmd = NULL; + + FOREACH(mf,len) + case 0x6922: // ChapterCommandTime + if (cmd == NULL) { + cmd = ASGET(mf,proc,Commands); + memset(cmd, 0, sizeof(*cmd)); + } + cmd->Time = (unsigned)readUInt(mf,(unsigned)len); + break; + case 0x6933: // ChapterCommandString + if (cmd == NULL) { + cmd = ASGET(mf,proc,Commands); + memset(cmd, 0, sizeof(*cmd)); + } + if (cmd->Command) + skipbytes(mf,len); + else { + cmd->CommandLength = (unsigned)len; + STRGETM(mf,cmd->Command,len); + } + break; + ENDFOR(mf); + + if (cmd && !cmd->Command) + --proc->nCommands; + break; + ENDFOR(mf); + + if (proc && !proc->nCommands) + --ch->nProcess; + break; + case 0xb6: // Nested ChapterAtom + parseChapter(mf,len,ch); + break; + ENDFOR(mf); + + ARELEASE(mf,ch,Tracks); + ARELEASE(mf,ch,Display); + ARELEASE(mf,ch,Children); +} + +static void parseChapters(MatroskaFile *mf,ulonglong toplen) { + struct Chapter *ch; + + mf->seen.Chapters = 1; + + FOREACH(mf,toplen) + case 0x45b9: // EditionEntry + ch = AGET(mf,Chapters); + memset(ch, 0, sizeof(*ch)); + FOREACH(mf,len) + case 0x45bc: // EditionUID + ch->UID = readUInt(mf,(unsigned)len); + break; + case 0x45bd: // EditionFlagHidden + ch->Hidden = readUInt(mf,(unsigned)len)!=0; + break; + case 0x45db: // EditionFlagDefault + ch->Default = readUInt(mf,(unsigned)len)!=0; + break; + case 0x45dd: // EditionFlagOrdered + ch->Ordered = readUInt(mf,(unsigned)len)!=0; + break; + case 0xb6: // ChapterAtom + parseChapter(mf,len,ch); + break; + ENDFOR(mf); + break; + ENDFOR(mf); +} + +static void parseTags(MatroskaFile *mf,ulonglong toplen) { + struct Tag *tag; + struct Target *target; + struct SimpleTag *st; + + mf->seen.Tags = 1; + + FOREACH(mf,toplen) + case 0x7373: // Tag + tag = AGET(mf,Tags); + memset(tag,0,sizeof(*tag)); + + FOREACH(mf,len) + case 0x63c0: // Targets + FOREACH(mf,len) + case 0x63c5: // TrackUID + target = ASGET(mf,tag,Targets); + target->UID = readUInt(mf,(unsigned)len); + target->Type = TARGET_TRACK; + break; + case 0x63c4: // ChapterUID + target = ASGET(mf,tag,Targets); + target->UID = readUInt(mf,(unsigned)len); + target->Type = TARGET_CHAPTER; + break; + case 0x63c6: // AttachmentUID + target = ASGET(mf,tag,Targets); + target->UID = readUInt(mf,(unsigned)len); + target->Type = TARGET_ATTACHMENT; + break; + case 0x63c9: // EditionUID + target = ASGET(mf,tag,Targets); + target->UID = readUInt(mf,(unsigned)len); + target->Type = TARGET_EDITION; + break; + ENDFOR(mf); + break; + case 0x67c8: // SimpleTag + st = ASGET(mf,tag,SimpleTags); + memset(st,0,sizeof(*st)); + + FOREACH(mf,len) + case 0x45a3: // TagName + if (st->Name) + skipbytes(mf,len); + else + STRGETM(mf,st->Name,len); + break; + case 0x4487: // TagString + if (st->Value) + skipbytes(mf,len); + else + STRGETM(mf,st->Value,len); + break; + case 0x447a: // TagLanguage + readLangCC(mf, len, st->Language); + break; + case 0x4484: // TagDefault + st->Default = readUInt(mf,(unsigned)len)!=0; + break; + ENDFOR(mf); + + if (!st->Name || !st->Value) { + mf->cache->memfree(mf->cache,st->Name); + mf->cache->memfree(mf->cache,st->Value); + --tag->nSimpleTags; + } + break; + ENDFOR(mf); + break; + ENDFOR(mf); +} + +static void parseContainer(MatroskaFile *mf) { + ulonglong len; + int id = readID(mf); + if (id==EOF) + errorjmp(mf,"Unexpected EOF in parseContainer"); + + len = readSize(mf); + + switch (id) { + case 0x1549a966: // SegmentInfo + parseSegmentInfo(mf,len); + break; + case 0x1f43b675: // Cluster + parseFirstCluster(mf,len); + break; + case 0x1654ae6b: // Tracks + parseTracks(mf,len); + break; + case 0x1c53bb6b: // Cues + parseCues(mf,len); + break; + case 0x1941a469: // Attachments + parseAttachments(mf,len); + break; + case 0x1043a770: // Chapters + parseChapters(mf,len); + break; + case 0x1254c367: // Tags + parseTags(mf,len); + break; + } +} + +static void parseContainerPos(MatroskaFile *mf,ulonglong pos) { + seek(mf,pos); + parseContainer(mf); +} + +static void parsePointers(MatroskaFile *mf) { + jmp_buf jb; + + if (mf->pSegmentInfo && !mf->seen.SegmentInfo) + parseContainerPos(mf,mf->pSegmentInfo); + if (mf->pCluster && !mf->seen.Cluster) + parseContainerPos(mf,mf->pCluster); + if (mf->pTracks && !mf->seen.Tracks) + parseContainerPos(mf,mf->pTracks); + + memcpy(&jb,&mf->jb,sizeof(jb)); + + if (setjmp(mf->jb)) + mf->flags &= ~MPF_ERROR; // ignore errors + else { + if (mf->pCues && !mf->seen.Cues) + parseContainerPos(mf,mf->pCues); + if (mf->pAttachments && !mf->seen.Attachments) + parseContainerPos(mf,mf->pAttachments); + if (mf->pChapters && !mf->seen.Chapters) + parseContainerPos(mf,mf->pChapters); + if (mf->pTags && !mf->seen.Tags) + parseContainerPos(mf,mf->pTags); + } + + memcpy(&mf->jb,&jb,sizeof(jb)); +} + +static void parseSegment(MatroskaFile *mf,ulonglong toplen) { + ulonglong nextpos; + unsigned nSeekHeads = 0, dontstop = 0; + + // we want to read data until we find a seekhead or a trackinfo + FOREACH(mf,toplen) + case 0x114d9b74: // SeekHead + if (mf->flags & MKVF_AVOID_SEEKS) { + skipbytes(mf,len); + break; + } + + nextpos = filepos(mf) + len; + do { + mf->pSeekHead = 0; + parseSeekHead(mf,len); + ++nSeekHeads; + if (mf->pSeekHead) { // this is possibly a chained SeekHead + seek(mf,mf->pSeekHead); + id = readID(mf); + if (id==EOF) // chained SeekHead points to EOF? + break; + if (id != 0x114d9b74) // chained SeekHead doesnt point to a SeekHead? + break; + len = readSize(mf); + } else if (mf->pSegmentInfo && mf->pTracks && mf->pCues && mf->pCluster) { // we have pointers to all key elements + // XXX EVIL HACK + // Some software doesnt index tags via SeekHead, so we continue + // reading the segment after the second SeekHead + if (mf->pTags || nSeekHeads<2 || filepos(mf)>=start+toplen) { + parsePointers(mf); + return; + } + // reset nextpos pointer to current position + nextpos = filepos(mf); + dontstop = 1; + } + } while (mf->pSeekHead); + seek(mf,nextpos); // resume reading segment + break; + case 0x1549a966: // SegmentInfo + mf->pSegmentInfo = cur; + parseSegmentInfo(mf,len); + break; + case 0x1f43b675: // Cluster + if (!mf->pCluster) + mf->pCluster = cur; + if (mf->seen.Cluster) + skipbytes(mf,len); + else + parseFirstCluster(mf,len); + break; + case 0x1654ae6b: // Tracks + mf->pTracks = cur; + parseTracks(mf,len); + break; + case 0x1c53bb6b: // Cues + mf->pCues = cur; + parseCues(mf,len); + break; + case 0x1941a469: // Attachments + mf->pAttachments = cur; + parseAttachments(mf,len); + break; + case 0x1043a770: // Chapters + mf->pChapters = cur; + parseChapters(mf,len); + break; + case 0x1254c367: // Tags + mf->pTags = cur; + parseTags(mf,len); + break; + ENDFOR1(mf); + // if we have pointers to all key elements + if (!dontstop && mf->pSegmentInfo && mf->pTracks && mf->pCluster) + break; + ENDFOR2(); + parsePointers(mf); +} + +static void parseBlockAdditions(MatroskaFile *mf, ulonglong toplen, ulonglong timecode, unsigned track) { + ulonglong add_id = 1, add_pos, add_len; + unsigned char have_add; + + FOREACH(mf, toplen) + case 0xa6: // BlockMore + have_add = 0; + FOREACH(mf, len) + case 0xee: // BlockAddId + add_id = readUInt(mf, (unsigned)len); + break; + case 0xa5: // BlockAddition + add_pos = filepos(mf); + add_len = len; + skipbytes(mf, len); + ++have_add; + break; + ENDFOR(mf); + if (have_add == 1 && id > 0 && id < 255) { + struct QueueEntry *qe = QAlloc(mf); + qe->Start = qe->End = timecode; + qe->Position = add_pos; + qe->Length = (unsigned)add_len; + qe->flags = FRAME_UNKNOWN_START | FRAME_UNKNOWN_END | + (((unsigned)add_id << FRAME_STREAM_SHIFT) & FRAME_STREAM_MASK); + + QPut(&mf->Queues[track],qe); + } + break; + ENDFOR(mf); +} + +static void parseBlockGroup(MatroskaFile *mf,ulonglong toplen,ulonglong timecode, int blockex) { + ulonglong v; + ulonglong duration = 0; + ulonglong dpos; + unsigned add_id = 0; + struct QueueEntry *qe,*qf = NULL; + unsigned char have_duration = 0, have_block = 0; + unsigned char gap = 0; + unsigned char lacing = 0; + unsigned char ref = 0; + unsigned char trackid; + unsigned tracknum = 0; + int c; + unsigned nframes = 0,i; + unsigned *sizes; + signed short block_timecode; + + if (blockex) + goto blockex; + + FOREACH(mf,toplen) + case 0xfb: // ReferenceBlock + readSInt(mf,(unsigned)len); + ref = 1; + break; +blockex: + cur = start = filepos(mf); + len = tmplen = toplen; + case 0xa1: // Block + have_block = 1; + + dpos = filepos(mf); + + v = readVLUInt(mf); + if (v>255) + errorjmp(mf,"Invalid track number in Block: %d",(int)v); + trackid = (unsigned char)v; + + for (tracknum=0;tracknumnTracks;++tracknum) + if (mf->Tracks[tracknum]->Number == trackid) { + if (mf->trackMask & (1<Tracks[tracknum]->TimecodeScale, + (timecode - mf->firstTimecode + block_timecode) * mf->Seg.TimecodeScale); + + c = readch(mf); + if (c==EOF) + errorjmp(mf,"Unexpected EOF while reading Block flags"); + + if (blockex) + ref = !(c & 0x80); + + gap = c & 0x1; + lacing = (c >> 1) & 3; + + if (lacing) { + c = readch(mf); + if (c == EOF) + errorjmp(mf,"Unexpected EOF while reading lacing data"); + nframes = c+1; + } else + nframes = 1; + sizes = alloca(nframes*sizeof(*sizes)); + + switch (lacing) { + case 0: // No lacing + sizes[0] = (unsigned)(len - filepos(mf) + dpos); + break; + case 1: // Xiph lacing + sizes[nframes-1] = 0; + for (i=0;i1) + sizes[nframes-1] = (unsigned)(len - filepos(mf) + dpos) - sizes[0] - sizes[nframes-1]; + break; + case 2: // Fixed lacing + sizes[0] = (unsigned)(len - filepos(mf) + dpos)/nframes; + for (i=1;iStart = timecode; + qe->End = timecode; + qe->Position = v; + qe->Length = sizes[i]; + qe->flags = FRAME_UNKNOWN_END | FRAME_KF; + if (i == nframes-1 && gap) + qe->flags |= FRAME_GAP; + if (i > 0) + qe->flags |= FRAME_UNKNOWN_START; + + QPut(&mf->Queues[tracknum],qe); + + v += sizes[i]; + } + + // we want to still load these bytes into cache + for (v = filepos(mf) & ~0x3fff; v < len + dpos; v += 0x4000) + mf->cache->read(mf->cache,v,NULL,0); // touch page + + skipbytes(mf,len - filepos(mf) + dpos); + + if (blockex) + goto out; + break; + case 0x9b: // BlockDuration + duration = readUInt(mf,(unsigned)len); + have_duration = 1; + break; + case 0x75a1: // BlockAdditions + if (nframes > 0) // have some frames + parseBlockAdditions(mf, len, timecode, tracknum); + else + skipbytes(mf, len); + break; + ENDFOR(mf); + +out: + if (!have_block) + errorjmp(mf,"Found a BlockGroup without Block"); + + if (nframes > 1) { + 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; + qe->next = mf->QFreeList; + mf->QFreeList = qe; + } + + q->head = NULL; + q->tail = NULL; +} + +static void EmptyQueues(MatroskaFile *mf) { + unsigned i; + + for (i=0;inTracks;++i) + ClearQueue(mf,&mf->Queues[i]); +} + +static int readMoreBlocks(MatroskaFile *mf) { + ulonglong toplen, cstop; + longlong cp; + int cid, ret = 0; + jmp_buf jb; + volatile unsigned retries = 0; + + if (mf->readPosition >= mf->pSegmentTop) + return EOF; + + memcpy(&jb,&mf->jb,sizeof(jb)); + + if (setjmp(mf->jb)) { // something evil happened here, try to resync + // always advance read position no matter what so + // we don't get caught in an endless loop + mf->readPosition = filepos(mf); + + ret = EOF; + + if (++retries > 3) // don't try too hard + goto ex; + + for (;;) { + if (filepos(mf) >= mf->pSegmentTop) + goto ex; + + cp = mf->cache->scan(mf->cache,filepos(mf),0x1f43b675); // cluster + + if (cp < 0 || (ulonglong)cp >= mf->pSegmentTop) + goto ex; + + seek(mf,cp); + + cid = readID(mf); + if (cid == EOF) + goto ex; + if (cid == 0x1f43b675) { + toplen = readSize(mf); + if (toplen < MAXCLUSTER) { + // reset error flags + mf->flags &= ~MPF_ERROR; + ret = RBRESYNC; + break; + } + } + } + + mf->readPosition = cp; + } + + cstop = mf->cache->getcachesize(mf->cache)>>1; + if (cstop > MAX_READAHEAD) + cstop = MAX_READAHEAD; + cstop += mf->readPosition; + + seek(mf,mf->readPosition); + + while (filepos(mf) < mf->pSegmentTop) { + cid = readID(mf); + if (cid == EOF) { + ret = EOF; + break; + } + toplen = readSize(mf); + + if (cid == 0x1f43b675) { // Cluster + unsigned char have_timecode = 0; + + FOREACH(mf,toplen) + case 0xe7: // Timecode + mf->tcCluster = readUInt(mf,(unsigned)len); + have_timecode = 1; + break; + case 0xa7: // Position + readUInt(mf,(unsigned)len); + break; + case 0xab: // PrevSize + readUInt(mf,(unsigned)len); + break; + case 0x5854: { // SilentTracks + unsigned stmask = 0, i, trk; + FOREACH(mf, len) + case 0x58d7: // SilentTrackNumber + trk = (unsigned)readUInt(mf, (unsigned)len); + for (i = 0; i < mf->nTracks; ++i) + if (mf->Tracks[i]->Number == trk) { + stmask |= 1 << i; + break; + } + break; + ENDFOR(mf); + // TODO pass stmask to reading app + break; } + case 0xa0: // BlockGroup + if (!have_timecode) + errorjmp(mf,"Found BlockGroup before cluster TimeCode"); + parseBlockGroup(mf,len,mf->tcCluster, 0); + goto out; + case 0xa3: // BlockEx + if (!have_timecode) + errorjmp(mf,"Found BlockGroup before cluster TimeCode"); + parseBlockGroup(mf, len, mf->tcCluster, 1); + goto out; + ENDFOR(mf); +out:; + } else { + if (toplen > MAXFRAME) + errorjmp(mf,"Element in a cluster is too large around %llu, %X [%u]",filepos(mf),cid,(unsigned)toplen); + if (cid == 0xa0) // BlockGroup + parseBlockGroup(mf,toplen,mf->tcCluster, 0); + else if (cid == 0xa3) // BlockEx + parseBlockGroup(mf, toplen, mf->tcCluster, 1); + else + skipbytes(mf,toplen); + } + + if ((mf->readPosition = filepos(mf)) > cstop) + break; + } + + mf->readPosition = filepos(mf); + +ex: + memcpy(&mf->jb,&jb,sizeof(jb)); + + return ret; +} + +// this is almost the same as readMoreBlocks, except it ensures +// there are no partial frames queued, however empty queues are ok +static int fillQueues(MatroskaFile *mf,unsigned int mask) { + unsigned i,j; + int ret = 0; + + for (;;) { + j = 0; + + for (i=0;inTracks;++i) + if (mf->Queues[i].head && !(mask & (1<0) // have at least some frames + return ret; + + if ((ret = readMoreBlocks(mf)) < 0) { + j = 0; + for (i=0;inTracks;++i) + if (mf->Queues[i].head && !(mask & (1<pCluster; + ulonglong step = 10*1024*1024; + ulonglong size, tc, isize; + longlong next_cluster; + int id, have_tc, bad; + struct Cue *cue; + + if (pos >= mf->pSegmentTop) + return; + + if (pos + step * 10 > mf->pSegmentTop) + step = (mf->pSegmentTop - pos) / 10; + if (step == 0) + step = 1; + + memcpy(&jb,&mf->jb,sizeof(jb)); + + // remove all cues + mf->nCues = 0; + + bad = 0; + + while (pos < mf->pSegmentTop) { + if (!mf->cache->progress(mf->cache,pos,mf->pSegmentTop)) + break; + + if (++bad > 50) { + pos += step; + bad = 0; + continue; + } + + // find next cluster header + next_cluster = mf->cache->scan(mf->cache,pos,0x1f43b675); // cluster + if (next_cluster < 0 || (ulonglong)next_cluster >= mf->pSegmentTop) + break; + + pos = next_cluster + 4; // prevent endless loops + + if (setjmp(mf->jb)) // something evil happened while reindexing + continue; + + seek(mf,next_cluster); + + id = readID(mf); + if (id == EOF) + break; + if (id != 0x1f43b675) // shouldn't happen + continue; + + size = readVLUInt(mf); + if (size >= MAXCLUSTER || size < 1024) + continue; + + have_tc = 0; + size += filepos(mf); + + while (filepos(mf) < (ulonglong)next_cluster + 1024) { + id = readID(mf); + if (id == EOF) + break; + + isize = readVLUInt(mf); + + if (id == 0xe7) { // cluster timecode + tc = readUInt(mf,(unsigned)isize); + have_tc = 1; + break; + } + + skipbytes(mf,isize); + } + + if (!have_tc) + continue; + + seek(mf,size); + id = readID(mf); + + if (id == EOF) + break; + + if (id != 0x1f43b675) // cluster + continue; + + // good cluster, remember it + cue = AGET(mf,Cues); + cue->Time = tc; + cue->Position = next_cluster - mf->pSegment; + cue->Block = 0; + cue->Track = 0; + + // advance to the next point + pos = next_cluster + step; + if (pos < size) + pos = size; + + bad = 0; + } + + fixupCues(mf); + + if (mf->nCues == 0) { + cue = AGET(mf,Cues); + cue->Time = mf->firstTimecode; + cue->Position = mf->pCluster - mf->pSegment; + cue->Block = 0; + cue->Track = 0; + } + + mf->cache->progress(mf->cache,0,0); + + memcpy(&mf->jb,&jb,sizeof(jb)); +} + +static void fixupChapter(ulonglong adj, struct Chapter *ch) { + unsigned i; + + if (ch->Start != 0) + ch->Start -= adj; + if (ch->End != 0) + ch->End -= adj; + + for (i=0;inChildren;++i) + fixupChapter(adj,&ch->Children[i]); +} + +static longlong findLastTimecode(MatroskaFile *mf) { + ulonglong nd = 0; + unsigned n,vtrack; + + if (mf->nTracks == 0) + return -1; + + for (n=vtrack=0;nnTracks;++n) + if (mf->Tracks[n]->Type == TT_VIDEO) { + vtrack = n; + goto ok; + } + + return -1; +ok: + + EmptyQueues(mf); + + if (mf->nCues == 0) { + mf->readPosition = mf->pCluster + 13000000 > mf->pSegmentTop ? mf->pCluster : mf->pSegmentTop - 13000000; + mf->tcCluster = 0; + } else { + mf->readPosition = mf->Cues[mf->nCues - 1].Position + mf->pSegment; + mf->tcCluster = mf->Cues[mf->nCues - 1].Time / mf->Seg.TimecodeScale; + } + mf->trackMask = ~(1 << vtrack); + + do + while (mf->Queues[vtrack].head) + { + ulonglong tc = mf->Queues[vtrack].head->flags & FRAME_UNKNOWN_END ? + mf->Queues[vtrack].head->Start : mf->Queues[vtrack].head->End; + if (nd < tc) + nd = tc; + QFree(mf,QGet(&mf->Queues[vtrack])); + } + while (fillQueues(mf,0) != EOF); + + mf->trackMask = 0; + + EmptyQueues(mf); + + // there may have been an error, but at this point we will ignore it + if (mf->flags & MPF_ERROR) { + mf->flags &= ~MPF_ERROR; + if (nd == 0) + return -1; + } + + return nd; +} + +static void parseFile(MatroskaFile *mf) { + ulonglong len = filepos(mf), adjust; + unsigned i; + int id = readID(mf); + int m; + + if (id==EOF) + errorjmp(mf,"Unexpected EOF at start of file"); + + // files with multiple concatenated segments can have only + // one EBML prolog + if (len > 0 && id == 0x18538067) + goto segment; + + if (id!=0x1a45dfa3) + errorjmp(mf,"First element in file is not EBML"); + + parseEBML(mf,readSize(mf)); + + // next we need to find the first segment + for (;;) { + id = readID(mf); + if (id==EOF) + errorjmp(mf,"No segments found in the file"); +segment: + len = readVLUIntImp(mf,&m); + // see if it's unspecified + if (len == (MAXU64 >> (57-m*7))) + len = MAXU64; + if (id == 0x18538067) // Segment + break; + skipbytes(mf,len); + } + + // found it + mf->pSegment = filepos(mf); + if (len == MAXU64) { + mf->pSegmentTop = MAXU64; + if (mf->cache->getfilesize) { + longlong seglen = mf->cache->getfilesize(mf->cache); + if (seglen > 0) + mf->pSegmentTop = seglen; + } + } else + mf->pSegmentTop = mf->pSegment + len; + parseSegment(mf,len); + + // check if we got all data + if (!mf->seen.SegmentInfo) + errorjmp(mf,"Couldn't find SegmentInfo"); + if (!mf->seen.Cluster) + mf->pCluster = mf->pSegmentTop; + + adjust = mf->firstTimecode * mf->Seg.TimecodeScale; + + for (i=0;inChapters;++i) + fixupChapter(adjust, &mf->Chapters[i]); + + fixupCues(mf); + + // release extra memory + ARELEASE(mf,mf,Tracks); + + // initialize reader + mf->Queues = mf->cache->memalloc(mf->cache,mf->nTracks * sizeof(*mf->Queues)); + if (mf->Queues == NULL) + errorjmp(mf, "Ouf of memory"); + memset(mf->Queues, 0, mf->nTracks * sizeof(*mf->Queues)); + + // try to detect real duration + if (!(mf->flags & MKVF_AVOID_SEEKS)) { + longlong nd = findLastTimecode(mf); + if (nd > 0) + mf->Seg.Duration = nd; + } + + // move to first frame + mf->readPosition = mf->pCluster; + mf->tcCluster = mf->firstTimecode; +} + +static void DeleteChapter(MatroskaFile *mf,struct Chapter *ch) { + unsigned i,j; + + for (i=0;inDisplay;++i) + mf->cache->memfree(mf->cache,ch->Display[i].String); + mf->cache->memfree(mf->cache,ch->Display); + mf->cache->memfree(mf->cache,ch->Tracks); + + for (i=0;inProcess;++i) { + for (j=0;jProcess[i].nCommands;++j) + mf->cache->memfree(mf->cache,ch->Process[i].Commands[j].Command); + mf->cache->memfree(mf->cache,ch->Process[i].Commands); + mf->cache->memfree(mf->cache,ch->Process[i].CodecPrivate); + } + mf->cache->memfree(mf->cache,ch->Process); + + for (i=0;inChildren;++i) + DeleteChapter(mf,&ch->Children[i]); + mf->cache->memfree(mf->cache,ch->Children); +} + +/////////////////////////////////////////////////////////////////////////// +// public interface +MatroskaFile *mkv_OpenEx(InputStream *io, + ulonglong base, + unsigned flags, + char *err_msg,unsigned msgsize) +{ + MatroskaFile *mf = io->memalloc(io,sizeof(*mf)); + if (mf == NULL) { + strlcpy(err_msg,"Out of memory",msgsize); + return NULL; + } + + memset(mf,0,sizeof(*mf)); + + mf->cache = io; + mf->flags = flags; + io->progress(io,0,0); + + if (setjmp(mf->jb)==0) { + seek(mf,base); + parseFile(mf); + } else { // parser error + strlcpy(err_msg,mf->errmsg,msgsize); + mkv_Close(mf); + return NULL; + } + + return mf; +} + +MatroskaFile *mkv_Open(InputStream *io, + char *err_msg,unsigned msgsize) +{ + return mkv_OpenEx(io,0,0,err_msg,msgsize); +} + +void mkv_Close(MatroskaFile *mf) { + unsigned i,j; + + if (mf==NULL) + return; + + for (i=0;inTracks;++i) + mf->cache->memfree(mf->cache,mf->Tracks[i]); + mf->cache->memfree(mf->cache,mf->Tracks); + + for (i=0;inQBlocks;++i) + mf->cache->memfree(mf->cache,mf->QBlocks[i]); + mf->cache->memfree(mf->cache,mf->QBlocks); + + mf->cache->memfree(mf->cache,mf->Queues); + + mf->cache->memfree(mf->cache,mf->Seg.Title); + mf->cache->memfree(mf->cache,mf->Seg.MuxingApp); + mf->cache->memfree(mf->cache,mf->Seg.WritingApp); + mf->cache->memfree(mf->cache,mf->Seg.Filename); + mf->cache->memfree(mf->cache,mf->Seg.NextFilename); + mf->cache->memfree(mf->cache,mf->Seg.PrevFilename); + + mf->cache->memfree(mf->cache,mf->Cues); + + for (i=0;inAttachments;++i) { + mf->cache->memfree(mf->cache,mf->Attachments[i].Description); + mf->cache->memfree(mf->cache,mf->Attachments[i].Name); + mf->cache->memfree(mf->cache,mf->Attachments[i].MimeType); + } + mf->cache->memfree(mf->cache,mf->Attachments); + + for (i=0;inChapters;++i) + DeleteChapter(mf,&mf->Chapters[i]); + mf->cache->memfree(mf->cache,mf->Chapters); + + for (i=0;inTags;++i) { + for (j=0;jTags[i].nSimpleTags;++j) { + mf->cache->memfree(mf->cache,mf->Tags[i].SimpleTags[j].Name); + mf->cache->memfree(mf->cache,mf->Tags[i].SimpleTags[j].Value); + } + mf->cache->memfree(mf->cache,mf->Tags[i].Targets); + mf->cache->memfree(mf->cache,mf->Tags[i].SimpleTags); + } + mf->cache->memfree(mf->cache,mf->Tags); + + mf->cache->memfree(mf->cache,mf); +} + +const char *mkv_GetLastError(MatroskaFile *mf) { + return mf->errmsg[0] ? mf->errmsg : NULL; +} + +SegmentInfo *mkv_GetFileInfo(MatroskaFile *mf) { + return &mf->Seg; +} + +unsigned int mkv_GetNumTracks(MatroskaFile *mf) { + return mf->nTracks; +} + +TrackInfo *mkv_GetTrackInfo(MatroskaFile *mf,unsigned track) { + if (track>mf->nTracks) + return NULL; + + return mf->Tracks[track]; +} + +void mkv_GetAttachments(MatroskaFile *mf,Attachment **at,unsigned *count) { + *at = mf->Attachments; + *count = mf->nAttachments; +} + +void mkv_GetChapters(MatroskaFile *mf,Chapter **ch,unsigned *count) { + *ch = mf->Chapters; + *count = mf->nChapters; +} + +void mkv_GetTags(MatroskaFile *mf,Tag **tag,unsigned *count) { + *tag = mf->Tags; + *count = mf->nTags; +} + +ulonglong mkv_GetSegmentTop(MatroskaFile *mf) { + return mf->pSegmentTop; +} + +#define IS_DELTA(f) (!((f)->flags & FRAME_KF) || ((f)->flags & FRAME_UNKNOWN_START)) + +void mkv_Seek(MatroskaFile *mf,ulonglong timecode,unsigned flags) { + int i,j,m,ret; + unsigned n,z,mask; + ulonglong m_kftime[MAX_TRACKS]; + unsigned char m_seendf[MAX_TRACKS]; + + if (mf->flags & MKVF_AVOID_SEEKS) + return; + + if (timecode == 0) { + EmptyQueues(mf); + mf->readPosition = mf->pCluster; + mf->tcCluster = mf->firstTimecode; + mf->flags &= ~MPF_ERROR; + + return; + } + + if (mf->nCues==0) + reindex(mf); + + if (mf->nCues==0) + return; + + mf->flags &= ~MPF_ERROR; + + i = 0; + j = mf->nCues - 1; + + for (;;) { + if (i>j) { + j = j>=0 ? j : 0; + + if (setjmp(mf->jb)!=0) + return; + + mkv_SetTrackMask(mf,mf->trackMask); + + if (flags & MKVF_SEEK_TO_PREV_KEYFRAME) { + // we do this in two stages + // a. find the last keyframes before the require position + // b. seek to them + + // pass 1 + for (;;) { + for (n=0;nnTracks;++n) { + m_kftime[n] = MAXU64; + m_seendf[n] = 0; + } + + EmptyQueues(mf); + + mf->readPosition = mf->Cues[j].Position + mf->pSegment; + mf->tcCluster = mf->Cues[j].Time / mf->Seg.TimecodeScale; + + for (;;) { + if ((ret = fillQueues(mf,0)) < 0 || ret == RBRESYNC) + return; + + // drain queues until we get to the required timecode + for (n=0;nnTracks;++n) { + if (mf->Queues[n].head && mf->Queues[n].head->StartQueues[n].head)) + m_seendf[n] = 1; + else + m_kftime[n] = mf->Queues[n].head->Start; + } + + while (mf->Queues[n].head && mf->Queues[n].head->StartQueues[n].head)) + m_seendf[n] = 1; + else + m_kftime[n] = mf->Queues[n].head->Start; + QFree(mf,QGet(&mf->Queues[n])); + } + + if (mf->Queues[n].head && (mf->Tracks[n]->Type != TT_AUDIO || mf->Queues[n].head->Start<=timecode)) + if (!IS_DELTA(mf->Queues[n].head)) + m_kftime[n] = mf->Queues[n].head->Start; + } + + for (n=0;nnTracks;++n) + if (mf->Queues[n].head && mf->Queues[n].head->Start>=timecode) + goto found; + } +found: + + for (n=0;nnTracks;++n) + if (!(mf->trackMask & (1<0) + { + // we need to restart the search from prev cue + --j; + goto again; + } + + break; +again:; + } + } else + for (n=0;nnTracks;++n) + m_kftime[n] = timecode; + + // now seek to this timecode + EmptyQueues(mf); + + mf->readPosition = mf->Cues[j].Position + mf->pSegment; + mf->tcCluster = mf->Cues[j].Time / mf->Seg.TimecodeScale; + + for (mask=0;;) { + if ((ret = fillQueues(mf,mask)) < 0 || ret == RBRESYNC) + return; + + // drain queues until we get to the required timecode + for (n=0;nnTracks;++n) { + struct QueueEntry *qe; + for (qe = mf->Queues[n].head;qe && qe->StartQueues[n].head) + QFree(mf,QGet(&mf->Queues[n])); + } + + for (n=z=0;nnTracks;++n) + if (m_kftime[n]==MAXU64 || (mf->Queues[n].head && mf->Queues[n].head->Start>=m_kftime[n])) { + ++z; + mask |= 1<nTracks) + return; + } + } + + m = (i+j)>>1; + + if (timecode < mf->Cues[m].Time) + j = m-1; + else + i = m+1; + } +} + +void mkv_SkipToKeyframe(MatroskaFile *mf) { + unsigned n,wait; + ulonglong ht; + + if (setjmp(mf->jb)!=0) + return; + + // remove delta frames from queues + do { + wait = 0; + + if (fillQueues(mf,0)<0) + return; + + for (n=0;nnTracks;++n) + if (mf->Queues[n].head && !(mf->Queues[n].head->flags & FRAME_KF)) { + ++wait; + QFree(mf,QGet(&mf->Queues[n])); + } + } while (wait); + + // find highest queued time + for (n=0,ht=0;nnTracks;++n) + if (mf->Queues[n].head && htQueues[n].head->Start) + ht = mf->Queues[n].head->Start; + + // ensure the time difference is less than 100ms + do { + wait = 0; + + if (fillQueues(mf,0)<0) + return; + + for (n=0;nnTracks;++n) + while (mf->Queues[n].head && mf->Queues[n].head->next && + (mf->Queues[n].head->next->flags & FRAME_KF) && + ht - mf->Queues[n].head->Start > 100000000) + { + ++wait; + QFree(mf,QGet(&mf->Queues[n])); + } + + } while (wait); +} + +ulonglong mkv_GetLowestQTimecode(MatroskaFile *mf) { + unsigned n,seen; + ulonglong t; + + // find the lowest queued timecode + for (n=seen=0,t=0;nnTracks;++n) + if (mf->Queues[n].head && (!seen || t > mf->Queues[n].head->Start)) + t = mf->Queues[n].head->Start, seen=1; + + return seen ? t : (ulonglong)LL(-1); +} + +int mkv_TruncFloat(MKFLOAT f) { +#ifdef MATROSKA_INTEGER_ONLY + return (int)(f.v >> 32); +#else + return (int)f; +#endif +} + +#define FTRACK 0xffffffff + +void mkv_SetTrackMask(MatroskaFile *mf,unsigned int mask) { + unsigned int i; + + if (mf->flags & MPF_ERROR) + return; + + mf->trackMask = mask; + + for (i=0;inTracks;++i) + if (mask & (1<Queues[i]); +} + +int mkv_ReadFrame(MatroskaFile *mf, + unsigned int mask,unsigned int *track, + ulonglong *StartTime,ulonglong *EndTime, + ulonglong *FilePos,unsigned int *FrameSize, + unsigned int *FrameFlags) +{ + unsigned int i,j; + struct QueueEntry *qe; + + if (setjmp(mf->jb)!=0) + return -1; + + do { + // extract required frame, use block with the lowest timecode + for (j=FTRACK,i=0;inTracks;++i) + if (!(mask & (1<Queues[i].head) { + j = i; + ++i; + break; + } + + for (;inTracks;++i) + if (!(mask & (1<Queues[i].head && + mf->Queues[j].head->Start > mf->Queues[i].head->Start) + j = i; + + if (j != FTRACK) { + qe = QGet(&mf->Queues[j]); + + *track = j; + *StartTime = qe->Start; + *EndTime = qe->End; + *FilePos = qe->Position; + *FrameSize = qe->Length; + *FrameFlags = qe->flags; + + QFree(mf,qe); + + return 0; + } + + if (mf->flags & MPF_ERROR) + return -1; + + } while (fillQueues(mf,mask)>=0); + + return EOF; +} + +#ifdef MATROSKA_COMPRESSION_SUPPORT +/************************************************************************* + * Compressed streams support + ************************************************************************/ +struct CompressedStream { + MatroskaFile *mf; + z_stream zs; + + /* current compressed frame */ + ulonglong frame_pos; + unsigned frame_size; + char frame_buffer[2048]; + + /* decoded data buffer */ + char decoded_buffer[2048]; + unsigned decoded_ptr; + unsigned decoded_size; + + /* error handling */ + char errmsg[128]; +}; + +CompressedStream *cs_Create(/* in */ MatroskaFile *mf, + /* in */ unsigned tracknum, + /* out */ char *errormsg, + /* in */ unsigned msgsize) +{ + CompressedStream *cs; + TrackInfo *ti; + int code; + + ti = mkv_GetTrackInfo(mf, tracknum); + if (ti == NULL) { + strlcpy(errormsg, "No such track.", msgsize); + return NULL; + } + + if (!ti->CompEnabled) { + strlcpy(errormsg, "Track is not compressed.", msgsize); + return NULL; + } + + if (ti->CompMethod != COMP_ZLIB) { + strlcpy(errormsg, "Unsupported compression method.", msgsize); + return NULL; + } + + cs = mf->cache->memalloc(mf->cache,sizeof(*cs)); + if (cs == NULL) { + strlcpy(errormsg, "Ouf of memory.", msgsize); + return NULL; + } + + memset(&cs->zs,0,sizeof(cs->zs)); + code = inflateInit(&cs->zs); + if (code != Z_OK) { + strlcpy(errormsg, "ZLib error.", msgsize); + mf->cache->memfree(mf->cache,cs); + return NULL; + } + + cs->frame_size = 0; + cs->decoded_ptr = cs->decoded_size = 0; + cs->mf = mf; + + return cs; +} + +void cs_Destroy(/* in */ CompressedStream *cs) { + if (cs == NULL) + return; + + inflateEnd(&cs->zs); + cs->mf->cache->memfree(cs->mf->cache,cs); +} + +/* advance to the next frame in matroska stream, you need to pass values returned + * by mkv_ReadFrame */ +void cs_NextFrame(/* in */ CompressedStream *cs, + /* in */ ulonglong pos, + /* in */ unsigned size) +{ + cs->zs.avail_in = 0; + inflateReset(&cs->zs); + cs->frame_pos = pos; + cs->frame_size = size; + cs->decoded_ptr = cs->decoded_size = 0; +} + +/* read and decode more data from current frame, return number of bytes decoded, + * 0 on end of frame, or -1 on error */ +int cs_ReadData(CompressedStream *cs,char *buffer,unsigned bufsize) +{ + char *cp = buffer; + unsigned rd = 0; + unsigned todo; + int code; + + do { + /* try to copy data from decoded buffer */ + if (cs->decoded_ptr < cs->decoded_size) { + todo = cs->decoded_size - cs->decoded_ptr;; + if (todo > bufsize - rd) + todo = bufsize - rd; + + memcpy(cp, cs->decoded_buffer + cs->decoded_ptr, todo); + + rd += todo; + cp += todo; + cs->decoded_ptr += todo; + } else { + /* setup output buffer */ + cs->zs.next_out = cs->decoded_buffer; + cs->zs.avail_out = sizeof(cs->decoded_buffer); + + /* try to read more data */ + if (cs->zs.avail_in == 0 && cs->frame_size > 0) { + todo = cs->frame_size; + if (todo > sizeof(cs->frame_buffer)) + todo = sizeof(cs->frame_buffer); + + if (cs->mf->cache->read(cs->mf->cache, cs->frame_pos, cs->frame_buffer, todo) != (int)todo) { + strlcpy(cs->errmsg, "File read failed", sizeof(cs->errmsg)); + return -1; + } + + cs->zs.next_in = cs->frame_buffer; + cs->zs.avail_in = todo; + + cs->frame_pos += todo; + cs->frame_size -= todo; + } + + /* try to decode more data */ + code = inflate(&cs->zs,Z_NO_FLUSH); + if (code != Z_OK && code != Z_STREAM_END) { + strlcpy(cs->errmsg, "ZLib error.", sizeof(cs->errmsg)); + return -1; + } + + /* handle decoded data */ + if (cs->zs.avail_out == sizeof(cs->decoded_buffer)) /* EOF */ + break; + + cs->decoded_ptr = 0; + cs->decoded_size = sizeof(cs->decoded_buffer) - cs->zs.avail_out; + } + } while (rd < bufsize); + + return rd; +} + +/* return error message for the last error */ +const char *cs_GetLastError(CompressedStream *cs) +{ + if (!cs->errmsg[0]) + return NULL; + return cs->errmsg; +} +#endif diff --git a/FFmpegSource/MatroskaParser.h b/FFmpegSource/MatroskaParser.h new file mode 100644 index 000000000..53dc49918 --- /dev/null +++ b/FFmpegSource/MatroskaParser.h @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2004-2006 Mike Matsnev. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice immediately at the beginning of the file, without modification, + * this list of conditions, and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Absolutely no warranty of function or purpose is made by the author + * Mike Matsnev. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $Id: MatroskaParser.h,v 1.19 2006/03/11 10:57:13 mike Exp $ + * + */ + +#ifndef MATROSKA_PARSER_H +#define MATROSKA_PARSER_H + +/* Random notes: + * + * The parser does not process frame data in any way and does not read it into + * the queue. The app should read it via mkv_ReadData if it is interested. + * + * The code here is 64-bit clean and was tested on FreeBSD/sparc 64-bit big endian + * system + */ + +#ifdef MPDLLBUILD +#define X __declspec(dllexport) +#else +#ifdef MPDLL +#define X __declspec(dllimport) +#pragma comment(lib,"MatroskaParser") +#else +#define X +#endif +#endif + +#define MATROSKA_COMPRESSION_SUPPORT +#define MATROSKA_INTEGER_ONLY + +#ifdef __cplusplus +extern "C" { +#endif + +/* 64-bit integers */ +#ifdef _WIN32_WCE +typedef signed __int64 longlong; +typedef unsigned __int64 ulonglong; +#else +typedef signed long long longlong; +typedef unsigned long long ulonglong; +#endif + +/* MKFLOATing point */ +#ifdef MATROSKA_INTEGER_ONLY +typedef struct { + longlong v; +} MKFLOAT; +#else +typedef double MKFLOAT; +#endif + +/* generic I/O */ +struct InputStream { + /* read bytes from stream */ + int (*read)(struct InputStream *cc,ulonglong pos,void *buffer,int count); + /* scan for a four byte signature, bytes must be nonzero */ + longlong (*scan)(struct InputStream *cc,ulonglong start,unsigned signature); + /* get cache size, this is used to cap readahead */ + unsigned (*getcachesize)(struct InputStream *cc); + /* fetch last error message */ + const char *(*geterror)(struct InputStream *cc); + /* memory allocation */ + void *(*memalloc)(struct InputStream *cc,size_t size); + void *(*memrealloc)(struct InputStream *cc,void *mem,size_t newsize); + void (*memfree)(struct InputStream *cc,void *mem); + /* zero return causes parser to abort open */ + int (*progress)(struct InputStream *cc,ulonglong cur,ulonglong max); + /* get file size, optional, can be NULL or return -1 if filesize is unknown */ + longlong (*getfilesize)(struct InputStream *cc); +}; + +typedef struct InputStream InputStream; + +/* matroska file */ +struct MatroskaFile; /* opaque */ + +typedef struct MatroskaFile MatroskaFile; + +#define COMP_ZLIB 0 +#define COMP_BZIP 1 +#define COMP_LZO1X 2 +#define COMP_PREPEND 3 + +#define TT_VIDEO 1 +#define TT_AUDIO 2 +#define TT_SUB 17 + +struct TrackInfo { + unsigned char Number; + unsigned char Type; + unsigned char TrackOverlay; + ulonglong UID; + ulonglong MinCache; + ulonglong MaxCache; + ulonglong DefaultDuration; + MKFLOAT TimecodeScale; + void *CodecPrivate; + unsigned CodecPrivateSize; + unsigned CompMethod; + void *CompMethodPrivate; + unsigned CompMethodPrivateSize; + unsigned MaxBlockAdditionID; + struct { + unsigned int Enabled:1; + unsigned int Default:1; + unsigned int Lacing:1; + unsigned int DecodeAll:1; + unsigned int CompEnabled:1; + }; + + union { + struct { + unsigned char StereoMode; + unsigned char DisplayUnit; + unsigned char AspectRatioType; + unsigned int PixelWidth; + unsigned int PixelHeight; + unsigned int DisplayWidth; + unsigned int DisplayHeight; + unsigned int CropL, CropT, CropR, CropB; + unsigned int ColourSpace; + MKFLOAT GammaValue; + struct { + unsigned int Interlaced:1; + }; + } Video; + struct { + MKFLOAT SamplingFreq; + MKFLOAT OutputSamplingFreq; + unsigned char Channels; + unsigned char BitDepth; + } Audio; + } AV; + + /* various strings */ + char *Name; + char Language[4]; + char *CodecID; +}; + +typedef struct TrackInfo TrackInfo; + +struct SegmentInfo { + char UID[16]; + char PrevUID[16]; + char NextUID[16]; + char *Filename; + char *PrevFilename; + char *NextFilename; + char *Title; + char *MuxingApp; + char *WritingApp; + ulonglong TimecodeScale; + ulonglong Duration; + 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]; + + struct { + unsigned int Hidden:1; + unsigned int Enabled:1; + + // Editions + unsigned int Default:1; + unsigned int Ordered:1; + }; +}; + +typedef struct Chapter Chapter; + +#define TARGET_TRACK 0 +#define TARGET_CHAPTER 1 +#define TARGET_ATTACHMENT 2 +#define TARGET_EDITION 3 +struct Target { + ulonglong UID; + unsigned Type; +}; + +struct SimpleTag { + char *Name; + char *Value; + char Language[4]; + unsigned Default:1; +}; + +struct Tag { + unsigned nTargets,nTargetsSize; + struct Target *Targets; + + unsigned nSimpleTags,nSimpleTagsSize; + struct SimpleTag *SimpleTags; +}; + +typedef struct Tag Tag; + +/* Open a matroska file + * io pointer is recorded inside MatroskaFile + */ +X MatroskaFile *mkv_Open(/* in */ InputStream *io, + /* out */ char *err_msg, + /* in */ unsigned msgsize); + +#define MKVF_AVOID_SEEKS 1 /* use sequential reading only */ + +X MatroskaFile *mkv_OpenEx(/* in */ InputStream *io, + /* in */ ulonglong base, + /* in */ unsigned flags, + /* out */ char *err_msg, + /* in */ unsigned msgsize); + +/* Close and deallocate mf + * NULL pointer is ok and is simply ignored + */ +X void mkv_Close(/* in */ MatroskaFile *mf); + +/* Fetch the error message of the last failed operation */ +X const char *mkv_GetLastError(/* in */ MatroskaFile *mf); + +/* Get file information */ +X SegmentInfo *mkv_GetFileInfo(/* in */ MatroskaFile *mf); + +/* Get track information */ +X unsigned int mkv_GetNumTracks(/* in */ MatroskaFile *mf); +X TrackInfo *mkv_GetTrackInfo(/* in */ MatroskaFile *mf,/* in */ unsigned track); + +/* chapters, tags and attachments */ +X void mkv_GetAttachments(/* in */ MatroskaFile *mf, + /* out */ Attachment **at, + /* out */ unsigned *count); +X void mkv_GetChapters(/* in */ MatroskaFile *mf, + /* out */ Chapter **ch, + /* out */ unsigned *count); +X void mkv_GetTags(/* in */ MatroskaFile *mf, + /* out */ Tag **tag, + /* out */ unsigned *count); + +X ulonglong mkv_GetSegmentTop(MatroskaFile *mf); + +/* Seek to specified timecode, + * if timecode is past end of file, + * all tracks are set to return EOF + * on next read + */ +#define MKVF_SEEK_TO_PREV_KEYFRAME 1 + +X void mkv_Seek(/* in */ MatroskaFile *mf, + /* in */ ulonglong timecode /* in ns */, + /* in */ unsigned flags); + +X void mkv_SkipToKeyframe(MatroskaFile *mf); + +X ulonglong mkv_GetLowestQTimecode(MatroskaFile *mf); + +X int mkv_TruncFloat(MKFLOAT f); + +/************************************************************************* + * reading data, pull model + */ + +/* frame flags */ +#define FRAME_UNKNOWN_START 0x00000001 +#define FRAME_UNKNOWN_END 0x00000002 +#define FRAME_KF 0x00000004 +#define FRAME_GAP 0x00800000 +#define FRAME_STREAM_MASK 0xff000000 +#define FRAME_STREAM_SHIFT 24 + +/* This sets the masking flags for the parser, + * masked tracks [with 1s in their bit positions] + * will be ignored when reading file data. + * This call discards all parsed and queued frames + */ +X void mkv_SetTrackMask(/* in */ MatroskaFile *mf,/* in */ unsigned int mask); + +/* Read one frame from the queue. + * mask specifies what tracks to ignore. + * Returns -1 if there are no more frames in the specified + * set of tracks, 0 on success + */ +X int mkv_ReadFrame(/* in */ MatroskaFile *mf, + /* in */ unsigned int mask, + /* out */ unsigned int *track, + /* out */ ulonglong *StartTime /* in ns */, + /* out */ ulonglong *EndTime /* in ns */, + /* out */ ulonglong *FilePos /* in bytes from start of file */, + /* out */ unsigned int *FrameSize /* in bytes */, + /* out */ unsigned int *FrameFlags); + +#ifdef MATROSKA_COMPRESSION_SUPPORT +/* Compressed streams support */ +struct CompressedStream; + +typedef struct CompressedStream CompressedStream; + +X CompressedStream *cs_Create(/* in */ MatroskaFile *mf, + /* in */ unsigned tracknum, + /* out */ char *errormsg, + /* in */ unsigned msgsize); +X void cs_Destroy(/* in */ CompressedStream *cs); + +/* advance to the next frame in matroska stream, you need to pass values returned + * by mkv_ReadFrame */ +X void cs_NextFrame(/* in */ CompressedStream *cs, + /* in */ ulonglong pos, + /* in */ unsigned size); + +/* read and decode more data from current frame, return number of bytes decoded, + * 0 on end of frame, or -1 on error */ +X int cs_ReadData(CompressedStream *cs,char *buffer,unsigned bufsize); + +/* return error message for the last error */ +X const char *cs_GetLastError(CompressedStream *cs); +#endif + +#ifdef __cplusplus +} +#endif + +#undef X + +#endif diff --git a/FFmpegSource/avisynth.h b/FFmpegSource/avisynth.h new file mode 100644 index 000000000..54aff1cdf --- /dev/null +++ b/FFmpegSource/avisynth.h @@ -0,0 +1,749 @@ +// Avisynth v2.5. Copyright 2002 Ben Rudiak-Gould et al. +// http://www.avisynth.org + +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA, or visit +// http://www.gnu.org/copyleft/gpl.html . +// +// Linking Avisynth statically or dynamically with other modules is making a +// combined work based on Avisynth. Thus, the terms and conditions of the GNU +// General Public License cover the whole combination. +// +// As a special exception, the copyright holders of Avisynth give you +// permission to link Avisynth with independent modules that communicate with +// Avisynth solely through the interfaces defined in avisynth.h, regardless of the license +// terms of these independent modules, and to copy and distribute the +// resulting combined work under terms of your choice, provided that +// every copy of the combined work is accompanied by a complete copy of +// the source code of Avisynth (the version of Avisynth used to produce the +// combined work), being distributed under the terms of the GNU General +// Public License plus this exception. An independent module is a module +// which is not derived from or based on Avisynth, such as 3rd-party filters, +// import and export plugins, or graphical user interfaces. + + + + + +#ifndef __AVISYNTH_H__ +#define __AVISYNTH_H__ + +enum { AVISYNTH_INTERFACE_VERSION = 3 }; + + +/* Define all types necessary for interfacing with avisynth.dll + Moved from internal.h */ + +// Win32 API macros, notably the types BYTE, DWORD, ULONG, etc. +#include + +// COM interface macros +#include + + +// Raster types used by VirtualDub & Avisynth +#define in64 (__int64)(unsigned short) +typedef unsigned long Pixel; // this will break on 64-bit machines! +typedef unsigned long Pixel32; +typedef unsigned char Pixel8; +typedef long PixCoord; +typedef long PixDim; +typedef long PixOffset; + + +/* Compiler-specific crap */ + +// Tell MSVC to stop precompiling here +#ifdef _MSC_VER + #pragma hdrstop +#endif + +// Set up debugging macros for MS compilers; for others, step down to the +// standard interface +#ifdef _MSC_VER + #include +#else + #define _RPT0(a,b) ((void)0) + #define _RPT1(a,b,c) ((void)0) + #define _RPT2(a,b,c,d) ((void)0) + #define _RPT3(a,b,c,d,e) ((void)0) + #define _RPT4(a,b,c,d,e,f) ((void)0) + + #define _ASSERTE(x) assert(x) + #include +#endif + + + +// I had problems with Premiere wanting 1-byte alignment for its structures, +// so I now set the Avisynth struct alignment explicitly here. +#pragma pack(push,8) + +#define FRAME_ALIGN 16 +// Default frame alignment is 16 bytes, to help P4, when using SSE2 + +// The VideoInfo struct holds global information about a clip (i.e. +// information that does not depend on the frame number). The GetVideoInfo +// method in IClip returns this struct. + +// Audio Sample information +typedef float SFLOAT; + +enum {SAMPLE_INT8 = 1<<0, + SAMPLE_INT16 = 1<<1, + SAMPLE_INT24 = 1<<2, // Int24 is a very stupid thing to code, but it's supported by some hardware. + SAMPLE_INT32 = 1<<3, + SAMPLE_FLOAT = 1<<4}; + +enum { + PLANAR_Y=1<<0, + PLANAR_U=1<<1, + PLANAR_V=1<<2, + PLANAR_ALIGNED=1<<3, + PLANAR_Y_ALIGNED=PLANAR_Y|PLANAR_ALIGNED, + PLANAR_U_ALIGNED=PLANAR_U|PLANAR_ALIGNED, + PLANAR_V_ALIGNED=PLANAR_V|PLANAR_ALIGNED, + }; + +struct VideoInfo { + int width, height; // width=0 means no video + unsigned fps_numerator, fps_denominator; + int num_frames; + // This is more extensible than previous versions. More properties can be added seeminglesly. + + // Colorspace properties. + enum { + CS_BGR = 1<<28, + CS_YUV = 1<<29, + CS_INTERLEAVED = 1<<30, + CS_PLANAR = 1<<31 + }; + + // Specific colorformats + enum { CS_UNKNOWN = 0, + CS_BGR24 = 1<<0 | CS_BGR | CS_INTERLEAVED, + CS_BGR32 = 1<<1 | CS_BGR | CS_INTERLEAVED, + CS_YUY2 = 1<<2 | CS_YUV | CS_INTERLEAVED, + CS_YV12 = 1<<3 | CS_YUV | CS_PLANAR, // y-v-u, planar + CS_I420 = 1<<4 | CS_YUV | CS_PLANAR, // y-u-v, planar + CS_IYUV = 1<<4 | CS_YUV | CS_PLANAR // same as above + }; + int pixel_type; // changed to int as of 2.5 + + + int audio_samples_per_second; // 0 means no audio + int sample_type; // as of 2.5 + __int64 num_audio_samples; // changed as of 2.5 + int nchannels; // as of 2.5 + + // Imagetype properties + + int image_type; + + enum { + IT_BFF = 1<<0, + IT_TFF = 1<<1, + IT_FIELDBASED = 1<<2 + }; + + // useful functions of the above + bool HasVideo() const { return (width!=0); } + bool HasAudio() const { return (audio_samples_per_second!=0); } + bool IsRGB() const { return !!(pixel_type&CS_BGR); } + bool IsRGB24() const { return (pixel_type&CS_BGR24)==CS_BGR24; } // Clear out additional properties + bool IsRGB32() const { return (pixel_type & CS_BGR32) == CS_BGR32 ; } + bool IsYUV() const { return !!(pixel_type&CS_YUV ); } + bool IsYUY2() const { return (pixel_type & CS_YUY2) == CS_YUY2; } + bool IsYV12() const { return ((pixel_type & CS_YV12) == CS_YV12)||((pixel_type & CS_I420) == CS_I420); } + bool IsColorSpace(int c_space) const { return ((pixel_type & c_space) == c_space); } + bool Is(int property) const { return ((pixel_type & property)==property ); } + bool IsPlanar() const { return !!(pixel_type & CS_PLANAR); } + bool IsFieldBased() const { return !!(image_type & IT_FIELDBASED); } + bool IsParityKnown() const { return ((image_type & IT_FIELDBASED)&&(image_type & (IT_BFF|IT_TFF))); } + bool IsBFF() const { return !!(image_type & IT_BFF); } + bool IsTFF() const { return !!(image_type & IT_TFF); } + + bool IsVPlaneFirst() const {return ((pixel_type & CS_YV12) == CS_YV12); } // Don't use this + int BytesFromPixels(int pixels) const { return pixels * (BitsPerPixel()>>3); } // Will not work on planar images, but will return only luma planes + int RowSize() const { return BytesFromPixels(width); } // Also only returns first plane on planar images + int BMPSize() const { if (IsPlanar()) {int p = height * ((RowSize()+3) & ~3); p+=p>>1; return p; } return height * ((RowSize()+3) & ~3); } + __int64 AudioSamplesFromFrames(__int64 frames) const { return (fps_numerator && HasVideo()) ? ((__int64)(frames) * audio_samples_per_second * fps_denominator / fps_numerator) : 0; } + int FramesFromAudioSamples(__int64 samples) const { return (fps_denominator && HasAudio()) ? (int)((samples * (__int64)fps_numerator)/((__int64)fps_denominator * (__int64)audio_samples_per_second)) : 0; } + __int64 AudioSamplesFromBytes(__int64 bytes) const { return HasAudio() ? bytes / BytesPerAudioSample() : 0; } + __int64 BytesFromAudioSamples(__int64 samples) const { return samples * BytesPerAudioSample(); } + int AudioChannels() const { return HasAudio() ? nchannels : 0; } + int SampleType() const{ return sample_type;} + bool IsSampleType(int testtype) const{ return !!(sample_type&testtype);} + int SamplesPerSecond() const { return audio_samples_per_second; } + int BytesPerAudioSample() const { return nchannels*BytesPerChannelSample();} + void SetFieldBased(bool isfieldbased) { if (isfieldbased) image_type|=IT_FIELDBASED; else image_type&=~IT_FIELDBASED; } + void Set(int property) { image_type|=property; } + void Clear(int property) { image_type&=~property; } + + int BitsPerPixel() const { + switch (pixel_type) { + case CS_BGR24: + return 24; + case CS_BGR32: + return 32; + case CS_YUY2: + return 16; + case CS_YV12: + case CS_I420: + return 12; + default: + return 0; + } + } + int BytesPerChannelSample() const { + switch (sample_type) { + case SAMPLE_INT8: + return sizeof(signed char); + case SAMPLE_INT16: + return sizeof(signed short); + case SAMPLE_INT24: + return 3; + case SAMPLE_INT32: + return sizeof(signed int); + case SAMPLE_FLOAT: + return sizeof(SFLOAT); + default: + _ASSERTE("Sample type not recognized!"); + return 0; + } + } + + // useful mutator + void SetFPS(unsigned numerator, unsigned denominator) { + if ((numerator == 0) || (denominator == 0)) { + fps_numerator = 0; + fps_denominator = 1; + } + else { + unsigned x=numerator, y=denominator; + while (y) { // find gcd + unsigned t = x%y; x = y; y = t; + } + fps_numerator = numerator/x; + fps_denominator = denominator/x; + } + } + + // Range protected multiply-divide of FPS + void MulDivFPS(unsigned multiplier, unsigned divisor) { + unsigned __int64 numerator = UInt32x32To64(fps_numerator, multiplier); + unsigned __int64 denominator = UInt32x32To64(fps_denominator, divisor); + + unsigned __int64 x=numerator, y=denominator; + while (y) { // find gcd + unsigned __int64 t = x%y; x = y; y = t; + } + numerator /= x; // normalize + denominator /= x; + + unsigned __int64 temp = numerator | denominator; // Just looking top bit + unsigned u = 0; + while (temp & 0xffffffff80000000) { // or perhaps > 16777216*2 + temp = Int64ShrlMod32(temp, 1); + u++; + } + if (u) { // Scale to fit + const unsigned round = 1 << (u-1); + SetFPS( (unsigned)Int64ShrlMod32(numerator + round, u), + (unsigned)Int64ShrlMod32(denominator + round, u) ); + } + else { + fps_numerator = (unsigned)numerator; + fps_denominator = (unsigned)denominator; + } + } + + // Test for same colorspace + bool IsSameColorspace(const VideoInfo& vi) const { + if (vi.pixel_type == pixel_type) return TRUE; + if (IsYV12() && vi.IsYV12()) return TRUE; + return FALSE; + } + +}; + + + + +// VideoFrameBuffer holds information about a memory block which is used +// for video data. For efficiency, instances of this class are not deleted +// when the refcount reaches zero; instead they're stored in a linked list +// to be reused. The instances are deleted when the corresponding AVS +// file is closed. + +class VideoFrameBuffer { + BYTE* const data; + const int data_size; + // sequence_number is incremented every time the buffer is changed, so + // that stale views can tell they're no longer valid. + long sequence_number; + + friend class VideoFrame; + friend class Cache; + friend class ScriptEnvironment; + long refcount; + +public: + VideoFrameBuffer(int size); + VideoFrameBuffer(); + ~VideoFrameBuffer(); + + const BYTE* GetReadPtr() const { return data; } + BYTE* GetWritePtr() { ++sequence_number; return data; } + int GetDataSize() { return data_size; } + int GetSequenceNumber() { return sequence_number; } + int GetRefcount() { return refcount; } +}; + + +class IClip; +class PClip; +class PVideoFrame; +class IScriptEnvironment; +class AVSValue; + + +// VideoFrame holds a "window" into a VideoFrameBuffer. Operator new +// is overloaded to recycle class instances. + +class VideoFrame { + int refcount; + VideoFrameBuffer* const vfb; + const int offset, pitch, row_size, height, offsetU, offsetV, pitchUV; // U&V offsets are from top of picture. + + friend class PVideoFrame; + void AddRef() { InterlockedIncrement((long *)&refcount); } + void Release() { if (refcount==1) InterlockedDecrement(&vfb->refcount); InterlockedDecrement((long *)&refcount); } + + friend class ScriptEnvironment; + friend class Cache; + + VideoFrame(VideoFrameBuffer* _vfb, int _offset, int _pitch, int _row_size, int _height); + VideoFrame(VideoFrameBuffer* _vfb, int _offset, int _pitch, int _row_size, int _height, int _offsetU, int _offsetV, int _pitchUV); + + void* operator new(unsigned size); +// TESTME: OFFSET U/V may be switched to what could be expected from AVI standard! +public: + int GetPitch() const { return pitch; } + int GetPitch(int plane) const { switch (plane) {case PLANAR_U: case PLANAR_V: return pitchUV;} return pitch; } + int GetRowSize() const { return row_size; } + int GetRowSize(int plane) const { + switch (plane) { + case PLANAR_U: case PLANAR_V: if (pitchUV) return row_size>>1; else return 0; + case PLANAR_U_ALIGNED: case PLANAR_V_ALIGNED: + if (pitchUV) { + int r = ((row_size+FRAME_ALIGN-1)&(~(FRAME_ALIGN-1)) )>>1; // Aligned rowsize + if (r<=pitchUV) + return r; + return row_size>>1; + } else return 0; + case PLANAR_Y_ALIGNED: + int r = (row_size+FRAME_ALIGN-1)&(~(FRAME_ALIGN-1)); // Aligned rowsize + if (r<=pitch) + return r; + return row_size; + } + return row_size; } + int GetHeight() const { return height; } + int GetHeight(int plane) const { switch (plane) {case PLANAR_U: case PLANAR_V: if (pitchUV) return height>>1; return 0;} return height; } + + // generally you shouldn't use these three + VideoFrameBuffer* GetFrameBuffer() const { return vfb; } + int GetOffset() const { return offset; } + int GetOffset(int plane) const { switch (plane) {case PLANAR_U: return offsetU;case PLANAR_V: return offsetV;default: return offset;}; } + + // in plugins use env->SubFrame() + VideoFrame* Subframe(int rel_offset, int new_pitch, int new_row_size, int new_height) const; + VideoFrame* Subframe(int rel_offset, int new_pitch, int new_row_size, int new_height, int rel_offsetU, int rel_offsetV, int pitchUV) const; + + + const BYTE* GetReadPtr() const { return vfb->GetReadPtr() + offset; } + const BYTE* GetReadPtr(int plane) const { return vfb->GetReadPtr() + GetOffset(plane); } + + bool IsWritable() const { return (refcount == 1 && vfb->refcount == 1); } + + BYTE* GetWritePtr() const { + if (vfb->GetRefcount()>1) { + _ASSERT(FALSE); + //throw AvisynthError("Internal Error - refcount was more than one!"); + } + return IsWritable() ? (vfb->GetWritePtr() + offset) : 0; + } + + BYTE* GetWritePtr(int plane) const { + if (plane==PLANAR_Y) { + if (vfb->GetRefcount()>1) { + _ASSERT(FALSE); +// throw AvisynthError("Internal Error - refcount was more than one!"); + } + return IsWritable() ? vfb->GetWritePtr() + GetOffset(plane) : 0; + } + return vfb->data + GetOffset(plane); + } + + ~VideoFrame() { InterlockedDecrement(&vfb->refcount); } +}; + +enum { + CACHE_NOTHING=0, + CACHE_RANGE=1, + CACHE_ALL=2, + CACHE_AUDIO=3, + CACHE_AUDIO_NONE=4 + }; + +// Base class for all filters. +class IClip { + friend class PClip; + friend class AVSValue; + int refcnt; + void AddRef() { InterlockedIncrement((long *)&refcnt); } + void Release() { InterlockedDecrement((long *)&refcnt); if (!refcnt) delete this; } +public: + IClip() : refcnt(0) {} + + virtual int __stdcall GetVersion() { return AVISYNTH_INTERFACE_VERSION; } + + virtual PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env) = 0; + virtual bool __stdcall GetParity(int n) = 0; // return field parity if field_based, else parity of first field in frame + virtual void __stdcall GetAudio(void* buf, __int64 start, __int64 count, IScriptEnvironment* env) = 0; // start and count are in samples + virtual void __stdcall SetCacheHints(int cachehints,int frame_range) = 0 ; // We do not pass cache requests upwards, only to the next filter. + virtual const VideoInfo& __stdcall GetVideoInfo() = 0; + virtual __stdcall ~IClip() {} +}; + + +// smart pointer to IClip +class PClip { + + IClip* p; + + IClip* GetPointerWithAddRef() const { if (p) p->AddRef(); return p; } + friend class AVSValue; + friend class VideoFrame; + + void Init(IClip* x) { + if (x) x->AddRef(); + p=x; + } + void Set(IClip* x) { + if (x) x->AddRef(); + if (p) p->Release(); + p=x; + } + +public: + PClip() { p = 0; } + PClip(const PClip& x) { Init(x.p); } + PClip(IClip* x) { Init(x); } + void operator=(IClip* x) { Set(x); } + void operator=(const PClip& x) { Set(x.p); } + + IClip* operator->() const { return p; } + + // useful in conditional expressions + operator void*() const { return p; } + bool operator!() const { return !p; } + + ~PClip() { if (p) p->Release(); } +}; + + +// smart pointer to VideoFrame +class PVideoFrame { + + VideoFrame* p; + + void Init(VideoFrame* x) { + if (x) x->AddRef(); + p=x; + } + void Set(VideoFrame* x) { + if (x) x->AddRef(); + if (p) p->Release(); + p=x; + } + +public: + PVideoFrame() { p = 0; } + PVideoFrame(const PVideoFrame& x) { Init(x.p); } + PVideoFrame(VideoFrame* x) { Init(x); } + void operator=(VideoFrame* x) { Set(x); } + void operator=(const PVideoFrame& x) { Set(x.p); } + + VideoFrame* operator->() const { return p; } + + // for conditional expressions + operator void*() const { return p; } + bool operator!() const { return !p; } + + ~PVideoFrame() { if (p) p->Release();} +}; + + +class AVSValue { +public: + + AVSValue() { type = 'v'; } + AVSValue(IClip* c) { type = 'c'; clip = c; if (c) c->AddRef(); } + AVSValue(const PClip& c) { type = 'c'; clip = c.GetPointerWithAddRef(); } + AVSValue(bool b) { type = 'b'; boolean = b; } + AVSValue(int i) { type = 'i'; integer = i; } +// AVSValue(__int64 l) { type = 'l'; longlong = l; } + AVSValue(float f) { type = 'f'; floating_pt = f; } + AVSValue(double f) { type = 'f'; floating_pt = float(f); } + AVSValue(const char* s) { type = 's'; string = s; } + AVSValue(const AVSValue* a, int size) { type = 'a'; array = a; array_size = size; } + AVSValue(const AVSValue& v) { Assign(&v, true); } + + ~AVSValue() { if (IsClip() && clip) clip->Release(); } + AVSValue& operator=(const AVSValue& v) { Assign(&v, false); return *this; } + + // Note that we transparently allow 'int' to be treated as 'float'. + // There are no int<->bool conversions, though. + + bool Defined() const { return type != 'v'; } + bool IsClip() const { return type == 'c'; } + bool IsBool() const { return type == 'b'; } + bool IsInt() const { return type == 'i'; } +// bool IsLong() const { return (type == 'l'|| type == 'i'); } + bool IsFloat() const { return type == 'f' || type == 'i'; } + bool IsString() const { return type == 's'; } + bool IsArray() const { return type == 'a'; } + + PClip AsClip() const { _ASSERTE(IsClip()); return IsClip()?clip:0; } + bool AsBool() const { _ASSERTE(IsBool()); return boolean; } + int AsInt() const { _ASSERTE(IsInt()); return integer; } +// int AsLong() const { _ASSERTE(IsLong()); return longlong; } + const char* AsString() const { _ASSERTE(IsString()); return IsString()?string:0; } + double AsFloat() const { _ASSERTE(IsFloat()); return IsInt()?integer:floating_pt; } + + bool AsBool(bool def) const { _ASSERTE(IsBool()||!Defined()); return IsBool() ? boolean : def; } + int AsInt(int def) const { _ASSERTE(IsInt()||!Defined()); return IsInt() ? integer : def; } + double AsFloat(double def) const { _ASSERTE(IsFloat()||!Defined()); return IsInt() ? integer : type=='f' ? floating_pt : def; } + const char* AsString(const char* def) const { _ASSERTE(IsString()||!Defined()); return IsString() ? string : def; } + + int ArraySize() const { _ASSERTE(IsArray()); return IsArray()?array_size:1; } + + const AVSValue& operator[](int index) const { + _ASSERTE(IsArray() && index>=0 && index=0 && indexIsClip() && src->clip) + src->clip->AddRef(); + if (!init && IsClip() && clip) + clip->Release(); + // make sure this copies the whole struct! + ((__int32*)this)[0] = ((__int32*)src)[0]; + ((__int32*)this)[1] = ((__int32*)src)[1]; + } +}; + + +// instantiable null filter +class GenericVideoFilter : public IClip { +protected: + PClip child; + VideoInfo vi; +public: + GenericVideoFilter(PClip _child) : child(_child) { vi = child->GetVideoInfo(); } + PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env) { return child->GetFrame(n, env); } + void __stdcall GetAudio(void* buf, __int64 start, __int64 count, IScriptEnvironment* env) { child->GetAudio(buf, start, count, env); } + const VideoInfo& __stdcall GetVideoInfo() { return vi; } + bool __stdcall GetParity(int n) { return child->GetParity(n); } + void __stdcall SetCacheHints(int cachehints,int frame_range) { } ; // We do not pass cache requests upwards, only to the next filter. +}; + + +class AvisynthError /* exception */ { +public: + const char* const msg; + AvisynthError(const char* _msg) : msg(_msg) {} +}; + + + + +/* Helper classes useful to plugin authors */ + +class AlignPlanar : public GenericVideoFilter +{ +public: + AlignPlanar(PClip _clip); + static PClip Create(PClip clip); + PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env); +}; + + + +class FillBorder : public GenericVideoFilter +{ +public: + FillBorder(PClip _clip); + static PClip Create(PClip clip); + PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env); +}; + + + +class ConvertAudio : public GenericVideoFilter +/** + * Helper class to convert audio to any format + **/ +{ +public: + ConvertAudio(PClip _clip, int prefered_format); + void __stdcall GetAudio(void* buf, __int64 start, __int64 count, IScriptEnvironment* env); + void __stdcall SetCacheHints(int cachehints,int frame_range); // We do pass cache requests upwards, to the cache! + + static PClip Create(PClip clip, int sample_type, int prefered_type); + static AVSValue __cdecl Create_float(AVSValue args, void*, IScriptEnvironment*); + static AVSValue __cdecl Create_32bit(AVSValue args, void*, IScriptEnvironment*); + static AVSValue __cdecl Create_24bit(AVSValue args, void*, IScriptEnvironment*); + static AVSValue __cdecl Create_16bit(AVSValue args, void*, IScriptEnvironment*); + static AVSValue __cdecl Create_8bit(AVSValue args, void*, IScriptEnvironment*); + virtual ~ConvertAudio(); + +private: + void convertToFloat(char* inbuf, float* outbuf, char sample_type, int count); + void convertToFloat_3DN(char* inbuf, float* outbuf, char sample_type, int count); + void convertToFloat_SSE(char* inbuf, float* outbuf, char sample_type, int count); + void convertToFloat_SSE2(char* inbuf, float* outbuf, char sample_type, int count); + void convertFromFloat(float* inbuf, void* outbuf, char sample_type, int count); + void convertFromFloat_3DN(float* inbuf, void* outbuf, char sample_type, int count); + void convertFromFloat_SSE(float* inbuf, void* outbuf, char sample_type, int count); + void convertFromFloat_SSE2(float* inbuf, void* outbuf, char sample_type, int count); + + __inline int Saturate_int8(float n); + __inline short Saturate_int16(float n); + __inline int Saturate_int24(float n); + __inline int Saturate_int32(float n); + + char src_format; + char dst_format; + int src_bps; + char *tempbuffer; + SFLOAT *floatbuffer; + int tempbuffer_size; +}; + + +// For GetCPUFlags. These are backwards-compatible with those in VirtualDub. +enum { + /* slowest CPU to support extension */ + CPUF_FORCE = 0x01, // N/A + CPUF_FPU = 0x02, // 386/486DX + CPUF_MMX = 0x04, // P55C, K6, PII + CPUF_INTEGER_SSE = 0x08, // PIII, Athlon + CPUF_SSE = 0x10, // PIII, Athlon XP/MP + CPUF_SSE2 = 0x20, // PIV, Hammer + CPUF_3DNOW = 0x40, // K6-2 + CPUF_3DNOW_EXT = 0x80, // Athlon + CPUF_X86_64 = 0xA0, // Hammer (note: equiv. to 3DNow + SSE2, which only Hammer + // will have anyway) + CPUF_SSE3 = 0x100, // Some P4 & Athlon 64. +}; +#define MAX_INT 0x7fffffff +#define MIN_INT -0x7fffffff + + + +class IScriptEnvironment { +public: + virtual __stdcall ~IScriptEnvironment() {} + + virtual /*static*/ long __stdcall GetCPUFlags() = 0; + + virtual char* __stdcall SaveString(const char* s, int length = -1) = 0; + virtual char* __stdcall Sprintf(const char* fmt, ...) = 0; + // note: val is really a va_list; I hope everyone typedefs va_list to a pointer + virtual char* __stdcall VSprintf(const char* fmt, void* val) = 0; + + __declspec(noreturn) virtual void __stdcall ThrowError(const char* fmt, ...) = 0; + + class NotFound /*exception*/ {}; // thrown by Invoke and GetVar + + typedef AVSValue (__cdecl *ApplyFunc)(AVSValue args, void* user_data, IScriptEnvironment* env); + + virtual void __stdcall AddFunction(const char* name, const char* params, ApplyFunc apply, void* user_data) = 0; + virtual bool __stdcall FunctionExists(const char* name) = 0; + virtual AVSValue __stdcall Invoke(const char* name, const AVSValue args, const char** arg_names=0) = 0; + + virtual AVSValue __stdcall GetVar(const char* name) = 0; + virtual bool __stdcall SetVar(const char* name, const AVSValue& val) = 0; + virtual bool __stdcall SetGlobalVar(const char* name, const AVSValue& val) = 0; + + virtual void __stdcall PushContext(int level=0) = 0; + virtual void __stdcall PopContext() = 0; + + // align should be 4 or 8 + virtual PVideoFrame __stdcall NewVideoFrame(const VideoInfo& vi, int align=FRAME_ALIGN) = 0; + + virtual bool __stdcall MakeWritable(PVideoFrame* pvf) = 0; + + virtual /*static*/ void __stdcall BitBlt(BYTE* dstp, int dst_pitch, const BYTE* srcp, int src_pitch, int row_size, int height) = 0; + + typedef void (__cdecl *ShutdownFunc)(void* user_data, IScriptEnvironment* env); + virtual void __stdcall AtExit(ShutdownFunc function, void* user_data) = 0; + + virtual void __stdcall CheckVersion(int version = AVISYNTH_INTERFACE_VERSION) = 0; + + virtual PVideoFrame __stdcall Subframe(PVideoFrame src, int rel_offset, int new_pitch, int new_row_size, int new_height) = 0; + + virtual int __stdcall SetMemoryMax(int mem) = 0; + + virtual int __stdcall SetWorkingDir(const char * newdir) = 0; + + virtual void* __stdcall ManageCache(int key, void* data) = 0; + + enum PlanarChromaAlignmentMode { + PlanarChromaAlignmentOff, + PlanarChromaAlignmentOn, + PlanarChromaAlignmentTest }; + + virtual bool __stdcall PlanarChromaAlignment(PlanarChromaAlignmentMode key) = 0; + + virtual PVideoFrame __stdcall SubframePlanar(PVideoFrame src, int rel_offset, int new_pitch, int new_row_size, int new_height, int rel_offsetU, int rel_offsetV, int new_pitchUV) = 0; +}; + + +// avisynth.dll exports this; it's a way to use it as a library, without +// writing an AVS script or without going through AVIFile. +IScriptEnvironment* __stdcall CreateScriptEnvironment(int version = AVISYNTH_INTERFACE_VERSION); + + +#pragma pack(pop) + +#endif //__AVISYNTH_H__ diff --git a/FFmpegSource/ffmpegsource.cpp b/FFmpegSource/ffmpegsource.cpp new file mode 100644 index 000000000..bd55d1924 --- /dev/null +++ b/FFmpegSource/ffmpegsource.cpp @@ -0,0 +1,686 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +} + +#include "MatroskaParser.h" +#include "avisynth.h" +#include "stdiostream.cpp" + +class FFBase : public IClip { +private: + SwsContext *SWS; + int ConvertToFormat; + int ConvertFromFormat; +protected: + VideoInfo VI; + + void SetOutputFormat(int CurrentFormat, IScriptEnvironment *Env) { + int Loss; + int BestFormat = avcodec_find_best_pix_fmt((1 << PIX_FMT_YUV420P) | (1 << PIX_FMT_YUYV422) | (1 << PIX_FMT_RGB32) | (1 << PIX_FMT_BGR24), CurrentFormat, 1, &Loss); + + switch (BestFormat) { + case PIX_FMT_YUV420P: VI.pixel_type = VideoInfo::CS_I420; break; + case PIX_FMT_YUYV422: VI.pixel_type = VideoInfo::CS_YUY2; break; + case PIX_FMT_RGB32: VI.pixel_type = VideoInfo::CS_BGR32; break; + case PIX_FMT_BGR24: VI.pixel_type = VideoInfo::CS_BGR24; break; + default: + Env->ThrowError("No suitable output format found"); + } + + if (BestFormat != CurrentFormat) { + ConvertFromFormat = CurrentFormat; + ConvertToFormat = BestFormat; + SWS = sws_getContext(VI.width, VI.height, ConvertFromFormat, VI.width, VI.height, ConvertToFormat, SWS_LANCZOS, NULL, NULL, NULL); + } + } + + PVideoFrame OutputFrame(AVFrame *Frame, IScriptEnvironment *Env) { + PVideoFrame Dst = Env->NewVideoFrame(VI); + + if (ConvertToFormat != PIX_FMT_NONE && VI.pixel_type == VideoInfo::CS_I420) { + uint8_t *DstData[3] = {Dst->GetWritePtr(PLANAR_Y), Dst->GetWritePtr(PLANAR_U), Dst->GetWritePtr(PLANAR_V)}; + int DstStride[3] = {Dst->GetPitch(PLANAR_Y), Dst->GetPitch(PLANAR_U), Dst->GetPitch(PLANAR_V)}; + sws_scale(SWS, Frame->data, Frame->linesize, 0, VI.height, DstData, DstStride); + } else if (ConvertToFormat != PIX_FMT_NONE) { + if (VI.IsRGB()) { + uint8_t *DstData[1] = {Dst->GetWritePtr() + Dst->GetPitch() * (Dst->GetHeight() - 1)}; + int DstStride[1] = {-Dst->GetPitch()}; + sws_scale(SWS, Frame->data, Frame->linesize, 0, VI.height, DstData, DstStride); + } else { + uint8_t *DstData[1] = {Dst->GetWritePtr()}; + int DstStride[1] = {Dst->GetPitch()}; + sws_scale(SWS, Frame->data, Frame->linesize, 0, VI.height, DstData, DstStride); + } + } else if (VI.pixel_type == VideoInfo::CS_I420) { + Env->BitBlt(Dst->GetWritePtr(PLANAR_Y), Dst->GetPitch(PLANAR_Y), Frame->data[0], Frame->linesize[0], Dst->GetRowSize(PLANAR_Y), Dst->GetHeight(PLANAR_Y)); + Env->BitBlt(Dst->GetWritePtr(PLANAR_U), Dst->GetPitch(PLANAR_U), Frame->data[1], Frame->linesize[1], Dst->GetRowSize(PLANAR_U), Dst->GetHeight(PLANAR_U)); + Env->BitBlt(Dst->GetWritePtr(PLANAR_V), Dst->GetPitch(PLANAR_V), Frame->data[2], Frame->linesize[2], Dst->GetRowSize(PLANAR_V), Dst->GetHeight(PLANAR_V)); + } else { + if (VI.IsRGB()) + Env->BitBlt(Dst->GetWritePtr() + Dst->GetPitch() * (Dst->GetHeight() - 1), -Dst->GetPitch(), Frame->data[0], Frame->linesize[0], Dst->GetRowSize(), Dst->GetHeight()); + else + Env->BitBlt(Dst->GetWritePtr(), Dst->GetPitch(), Frame->data[0], Frame->linesize[0], Dst->GetRowSize(), Dst->GetHeight()); + } + return Dst; + } + +public: + virtual bool __stdcall GetParity(int n) { return 0; } + virtual void __stdcall GetAudio(void* buf, __int64 start, __int64 count, IScriptEnvironment* env) { } + virtual void __stdcall SetCacheHints(int cachehints, int frame_range) { } + virtual const VideoInfo& __stdcall GetVideoInfo() { return VI; } + + FFBase() { + SWS = NULL; + ConvertToFormat = PIX_FMT_NONE; + ConvertFromFormat = PIX_FMT_NONE; + memset(&VI, 0, sizeof(VI)); + } +}; + +class FFMKVSource : public FFBase { +private: + AVCodecContext CodecContext; + AVCodec *Codec; + AVFrame *Frame; + int CurrentFrame; + + struct FFMKVFrameInfo { + ulonglong DTS; + bool KeyFrame; + }; + + std::vector FrameToDTS; + std::map DTSToFrame; + + StdIoStream ST; + MatroskaFile *MF; + + char ErrorMessage[256]; + unsigned int BufferSize; + void *Buffer; + CompressedStream *CS; + + int ReadNextFrame(AVFrame *Frame, ulonglong *StartTime, IScriptEnvironment* Env); + + CodecID MatroskaToFFCodecID(TrackInfo *TI) { + char *Codec = TI->CodecID; + if (!strcmp(Codec, "V_MS/VFW/FOURCC")) { + switch (((BITMAPINFOHEADER *)TI->CodecPrivate)->biCompression) { + case MAKEFOURCC('F', 'F', 'D', 'S'): + case MAKEFOURCC('F', 'V', 'F', 'W'): + case MAKEFOURCC('X', 'V', 'I', 'D'): + case MAKEFOURCC('D', 'I', 'V', 'X'): + case MAKEFOURCC('D', 'X', '5', '0'): + case MAKEFOURCC('M', 'P', '4', 'V'): + case MAKEFOURCC('3', 'I', 'V', 'X'): + case MAKEFOURCC('W', 'V', '1', 'F'): + case MAKEFOURCC('F', 'M', 'P', '4'): + case MAKEFOURCC('S', 'M', 'P', '4'): + return CODEC_ID_MPEG4; + case MAKEFOURCC('D', 'I', 'V', '3'): + case MAKEFOURCC('D', 'V', 'X', '3'): + case MAKEFOURCC('M', 'P', '4', '3'): + return CODEC_ID_MSMPEG4V3; + case MAKEFOURCC('M', 'P', '4', '2'): + return CODEC_ID_MSMPEG4V2; + case MAKEFOURCC('M', 'P', '4', '1'): + return CODEC_ID_MSMPEG4V1; + case MAKEFOURCC('W', 'M', 'V', '1'): + return CODEC_ID_WMV1; + case MAKEFOURCC('W', 'M', 'V', '2'): + return CODEC_ID_WMV2; + case MAKEFOURCC('W', 'M', 'V', '3'): + return CODEC_ID_WMV3; +/* + case MAKEFOURCC('M', 'S', 'S', '1'): + case MAKEFOURCC('M', 'S', 'S', '2'): + case MAKEFOURCC('W', 'V', 'P', '2'): + case MAKEFOURCC('W', 'M', 'V', 'P'): + return CODEC_ID_WMV9_LIB; +*/ + case MAKEFOURCC('W', 'V', 'C', '1'): + return CODEC_ID_VC1; + case MAKEFOURCC('V', 'P', '5', '0'): + return CODEC_ID_VP5; + case MAKEFOURCC('V', 'P', '6', '0'): + case MAKEFOURCC('V', 'P', '6', '1'): + case MAKEFOURCC('V', 'P', '6', '2'): + return CODEC_ID_VP6; + case MAKEFOURCC('V', 'P', '6', 'F'): + case MAKEFOURCC('F', 'L', 'V', '4'): + return CODEC_ID_VP6F; + case MAKEFOURCC('C', 'A', 'V', 'S'): + return CODEC_ID_CAVS; + case MAKEFOURCC('M', 'P', 'G', '1'): + case MAKEFOURCC('M', 'P', 'E', 'G'): + return CODEC_ID_MPEG2VIDEO; // not a typo + case MAKEFOURCC('M', 'P', 'G', '2'): + case MAKEFOURCC('E', 'M', '2', 'V'): + case MAKEFOURCC('M', 'M', 'E', 'S'): + return CODEC_ID_MPEG2VIDEO; + case MAKEFOURCC('H', '2', '6', '3'): + case MAKEFOURCC('S', '2', '6', '3'): + case MAKEFOURCC('L', '2', '6', '3'): + case MAKEFOURCC('M', '2', '6', '3'): + case MAKEFOURCC('U', '2', '6', '3'): + case MAKEFOURCC('X', '2', '6', '3'): + return CODEC_ID_H263; + case MAKEFOURCC('H', '2', '6', '4'): + case MAKEFOURCC('X', '2', '6', '4'): + case MAKEFOURCC('V', 'S', 'S', 'H'): + case MAKEFOURCC('D', 'A', 'V', 'C'): + case MAKEFOURCC('P', 'A', 'V', 'C'): + case MAKEFOURCC('A', 'V', 'C', '1'): + return CODEC_ID_H264; + case MAKEFOURCC('M', 'J', 'P', 'G'): + case MAKEFOURCC('L', 'J', 'P', 'G'): + case MAKEFOURCC('M', 'J', 'L', 'S'): + case MAKEFOURCC('J', 'P', 'E', 'G'): // questionable fourcc? + case MAKEFOURCC('A', 'V', 'R', 'N'): + case MAKEFOURCC('M', 'J', 'P', 'A'): + return CODEC_ID_MJPEG; + case MAKEFOURCC('D', 'V', 'S', 'D'): + case MAKEFOURCC('D', 'V', '2', '5'): + case MAKEFOURCC('D', 'V', '5', '0'): + case MAKEFOURCC('C', 'D', 'V', 'C'): + case MAKEFOURCC('C', 'D', 'V', '5'): + case MAKEFOURCC('D', 'V', 'I', 'S'): + case MAKEFOURCC('P', 'D', 'V', 'C'): + return CODEC_ID_DVVIDEO; + case MAKEFOURCC('H', 'F', 'Y', 'U'): + case MAKEFOURCC('F', 'F', 'V', 'H'): + return CODEC_ID_HUFFYUV; + case MAKEFOURCC('C', 'Y', 'U', 'V'): + return CODEC_ID_CYUV; + case MAKEFOURCC('A', 'S', 'V', '1'): + return CODEC_ID_ASV1; + case MAKEFOURCC('A', 'S', 'V', '2'): + return CODEC_ID_ASV2; + case MAKEFOURCC('V', 'C', 'R', '1'): + return CODEC_ID_VCR1; + case MAKEFOURCC('T', 'H', 'E', 'O'): + return CODEC_ID_THEORA; + case MAKEFOURCC('S', 'V', 'Q', '1'): + return CODEC_ID_SVQ1; + case MAKEFOURCC('S', 'V', 'Q', '3'): + return CODEC_ID_SVQ3; + case MAKEFOURCC('R', 'P', 'Z', 'A'): + return CODEC_ID_RPZA; + case MAKEFOURCC('F', 'F', 'V', '1'): + return CODEC_ID_FFV1; + case MAKEFOURCC('V', 'P', '3', '1'): + return CODEC_ID_VP3; + case MAKEFOURCC('R', 'L', 'E', '8'): + return CODEC_ID_MSRLE; + case MAKEFOURCC('M', 'S', 'Z', 'H'): + return CODEC_ID_MSZH; + case MAKEFOURCC('Z', 'L', 'I', 'B'): + return CODEC_ID_FLV1; + case MAKEFOURCC('F', 'L', 'V', '1'): + return CODEC_ID_ZLIB; +/* + case MAKEFOURCC('P', 'N', 'G', '1'): + return CODEC_ID_COREPNG; +*/ + case MAKEFOURCC('M', 'P', 'N', 'G'): + return CODEC_ID_PNG; +/* + case MAKEFOURCC('A', 'V', 'I', 'S'): + return CODEC_ID_AVISYNTH; +*/ + case MAKEFOURCC('C', 'R', 'A', 'M'): + return CODEC_ID_MSVIDEO1; + case MAKEFOURCC('R', 'T', '2', '1'): + return CODEC_ID_INDEO2; + case MAKEFOURCC('I', 'V', '3', '2'): + case MAKEFOURCC('I', 'V', '3', '1'): + return CODEC_ID_INDEO3; + case MAKEFOURCC('C', 'V', 'I', 'D'): + return CODEC_ID_CINEPAK; + case MAKEFOURCC('R', 'V', '1', '0'): + return CODEC_ID_RV10; + case MAKEFOURCC('R', 'V', '2', '0'): + return CODEC_ID_RV20; + case MAKEFOURCC('8', 'B', 'P', 'S'): + return CODEC_ID_8BPS; + case MAKEFOURCC('Q', 'R', 'L', 'E'): + return CODEC_ID_QTRLE; + case MAKEFOURCC('D', 'U', 'C', 'K'): + return CODEC_ID_TRUEMOTION1; + case MAKEFOURCC('T', 'M', '2', '0'): + return CODEC_ID_TRUEMOTION2; + case MAKEFOURCC('T', 'S', 'C', 'C'): + return CODEC_ID_TSCC; + case MAKEFOURCC('S', 'N', 'O', 'W'): + return CODEC_ID_SNOW; + case MAKEFOURCC('Q', 'P', 'E', 'G'): + case MAKEFOURCC('Q', '1', '_', '0'): + case MAKEFOURCC('Q', '1', '_', '1'): + return CODEC_ID_QPEG; + case MAKEFOURCC('H', '2', '6', '1'): + case MAKEFOURCC('M', '2', '6', '1'): + return CODEC_ID_H261; + case MAKEFOURCC('L', 'O', 'C', 'O'): + return CODEC_ID_LOCO; + case MAKEFOURCC('W', 'N', 'V', '1'): + return CODEC_ID_WNV1; + case MAKEFOURCC('C', 'S', 'C', 'D'): + return CODEC_ID_CSCD; + case MAKEFOURCC('Z', 'M', 'B', 'V'): + return CODEC_ID_ZMBV; + case MAKEFOURCC('U', 'L', 'T', 'I'): + return CODEC_ID_ULTI; + case MAKEFOURCC('V', 'I', 'X', 'L'): + return CODEC_ID_VIXL; + case MAKEFOURCC('A', 'A', 'S', 'C'): + return CODEC_ID_AASC; + case MAKEFOURCC('F', 'P', 'S', '1'): + return CODEC_ID_FRAPS; + default: + return CODEC_ID_NONE; + } + } else if (!strcmp(Codec, "V_MPEG4/ISO/AVC")) + return CODEC_ID_H264; + else if (!strcmp(Codec, "V_MPEG4/ISO/ASP")) + return CODEC_ID_MPEG4; + else if (!strcmp(Codec, "V_MPEG2")) + return CODEC_ID_MPEG2VIDEO; + else if (!strcmp(Codec, "V_MPEG1")) + return CODEC_ID_MPEG2VIDEO; // still not a typo + else if (!strcmp(Codec, "V_SNOW")) + return CODEC_ID_SNOW; + else if (!strcmp(Codec, "V_THEORA")) + return CODEC_ID_THEORA; + else if (!strncmp(Codec, "V_REAL/RV", 9)) { + switch (Codec[9]) { + case '1': + return CODEC_ID_RV10; + case '2': + return CODEC_ID_RV20; + case '3': + return CODEC_ID_RV30; + case '4': + return CODEC_ID_RV40; + default: + return CODEC_ID_NONE; + } + } else + return CODEC_ID_NONE; + } +public: + FFMKVSource(const char *Source, int Track, IScriptEnvironment* Env) { + BufferSize = 0; + Buffer = NULL; + Frame = NULL; + CS = NULL; + CurrentFrame = 0; + + memset(&ST,0,sizeof(ST)); + ST.base.read = (int (__cdecl *)(InputStream *,ulonglong,void *,int))StdIoRead; + ST.base.scan = (longlong (__cdecl *)(InputStream *,ulonglong,unsigned int))StdIoScan; + ST.base.getcachesize = (unsigned int (__cdecl *)(InputStream *))StdIoGetCacheSize; + ST.base.geterror = (const char *(__cdecl *)(InputStream *))StdIoGetLastError; + ST.base.memalloc = (void *(__cdecl *)(InputStream *,size_t))StdIoMalloc; + ST.base.memrealloc = (void *(__cdecl *)(InputStream *,void *,size_t))StdIoRealloc; + ST.base.memfree = (void (__cdecl *)(InputStream *,void *)) StdIoFree; + ST.base.progress = (int (__cdecl *)(InputStream *,ulonglong,ulonglong))StdIoProgress; + + ST.fp = fopen(Source ,"rb"); + if (ST.fp == NULL) + Env->ThrowError("Can't open '%s': %s\n", Source, strerror(errno)); + + setvbuf(ST.fp, NULL, _IOFBF, CACHESIZE); + + MF = mkv_OpenEx(&ST.base, 0, 0, ErrorMessage, sizeof(ErrorMessage)); + if (MF == NULL) { + fclose(ST.fp); + Env->ThrowError("Can't parse Matroska file: %s\n", ErrorMessage); + } + + if (Track < 0) + for (unsigned int i = 0; i < mkv_GetNumTracks(MF); i++) + if (mkv_GetTrackInfo(MF, i)->Type == TT_VIDEO) { + Track = i; + break; + } + + if (Track < 0) + Env->ThrowError("No video track found"); + + if ((unsigned)Track >= mkv_GetNumTracks(MF)) + Env->ThrowError("Invalid track number: %d\n", Track); + + TrackInfo *TI = mkv_GetTrackInfo(MF, Track); + + if (TI->Type != TT_VIDEO) + Env->ThrowError("Selected track is not video"); + + mkv_SetTrackMask(MF, ~(1 << Track)); + + if (TI->CompEnabled) { + CS = cs_Create(MF, Track, ErrorMessage, sizeof(ErrorMessage)); + if (CS == NULL) + Env->ThrowError("Can't create decompressor: %s\n", ErrorMessage); + } + + avcodec_get_context_defaults(&CodecContext); + CodecContext.extradata = (uint8_t *)TI->CodecPrivate; + CodecContext.extradata_size = TI->CodecPrivateSize; + + Codec = avcodec_find_decoder(MatroskaToFFCodecID(TI)); + if(Codec == NULL) + Env->ThrowError("Codec not found"); + + if(avcodec_open(&CodecContext, Codec) < 0) + Env->ThrowError("Could not open codec"); + + VI.image_type = VideoInfo::IT_TFF; + VI.width = TI->AV.Video.PixelWidth; + VI.height = TI->AV.Video.PixelHeight; + VI.fps_denominator = 1; + VI.fps_numerator = 30; + + SetOutputFormat(CodecContext.pix_fmt, Env); + + unsigned TrackNumber, FrameSize, FrameFlags; + ulonglong StartTime, EndTime, FilePos; + + while (mkv_ReadFrame(MF, 0, &TrackNumber, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0) { + FFMKVFrameInfo FI; + FI.DTS = StartTime; + FI.KeyFrame = (FrameFlags & FRAME_KF) != 0; + + FrameToDTS.push_back(FI); + DTSToFrame[StartTime] = VI.num_frames; + VI.num_frames++; + } + + Frame = avcodec_alloc_frame(); + + mkv_Seek(MF, FrameToDTS[0].DTS, MKVF_SEEK_TO_PREV_KEYFRAME); + } + + ~FFMKVSource() { + free(Buffer); + mkv_Close(MF); + fclose(ST.fp); + av_free(Frame); + avcodec_close(&CodecContext); + } + + PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* Env); +}; + +int FFMKVSource::ReadNextFrame(AVFrame *Frame, ulonglong *StartTime, IScriptEnvironment* Env) { + unsigned TrackNumber, FrameFlags, FrameSize; + ulonglong EndTime, FilePos, StartTime2; + *StartTime = -1; + int FrameFinished = 0; + int Ret = -1; + + while (mkv_ReadFrame(MF, 0, &TrackNumber, &StartTime2, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0) { + if ((longlong)*StartTime < 0) + *StartTime = StartTime2; + if (CS) { + char CSBuffer[1024]; + + cs_NextFrame(CS, FilePos, FrameSize); + for (;;) { + int ReadBytes = cs_ReadData(CS, CSBuffer, sizeof(CSBuffer)); + if (ReadBytes < 0) + Env->ThrowError("Error decompressing data: %s\n", cs_GetLastError(CS)); + if (ReadBytes == 0) + break; + Ret = avcodec_decode_video(&CodecContext, Frame, &FrameFinished, (uint8_t *)CSBuffer, ReadBytes); + if (FrameFinished) + goto Done; + } + } else { + size_t ReadBytes; + + if (fseek(ST.fp, FilePos, SEEK_SET)) + Env->ThrowError("fseek(): %s\n", strerror(errno)); + + if (BufferSize < FrameSize) { + BufferSize = FrameSize; + Buffer = realloc(Buffer, BufferSize); + if (Buffer == NULL) + Env->ThrowError("Out of memory\n"); + } + + ReadBytes = fread(Buffer, 1, FrameSize, ST.fp); + if (ReadBytes != FrameSize) { + if (ReadBytes == 0) { + if (feof(ST.fp)) + fprintf(stderr, "Unexpected EOF while reading frame\n"); + else + fprintf(stderr, "Error reading frame: %s\n", strerror(errno)); + } else + fprintf(stderr,"Short read while reading frame\n"); + goto Done; + } + + Ret = avcodec_decode_video(&CodecContext, Frame, &FrameFinished, (uint8_t *)Buffer, FrameSize); + + if (FrameFinished) + goto Done; + } + } + +Done: + return Ret; +} + +PVideoFrame __stdcall FFMKVSource::GetFrame(int n, IScriptEnvironment* Env) { + bool HasSeeked = false; + bool HasBiggerKF = false; + + for (int i = CurrentFrame + 1; i <= n; i++) + if (FrameToDTS[i].KeyFrame) { + HasBiggerKF = true; + break; + } + + if (n < CurrentFrame || HasBiggerKF) { + mkv_Seek(MF, FrameToDTS[n].DTS, MKVF_SEEK_TO_PREV_KEYFRAME); + avcodec_flush_buffers(&CodecContext); + HasSeeked = true; + } + + do { + ulonglong StartTime; + int Ret = ReadNextFrame(Frame, &StartTime, Env); + + if (HasSeeked) { + CurrentFrame = DTSToFrame[StartTime]; + HasSeeked = false; + } + + CurrentFrame++; + } while (CurrentFrame <= n); + + return OutputFrame(Frame, Env); +} + +class FFMpegSource : public FFBase { +private: + AVFormatContext *FormatContext; + AVCodecContext *CodecContext; + AVCodec *Codec; + AVFrame *Frame; + int Track; + int CurrentFrame; + std::vector FrameToDTS; + std::map DTSToFrame; + bool ForceSeek; + + int ReadNextFrame(AVFrame *Frame, int64_t *DTS); +public: + FFMpegSource(const char *Source, int _Track, bool _ForceSeek, IScriptEnvironment* Env) : Track(_Track), ForceSeek(_ForceSeek) { + CurrentFrame = 0; + + if(av_open_input_file(&FormatContext, Source, NULL, 0, NULL) != 0) + Env->ThrowError("Couldn't open \"%s\"", Source); + + if(av_find_stream_info(FormatContext) < 0) + Env->ThrowError("Couldn't find stream information"); + + if (Track >= (int)FormatContext->nb_streams) + Env->ThrowError("Invalid track number"); + + if (Track < 0) + for(unsigned int i = 0; i < FormatContext->nb_streams; i++) + if(FormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO) { + Track = i; + break; + } + + if(Track < -1) + Env->ThrowError("Couldn't find a video stream"); + + if (FormatContext->streams[Track]->codec->codec_type != CODEC_TYPE_VIDEO) + Env->ThrowError("Selected stream doesn't contain video"); + + CodecContext = FormatContext->streams[Track]->codec; + + Codec = avcodec_find_decoder(CodecContext->codec_id); + if(Codec == NULL) + Env->ThrowError("Codec not found"); + + if(avcodec_open(CodecContext, Codec) < 0) + Env->ThrowError("Could not open codec"); + + VI.image_type = VideoInfo::IT_TFF; + VI.width = CodecContext->width; + VI.height = CodecContext->height; + VI.fps_denominator = CodecContext->time_base.num * 1000; + VI.fps_numerator = CodecContext->time_base.den; + + // sanity check framerate + if (VI.fps_numerator < VI.fps_denominator || CodecContext->time_base.num <= 0 || CodecContext->time_base.den <= 0) { + VI.fps_denominator = 1; + VI.fps_numerator = 30; + } + + SetOutputFormat(CodecContext->pix_fmt, Env); + + AVPacket Packet; + + while (av_read_frame(FormatContext, &Packet) >= 0) { + if (Packet.stream_index == Track) { + FrameToDTS.push_back(Packet.dts); + DTSToFrame[Packet.dts] = VI.num_frames; + VI.num_frames++; + } + av_free_packet(&Packet); + } + + Frame = avcodec_alloc_frame(); + + av_seek_frame(FormatContext, Track, 0, AVSEEK_FLAG_BACKWARD); + } + + ~FFMpegSource() { + av_free(Frame); + avcodec_close(CodecContext); + av_close_input_file(FormatContext); + } + + PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* Env); +}; + +int FFMpegSource::ReadNextFrame(AVFrame *Frame, int64_t *DTS) { + AVPacket Packet; + int FrameFinished = 0; + int Ret = -1; + *DTS = -1; + + while (av_read_frame(FormatContext, &Packet) >= 0) { + if (Packet.stream_index == Track) { + + Ret = avcodec_decode_video(CodecContext, Frame, &FrameFinished, Packet.data, Packet.size); + + if (*DTS < 0) + *DTS = Packet.dts; + } + + av_free_packet(&Packet); + + if (FrameFinished) + break; + } + + return Ret; +} + +PVideoFrame __stdcall FFMpegSource::GetFrame(int n, IScriptEnvironment* Env) { + bool HasSeeked = false; + + int IndexPosition = av_index_search_timestamp(FormatContext->streams[Track], FrameToDTS[n], AVSEEK_FLAG_BACKWARD); + int64_t NearestIndexDTS = -1; + if (IndexPosition >= 0) + NearestIndexDTS = FormatContext->streams[Track]->index_entries[IndexPosition].timestamp; + + if (n < CurrentFrame || NearestIndexDTS > FrameToDTS[CurrentFrame] || (ForceSeek && IndexPosition == -1 && n > CurrentFrame + 10)) { + av_seek_frame(FormatContext, Track, FrameToDTS[n], AVSEEK_FLAG_BACKWARD); + avcodec_flush_buffers(CodecContext); + HasSeeked = true; + } + + do { + int64_t DTS; + int Ret = ReadNextFrame(Frame, &DTS); + + if (HasSeeked) { + CurrentFrame = DTSToFrame[DTS]; + HasSeeked = DTS < 0; + } + + CurrentFrame++; + } while (CurrentFrame <= n || HasSeeked); + + return OutputFrame(Frame, Env); +} + +AVSValue __cdecl CreateFFMpegSource(AVSValue Args, void* UserData, IScriptEnvironment* Env) { + if (!Args[0].Defined()) + Env->ThrowError("No source specified"); + + av_register_all(); + + AVFormatContext *FormatContext; + + if (av_open_input_file(&FormatContext, Args[0].AsString(), NULL, 0, NULL) != 0) + Env->ThrowError("Couldn't open \"%s\"", Args[0].AsString()); + + bool IsMatroska = !strcmp(FormatContext->iformat->name, "matroska"); + + av_close_input_file(FormatContext); + + if (IsMatroska) + return new FFMKVSource(Args[0].AsString(), Args[1].AsInt(-1), Env); + else + return new FFMpegSource(Args[0].AsString(), Args[1].AsInt(-1), Args[2].AsBool(false), Env); +} + +extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* Env) { + Env->AddFunction("FFMpegSource", "[source]s[track]i[forceseek]b", CreateFFMpegSource, 0); + return "FFMpegSource"; +}; + diff --git a/FFmpegSource/ffmpegsource.txt b/FFmpegSource/ffmpegsource.txt new file mode 100644 index 000000000..e3809ed78 --- /dev/null +++ b/FFmpegSource/ffmpegsource.txt @@ -0,0 +1,24 @@ +Usage +FFMpegSource(string source, int track = -1, bool forceseek = false) +source: source file +track: video track nubmer as seen by the relevant demuxer, must be a video track + negative values means the first found video track +forceseek: seek even if the format has no registered index, only useful for containers with limited avformat support + +Compatibility +AVI, MKV, MP4, FLV1: Frame accurate +OGM: Messed up first frame and seeking produces smearing with forceseek=true, incredibly slow seeking without +WMV: No seeking +VOB: No rff flags, otherwise it appears to work +TS: Seeking goes way off, number of total frames is probably off too + +Compiling +zlib compiled DLL from http://www.zlib.net/ + +ffmpeg svn from http://ffmpeg.mplayerhq.hu/ + +required configuration +./configure --enable-shared --disable-static --enable-memalign-hack --enable-swscaler --enable-gpl + +suggested additions +--disable-encoders --disable-muxers \ No newline at end of file diff --git a/FFmpegSource/stdiostream.cpp b/FFmpegSource/stdiostream.cpp new file mode 100644 index 000000000..0fa6e0d4f --- /dev/null +++ b/FFmpegSource/stdiostream.cpp @@ -0,0 +1,82 @@ +/* first we need to create an I/O object that the parser will use to read the + * source file + */ +struct StdIoStream { + struct InputStream base; + FILE *fp; + int error; +}; +typedef struct StdIoStream StdIoStream; + +#define CACHESIZE 65536 + +/* StdIoStream methods */ + +/* read count bytes into buffer starting at file position pos + * return the number of bytes read, -1 on error or 0 on EOF + */ +int StdIoRead(StdIoStream *st, ulonglong pos, void *buffer, int count) { + size_t rd; + if (fseek(st->fp, pos, SEEK_SET)) { + st->error = errno; + return -1; + } + rd = fread(buffer, 1, count, st->fp); + if (rd == 0) { + if (feof(st->fp)) + return 0; + st->error = errno; + return -1; + } + return rd; +} + +/* scan for a signature sig(big-endian) starting at file position pos + * return position of the first byte of signature or -1 if error/not found + */ +longlong StdIoScan(StdIoStream *st, ulonglong start, unsigned signature) { + int c; + unsigned cmp = 0; + FILE *fp = st->fp; + + if (fseek(fp, start, SEEK_SET)) + return -1; + + while ((c = getc(fp)) != EOF) { + cmp = ((cmp << 8) | c) & 0xffffffff; + if (cmp == signature) + return ftell(fp) - 4; + } + + return -1; +} + +/* return cache size, this is used to limit readahead */ +unsigned StdIoGetCacheSize(StdIoStream *st) { + return CACHESIZE; +} + +/* return last error message */ +const char *StdIoGetLastError(StdIoStream *st) { + return strerror(st->error); +} + +/* memory allocation, this is done via stdlib */ +void *StdIoMalloc(StdIoStream *st, size_t size) { + return malloc(size); +} + +void *StdIoRealloc(StdIoStream *st, void *mem, size_t size) { + return realloc(mem,size); +} + +void StdIoFree(StdIoStream *st, void *mem) { + free(mem); +} + +/* progress report handler for lengthy operations + * returns 0 to abort operation, nonzero to continue + */ +int StdIoProgress(StdIoStream *st, ulonglong cur, ulonglong max) { + return 1; +} \ No newline at end of file