diff --git a/FFmpegSource2/MatroskaParser.c b/FFmpegSource2/MatroskaParser.c new file mode 100644 index 000000000..d6ff7b320 --- /dev/null +++ b/FFmpegSource2/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/FFmpegSource2/MatroskaParser.h b/FFmpegSource2/MatroskaParser.h new file mode 100644 index 000000000..53dc49918 --- /dev/null +++ b/FFmpegSource2/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/FFmpegSource2/avisynth.h b/FFmpegSource2/avisynth.h new file mode 100644 index 000000000..54aff1cdf --- /dev/null +++ b/FFmpegSource2/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/FFmpegSource2/ffavisynth.cpp b/FFmpegSource2/ffavisynth.cpp new file mode 100644 index 000000000..cdd5ca0fb --- /dev/null +++ b/FFmpegSource2/ffavisynth.cpp @@ -0,0 +1,139 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include "ffavisynth.h" +#include "utils.h" + +AvisynthVideoSource::AvisynthVideoSource(const char *SourceFile, int Track, FrameIndex *TrackIndices, const char *PP, int Threads, int SeekMode, IScriptEnvironment* Env, char *ErrorMsg, unsigned MsgSize) { + SWS = NULL; + ConvertToFormat = PIX_FMT_NONE; + + try { + VS = FFMS_CreateVideoSource(SourceFile, Track, TrackIndices, PP, Threads, SeekMode, ErrorMsg, MsgSize); + } catch (...) { + Env->ThrowError(ErrorMsg); + } + + const VideoProperties VP = *FFMS_GetVideoProperties(VS); + + VI.image_type = VideoInfo::IT_TFF; + VI.width = VP.Width; + VI.height = VP.Height; + VI.fps_denominator = VP.FPSDenominator; + VI.fps_numerator = VP.FPSNumerator; + VI.num_frames = VP.NumFrames; + + try { + InitOutputFormat(VP.PixelFormat, Env); + } catch (AvisynthError &) { + FFMS_DestroyVideoSource(VS); + throw; + } + + // Set AR variables + Env->SetVar("FFSAR_NUM", VP.SARNum); + Env->SetVar("FFSAR_DEN", VP.SARDen); + Env->SetVar("FFSAR", VP.SARNum / (double)VP.SARDen); + + // Set crop variables + Env->SetVar("FFCROP_LEFT", VP.CropLeft); + Env->SetVar("FFCROP_RIGHT", VP.CropRight); + Env->SetVar("FFCROP_TOP", VP.CropTop); + Env->SetVar("FFCROP_BOTTOM", VP.CropBottom); +} + +AvisynthVideoSource::~AvisynthVideoSource() { + if (SWS) + sws_freeContext(SWS); + FFMS_DestroyVideoSource(VS); +} + +void AvisynthVideoSource::InitOutputFormat(int CurrentFormat, IScriptEnvironment *Env) { + int Loss; + int BestFormat = avcodec_find_best_pix_fmt((1 << PIX_FMT_YUVJ420P) | (1 << PIX_FMT_YUV420P) | (1 << PIX_FMT_YUYV422) | (1 << PIX_FMT_RGB32) | (1 << PIX_FMT_BGR24), CurrentFormat, 1 /* Required to prevent pointless RGB32 => RGB24 conversion */, &Loss); + + switch (BestFormat) { + case PIX_FMT_YUVJ420P: // stupid yv12 distinctions, also inexplicably completely undeniably incompatible with all other supported output formats + 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("FFVideoSource: No suitable output format found"); + } + + if (BestFormat != CurrentFormat) { + ConvertToFormat = BestFormat; + SWS = sws_getContext(VI.width, VI.height, CurrentFormat, VI.width, VI.height, ConvertToFormat, GetCPUFlags() | SWS_BICUBIC, NULL, NULL, NULL); + } + + if (BestFormat == PIX_FMT_YUVJ420P || BestFormat == PIX_FMT_YUV420P) { + VI.height -= VI.height & 1; + VI.width -= VI.width & 1; + } + + if (BestFormat == PIX_FMT_YUYV422) { + VI.width -= VI.width & 1; + } +} + +PVideoFrame AvisynthVideoSource::OutputFrame(const AVFrameLite *Frame, IScriptEnvironment *Env) { + // Yes, this function is overly complex and could probably be simplified + AVPicture *SrcPicture = reinterpret_cast(const_cast(Frame)); + 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, SrcPicture->data, SrcPicture->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, SrcPicture->data, SrcPicture->linesize, 0, VI.height, DstData, DstStride); + } else { + uint8_t *DstData[1] = {Dst->GetWritePtr()}; + int DstStride[1] = {Dst->GetPitch()}; + sws_scale(SWS, SrcPicture->data, SrcPicture->linesize, 0, VI.height, DstData, DstStride); + } + } else if (VI.pixel_type == VideoInfo::CS_I420) { + Env->BitBlt(Dst->GetWritePtr(PLANAR_Y), Dst->GetPitch(PLANAR_Y), SrcPicture->data[0], SrcPicture->linesize[0], Dst->GetRowSize(PLANAR_Y), Dst->GetHeight(PLANAR_Y)); + Env->BitBlt(Dst->GetWritePtr(PLANAR_U), Dst->GetPitch(PLANAR_U), SrcPicture->data[1], SrcPicture->linesize[1], Dst->GetRowSize(PLANAR_U), Dst->GetHeight(PLANAR_U)); + Env->BitBlt(Dst->GetWritePtr(PLANAR_V), Dst->GetPitch(PLANAR_V), SrcPicture->data[2], SrcPicture->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(), SrcPicture->data[0], SrcPicture->linesize[0], Dst->GetRowSize(), Dst->GetHeight()); + else + Env->BitBlt(Dst->GetWritePtr(), Dst->GetPitch(), SrcPicture->data[0], SrcPicture->linesize[0], Dst->GetRowSize(), Dst->GetHeight()); + } + + return Dst; +} + +PVideoFrame AvisynthVideoSource::GetFrame(int n, IScriptEnvironment *Env) { + char ErrorMsg[1024]; + unsigned MsgSize = sizeof(ErrorMsg); + const AVFrameLite *Frame = FFMS_GetFrame(VS, n, ErrorMsg, MsgSize); + if (Frame == NULL) + Env->ThrowError("FFVideoSource: %s", ErrorMsg); + + Env->SetVar("FFPICT_TYPE", Frame->PictType); + return OutputFrame(Frame, Env); +} diff --git a/FFmpegSource2/ffavisynth.h b/FFmpegSource2/ffavisynth.h new file mode 100644 index 000000000..91660cc8e --- /dev/null +++ b/FFmpegSource2/ffavisynth.h @@ -0,0 +1,55 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef FFAVISYNTH_H +#define FFAVISYNTH_H + +extern "C" { +#include +#include +#include +#include +} + +#include +#include "avisynth.h" +#include "ffms.h" + +class AvisynthVideoSource : public IClip { +private: + VideoInfo VI; + VideoBase *VS; + SwsContext *SWS; + int ConvertToFormat; + + void InitOutputFormat(int CurrentFormat, IScriptEnvironment *Env); + PVideoFrame OutputFrame(const AVFrameLite *SrcPicture, IScriptEnvironment *Env); +public: + AvisynthVideoSource(const char *SourceFile, int Track, FrameIndex *TrackIndices, const char *PP, int Threads, int SeekMode, IScriptEnvironment* Env, char *ErrorMsg, unsigned MsgSize); + ~AvisynthVideoSource(); + int GetTrack() { return FFMS_GetVSTrack(VS); } + bool __stdcall GetParity(int n) { return false; } + void __stdcall SetCacheHints(int cachehints, int frame_range) { } + const VideoInfo& __stdcall GetVideoInfo() { return VI; } + void __stdcall GetAudio(void* Buf, __int64 Start, __int64 Count, IScriptEnvironment *Env) { } + PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment *Env); +}; + +#endif diff --git a/FFmpegSource2/ffavsfilters.cpp b/FFmpegSource2/ffavsfilters.cpp new file mode 100644 index 000000000..1003d5839 --- /dev/null +++ b/FFmpegSource2/ffavsfilters.cpp @@ -0,0 +1,150 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include +#include "ffms.h" +#include "ffavisynth.h" +#include "ffswscale.h" +#include "ffpp.h" +#include "utils.h" + +AVSValue __cdecl CreateFFIndex(AVSValue Args, void* UserData, IScriptEnvironment* Env) { + if (!UserData) { + av_register_all(); + UserData = (void *)-1; + } + + char ErrorMsg[1024]; + unsigned MsgSize = sizeof(ErrorMsg); + + + if (!Args[0].Defined()) + Env->ThrowError("FFIndex: No source specified"); + + const char *Source = Args[0].AsString(); + const char *CacheFile = Args[1].AsString(""); + int TrackMask = Args[2].AsInt(0); + const char *AudioFile = Args[3].AsString(""); + bool OverWrite = Args[4].AsBool(false); + + std::string DefaultCache(Source); + DefaultCache.append(".ffindex"); + if (!strcmp(CacheFile, "")) + CacheFile = DefaultCache.c_str(); + + if (!strcmp(AudioFile, "")) + AudioFile = Source; + + // Return values + // 0: Index already present + // 1: Index generated + // 2: Index forced to be overwritten + + FrameIndex *Index = FFMS_CreateFrameIndex(); + if (OverWrite || FFMS_ReadIndex(CacheFile, Index, ErrorMsg, MsgSize)) { + if (FFMS_MakeIndex(Source, Index, TrackMask, AudioFile, ErrorMsg, MsgSize)) + Env->ThrowError("FFIndex: %s", ErrorMsg); + if (FFMS_WriteIndex(CacheFile, Index, ErrorMsg, MsgSize)) + Env->ThrowError("FFIndex: %s", ErrorMsg); + FFMS_DestroyFrameIndex(Index); + if (!OverWrite) + return AVSValue(1); + else + return AVSValue(2); + } else { + FFMS_DestroyFrameIndex(Index); + return AVSValue(0); + } +} + +AVSValue __cdecl CreateFFVideoSource(AVSValue Args, void* UserData, IScriptEnvironment* Env) { + if (!UserData) { + av_register_all(); + UserData = (void *)-1; + } + + char ErrorMsg[1024]; + unsigned MsgSize = sizeof(ErrorMsg); + + if (!Args[0].Defined()) + Env->ThrowError("FFVideoSource: No source specified"); + + const char *Source = Args[0].AsString(); + int Track = Args[1].AsInt(-1); + bool Cache = Args[2].AsBool(true); + const char *CacheFile = Args[3].AsString(""); + const char *PP = Args[4].AsString(""); + int Threads = Args[5].AsInt(-1); + const char *Timecodes = Args[6].AsString(""); + int SeekMode = Args[7].AsInt(1); + + if (Track <= -2) + Env->ThrowError("FFVideoSource: No video track selected"); + + if (SeekMode < -1 || SeekMode > 3) + Env->ThrowError("FFVideoSource: Invalid seekmode selected"); + + if (Threads <= 0) + Threads = GetNumberOfLogicalCPUs(); + if (Threads < 1) + Env->ThrowError("FFVideoSource: Invalid thread count"); + + std::string DefaultCache(Source); + DefaultCache.append(".ffindex"); + if (!strcmp(CacheFile, "")) + CacheFile = DefaultCache.c_str(); + + FrameIndex *Index = FFMS_CreateFrameIndex(); + if (Cache) { + if (FFMS_ReadIndex(CacheFile, Index, ErrorMsg, MsgSize)) { + if (FFMS_MakeIndex(Source, Index, 0, NULL, ErrorMsg, MsgSize)) + Env->ThrowError("FFVideoSource: %s", ErrorMsg); + + if (Cache) + if (FFMS_WriteIndex(CacheFile, Index, ErrorMsg, MsgSize)) + Env->ThrowError("FFVideoSource: %s", ErrorMsg); + } + } + + AvisynthVideoSource *Filter = new AvisynthVideoSource(Source, Track, Index, PP, Threads, SeekMode, Env, ErrorMsg, MsgSize); + + if (strcmp(Timecodes, "")) + if (FFMS_WriteTimecodes(FFMS_GetTITrackIndex(Index, Filter->GetTrack(), ErrorMsg, MsgSize), Timecodes, ErrorMsg, MsgSize)) + Env->ThrowError("FFVideoSource: %s", ErrorMsg); + + return Filter; +} + +AVSValue __cdecl CreateFFPP(AVSValue Args, void* UserData, IScriptEnvironment* Env) { + return new FFPP(Args[0].AsClip(), Args[1].AsString(""), Env); +} + +AVSValue __cdecl CreateSWScale(AVSValue Args, void* UserData, IScriptEnvironment* Env) { + return new SWScale(Args[0].AsClip(), Args[1].AsInt(0), Args[2].AsInt(0), Args[3].AsString("BICUBIC"), Args[4].AsString(""), Env); +} + +extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* Env) { + Env->AddFunction("FFIndex", "[source]s[cachefile]s[trackmask]i[audiofile]s[overwrite]b", CreateFFIndex, 0); + Env->AddFunction("FFVideoSource", "[source]s[track]i[cache]b[cachefile]s[pp]s[threads]i[timecodes]s[seekmode]i", CreateFFVideoSource, 0); + Env->AddFunction("FFPP", "c[pp]s", CreateFFPP, 0); + Env->AddFunction("SWScale", "c[width]i[height]i[resizer]s[colorspace]s", CreateSWScale, 0); + + return "FFmpegSource - The Second Coming"; +} diff --git a/FFmpegSource2/ffms.cpp b/FFmpegSource2/ffms.cpp new file mode 100644 index 000000000..27b4d247a --- /dev/null +++ b/FFmpegSource2/ffms.cpp @@ -0,0 +1,123 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include "ffms.h" +#include "ffvideosource.h" +#include "indexing.h" + +FFMS_API(VideoBase *) FFMS_CreateVideoSource(const char *SourceFile, int Track, FrameIndex *TrackIndices, const char *PP, int Threads, int SeekMode, char *ErrorMsg, unsigned MsgSize) { + switch (TrackIndices->Decoder) { + case 0: return new FFVideoSource(SourceFile, Track, TrackIndices, PP, Threads, SeekMode, ErrorMsg, MsgSize); + case 1: return new MatroskaVideoSource(SourceFile, Track, TrackIndices, PP, Threads, ErrorMsg, MsgSize); + default: return NULL; + } +} + +FFMS_API(void) FFMS_DestroyVideoSource(VideoBase *VB) { + delete VB; +} + +FFMS_API(int) FFMS_GetVSTrack(VideoBase *VB) { + return VB->GetTrack(); +} + +FFMS_API(const VideoProperties *) FFMS_GetVideoProperties(VideoBase *VB) { + return &VB->GetVideoProperties(); +} + +FFMS_API(const AVFrameLite *) FFMS_GetFrame(VideoBase *VB, int n, char *ErrorMsg, unsigned MsgSize) { + return (AVFrameLite *)VB->GetFrame(n, ErrorMsg, MsgSize); +} + +FFMS_API(FrameIndex *) FFMS_CreateFrameIndex() { + return new FrameIndex(); +} + +FFMS_API(void) FFMS_DestroyFrameIndex(FrameIndex *FI) { + delete FI; +} + +FFMS_API(int) FFMS_GetNumTracks(FrameIndex *TrackIndices, char *ErrorMsg, unsigned MsgSize) { + return TrackIndices->size(); +} + +FFMS_API(int) FFMS_GetNumFrames(FrameInfoVector *FIV, char *ErrorMsg, unsigned MsgSize) { + return FIV->size(); +} + +FFMS_API(const FrameInfo *) FFMS_GetFrameInfo(FrameInfoVector *FIV, int Frame, char *ErrorMsg, unsigned MsgSize) { + if (Frame < 0 || Frame >= FIV->size()) { + _snprintf(ErrorMsg, MsgSize, "Invalid frame specified"); + return NULL; + } else { + return &(*FIV)[Frame]; + } +} + +FFMS_API(FrameInfoVector *) FFMS_GetTITrackIndex(FrameIndex *TrackIndices, int Track, char *ErrorMsg, unsigned MsgSize) { + if (Track < 0 || Track >= TrackIndices->size()) { + _snprintf(ErrorMsg, MsgSize, "Invalid track specified"); + return NULL; + } else { + return &(*TrackIndices)[Track]; + } +} + +FFMS_API(FrameInfoVector *) FFMS_GetVSTrackIndex(VideoBase *VB, char *ErrorMsg, unsigned MsgSize) { + return VB->GetFrameInfoVector(); +} + +FFMS_API(int) FFMS_FindClosestKeyFrame(FrameInfoVector *FIV, int Frame, char *ErrorMsg, unsigned MsgSize) { + if (Frame < 0 || Frame >= FIV->size()) { + _snprintf(ErrorMsg, MsgSize, "Out of range frame specified"); + return -1; + } else { + return FIV->FindClosestKeyFrame(Frame); + } +} + +FFMS_API(int) FFMS_FrameFromDTS(FrameInfoVector *FIV, int64_t DTS, char *ErrorMsg, unsigned MsgSize) { + return FIV->FrameFromDTS(DTS); +} + +FFMS_API(int) FFMS_ClosestFrameFromDTS(FrameInfoVector *FIV, int64_t DTS, char *ErrorMsg, unsigned MsgSize) { + return FIV->ClosestFrameFromDTS(DTS); +} + +FFMS_API(const TrackTimeBase *) FFMS_GetTimeBase(FrameInfoVector *FIV, char *ErrorMsg, unsigned MsgSize) { + return &FIV->TB; +} + +FFMS_API(int) FFMS_WriteTimecodes(FrameInfoVector *FIV, const char *TimecodeFile, char *ErrorMsg, unsigned MsgSize) { + return FIV->WriteTimecodes(TimecodeFile, ErrorMsg, MsgSize); +} + +FFMS_API(int) FFMS_MakeIndex(const char *SourceFile, FrameIndex *TrackIndices, int AudioTrackMask, const char *AudioFile, char *ErrorMsg, unsigned MsgSize) { + return MakeIndex(SourceFile, TrackIndices, AudioTrackMask, AudioFile, ErrorMsg, MsgSize); +} + +FFMS_API(int) FFMS_ReadIndex(const char *IndexFile, FrameIndex *TrackIndices, char *ErrorMsg, unsigned MsgSize) { + return ReadIndex(IndexFile, TrackIndices, ErrorMsg, MsgSize); +} + +FFMS_API(int) FFMS_WriteIndex(const char *IndexFile, FrameIndex *TrackIndices, char *ErrorMsg, unsigned MsgSize) { + return WriteIndex(IndexFile, TrackIndices, ErrorMsg, MsgSize); +} + diff --git a/FFmpegSource2/ffms.h b/FFmpegSource2/ffms.h new file mode 100644 index 000000000..c39e3388e --- /dev/null +++ b/FFmpegSource2/ffms.h @@ -0,0 +1,104 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef FFMS_H +#define FFMS_H + +#include + +#define FFMS_EXPORTS + +#ifdef __cplusplus +# define EXTERN_C extern "C" +#else +# define EXTERN_C +#endif + +#ifdef FFMS_EXPORTS +# define FFMS_API(ret) EXTERN_C __declspec(dllexport) ret __stdcall +#else +# define FFMS_API(ret) EXTERN_C __declspec(dllimport) ret __stdcall +#endif + +class VideoBase; +class FrameIndex; +class FrameInfoVector; + +// This is a subset of the original AVFrame only containing the most used parts. +// Even if it might seem like a good idea to cast it back to a full AVFrame to +// access a few more values you really shouldn't do that. Only the values present +// in AVFrameLite are actually updated when postprocessing is used. + +struct AVFrameLite { + uint8_t *Data[4]; + int Linesize[4]; + uint8_t *Base[4]; + int KeyFrame; + int PictType; +}; + +struct TrackTimeBase { + int Num; + int Den; +}; + +struct FrameInfo { + int64_t DTS; + bool KeyFrame; + FrameInfo(int64_t DTS, bool KeyFrame); +}; + +struct VideoProperties { +public: + int Width; + int Height; + int FPSDenominator; + int FPSNumerator; + int NumFrames; + int PixelFormat; + int SARNum; + int SARDen; + int CropTop; + int CropBottom; + int CropLeft; + int CropRight; +}; + +FFMS_API(VideoBase *) FFMS_CreateVideoSource(const char *SourceFile, int Track, FrameIndex *TrackIndices, const char *PP, int Threads, int SeekMode, char *ErrorMsg, unsigned MsgSize); +FFMS_API(void) FFMS_DestroyVideoSource(VideoBase *VB); +FFMS_API(int) FFMS_GetVSTrack(VideoBase *VB); +FFMS_API(const VideoProperties *) FFMS_GetVideoProperties(VideoBase *VB); +FFMS_API(const AVFrameLite *) FFMS_GetFrame(VideoBase *VB, int n, char *ErrorMsg, unsigned MsgSize); +FFMS_API(FrameIndex *) FFMS_CreateFrameIndex(); +FFMS_API(void) FFMS_DestroyFrameIndex(FrameIndex *FI); +FFMS_API(int) FFMS_GetNumTracks(FrameIndex *TrackIndices, char *ErrorMsg, unsigned MsgSize); +FFMS_API(const FrameInfo *) FFMS_GetFrameInfo(FrameInfoVector *FIV, int Frame, char *ErrorMsg, unsigned MsgSize); +FFMS_API(FrameInfoVector *) FFMS_GetTITrackIndex(FrameIndex *TrackIndices, int Track, char *ErrorMsg, unsigned MsgSize); +FFMS_API(FrameInfoVector *) FFMS_GetVSTrackIndex(VideoBase *VB, char *ErrorMsg, unsigned MsgSize); +FFMS_API(int) FFMS_FindClosestKeyFrame(FrameInfoVector *FIV, int Frame, char *ErrorMsg, unsigned MsgSize); +FFMS_API(int) FFMS_FrameFromDTS(FrameInfoVector *FIV, int64_t DTS, char *ErrorMsg, unsigned MsgSize); +FFMS_API(int) FFMS_ClosestFrameFromDTS(FrameInfoVector *FIV, int64_t DTS, char *ErrorMsg, unsigned MsgSize); +FFMS_API(const TrackTimeBase *) FFMS_GetTimeBase(FrameInfoVector *FIV, char *ErrorMsg, unsigned MsgSize); +FFMS_API(int) FFMS_WriteTimecodes(FrameInfoVector *FIV, const char *TimecodeFile, char *ErrorMsg, unsigned MsgSize); +FFMS_API(int) FFMS_MakeIndex(const char *SourceFile, FrameIndex *TrackIndices, int AudioTrackMask, const char *AudioFile, char *ErrorMsg, unsigned MsgSize); +FFMS_API(int) FFMS_ReadIndex(const char *IndexFile, FrameIndex *TrackIndices, char *ErrorMsg, unsigned MsgSize); +FFMS_API(int) FFMS_WriteIndex(const char *IndexFile, FrameIndex *TrackIndices, char *ErrorMsg, unsigned MsgSize); + +#endif diff --git a/FFmpegSource2/ffpp.cpp b/FFmpegSource2/ffpp.cpp new file mode 100644 index 000000000..f87e4eca7 --- /dev/null +++ b/FFmpegSource2/ffpp.cpp @@ -0,0 +1,96 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include "ffpp.h" +#include "utils.h" + +FFPP::FFPP(PClip AChild, const char *PP, IScriptEnvironment *Env) : GenericVideoFilter(AChild) { + if (!strcmp(PP, "")) + Env->ThrowError("FFPP: PP argument is empty"); + + PPContext = NULL; + PPMode = NULL; + SWSTo422P = NULL; + SWSFrom422P = NULL; + + memset(&InputPicture, 0, sizeof(InputPicture)); + memset(&OutputPicture, 0, sizeof(OutputPicture)); + + PPMode = pp_get_mode_by_name_and_quality((char *)PP, PP_QUALITY_MAX); + if (!PPMode) + Env->ThrowError("FFPP: Invalid postprocesing settings"); + + int Flags = GetCPUFlags(); + + if (vi.IsYV12()) { + Flags |= PP_FORMAT_420; + } else if (vi.IsYUY2()) { + Flags |= PP_FORMAT_422; + SWSTo422P = sws_getContext(vi.width, vi.height, PIX_FMT_YUV422, vi.width, vi.height, PIX_FMT_YUV422P, GetCPUFlags() | SWS_BICUBIC, NULL, NULL, NULL); + SWSFrom422P = sws_getContext(vi.width, vi.height, PIX_FMT_YUV422P, vi.width, vi.height, PIX_FMT_YUV422, GetCPUFlags() | SWS_BICUBIC, NULL, NULL, NULL); + avpicture_alloc(&InputPicture, PIX_FMT_YUV422P, vi.width, vi.height); + avpicture_alloc(&OutputPicture, PIX_FMT_YUV422P, vi.width, vi.height); + } else { + Env->ThrowError("FFPP: Only YV12 and YUY2 video supported"); + } + + PPContext = pp_get_context(vi.width, vi.height, Flags); + if (!PPContext) + Env->ThrowError("FFPP: Failed to create context"); +} + +FFPP::~FFPP() { + if (PPMode) + pp_free_mode(PPMode); + if (PPContext) + pp_free_context(PPContext); + if (SWSTo422P) + sws_freeContext(SWSTo422P); + if (SWSFrom422P) + sws_freeContext(SWSFrom422P); + avpicture_free(&InputPicture); + avpicture_free(&OutputPicture); +} + +PVideoFrame FFPP::GetFrame(int n, IScriptEnvironment* Env) { + PVideoFrame Src = child->GetFrame(n, Env); + PVideoFrame Dst = Env->NewVideoFrame(vi); + + if (vi.IsYV12()) { + const uint8_t *SrcData[3] = {(uint8_t *)Src->GetReadPtr(PLANAR_Y), (uint8_t *)Src->GetReadPtr(PLANAR_U), (uint8_t *)Src->GetReadPtr(PLANAR_V)}; + int SrcStride[3] = {Src->GetPitch(PLANAR_Y), Src->GetPitch(PLANAR_U), Src->GetPitch(PLANAR_V)}; + 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)}; + + pp_postprocess(SrcData, SrcStride, DstData, DstStride, vi.width, vi.height, NULL, 0, PPMode, PPContext, 0); + } else if (vi.IsYUY2()) { + uint8_t *SrcData[1] = {(uint8_t *)Src->GetReadPtr()}; + int SrcStride[1] = {Src->GetPitch()}; + sws_scale(SWSTo422P, SrcData, SrcStride, 0, vi.height, InputPicture.data, InputPicture.linesize); + + pp_postprocess(const_cast(InputPicture.data), InputPicture.linesize, OutputPicture.data, OutputPicture.linesize, vi.width, vi.height, NULL, 0, PPMode, PPContext, 0); + + uint8_t *DstData[1] = {Dst->GetWritePtr()}; + int DstStride[1] = {Dst->GetPitch()}; + sws_scale(SWSFrom422P, OutputPicture.data, OutputPicture.linesize, 0, vi.height, DstData, DstStride); + } + + return Dst; +} diff --git a/FFmpegSource2/ffpp.h b/FFmpegSource2/ffpp.h new file mode 100644 index 000000000..0f8f3bbb6 --- /dev/null +++ b/FFmpegSource2/ffpp.h @@ -0,0 +1,47 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef FFPP_H +#define FFPP_H + +extern "C" { +#include +#include +#include +} + +#include +#include "avisynth.h" + +class FFPP : public GenericVideoFilter { +private: + pp_context_t *PPContext; + pp_mode_t *PPMode; + SwsContext *SWSTo422P; + SwsContext *SWSFrom422P; + AVPicture InputPicture; + AVPicture OutputPicture; +public: + FFPP(PClip AChild, const char *PP, IScriptEnvironment *Env); + ~FFPP(); + PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* Env); +}; + +#endif diff --git a/FFmpegSource2/ffswscale.cpp b/FFmpegSource2/ffswscale.cpp new file mode 100644 index 000000000..c3334bb59 --- /dev/null +++ b/FFmpegSource2/ffswscale.cpp @@ -0,0 +1,102 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include "ffswscale.h" +#include "utils.h" + +SWScale::SWScale(PClip Child, int ResizeToWidth, int ResizeToHeight, const char *ResizerName, const char *ConvertToFormatName, IScriptEnvironment *Env) : GenericVideoFilter(Child) { + Context = NULL; + OrigWidth = vi.width; + OrigHeight = vi.height; + FlipOutput = vi.IsYUV(); + + int ConvertFromFormat = PIX_FMT_NONE; + if (vi.IsYV12()) + ConvertFromFormat = PIX_FMT_YUV420P; + if (vi.IsYUY2()) + ConvertFromFormat = PIX_FMT_YUYV422; + if (vi.IsRGB24()) + ConvertFromFormat = PIX_FMT_BGR24; + if (vi.IsRGB32()) + ConvertFromFormat = PIX_FMT_RGB32; + + if (ResizeToHeight <= 0) + ResizeToHeight = OrigHeight; + else + vi.height = ResizeToHeight; + + if (ResizeToWidth <= 0) + ResizeToWidth = OrigWidth; + else + vi.width = ResizeToWidth; + + int ConvertToFormat = CSNameToPIXFMT(ConvertToFormatName, ConvertFromFormat); + if (ConvertToFormat == PIX_FMT_NONE) + Env->ThrowError("SWScale: Invalid colorspace specified (%s)", ConvertToFormatName); + + switch (ConvertToFormat) { + 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_BGR24: vi.pixel_type = VideoInfo::CS_BGR24; break; + case PIX_FMT_RGB32: vi.pixel_type = VideoInfo::CS_BGR32; break; + } + + FlipOutput ^= vi.IsYUV(); + + int Resizer = ResizerNameToSWSResizer(ResizerName); + if (Resizer == 0) + Env->ThrowError("SWScale: Invalid resizer specified (%s)", ResizerName); + + if (ConvertToFormat == PIX_FMT_YUV420P && vi.height & 1) + Env->ThrowError("SWScale: mod 2 output height required"); + + if ((ConvertToFormat == PIX_FMT_YUV420P || ConvertToFormat == PIX_FMT_YUYV422) && vi.width & 1) + Env->ThrowError("SWScale: mod 2 output width required"); + + // may one day need a SWS_CS_DEFAULT in flags + Context = sws_getContext(OrigWidth, OrigHeight, ConvertFromFormat, vi.width, vi.height, ConvertToFormat, GetCPUFlags() | Resizer, NULL, NULL, NULL); + if (Context == NULL) + Env->ThrowError("SWScale: Context creation failed"); +} + +SWScale::~SWScale() { + if (Context) + sws_freeContext(Context); +} + +PVideoFrame SWScale::GetFrame(int n, IScriptEnvironment *Env) { + PVideoFrame Src = child->GetFrame(n, Env); + PVideoFrame Dst = Env->NewVideoFrame(vi); + + uint8_t *SrcData[3] = {(uint8_t *)Src->GetReadPtr(PLANAR_Y), (uint8_t *)Src->GetReadPtr(PLANAR_U), (uint8_t *)Src->GetReadPtr(PLANAR_V)}; + int SrcStride[3] = {Src->GetPitch(PLANAR_Y), Src->GetPitch(PLANAR_U), Src->GetPitch(PLANAR_V)}; + + if (FlipOutput) { + uint8_t *DstData[3] = {Dst->GetWritePtr(PLANAR_Y) + Dst->GetPitch(PLANAR_Y) * (Dst->GetHeight(PLANAR_Y) - 1), Dst->GetWritePtr(PLANAR_U) + Dst->GetPitch(PLANAR_U) * (Dst->GetHeight(PLANAR_U) - 1), Dst->GetWritePtr(PLANAR_V) + Dst->GetPitch(PLANAR_V) * (Dst->GetHeight(PLANAR_V) - 1)}; + int DstStride[3] = {-Dst->GetPitch(PLANAR_Y), -Dst->GetPitch(PLANAR_U), -Dst->GetPitch(PLANAR_V)}; + sws_scale(Context, SrcData, SrcStride, 0, OrigHeight, DstData, DstStride); + } else { + 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(Context, SrcData, SrcStride, 0, OrigHeight, DstData, DstStride); + } + + return Dst; +} diff --git a/FFmpegSource2/ffswscale.h b/FFmpegSource2/ffswscale.h new file mode 100644 index 000000000..04f47658d --- /dev/null +++ b/FFmpegSource2/ffswscale.h @@ -0,0 +1,43 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef FFSWSCALE_H +#define FFSWSCALE_H + +extern "C" { +#include +} + +#include +#include "avisynth.h" + +class SWScale : public GenericVideoFilter { +private: + SwsContext *Context; + int OrigWidth; + int OrigHeight; + bool FlipOutput; +public: + SWScale(PClip Child, int ResizeToWidth, int ResizeToHeight, const char *ResizerName, const char *ConvertToFormatName, IScriptEnvironment *Env); + ~SWScale(); + PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment *Env); +}; + +#endif diff --git a/FFmpegSource2/ffvideosource.cpp b/FFmpegSource2/ffvideosource.cpp new file mode 100644 index 000000000..9995ae838 --- /dev/null +++ b/FFmpegSource2/ffvideosource.cpp @@ -0,0 +1,526 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include "ffvideosource.h" + +int VideoBase::InitPP(const char *PP, int PixelFormat, char *ErrorMsg, unsigned MsgSize) { + if (!strcmp(PP, "")) + return 0; + + PPMode = pp_get_mode_by_name_and_quality(PP, PP_QUALITY_MAX); + if (!PPMode) { + _snprintf(ErrorMsg, MsgSize, "Invalid postprocesing settings"); + return 1; + } + + int Flags = GetCPUFlags(); + + switch (PixelFormat) { + case PIX_FMT_YUV420P: Flags |= PP_FORMAT_420; break; + case PIX_FMT_YUV422P: Flags |= PP_FORMAT_422; break; + case PIX_FMT_YUV411P: Flags |= PP_FORMAT_411; break; + case PIX_FMT_YUV444P: Flags |= PP_FORMAT_444; break; + default: + _snprintf(ErrorMsg, MsgSize, "Input format is not supported for postprocessing"); + return 2; + } + + PPContext = pp_get_context(VP.Width, VP.Height, Flags); + + if (!(PPFrame = avcodec_alloc_frame())) { + _snprintf(ErrorMsg, MsgSize, "Failed to allocate temporary frame"); + return 3; + } + + if (avpicture_alloc((AVPicture *)PPFrame, PixelFormat, VP.Width, VP.Height) < 0) { + _snprintf(ErrorMsg, MsgSize, "Failed to allocate picture"); + return 4; + } + + return 0; +} + +AVFrameLite *VideoBase::OutputFrame(AVFrame *Frame) { + if (PPContext) { + pp_postprocess(const_cast(Frame->data), Frame->linesize, PPFrame->data, PPFrame->linesize, VP.Width, VP.Height, Frame->qscale_table, Frame->qstride, PPMode, PPContext, Frame->pict_type | (Frame->qscale_type ? PP_PICT_TYPE_QP2 : 0)); + PPFrame->key_frame = Frame->key_frame; + PPFrame->pict_type = Frame->pict_type; + return reinterpret_cast(PPFrame); + } else { + return reinterpret_cast(Frame); + } +} + +VideoBase::VideoBase() { + memset(&VP, 0, sizeof(VP)); + PPContext = NULL; + PPMode = NULL; + LastFrameNum = -1; + CurrentFrame = 0; + CodecContext = NULL; + DecodeFrame = avcodec_alloc_frame(); + PPFrame = DecodeFrame; +} + +VideoBase::~VideoBase() { + if (PPMode) + pp_free_mode(PPMode); + + if (PPContext) + pp_free_context(PPContext); + + if (PPFrame != DecodeFrame) { + avpicture_free((AVPicture *)PPFrame); + av_free(PPFrame); + } + + av_free(DecodeFrame); +} + +AVFrame *GetFrameByTime(double Time, char *ErrorMsg, unsigned MsgSize) { + + //Frames.ClosestFrameFromDTS(); + //return GetFrame(, ErrorMsg, MsgSize); + return NULL; +} + +int FFVideoSource::GetTrackIndex(int &Index, char *ErrorMsg, unsigned MsgSize) { + if (Index < 0) { + Index = -1; + for (unsigned int i = 0; i < FormatContext->nb_streams; i++) + if (FormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO) { + Index = i; + break; + } + } + + if (Index < 0) { + _snprintf(ErrorMsg, MsgSize, "No video track found"); + return 1; + } + + if (Index >= (int)FormatContext->nb_streams) { + _snprintf(ErrorMsg, MsgSize, "Invalid video track number"); + return 2; + } + + if (FormatContext->streams[Index]->codec->codec_type != CODEC_TYPE_VIDEO) { + _snprintf(ErrorMsg, MsgSize, "Selected track is not video"); + return 3; + } + + return 0; +} + +FFVideoSource::FFVideoSource(const char *SourceFile, int Track, FrameIndex *TrackIndices, + const char *PP, int Threads, int SeekMode, char *ErrorMsg, unsigned MsgSize) { + + FormatContext = NULL; + AVCodec *Codec = NULL; + this->SeekMode = SeekMode; + + if (av_open_input_file(&FormatContext, SourceFile, NULL, 0, NULL) != 0) { + _snprintf(ErrorMsg, MsgSize, "Couldn't open '%s'", SourceFile); + throw ErrorMsg; + } + + if (av_find_stream_info(FormatContext) < 0) { + _snprintf(ErrorMsg, MsgSize, "Couldn't find stream information"); + throw ErrorMsg; + } + + VideoTrack = Track; + if (GetTrackIndex(VideoTrack, ErrorMsg, MsgSize)) + throw ErrorMsg; + + Frames = (*TrackIndices)[VideoTrack]; + + if (Frames.size() == 0) { + _snprintf(ErrorMsg, MsgSize, "Video track contains no frames"); + throw ErrorMsg; + } + + if (SeekMode >= 0 && av_seek_frame(FormatContext, VideoTrack, Frames[0].DTS, AVSEEK_FLAG_BACKWARD) < 0) { + _snprintf(ErrorMsg, MsgSize, "Video track is unseekable"); + throw ErrorMsg; + } + + CodecContext = FormatContext->streams[VideoTrack]->codec; + CodecContext->thread_count = Threads; + + Codec = avcodec_find_decoder(CodecContext->codec_id); + if (Codec == NULL) { + _snprintf(ErrorMsg, MsgSize, "Video codec not found"); + throw ErrorMsg; + } + + if (avcodec_open(CodecContext, Codec) < 0) { + _snprintf(ErrorMsg, MsgSize, "Could not open video codec"); + throw ErrorMsg; + } + + // Always try to decode a frame to make sure all required parameters are known + int64_t Dummy; + if (DecodeNextFrame(DecodeFrame, &Dummy, ErrorMsg, MsgSize)) + throw ErrorMsg; + + //VP.image_type = VideoInfo::IT_TFF; + VP.Width = CodecContext->width; + VP.Height = CodecContext->height; + VP.FPSDenominator = FormatContext->streams[VideoTrack]->time_base.num; + VP.FPSNumerator = FormatContext->streams[VideoTrack]->time_base.den; + VP.NumFrames = Frames.size(); + VP.PixelFormat = CodecContext->pix_fmt; + + if (VP.Width <= 0 || VP.Height <= 0) { + _snprintf(ErrorMsg, MsgSize, "Codec returned zero size video"); + throw ErrorMsg; + } + + // sanity check framerate + if (VP.FPSDenominator > VP.FPSNumerator || VP.FPSDenominator <= 0 || VP.FPSNumerator <= 0) { + VP.FPSDenominator = 1; + VP.FPSNumerator = 30; + } + + InitPP(PP, CodecContext->pix_fmt, ErrorMsg, MsgSize); + + // Adjust framerate to match the duration of the first frame + if (Frames.size() >= 2) { + unsigned int DTSDiff = (unsigned int)FFMAX(Frames[1].DTS - Frames[0].DTS, 1); + VP.FPSDenominator *= DTSDiff; + } + + // Cannot "output" to PPFrame without doing all other initialization + // This is the additional mess required for seekmode=-1 to work in a reasonable way + OutputFrame(DecodeFrame); + LastFrameNum = 0; + + + // Set AR variables + VP.SARNum = CodecContext->sample_aspect_ratio.num; + VP.SARDen = CodecContext->sample_aspect_ratio.den; +} + +FFVideoSource::~FFVideoSource() { + if (VideoTrack >= 0) + avcodec_close(CodecContext); + av_close_input_file(FormatContext); +} + +int FFVideoSource::DecodeNextFrame(AVFrame *AFrame, int64_t *AStartTime, char *ErrorMsg, unsigned MsgSize) { + AVPacket Packet; + int FrameFinished = 0; + *AStartTime = -1; + + while (av_read_frame(FormatContext, &Packet) >= 0) { + if (Packet.stream_index == VideoTrack) { + if (*AStartTime < 0) + *AStartTime = Packet.dts; + + avcodec_decode_video(CodecContext, AFrame, &FrameFinished, Packet.data, Packet.size); + } + + av_free_packet(&Packet); + + if (FrameFinished) + goto Done; + } + + // Flush the last frames + if (CodecContext->has_b_frames) + avcodec_decode_video(CodecContext, AFrame, &FrameFinished, NULL, 0); + + if (!FrameFinished) + goto Error; + +// Ignore errors for now +Error: +Done: + return 0; +} + +AVFrameLite *FFVideoSource::GetFrame(int n, char *ErrorMsg, unsigned MsgSize) { + // PPFrame always holds frame LastFrameNum even if no PP is applied + if (LastFrameNum == n) + return reinterpret_cast(PPFrame); + + bool HasSeeked = false; + + if (SeekMode >= 0) { + int ClosestKF = Frames.FindClosestKeyFrame(n); + + if (SeekMode == 0) { + if (n < CurrentFrame) { + av_seek_frame(FormatContext, VideoTrack, Frames[0].DTS, AVSEEK_FLAG_BACKWARD); + avcodec_flush_buffers(CodecContext); + CurrentFrame = 0; + } + } else { + // 10 frames is used as a margin to prevent excessive seeking since the predicted best keyframe isn't always selected by avformat + if (n < CurrentFrame || ClosestKF > CurrentFrame + 10 || (SeekMode == 3 && n > CurrentFrame + 10)) { + av_seek_frame(FormatContext, VideoTrack, (SeekMode == 3) ? Frames[n].DTS : Frames[ClosestKF].DTS, AVSEEK_FLAG_BACKWARD); + avcodec_flush_buffers(CodecContext); + HasSeeked = true; + } + } + } else if (n < CurrentFrame) { + _snprintf(ErrorMsg, MsgSize, "Non-linear access attempted"); + return NULL; + } + + do { + int64_t StartTime; + if (DecodeNextFrame(DecodeFrame, &StartTime, ErrorMsg, MsgSize)) + return NULL; + + if (HasSeeked) { + HasSeeked = false; + + // Is the seek destination time known? Does it belong to a frame? + if (StartTime < 0 || (CurrentFrame = Frames.FrameFromDTS(StartTime)) < 0) { + switch (SeekMode) { + case 1: + _snprintf(ErrorMsg, MsgSize, "Frame accurate seeking is not possible in this file"); + return NULL; + case 2: + case 3: + CurrentFrame = Frames.ClosestFrameFromDTS(StartTime); + break; + default: + _snprintf(ErrorMsg, MsgSize, "Failed assertion"); + return NULL; + } + } + } + + CurrentFrame++; + } while (CurrentFrame <= n); + + LastFrameNum = n; + return OutputFrame(DecodeFrame); +} + + +int MatroskaVideoSource::GetTrackIndex(int &Index, char *ErrorMsg, unsigned MsgSize) { + if (Index < 0) { + Index = -1; + for (unsigned int i = 0; i < mkv_GetNumTracks(MF); i++) + if (mkv_GetTrackInfo(MF, i)->Type == TT_VIDEO) { + Index = i; + break; + } + } + + if (Index < 0) { + _snprintf(ErrorMsg, MsgSize, "No video track found"); + return 1; + } + + if (Index >= (int)mkv_GetNumTracks(MF)) { + _snprintf(ErrorMsg, MsgSize, "Invalid video track number"); + return 2; + } + + if (mkv_GetTrackInfo(MF, Index)->Type != TT_VIDEO) { + _snprintf(ErrorMsg, MsgSize, "Selected track is not video"); + return 3; + } + + return 0; +} + +MatroskaVideoSource::MatroskaVideoSource(const char *SourceFile, int Track, + FrameIndex *TrackIndices, const char *PP, + int Threads, char *ErrorMsg, unsigned MsgSize) { + + unsigned int TrackMask = ~0; + AVCodec *Codec = NULL; + TrackInfo *TI = NULL; + CS = NULL; + + MC.ST.fp = fopen(SourceFile, "rb"); + if (MC.ST.fp == NULL) { + _snprintf(ErrorMsg, MsgSize, "Can't open '%s': %s", SourceFile, strerror(errno)); + throw ErrorMsg; + } + + setvbuf(MC.ST.fp, NULL, _IOFBF, CACHESIZE); + + MF = mkv_OpenEx(&MC.ST.base, 0, 0, ErrorMessage, sizeof(ErrorMessage)); + if (MF == NULL) { + fclose(MC.ST.fp); + _snprintf(ErrorMsg, MsgSize, "Can't parse Matroska file: %s", ErrorMessage); + throw ErrorMsg; + } + + VideoTrack = Track; + if (GetTrackIndex(VideoTrack, ErrorMsg, MsgSize)) + throw ErrorMsg; + + Frames = (*TrackIndices)[VideoTrack]; + + if (Frames.size() == 0) { + _snprintf(ErrorMsg, MsgSize, "Video track contains no frames"); + throw ErrorMsg; + } + + mkv_SetTrackMask(MF, ~(1 << VideoTrack)); + TI = mkv_GetTrackInfo(MF, VideoTrack); + + if (TI->CompEnabled) { + CS = cs_Create(MF, VideoTrack, ErrorMessage, sizeof(ErrorMessage)); + if (CS == NULL) { + _snprintf(ErrorMsg, MsgSize, "Can't create decompressor: %s", ErrorMessage); + throw ErrorMsg; + } + } + + CodecContext = avcodec_alloc_context(); + CodecContext->extradata = (uint8_t *)TI->CodecPrivate; + CodecContext->extradata_size = TI->CodecPrivateSize; + CodecContext->thread_count = Threads; + + Codec = avcodec_find_decoder(MatroskaToFFCodecID(TI)); + if (Codec == NULL) { + _snprintf(ErrorMsg, MsgSize, "Video codec not found"); + throw ErrorMsg; + } + + if (avcodec_open(CodecContext, Codec) < 0) { + _snprintf(ErrorMsg, MsgSize, "Could not open video codec"); + throw ErrorMsg; + } + + // Always try to decode a frame to make sure all required parameters are known + int64_t Dummy; + if (DecodeNextFrame(DecodeFrame, &Dummy, ErrorMsg, MsgSize)) + throw ErrorMsg; + + VP.Width = CodecContext->width; + VP.Height = CodecContext->height;; + VP.FPSDenominator = 1; + VP.FPSNumerator = 30; + VP.NumFrames = Frames.size(); + VP.PixelFormat = CodecContext->pix_fmt; + + if (VP.Width <= 0 || VP.Height <= 0) { + _snprintf(ErrorMsg, MsgSize, "Codec returned zero size video"); + throw ErrorMsg; + } + + InitPP(PP, CodecContext->pix_fmt, ErrorMsg, MsgSize); + + // Calculate the average framerate + if (Frames.size() >= 2) { + double DTSDiff = (double)(Frames.back().DTS - Frames.front().DTS); + VP.FPSDenominator = (unsigned int)(DTSDiff * mkv_TruncFloat(TI->TimecodeScale) / (double)1000 / (double)(VP.NumFrames - 1) + 0.5); + VP.FPSNumerator = 1000000; + } + + // Output the already decoded frame so it isn't wasted + OutputFrame(DecodeFrame); + LastFrameNum = 0; + + // Set AR variables + VP.SARNum = TI->AV.Video.DisplayWidth * TI->AV.Video.PixelHeight; + VP.SARDen = TI->AV.Video.DisplayHeight * TI->AV.Video.PixelWidth; + + // Set crop variables + VP.CropLeft = TI->AV.Video.CropL; + VP.CropRight = TI->AV.Video.CropR; + VP.CropTop = TI->AV.Video.CropT; + VP.CropBottom = TI->AV.Video.CropB; +} + +MatroskaVideoSource::~MatroskaVideoSource() { + free(Buffer); + mkv_Close(MF); + fclose(MC.ST.fp); + if (CodecContext) + avcodec_close(CodecContext); + av_free(CodecContext); +} + +int MatroskaVideoSource::DecodeNextFrame(AVFrame *AFrame, int64_t *AFirstStartTime, char *ErrorMsg, unsigned MsgSize) { + int FrameFinished = 0; + *AFirstStartTime = -1; + + uint64_t StartTime, EndTime, FilePos; + unsigned int Track, FrameFlags, FrameSize; + + while (mkv_ReadFrame(MF, 0, &Track, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0) { + if (*AFirstStartTime < 0) + *AFirstStartTime = StartTime; + + if (ReadFrame(FilePos, FrameSize, CS, MC, ErrorMsg, MsgSize)) + return 1; + + avcodec_decode_video(CodecContext, AFrame, &FrameFinished, MC.Buffer, FrameSize); + + if (FrameFinished) + goto Done; + } + + // Flush the last frames + if (CodecContext->has_b_frames) + avcodec_decode_video(CodecContext, AFrame, &FrameFinished, NULL, 0); + + if (!FrameFinished) + goto Error; + +Error: +Done: + return 0; +} + +AVFrameLite *MatroskaVideoSource::GetFrame(int n, char *ErrorMsg, unsigned MsgSize) { + // PPFrame always holds frame LastFrameNum even if no PP is applied + if (LastFrameNum == n) + return reinterpret_cast(PPFrame); + + bool HasSeeked = false; + + if (n < CurrentFrame || Frames.FindClosestKeyFrame(n) > CurrentFrame) { + mkv_Seek(MF, Frames[n].DTS, MKVF_SEEK_TO_PREV_KEYFRAME); + avcodec_flush_buffers(CodecContext); + HasSeeked = true; + } + + do { + int64_t StartTime; + if (DecodeNextFrame(DecodeFrame, &StartTime, ErrorMsg, MsgSize)) + return NULL; + + if (HasSeeked) { + HasSeeked = false; + + if (StartTime < 0 || (CurrentFrame = Frames.FrameFromDTS(StartTime)) < 0) { + _snprintf(ErrorMsg, MsgSize, "Frame accurate seeking is not possible in this file"); + return NULL; + } + } + + CurrentFrame++; + } while (CurrentFrame <= n); + + LastFrameNum = n; + return OutputFrame(DecodeFrame); +} diff --git a/FFmpegSource2/ffvideosource.h b/FFmpegSource2/ffvideosource.h new file mode 100644 index 000000000..6f07d3998 --- /dev/null +++ b/FFmpegSource2/ffvideosource.h @@ -0,0 +1,95 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef FFVIDEOSOURCE_H +#define FFVIDEOSOURCE_H + +extern "C" { +#include +#include +#include +#include +} + +#include +#include + +#include "avisynth.h" +#include "indexing.h" +#include "utils.h" +#include "ffms.h" + +class VideoBase { +private: + pp_context_t *PPContext; + pp_mode_t *PPMode; +protected: + VideoProperties VP; + AVFrame *PPFrame; + AVFrame *DecodeFrame; + int LastFrameNum; + FrameInfoVector Frames; + int VideoTrack; + int CurrentFrame; + AVCodecContext *CodecContext; + + VideoBase(); + int InitPP(const char *PP, int PixelFormat, char *ErrorMsg, unsigned MsgSize); + AVFrameLite *OutputFrame(AVFrame *Frame); +public: + virtual ~VideoBase(); + const VideoProperties& __stdcall GetVideoProperties() { return VP; } + int GetTrack() { return VideoTrack; } + FrameInfoVector *GetFrameInfoVector() { return &Frames; } + virtual AVFrameLite *GetFrame(int n, char *ErrorMsg, unsigned MsgSize) = 0;; + AVFrameLite *GetFrameByTime(double Time, char *ErrorMsg, unsigned MsgSize); +}; + +class FFVideoSource : public VideoBase { +private: + AVFormatContext *FormatContext; + + int SeekMode; + int GetTrackIndex(int &Index, char *ErrorMsg, unsigned MsgSize); + int DecodeNextFrame(AVFrame *Frame, int64_t *DTS, char *ErrorMsg, unsigned MsgSize); +public: + FFVideoSource(const char *SourceFile, int Track, FrameIndex *TrackIndices, const char *PP, int Threads, int SeekMode, char *ErrorMsg, unsigned MsgSize); + ~FFVideoSource(); + AVFrameLite *GetFrame(int n, char *ErrorMsg, unsigned MsgSize); +}; + +class MatroskaVideoSource : public VideoBase { +private: + unsigned int BufferSize; + CompressedStream *CS; + MatroskaFile *MF; + char ErrorMessage[256]; + uint8_t *Buffer; + MatroskaReaderContext MC; + + int DecodeNextFrame(AVFrame *AFrame, int64_t *AFirstStartTime, char *ErrorMsg, unsigned MsgSize); + int GetTrackIndex(int &Index, char *ErrorMsg, unsigned MsgSize); +public: + MatroskaVideoSource(const char *SourceFile, int Track, FrameIndex *TrackIndices, const char *PP, int Threads, char *ErrorMsg, unsigned MsgSize); + ~MatroskaVideoSource(); + AVFrameLite *GetFrame(int n, char *ErrorMsg, unsigned MsgSize); +}; + +#endif diff --git a/FFmpegSource2/indexing.cpp b/FFmpegSource2/indexing.cpp new file mode 100644 index 000000000..97a145ab2 --- /dev/null +++ b/FFmpegSource2/indexing.cpp @@ -0,0 +1,427 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include +#include +#include +#include +#include "indexing.h" +#include "wave64writer.h" + +class AudioContext { +public: + Wave64Writer *W64W; + AVCodecContext *CTX; + CompressedStream *CS; + + AudioContext() { + W64W = NULL; + CTX = NULL; + CS = NULL; + } + + ~AudioContext() { + delete W64W; + if (CTX) { + avcodec_close(CTX); + av_free(CTX); + } + if (CS) + cs_Destroy(CS); + } +}; + +static bool DTSComparison(FrameInfo FI1, FrameInfo FI2) { + return FI1.DTS < FI2.DTS; +} + +static void SortTrackIndices(FrameIndex *TrackIndices) { + for (FrameIndex::iterator Cur=TrackIndices->begin(); Cur!=TrackIndices->end(); Cur++) + std::sort(Cur->begin(), Cur->end(), DTSComparison); +} + +int WriteIndex(const char *IndexFile, FrameIndex *TrackIndices, char *ErrorMsg, unsigned MsgSize) { + std::ofstream Index(IndexFile, std::ios::out | std::ios::binary | std::ios::trunc); + + if (!Index.is_open()) { + _snprintf(ErrorMsg, MsgSize, "Failed to open '%s' for writing", IndexFile); + return 1; + } + + // Write the index file header + IndexHeader IH; + IH.Id = INDEXID; + IH.Version = INDEXVERSION; + IH.Tracks = TrackIndices->size(); + IH.Decoder = TrackIndices->Decoder; + Index.write(reinterpret_cast(&IH), sizeof(IH)); + + for (unsigned int i = 0; i < IH.Tracks; i++) { + // Write how many records belong to the current stream + size_t Frames = (*TrackIndices)[i].size(); + Index.write(reinterpret_cast(&Frames), sizeof(Frames)); + int Num = (*TrackIndices)[i].TB.Num; + Index.write(reinterpret_cast(&Num), sizeof(Num)); + int Den = (*TrackIndices)[i].TB.Den; + Index.write(reinterpret_cast(&Den), sizeof(Den)); + + for (size_t j = 0; j < Frames; j++) + Index.write(reinterpret_cast(&(TrackIndices->at(i)[j])), sizeof(FrameInfo)); + } + + Index.close(); + + return 0; +} + +static int MakeMatroskaIndex(const char *SourceFile, FrameIndex *TrackIndices, int AudioTrackMask, const char *AudioFile, char *ErrorMsg, unsigned MsgSize) { + TrackIndices->Decoder = 1; + + MatroskaFile *MF; + char ErrorMessage[256]; + MatroskaReaderContext MC; + MC.Buffer = NULL; + MC.BufferSize = 0; + + InitStdIoStream(&MC.ST); + MC.ST.fp = fopen(SourceFile, "rb"); + if (MC.ST.fp == NULL) { + _snprintf(ErrorMsg, MsgSize, "Can't open '%s': %s", SourceFile, strerror(errno)); + return 1; + } + + setvbuf(MC.ST.fp, NULL, _IOFBF, CACHESIZE); + + MF = mkv_OpenEx(&MC.ST.base, 0, 0, ErrorMessage, sizeof(ErrorMessage)); + if (MF == NULL) { + fclose(MC.ST.fp); + _snprintf(ErrorMsg, MsgSize, "Can't parse Matroska file: %s", ErrorMessage); + return 2; + } + + // Audio stuff + + AudioContext *AudioContexts = new AudioContext[mkv_GetNumTracks(MF)]; + + for (unsigned int i = 0; i < mkv_GetNumTracks(MF); i++) { + if (AudioTrackMask & (1 << i) && mkv_GetTrackInfo(MF, i)->Type == TT_AUDIO) { + AVCodecContext *AudioCodecContext = avcodec_alloc_context(); + AudioCodecContext->extradata = (uint8_t *)mkv_GetTrackInfo(MF, i)->CodecPrivate; + AudioCodecContext->extradata_size = mkv_GetTrackInfo(MF, i)->CodecPrivateSize; + AudioContexts[i].CTX = AudioCodecContext; + + if (mkv_GetTrackInfo(MF, i)->CompEnabled) { + AudioContexts[i].CS = cs_Create(MF, i, ErrorMessage, sizeof(ErrorMessage)); + if (AudioContexts[i].CS == NULL) { + _snprintf(ErrorMsg, MsgSize, "Can't create decompressor: %s", ErrorMessage); + return 3; + } + } + + AVCodec *AudioCodec = avcodec_find_decoder(MatroskaToFFCodecID(mkv_GetTrackInfo(MF, i))); + if (AudioCodec == NULL) { + _snprintf(ErrorMsg, MsgSize, "Audio codec not found"); + return 4; + } + + if (avcodec_open(AudioCodecContext, AudioCodec) < 0) { + _snprintf(ErrorMsg, MsgSize, "Could not open audio codec"); + return 5; + } + } else { + AudioTrackMask &= ~(1 << i); + } + } + + // + + for (unsigned int i = 0; i < mkv_GetNumTracks(MF); i++) + TrackIndices->push_back(FrameInfoVector(mkv_TruncFloat(mkv_GetTrackInfo(MF, i)->TimecodeScale), 1000000)); + + uint64_t StartTime, EndTime, FilePos; + unsigned int Track, FrameFlags, FrameSize; + + int16_t *db = new int16_t[AVCODEC_MAX_AUDIO_FRAME_SIZE*10]; + + while (mkv_ReadFrame(MF, 0, &Track, &StartTime, &EndTime, &FilePos, &FrameSize, &FrameFlags) == 0) { + // Only create index entries for video for now to save space + if (mkv_GetTrackInfo(MF, Track)->Type == TT_VIDEO) + (*TrackIndices)[Track].push_back(FrameInfo(StartTime, (FrameFlags & FRAME_KF) != 0)); + + if (AudioTrackMask & (1 << Track)) { + ReadFrame(FilePos, FrameSize, AudioContexts[Track].CS, MC, ErrorMsg, MsgSize); + + int Size = FrameSize; + uint8_t *Data = MC.Buffer; + AVCodecContext *AudioCodecContext = AudioContexts[Track].CTX; + + while (Size > 0) { + int dbsize = AVCODEC_MAX_AUDIO_FRAME_SIZE*10; + int Ret = avcodec_decode_audio2(AudioCodecContext, db, &dbsize, Data, Size); + if (Ret < 0) { + _snprintf(ErrorMsg, MsgSize, "Audio decoding error"); + return 5; + } + + if (Ret > 0) { + Size -= Ret; + Data += Ret; + } + if (dbsize > 0) { + // Delay writer creation until after an audio frame has been decoded. This ensures that all parameters are known when writing the headers. + if (!AudioContexts[Track].W64W) { + char ABuf[50]; + std::string WN(AudioFile); + int Offset = StartTime * mkv_TruncFloat(mkv_GetTrackInfo(MF, Track)->TimecodeScale) / (double)1000000; + _snprintf(ABuf, sizeof(ABuf), ".%02d.delay.%d.w64", Track, Offset); + WN += ABuf; + + AudioContexts[Track].W64W = new Wave64Writer(WN.c_str(), av_get_bits_per_sample_format(AudioCodecContext->sample_fmt), + AudioCodecContext->channels, AudioCodecContext->sample_rate, AudioFMTIsFloat(AudioCodecContext->sample_fmt)); + } + + AudioContexts[Track].W64W->WriteData(db, dbsize); + } + } + } + } + + delete [] db; + delete [] AudioContexts; + + mkv_Close(MF); + fclose(MC.ST.fp); + + SortTrackIndices(TrackIndices); + return 0; +} + +int MakeIndex(const char *SourceFile, FrameIndex *TrackIndices, int AudioTrackMask, const char *AudioFile, char *ErrorMsg, unsigned MsgSize) { + TrackIndices->Decoder = 0; + TrackIndices->clear(); + + AVFormatContext *FormatContext = NULL; + + if (av_open_input_file(&FormatContext, SourceFile, NULL, 0, NULL) != 0) { + _snprintf(ErrorMsg, MsgSize, "Can't open '%s'", SourceFile); + return 1; + } + + // Do matroska indexing instead? + if (!strcmp(FormatContext->iformat->name, "matroska")) { + av_close_input_file(FormatContext); + return MakeMatroskaIndex(SourceFile, TrackIndices, AudioTrackMask, AudioFile, ErrorMsg, MsgSize); + } + + if (av_find_stream_info(FormatContext) < 0) { + av_close_input_file(FormatContext); + _snprintf(ErrorMsg, MsgSize, "Couldn't find stream information"); + return 2; + } + + // Audio stuff + + AudioContext *AudioContexts = new AudioContext[FormatContext->nb_streams]; + + for (unsigned int i = 0; i < FormatContext->nb_streams; i++) { + if (AudioTrackMask & (1 << i) && FormatContext->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO) { + AVCodecContext *AudioCodecContext = FormatContext->streams[i]->codec; + + AVCodec *AudioCodec = avcodec_find_decoder(AudioCodecContext->codec_id); + if (AudioCodec == NULL) { + _snprintf(ErrorMsg, MsgSize, "Audio codec not found"); + return 3; + } + + if (avcodec_open(AudioCodecContext, AudioCodec) < 0) { + _snprintf(ErrorMsg, MsgSize, "Could not open audio codec"); + return 4; + } + } else { + AudioTrackMask &= ~(1 << i); + } + } + + // + + for (unsigned int i = 0; i < FormatContext->nb_streams; i++) + TrackIndices->push_back(FrameInfoVector(FormatContext->streams[i]->time_base.den, + FormatContext->streams[i]->time_base.num * 1000)); + + int16_t *db = new int16_t[AVCODEC_MAX_AUDIO_FRAME_SIZE*10]; + + AVPacket Packet; + while (av_read_frame(FormatContext, &Packet) >= 0) { + // Only create index entries for video for now to save space + if (FormatContext->streams[Packet.stream_index]->codec->codec_type == CODEC_TYPE_VIDEO) + (*TrackIndices)[Packet.stream_index].push_back(FrameInfo(Packet.dts, (Packet.flags & PKT_FLAG_KEY) ? 1 : 0)); + + if (AudioTrackMask & (1 << Packet.stream_index)) { + AVCodecContext *AudioCodecContext = FormatContext->streams[Packet.stream_index]->codec; + int Size = Packet.size; + uint8_t *Data = Packet.data; + + while (Size > 0) { + int dbsize = AVCODEC_MAX_AUDIO_FRAME_SIZE*10; + int Ret = avcodec_decode_audio2(AudioCodecContext, db, &dbsize, Data, Size); + if (Ret < 0) { + _snprintf(ErrorMsg, MsgSize, "Audio decoding error"); + return 5; + } + + if (Ret > 0) { + Size -= Ret; + Data += Ret; + } + + if (dbsize > 0) { + // Delay writer creation until after an audio frame has been decoded. This ensures that all parameters are known when writing the headers. + if (!AudioContexts[Packet.stream_index].W64W) { + char ABuf[50]; + std::string WN(AudioFile); + int Offset = (Packet.dts * FormatContext->streams[Packet.stream_index]->time_base.num) + / (double)(FormatContext->streams[Packet.stream_index]->time_base.den * 1000); + _snprintf(ABuf, sizeof(ABuf), ".%02d.delay.%d.w64", Packet.stream_index, Offset); + WN += ABuf; + + AudioContexts[Packet.stream_index].W64W = new Wave64Writer(WN.c_str(), av_get_bits_per_sample_format(AudioCodecContext->sample_fmt), + AudioCodecContext->channels, AudioCodecContext->sample_rate, AudioFMTIsFloat(AudioCodecContext->sample_fmt)); + } + + AudioContexts[Packet.stream_index].W64W->WriteData(db, dbsize); + } + } + } + + av_free_packet(&Packet); + } + + delete [] db; + delete [] AudioContexts; + + av_close_input_file(FormatContext); + + SortTrackIndices(TrackIndices); + return 0; +} + +int ReadIndex(const char *IndexFile, FrameIndex *TrackIndices, char *ErrorMsg, unsigned MsgSize) { + std::ifstream Index(IndexFile, std::ios::in | std::ios::binary); + + if (!Index.is_open()) { + _snprintf(ErrorMsg, MsgSize, "Failed to open '%s' for reading", IndexFile); + return 1; + } + + // Read the index file header + IndexHeader IH; + Index.read(reinterpret_cast(&IH), sizeof(IH)); + if (IH.Id != INDEXID) { + _snprintf(ErrorMsg, MsgSize, "'%s' is not a valid index file", IndexFile); + return 2; + } + + if (IH.Version != INDEXVERSION) { + _snprintf(ErrorMsg, MsgSize, "'%s' is not the expected index version", IndexFile); + return 3; + } + + TrackIndices->Decoder = IH.Decoder; + TrackIndices->clear(); + + for (unsigned int i = 0; i < IH.Tracks; i++) { + // Read how many records belong to the current stream + size_t Frames; + Index.read(reinterpret_cast(&Frames), sizeof(Frames)); + int Num; + Index.read(reinterpret_cast(&Num), sizeof(Num)); + int Den; + Index.read(reinterpret_cast(&Den), sizeof(Den)); + TrackIndices->push_back(FrameInfoVector(Num, Den)); + + FrameInfo FI(0, false); + for (size_t j = 0; j < Frames; j++) { + Index.read(reinterpret_cast(&FI), sizeof(FrameInfo)); + TrackIndices->at(i).push_back(FI); + } + } + + Index.close(); + + return 0; +} + +FrameInfo::FrameInfo(int64_t DTS, bool KeyFrame) { + this->DTS = DTS; + this->KeyFrame = KeyFrame; +} + +int FrameInfoVector::WriteTimecodes(const char *TimecodeFile, char *ErrorMsg, unsigned MsgSize) { + std::ofstream Timecodes(TimecodeFile, std::ios::out | std::ios::trunc); + + if (!Timecodes.is_open()) { + _snprintf(ErrorMsg, MsgSize, "Failed to open '%s' for writing", TimecodeFile); + return 1; + } + + Timecodes << "# timecode format v2\r\n"; + + for (iterator Cur=begin(); Cur!=end(); Cur++) + Timecodes << (int64_t)((Cur->DTS * TB.Num) / (double)TB.Den) << "\r\n"; + + return 0; +} + +int FrameInfoVector::FrameFromDTS(int64_t DTS) { + for (int i = 0; i < static_cast(size()); i++) + if (at(i).DTS == DTS) + return i; + return -1; +} + +int FrameInfoVector::ClosestFrameFromDTS(int64_t DTS) { + int Frame = 0; + int64_t BestDiff = 0xFFFFFFFFFFFFFFLL; // big number + for (int i = 0; i < static_cast(size()); i++) { + int64_t CurrentDiff = FFABS(at(i).DTS - DTS); + if (CurrentDiff < BestDiff) { + BestDiff = CurrentDiff; + Frame = i; + } + } + return Frame; +} + +int FrameInfoVector::FindClosestKeyFrame(int Frame) { + for (int i = Frame; i > 0; i--) + if (at(i).KeyFrame) + return i; + return 0; +} + +FrameInfoVector::FrameInfoVector() { + TB.Num = 0; + TB.Den = 0; +} + +FrameInfoVector::FrameInfoVector(int Num, int Den) { + TB.Num = Num; + TB.Den = Den; +} diff --git a/FFmpegSource2/indexing.h b/FFmpegSource2/indexing.h new file mode 100644 index 000000000..236a87e80 --- /dev/null +++ b/FFmpegSource2/indexing.h @@ -0,0 +1,68 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef INDEXING_H +#define INDEXING_H + +extern "C" { +#include +#include + +#include "MatroskaParser.h" +#include "stdiostream.h" +} + +#include +#include "utils.h" +#include "ffms.h" + +#define INDEXVERSION 4 +#define INDEXID 0x53920873 + +struct IndexHeader { + uint32_t Id; + uint32_t Version; + uint32_t Tracks; + uint32_t Decoder; +}; + +class FrameInfoVector : public std::vector { +public: + TrackTimeBase TB; + + int FindClosestKeyFrame(int Frame); + int FrameFromDTS(int64_t DTS); + int ClosestFrameFromDTS(int64_t DTS); + int WriteTimecodes(const char *TimecodeFile, char *ErrorMsg, unsigned MsgSize); + + FrameInfoVector(); + FrameInfoVector(int Num, int Den); +}; + +class FrameIndex : public std::vector { +public: + int Decoder; +}; + +int MakeIndex(const char *SourceFile, FrameIndex *TrackIndices, int AudioTrackMask, const char *AudioFile, char *ErrorMsg, unsigned MsgSize); +int ReadIndex(const char *IndexFile, FrameIndex *TrackIndices, char *ErrorMsg, unsigned MsgSize); +int WriteIndex(const char *IndexFile, FrameIndex *TrackIndices, char *ErrorMsg, unsigned MsgSize); + +#endif diff --git a/FFmpegSource2/stdiostream.c b/FFmpegSource2/stdiostream.c new file mode 100644 index 000000000..68c2f2f73 --- /dev/null +++ b/FFmpegSource2/stdiostream.c @@ -0,0 +1,104 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include "stdiostream.h" + +/* 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 (_fseeki64(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 (_fseeki64(fp, start, SEEK_SET)) + return -1; + + while ((c = getc(fp)) != EOF) { + cmp = ((cmp << 8) | c) & 0xffffffff; + if (cmp == signature) + return _ftelli64(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; +} + +void InitStdIoStream(StdIoStream *st) { + memset(st,0,sizeof(st)); + st->base.read = StdIoRead; + st->base.scan = StdIoScan; + st->base.getcachesize = StdIoGetCacheSize; + st->base.geterror = StdIoGetLastError; + st->base.memalloc = StdIoMalloc; + st->base.memrealloc = StdIoRealloc; + st->base.memfree = StdIoFree; + st->base.progress = StdIoProgress; +} diff --git a/FFmpegSource2/stdiostream.h b/FFmpegSource2/stdiostream.h new file mode 100644 index 000000000..f02de0684 --- /dev/null +++ b/FFmpegSource2/stdiostream.h @@ -0,0 +1,83 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef STDIOSTREAM_H +#define STDIOSTREAM_H + +#include +#include +#include +#include +#include +#include +#include "MatroskaParser.h" + +#define CACHESIZE 65536 + +/************\ +* Structures * +\************/ + +/* 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; + +/***********\ +* Functions * +\***********/ + +/* 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); + +/* 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); + +/* return cache size, this is used to limit readahead */ +unsigned StdIoGetCacheSize(StdIoStream *st); + +/* return last error message */ +const char *StdIoGetLastError(StdIoStream *st); + +/* memory allocation, this is done via stdlib */ +void *StdIoMalloc(StdIoStream *st, size_t size); + +void *StdIoRealloc(StdIoStream *st, void *mem, size_t size); + +void StdIoFree(StdIoStream *st, void *mem); + +/* progress report handler for lengthy operations + * returns 0 to abort operation, nonzero to continue + */ +int StdIoProgress(StdIoStream *st, ulonglong cur, ulonglong max); + +void InitStdIoStream(StdIoStream *st); + +#endif /* #ifndef STDIOSTREAM_H */ diff --git a/FFmpegSource2/utils.cpp b/FFmpegSource2/utils.cpp new file mode 100644 index 000000000..3eb687a8a --- /dev/null +++ b/FFmpegSource2/utils.cpp @@ -0,0 +1,484 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include "utils.h" + +int GetCPUFlags() { + bool hasmmx, hassse, hasisse = false; + + __asm { + mov eax, 1 // CPU feature detection + CPUID + + shr edx, 23 // MMX + mov eax, edx + and eax, 1 + mov [hasmmx], al + + shr edx, 2 // SSE + mov eax, edx + and eax, 1 + mov [hassse], al + jnz End // End if SSE detected, else... + + + //Athlon TBird Detection, since it has iSSE but not SSE + xor eax, eax // CPU Vendor detection (eax=0) + CPUID // call CPU identification function + + cmp ebx, 'htuA' + jne End + cmp edx, 'itne' + jne End + cmp ebx, 'DMAc' + jne End + + //detect Athlon Tbird, which has iSSE but not SSE + sub eax, 0x460000 //Find Family 6, Model 4 = Athlon Tbird + cmp eax, 0 + sete [hasisse] + +End: + } + + int Flags = 0; + + // PP and SWS defines have the same values for their defines so this actually works + if (hasmmx) + Flags |= PP_CPU_CAPS_MMX; + if (hasisse) + Flags |= PP_CPU_CAPS_MMX2; + + return Flags; +} + +int CSNameToPIXFMT(const char * ACSName, int ADefault) { + if (!_strcmpi(ACSName, "")) + return ADefault; + if (!_strcmpi(ACSName, "YV12")) + return PIX_FMT_YUV420P; + if (!_strcmpi(ACSName, "YUY2")) + return PIX_FMT_YUYV422; + if (!_strcmpi(ACSName, "RGB24")) + return PIX_FMT_BGR24; + if (!_strcmpi(ACSName, "RGB32")) + return PIX_FMT_RGB32; + return PIX_FMT_NONE; +} + +int ResizerNameToSWSResizer(const char *AResizerName) { + if (!_strcmpi(AResizerName, "FAST_BILINEAR")) + return SWS_FAST_BILINEAR; + if (!_strcmpi(AResizerName, "BILINEAR")) + return SWS_BILINEAR; + if (!_strcmpi(AResizerName, "BICUBIC")) + return SWS_BICUBIC; + if (!_strcmpi(AResizerName, "X")) + return SWS_X; + if (!_strcmpi(AResizerName, "POINT")) + return SWS_POINT; + if (!_strcmpi(AResizerName, "AREA")) + return SWS_AREA; + if (!_strcmpi(AResizerName, "BICUBLIN")) + return SWS_BICUBLIN; + if (!_strcmpi(AResizerName, "GAUSS")) + return SWS_GAUSS; + if (!_strcmpi(AResizerName, "SINC")) + return SWS_SINC; + if (!_strcmpi(AResizerName, "LANCZOS")) + return SWS_LANCZOS; + if (!_strcmpi(AResizerName, "SPLINE")) + return SWS_SPLINE; + return 0; +} + +int GetNumberOfLogicalCPUs() { + SYSTEM_INFO SI; + GetSystemInfo(&SI); + return SI.dwNumberOfProcessors; +} + +int ReadFrame(uint64_t FilePos, unsigned int &FrameSize, CompressedStream *CS, MatroskaReaderContext &Context, char *ErrorMsg, unsigned MsgSize) { + if (CS) { + char CSBuffer[4096]; + + unsigned int DecompressedFrameSize = 0; + + cs_NextFrame(CS, FilePos, FrameSize); + + for (;;) { + int ReadBytes = cs_ReadData(CS, CSBuffer, sizeof(CSBuffer)); + if (ReadBytes < 0) { + _snprintf(ErrorMsg, MsgSize, "Error decompressing data: %s", cs_GetLastError(CS)); + return 1; + } + if (ReadBytes == 0) { + FrameSize = DecompressedFrameSize; + return 0; + } + + if (Context.BufferSize < DecompressedFrameSize + ReadBytes) { + Context.BufferSize = FrameSize; + Context.Buffer = (uint8_t *)realloc(Context.Buffer, Context.BufferSize + 16); + if (Context.Buffer == NULL) { + _snprintf(ErrorMsg, MsgSize, "Out of memory"); + return 2; + } + } + + memcpy(Context.Buffer + DecompressedFrameSize, CSBuffer, ReadBytes); + DecompressedFrameSize += ReadBytes; + } + } else { + if (_fseeki64(Context.ST.fp, FilePos, SEEK_SET)) { + _snprintf(ErrorMsg, MsgSize, "fseek(): %s", strerror(errno)); + return 3; + } + + if (Context.BufferSize < FrameSize) { + Context.BufferSize = FrameSize; + Context.Buffer = (uint8_t *)realloc(Context.Buffer, Context.BufferSize + 16); + if (Context.Buffer == NULL) { + _snprintf(ErrorMsg, MsgSize, "Out of memory"); + return 4; + } + } + + size_t ReadBytes = fread(Context.Buffer, 1, FrameSize, Context.ST.fp); + if (ReadBytes != FrameSize) { + if (ReadBytes == 0) { + if (feof(Context.ST.fp)) { + _snprintf(ErrorMsg, MsgSize, "Unexpected EOF while reading frame"); + return 5; + } else { + _snprintf(ErrorMsg, MsgSize, "Error reading frame: %s", strerror(errno)); + return 6; + } + } else { + _snprintf(ErrorMsg, MsgSize, "Short read while reading frame"); + return 7; + } + _snprintf(ErrorMsg, MsgSize, "Unknown read error"); + return 8; + } + + return 0; + } + + FrameSize = 0; + return 0; +} + +bool AudioFMTIsFloat(SampleFormat FMT){ + switch (FMT) { + case SAMPLE_FMT_FLT: + case SAMPLE_FMT_DBL: + return true; + default: + return false; + } +} + +CodecID MatroskaToFFCodecID(TrackInfo *TI) { + char *Codec = TI->CodecID; +/* Video Codecs */ + if (!strcmp(Codec, "V_MS/VFW/FOURCC")) { + // fourcc list from ffdshow + 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'): + return CODEC_ID_HUFFYUV; + case MAKEFOURCC('F', 'F', 'V', 'H'): + return CODEC_ID_FFVHUFF; + 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_ZLIB; + case MAKEFOURCC('F', 'L', 'V', '1'): + return CODEC_ID_FLV1; +/* + 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/AP")) + return CODEC_ID_MPEG4; + else if (!strcmp(Codec, "V_MPEG4/ISO/ASP")) + return CODEC_ID_MPEG4; + else if (!strcmp(Codec, "V_MPEG4/ISO/SP")) + return CODEC_ID_MPEG4; + else if (!strcmp(Codec, "V_MPEG4/MS/V3")) + return CODEC_ID_MSMPEG4V3; + 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_VC1")) + return CODEC_ID_VC1; + else if (!strcmp(Codec, "V_SNOW")) + return CODEC_ID_SNOW; + else if (!strcmp(Codec, "V_THEORA")) + return CODEC_ID_THEORA; + else if (!strcmp(Codec, "V_UNCOMPRESSED")) + return CODEC_ID_NONE; // bleh + else if (!strcmp(Codec, "V_QUICKTIME")) + return CODEC_ID_SVQ3; // no idea if this is right + else if (!strcmp(Codec, "V_CIPC")) + return CODEC_ID_NONE; // don't know, don't care + 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; + } +/* Audio Codecs */ + } else if (!strcmp(Codec, "A_AC3")) + return CODEC_ID_AC3; + else if (!strcmp(Codec, "A_EAC3")) + return CODEC_ID_AC3; + else if (!strcmp(Codec, "A_MPEG/L3")) + return CODEC_ID_MP3; + else if (!strcmp(Codec, "A_MPEG/L2")) + return CODEC_ID_MP2; + else if (!strcmp(Codec, "A_MPEG/L1")) + return CODEC_ID_MP2; // correct? + else if (!strcmp(Codec, "A_DTS")) + return CODEC_ID_DTS; + else if (!strcmp(Codec, "A_PCM/INT/LIT")) { + switch (TI->AV.Audio.BitDepth) { + case 8: return CODEC_ID_PCM_S8; + case 16: return CODEC_ID_PCM_S16LE; + case 24: return CODEC_ID_PCM_S24LE; + case 32: return CODEC_ID_PCM_S32LE; + default: return CODEC_ID_NONE; + } + } else if (!strcmp(Codec, "A_PCM/INT/BIG")) { + switch (TI->AV.Audio.BitDepth) { + case 8: return CODEC_ID_PCM_S8; + case 16: return CODEC_ID_PCM_S16BE; + case 24: return CODEC_ID_PCM_S24BE; + case 32: return CODEC_ID_PCM_S32BE; + default: return CODEC_ID_NONE; + } + } else if (!strcmp(Codec, "A_PCM/FLOAT/IEEE")) + return CODEC_ID_PCM_F32LE; // only a most likely guess, may do bad things + else if (!strcmp(Codec, "A_FLAC")) + return CODEC_ID_FLAC; + else if (!strcmp(Codec, "A_MPC")) + return CODEC_ID_MUSEPACK8; // or is it CODEC_ID_MUSEPACK7? both? + else if (!strcmp(Codec, "A_TTA1")) + return CODEC_ID_TTA; + else if (!strcmp(Codec, "A_WAVPACK4")) + return CODEC_ID_WAVPACK; + else if (!strcmp(Codec, "A_VORBIS")) + return CODEC_ID_VORBIS; + else if (!strcmp(Codec, "A_REAL/14_4")) + return CODEC_ID_RA_144; + else if (!strcmp(Codec, "A_REAL/28_8")) + return CODEC_ID_RA_288; + else if (!strcmp(Codec, "A_REAL/COOK")) + return CODEC_ID_COOK; + else if (!strcmp(Codec, "A_REAL/SIPR")) + return CODEC_ID_NONE; // no sipr codec id? + else if (!strcmp(Codec, "A_REAL/ATRC")) + return CODEC_ID_ATRAC3; + else if (!strncmp(Codec, "A_AAC", 5)) + return CODEC_ID_AAC; + else if (!strcmp(Codec, "A_SPEEX")) + return CODEC_ID_SPEEX; + else if (!strcmp(Codec, "A_QUICKTIME")) + return CODEC_ID_NONE; // no + else if (!strcmp(Codec, "A_MS/ACM")) { + // nothing useful here anyway? + //#include "Mmreg.h" + //((WAVEFORMATEX *)TI->CodecPrivate)->wFormatTag + return CODEC_ID_NONE; + } else { + return CODEC_ID_NONE; + } +} diff --git a/FFmpegSource2/utils.h b/FFmpegSource2/utils.h new file mode 100644 index 000000000..76f726c58 --- /dev/null +++ b/FFmpegSource2/utils.h @@ -0,0 +1,63 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef UTILS_H +#define UTILS_H + +#include + +extern "C" { +#include +#include +#include +#include + +#include "MatroskaParser.h" +} + +#include "avisynth.h" +#include "stdiostream.h" + +struct MatroskaReaderContext { +public: + StdIoStream ST; + uint8_t *Buffer; + unsigned int BufferSize; + + MatroskaReaderContext() { + InitStdIoStream(&ST); + Buffer = NULL; + BufferSize = 0; + } + + ~MatroskaReaderContext() { + free(Buffer); + } +}; + +int GetCPUFlags(); +int CSNameToPIXFMT(const char * ACSName, int ADefault); +int ResizerNameToSWSResizer(const char *AResizerName); +int GetNumberOfLogicalCPUs(); +int ReadFrame(uint64_t FilePos, unsigned int &FrameSize, CompressedStream *CS, MatroskaReaderContext &Context, char *ErrorMsg, unsigned MsgSize); +bool AudioFMTIsFloat(SampleFormat FMT); +CodecID MatroskaToFFCodecID(TrackInfo *TI); + +#endif diff --git a/FFmpegSource2/wave64writer.cpp b/FFmpegSource2/wave64writer.cpp new file mode 100644 index 000000000..b63eadba8 --- /dev/null +++ b/FFmpegSource2/wave64writer.cpp @@ -0,0 +1,109 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#include "wave64writer.h" + +static const uint8_t GuidRIFF[16]={ + // {66666972-912E-11CF-A5D6-28DB04C10000} + 0x72, 0x69, 0x66, 0x66, 0x2E, 0x91, 0xCF, 0x11, 0xA5, 0xD6, 0x28, 0xDB, 0x04, 0xC1, 0x00, 0x00 +}; + +static const uint8_t GuidWAVE[16]={ + // {65766177-ACF3-11D3-8CD1-00C04F8EDB8A} + 0x77, 0x61, 0x76, 0x65, 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A +}; + +static const uint8_t Guidfmt[16]={ + // {20746D66-ACF3-11D3-8CD1-00C04F8EDB8A} + 0x66, 0x6D, 0x74, 0x20, 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A +}; + +static const uint8_t Guiddata[16]={ + // {61746164-ACF3-11D3-8CD1-00C04F8EDB8A} + 0x64, 0x61, 0x74, 0x61, 0xF3, 0xAC, 0xD3, 0x11, 0x8C, 0xD1, 0x00, 0xC0, 0x4F, 0x8E, 0xDB, 0x8A +}; + +Wave64Writer::Wave64Writer(const char *Filename, WORD BitsPerSample, WORD Channels, DWORD SamplesPerSec, bool IsFloat) : std::ofstream(Filename, std::ios::out | std::ios::binary | std::ios::trunc) { + BytesWritten = 0; + this->BitsPerSample = BitsPerSample; + this->Channels = Channels; + this->SamplesPerSec = SamplesPerSec; + this->IsFloat = IsFloat; + + if (!is_open()) + throw "Blerror"; + + WriteHeader(true, IsFloat); +} + +Wave64Writer::~Wave64Writer() { + WriteHeader(false, IsFloat); + close(); +} + +void Wave64Writer::WriteHeader(bool Initial, bool IsFloat) { + WAVEFORMATEX WFEX; + if (IsFloat) + WFEX.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + else + WFEX.wFormatTag = WAVE_FORMAT_PCM; + WFEX.nChannels = Channels; + WFEX.nSamplesPerSec = SamplesPerSec; + WFEX.nAvgBytesPerSec = (BitsPerSample * Channels * SamplesPerSec) / 8; + WFEX.nBlockAlign = (BitsPerSample * Channels) / 8; + WFEX.wBitsPerSample = BitsPerSample; + WFEX.cbSize = 0; + + uint64_t Header[14]; + + memset(Header, 0, sizeof(Header)); + + memcpy(Header + 0, GuidRIFF, 16); + if (Initial) { + Header[2] = 0x7F00000000000000; + } else { + Header[2] = BytesWritten + sizeof(Header); + } + + memcpy(Header + 3, GuidWAVE, 16); + memcpy(Header + 5, Guidfmt, 16); + + // sizeof(WFEX) = 18 + Header[7] = sizeof(WFEX) + 24; + + memcpy(Header + 8, &WFEX, sizeof(WFEX)); + memcpy(Header + 11, Guiddata, 16); + + if (Initial) + Header[13] = 0x7E00000000000000; + else + Header[13] = BytesWritten + 24; + + std::ios::streampos CPos = tellp(); + seekp(0, std::ios::beg); + write(reinterpret_cast(Header), sizeof(Header)); + if (!Initial) + seekp(CPos, std::ios::beg); +} + +void Wave64Writer::WriteData(void *Data, std::streamsize Length) { + write(reinterpret_cast(Data), Length); + BytesWritten += Length; +} diff --git a/FFmpegSource2/wave64writer.h b/FFmpegSource2/wave64writer.h new file mode 100644 index 000000000..504e2dc4c --- /dev/null +++ b/FFmpegSource2/wave64writer.h @@ -0,0 +1,46 @@ +// Copyright (c) 2007-2008 Fredrik Mellbin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#ifndef WAVE64WRITER_H +#define WAVE64WRITER_H + +#include +#include +#include +#include +#include + +class Wave64Writer : std::ofstream { +public: + Wave64Writer(const char *Filename, WORD BitsPerSample, WORD Channels, DWORD SamplesPerSec, bool IsFloat); + ~Wave64Writer(); + void WriteData(void *Data, std::streamsize Length); +private: + WORD BitsPerSample; + WORD Channels; + DWORD SamplesPerSec; + uint64_t BytesWritten; + uint32_t HeaderSize; + bool IsFloat; + + void WriteHeader(bool Initial, bool IsFloat); +}; + +#endif