windows-nt/Source/XPSP1/NT/multimedia/media/avi/avifile/aviplay/aviplay.c
2020-09-26 16:20:57 +08:00

1966 lines
52 KiB
C

#include <windows.h>
#include <windowsx.h>
#include <win32.h>
#include <mmsystem.h>
#include <vfw.h>
#include "aviview.h"
#include "audplay.h"
#include "aviplay.h"
#ifdef DEBUG
extern void FAR CDECL dprintf(LPSTR, ...);
#define DPF dprintf
#define DPF2 / ## /
#define DPF3 / ## /
#else
#define DPF / ## /
#define DPF2 / ## /
#define DPF3 / ## /
#endif
#define ProfBegin() ProfClear(); ProfSampRate(5,1); ProfStart();
#define ProfEnd() ProfStop(); ProfFlush();
#define FillR(hdc, x, y, dx, dy, rgb) \
SetBkColor(hdc, rgb); \
SetRect(&rc, x, y, x+dx, y+dy); \
ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
// Why is this necessary?
#define HPBYTE BYTE huge *
// !!! All of this is out of thin air
#define MAXNUMSTREAMS 10
#define AUD_BUFFERS_MAX_SIZE 4096L // never read > this many bytes
#define NUM_WAVE_HEADERS 32
#define YIELD_WAIT_TIME 150
#define READ_WAIT_TIME 150
#define DECOMPRESS_WAIT_TIME 150
#define DRAW_WAIT_TIME 150
#ifndef WIN32
extern LONG FAR PASCAL muldiv32(LONG, LONG, LONG);
#endif
extern BOOL gfCheat, gfDecompress; // do we cheat? does our queue hold
// compressed/decompressed data?
extern BOOL gfYieldBound, gfReadBound; // pretend to be these things?
extern BOOL gfDecompressBound, gfDrawBound;
static LPVOID AllocMem(DWORD dw);
///////////////////////////////////////////////////////////////////////////
//
// useful macros
//
///////////////////////////////////////////////////////////////////////////
#define ALIGNULONG(i) ((i+3)&(~3)) /* ULONG aligned ! */
#define WIDTHBYTES(i) ((unsigned)((i+31)&(~31))/8) /* ULONG aligned ! */
#define DIBWIDTHBYTES(bi) (int)WIDTHBYTES((int)(bi).biWidth * (int)(bi).biBitCount)
#define DIBPTR(lpbi) ((LPBYTE)(lpbi) + \
(int)(lpbi)->biSize + \
(int)(lpbi)->biClrUsed * sizeof(RGBQUAD) )
extern BOOL gfPlaying; // are we playing?
extern HWND ghwndApp;
extern HACCEL ghAccel;
// for DPF's
int Skip, Jump, Empty, Cheat;
LONG frPlayed, frStart;
BOOL fAudioBroke;
// Decide whether or not you want the profiler running
#define PROFILE
#ifdef PROFILE
#define TIME(x) \
LONG time ## x, cnt ## x;
#define ZERO(x) \
time ## x = 0, cnt ## x = 0
#define START(x) \
time ## x -= (LONG)timeGetTime()
#define END(x) \
(time ## x += (LONG)timeGetTime()), cnt ## x ++
#else
#define TIME(x)
#define ZERO(x)
#define START(x)
#define END(x)
#endif
TIME(Total); // for profiling
TIME(Other);
TIME(Time);
TIME(Key);
TIME(Read);
TIME(Copy);
TIME(Draw);
TIME(Audio);
TIME(Decomp);
TIME(Yield);
TIME(Free);
// wFlags in Q structure
#define FAST_TEMPORAL 1
typedef struct {
HWND hwnd;
HDC hdc;
LPBITMAPINFOHEADER lpbi;
HDRAWDIB hdd;
RECT rc;
} VIDEORENDER, FAR *LPVIDEORENDER;
typedef struct {
HWAVEOUT hWaveOut;
WAVEHDR wavehdr[NUM_WAVE_HEADERS];
BOOL fBusy[NUM_WAVE_HEADERS]; // waiting for WHDR_DONE
} AUDIORENDER, FAR *LPAUDIORENDER;
typedef struct _QENTRY {
LONG start; // first TIME!!! sample number in our buffer
LONG length; // number of samples in our buffer
LONG size; // length in bytes of the buffer
struct _QENTRY huge *next; // where to find the next bunch of data
// after struct comes the compressed data from the stream
} QENTRY, huge *HPQENTRY;
typedef struct {
PAVISTREAM pavi; // stream we'll read from
DWORD fccType;// what kind of stream is this? (eg. streamtypeVIDEO)
DWORD dwSampleSize; // 0 if samples are variable length
//!!!!
HIC hic; // a compressor that can decompress the stream
WORD wFlags; // eg. FAST_TEMPORAL
LPVOID lpfmtIn; // format of the compressed data in the stream
LPVOID lpfmtOut; // decompressed format stored in queue
LPVOID lpRender; // handle to rendering information
//!!!
LONG timeStart; // the time when the stream started playing
LONG sampleStart; // the first sample number we played
LONG streamEnd; // the last sample in the stream
HPBYTE buffer; // points to the beginning of the buffer
LONG bufsize; // size of buffer
LONG buffree; // how many of these bytes are free?
HPQENTRY head; // where next chunk from disk is written
HPQENTRY tail; // other end of queue - start of real data
HPQENTRY read; // where render will read from to get data
HPQENTRY recent; // last thing read into the q (to update ->next)
int count; // how many chunks of junk in the queue?
int countR; // how many of them haven't started rendering?
long pos; // which sample we'll start reading next
DWORD dwSampPerRead; // How many samples to read at a time
HPBYTE lpBitsIn; // buffer for a stream read
LONG cbBitsIn; // size of buffer
LONG cbBitsInUsed; // amount of data read into buffer
BOOL fBitsInReady; // does this buffer have frame data in it?
LONG lBitsInSample; // 1st sample in the buffer
} QUEUE, *PQUEUE, FAR *LPQUEUE;
typedef struct {
int count;
LPQUEUE queue[MAXNUMSTREAMS];
} AVIQUEUE, *PAVIQUEUE, far *LPAVIQUEUE;
// global - for aviTime()
LPAVIQUEUE qAVI;
/***************************************************************************/
/***************************************************************************/
/***** INTERNAL FUNCTIONS THAT KNOW ABOUT SPECIFIC STREAM TYPES **********/
/***************************************************************************/
/***************************************************************************/
//
// Wait the specified number of milliseconds
//
void NEAR PASCAL Wait(LONG msec)
{
LONG l;
l = timeGetTime();
while ((LONG)timeGetTime() < l + msec);
return;
}
//
// Determine if the given stream is compressed.
//
BOOL NEAR qIsCompressed(LPQUEUE q)
{
if (q->fccType == streamtypeVIDEO) {
// If we want DRAWDIB to decompress for us, just pretend we're
// not comrpressed!
return gfDecompress &&
((LPBITMAPINFOHEADER)q->lpfmtIn)->biCompression != BI_RGB;
} else {
return FALSE; // !!! decompression is pretty video specific now.
}
}
//
// Return how much space it will take to decompress the given bits
//
LONG NEAR qDecompressedSize(LPQUEUE q, HPBYTE hp, LONG cb)
{
if (q->fccType == streamtypeVIDEO) {
if (qIsCompressed(q))
return ((LPBITMAPINFOHEADER)q->lpfmtOut)->biSizeImage;
else
return cb;
} else {
return cb; // it's not compressed
}
}
//
// Locate and return the HIC of a compressor that can decompress the given
// type of format from the given type of stream.
// This will return the output format that it will decompress into.
//
HIC NEAR qLocate(LPQUEUE q, LPVOID FAR *lplpfmtOut) {
DWORD fccHandler;
HIC hic;
LONG cb;
if (q->fccType == streamtypeVIDEO) {
if (lplpfmtOut == NULL)
return NULL;
// ICM won't search for compressors to decompress BI_RLE8.
// We need to provide the handler of a known decompressor that comes
// with our AVIFile read API code. !!! HACK
if (((LPBITMAPINFOHEADER)q->lpfmtIn)->biCompression == BI_RLE8)
fccHandler = mmioFOURCC('R','L','E',' ');
else
fccHandler = 0;
// trust that the default format to decompress to is something usable
hic = ICLocate(ICTYPE_VIDEO, fccHandler, q->lpfmtIn, NULL,
ICMODE_DECOMPRESS);
if (hic == NULL)
return NULL;
// get ready for the Decompress calls we'll be making later
*lplpfmtOut = GlobalAllocPtr(GMEM_MOVEABLE,
sizeof(BITMAPINFOHEADER) + 256*sizeof(RGBQUAD));
if (*lplpfmtOut == NULL) {
ICClose(hic);
return NULL;
}
//!!! pass the size of the video so it knows whether to dither or not.
// Making it stretch the dither will look ugly!
ICGetDisplayFormat(hic, q->lpfmtIn, *lplpfmtOut, 0, 0, 0);
// !!! ICM bug... biSizeImage is not set right by ICGetDisplayFormat
// Luckily I happen to know it's uncompressed and can set it myself
((LPBITMAPINFOHEADER)*lplpfmtOut)->biSizeImage =
((LPBITMAPINFOHEADER)*lplpfmtOut)->biHeight *
DIBWIDTHBYTES(*(LPBITMAPINFOHEADER)(*lplpfmtOut));
} else {
hic = NULL;
if (hic) {
// get ready for the Decompress calls we'll be making later
cb = ICDecompressGetFormatSize(hic, q->lpfmtIn);
*lplpfmtOut = GlobalAllocPtr(GMEM_MOVEABLE, cb);
if (*lplpfmtOut == NULL) {
ICClose(hic);
return NULL;
}
ICDecompressGetFormat(hic, q->lpfmtIn, *lplpfmtOut);
}
}
return hic;
}
//
// Set flags specific to the type of data we're handling.
//
WORD NEAR qSetFlags(LPQUEUE q)
{
ICINFO icinfo;
WORD wFlags = 0;
if (q->fccType == streamtypeVIDEO) {
// Figure out if we can do fast temporal compression or not.
// (Do we need to decompress on top of the previous frame?)
if (q->hic) {
ICGetInfo(q->hic, &icinfo, sizeof(ICINFO));
if (icinfo.dwFlags & VIDCF_FASTTEMPORALD)
wFlags |= FAST_TEMPORAL;
}
}
q->wFlags = wFlags;
return wFlags;
}
//
// Pick an arbitrary size to make the buffer in the queue
//
BOOL NEAR qBufferStuff(LPQUEUE q)
{
LONG cb;
// !!! Pick a better size for our video queue?
#define NUM_VID_BUFFERS 6
if (q->fccType == streamtypeVIDEO) {
if (q->lpfmtOut) { // stream is compressed, use uncompressed size
cb = ((LPBITMAPINFOHEADER)(q->lpfmtOut))->biSizeImage;
q->bufsize = cb * NUM_VID_BUFFERS + NUM_VID_BUFFERS *sizeof(QENTRY);
} else { // stream is not compressed, use input size
cb = ((LPBITMAPINFOHEADER)(q->lpfmtIn))->biSizeImage;
q->bufsize = cb * NUM_VID_BUFFERS + NUM_VID_BUFFERS *sizeof(QENTRY);
}
q->dwSampPerRead = 1;
q->buffree = q->bufsize;
// We want n reads of Audio to exactly fill the buffer so no space is wasted
// and we'll never waste time trying to read into buffers that are too small
} else if (q->fccType == streamtypeAUDIO) {
if (q->dwSampleSize == 0) {
DPF("***********ASSERT! Audio has variable sample size!");
}
q->dwSampPerRead = AUD_BUFFERS_MAX_SIZE / q->dwSampleSize;
cb = AUD_BUFFERS_MAX_SIZE;
q->bufsize = cb * NUM_WAVE_HEADERS + NUM_WAVE_HEADERS * sizeof(QENTRY);
q->buffree = q->bufsize;
} else {
cb = 0;
q->dwSampPerRead = 0;
q->bufsize = 0;
q->buffree = q->bufsize;
}
// Make a buffer to read the stream data into. Unfortunately, I have
// no good way of knowing how big the buffer needs to be. I can't
// re-alloc it bigger while we're playing if my guess is wrong (not
// enough time). Hopefully 3/2 the uncompressed size is big enough
//
// Use DOS Memory for speed
q->fBitsInReady = FALSE;
q->cbBitsInUsed = 0;
q->cbBitsIn = cb * 3 / 2;
q->lpBitsIn = AllocMem(cb * 3 / 2); // !!! What is the real size?
if (q->lpBitsIn == NULL)
return FALSE;
return TRUE;
}
// Return the decompressed format
LPVOID NEAR qFmt(LPQUEUE q)
{
// If we're decompressing, we know it already
if (qIsCompressed(q))
return q->lpfmtOut;
// If not, it's the same as the input format
else {
return q->lpfmtIn;
}
}
// !!! ouch
LPVOID NEAR PASCAL qRead(LPQUEUE q);
BOOL NEAR PASCAL qEat(LPQUEUE q);
LPVOID NEAR PASCAL qPeek(LPQUEUE q);
LONG NEAR PASCAL qPeekSize(LPQUEUE q);
BOOL NEAR qRender(LPQUEUE q, LPVOID lpBits, LONG cbBits, BOOL fRender)
{
#define VidRender ((LPVIDEORENDER)q->lpRender)
#define AudRender ((LPAUDIORENDER)q->lpRender)
if (q->fccType == streamtypeVIDEO) {
RECT rc = VidRender->rc;
WORD wFlags = DDF_SAME_HDC | DDF_SAME_DRAW;
if (lpBits == NULL)
return FALSE;
// We don't want to draw this, but the decompressor needs to see it
// (eg. for temporal compression)
if (!fRender)
wFlags |= DDF_DONTDRAW;
START(Draw);
DrawDibDraw(VidRender->hdd, VidRender->hdc,
rc.left, rc.top,
rc.right - rc.left,
rc.bottom - rc.top,
VidRender->lpbi, lpBits, 0, 0, -1, -1,
wFlags);
if (fRender)
qEat(q); // we're done with this right away - remove w/o render
if (!gfDecompress && gfDecompressBound)
Wait(DECOMPRESS_WAIT_TIME);
if (gfDrawBound)
Wait(DRAW_WAIT_TIME);
DPF3("DRAW: Rendered a video frame");
END(Draw);
return TRUE;
} else if (q->fccType == streamtypeAUDIO) {
BOOL f = FALSE;
int i;
UINT w;
START(Audio);
//
// First of all, free up any buffers from the queue that are done
// !!! Assumes they come back in the order they were sent
//
for (i = 0; i < NUM_WAVE_HEADERS; i++) {
if (AudRender->fBusy[i] &&
(AudRender->wavehdr[i].dwFlags & WHDR_DONE)) {
DPF3("AUDIO: Wave Buffer %d freed", i);
qEat(q); // remove from queue - without rendering it
AudRender->fBusy[i] = FALSE;
}
}
for (i = 0; i < NUM_WAVE_HEADERS; i++) {
if (!(AudRender->wavehdr[i].dwFlags & WHDR_DONE))
break;
}
if (i == NUM_WAVE_HEADERS && !fAudioBroke &&
q->pos < AVIStreamEnd(q->pavi)) {
DPF("AUDIO: ************** AUDIO BROKE!!! ***************");
fAudioBroke = TRUE;
}
if (!lpBits || !fRender) {
DPF3("AUDIO: No bits to render");
END(Audio);
return FALSE;
}
for (i = 0; i < NUM_WAVE_HEADERS; i++) {
if ((AudRender->wavehdr[i].dwFlags & WHDR_DONE) &&
AudRender->hWaveOut) {
AudRender->wavehdr[i].lpData = lpBits;
AudRender->wavehdr[i].dwBufferLength = cbBits;
AudRender->fBusy[i] = TRUE;
w = waveOutWrite(AudRender->hWaveOut, &AudRender->wavehdr[i],
sizeof(WAVEHDR));
f = TRUE;
DPF3("AUDIO: Wrote audio buffer %d", i);
// We used some data - advance the read pointer so the next
// read will give us new wave data
qRead(q);
break;
}
}
if (i == NUM_WAVE_HEADERS) { // braces necessary
DPF3("AUDIO: Can't render - no free buffers");
}
END(Audio);
return f;
} else {
return FALSE;
}
}
void NEAR qRenderFini(LPQUEUE q)
{
#define VidRender ((LPVIDEORENDER)q->lpRender)
#define AudRender ((LPAUDIORENDER)q->lpRender)
int i;
UINT w;
if (q->fccType == streamtypeVIDEO) {
SelectPalette(VidRender->hdc, GetStockObject(DEFAULT_PALETTE), FALSE);
RealizePalette(VidRender->hdc);
DrawDibClose(VidRender->hdd);
ReleaseDC(VidRender->hwnd, VidRender->hdc);
GlobalFreePtr(VidRender);
} else if (q->fccType == streamtypeAUDIO) {
if (AudRender->hWaveOut) {
w = waveOutReset(AudRender->hWaveOut);
for (i = 0; i < NUM_WAVE_HEADERS; i++) {
// set these back to what they were when we prepared them
AudRender->wavehdr[i].lpData = (LPBYTE) q->buffer;
AudRender->wavehdr[i].dwBufferLength = q->bufsize;
waveOutUnprepareHeader(AudRender->hWaveOut,
&AudRender->wavehdr[i], sizeof(WAVEHDR));
}
w = waveOutClose(AudRender->hWaveOut);
}
GlobalFreePtr(AudRender);
} else {
}
}
BOOL NEAR qRenderInit(LPQUEUE q, HWND hwnd, RECT rc)
{
#define VidRender ((LPVIDEORENDER)q->lpRender)
#define AudRender ((LPAUDIORENDER)q->lpRender)
int i;
UINT w;
if (q->fccType == streamtypeVIDEO) {
LPVOID lpfmt = qFmt(q);
VidRender = (LPVIDEORENDER)GlobalAllocPtr(GMEM_MOVEABLE,
sizeof(VIDEORENDER));
if (VidRender == NULL)
return FALSE;
VidRender->hwnd = hwnd;
VidRender->hdc = GetDC(hwnd);
VidRender->lpbi = (LPBITMAPINFOHEADER)lpfmt;
VidRender->hdd = DrawDibOpen();
VidRender->rc = rc;
// !!! Error code?
DrawDibBegin(VidRender->hdd, VidRender->hdc,
rc.right - rc.left,
rc.bottom - rc.top,
VidRender->lpbi,
(int)VidRender->lpbi->biWidth,
(int)VidRender->lpbi->biHeight, 0);
DrawDibRealize(VidRender->hdd, VidRender->hdc, FALSE);
q->timeStart = timeGetTime(); // start the clock
return TRUE;
} else if (q->fccType == streamtypeAUDIO) {
AudRender = (LPAUDIORENDER)GlobalAllocPtr(GMEM_MOVEABLE,
sizeof(AUDIORENDER));
if (AudRender == NULL)
return FALSE;
w = waveOutOpen(&AudRender->hWaveOut, WAVE_MAPPER, q->lpfmtIn,
0, 0L, 0);
if (w) { // close the device and try once more before giving up
// !!! Hack for known bugs in other people's stuff
LPWAVEFORMAT lpwf = q->lpfmtIn;
if (lpwf->wFormatTag == WAVE_FORMAT_PCM) {
lpwf->nAvgBytesPerSec = lpwf->nSamplesPerSec*lpwf->nBlockAlign;
}
sndPlaySound(NULL, 0);
w = waveOutOpen(&AudRender->hWaveOut, WAVE_MAPPER, q->lpfmtIn,
0, 0L, 0);
}
if (w) {
DPF("AUDIO: *************Cannot open the wave device");
AudRender->hWaveOut = NULL; // paranoia?
return FALSE;
}
for (i = 0; i < NUM_WAVE_HEADERS; i++) {
AudRender->fBusy[i] = FALSE; // not outstanding
AudRender->wavehdr[i].dwFlags = 0;
AudRender->wavehdr[i].lpData = (LPBYTE) q->buffer;
AudRender->wavehdr[i].dwBufferLength = q->bufsize;
if (waveOutPrepareHeader(AudRender->hWaveOut,&AudRender->wavehdr[i],
sizeof(WAVEHDR))) {
DPF("AUDIO: *************Cannot prepare header %d", i);
qRenderFini(q);
return FALSE;
}
AudRender->wavehdr[i].dwFlags |= WHDR_DONE; // nuked by Prepare
}
// Pause for now so we can start instantly by un-pausing
waveOutPause(AudRender->hWaveOut);
// we must pre-stuff our audio wave buffers with all the data we have
// so that whenever a buffer is free we know it's because it's done with
// some more data
// !!! If we have more wavebuffers than data, the leftovers will think
// !!! that they've completed and destroy some audio!
while (qRender(q, qPeek(q), qPeekSize(q), TRUE)); //don't remove from Q
q->timeStart = timeGetTime(); // start the clock
return TRUE;
} else {
return 0;
}
}
/***************************************************************************/
/***************************************************************************/
/************* END OF SPECIFIC STREAM TYPE FUNCTIONS ********************/
/***************************************************************************/
/***************************************************************************/
//
// Determine if a queue is getting low on data.
// Returns the percentage of queue buffer space that is full.
//
int NEAR qStarved(LPQUEUE q)
{
int i;
i = (int)(100L - (q->buffree * 100L / q->bufsize));
DPF3("STARVED: %ld%%", i);
return i;
}
// Shut down the queueing system
void NEAR PASCAL qFini(LPQUEUE q)
{
if (q->lpfmtIn)
GlobalFreePtr(q->lpfmtIn);
q->lpfmtIn = NULL;
if (q->hic) {
ICDecompressEnd(q->hic);
ICClose(q->hic);
if (q->lpfmtOut)
GlobalFreePtr(q->lpfmtOut);
q->lpfmtOut = NULL;
}
q->hic = NULL;
if (q->lpBitsIn)
GlobalFreePtr(q->lpBitsIn);
q->lpBitsIn = NULL;
if (q->buffer)
GlobalFreePtr(q->buffer);
q->buffer = NULL;
q->head = q->tail = q->read = NULL;
}
// initialize the queueing system
BOOL NEAR PASCAL qInit(PAVISTREAM pavi, LPQUEUE q)
{
LONG cb;
AVISTREAMINFO avis;
if (q == NULL)
return FALSE;
q->pavi = pavi;
q->lpfmtIn = q->lpfmtOut = NULL;
q->buffer = q->lpBitsIn = NULL;
q->recent = NULL;
q->hic = NULL;
if (pavi == NULL)
return FALSE;
if (AVIStreamInfo(pavi, &avis, sizeof(avis)) != AVIERR_OK)
goto qInitError;
q->fccType = avis.fccType;
q->dwSampleSize = avis.dwSampleSize;
// Get the format of the compressed data from the stream
AVIStreamReadFormat(pavi, AVIStreamStart(pavi), NULL, &cb);
q->lpfmtIn = GlobalAllocPtr(GMEM_MOVEABLE, cb);
if (q->lpfmtIn == NULL)
goto qInitError;
AVIStreamReadFormat(pavi, 0, q->lpfmtIn, &cb);
// Maybe we haven't been given the size of a compressed image. Fix it.
if (((LPBITMAPINFOHEADER)q->lpfmtIn)->biSizeImage == 0)
((LPBITMAPINFOHEADER)q->lpfmtIn)->biSizeImage =
avis.dwSuggestedBufferSize;
if (((LPBITMAPINFOHEADER)q->lpfmtIn)->biSizeImage == 0)
((LPBITMAPINFOHEADER)q->lpfmtIn)->biSizeImage = 20000; // !!!
// Open a compressor that can decompress it
if (qIsCompressed(q)) {
// Find a decompressor and get the decompressed format
q->hic = qLocate(q, &(q->lpfmtOut));
if (q->hic == NULL)
goto qInitError;
ICDecompressBegin(q->hic, q->lpfmtIn, q->lpfmtOut);
}
// Pick a buffer size, and the number of samples to read each time
// !!! Must have a valid output format before we call this.
if (!qBufferStuff(q))
goto qInitError;
qSetFlags(q);
// Queue starts empty, and is about to read the first sample
q->count = q->countR = 0;
q->pos = q->sampleStart = AVIStreamStart(pavi);
q->streamEnd = AVIStreamEnd(pavi);
q->buffer = GlobalAllocPtr(GMEM_MOVEABLE, q->bufsize);
q->head = q->tail = q->read = (HPQENTRY)q->buffer;
if (q->buffer == NULL)
goto qInitError;
return TRUE;
qInitError:
qFini(q);
return FALSE;
}
#if 0
// return the previous buffer of the queue
HPSTR NEAR qPrev(LPQUEUE q, HPSTR hp)
{
// !!! Even if the queue is empty, the old information is still there.
// Special case that we're at the beginning of the buffer
if (hp == q->buffer)
return hp + (QSIZE - 1) * q->size;
else
return hp - q->size;
}
#endif
#define HeadBuf(q) (HPBYTE)((q)->head + 1)
#define TailBuf(q) (HPBYTE)((q)->tail + 1)
#define ReadBuf(q) (HPBYTE)((q)->read + 1)
#define BufEnd(q) (HPBYTE)((HPBYTE)((q)->buffer) + (q)->bufsize)
// Decompress the entry in our input buffer into the queue
BOOL NEAR qDecompress(LPQUEUE q)
{
LONG lBytesNeeded, lBytesFree;
LONG lKey;
BOOL fHack;
// Sometimes the RLE8 file has RGB frames in it and will blow if we
// try to decompress them.
// !!! fix RLEC so this hack is not needed!
#define qlpbi ((LPBITMAPINFOHEADER)q->lpfmtIn)
fHack = q->fccType == streamtypeVIDEO &&
qlpbi->biCompression == BI_RLE8 &&
(LONG)(qlpbi->biSizeImage) == q->cbBitsInUsed;
lBytesNeeded = (qIsCompressed(q) && !fHack) ?
qDecompressedSize(q, q->lpBitsIn, q->cbBitsInUsed) : q->cbBitsInUsed;
// How many contiguous bytes do we have left in the queue?
if ((HPBYTE)(q->tail) <= (HPBYTE)(q->head)) {
lBytesFree = BufEnd(q) - HeadBuf(q);
// If head and tail are equal - some special cases
if ((HPBYTE)(q->tail) == (HPBYTE)(q->head)) {
if (q->count > 0)
lBytesFree = 0;
else {
q->head = q->tail = q->read = (HPQENTRY)q->buffer;
lBytesFree = BufEnd(q) - HeadBuf(q);
}
}
} else {
lBytesFree = (HPBYTE)(q->tail) - HeadBuf(q);
}
// Not enough space in the queue to decompress this frame!
if (lBytesFree < lBytesNeeded) {
// Did we fail because we're at the end of the queue? Then
// try reading into the beginning of the queue.
if ((HPBYTE)(q->head) > (HPBYTE)(q->tail) &&
(HPBYTE)(q->tail) != (HPBYTE)(q->buffer)) {
q->head = (HPQENTRY)q->buffer;
if (q->countR == 0)
q->read = q->head;
lBytesFree = (HPBYTE)(q->tail) - HeadBuf(q);
} else {
lBytesFree = 0;
}
}
// Still not enough space in the queue? Then the queue is really full
if (lBytesFree < lBytesNeeded) {
DPF3("Q too full to decompress into");
return FALSE;
}
//
// Now decompress the frame into our buffer
// If we're not compressed, we can do a straight copy.
//
if (fHack || !qIsCompressed(q)) {
START(Copy);
hmemcpy(HeadBuf(q), q->lpBitsIn, lBytesNeeded);
END(Copy);
} else {
// !!! This is kind of video specific, isn't it? What will decompressing
// audio look like?
START(Key);
lKey = AVIStreamFindKeyFrame(q->pavi, q->lBitsInSample, 0);
END(Key);
// We need to copy of the previous bits and decompress on top of them
// !!! This assumes the previous entry is the previous bits!
// !!! And that they're still there!
if (!(q->wFlags & FAST_TEMPORAL) && lKey != q->lBitsInSample &&
q->recent) {
START(Copy);
hmemcpy(HeadBuf(q), q->recent + 1, q->recent->size);
END(Copy);
}
START(Decomp);
ICDecompress(q->hic, 0, q->lpfmtIn, q->lpBitsIn, q->lpfmtOut,
HeadBuf(q));
if (gfDecompressBound)
Wait(DECOMPRESS_WAIT_TIME);
DPF3("DECOMPRESS: %ld --> %ld bytes", q->cbBitsInUsed, lBytesNeeded);
END(Decomp);
}
// Now recognize that less space is available in the queue
q->buffree -= lBytesNeeded + sizeof(QENTRY);
// Fix the link from the previous read to this one in case it's moved
if (q->recent && q->count)
q->recent->next = q->head;
q->recent = q->head;
q->head->start = q->pos;
// !!! Audio picked it's own number of samples to read -- video read one
q->head->length = q->dwSampleSize ? lBytesNeeded / q->dwSampleSize
: q->dwSampPerRead;
q->head->size = lBytesNeeded;
q->head = (HPQENTRY)(HeadBuf(q) + lBytesNeeded);
if ((HPBYTE)(q->head) > BufEnd(q)) {
DPF("*************Head went past Buffer End!");
}
if ((HPBYTE)(q->head) >= BufEnd(q))
q->head = (HPQENTRY)q->buffer;
// Initially, the block we just read in will say its next block will be
// wherever the new head is. This could change later.
q->recent->next = q->head;
q->pos += q->dwSampleSize ? lBytesNeeded / q->dwSampleSize
: q->dwSampPerRead;
q->count++;
q->countR++;
q->fBitsInReady = FALSE;
return TRUE;
}
// read something into the queue
BOOL NEAR qFill(LPQUEUE q)
{
LONG lBytesRead;
HRESULT hRet;
BOOL f = FALSE;
// If there is already a compressed frame in the buffer, stuff it in the
// queue before reading another frame.
if (q->fBitsInReady) {
DPF3("Purging a previous buffer");
f = qDecompress(q);
}
// The buffer is still full! I guess the queue was full. Bail.
if (q->fBitsInReady) {
DPF3("Can't purge buffer!");
return FALSE;
}
// Seems we're at the end of the stream! No more to read.
if (q->pos >= q->streamEnd)
return FALSE;
START(Read);
// For fixed sample sizes, read a "convenient" number of samples, ie.
// how ever many samples are left in this AVI chunk. Never read more than
// a certain amount, though.
if (q->dwSampleSize) {
// !!! READ CONVENIENT NUMBER!!!
hRet = AVIStreamRead(q->pavi, q->pos, q->dwSampPerRead, q->lpBitsIn,
min((DWORD)q->cbBitsIn, q->dwSampPerRead * q->dwSampleSize),
&lBytesRead, NULL);
// for variable length samples, just read normally whatever we'd decided
} else {
hRet = AVIStreamRead(q->pavi, q->pos, q->dwSampPerRead, q->lpBitsIn,
q->cbBitsIn, &lBytesRead, NULL);
}
if (gfReadBound)
Wait(READ_WAIT_TIME);
DPF3("READ: Read %ld bytes", lBytesRead);
END(Read);
if (hRet == AVIERR_OK) {
q->fBitsInReady = TRUE;
q->cbBitsInUsed = lBytesRead;
q->lBitsInSample = q->pos;
} else {
DPF("******************Stream read failed");
return FALSE; // uh oh! This shouldn't happen!
}
// Now decompress into the queue
return (f || qDecompress(q));
}
// Fill the entire queue
void NEAR qPrime(LPQUEUE q) // Preroll for clockwork people
{
while (qFill(q))
;
}
// Return the number of entries in the queue
int NEAR qCount(LPQUEUE q)
{
return q->count;
}
// Return the lowest sample number in the queue that hasn't been rendered yet.
// If the queue is empty, it will return the next sample it will read, because
// it would then become the lowest sample in the queue.
LONG NEAR PASCAL qSample(LPQUEUE q)
{
if (q->countR == 0)
return q->pos;
else
return q->read->start;
}
//
// Return a pointer to the lowest sample in the queue that hasn't been
// rendered yet. Don't eat it out of the queue because it's still needed,
// and don't skip past it or anything.
//
LPVOID NEAR PASCAL qPeek(LPQUEUE q)
{
if (q->countR == 0)
return NULL;
return ReadBuf(q);
}
//
// Return the size of what qPeek() would return.
//
LONG NEAR PASCAL qPeekSize(LPQUEUE q)
{
if (q->countR == 0)
return 0;
return q->read->size;
}
//
// Return a pointer to the lowest sample in the queue. Don't eat it out of the
// queue because it's still needed, but subsequent Peeks will get newer data
//
LPVOID NEAR PASCAL qRead(LPQUEUE q)
{
LPVOID lp;
if (q->countR == 0)
return NULL;
lp = ReadBuf(q);
// We can't destroy this data yet... so don't move the tail
q->read = (HPQENTRY)(q->read->next);
q->countR--;
// paranoia
if (q->countR == 0)
q->read = q->head;
return lp;
}
//
// return the Next sample that will be read into the queue.
//
long qPos(LPQUEUE q)
{
return q->pos;
}
//
// Remove something from the queue without decompressing or remembering it.
// But pass it to the renderer in case it needs to see the bits (eg. temporal
// compression).
//
BOOL NEAR qSkip(LPQUEUE q)
{
// !!! This code should be identical to qEat() except it renders!
if (q->count == 0)
return FALSE;
// More space is now available in the queue
q->buffree += sizeof(QENTRY) + q->tail->size;
if (q->count == 1) {
// Renderer may need to see the data even though we're skipping it
// (e.g. for temporal video compression)
qRender(q, qPeek(q), qPeekSize(q), FALSE); // DON'T ACTUALLY RENDER
q->tail = q->read = q->head = (HPQENTRY)q->buffer;
q->countR = 0;
// !!! q->recent = NULL; // hmemcpy needs recent bits for non key frames
} else {
qRender(q, qPeek(q), qPeekSize(q), FALSE); // DON'T ACTUALLY RENDER
if (q->tail == q->read && q->countR) {
q->countR--;
q->read = (HPQENTRY)(q->tail->next);
}
q->tail = (HPQENTRY)(q->tail->next);
}
if ((HPBYTE)(q->tail) >= BufEnd(q)) {
DPF("******************Tail went past Buffer End!");
}
q->count--;
return TRUE;
}
//
// Remove the first thing in the queue, without sending it to the renderer first
// Returns whether or not anything was removed.
//
BOOL NEAR PASCAL qEat(LPQUEUE q)
{
// !!! This code should be identical to qSkip() except it doesn't render!
if (q->count == 0)
return FALSE;
// More space is now available in the queue
q->buffree += sizeof(QENTRY) + q->tail->size;
if (q->count == 1) {
q->tail = q->read = q->head = (HPQENTRY)q->buffer;
q->countR = 0;
// !!! q->recent = NULL; // hmemcpy needs recent bits for non key frames
} else {
if (q->tail == q->read && q->countR) {
q->countR--;
q->read = (HPQENTRY)(q->tail->next);
}
q->tail = (HPQENTRY)(q->tail->next);
}
if ((HPBYTE)(q->tail) >= BufEnd(q)) {
DPF("******************Tail went past Buffer End!");
}
q->count--;
return TRUE;
}
//
// set the Next sample to be read into the queue using the time provided
//
void NEAR qSeek(LPQUEUE q, LONG pos)
{
// !!! Don't necessarily just empty the queue!
while (qEat(q)); // remove from queue without rendering
q->fBitsInReady = FALSE; // never bother to decompress this guy
// !!! Do something intelligent if they seek to something in the queue
// !!! already
if (pos < q->pos) {
DPF("******************Seeking backwards!");
}
q->pos = pos;
// USED FOR DPRINTF ONLY
if (q->fccType == streamtypeVIDEO)
frPlayed = max(frPlayed, pos - 1);
}
//
// Inform a stream what time it is.
//
void NEAR PASCAL qInformTime(LPQUEUE q, LONG time)
{
LONG fr, frNextKey, frPrevKey;
if (q->fccType == streamtypeVIDEO) {
LONG frEnd;
// What frame of video should be showing?
fr = AVIStreamTimeToSample(q->pavi, time) + q->sampleStart;
DPF2("VIDEO: Time for frame %ld", fr);
// Don't go past the end
frEnd = AVIStreamEnd(q->pavi);
if (fr >= frEnd)
return;
// for DPF's at end
frPlayed = max(frPlayed, fr);
// The earliest frame available is later in the movie, so we'll
// need to wait until we can draw something. (We're caught up).
if (qSample(q) > fr) {
START(Free);
DPF2("VIDEO: First available frame is %ld. Free time!", qSample(q));
qFill(q); // read at LEAST one no matter how full we are
END(Free);
goto DontStarve; // !!! make sure we're aren't starving?
}
//
// the frame we want is not in the q at all, and not the next frame
// what should we do?
//
if (fr - qPos(q) > 0) {
START(Key);
frPrevKey = AVIStreamFindKeyFrame(q->pavi, fr,SEARCH_BACKWARD);
frNextKey = AVIStreamFindKeyFrame(q->pavi, fr, SEARCH_FORWARD);
END(Key);
DPF2("VIDEO: Panic! qPos=%ld prev=%ld fr=%ld next=%ld",qPos(q),frPrevKey,fr,frNextKey);
// If the previous key frame is in the queue somewhere, let's
// draw it !!!
if (qCount(q) &&
frPrevKey >= qSample(q) && frPrevKey < qPos(q)) {
while (qSample(q) < frPrevKey) { // find the prev key
qSkip(q);
Skip++; // remember we skipped a frame
}
if (qSample(q) == frPrevKey) {
DPF2("VIDEO: Found PREV key %ld in queue", frPrevKey);
qRender(q, qPeek(q), qPeekSize(q), TRUE); // draw it
if (gfCheat) Cheat++; // not at exact time we wanted
}
if (qCount(q) == 0)
Empty++;
}
// !!! Random if statement
if (frPrevKey >= qPos(q) &&
(fr - frPrevKey) <= (frNextKey - fr)) {
DPF2("VIDEO: Jumping %d to PREV key frame %ld", (int)(frPrevKey - qSample(q)), frPrevKey);
Jump += (int)(frPrevKey - qSample(q));
Empty++;
qSeek(q, frPrevKey);
if (qFill(q)) { // get prev key frame
qRender(q, qPeek(q), qPeekSize(q), TRUE); // draw it
if (fr != frPrevKey && gfCheat)
Cheat++; // weren't supposed to draw now
}
//qFill(q); // !!! waste of time?
return; // !!! goto DontStarve would waste time ???
} else if (frNextKey >= fr && frNextKey < frEnd) {
DPF2("VIDEO: Jumping %d to NEXT key frame %ld", (int)(frNextKey - qSample(q)), frNextKey);
Jump += (int)(frNextKey - qSample(q));
Empty++;
if (frPrevKey >= qPos(q)) {
qSeek(q, frPrevKey);
if (qFill(q)) { // get prev key frame
qRender(q, qPeek(q), qPeekSize(q), TRUE); // draw it
Jump -= 1; // we didn't jump this 1
if (gfCheat) Cheat++;
}
}
qSeek(q, frNextKey);
qFill(q); // put something in the empty queue
return; // !!! goto DontStarve ???
} else { // braces necessary
DPF2("VIDEO: Not jumping anywhere. End of movie");
}
}
// The frame available is too early, get some more frames until we
// have the one we need.
while (!qCount(q) || qSample(q) < fr) {
DPF2("VIDEO: We're behind! Count=%d, Available=%ld Want=%ld", qCount(q), qSample(q), fr);
if (qCount(q) == 0) { // get another sample if we're empty
Empty++; // remember we've been empty
DPF2("VIDEO: Queue is empty. Reading frame %ld, want frame %ld - SAME???", qPos(q), fr);
if (!qFill(q)) { // don't get caught in endless loop
DPF("VIDEO: ********Assertion failure! Heading south!");
break;
}
continue;
}
if (qSample(q) == fr - 1 && gfCheat) {
// Cheat! If we only need to skip one frame, draw it now
// and pretend we never skipped it
DPF2("VIDEO: Cheating at frame %ld", fr - 1);
qRender(q, qPeek(q), qPeekSize(q), TRUE);
Cheat++;
// !!! Return now? Or always draw frame fr next?
} else {
Skip++; // remember we skipped a frame
qSkip(q); // skip the frame we'll never need
}
}
// Something went wrong, abort
if (qSample(q) != fr) {
DPF("VIDEO: ***********Assertion failure! Wanted frame %ld but we died at frame %ld with %d entries in the queue", fr, qSample(q), qCount(q));
// !!! How to abort the main loop from here?
return;
}
// Read something if we're empty - we're about to need it
if (qCount(q) == 0) {
DPF("VIDEO: *************Why are we empty?");
Empty++; // remember we've been empty
if (!qFill(q)) // can't draw if this fails
return;
}
// Eat a frame and draw it
qRender(q, qPeek(q), qPeekSize(q), TRUE);
DontStarve:
// It's bad to let the queue get too empty
while (qStarved(q) < 50 && qFill(q)) { // braces necessary
DPF2("VIDEO: Filling a starving queue");
}
} else if (q->fccType == streamtypeAUDIO) {
// I don't care what time it is, I'm going to read and play audio
// as fast as I possibly can!
// Send all the information we can to the wave device to make sure
// audio never breaks. If there's nothing to send, it'll at least
// notice all of the buffers that are done and let them be re-used.
while (qRender(q, qPeek(q), qPeekSize(q), TRUE));
// Now Read and render at least one chunk of audio
qFill(q);
qRender(q, qPeek(q), qPeekSize(q), TRUE);
// If we're starving, keep reading and rendering
while (qStarved(q) < 50 && qFill(q)) {
DPF2("AUDIO: Filling a starving queue");
qRender(q, qPeek(q), qPeekSize(q), TRUE);
}
} else {
return;
}
}
// !!! static variables
static LONG timeDriverPrev = -1, timeClockBase, timeBase;
///////////////////////////////////////////////////////////////////////////////
// Video Stream method:
// Just take the current time minus the start time
// Audio Stream method:
// We can't just use timeGetTime cuz the audio clock drifts from the real
// time clock and we need to sync to the audio we're hearing, even if it's the
// wrong time. But we can't use the wave driver call to find out what time it
// is either, cuz it may only be accurate to 1/5 of a second. So we have to
// use a combination of the two.
///////////////////////////////////////////////////////////////////////////////
LONG NEAR PASCAL qNow(LPQUEUE q)
{
if (q->fccType == streamtypeVIDEO) {
return timeGetTime() - q->timeStart;
} else if (q->fccType == streamtypeAUDIO) {
MMTIME mmtime;
LONG now, timeDriver, l;
//
// Get the current time
//
now = timeGetTime();
//
// Ask the wave driver how long it's been playing for
//
if (((LPAUDIORENDER)q->lpRender)->hWaveOut) {
mmtime.wType = TIME_SAMPLES;
waveOutGetPosition(((LPAUDIORENDER)q->lpRender)->hWaveOut,
&mmtime, sizeof(mmtime));
if (mmtime.wType == TIME_SAMPLES)
timeDriver = AVIStreamSampleToTime(q->pavi, q->sampleStart) +
muldiv32(mmtime.u.sample, 1000,
((LPWAVEFORMAT)q->lpfmtIn)->nSamplesPerSec);
else if (mmtime.wType == TIME_BYTES)
timeDriver = AVIStreamSampleToTime(q->pavi, q->sampleStart) +
muldiv32(mmtime.u.cb, 1000,
((LPWAVEFORMAT)q->lpfmtIn)->nAvgBytesPerSec);
else
timeDriver = -1;
} else
timeDriver = -1;
//
// Something's wrong with the audio clock.. just use the main clock
//
if (timeDriver == -1) {
DPF("AUDIO: **********Can't get current time from audio driver!");
return now - q->timeStart;
}
//
// Audio driver still thinks it's playing the same spot as last time
//
if (timeDriver == timeDriverPrev) {
// !!! Assumes timeDriver was 0 at the beginning of play
l = now - timeClockBase + timeDriverPrev - timeBase;
//
// Ah! A new sample of audio being played
//
} else {
timeClockBase = now;
timeDriverPrev = timeDriver;
// !!! Assumes timeDriver was 0 at the beginning of play
l = timeDriverPrev - timeBase;
}
return l;
} else {
return timeGetTime() - q->timeStart;
}
}
//
// Set the first sample we will play, so we can time things properly
// This is the first thing in our queue, or the sample we're going to read next
// if it's empty.
//
void NEAR PASCAL qSetStartSample(LPQUEUE q)
{
q->sampleStart = qSample(q);
}
//
// Set the start time of the movie to the current time
//
void NEAR PASCAL qStartClock(LPQUEUE q)
{
q->timeStart = timeGetTime();
if (q->fccType == streamtypeAUDIO) {
MMTIME mmtime;
LONG timeDriver;
if (!((LPAUDIORENDER)q->lpRender)->hWaveOut) {
timeBase = timeDriver = -1;
DPF("AUDIO: Can't Start Clock");
return;
}
// un-pause the device
waveOutRestart(((LPAUDIORENDER)q->lpRender)->hWaveOut);
//
// Ask the wave driver how long it's been playing for
//
mmtime.wType = TIME_SAMPLES;
waveOutGetPosition(((LPAUDIORENDER)q->lpRender)->hWaveOut,
&mmtime, sizeof(mmtime));
if (mmtime.wType == TIME_SAMPLES)
timeDriver = AVIStreamSampleToTime(q->pavi, q->sampleStart) +
muldiv32(mmtime.u.sample, 1000,
((LPWAVEFORMAT)q->lpfmtIn)->nSamplesPerSec);
else if (mmtime.wType == TIME_BYTES)
timeDriver = AVIStreamSampleToTime(q->pavi, q->sampleStart) +
muldiv32(mmtime.u.cb, 1000,
((LPWAVEFORMAT)q->lpfmtIn)->nAvgBytesPerSec);
else
timeDriver = -1;
timeBase = timeDriver;
}
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
/////// Top layer q routines an application would call //////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
LPAVIQUEUE NEAR PASCAL QueueInit(PAVIFILE pfile)
{
PAVISTREAM pavi;
LPAVIQUEUE aviQ;
int i;
aviQ = (LPAVIQUEUE)GlobalAllocPtr(GMEM_MOVEABLE, sizeof(AVIQUEUE));
if (!aviQ)
return NULL;
for (i = 0; i < MAXNUMSTREAMS; i++) {
if (AVIFileGetStream(pfile, &pavi, 0L, i) != AVIERR_OK)
break;
aviQ->queue[i] = (LPQUEUE)GlobalAllocPtr(GMEM_MOVEABLE, sizeof(QUEUE));
if (!qInit(pavi, aviQ->queue[i]))
goto QIError;
}
aviQ->count = i;
if (i == 0)
return NULL;
return aviQ;
QIError:
for (--i; i >= 0; i--) {
if (aviQ->queue[i]) {
GlobalFreePtr(aviQ->queue[i]);
qFini(aviQ->queue[i]);
}
}
return NULL;
}
//
// Throw away all the queue stuff
//
void NEAR PASCAL QueueFini(LPAVIQUEUE q)
{
int i;
// Just tell each stream to seek there
for (i = 0; i < q->count; i++) {
qFini(q->queue[i]);
AVIStreamClose(q->queue[i]->pavi);
GlobalFreePtr(q->queue[i]);
}
GlobalFreePtr(q);
}
//
// Seek to a certain spot in the movie. We are given a TIME and convert it to
// a sample number for each stream. This is the point we'll start playing
// from, so remember this sample number.
//
void NEAR PASCAL QueueSeek(LPAVIQUEUE q, LONG time)
{
int i;
// Just tell each stream to seek there
for (i = 0; i < q->count; i++) {
qSeek(q->queue[i], AVIStreamTimeToSample(q->queue[i]->pavi, time));
qSetStartSample(q->queue[i]);
// FOR DPF ONLY
if (q->queue[i]->fccType == streamtypeVIDEO)
frStart = frPlayed = qPos(q->queue[i]);
}
}
//
// Prime all the queues -- (fill them up entirely)
//
void NEAR PASCAL QueuePrime(LPAVIQUEUE q)
{
int i;
// Prime each queue
for (i = 0; i < q->count; i++) {
qPrime(q->queue[i]);
}
}
//
// Get the time when the movie ends
//
LONG NEAR PASCAL QueueGetEndTime(LPAVIQUEUE q)
{
int i;
LONG time = 0;
// Ask each stream
for (i = 0; i < q->count; i++) {
time = max(time, AVIStreamEndTime(q->queue[i]->pavi));
}
return time;
}
//
// Get the decompressed video format that will be used to display this movie
// (use the first video stream found). If DRAWDIB is decompressing for us,
// we don't know it and will return some compressed format !!!
//
LPVOID NEAR PASCAL QueueGetVideoDisplayFormat(LPAVIQUEUE q)
{
int i;
// Ask each stream
for (i = 0; i < q->count; i++) {
if (q->queue[i]->fccType == streamtypeVIDEO)
return qFmt(q->queue[i]);
}
return NULL; // no video streams
}
//
// Prepare to render each stream. We are passed pointers to an array of
// hwnd's and rc's to use for the different video streams. Audio streams ignore
// those parameters.
// !!! Does audio need anything passed to it?
// !!! Return an error code? Abort on error? Continue anyway?
//
BOOL NEAR PASCAL QueueRenderInit(LPAVIQUEUE q, HWND FAR *phwnd, RECT FAR *prc)
{
int i, v = 0;
// Init each stream, give different parms to each video stream
for (i = 0; i < q->count; i++) {
qRenderInit(q->queue[i], *(phwnd + v), *(prc + v));
if (q->queue[i]->fccType == streamtypeVIDEO)
v++;
}
return TRUE;
}
//
// Finish up rendering.
//
void NEAR PASCAL QueueRenderFini(LPAVIQUEUE q)
{
int i;
// Tell each stream
for (i = 0; i < q->count; i++) {
qRenderFini(q->queue[i]);
}
}
//
// Inform the master queue what time it is. This should be called often.
//
void NEAR PASCAL QueueInformTime(LPAVIQUEUE q, LONG time)
{
int i;
// Just tell each stream what time it is
for (i = 0; i < q->count; i++) {
qInformTime(q->queue[i], time);
}
}
void NEAR PASCAL QueueStartClock(LPAVIQUEUE q)
{
int i;
// Set the play start time for each stream
for (i = 0; i < q->count; i++) {
qStartClock(q->queue[i]);
}
}
//
// Find out how many milliseconds since we started playing. Ask an AUDIO stream
// first, if there is one (they have priority). If not, ask a VIDEO stream.
// If not, ask any stream.
//
LONG NEAR PASCAL QueueNow(LPAVIQUEUE q)
{
int i;
for (i = 0; i < q->count; i++) {
if (q->queue[i]->fccType == streamtypeAUDIO)
return qNow(q->queue[i]);
}
for (i = 0; i < q->count; i++) {
if (q->queue[i]->fccType == streamtypeVIDEO)
return qNow(q->queue[i]);
}
return qNow(q->queue[0]);
}
BOOL NEAR PASCAL WinYield()
{
MSG msg;
BOOL fAbort=FALSE;
START(Yield);
DPF2("YIELDING...");
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)
fAbort = TRUE;
if (msg.message == WM_SYSCOMMAND && (msg.wParam & 0xFFF0) == SC_CLOSE)
fAbort = TRUE;
if (TranslateAccelerator(ghwndApp, ghAccel, &msg))
continue;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (gfYieldBound)
Wait(YIELD_WAIT_TIME);
END(Yield);
return fAbort;
}
//
// should have function take :
//
// pavis[] and a count
// along with a "draw" proc () to render wierd custom streams
//
LONG FAR PASCAL aviPlay(HWND hwnd, PAVIFILE pfile, LONG movieStart)
{
extern UINT gwZoom;
LONG l = 0, movieEnd;
int iYield=0;
RECT rcFrame, rc;
LPBITMAPINFOHEADER lpbi;
HDC hdc;
// Clear these out, so we don't abort by mistake
GetAsyncKeyState(VK_ESCAPE);
GetAsyncKeyState(VK_RBUTTON);
fAudioBroke = TRUE;
// Init the queue and fill it up
if ((qAVI = QueueInit(pfile)) == NULL)
return movieStart; // error code?
QueueSeek(qAVI, movieStart); // decide where we'll start playing
QueuePrime(qAVI); // pre-stuff all the queues
// When does this AVI end?
movieEnd = QueueGetEndTime(qAVI);
GetClientRect(hwnd, &rc);
////PatBlt(hdc, 0, 0, rc.right, rc.bottom, WHITENESS);
//
// Calculate the location to play the video based on its frame size
//
lpbi = (LPBITMAPINFOHEADER)QueueGetVideoDisplayFormat(qAVI);
rcFrame.left = rc.right / 2 -((int)lpbi->biWidth*gwZoom/4)/2;
rcFrame.top = 40; //!!! yStreamTop + TSPACE;
rcFrame.right = rcFrame.left + (int)lpbi->biWidth*gwZoom/4;
rcFrame.bottom = rcFrame.top + (int)lpbi->biHeight*gwZoom/4;
// Play the AVI in our window, centred.
// !!! This will die if > 1 video stream in a movie!
QueueRenderInit(qAVI, &hwnd, &rcFrame);
hdc = GetDC(hwnd);
if (WinYield()) // let palette change happen
// goto byebye;
// ProfBegin();
ZERO(Total);
ZERO(Other);
ZERO(Time);
ZERO(Key);
ZERO(Read);
ZERO(Copy);
ZERO(Draw);
ZERO(Audio);
ZERO(Decomp);
ZERO(Yield);
ZERO(Free);
// for DPF
Skip = Jump = Empty = Cheat = 0;
fAudioBroke = FALSE;
// Call just before main loop to set start time and waveBase hack
QueueStartClock(qAVI);
START(Total);
while (1) {
// We've been told to stop, so do so
// -1 means close after stopping
if (gfPlaying == 0 || gfPlaying == -1)
break;
// What time is it right now?
START(Time);
// What time in the movie are we at right now?
l = QueueNow(qAVI); // elapsed time since play start
END(Time);
DPF3("Time %ld", l);
// Ah! The movie is done!
if (l > movieEnd - movieStart)
break;
QueueInformTime(qAVI, l);
#ifdef DDEBUG
{
int i;
char ach[128];
i = wsprintfA(ach,
"Time %d.%02d S %d J %d E %d ",
(int)(l/1000), (int)(l%1000)/10,
Skip, Jump, Empty);
SetBkColor(hdc, RGB(255,255,255));
TextOutA(hdc, 0, 0, ach, i);
}
#endif
#ifdef DDEBUG // !!! move into a specific stream
#define W 16
#define H 16
i = qCount(q) * W;
FillR(hdc, 4, 20, i, H, RGB(255,255,0));
FillR(hdc, 4+i, 20, QSIZE*W-i, H, RGB(255,0,0));
i = (fr - qSample(q)) * W;
FillR(hdc, 4+i, 20, 1, H, RGB(0,0,0));
#endif
// Yield every once in a while. Always yielding makes performance
// plummet.
if ((++iYield % 8) == 0) {
if (WinYield())
break;
}
}
END(Total);
// ProfEnd();
timeOther =
timeTotal -
timeTime -
timeKey -
timeRead -
timeCopy -
timeDraw -
timeAudio -
timeDecomp -
timeYield;
byebye:
#ifdef PROFILE
DPF("***********************************************************");
if (fAudioBroke) { // braces necessary
DPF("****************** AUDIO BROKE!!! ************************");
}
DPF("Total Frames: %d", frPlayed - frStart + 1);
DPF("Frames skipped: %d", Skip);
DPF("Frames jumped: %d", Jump);
DPF("Total Frames missed: %d, (%d %%)", Skip + Jump, (int)(100l * (Skip + Jump) / (frPlayed - frStart + 1)));
DPF("Times cheated: %d", Cheat);
DPF("Times empty: %d", Empty);
#define SEC(x) SECA(x), SECB(x), (timeTotal ? (int)(time ## x * 100 / timeTotal) : 0)
#define SECA(x) (time ## x / 1000l) , (time ## x % 1000l)
#define SECB(x) (cnt ## x ? (time ## x / cnt ## x / 1000l) : 0l), (cnt ## x ? ((time ## x / cnt ## x) % 1000l) : 0l)
DPF(" timeTotal: %3ld.%03ldsec (%3ld.%03ldsec) %d%%",SEC(Total));
DPF(" timeOther: %3ld.%03ldsec (%3ld.%03ldsec) %d%%",SEC(Other));
DPF(" timeTime: %3ld.%03ldsec (%3ld.%03ldsec) %d%%",SEC(Time));
DPF(" timeKey: %3ld.%03ldsec (%3ld.%03ldsec) %d%%",SEC(Key));
DPF(" timeRead: %3ld.%03ldsec (%3ld.%03ldsec) %d%%",SEC(Read));
DPF(" timeCopy: %3ld.%03ldsec (%3ld.%03ldsec) %d%%",SEC(Copy));
DPF(" timeDraw: %3ld.%03ldsec (%3ld.%03ldsec) %d%%",SEC(Draw));
DPF(" timeAudio: %3ld.%03ldsec (%3ld.%03ldsec) %d%%",SEC(Audio));
DPF(" timeDecompress: %3ld.%03ldsec (%3ld.%03ldsec) %d%%",SEC(Decomp));
DPF(" timeYield: %3ld.%03ldsec (%3ld.%03ldsec) %d%%",SEC(Yield));
DPF("");
DPF(" timeFree: %3ld.%03ldsec (%3ld.%03ldsec) %d%%",SEC(Free));
DPF("***********************************************************");
#endif
ReleaseDC(hwnd, hdc);
QueueRenderFini(qAVI);
QueueFini(qAVI);
InvalidateRect(hwnd, NULL, TRUE); // we've hosed their DC
// Tell where the movie stopped
return movieStart + l;
}
void FAR PASCAL aviStop(void)
{
gfPlaying = 0;
}
LONG FAR PASCAL aviTime(void)
{
if (gfPlaying && qAVI)
return QueueNow(qAVI);
else
return -1;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api LPVOID | AllocMem | try to allocate DOS memory (< 1Mb)
*
* @parm DWORD | dw | size in bytes
*
***************************************************************************/
static LPVOID AllocMem(DWORD dw)
{
#ifndef WIN32
/* Memory allocation internal routines */
extern DWORD FAR PASCAL GlobalDosAlloc(DWORD);
LPVOID p;
if (p = (LPVOID)MAKELONG(0, LOWORD(GlobalDosAlloc(dw))))
{
GlobalReAlloc((HANDLE)HIWORD((DWORD)p), 0, GMEM_MODIFY|GMEM_SHARE);
return p;
}
else
#endif
{
DPF("Couldn't get DOS Memory");
return GlobalLock(GlobalAlloc(GMEM_MOVEABLE|GMEM_SHARE, dw));
}
}
/*****************************************************************************
*
* dprintf() is called by the DPF macro if DEBUG is defined at compile time.
*
* The messages will be send to COM1: like any debug message. To
* enable debug output, add the following to WIN.INI :
*
* [debug]
* AVIView=1
*
****************************************************************************/
#ifdef DEBUG
#define MODNAME "AVIView"
void FAR cdecl dprintf(LPSTR szFormat, ...)
{
char ach[128];
va_list va;
static BOOL fDebug = -1;
if (fDebug == -1)
fDebug = GetProfileIntA("Debug", MODNAME, FALSE);
if (!fDebug)
return;
lstrcpyA(ach, MODNAME ": ");
va_start(va, szFormat);
wvsprintfA(ach+lstrlenA(ach),szFormat, va);
va_end(va);
lstrcatA(ach, "\r\n");
OutputDebugStringA(ach);
}
#endif