mirror of
https://gitlab.com/mbunkus/mkvtoolnix.git
synced 2024-12-24 20:01:53 +00:00
2472 lines
66 KiB
C++
2472 lines
66 KiB
C++
// VirtualDub - Video processing and capture application
|
||
// Copyright (C) 1998-2001 Avery Lee
|
||
//
|
||
// 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.
|
||
|
||
//
|
||
// Modified by Julien 'Cyrius' Coloos
|
||
// 20-09-2003
|
||
//
|
||
|
||
#include "AVIReadHandler.h"
|
||
#include "AVIIndex.h"
|
||
#include "list.h"
|
||
#include "Fixes.h"
|
||
|
||
#include "common_mmreg.h"
|
||
#include <math.h>
|
||
|
||
#include "common.h"
|
||
#include "error.h"
|
||
|
||
//#define STREAMING_DEBUG
|
||
|
||
///////////////////////////////////////////////////////////////////////////
|
||
|
||
/*namespace {
|
||
enum { kVDST_AVIReadHandler = 2 };
|
||
|
||
enum {
|
||
kVDM_AvisynthDetected, // AVI: Avisynth detected. Extended error handling enabled.
|
||
kVDM_OpenDMLIndexDetected, // AVI: OpenDML hierarchical index detected on stream %d.
|
||
kVDM_IndexMissing, // AVI: Index not found or damaged -- reconstructing via file scan.
|
||
kVDM_InvalidChunkDetected, // AVI: Invalid chunk detected at %lld. Enabling aggressive recovery mode.
|
||
kVDM_StreamFailure, // AVI: Invalid block found at %lld -- disabling streaming.
|
||
kVDM_FixingBadSampleRate, // AVI: Stream %d has an invalid sample rate. Substituting %lu samples/sec as placeholder.
|
||
};
|
||
}*/
|
||
|
||
///////////////////////////////////////////////////////////////////////////
|
||
|
||
// 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 {
|
||
sint64_le qwOffset; // absolute file offset, offset 0 is
|
||
// unused entry??
|
||
uint32_le dwSize; // size of index chunk at this offset
|
||
uint32_le dwDuration; // time span in stream ticks
|
||
};
|
||
struct _avistdindex_entry {
|
||
uint32_le dwOffset; // qwBaseOffset + this is absolute file offset
|
||
uint32_le dwSize; // bit 31 is set if this is NOT a keyframe
|
||
};
|
||
struct _avifieldindex_entry {
|
||
uint32_le dwOffset;
|
||
uint32_le dwSize;
|
||
uint32_le dwOffsetField2;
|
||
};
|
||
|
||
typedef struct __attribute__((__packed__)) _avisuperindex_chunk {
|
||
uint32_le fcc; // <20>ix##<23>
|
||
uint32_le cb; // size of this structure
|
||
uint16_le wLongsPerEntry; // must be 4 (size of each entry in aIndex array)
|
||
uint8 bIndexSubType; // must be 0 or AVI_INDEX_2FIELD
|
||
uint8 bIndexType; // must be AVI_INDEX_OF_INDEXES
|
||
uint32_le nEntriesInUse; // number of entries in aIndex array that
|
||
// are used
|
||
uint32_le dwChunkId; // <20>##dc<64> or <20>##db<64> or <20>##wb<77>, etc
|
||
uint32_le dwReserved[3]; // must be 0
|
||
struct _avisuperindex_entry aIndex[];
|
||
} AVISUPERINDEX, * PAVISUPERINDEX;
|
||
|
||
typedef struct __attribute__((__packed__)) _avistdindex_chunk {
|
||
uint32_le fcc; // <20>ix##<23>
|
||
uint32_le cb;
|
||
uint16_le wLongsPerEntry; // must be sizeof(aIndex[0])/sizeof(DWORD)
|
||
uint8 bIndexSubType; // must be 0
|
||
uint8 bIndexType; // must be AVI_INDEX_OF_CHUNKS
|
||
uint32_le nEntriesInUse; //
|
||
uint32_le dwChunkId; // <20>##dc<64> or <20>##db<64> or <20>##wb<77> etc..
|
||
sint64_le qwBaseOffset; // all dwOffsets in aIndex array are
|
||
// relative to this
|
||
uint32_le dwReserved3; // must be 0
|
||
struct _avistdindex_entry aIndex[];
|
||
} AVISTDINDEX, * PAVISTDINDEX;
|
||
|
||
/*typedef struct _avifieldindex_chunk {
|
||
uint32_le fcc;
|
||
uint32_le cb;
|
||
uint16_le wLongsPerEntry;
|
||
uint8 bIndexSubType;
|
||
uint8 bIndexType;
|
||
uint32_le nEntriesInUse;
|
||
uint32_le dwChunkId;
|
||
sint64_le qwBaseOffset;
|
||
uint32_le dwReserved3;
|
||
struct _avifieldindex_entry aIndex[];
|
||
} AVIFIELDINDEX, * PAVIFIELDINDEX;*/
|
||
|
||
///////////////////////////////////////////////////////////////////////////
|
||
|
||
IAVIReadStream::~IAVIReadStream() {
|
||
}
|
||
|
||
///////////////////////////////////////////////////////////////////////////
|
||
|
||
class AVIStreamNode;
|
||
class AVIReadHandler;
|
||
|
||
class AVIReadCache {
|
||
public:
|
||
long cache_hit_bytes, cache_miss_bytes;
|
||
int reads;
|
||
|
||
AVIReadCache(int nlines, int nstream, AVIReadHandler *root, AVIStreamNode *psnData);
|
||
~AVIReadCache();
|
||
|
||
void ResetStatistics();
|
||
bool WriteBegin(sint64 pos, long len);
|
||
void Write(void *buffer, long len);
|
||
void WriteEnd();
|
||
long Read(void *dest, sint64 chunk_pos, sint64 pos, long len);
|
||
|
||
long getMaxRead() {
|
||
return (lines_max - 1) << 4;
|
||
}
|
||
|
||
private:
|
||
AVIStreamNode *psnData;
|
||
sint64 (*buffer)[2];
|
||
int lines_max, lines;
|
||
long read_tail, write_tail, write_hdr;
|
||
int write_offset;
|
||
int stream;
|
||
AVIReadHandler *source;
|
||
};
|
||
|
||
class AVIStreamNode : public ListNode2<AVIStreamNode> {
|
||
public:
|
||
AVIStreamHeader_fixed hdr;
|
||
char *pFormat;
|
||
long lFormatLen;
|
||
AVIIndex index;
|
||
sint64 bytes;
|
||
bool keyframe_only;
|
||
bool was_VBR;
|
||
double bitrate_mean;
|
||
double bitrate_stddev;
|
||
double max_deviation; // seconds
|
||
int handler_count;
|
||
class AVIReadCache *cache;
|
||
int streaming_count;
|
||
sint64 stream_push_pos;
|
||
sint64 stream_bytes;
|
||
int stream_pushes;
|
||
long length;
|
||
long frames;
|
||
List2<class AVIReadStream> listHandlers;
|
||
|
||
AVIStreamNode();
|
||
~AVIStreamNode();
|
||
|
||
void FixVBRAudio();
|
||
};
|
||
|
||
AVIStreamNode::AVIStreamNode() {
|
||
pFormat = NULL;
|
||
bytes = 0;
|
||
handler_count = 0;
|
||
streaming_count = 0;
|
||
|
||
stream_bytes = 0;
|
||
stream_pushes = 0;
|
||
cache = NULL;
|
||
|
||
was_VBR = false;
|
||
}
|
||
|
||
AVIStreamNode::~AVIStreamNode() {
|
||
delete pFormat;
|
||
delete cache;
|
||
}
|
||
|
||
void AVIStreamNode::FixVBRAudio() {
|
||
w32WAVEFORMATEX *pWaveFormat = (w32WAVEFORMATEX *)pFormat;
|
||
|
||
// If this is an MP3 stream, undo the Nandub 1152 value.
|
||
|
||
if (pWaveFormat->wFormatTag == 0x0055) {
|
||
pWaveFormat->nBlockAlign = 1;
|
||
}
|
||
|
||
// Determine VBR statistics.
|
||
|
||
was_VBR = true;
|
||
|
||
const AVIIndexEntry2 *pent = index.index2Ptr();
|
||
sint64 size_accum = 0;
|
||
sint64 max_dev = 0;
|
||
double size_sq_sum = 0.0;
|
||
|
||
for(int i=0; i<frames; ++i) {
|
||
unsigned size = pent[i].size & 0x7ffffffful;
|
||
sint64 mean_center = (bytes * (2*i+1)) / (2*frames);
|
||
sint64 dev = mean_center - (size_accum + (size>>1));
|
||
|
||
if (dev<0)
|
||
dev = -dev;
|
||
|
||
if (dev > max_dev)
|
||
max_dev = dev;
|
||
|
||
size_accum += size;
|
||
size_sq_sum += (double)size*size;
|
||
}
|
||
|
||
// I hate probability & sadistics.
|
||
//
|
||
// Var(X) = E(X2) - E(X)^2
|
||
// = S(X2)/n - (S(x)/n)^2
|
||
// = (n*S(X2) - S(X)^2)/n^2
|
||
//
|
||
// SD(x) = sqrt(n*S(X2) - S(X)^2) / n
|
||
|
||
double frames_per_second = (double)hdr.dwRate / (double)hdr.dwScale;
|
||
double sum1_bits = bytes * 8.0;
|
||
double sum2_bits = size_sq_sum * 64.0;
|
||
|
||
bitrate_mean = (sum1_bits / frames) * frames_per_second;
|
||
bitrate_stddev = sqrt(frames * sum2_bits - sum1_bits * sum1_bits) / frames * frames_per_second;
|
||
max_deviation = (double)max_dev * 8.0 / bitrate_mean;
|
||
|
||
// Assume that each audio block is of the same duration.
|
||
|
||
hdr.dwRate = (uint32)(bitrate_mean/8.0 + 0.5);
|
||
hdr.dwScale = pWaveFormat->nBlockAlign;
|
||
hdr.dwSampleSize = pWaveFormat->nBlockAlign;
|
||
}
|
||
|
||
///////////////////////////////////////////////////////////////////////////
|
||
|
||
/*class AVIFileDesc : public ListNode2<AVIFileDesc> {
|
||
public:
|
||
HANDLE hFile;
|
||
HANDLE hFileUnbuffered;
|
||
sint64 i64Size;
|
||
};*/
|
||
|
||
class AVIStreamNode;
|
||
|
||
class AVIReadHandler : public IAVIReadHandler /*, private File64*/ {
|
||
public:
|
||
bool fDisableFastIO;
|
||
|
||
AVIReadHandler(mm_io_c *input);
|
||
virtual ~AVIReadHandler();
|
||
|
||
void AddRef();
|
||
void Release();
|
||
IAVIReadStream *GetStream(uint32 fccType, sint32 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);
|
||
bool Stream(AVIStreamNode *, sint64 pos);
|
||
sint64 getStreamPtr();
|
||
void FixCacheProblems(class AVIReadStream *);
|
||
long ReadData(int stream, void *buffer, sint64 position, long len);
|
||
|
||
private:
|
||
// enum { STREAM_SIZE = 65536 };
|
||
enum { STREAM_SIZE = 1048576 };
|
||
enum { STREAM_RT_SIZE = 65536 };
|
||
enum { STREAM_BLOCK_SIZE = 4096 };
|
||
|
||
mm_io_c *m_pInput, *m_pUnbufferedInput;
|
||
|
||
int ref_count;
|
||
sint64 i64StreamPosition;
|
||
int streams;
|
||
char *streamBuffer;
|
||
int sbPosition;
|
||
int sbSize;
|
||
long fStreamsActive;
|
||
int nRealTime;
|
||
int nActiveStreamers;
|
||
bool fFakeIndex;
|
||
sint64 i64Size;
|
||
int nFiles, nCurrentFile;
|
||
char * pSegmentHint;
|
||
|
||
// Whenever a file is aggressively recovered, do not allow streaming.
|
||
|
||
bool mbFileIsDamaged;
|
||
|
||
List2<AVIStreamNode> listStreams;
|
||
//List2<AVIFileDesc> listFiles;
|
||
|
||
void _construct(/*const char *pszFile*/);
|
||
void _parseFile(List2<AVIStreamNode>& streams);
|
||
bool _parseStreamHeader(List2<AVIStreamNode>& streams, uint32 dwLengthLeft, bool& bIndexDamaged);
|
||
bool _parseIndexBlock(List2<AVIStreamNode>& streams, int count, sint64);
|
||
void _parseExtendedIndexBlock(List2<AVIStreamNode>& streams, AVIStreamNode *pasn, sint64 fpos, uint32 dwLength);
|
||
void _destruct();
|
||
|
||
char * _StreamRead(long& bytes);
|
||
void _SelectFile(int file);
|
||
|
||
bool _readChunkHeader(uint32_le& pfcc, uint32& pdwLen);
|
||
};
|
||
|
||
IAVIReadHandler *CreateAVIReadHandler(mm_io_c *input) {
|
||
return new AVIReadHandler(input);
|
||
}
|
||
|
||
///////////////////////////////////////////////////////////////////////////
|
||
|
||
AVIReadCache::AVIReadCache(int nlines, int nstream, AVIReadHandler *root, AVIStreamNode *psnData) {
|
||
buffer = new sint64[nlines][2];
|
||
if (!buffer) throw error_c("out of memory");
|
||
|
||
this->psnData = psnData;
|
||
lines = 0;
|
||
lines_max = nlines;
|
||
read_tail = 0;
|
||
write_tail = 0;
|
||
stream = nstream;
|
||
source = root;
|
||
ResetStatistics();
|
||
}
|
||
|
||
AVIReadCache::~AVIReadCache() {
|
||
delete[] buffer;
|
||
}
|
||
|
||
void AVIReadCache::ResetStatistics() {
|
||
reads = 0;
|
||
cache_hit_bytes = cache_miss_bytes = 0;
|
||
}
|
||
|
||
bool AVIReadCache::WriteBegin(sint64 pos, long len) {
|
||
int needed;
|
||
|
||
// delete lines as necessary to make room
|
||
|
||
needed = 1 + (len+15)/16;
|
||
|
||
if (needed > lines_max)
|
||
return false;
|
||
|
||
while(lines+needed > lines_max) {
|
||
int siz = (int)((buffer[read_tail][1]+15)/16 + 1);
|
||
read_tail += siz;
|
||
lines -= siz;
|
||
if (read_tail >= lines_max)
|
||
read_tail -= lines_max;
|
||
}
|
||
|
||
// write in header
|
||
|
||
// _RPT1(0,"\tbeginning write at line %ld\n", write_tail);
|
||
|
||
write_hdr = write_tail;
|
||
write_offset = 0;
|
||
|
||
buffer[write_tail][0] = pos;
|
||
buffer[write_tail][1] = 0;
|
||
|
||
if (++write_tail >= lines_max)
|
||
write_tail = 0;
|
||
|
||
return true;
|
||
}
|
||
|
||
void AVIReadCache::Write(void *src, long len) {
|
||
long dest;
|
||
|
||
// copy in data
|
||
|
||
buffer[write_hdr][1] += len;
|
||
|
||
dest = write_tail + (len + write_offset + 15)/16;
|
||
|
||
if (dest > lines_max) {
|
||
int tc = (lines_max - write_tail)*16 - write_offset;
|
||
|
||
memcpy((char *)&buffer[write_tail][0] + write_offset, src, tc);
|
||
memcpy(&buffer[0][0], (char *)src + tc, len - tc);
|
||
|
||
write_tail = (len-tc)/16;
|
||
write_offset = (len-tc)&15;
|
||
|
||
} else {
|
||
memcpy((char *)&buffer[write_tail][0] + write_offset, src, len);
|
||
write_tail += (len+write_offset)/16;
|
||
if (write_tail >= lines_max)
|
||
write_tail = 0;
|
||
|
||
write_offset = (len+write_offset) & 15;
|
||
}
|
||
}
|
||
|
||
void AVIReadCache::WriteEnd() {
|
||
long cnt = (long)(1 + (buffer[write_hdr][1]+15)/16);
|
||
lines += cnt;
|
||
write_tail = write_hdr + cnt;
|
||
|
||
if (write_tail >= lines_max)
|
||
write_tail -= lines_max;
|
||
|
||
// _RPT3(0,"\twrite complete -- header at line %d, size %ld, next line %ld\n", write_hdr, (long)buffer[write_hdr][1], write_tail);
|
||
}
|
||
|
||
long AVIReadCache::Read(void *dest, sint64 chunk_pos, sint64 pos, long len) {
|
||
long ptr;
|
||
sint64 offset;
|
||
|
||
// _RPT3(0,"Read request: chunk %16I64, pos %16I64d, %ld bytes\n", chunk_pos, pos, len);
|
||
|
||
++reads;
|
||
|
||
do {
|
||
// scan buffer looking for a range that contains data
|
||
|
||
ptr = read_tail;
|
||
while(ptr != write_tail) {
|
||
offset = pos - buffer[ptr][0];
|
||
|
||
// _RPT4(0,"line %8d: pos %16I64d, len %ld bytes (%ld lines)\n", ptr, buffer[ptr][0], (long)buffer[ptr][1], (long)(buffer[ptr][1]+15)/16);
|
||
|
||
if (offset>=0 && offset < buffer[ptr][1]) {
|
||
long end;
|
||
|
||
// cache hit
|
||
|
||
// _RPT1(0, "cache hit (offset %I64d)\n", chunk_pos);
|
||
|
||
cache_hit_bytes += len;
|
||
|
||
while (cache_hit_bytes > 16777216) {
|
||
cache_miss_bytes >>= 1;
|
||
cache_hit_bytes >>= 1;
|
||
}
|
||
|
||
if (len > (long)(buffer[ptr][1]*16 - offset))
|
||
len = (long)(buffer[ptr][1]*16 - offset);
|
||
|
||
ptr += 1+((long)offset>>4);
|
||
if (ptr >= lines_max)
|
||
ptr -= lines_max;
|
||
|
||
end = ptr + ((len+((long)offset&15)+15)>>4);
|
||
|
||
if (end > lines_max) {
|
||
long tc = (lines_max - ptr)*16 - ((long)offset&15);
|
||
memcpy(dest, (char *)&buffer[ptr][0] + ((long)offset&15), tc);
|
||
memcpy((char *)dest + tc, (char *)&buffer[0][0], len-tc);
|
||
} else {
|
||
memcpy(dest, (char *)&buffer[ptr][0] + ((long)offset&15), len);
|
||
}
|
||
|
||
return len;
|
||
}
|
||
|
||
// _RPT4(0,"[x] line %8d: pos %16I64d, len %ld bytes (%ld lines)\n", ptr, buffer[ptr][0], (long)buffer[ptr][1], (long)(buffer[ptr][1]+15)/16);
|
||
ptr += (long)(1+(buffer[ptr][1] + 15)/16);
|
||
|
||
if (ptr >= lines_max)
|
||
ptr -= lines_max;
|
||
}
|
||
|
||
if (source->getStreamPtr() > chunk_pos)
|
||
break;
|
||
|
||
} while(source->Stream(psnData, chunk_pos));
|
||
|
||
// OutputDebugString("cache miss\n");
|
||
// _RPT1(0, "cache miss (offset %I64d)\n", chunk_pos);
|
||
|
||
cache_miss_bytes += len;
|
||
|
||
while (cache_miss_bytes > 16777216) {
|
||
cache_miss_bytes >>= 1;
|
||
cache_hit_bytes >>= 1;
|
||
}
|
||
|
||
return source->ReadData(stream, dest, pos, len);
|
||
}
|
||
|
||
///////////////////////////////////////////////////////////////////////////
|
||
|
||
class AVIReadStream : public IAVIReadStream, public ListNode2<AVIReadStream> {
|
||
friend class AVIReadHandler;
|
||
|
||
public:
|
||
AVIReadStream(AVIReadHandler *, AVIStreamNode *, int);
|
||
~AVIReadStream();
|
||
|
||
long BeginStreaming(long lStart, long lEnd, long lRate);
|
||
long EndStreaming();
|
||
long Info(w32AVISTREAMINFO *pasi, long lSize);
|
||
bool IsKeyFrame(long lFrame);
|
||
long 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);
|
||
long FormatSize(long lFrame, long *plSize);
|
||
long ReadFormat(long lFrame, void *pFormat, long *plSize);
|
||
bool isStreaming();
|
||
bool isKeyframeOnly();
|
||
void Reinit();
|
||
bool getVBRInfo(double& bitrate_mean, double& bitrate_stddev, double& maxdev);
|
||
|
||
private:
|
||
AVIReadHandler *parent;
|
||
AVIStreamNode *psnData;
|
||
AVIIndexEntry2 *pIndex;
|
||
AVIReadCache *rCache;
|
||
long& length;
|
||
long& frames;
|
||
long sampsize;
|
||
int streamno;
|
||
bool fStreamingEnabled;
|
||
bool fStreamingActive;
|
||
int iStreamTrackCount;
|
||
long lStreamTrackValue;
|
||
long lStreamTrackInterval;
|
||
bool fRealTime;
|
||
|
||
sint64 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;
|
||
|
||
// Hack to imitate Microsoft's parser. It seems to ignore this value
|
||
// for audio streams.
|
||
|
||
if (psnData->hdr.fccType == streamtypeAUDIO)
|
||
sampsize = ((w32WAVEFORMATEX *)psnData->pFormat)->nBlockAlign;
|
||
|
||
if (sampsize) {
|
||
i64CachedPosition = 0;
|
||
pCachedEntry = pIndex;
|
||
}
|
||
|
||
psnData->listHandlers.AddTail(this);
|
||
}
|
||
|
||
AVIReadStream::~AVIReadStream() {
|
||
EndStreaming();
|
||
// AVIReadStream is a Node in a list owned by AVIReadHandler (its parent)
|
||
// So call Remove() _before_ releasing the parent
|
||
Remove();
|
||
parent->Release();
|
||
}
|
||
|
||
void AVIReadStream::Reinit() {
|
||
pIndex = psnData->index.index2Ptr();
|
||
i64CachedPosition = 0;
|
||
pCachedEntry = pIndex;
|
||
}
|
||
|
||
long 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) {
|
||
// if (!(psnData->cache = new AVIReadCache(psnData->hdr.fccType == streamtypeVIDEO ? 65536 : 16384, streamno, parent, psnData)))
|
||
// return AVIERR_MEMORY;
|
||
|
||
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;
|
||
}
|
||
|
||
long AVIReadStream::EndStreaming() {
|
||
if (!fStreamingEnabled)
|
||
return 0;
|
||
|
||
if (fRealTime)
|
||
parent->AdjustRealTime(false);
|
||
|
||
if (fStreamingActive)
|
||
parent->DisableStreaming(streamno);
|
||
|
||
fStreamingEnabled = false;
|
||
fStreamingActive = false;
|
||
|
||
if (!--psnData->streaming_count) {
|
||
delete psnData->cache;
|
||
psnData->cache = NULL;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
long AVIReadStream::Info(w32AVISTREAMINFO *pasi, long lSize) {
|
||
w32AVISTREAMINFO 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) {
|
||
if (sampsize)
|
||
return true;
|
||
else {
|
||
if (lFrame < 0 || lFrame >= length)
|
||
return false;
|
||
|
||
return !(pIndex[lFrame].size & 0x80000000);
|
||
}
|
||
}
|
||
|
||
long 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;
|
||
sint64 byte_off = (sint64)lStart * sampsize;
|
||
sint64 bytecnt;
|
||
sint64 actual_bytes=0;
|
||
sint64 block_pos;
|
||
|
||
// too small to hold a sample?
|
||
|
||
if (lpBuffer && cbBuffer < sampsize) {
|
||
if (plBytes) *plBytes = sampsize * lSamples;
|
||
if (plSamples) *plSamples = lSamples;
|
||
|
||
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) - (long)byte_off) / sampsize;
|
||
|
||
if (!lSamples && avie2+1 < avie2_limit)
|
||
lSamples = ((avie2[0].size & 0x7FFFFFFF) + (avie2[1].size & 0x7FFFFFFF) - (long)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;
|
||
|
||
// begin reading frames from this point on
|
||
|
||
if (lpBuffer) {
|
||
// detect streaming
|
||
|
||
if (fStreamingEnabled) {
|
||
|
||
// We consider the client to be streaming if we detect at least
|
||
// 3 consecutive accesses
|
||
|
||
if (lStart == lStreamTrackValue) {
|
||
++iStreamTrackCount;
|
||
|
||
if (iStreamTrackCount >= 15) {
|
||
|
||
sint64 streamptr = parent->getStreamPtr();
|
||
sint64 fptrdiff = streamptr - avie2->pos;
|
||
|
||
if (!parent->isStreaming() || streamptr<0 || (fptrdiff<4194304 && fptrdiff>-4194304)) {
|
||
if (!psnData->cache)
|
||
psnData->cache = new AVIReadCache(psnData->hdr.fccType == streamtypeVIDEO ? 131072 : 16384, streamno, parent, psnData);
|
||
else
|
||
psnData->cache->ResetStatistics();
|
||
|
||
if (!fStreamingActive) {
|
||
fStreamingActive = true;
|
||
parent->EnableStreaming(streamno);
|
||
}
|
||
|
||
#ifdef STREAMING_DEBUG
|
||
OutputDebugString("[a] streaming enabled\n");
|
||
#endif
|
||
}
|
||
} else {
|
||
#ifdef STREAMING_DEBUG
|
||
OutputDebugString("[a] streaming detected\n");
|
||
#endif
|
||
}
|
||
} else {
|
||
#ifdef STREAMING_DEBUG
|
||
OutputDebugString("[a] streaming disabled\n");
|
||
#endif
|
||
iStreamTrackCount = 0;
|
||
|
||
if (fStreamingActive) {
|
||
fStreamingActive = false;
|
||
parent->DisableStreaming(streamno);
|
||
}
|
||
}
|
||
}
|
||
|
||
while(bytecnt > 0) {
|
||
long tc;
|
||
|
||
tc = (avie2->size & 0x7FFFFFFF) - (long)byte_off;
|
||
if (tc > bytecnt)
|
||
tc = (long)bytecnt;
|
||
|
||
if (psnData->cache && fStreamingActive && tc < psnData->cache->getMaxRead()) {
|
||
//OutputDebugString("[a] attempting cached read\n");
|
||
lActual = psnData->cache->Read(lpBuffer, avie2->pos, avie2->pos + byte_off + 8, tc);
|
||
psnData->stream_bytes += lActual;
|
||
} else
|
||
lActual = parent->ReadData(streamno, lpBuffer, avie2->pos + byte_off + 8, tc);
|
||
|
||
if (lActual < 0)
|
||
break;
|
||
|
||
actual_bytes += lActual;
|
||
++avie2;
|
||
byte_off = 0;
|
||
|
||
if (lActual < tc)
|
||
break;
|
||
|
||
bytecnt -= tc;
|
||
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 = (long)actual_bytes;
|
||
if (plSamples) *plSamples = (long)actual_bytes / sampsize;
|
||
|
||
lStreamTrackValue = lStart + (long)actual_bytes / sampsize;
|
||
|
||
} else {
|
||
if (plBytes) *plBytes = (long)bytecnt;
|
||
if (plSamples) *plSamples = lSamples;
|
||
}
|
||
|
||
} else {
|
||
AVIIndexEntry2 *avie2 = &pIndex[lStart];
|
||
|
||
if (lpBuffer && (avie2->size & 0x7FFFFFFF) > cbBuffer) {
|
||
if (plBytes) *plBytes = avie2->size & 0x7FFFFFFF;
|
||
if (plSamples) *plSamples = 1;
|
||
|
||
return AVIERR_BUFFERTOOSMALL;
|
||
}
|
||
|
||
if (lpBuffer) {
|
||
|
||
// detect streaming
|
||
|
||
if (fStreamingEnabled && lStart != lStreamTrackValue) {
|
||
if (lStreamTrackValue>=0 && lStart-lStreamTrackValue == lStreamTrackInterval) {
|
||
if (++iStreamTrackCount >= 15) {
|
||
|
||
sint64 streamptr = parent->getStreamPtr();
|
||
sint64 fptrdiff = streamptr - avie2->pos;
|
||
|
||
if (!parent->isStreaming() || streamptr<0 || (fptrdiff<4194304 && fptrdiff>-4194304)) {
|
||
if (!psnData->cache)
|
||
psnData->cache = new AVIReadCache(psnData->hdr.fccType == streamtypeVIDEO ? 131072 : 16384, streamno, parent, psnData);
|
||
else
|
||
psnData->cache->ResetStatistics();
|
||
|
||
if (!fStreamingActive) {
|
||
fStreamingActive = true;
|
||
parent->EnableStreaming(streamno);
|
||
}
|
||
|
||
#ifdef STREAMING_DEBUG
|
||
OutputDebugString("[v] streaming activated\n");
|
||
#endif
|
||
}
|
||
} else {
|
||
#ifdef STREAMING_DEBUG
|
||
OutputDebugString("[v] streaming detected\n");
|
||
#endif
|
||
}
|
||
} else {
|
||
iStreamTrackCount = 0;
|
||
#ifdef STREAMING_DEBUG
|
||
OutputDebugString("[v] streaming disabled\n");
|
||
#endif
|
||
if (lStreamTrackValue>=0 && lStart > lStreamTrackValue) {
|
||
lStreamTrackInterval = lStart - lStreamTrackValue;
|
||
} else
|
||
lStreamTrackInterval = -1;
|
||
|
||
if (fStreamingActive) {
|
||
fStreamingActive = false;
|
||
parent->DisableStreaming(streamno);
|
||
}
|
||
}
|
||
|
||
lStreamTrackValue = lStart;
|
||
}
|
||
|
||
// read data
|
||
|
||
unsigned size = avie2->size & 0x7FFFFFFF;
|
||
|
||
if (psnData->cache && fStreamingActive && size < psnData->cache->getMaxRead()) {
|
||
//OutputDebugString("[v] attempting cached read\n");
|
||
lActual = psnData->cache->Read(lpBuffer, avie2->pos, avie2->pos + 8, size);
|
||
psnData->stream_bytes += lActual;
|
||
} else
|
||
lActual = parent->ReadData(streamno, lpBuffer, avie2->pos+8, size);
|
||
|
||
if (lActual != size) {
|
||
if (plBytes) *plBytes = 0;
|
||
if (plSamples) *plSamples = 0;
|
||
return AVIERR_FILEREAD;
|
||
}
|
||
}
|
||
|
||
if (plBytes) *plBytes = avie2->size & 0x7FFFFFFF;
|
||
if (plSamples) *plSamples = 1;
|
||
}
|
||
|
||
if (psnData->cache && fStreamingActive) {
|
||
|
||
// Are we experiencing a high rate of cache misses?
|
||
|
||
if (psnData->cache->cache_miss_bytes*2 > psnData->cache->cache_hit_bytes && psnData->cache->reads > 50) {
|
||
|
||
// sh*t, notify the parent that we have cache misses so it can check which stream is
|
||
// screwing up, and disable streaming on feeds that are too far off
|
||
|
||
parent->FixCacheProblems(this);
|
||
iStreamTrackCount = 0;
|
||
}
|
||
}/* else if (fStreamingEnabled) {
|
||
|
||
// hmm... our cache got killed!
|
||
|
||
iStreamTrackCount = 0;
|
||
|
||
}*/
|
||
|
||
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 ? lFrame+1 : -1;
|
||
|
||
if (lFrame < 0)
|
||
return 0;
|
||
|
||
if (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;
|
||
}
|
||
|
||
long AVIReadStream::FormatSize(long lFrame, long *plSize) {
|
||
*plSize = psnData->lFormatLen;
|
||
return 0;
|
||
}
|
||
|
||
long 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 psnData->cache && fStreamingActive && parent->isStreaming();
|
||
}
|
||
|
||
bool AVIReadStream::isKeyframeOnly() {
|
||
return psnData->keyframe_only;
|
||
}
|
||
|
||
bool AVIReadStream::getVBRInfo(double& bitrate_mean, double& bitrate_stddev, double& maxdev) {
|
||
if (psnData->was_VBR) {
|
||
bitrate_mean = psnData->bitrate_mean;
|
||
bitrate_stddev = psnData->bitrate_stddev;
|
||
maxdev = psnData->max_deviation;
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
///////////////////////////////////////////////////////////////////////////
|
||
|
||
AVIReadHandler::AVIReadHandler(mm_io_c *input)
|
||
:/* pAvisynthClipInfo(0)
|
||
, */
|
||
m_pInput(input),
|
||
mbFileIsDamaged(false)
|
||
{
|
||
//this->hFile = INVALID_HANDLE_VALUE;
|
||
//this->hFileUnbuffered = INVALID_HANDLE_VALUE;
|
||
//this->paf = NULL;
|
||
ref_count = 1;
|
||
streams=0;
|
||
fStreamsActive = 0;
|
||
fDisableFastIO = false;
|
||
streamBuffer = NULL;
|
||
nRealTime = 0;
|
||
nActiveStreamers = 0;
|
||
fFakeIndex = false;
|
||
nFiles = 1;
|
||
nCurrentFile = 0;
|
||
pSegmentHint = NULL;
|
||
|
||
_construct(/*s*/);
|
||
}
|
||
|
||
AVIReadHandler::~AVIReadHandler() {
|
||
_destruct();
|
||
}
|
||
|
||
void AVIReadHandler::_construct(/*const char *pszFile*/) {
|
||
|
||
try {
|
||
//AVIFileDesc *pDesc;
|
||
|
||
// open file
|
||
|
||
/*hFile = CreateFile(pszFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
|
||
|
||
if (INVALID_HANDLE_VALUE == hFile)
|
||
throw MyWin32Error("Couldn't open %s: %%s", GetLastError(), pszFile);
|
||
|
||
hFileUnbuffered = CreateFile(
|
||
pszFile,
|
||
GENERIC_READ,
|
||
FILE_SHARE_READ,
|
||
NULL,
|
||
OPEN_EXISTING,
|
||
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING,
|
||
NULL
|
||
);*/
|
||
|
||
//i64FilePosition = 0;
|
||
|
||
// m_pUnbufferedInput = m_pInput->Clone();
|
||
// if(m_pUnbufferedInput) {
|
||
// m_pUnbufferedInput->setAccess(false, true);
|
||
// if(!m_pUnbufferedInput->IsOpened()) {
|
||
// delete m_pUnbufferedInput;
|
||
// m_pUnbufferedInput = &m_pInput;
|
||
// }
|
||
// } else
|
||
m_pUnbufferedInput = m_pInput;
|
||
|
||
m_pInput->setFilePointer2(0, seek_beginning);
|
||
|
||
// recursively parse file
|
||
|
||
_parseFile(listStreams);
|
||
|
||
// Create first link
|
||
|
||
i64Size = m_pInput->get_size();
|
||
|
||
/*if (!(pDesc = new AVIFileDesc))
|
||
throw error_c("out of memory");
|
||
|
||
pDesc->hFile = hFile;
|
||
pDesc->hFileUnbuffered = hFileUnbuffered;
|
||
pDesc->i64Size = i64Size = _sizeFile();
|
||
|
||
listFiles.AddHead(pDesc);*/
|
||
|
||
} catch(...) {
|
||
_destruct();
|
||
throw;
|
||
}
|
||
}
|
||
|
||
#if 0
|
||
bool AVIReadHandler::AppendFile(const char *pszFile) {
|
||
List2<AVIStreamNode> 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);
|
||
|
||
if (INVALID_HANDLE_VALUE == hFile)
|
||
throw MyWin32Error("Couldn't open %s: %%s", GetLastError(), pszFile);
|
||
|
||
hFileUnbuffered = CreateFile(
|
||
pszFile,
|
||
GENERIC_READ,
|
||
FILE_SHARE_READ,
|
||
NULL,
|
||
OPEN_EXISTING,
|
||
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING,
|
||
NULL
|
||
);*/
|
||
|
||
try {
|
||
_parseFile(newstreams);
|
||
|
||
pasn_old = listStreams.AtHead();
|
||
pasn_new = newstreams.AtHead();
|
||
|
||
while(!!(pasn_old_next = pasn_old->NextFromHead()) & !!(pasn_new_next = pasn_new->NextFromHead())) {
|
||
const char *szPrefix;
|
||
|
||
switch(pasn_old->hdr.fccType) {
|
||
case streamtypeAUDIO: szPrefix = "Cannot append segment: The audio streams "; break;
|
||
case streamtypeVIDEO: szPrefix = "Cannot append segment: The video streams "; break;
|
||
case streamtypeINTERLEAVED: szPrefix = "Cannot append segment: The DV streams "; break;
|
||
default: szPrefix = ""; break;
|
||
}
|
||
|
||
// If it's not an audio or video stream, why do we care?
|
||
|
||
if (szPrefix) {
|
||
// allow ivas as a synonym for vids
|
||
|
||
uint32_le fccOld = pasn_old->hdr.fccType;
|
||
uint32_le fccNew = pasn_new->hdr.fccType;
|
||
|
||
if (fccOld != fccNew)
|
||
throw error_c("Cannot append segment: The stream types do not match.");
|
||
|
||
// if (pasn_old->hdr.fccHandler != pasn_new->hdr.fccHandler)
|
||
// throw error_c("%suse incompatible compression types.", szPrefix);
|
||
|
||
// A/B ?= C/D ==> AD ?= BC
|
||
|
||
if ((sint64)pasn_old->hdr.dwScale * pasn_new->hdr.dwRate != (sint64)pasn_new->hdr.dwScale * pasn_old->hdr.dwRate)
|
||
throw error_c("%shave different sampling rates (%.5f vs. %.5f)"
|
||
,szPrefix
|
||
,(double)pasn_old->hdr.dwRate / pasn_old->hdr.dwScale
|
||
,(double)pasn_new->hdr.dwRate / pasn_new->hdr.dwScale
|
||
);
|
||
|
||
if (pasn_old->hdr.dwSampleSize != pasn_new->hdr.dwSampleSize)
|
||
throw error_c("%shave different block sizes (%d vs %d).", szPrefix, pasn_old->hdr.dwSampleSize, pasn_new->hdr.dwSampleSize);
|
||
|
||
// I hate PCMWAVEFORMAT.
|
||
|
||
bool fOk = pasn_old->lFormatLen == pasn_new->lFormatLen && !memcmp(pasn_old->pFormat, pasn_new->pFormat, pasn_old->lFormatLen);
|
||
|
||
if (pasn_old->hdr.fccType == streamtypeAUDIO)
|
||
if (((w32WAVEFORMATEX *)pasn_old->pFormat)->wFormatTag == WAVE_FORMAT_PCM
|
||
&& ((w32WAVEFORMATEX *)pasn_new->pFormat)->wFormatTag == WAVE_FORMAT_PCM)
|
||
fOk = !memcmp(pasn_old->pFormat, pasn_new->pFormat, sizeof(w32PCMWAVEFORMAT));
|
||
|
||
if (!fOk)
|
||
throw error_c("%shave different data formats.", szPrefix);
|
||
}
|
||
|
||
pasn_old = pasn_old_next;
|
||
pasn_new = pasn_new_next;
|
||
}
|
||
|
||
if (pasn_old_next || pasn_new_next)
|
||
throw error_c("Cannot append segment: The segment has a different number of streams.");
|
||
|
||
/*if (!(pDesc = new AVIFileDesc))
|
||
throw error_c("out of memory");
|
||
|
||
pDesc->hFile = hFile;
|
||
pDesc->hFileUnbuffered = hFileUnbuffered;
|
||
pDesc->i64Size = _sizeFile();*/
|
||
} catch(const error_c&) {
|
||
while(pasn_new = newstreams.RemoveHead())
|
||
delete pasn_new;
|
||
|
||
/*CloseHandle(hFile);
|
||
if (hFileUnbuffered != INVALID_HANDLE_VALUE)
|
||
CloseHandle(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; i<oldlen; i++) {
|
||
idx_old[i].size ^= 0x80000000;
|
||
pasn_old->index.add(&idx_old[i]);
|
||
}
|
||
|
||
delete[] idx_old;
|
||
|
||
for(i=pasn_new->index.indexLen(); i; i--) {
|
||
idx_new->size ^= 0x80000000;
|
||
idx_new->pos += (sint64)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;
|
||
}
|
||
#endif
|
||
|
||
void AVIReadHandler::_parseFile(List2<AVIStreamNode>& streamlist) {
|
||
uint32_le fccType;
|
||
uint32 dwLength;
|
||
bool index_found = false;
|
||
bool fAcceptIndexOnly = true;
|
||
bool hyperindexed = false;
|
||
bool bScanRequired = false;
|
||
AVIStreamNode *pasn, *pasn_next;
|
||
w32MainAVIHeader avihdr;
|
||
bool bMainAVIHeaderFound = false;
|
||
|
||
sint64 i64ChunkMoviPos = 0;
|
||
uint32 dwChunkMoviLength = 0;
|
||
|
||
if (!_readChunkHeader(fccType, dwLength))
|
||
throw error_c("Invalid AVI file: File is less than 8 bytes");
|
||
|
||
if (fccType != FOURCC_RIFF)
|
||
throw error_c("Invalid AVI file: Not a RIFF file");
|
||
|
||
// If the RIFF header is <4 bytes, assume it was an improperly closed
|
||
// file.
|
||
|
||
if(m_pInput->read(&fccType, 4) != 4)
|
||
return;
|
||
|
||
if (fccType != formtypeAVI)
|
||
throw error_c("Invalid AVI file: RIFF type is not 'AVI'");
|
||
|
||
// Aggressive mode recovery does extensive validation and searching to attempt to
|
||
// recover an AVI. It is significantly slower, however. We place the flag here
|
||
// so we can set it if something dreadfully wrong is with the AVI.
|
||
|
||
bool bAggressive = false;
|
||
|
||
// begin parsing chunks
|
||
|
||
while(_readChunkHeader(fccType, dwLength)) {
|
||
|
||
// _RPT4(0,"%08I64x %08I64x Chunk '%-4s', length %08lx\n", _posFile()+dwLengthLeft, _posFile(), &fccType, dwLength);
|
||
|
||
// Invalid FCCs are bad. If we find one, set the aggressive flag so if a scan
|
||
// starts, we aggressively search for and validate data.
|
||
|
||
if (!isValidFOURCC(fccType)) {
|
||
bAggressive = true;
|
||
break;
|
||
}
|
||
|
||
// bool bInvalidLength = false;
|
||
|
||
switch(fccType) {
|
||
case FOURCC_LIST:
|
||
if(m_pInput->read(&fccType, 4) != 4)
|
||
return;
|
||
|
||
// If we find a LIST/movi chunk with zero size, jump straight to reindexing
|
||
// (unclosed AVIFile output).
|
||
|
||
if (!dwLength && fccType == listtypeAVIMOVIE) {
|
||
i64ChunkMoviPos = m_pInput->getFilePointer();
|
||
dwChunkMoviLength = 0xFFFFFFF0;
|
||
goto terminate_scan;
|
||
}
|
||
|
||
// Some idiot Premiere plugin is writing AVI files with an invalid
|
||
// size field in the LIST/hdrl chunk.
|
||
|
||
if (dwLength<4 && fccType != listtypeAVIHEADER)
|
||
throw error_c("Invalid AVI file: LIST chunk <4 bytes");
|
||
|
||
if (dwLength < 4)
|
||
dwLength = 0;
|
||
else
|
||
dwLength -= 4;
|
||
|
||
// _RPT1(0,"\tList type '%-4s'\n", &fccType);
|
||
|
||
switch(fccType) {
|
||
case listtypeAVIMOVIE:
|
||
|
||
if (dwLength < 8) {
|
||
i64ChunkMoviPos = m_pInput->getFilePointer();
|
||
dwChunkMoviLength = 0xFFFFFFF0;
|
||
dwLength = 0;
|
||
} else {
|
||
i64ChunkMoviPos = m_pInput->getFilePointer();
|
||
dwChunkMoviLength = dwLength;
|
||
}
|
||
|
||
if (fAcceptIndexOnly)
|
||
goto terminate_scan;
|
||
|
||
break;
|
||
case listtypeAVIRECORD: // silently enter grouping blocks
|
||
case listtypeAVIHEADER: // silently enter header blocks
|
||
dwLength = 0;
|
||
break;
|
||
case listtypeSTREAMHEADER:
|
||
if (!_parseStreamHeader(streamlist, dwLength, bScanRequired))
|
||
fAcceptIndexOnly = false;
|
||
else {
|
||
mxverb(3, "AVI: OpenDML hierarchical index detected on stream %d.\n", streams);
|
||
hyperindexed = true;
|
||
}
|
||
|
||
++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 mmioFOURCC('s','e','g','m'): // VirtualDub segment hint block
|
||
delete pSegmentHint;
|
||
if (!(pSegmentHint = new char[dwLength]))
|
||
throw error_c("out of memory");
|
||
|
||
if(m_pInput->read(pSegmentHint, dwLength) != dwLength)
|
||
return;
|
||
|
||
if (dwLength&1)
|
||
m_pInput->setFilePointer2(1, seek_current);
|
||
|
||
dwLength = 0;
|
||
break;
|
||
|
||
case listtypeAVIHEADER:
|
||
if (!bMainAVIHeaderFound) {
|
||
uint32 tc = std::min<uint32>(dwLength, sizeof avihdr);
|
||
memset(&avihdr, 0, sizeof avihdr);
|
||
if(m_pInput->read(&avihdr, tc) != tc)
|
||
return;
|
||
dwLength -= tc;
|
||
bMainAVIHeaderFound = true;
|
||
}
|
||
break;
|
||
}
|
||
|
||
if (dwLength) {
|
||
if (!m_pInput->setFilePointer2(dwLength + (dwLength&1), seek_current))
|
||
break;
|
||
}
|
||
|
||
// Quit as soon as we see the index block.
|
||
|
||
if (fccType == ckidAVINEWINDEX)
|
||
break;
|
||
}
|
||
|
||
if (i64ChunkMoviPos == 0)
|
||
throw error_c("This AVI file doesn't have a movie data block (movi)!");
|
||
|
||
terminate_scan:
|
||
|
||
if (!hyperindexed && !index_found)
|
||
bScanRequired = true;
|
||
|
||
if (bScanRequired) {
|
||
mxverb(3, "AVI: Index not found or damaged -- reconstructing via file scan.\n");
|
||
|
||
// It's possible that we were in the middle of reading an index when an error
|
||
// occurred, so we need to clear all of the indices for all streams.
|
||
|
||
pasn = streamlist.AtHead();
|
||
|
||
while((pasn_next = pasn->NextFromHead()) != 0) {
|
||
pasn->index.clear();
|
||
pasn = pasn_next;
|
||
}
|
||
|
||
// obtain length of file and limit scanning if so
|
||
|
||
sint64 i64FileSize = m_pInput->get_size();
|
||
|
||
uint32 dwLengthLeft = dwChunkMoviLength;
|
||
|
||
// long short_length = (long)((dwChunkMoviLength + _LL(1023)) >> 10);
|
||
// long long_length = (long)((i64FileSize - i64ChunkMoviPos + _LL(1023)) >> 10);
|
||
|
||
// sint64 length = (hyperindexed || bAggressive) ? long_length : short_length;
|
||
// ProgressDialog pd("AVI Import Filter", bAggressive ? "Reconstructing missing index block (aggressive mode)" : "Reconstructing missing index block", length, true);
|
||
|
||
// pd.setValueFormat("%ldK of %ldK");
|
||
|
||
fFakeIndex = true;
|
||
|
||
m_pInput->setFilePointer2(i64ChunkMoviPos, seek_beginning);
|
||
|
||
// For standard AVI files, stop as soon as the movi chunk is exhausted or
|
||
// the end of the AVI file is hit. For OpenDML files, continue as long as
|
||
// valid chunks are found.
|
||
|
||
bool bStopWhenLengthExhausted = !hyperindexed && !bAggressive;
|
||
|
||
for(;;) {
|
||
// pd.advance((long)((m_pInput->getFilePointer() - i64ChunkMoviPos)/1024));
|
||
// pd.check();
|
||
|
||
// Exit if we are out of movi chunk -- except for OpenDML files.
|
||
|
||
if (!bStopWhenLengthExhausted && dwLengthLeft < 8)
|
||
break;
|
||
|
||
// Validate the FOURCC itself but avoid validating the size, since it
|
||
// may be too large for the last LIST/movi.
|
||
|
||
if (!_readChunkHeader(fccType, dwLength))
|
||
break;
|
||
|
||
bool bValid = isValidFOURCC(fccType) && ((m_pInput->getFilePointer() + dwLength) <= i64FileSize);
|
||
|
||
// In aggressive mode, verify that a valid FOURCC follows this chunk.
|
||
|
||
if (bAggressive) {
|
||
sint64 current_pos = m_pInput->getFilePointer();
|
||
sint64 rounded_length = (dwLength+_LL(1)) & ~_LL(1);
|
||
|
||
if (current_pos + dwLength > i64FileSize)
|
||
bValid = false;
|
||
else if (current_pos + rounded_length <= i64FileSize-8) {
|
||
uint32_le fccNext;
|
||
uint32 dwLengthNext;
|
||
|
||
m_pInput->setFilePointer2(current_pos + rounded_length, seek_beginning);
|
||
if (!_readChunkHeader(fccNext, dwLengthNext))
|
||
break;
|
||
|
||
bValid &= isValidFOURCC(fccNext) && ((m_pInput->getFilePointer() + dwLengthNext) <= i64FileSize);
|
||
|
||
m_pInput->setFilePointer2(current_pos, seek_beginning);
|
||
}
|
||
}
|
||
|
||
if (!bValid) {
|
||
// Notify the user that recovering this file requires stronger measures.
|
||
|
||
if (!bAggressive) {
|
||
sint64 bad_pos = m_pInput->getFilePointer();
|
||
mxverb(3, "AVI: Invalid chunk detected at %lld. Enabling aggressive recovery mode.\n", bad_pos);
|
||
|
||
bAggressive = true;
|
||
bStopWhenLengthExhausted = false;
|
||
// pd.setCaption("Reconstructing missing index block (aggressive mode)");
|
||
// pd.setLimit(long_length);
|
||
}
|
||
|
||
// Backup by seven bytes and continue.
|
||
|
||
m_pInput->setFilePointer2(m_pInput->getFilePointer()-7, seek_beginning);
|
||
continue;
|
||
}
|
||
|
||
int stream;
|
||
|
||
// _RPT2(0,"(stream header) Chunk '%-4s', length %08lx\n", &fccType, dwLength);
|
||
|
||
dwLengthLeft -= 8+(dwLength + (dwLength&1));
|
||
|
||
// Skip over the chunk. Don't skip over RIFF chunks of type AVIX, or
|
||
// LIST chunks of type 'movi'.
|
||
|
||
if (dwLength) {
|
||
if (fccType == FOURCC_RIFF || fccType == FOURCC_LIST) {
|
||
uint32_le fccType2;
|
||
|
||
if (m_pInput->read(&fccType2, 4) != 4)
|
||
break;
|
||
|
||
if (fccType2 != formtypeAVIEXTENDED && fccType2 != listtypeAVIMOVIE) {
|
||
if (!m_pInput->setFilePointer2(dwLength + (dwLength&1) - 4, seek_current))
|
||
break;
|
||
}
|
||
} else {
|
||
if (!m_pInput->setFilePointer2(dwLength + (dwLength&1), seek_current))
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (m_pInput->getFilePointer() > i64FileSize)
|
||
break;
|
||
|
||
if (isxdigit(fccType&0xff) && isxdigit((fccType>>8)&0xff)) {
|
||
stream = StreamFromFOURCC(fccType);
|
||
|
||
if (stream >=0 && stream < streams) {
|
||
|
||
pasn = streamlist.AtHead();
|
||
|
||
while((pasn_next = pasn->NextFromHead()) && stream--)
|
||
pasn = pasn_next;
|
||
|
||
if (pasn && pasn_next) {
|
||
|
||
// Set the keyframe flag for the first sample in the stream, or
|
||
// if this is known to be a keyframe-only stream. Do not set the
|
||
// keyframe flag if the frame has zero bytes (drop frame).
|
||
|
||
pasn->index.add(fccType, m_pInput->getFilePointer()-(dwLength + (dwLength&1))-8, dwLength, (!pasn->bytes || pasn->keyframe_only) && dwLength>0);
|
||
pasn->bytes += dwLength;
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
mbFileIsDamaged |= bAggressive;
|
||
|
||
// glue together indices
|
||
|
||
pasn = streamlist.AtHead();
|
||
|
||
int nStream = 0;
|
||
while((pasn_next = pasn->NextFromHead()) != 0) {
|
||
if (!pasn->index.makeIndex2())
|
||
throw error_c("out of memory");
|
||
|
||
pasn->frames = pasn->index.indexLen();
|
||
|
||
// Clear sample size for video streams!
|
||
|
||
if (pasn->hdr.fccType == streamtypeVIDEO)
|
||
pasn->hdr.dwSampleSize=0;
|
||
|
||
// Attempt to fix invalid dwRate/dwScale fractions (can result from unclosed
|
||
// AVI files being written by DirectShow).
|
||
|
||
if (pasn->hdr.dwRate==0 || pasn->hdr.dwScale == 0) {
|
||
// If we're dealing with a video stream, try the frame rate in the AVI header.
|
||
// If we're dealing with an audio stream, try the frame rate in the audio
|
||
// format.
|
||
// Otherwise, just use... uh, 15.
|
||
|
||
if (pasn->hdr.fccType == streamtypeVIDEO) {
|
||
if (bMainAVIHeaderFound) {
|
||
pasn->hdr.dwRate = avihdr.dwMicroSecPerFrame; // This can be zero, in which case the default '15' will kick in below.
|
||
pasn->hdr.dwScale = 1000000;
|
||
}
|
||
} else if (pasn->hdr.fccType == streamtypeAUDIO) {
|
||
const w32WAVEFORMATEX *pwfex = (const w32WAVEFORMATEX *)pasn->pFormat;
|
||
|
||
pasn->hdr.dwRate = pwfex->nAvgBytesPerSec;
|
||
pasn->hdr.dwScale = pwfex->nBlockAlign;
|
||
}
|
||
|
||
if (pasn->hdr.dwRate==0 || pasn->hdr.dwScale == 0) {
|
||
pasn->hdr.dwRate = 15;
|
||
pasn->hdr.dwScale = 1;
|
||
}
|
||
|
||
const int badstream = nStream;
|
||
const double newrate = pasn->hdr.dwRate / (double)pasn->hdr.dwScale;
|
||
mxverb(3, "AVI: Stream %d has an invalid sample rate. Substituting %lu samples/sec as placeholder.\n", badstream, newrate);
|
||
}
|
||
|
||
// Verify sample size == nBlockAlign for audio. If we find runt samples,
|
||
// assume someone did a VBR hack.
|
||
|
||
if (pasn->hdr.fccType == streamtypeAUDIO) {
|
||
const AVIIndexEntry2 *pIdx = pasn->index.index2Ptr();
|
||
long nBlockAlign = ((w32WAVEFORMATEX *)pasn->pFormat)->nBlockAlign;
|
||
|
||
pasn->hdr.dwSampleSize = nBlockAlign;
|
||
|
||
for(int i=0; i<pasn->frames-1; ++i) {
|
||
long s = pIdx[i].size & 0x7FFFFFFF;
|
||
|
||
if (s && s < nBlockAlign) {
|
||
pasn->FixVBRAudio();
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (pasn->hdr.dwSampleSize)
|
||
pasn->length = (long)pasn->bytes / pasn->hdr.dwSampleSize;
|
||
else
|
||
pasn->length = pasn->frames;
|
||
|
||
pasn = pasn_next;
|
||
++nStream;
|
||
}
|
||
|
||
// throw error_c("Parse complete. Aborting.");
|
||
}
|
||
|
||
bool AVIReadHandler::_parseStreamHeader(List2<AVIStreamNode>& streamlist, uint32 dwLengthLeft, bool& bIndexDamaged) {
|
||
AVIStreamNode *pasn;
|
||
uint32_le fccType;
|
||
uint32 dwLength;
|
||
bool hyperindexed = false;
|
||
|
||
if (!(pasn = new AVIStreamNode()))
|
||
throw error_c("out of memory");
|
||
|
||
try {
|
||
while(dwLengthLeft >= 8 && _readChunkHeader(fccType, dwLength)) {
|
||
|
||
// _RPT2(0,"(stream header) Chunk '%-4s', length %08lx\n", &fccType, dwLength);
|
||
|
||
dwLengthLeft -= 8;
|
||
|
||
if (dwLength > dwLengthLeft)
|
||
throw error_c("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) {
|
||
if(m_pInput->read(&pasn->hdr, dwLength) != dwLength)
|
||
throw error_c();
|
||
if (dwLength & 1)
|
||
m_pInput->setFilePointer2(1, seek_current);
|
||
} else {
|
||
if(m_pInput->read(&pasn->hdr, sizeof pasn->hdr) != sizeof pasn->hdr)
|
||
throw error_c();
|
||
m_pInput->setFilePointer2(dwLength+(dwLength&1) - sizeof pasn->hdr, seek_current);
|
||
}
|
||
dwLength = 0;
|
||
|
||
pasn->keyframe_only = false;
|
||
|
||
break;
|
||
|
||
case ckidSTREAMFORMAT:
|
||
if (!(pasn->pFormat = new char[pasn->lFormatLen = dwLength]))
|
||
throw error_c("out of memory");
|
||
|
||
if(m_pInput->read(pasn->pFormat, dwLength) != dwLength)
|
||
throw error_c();
|
||
|
||
if (pasn->hdr.fccType == streamtypeVIDEO) {
|
||
switch(((w32BITMAPINFOHEADER *)pasn->pFormat)->biCompression) {
|
||
case NULL:
|
||
case bitmapFOURCC('R', 'A', 'W', ' '):
|
||
case bitmapFOURCC('D', 'I', 'B', ' '):
|
||
case bitmapFOURCC('d', 'm', 'b', '1'):
|
||
case bitmapFOURCC('m', 'j', 'p', 'g'):
|
||
case bitmapFOURCC('M', 'J', 'P', 'G'):
|
||
case bitmapFOURCC('V', 'Y', 'U', 'Y'):
|
||
case bitmapFOURCC('Y', 'U', 'Y', '2'):
|
||
case bitmapFOURCC('U', 'Y', 'V', 'Y'):
|
||
case bitmapFOURCC('Y', 'V', 'Y', 'U'):
|
||
case bitmapFOURCC('Y', 'V', '1', '2'):
|
||
case bitmapFOURCC('I', '4', '2', '0'):
|
||
case bitmapFOURCC('Y', '4', '1', 'P'):
|
||
case bitmapFOURCC('c', 'y', 'u', 'v'):
|
||
case bitmapFOURCC('H', 'F', 'Y', 'U'):
|
||
case bitmapFOURCC('b', 't', '2', '0'):
|
||
pasn->keyframe_only = true;
|
||
}
|
||
}
|
||
|
||
if (dwLength & 1)
|
||
m_pInput->setFilePointer2(1, seek_current);
|
||
dwLength = 0;
|
||
break;
|
||
|
||
case ckidOPENDMLINDEX: // OpenDML extended index
|
||
{
|
||
sint64 posFileSave = m_pInput->getFilePointer();
|
||
|
||
try {
|
||
_parseExtendedIndexBlock(streamlist, pasn, -1, dwLength);
|
||
} catch(const error_c&) {
|
||
bIndexDamaged = true;
|
||
}
|
||
|
||
m_pInput->setFilePointer2(posFileSave, seek_beginning);
|
||
}
|
||
hyperindexed = true;
|
||
break;
|
||
|
||
case ckidAVIPADDING: // JUNK
|
||
break;
|
||
}
|
||
|
||
if (dwLength) {
|
||
if (!m_pInput->setFilePointer2(dwLength + (dwLength&1), seek_current))
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (dwLengthLeft)
|
||
m_pInput->setFilePointer2(dwLengthLeft, seek_current);
|
||
} 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<AVIStreamNode>& streamlist, int count, sint64 movi_offset) {
|
||
w32AVIINDEXENTRY 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(w32AVIINDEXENTRY) != m_pInput->read(avie, tc*sizeof(w32AVIINDEXENTRY))) {
|
||
pasn = streamlist.AtHead();
|
||
|
||
while((pasn_next = pasn->NextFromHead()) != 0) {
|
||
pasn->index.clear();
|
||
pasn->bytes = 0;
|
||
|
||
pasn = pasn_next;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
for(i=0; i<tc; i++) {
|
||
int stream = StreamFromFOURCC(avie[i].ckid);
|
||
|
||
if (absolute_addr && avie[i].dwChunkOffset<movi_offset)
|
||
absolute_addr = false;
|
||
|
||
pasn = streamlist.AtHead();
|
||
|
||
while((pasn_next = (AVIStreamNode *)pasn->NextFromHead()) && 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) + (sint64)avie[i].dwChunkOffset, avie[i].dwChunkLength, !!(avie[i].dwFlags & AVIIF_KEYFRAME));
|
||
|
||
pasn->bytes += avie[i].dwChunkLength;
|
||
}
|
||
}
|
||
}
|
||
|
||
return true;
|
||
|
||
}
|
||
|
||
void AVIReadHandler::_parseExtendedIndexBlock(List2<AVIStreamNode>& streamlist, AVIStreamNode *pasn, sint64 fpos, uint32 dwLength) {
|
||
union {
|
||
AVISUPERINDEX idxsuper;
|
||
AVISTDINDEX idxstd;
|
||
};
|
||
|
||
union {
|
||
struct _avisuperindex_entry superent[64];
|
||
uint32 dwHeap[256];
|
||
};
|
||
|
||
int entries, tp;
|
||
int i;
|
||
sint64 i64FPSave = m_pInput->getFilePointer();
|
||
|
||
if (fpos>=0)
|
||
m_pInput->setFilePointer2(fpos, seek_beginning);
|
||
if(m_pInput->read((char *)&idxsuper + 8, sizeof(AVISUPERINDEX) - 8) != sizeof(AVISUPERINDEX) - 8)
|
||
throw error_c();
|
||
|
||
switch(idxsuper.bIndexType) {
|
||
case AVI_INDEX_OF_INDEXES:
|
||
// sanity check
|
||
|
||
if (idxsuper.wLongsPerEntry != 4)
|
||
throw error_c("Invalid superindex block in stream");
|
||
|
||
// if (idxsuper.bIndexSubType != 0)
|
||
// throw error_c("Field indexes not supported");
|
||
|
||
entries = idxsuper.nEntriesInUse;
|
||
|
||
while(entries > 0) {
|
||
tp = sizeof superent / sizeof superent[0];
|
||
if (tp>entries) tp=entries;
|
||
|
||
if(m_pInput->read(superent, tp*sizeof superent[0]) != tp*sizeof superent[0])
|
||
throw error_c();
|
||
|
||
for(i=0; i<tp; i++)
|
||
_parseExtendedIndexBlock(streamlist, pasn, superent[i].qwOffset+8, superent[i].dwSize-8);
|
||
|
||
entries -= tp;
|
||
}
|
||
|
||
break;
|
||
|
||
case AVI_INDEX_OF_CHUNKS:
|
||
|
||
// if (idxstd.bIndexSubType != 0)
|
||
// throw error_c("Frame indexes not supported");
|
||
|
||
entries = idxstd.nEntriesInUse;
|
||
|
||
// In theory, if bIndexSubType==AVI_INDEX_2FIELD it's supposed to have
|
||
// wLongsPerEntry=3, and bIndexSubType==0 gives wLongsPerEntry=2.
|
||
// Matrox's MPEG-2 stuff generates bIndexSubType=16 and wLongsPerEntry=6.
|
||
// *sigh*
|
||
//
|
||
// For wLongsPerEntry==2 and ==3, dwOffset is at 0 and dwLength at 1;
|
||
// for wLongsPerEntry==6, dwOffset is at 2 and all are keyframes.
|
||
|
||
{
|
||
if (idxstd.wLongsPerEntry!=2 && idxstd.wLongsPerEntry!=3 && idxstd.wLongsPerEntry!=6)
|
||
throw error_c(string("Invalid OpenDML index block in stream, wLongsPerEntry=") + to_string(idxstd.wLongsPerEntry));
|
||
|
||
while(entries > 0) {
|
||
tp = (sizeof dwHeap / sizeof dwHeap[0]) / idxstd.wLongsPerEntry;
|
||
if (tp>entries) tp=entries;
|
||
|
||
if(m_pInput->read(dwHeap, tp*idxstd.wLongsPerEntry*sizeof(uint32)) != tp*idxstd.wLongsPerEntry*sizeof(uint32))
|
||
throw error_c();
|
||
|
||
if (idxstd.wLongsPerEntry == 6)
|
||
for(i=0; i<tp; i++) {
|
||
uint32 dwOffset = dwHeap[i*idxstd.wLongsPerEntry + 0];
|
||
uint32 dwSize = dwHeap[i*idxstd.wLongsPerEntry + 2];
|
||
|
||
pasn->index.add(idxstd.dwChunkId, (idxstd.qwBaseOffset+dwOffset)-8, dwSize, true);
|
||
|
||
pasn->bytes += dwSize;
|
||
}
|
||
else
|
||
for(i=0; i<tp; i++) {
|
||
uint32 dwOffset = dwHeap[i*idxstd.wLongsPerEntry + 0];
|
||
uint32 dwSize = dwHeap[i*idxstd.wLongsPerEntry + 1];
|
||
|
||
pasn->index.add(idxstd.dwChunkId, (idxstd.qwBaseOffset+dwOffset)-8, dwSize&0x7FFFFFFF, !(dwSize&0x80000000));
|
||
|
||
pasn->bytes += dwSize & 0x7FFFFFFF;
|
||
}
|
||
|
||
entries -= tp;
|
||
}
|
||
}
|
||
|
||
break;
|
||
|
||
default:
|
||
throw error_c("Unknown hyperindex type");
|
||
}
|
||
|
||
m_pInput->setFilePointer2(i64FPSave, seek_beginning);
|
||
}
|
||
|
||
void AVIReadHandler::_destruct() {
|
||
AVIStreamNode *pasn;
|
||
//AVIFileDesc *pDesc;
|
||
|
||
/*if (pAvisynthClipInfo)
|
||
pAvisynthClipInfo->Release();
|
||
|
||
if (paf)
|
||
AVIFileRelease(paf);*/
|
||
|
||
while((pasn = listStreams.RemoveTail()) != 0)
|
||
delete pasn;
|
||
|
||
delete streamBuffer;
|
||
|
||
if(m_pUnbufferedInput && (m_pUnbufferedInput!=m_pInput))
|
||
delete m_pUnbufferedInput;
|
||
|
||
/*if (listFiles.IsEmpty()) {
|
||
if (hFile != INVALID_HANDLE_VALUE)
|
||
CloseHandle(hFile);
|
||
if (hFileUnbuffered != INVALID_HANDLE_VALUE)
|
||
CloseHandle(hFileUnbuffered);
|
||
} else
|
||
while(pDesc = listFiles.RemoveTail()) {
|
||
CloseHandle(pDesc->hFile);
|
||
CloseHandle(pDesc->hFileUnbuffered);
|
||
delete pDesc;
|
||
}*/
|
||
|
||
delete pSegmentHint;
|
||
}
|
||
|
||
///////////////////////////////////////////////////////////////////////////
|
||
|
||
void AVIReadHandler::Release() {
|
||
if (!--ref_count)
|
||
delete this;
|
||
}
|
||
|
||
void AVIReadHandler::AddRef() {
|
||
++ref_count;
|
||
}
|
||
|
||
IAVIReadStream *AVIReadHandler::GetStream(uint32 fccType, sint32 lParam) {
|
||
{
|
||
AVIStreamNode *pasn, *pasn_next;
|
||
int streamno = 0;
|
||
|
||
pasn = listStreams.AtHead();
|
||
|
||
while((pasn_next = pasn->NextFromHead()) != NULL) {
|
||
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 && !mbFileIsDamaged;
|
||
}
|
||
|
||
bool AVIReadHandler::isIndexFabricated() {
|
||
return fFakeIndex;
|
||
}
|
||
|
||
#if 0
|
||
bool AVIReadHandler::getSegmentHint(const char **ppszPath) {
|
||
if (!pSegmentHint) {
|
||
if (ppszPath)
|
||
*ppszPath = NULL;
|
||
|
||
return false;
|
||
}
|
||
|
||
if (ppszPath)
|
||
*ppszPath = pSegmentHint+1;
|
||
|
||
return !!pSegmentHint[0];
|
||
}
|
||
#endif
|
||
|
||
///////////////////////////////////////////////////////////////////////////
|
||
|
||
void AVIReadHandler::EnableStreaming(int stream) {
|
||
if (!fStreamsActive) {
|
||
if (!(streamBuffer = new char[STREAM_SIZE]))
|
||
throw error_c("out of memory");
|
||
|
||
i64StreamPosition = -1;
|
||
sbPosition = sbSize = 0;
|
||
}
|
||
|
||
fStreamsActive |= (1<<stream);
|
||
++nActiveStreamers;
|
||
}
|
||
|
||
void AVIReadHandler::DisableStreaming(int stream) {
|
||
fStreamsActive &= ~(1<<stream);
|
||
|
||
if (!fStreamsActive) {
|
||
delete streamBuffer;
|
||
streamBuffer = NULL;
|
||
}
|
||
--nActiveStreamers;
|
||
}
|
||
|
||
void AVIReadHandler::AdjustRealTime(bool fInc) {
|
||
if (fInc)
|
||
++nRealTime;
|
||
else
|
||
--nRealTime;
|
||
}
|
||
|
||
char *AVIReadHandler::_StreamRead(long& bytes) {
|
||
if (nCurrentFile<0 || nCurrentFile != (int)(i64StreamPosition>>48))
|
||
_SelectFile((int)(i64StreamPosition>>48));
|
||
|
||
if (sbPosition >= sbSize) {
|
||
if (nRealTime || (((i64StreamPosition&_LL(0x0000FFFFFFFFFFFF))+sbSize) & -STREAM_BLOCK_SIZE)+STREAM_SIZE > i64Size) {
|
||
i64StreamPosition += sbSize;
|
||
sbPosition = 0;
|
||
m_pInput->setFilePointer2(i64StreamPosition & _LL(0x0000FFFFFFFFFFFF), seek_beginning);
|
||
|
||
sbSize = m_pInput->read(streamBuffer, STREAM_RT_SIZE);
|
||
|
||
if (sbSize < 0) {
|
||
sbSize = 0;
|
||
// #pragma message(__TODO__ "Throw ?")
|
||
//throw MyWin32Error("Failure streaming AVI file: %%s.",GetLastError());
|
||
}
|
||
} else {
|
||
i64StreamPosition += sbSize;
|
||
sbPosition = i64StreamPosition & (STREAM_BLOCK_SIZE-1);
|
||
i64StreamPosition &= -STREAM_BLOCK_SIZE;
|
||
// #pragma message(__TODO__ "What to do with the unbuffered part ?")
|
||
m_pUnbufferedInput->setFilePointer(i64StreamPosition & _LL(0x0000FFFFFFFFFFFF), seek_beginning);
|
||
|
||
sbSize = m_pUnbufferedInput->read(streamBuffer, STREAM_SIZE);
|
||
|
||
if (sbSize < 0) {
|
||
sbSize = 0;
|
||
// #pragma message(__TODO__ "Throw ?")
|
||
//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;
|
||
}
|
||
|
||
bool AVIReadHandler::Stream(AVIStreamNode *pusher, sint64 pos) {
|
||
|
||
// Do not stream aggressively recovered files.
|
||
|
||
if (mbFileIsDamaged)
|
||
return false;
|
||
|
||
bool read_something = false;
|
||
|
||
if (!streamBuffer)
|
||
return false;
|
||
|
||
if (i64StreamPosition == -1) {
|
||
i64StreamPosition = pos;
|
||
sbPosition = 0;
|
||
}
|
||
|
||
if (pos < i64StreamPosition+sbPosition)
|
||
return false;
|
||
|
||
// >4Mb past current position!?
|
||
|
||
if (pos > i64StreamPosition+sbPosition+4194304) {
|
||
// OutputDebugString("Resetting streaming position!\n");
|
||
i64StreamPosition = pos;
|
||
sbSize = sbPosition = 0;
|
||
}
|
||
|
||
/* if (pusher->hdr.fccType == streamtypeVIDEO)
|
||
OutputDebugString("pushed by video\n");
|
||
else
|
||
OutputDebugString("pushed by audio\n");*/
|
||
|
||
++pusher->stream_pushes;
|
||
pusher->stream_push_pos = pos;
|
||
|
||
while(pos >= i64StreamPosition+sbPosition) {
|
||
long actual, left;
|
||
char *src;
|
||
uint32_le hdr[2];
|
||
int stream;
|
||
|
||
// read next header
|
||
|
||
left = 8;
|
||
while(left > 0) {
|
||
actual = left;
|
||
src = _StreamRead(actual);
|
||
|
||
if (!src)
|
||
return read_something;
|
||
|
||
memcpy((char *)hdr + (8-left), src, actual);
|
||
left -= actual;
|
||
}
|
||
|
||
stream = StreamFromFOURCC(hdr[0]);
|
||
|
||
if (isxdigit(hdr[0]&0xff) && isxdigit((hdr[0]>>8)&0xff) && stream<32 &&
|
||
((1L<<stream) & fStreamsActive)) {
|
||
|
||
// _RPT3(0,"\tstream: reading chunk at %I64x, length %6ld, stream %ld\n", i64StreamPosition+sbPosition-8, hdr[1], stream);
|
||
|
||
AVIStreamNode *pasn, *pasn_next;
|
||
int streamno = 0;
|
||
|
||
pasn = listStreams.AtHead();
|
||
|
||
while((pasn_next = pasn->NextFromHead()) != NULL) {
|
||
if (streamno == stream) {
|
||
unsigned chunk_size = hdr[1] + (hdr[1]&1);
|
||
|
||
if (chunk_size >= 0x7ffffff0) {
|
||
// Uh oh... assume the file has been damaged. Disable streaming.
|
||
sint64 bad_pos = i64StreamPosition+sbPosition-8;
|
||
|
||
mxverb(3, "AVI: Invalid block found at %lld -- disabling streaming.\n", bad_pos);
|
||
mbFileIsDamaged = true;
|
||
i64StreamPosition = -1;
|
||
sbPosition = sbSize = 0;
|
||
return false;
|
||
}
|
||
|
||
long left = chunk_size;
|
||
bool fWrite = pasn->cache->WriteBegin(i64StreamPosition + sbPosition, left);
|
||
char *dst;
|
||
|
||
while(left > 0) {
|
||
actual = left;
|
||
|
||
dst = _StreamRead(actual);
|
||
|
||
if (!dst) {
|
||
if (fWrite)
|
||
pasn->cache->WriteEnd();
|
||
return read_something;
|
||
}
|
||
|
||
if (fWrite)
|
||
pasn->cache->Write(dst, actual);
|
||
|
||
left -= actual;
|
||
}
|
||
|
||
if (fWrite)
|
||
pasn->cache->WriteEnd();
|
||
|
||
read_something = true;
|
||
|
||
break;
|
||
}
|
||
|
||
pasn = pasn_next;
|
||
++streamno;
|
||
}
|
||
} else {
|
||
|
||
if (hdr[0] != FOURCC_LIST && hdr[0] != FOURCC_RIFF) {
|
||
long actual;
|
||
|
||
// skip chunk
|
||
|
||
unsigned chunk_size = hdr[1] + (hdr[1] & 1);
|
||
|
||
if (chunk_size >= 0x7ffffff0) {
|
||
mbFileIsDamaged = true;
|
||
i64StreamPosition = -1;
|
||
sbPosition = sbSize = 0;
|
||
return false;
|
||
}
|
||
|
||
// Determine if the chunk is overly large. If the chunk is too large, don't
|
||
// stream through it.
|
||
|
||
if (chunk_size > 262144) {
|
||
// Force resynchronization on next read.
|
||
i64StreamPosition += chunk_size;
|
||
sbPosition = sbSize = 0;
|
||
return read_something;
|
||
}
|
||
|
||
left = chunk_size;
|
||
|
||
while(left > 0) {
|
||
actual = left;
|
||
|
||
if (!_StreamRead(actual))
|
||
return read_something;
|
||
|
||
left -= actual;
|
||
}
|
||
} else {
|
||
left = 4;
|
||
|
||
while(left > 0) {
|
||
actual = left;
|
||
|
||
if (!_StreamRead(actual))
|
||
return read_something;
|
||
|
||
left -= actual;
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
sint64 AVIReadHandler::getStreamPtr() {
|
||
return i64StreamPosition + sbPosition;
|
||
}
|
||
|
||
void AVIReadHandler::FixCacheProblems(AVIReadStream *arse) {
|
||
AVIStreamNode *pasn, *pasn_next;
|
||
|
||
// The simplest fix is simply to disable caching on the stream that's
|
||
// cache-missing. However, this is a bad idea if the problem is a low-bandwidth
|
||
// audio stream that's outrunning a high-bandwidth video stream behind it.
|
||
// Check the streaming leader, and if the streaming leader is comparatively
|
||
// low bandwidth and running at least 512K ahead of the cache-missing stream,
|
||
// disable its cache.
|
||
|
||
AVIStreamNode *stream_leader = NULL;
|
||
int stream_leader_no;
|
||
int streamno=0;
|
||
|
||
pasn = listStreams.AtHead();
|
||
|
||
while((pasn_next = pasn->NextFromHead()) != NULL) {
|
||
if (pasn->cache)
|
||
if (!stream_leader || pasn->stream_pushes > stream_leader->stream_pushes) {
|
||
stream_leader = pasn;
|
||
stream_leader_no = streamno;
|
||
}
|
||
|
||
pasn = pasn_next;
|
||
++streamno;
|
||
}
|
||
|
||
if (stream_leader && stream_leader->stream_bytes*2 < arse->psnData->stream_bytes
|
||
&& stream_leader->stream_push_pos >= arse->psnData->stream_push_pos+524288) {
|
||
#ifdef STREAMING_DEBUG
|
||
OutputDebugString("caching disabled on fast puny leader\n");
|
||
#endif
|
||
delete stream_leader->cache;
|
||
stream_leader->cache = NULL;
|
||
|
||
DisableStreaming(stream_leader_no);
|
||
|
||
i64StreamPosition = -1;
|
||
sbPosition = sbSize = 0;
|
||
} else {
|
||
#ifdef STREAMING_DEBUG
|
||
OutputDebugString("disabling caching at request of client\n");
|
||
#endif
|
||
|
||
arse->EndStreaming();
|
||
|
||
if (arse->psnData == stream_leader) {
|
||
i64StreamPosition = -1;
|
||
sbPosition = sbSize = 0;
|
||
}
|
||
}
|
||
|
||
// Reset cache statistics on all caches.
|
||
|
||
pasn = listStreams.AtHead();
|
||
|
||
while((pasn_next = pasn->NextFromHead()) != 0) {
|
||
if (pasn->cache)
|
||
pasn->cache->ResetStatistics();
|
||
|
||
pasn = pasn_next;
|
||
}
|
||
}
|
||
|
||
long AVIReadHandler::ReadData(int stream, void *buffer, sint64 position, long len) {
|
||
if (nCurrentFile<0 || nCurrentFile != (int)(position>>48))
|
||
_SelectFile((int)(position>>48));
|
||
|
||
// _RPT3(0,"Reading from file %d, position %I64x, size %d\n", nCurrentFile, position, len);
|
||
|
||
if (!m_pInput->setFilePointer2(position & _LL(0x0000FFFFFFFFFFFF), seek_beginning))
|
||
return -1;
|
||
return m_pInput->read(buffer, len);
|
||
}
|
||
|
||
bool AVIReadHandler::_readChunkHeader(uint32_le& pfcc, uint32& pdwLen) {
|
||
uint32_le dw[2];
|
||
sint32 actual;
|
||
|
||
actual = m_pInput->read(dw, 8);
|
||
|
||
if(actual != 8)
|
||
return false;
|
||
|
||
pfcc = dw[0];
|
||
pdwLen = dw[1];
|
||
|
||
return true;
|
||
}
|
||
|
||
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;*/
|
||
}
|