#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
