//#include #define _LARGEFILE64_SOURCE #include #include #include #include #include #include #include #include #include "AVIReadHandler.h" #include "FastReadStream.h" //#include "ProgressDialog.h" #include "List.h" #include "File64.h" #include #define __MODULE__ "AVIReadHandler" //#define STREAMING_DEBUG // HACK!!!! #define _RPT4 printf pthread_mutex_t g_diskcs; bool g_disklockinited=false; #define GetLastError() 0 typedef long long __int64; /////////////////////////////////////////// typedef __int64 QUADWORD; // The following comes from the OpenDML 1.0 spec for extended AVI files // bIndexType codes // #define AVI_INDEX_OF_INDEXES 0x00 // when each entry in aIndex // array points to an index chunk #define AVI_INDEX_OF_CHUNKS 0x01 // when each entry in aIndex // array points to a chunk in the file #define AVI_INDEX_IS_DATA 0x80 // when each entry is aIndex is // really the data // bIndexSubtype codes for INDEX_OF_CHUNKS #define AVI_INDEX_2FIELD 0x01 // when fields within frames // are also indexed struct _avisuperindex_entry { QUADWORD qwOffset; // absolute file offset, offset 0 is // unused entry?? DWORD dwSize; // size of index chunk at this offset DWORD dwDuration; // time span in stream ticks }; struct _avistdindex_entry { DWORD dwOffset; // qwBaseOffset + this is absolute file offset DWORD dwSize; // bit 31 is set if this is NOT a keyframe }; struct _avifieldindex_entry { DWORD dwOffset; DWORD dwSize; DWORD dwOffsetField2; }; //#pragma pack(2) typedef struct _avisuperindex_chunk { FOURCC fcc; // ’ix##’ DWORD cb; // size of this structure WORD wLongsPerEntry; // must be 4 (size of each entry in aIndex array) BYTE bIndexSubType; // must be 0 or AVI_INDEX_2FIELD BYTE bIndexType; // must be AVI_INDEX_OF_INDEXES DWORD nEntriesInUse; // number of entries in aIndex array that // are used DWORD dwChunkId; // ’##dc’ or ’##db’ or ’##wb’, etc DWORD dwReserved[3]; // must be 0 // struct _avisuperindex_entry aIndex; //[] } AVISUPERINDEX, * PAVISUPERINDEX; typedef struct _avistdindex_chunk { FOURCC fcc; // ’ix##’ DWORD cb; WORD wLongsPerEntry; // must be sizeof(aIndex[0])/sizeof(DWORD) BYTE bIndexSubType; // must be 0 BYTE bIndexType; // must be AVI_INDEX_OF_CHUNKS DWORD nEntriesInUse; // DWORD dwChunkId; // ’##dc’ or ’##db’ or ’##wb’ etc.. QUADWORD qwBaseOffset; // all dwOffsets in aIndex array are // relative to this DWORD dwReserved3; // must be 0 // struct _avistdindex_entry aIndex; //[] } AVISTDINDEX, * PAVISTDINDEX; typedef struct _avifieldindex_chunk { FOURCC fcc; DWORD cb; WORD wLongsPerEntry; BYTE bIndexSubType; BYTE bIndexType; DWORD nEntriesInUse; DWORD dwChunkId; QUADWORD qwBaseOffset; DWORD dwReserved3; // struct _avifieldindex_entry aIndex; //[] } AVIFIELDINDEX, * PAVIFIELDINDEX; /////////////////////////////////////////////////////////////////////////// IvAVIReadStream::~IvAVIReadStream() { } IvAVIReadHandler::~IvAVIReadHandler() { } /////////////////////////////////////////////////////////////////////////// class AVIStreamNode; class AVIReadHandler; class AVIStreamNode : public ListNode2 { public: AVIStreamHeader hdr; void *pFormat; long lFormatLen; AVIIndex index; __int64 bytes; bool keyframe_only; int handler_count; int streaming_count; __int64 stream_push_pos; __int64 stream_bytes; int stream_pushes; long length; long frames; List2 listHandlers; AVIStreamNode(); ~AVIStreamNode(); }; AVIStreamNode::AVIStreamNode() { pFormat = NULL; bytes = 0; handler_count = 0; streaming_count = 0; stream_bytes = 0; stream_pushes = 0; } AVIStreamNode::~AVIStreamNode() { delete (char*)pFormat; } /////////////////////////////////////////////////////////////////////////// class AVIFileDesc : public ListNode2 { public: HANDLE hFile; HANDLE hFileUnbuffered; __int64 i64Size; }; class AVIStreamNode; class AVIReadHandler : public IvAVIReadHandler, private File64 { public: bool fDisableFastIO; AVIReadHandler(const char *); ~AVIReadHandler(); void AddRef(); void Release(); IvAVIReadStream *GetStream(DWORD fccType, LONG lParam); void EnableFastIO(bool); bool isOptimizedForRealtime(); bool isStreaming(); bool isIndexFabricated(); bool AppendFile(const char *pszFile); bool getSegmentHint(const char **ppszPath); void EnableStreaming(int stream); void DisableStreaming(int stream); void AdjustRealTime(bool fRealTime); __int64 getStreamPtr(); long ReadData(int stream, void *buffer, __int64 position, long len); HRESULT Header(MainAVIHeader* mh) { if(mh==0)return 0; *mh=m_header; return 0; } InputStream* cache; private: // enum { STREAM_SIZE = 65536 }; enum { STREAM_SIZE = 1048576 }; enum { STREAM_RT_SIZE = 65536 }; enum { STREAM_BLOCK_SIZE = 4096 }; int ref_count; __int64 i64StreamPosition; int streams; char *streamBuffer; int sbPosition; int sbSize; bool hyperindexed; long fStreamsActive; int nRealTime; int nActiveStreamers; bool fFakeIndex; __int64 i64Size; int nFiles, nCurrentFile; char * pSegmentHint; MainAVIHeader m_header; List2 listStreams; List2 listFiles; int _construct(const char *pszFile); int _parseFile(List2& streams); bool _parseStreamHeader(List2& streams, DWORD dwLengthLeft); bool _parseIndexBlock(List2& streams, int count, __int64); int _parseExtendedIndexBlock(List2& streams, AVIStreamNode *pasn, __int64 fpos, DWORD dwLength); void _destruct(); char * _StreamRead(long& bytes); void _SelectFile(int file); }; IvAVIReadHandler *CreateAVIReadHandler(const char *pszFile) { // try // { // printf("Creating\n"); return new AVIReadHandler(pszFile); // } // catch(FatalError& error) // { // printf("Oops\n"); // error.post("CreateAVIReadHandler"); // return NULL; // } // catch(MyWin32Error& error) // { // error.post("CreateAVIReadHandler"); // return NULL; // } } class AVIReadStream : public IvAVIReadStream, public ListNode2 { friend class AVIReadHandler; public: AVIReadStream(AVIReadHandler *, AVIStreamNode *, int); ~AVIReadStream(); HRESULT BeginStreaming(long lStart, long lEnd, long lRate); HRESULT EndStreaming(); HRESULT Info(AVISTREAMINFO *pasi, long lSize); bool IsKeyFrame(long lFrame) const; HRESULT Read(long lStart, long lSamples, void *lpBuffer, long cbBuffer, long *plBytes, long *plSamples); long Start(); long End(); long PrevKeyFrame(long lFrame); long NextKeyFrame(long lFrame); long NearestKeyFrame(long lFrame); HRESULT FormatSize(long lFrame, long *plSize); HRESULT ReadFormat(long lFrame, void *pFormat, long *plSize); bool isStreaming(); void ClearCache(){parent->cache->Clear();} void Reinit(); long Length() const {return length;} HRESULT Header(AVIStreamHeader* pheader) { if(pheader!=0)*pheader=psnData->hdr; return 0; } /* HRESULT ChunkTable(AVIIndex** ppIndex) { if(ppIndex!=0)*ppIndex=&psnData->index; return 0; }*/ private: AVIReadHandler *parent; AVIStreamNode *psnData; AVIIndexEntry2 *pIndex; InputStream *cache; long& length; long& frames; long sampsize; int streamno; bool fStreamingEnabled; bool fStreamingActive; int iStreamTrackCount; long lStreamTrackValue; long lStreamTrackInterval; bool fRealTime; __int64 i64CachedPosition; AVIIndexEntry2 *pCachedEntry; }; /////////////////////////////////////////////////////////////////////////// AVIReadStream::AVIReadStream(AVIReadHandler *parent, AVIStreamNode *psnData, int streamno) :length(psnData->length) ,frames(psnData->frames) { this->parent = parent; this->psnData = psnData; this->streamno = streamno; fStreamingEnabled = false; fStreamingActive = false; fRealTime = false; parent->AddRef(); pIndex = psnData->index.index2Ptr(); sampsize = psnData->hdr.dwSampleSize; if (sampsize) { i64CachedPosition = 0; pCachedEntry = pIndex; } psnData->listHandlers.AddTail(this); } AVIReadStream::~AVIReadStream() { EndStreaming(); parent->Release(); Remove(); } void AVIReadStream::Reinit() { pIndex = psnData->index.index2Ptr(); i64CachedPosition = 0; pCachedEntry = pIndex; } HRESULT AVIReadStream::BeginStreaming(long lStart, long lEnd, long lRate) { if (fStreamingEnabled) return 0; // OutputDebugString(lRate>1500 ? "starting: fast" : "starting: slow"); if (lRate <= 1500) { parent->AdjustRealTime(true); fRealTime = true; } else fRealTime = false; if (parent->fDisableFastIO) return 0; if (!psnData->streaming_count) { psnData->stream_bytes = 0; psnData->stream_pushes = 0; psnData->stream_push_pos = 0; } ++psnData->streaming_count; fStreamingEnabled = true; fStreamingActive = false; iStreamTrackCount = 0; lStreamTrackValue = -1; lStreamTrackInterval = -1; return 0; } HRESULT AVIReadStream::EndStreaming() { if (!fStreamingEnabled) return 0; if (fRealTime) parent->AdjustRealTime(false); if (fStreamingActive) parent->DisableStreaming(streamno); fStreamingEnabled = false; fStreamingActive = false; if (!--psnData->streaming_count) { } return 0; } HRESULT AVIReadStream::Info(AVISTREAMINFO *pasi, long lSize) { AVISTREAMINFO asi; memset(&asi, 0, sizeof asi); asi.fccType = psnData->hdr.fccType; asi.fccHandler = psnData->hdr.fccHandler; asi.dwFlags = psnData->hdr.dwFlags; asi.wPriority = psnData->hdr.wPriority; asi.wLanguage = psnData->hdr.wLanguage; asi.dwScale = psnData->hdr.dwScale; asi.dwRate = psnData->hdr.dwRate; asi.dwStart = psnData->hdr.dwStart; asi.dwLength = psnData->hdr.dwLength; asi.dwInitialFrames = psnData->hdr.dwInitialFrames; asi.dwSuggestedBufferSize = psnData->hdr.dwSuggestedBufferSize; asi.dwQuality = psnData->hdr.dwQuality; asi.dwSampleSize = psnData->hdr.dwSampleSize; asi.rcFrame.top = psnData->hdr.rcFrame.top; asi.rcFrame.left = psnData->hdr.rcFrame.left; asi.rcFrame.right = psnData->hdr.rcFrame.right; asi.rcFrame.bottom = psnData->hdr.rcFrame.bottom; if (lSize < sizeof asi) memcpy(pasi, &asi, lSize); else { memcpy(pasi, &asi, sizeof asi); memset((char *)pasi + sizeof asi, 0, lSize - sizeof asi); } return 0; } bool AVIReadStream::IsKeyFrame(long lFrame) const { if (sampsize) return true; else { if (lFrame < 0 || lFrame >= length) return false; return !(pIndex[lFrame].size & 0x80000000); } } HRESULT AVIReadStream::Read(long lStart, long lSamples, void *lpBuffer, long cbBuffer, long *plBytes, long *plSamples) { long lActual; if (lStart < 0 || lStart >= length || (lSamples <= 0 && lSamples != AVISTREAMREAD_CONVENIENT)) { // umm... dummy! can't read outside of stream! if (plBytes) *plBytes = 0; if (plSamples) *plSamples = 0; return 0; } // blocked or discrete? if (sampsize) { AVIIndexEntry2 *avie2, *avie2_limit = pIndex+frames; __int64 byte_off = (__int64)lStart * sampsize; __int64 bytecnt; __int64 actual_bytes=0; __int64 block_pos; // too small to hold a sample? if (lpBuffer && cbBuffer < sampsize) return AVIERR_BUFFERTOOSMALL; // find the frame that has the starting sample -- try and work // from our last position to save time if (byte_off >= i64CachedPosition) { block_pos = i64CachedPosition; avie2 = pCachedEntry; byte_off -= block_pos; } else { block_pos = 0; avie2 = pIndex; } while(byte_off >= (avie2->size & 0x7FFFFFFF)) { byte_off -= (avie2->size & 0x7FFFFFFF); block_pos += (avie2->size & 0x7FFFFFFF); ++avie2; } pCachedEntry = avie2; i64CachedPosition = block_pos; // Client too lazy to specify a size? if (lSamples == AVISTREAMREAD_CONVENIENT) { lSamples = ((avie2->size & 0x7FFFFFFF) - byte_off) / sampsize; if (!lSamples && avie2+1 < avie2_limit) lSamples = ((avie2[0].size & 0x7FFFFFFF) + (avie2[1].size & 0x7FFFFFFF) - byte_off) / sampsize; if (lSamples < 0) lSamples = 1; } // trim down sample count if (lpBuffer && lSamples > cbBuffer / sampsize) lSamples = cbBuffer / sampsize; if (lStart+lSamples > length) lSamples = length - lStart; bytecnt = lSamples * sampsize; // printf("Will read %d bytes ( %d samples )\n", bytecnt, lSamples); // begin reading frames from this point on if (lpBuffer) { while(bytecnt > 0) { long tc; tc = (avie2->size & 0x7FFFFFFF) - byte_off; if (tc > bytecnt) tc = (long)bytecnt; lActual = parent->cache->Read((char*)lpBuffer, (Unsigned)streamno, ((int)avie2-(int)pIndex)/sizeof(AVIIndexEntry2), (Unsigned)bytecnt, (Unsigned)byte_off); if (lActual < 0) break; actual_bytes += lActual; ++avie2; byte_off = 0; if (lActual < tc) break; bytecnt -= tc; // printf("cache->read returns %d, new bytecnt %d\n", // lActual, bytecnt); lpBuffer = (char *)lpBuffer + tc; } if (actual_bytes < sampsize) { if (plBytes) *plBytes = 0; if (plSamples) *plSamples = 0; return AVIERR_FILEREAD; } actual_bytes -= actual_bytes % sampsize; if (plBytes) *plBytes = actual_bytes; if (plSamples) *plSamples = actual_bytes / sampsize; lStreamTrackValue = lStart + actual_bytes / sampsize; } else { if (plBytes) *plBytes = (long)bytecnt; if (plSamples) *plSamples = lSamples; } } else { AVIIndexEntry2 *avie2 = &pIndex[lStart]; if (lpBuffer && (avie2->size & 0x7FFFFFFF) > cbBuffer) { return AVIERR_BUFFERTOOSMALL; } if (lpBuffer) { // detect streaming // read data // if (psnData->cache && fStreamingActive) { //OutputDebugString("[v] attempting cached read\n"); /* lActual = psnData->cache->Read(lpBuffer, avie2->pos, avie2->pos + 8, avie2->size & 0x7FFFFFFF); psnData->stream_bytes += lActual; } else lActual = parent->ReadData(streamno, lpBuffer, avie2->pos+8, avie2->size & 0x7FFFFFFF); */ lActual = parent->cache->Read((char*)lpBuffer, (Unsigned)streamno, ((int)avie2-(int)pIndex)/sizeof(AVIIndexEntry2), (Unsigned)(avie2->size & 0x7FFFFFFF), 0); if (lActual != (avie2->size & 0x7FFFFFFF)) { if (plBytes) *plBytes = 0; if (plSamples) *plSamples = 0; return AVIERR_FILEREAD; } } if (plBytes) *plBytes = avie2->size & 0x7FFFFFFF; if (plSamples) *plSamples = 1; } return 0; } long AVIReadStream::Start() { return 0; } long AVIReadStream::End() { return length; } long AVIReadStream::PrevKeyFrame(long lFrame) { if (sampsize) return lFrame>0 ? lFrame-1 : -1; if (lFrame < 0) return -1; if (lFrame >= length) lFrame = length; while(--lFrame > 0) if (!(pIndex[lFrame].size & 0x80000000)) return lFrame; return -1; } long AVIReadStream::NextKeyFrame(long lFrame) { if (sampsize) return lFrame= length) return -1; while(++lFrame < length) if (!(pIndex[lFrame].size & 0x80000000)) return lFrame; return -1; } long AVIReadStream::NearestKeyFrame(long lFrame) { long lprev; if (sampsize) return lFrame; if (IsKeyFrame(lFrame)) return lFrame; lprev = PrevKeyFrame(lFrame); if (lprev < 0) return 0; else return lprev; } HRESULT AVIReadStream::FormatSize(long lFrame, long *plSize) { *plSize = psnData->lFormatLen; return 0; } HRESULT AVIReadStream::ReadFormat(long lFrame, void *pFormat, long *plSize) { if (!pFormat) { *plSize = psnData->lFormatLen; return 0; } if (*plSize < psnData->lFormatLen) { memcpy(pFormat, psnData->pFormat, *plSize); } else { memcpy(pFormat, psnData->pFormat, psnData->lFormatLen); *plSize = psnData->lFormatLen; } return 0; } bool AVIReadStream::isStreaming() { return fStreamingActive; } /////////////////////////////////////////////////////////////////////////// AVIReadHandler::AVIReadHandler(const char *s) { this->hFile = INVALID_HANDLE_VALUE; this->hFileUnbuffered = INVALID_HANDLE_VALUE; ref_count = 1; streams=0; hyperindexed = false; fStreamsActive = 0; fDisableFastIO = false; streamBuffer = NULL; nRealTime = 0; nActiveStreamers = 0; fFakeIndex = false; nFiles = 1; nCurrentFile = 0; pSegmentHint = NULL; cache=0; if (!g_disklockinited) { g_disklockinited=true; pthread_mutex_init(&g_diskcs, NULL); } _construct(s); } /* AVIReadHandler::AVIReadHandler(PAVIFILE paf) { this->hFile = INVALID_HANDLE_VALUE; this->hFileUnbuffered = INVALID_HANDLE_VALUE; this->paf = paf; ref_count = 1; streams=0; streamBuffer = NULL; pSegmentHint = NULL; fFakeIndex = false; } */ AVIReadHandler::~AVIReadHandler() { // cout<<"~AVIReadHandler"<NextFromHead()) /// while(pasn) { // printf("%x\n", pasn); if(pasn->index.index2Ptr()==NULL) pasn->index.makeIndex2(); cache->AddStream(i++, pasn->index.index2Ptr(), 0, pasn->index.indexLen()); pasn=pasn_next; } cache->Async(); // Create first link if (!(pDesc = new AVIFileDesc)) throw MEMORY_FATAL; pDesc->hFile = hFile; pDesc->hFileUnbuffered = hFileUnbuffered; pDesc->i64Size = i64Size = _sizeFile(); listFiles.AddHead(pDesc); } catch(FatalError&) { _destruct(); throw; } } bool AVIReadHandler::AppendFile(const char *pszFile) { List2 newstreams; AVIStreamNode *pasn_old, *pasn_new, *pasn_old_next=NULL, *pasn_new_next=NULL; AVIFileDesc *pDesc; nCurrentFile = -1; // open file // hFile = CreateFile(pszFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); hFile=open(pszFile, O_RDONLY); hFileUnbuffered=open(pszFile, O_RDONLY); if (hFile<0) throw FATAL("Couldn't open file"); try { _parseFile(newstreams); pasn_old = listStreams.AtHead(); pasn_new = newstreams.AtHead(); while((bool)(pasn_old_next = pasn_old->NextFromHead()) & (bool)(pasn_new_next = pasn_new->NextFromHead())) { if (pasn_old->hdr.fccType != pasn_new->hdr.fccType) break; if (pasn_old->hdr.fccHandler != pasn_new->hdr.fccHandler) break; if (pasn_old->hdr.dwScale != pasn_new->hdr.dwScale) break; if (pasn_old->hdr.dwRate != pasn_new->hdr.dwRate) break; if (pasn_old->hdr.dwSampleSize != pasn_new->hdr.dwSampleSize) break; if (pasn_old->lFormatLen != pasn_new->lFormatLen) break; if (memcmp(pasn_old->pFormat, pasn_new->pFormat, pasn_old->lFormatLen)) break; pasn_old = pasn_old_next; pasn_new = pasn_new_next; } if (pasn_old_next || pasn_new_next) throw FATAL("Cannot append segment: The segment is not from the same video clip."); if (!(pDesc = new AVIFileDesc)) throw MEMORY_FATAL; pDesc->hFile = hFile; pDesc->hFileUnbuffered = hFileUnbuffered; pDesc->i64Size = _sizeFile(); } catch(FatalError& e) { while(pasn_new = newstreams.RemoveHead()) delete pasn_new; close(hFile); if (hFileUnbuffered >=0) close(hFileUnbuffered); throw; } // Accept segment; begin merging process. pasn_old = listStreams.AtHead(); while(pasn_old_next = pasn_old->NextFromHead()) { pasn_new = newstreams.RemoveHead(); // Fix up header. pasn_old->hdr.dwLength += pasn_new->hdr.dwLength; if (pasn_new->hdr.dwSuggestedBufferSize > pasn_old->hdr.dwSuggestedBufferSize) pasn_old->hdr.dwSuggestedBufferSize = pasn_new->hdr.dwSuggestedBufferSize; pasn_old->bytes += pasn_new->bytes; pasn_old->frames += pasn_new->frames; pasn_old->length += pasn_new->length; // Merge indices. int oldlen = pasn_old->index.indexLen(); AVIIndexEntry2 *idx_old = pasn_old->index.takeIndex2(); AVIIndexEntry2 *idx_new = pasn_new->index.index2Ptr(); int i; pasn_old->index.clear(); for(i=0; iindex.add(&idx_old[i]); } delete[] idx_old; for(i=pasn_new->index.indexLen(); i; i--) { idx_new->size ^= 0x80000000; idx_new->pos += (__int64)nFiles << 48; pasn_old->index.add(idx_new++); } pasn_old->index.makeIndex2(); // Notify all handlers. AVIReadStream *pStream, *pStreamNext; pStream = pasn_old->listHandlers.AtHead(); while(pStreamNext = pStream->NextFromHead()) { pStream->Reinit(); pStream = pStreamNext; } // Next! pasn_old = pasn_old_next; delete pasn_new; } ++nFiles; listFiles.AddTail(pDesc); return true; } int AVIReadHandler::_parseFile(List2& streamlist) { FOURCC fccType; DWORD dwLength; DWORD dwLengthLeft; bool index_found = false; bool size_invalid = false; bool fAcceptIndexOnly = true; AVIStreamNode *pasn, *pasn_next; __int64 i64ChunkMoviPos = 0; DWORD dwChunkMoviLength = 0; if (!_readChunkHeader(fccType, dwLength)) throw FATAL("Invalid AVI file: File is less than 8 bytes"); if (fccType != FOURCC_RIFF) throw FATAL("Invalid AVI file: Not a RIFF file"); // If the RIFF header is <4 bytes, assume it was an improperly closed // file. // if (dwLength < 4) // throw MyError("Invalid AVI file: RIFF wrapper has invalid size"); if (dwLength < 4) { dwLength = 0xFFFFFFF0; size_invalid = true; } dwLengthLeft = dwLength-4; _readFile2(&fccType, 4); if (fccType != ' IVA') throw FATAL("Invalid AVI file: RIFF type is not 'AVI'"); // begin parsing chunks while(dwLengthLeft >= 8 && _readChunkHeader(fccType, dwLength)) { char s[5]; memcpy(s, &fccType, 4); s[4]=0; // printf("%08Lx %08Lx ", _posFile()+dwLengthLeft, _posFile()); // printf("Chunk '%.4s', length %08lx\n", s, dwLength); if (!size_invalid) { dwLengthLeft -= 8; if (dwLength > dwLengthLeft) { printf("Invalid AVI file\n"); return -1; } // throw MyError("Invalid AVI file: chunk at %I64d extends outside of parent by %ld bytes", _posFile()-8, dwLength-dwLengthLeft); dwLengthLeft -= (dwLength + (dwLength&1)); } int hdrSize; switch(fccType) { case ckidAVIMAINHDR: hdrSize= (dwLength, sizeof(m_header)) ? dwLength: sizeof(m_header); _readFile2(&m_header, hdrSize); // printf("Found main header, size %d\n", hdrSize); dwLength-=hdrSize; break; case FOURCC_LIST: _readFile2(&fccType, 4); if (dwLength<4 && (fccType != 'ivom' || !size_invalid)) throw FATAL("Invalid AVI file: LIST chunk <4 bytes"); dwLength -= 4; // _RPT1(0,"\tList type '%-4s'\n", &fccType); switch(fccType) { case 'ivom': // printf("Found MOVI chunk. Position %Lx\n", _posFile()); if (dwLength < 8 && size_invalid) { i64ChunkMoviPos = _posFile(); dwChunkMoviLength = 0xFFFFFFF0; dwLength = 0; } else { i64ChunkMoviPos = _posFile(); dwChunkMoviLength = dwLength; } if (fAcceptIndexOnly) goto terminate_scan; break; case listtypeAVIHEADER: dwLengthLeft += (dwLength + (dwLength&1)) + 4; dwLength = 0; // silently enter the header block break; case listtypeSTREAMHEADER: if (!_parseStreamHeader(streamlist, dwLength)) fAcceptIndexOnly = false; ++streams; dwLength = 0; break; } break; case ckidAVINEWINDEX: // idx1 if (!hyperindexed) { index_found = _parseIndexBlock(streamlist, dwLength/16, i64ChunkMoviPos); dwLength &= 15; } break; case ckidAVIPADDING: // JUNK break; case 'mges': // VirtualDub segment hint block delete pSegmentHint; if (!(pSegmentHint = new char[dwLength])) throw MEMORY_FATAL; _readFile2(pSegmentHint, dwLength); if (dwLength&1) _skipFile2(1); dwLength = 0; break; } if (dwLength) { if (!_skipFile2(dwLength + (dwLength&1))) break; } // If we're trying to read a broken file, quit as soon as we see the index // block. if (size_invalid && fccType == ckidAVINEWINDEX) break; } if (i64ChunkMoviPos == 0) throw FATAL("This AVI file doesn't have a movie data block (movi)!"); terminate_scan: if (!index_found && !hyperindexed) { printf("Reconstructing table\n"); pasn = streamlist.AtHead(); pasn->hdr.dwLength=0; while(pasn_next = pasn->NextFromHead()) { pasn = pasn_next; pasn->hdr.dwLength=0; } // ProgressDialog pd(NULL, "AVI Import Filter", "Reconstructing missing index block", (dwChunkMoviLength+1023)/1024, true); // pd.setValueFormat("%ldK of %ldK"); // obtain length of file and limit scanning if so int progress=0; __int64 i64FileSize = 0x7FFFFFFFFFFFFFFFLL; dwLengthLeft = dwChunkMoviLength; __int64 pos=lseek64(hFile, 0, SEEK_CUR); i64FileSize=lseek64(hFile, 0, SEEK_END); lseek64(hFile, pos, SEEK_SET); fFakeIndex = true; _seekFile(i64ChunkMoviPos); while(dwLengthLeft >= 8 && _readChunkHeader(fccType, dwLength)) { int stream; __int64 cpos=_posFile(); while(cpos/(i64FileSize/10)>progress) { std::cerr<<"."; progress++; } // _RPT2(0,"(stream header) Chunk '%-4s', length %08lx\n", &fccType, dwLength); dwLengthLeft -= 8+(dwLength + (dwLength&1)); if (dwLength) { if (!_skipFile2(dwLength + (dwLength&1))) break; } if (_posFile() > i64FileSize) break; if (isxdigit(fccType&0xff) && isxdigit((fccType>>8)&0xff)) { stream = StreamFromFOURCC(fccType); if (stream >=0 && stream < streams) { pasn = streamlist.AtHead(); while(stream-- && (pasn_next = pasn->NextFromHead())) pasn = pasn_next; if (pasn && pasn_next) { pasn->index.add(fccType, _posFile()-(dwLength + (dwLength&1))-8, dwLength, 1); pasn->bytes += dwLength; if(pasn->hdr.fccType==streamtypeAUDIO) { if(pasn->hdr.dwSampleSize) pasn->hdr.dwLength+=dwLength/pasn->hdr.dwSampleSize; else pasn->hdr.dwLength+=dwLength; } else pasn->hdr.dwLength++; } } } // pd.advance((long)((_posFile() - i64ChunkMoviPos)/1024)); // pd.check(); } std::cerr<NextFromHead()) { if (!pasn->index.makeIndex2()) throw MEMORY_FATAL; pasn->frames = pasn->index.indexLen(); // Clear sample size for video streams! if (pasn->hdr.fccType == streamtypeVIDEO) pasn->hdr.dwSampleSize=0; if (pasn->hdr.dwSampleSize) pasn->length = pasn->bytes / pasn->hdr.dwSampleSize; else pasn->length = pasn->frames; pasn = pasn_next; } // throw MyError("Parse complete. Aborting."); } bool AVIReadHandler::_parseStreamHeader(List2& streamlist, DWORD dwLengthLeft) { AVIStreamNode *pasn; FOURCC fccType; DWORD dwLength; if (!(pasn = new AVIStreamNode())) return false; // throw MyMemoryError(); try { while(dwLengthLeft >= 8 && _readChunkHeader(fccType, dwLength)) { // _RPT2(0,"(stream header) Chunk '%-4s', length %08lx\n", &fccType, dwLength); dwLengthLeft -= 8; if (dwLength > dwLengthLeft) throw FATAL("Invalid AVI file: chunk size extends outside of parent"); dwLengthLeft -= (dwLength + (dwLength&1)); switch(fccType) { case ckidSTREAMHEADER: memset(&pasn->hdr, 0, sizeof pasn->hdr); if (dwLength < sizeof pasn->hdr) { _readFile2(&pasn->hdr, dwLength); if (dwLength & 1) _skipFile(1); } else { _readFile2(&pasn->hdr, sizeof pasn->hdr); _skipFile(dwLength+(dwLength&1) - sizeof pasn->hdr); } dwLength = 0; pasn->keyframe_only = false; break; case ckidSTREAMFORMAT: if (!(pasn->pFormat = new char[pasn->lFormatLen = dwLength])) return false; // throw MyMemoryError(); _readFile2(pasn->pFormat, dwLength); if (pasn->hdr.fccType == streamtypeVIDEO) { switch(((BITMAPINFOHEADER *)pasn->pFormat)->biCompression) { case 0: case ' WAR': case ' BID': case '1bmd': case 'gpjm': case 'GPJM': case '2YUY': case 'YVYU': case 'UYVY': case '21VY': case '024I': case 'P14Y': case 'vuyc': pasn->keyframe_only = true; } } if (dwLength & 1) _skipFile(1); dwLength = 0; break; case 'xdni': // OpenDML extended index _parseExtendedIndexBlock(streamlist, pasn, -1, dwLength); hyperindexed = true; break; case ckidAVIPADDING: // JUNK break; } if (dwLength) { if (!_skipFile2(dwLength + (dwLength&1))) break; } } if (dwLengthLeft) _skipFile2(dwLengthLeft); } catch(...) { delete pasn; throw; } // _RPT1(0,"Found stream: type %s\n", pasn->hdr.fccType==streamtypeVIDEO ? "video" : pasn->hdr.fccType==streamtypeAUDIO ? "audio" : "unknown"); streamlist.AddTail(pasn); return hyperindexed; } bool AVIReadHandler::_parseIndexBlock(List2& streamlist, int count, __int64 movi_offset) { AVIINDEXENTRY avie[32]; AVIStreamNode *pasn, *pasn_next; bool absolute_addr = true; // Some AVI files have relative addresses in their AVI index chunks, and some // relative. They're supposed to be relative to the 'movi' chunk; all versions // of VirtualDub using fast write routines prior to build 4936 generate absolute // addresses (oops). AVIFile and ActiveMovie are both ambivalent. I guess we'd // better be as well. while(count > 0) { int tc = count; int i; if (tc>32) tc=32; count -= tc; if (tc*sizeof(AVIINDEXENTRY) != _readFile(avie, tc*sizeof(AVIINDEXENTRY))) { pasn = streamlist.AtHead(); while(pasn_next = pasn->NextFromHead()) { pasn->index.clear(); pasn->bytes = 0; pasn = pasn_next; } return false; } for(i=0; iNextFromHead()) && stream--) pasn = pasn_next; if (pasn && pasn_next) { if (absolute_addr) pasn->index.add(&avie[i]); else pasn->index.add(avie[i].ckid, (movi_offset-4) + (__int64)avie[i].dwChunkOffset, avie[i].dwChunkLength, !!(avie[i].dwFlags & AVIIF_KEYFRAME)); pasn->bytes += avie[i].dwChunkLength; } } } return true; } int AVIReadHandler::_parseExtendedIndexBlock(List2& streamlist, AVIStreamNode *pasn, __int64 fpos, DWORD dwLength) { union { AVISUPERINDEX idxsuper; AVISTDINDEX idxstd; }; union { struct _avisuperindex_entry superent[64]; struct _avistdindex_entry stdent[128]; struct _avifieldindex_entry fieldent[85]; }; int entries, tp; int i; __int64 i64FPSave = _posFile(); if (fpos>=0) _seekFile(fpos); _readFile2((char *)&idxsuper + 8, sizeof(AVISUPERINDEX) - 8); switch(idxsuper.bIndexType) { case AVI_INDEX_OF_INDEXES: // sanity check if (idxsuper.wLongsPerEntry != 4) return false; // throw MyError("Invalid superindex block in stream"); // if (idxsuper.bIndexSubType != 0) // throw MyError("Field indexes not supported"); entries = idxsuper.nEntriesInUse; while(entries > 0) { tp = sizeof superent / sizeof superent[0]; if (tp>entries) tp=entries; _readFile2(superent, tp*sizeof superent[0]); for(i=0; i 0) { tp = sizeof fieldent / sizeof fieldent[0]; if (tp>entries) tp=entries; _readFile2(fieldent, tp*sizeof fieldent[0]); for(i=0; iindex.add(idxstd.dwChunkId, idxstd.qwBaseOffset+fieldent[i].dwOffset-8, fieldent[i].dwSize&0x7FFFFFFF, !(fieldent[i].dwSize&0x80000000)); pasn->bytes += fieldent[i].dwSize & 0x7FFFFFFF; } entries -= tp; } } else { if (idxstd.wLongsPerEntry != 2) return false; // throw MyError("Invalid frame stdindex block in video stream"); while(entries > 0) { tp = sizeof stdent / sizeof stdent[0]; if (tp>entries) tp=entries; _readFile2(stdent, tp*sizeof stdent[0]); for(i=0; iindex.add(idxstd.dwChunkId, idxstd.qwBaseOffset+stdent[i].dwOffset-8, stdent[i].dwSize&0x7FFFFFFF, !(stdent[i].dwSize&0x80000000)); pasn->bytes += stdent[i].dwSize & 0x7FFFFFFF; } entries -= tp; } } break; default: // throw MyError("Unknown hyperindex type"); return false; } _seekFile(i64FPSave); } void AVIReadHandler::_destruct() { AVIStreamNode *pasn; AVIFileDesc *pDesc; /* if (paf) AVIFileRelease(paf); */ while(pasn = listStreams.RemoveTail()) delete pasn; delete streamBuffer; if (listFiles.IsEmpty()) { if (hFile >=0) close(hFile); if (hFileUnbuffered >=0) close(hFileUnbuffered); } else while(pDesc = listFiles.RemoveTail()) { close(pDesc->hFile); close(pDesc->hFileUnbuffered); delete pDesc; } if(cache)delete cache; delete pSegmentHint; } /////////////////////////////////////////////////////////////////////////// void AVIReadHandler::Release() { if (!--ref_count) delete this; } void AVIReadHandler::AddRef() { ++ref_count; } IvAVIReadStream *AVIReadHandler::GetStream(DWORD fccType, LONG lParam) { { AVIStreamNode *pasn, *pasn_next; int streamno = 0; pasn = listStreams.AtHead(); while(pasn_next = pasn->NextFromHead()) { if (pasn->hdr.fccType == fccType && !lParam--) break; pasn = pasn_next; ++streamno; } if (pasn_next) { return new AVIReadStream(this, pasn, streamno); } return NULL; } } void AVIReadHandler::EnableFastIO(bool f) { fDisableFastIO = !f; } bool AVIReadHandler::isOptimizedForRealtime() { return nRealTime!=0; } bool AVIReadHandler::isStreaming() { return nActiveStreamers!=0; } bool AVIReadHandler::isIndexFabricated() { return fFakeIndex; } bool AVIReadHandler::getSegmentHint(const char **ppszPath) { if (!pSegmentHint) { if (ppszPath) *ppszPath = NULL; return false; } if (ppszPath) *ppszPath = pSegmentHint+1; return !!pSegmentHint[0]; } /////////////////////////////////////////////////////////////////////////// void AVIReadHandler::EnableStreaming(int stream) { if (!fStreamsActive) { if (!(streamBuffer = new char[STREAM_SIZE])) return; // throw MyMemoryError(); i64StreamPosition = -1; sbPosition = sbSize = 0; } fStreamsActive |= (1<>48)) _SelectFile((int)(i64StreamPosition>>48)); if (sbPosition >= sbSize) { if (nRealTime || ((i64StreamPosition+sbSize) & -STREAM_BLOCK_SIZE)+STREAM_SIZE > i64Size) { i64StreamPosition += sbSize; sbPosition = 0; _seekFile(i64StreamPosition & 0x0000FFFFFFFFFFFFLL); sbSize = _readFile(streamBuffer, STREAM_RT_SIZE); if (sbSize < 0) { sbSize = 0; return NULL; // throw MyWin32Error("Failure streaming AVI file: %%s.",GetLastError()); } } else { i64StreamPosition += sbSize; sbPosition = i64StreamPosition & (STREAM_BLOCK_SIZE-1); i64StreamPosition &= -STREAM_BLOCK_SIZE; _seekFileUnbuffered(i64StreamPosition & 0x0000FFFFFFFFFFFFLL); sbSize = _readFileUnbuffered(streamBuffer, STREAM_SIZE); if (sbSize < 0) { sbSize = 0; return NULL; // throw MyWin32Error("Failure streaming AVI file: %%s.",GetLastError()); } } } if (sbPosition >= sbSize) return NULL; if (bytes > sbSize - sbPosition) bytes = sbSize - sbPosition; sbPosition += bytes; return streamBuffer + sbPosition - bytes; } __int64 AVIReadHandler::getStreamPtr() { return i64StreamPosition + sbPosition; } long AVIReadHandler::ReadData(int stream, void *buffer, __int64 position, long len) { if (nCurrentFile<0 || nCurrentFile != (int)(position>>48)) _SelectFile((int)(position>>48)); if (!_seekFile2(position & 0x0000FFFFFFFFFFFFLL)) return -1; return _readFile(buffer, len); } void AVIReadHandler::_SelectFile(int file) { AVIFileDesc *pDesc, *pDesc_next; nCurrentFile = file; pDesc = listFiles.AtHead(); while((pDesc_next = pDesc->NextFromHead()) && file--) pDesc = pDesc_next; hFile = pDesc->hFile; hFileUnbuffered = pDesc->hFileUnbuffered; i64Size = pDesc->i64Size; }