/****************************************************************************
 *
 *   capio.c
 *
 *   i/o routines for video capture
 *
 *   Microsoft Video for Windows Sample Capture Class
 *
 *   Copyright (c) 1992 - 1995 Microsoft Corporation.  All Rights Reserved.
 *
 *    You have a royalty-free right to use, modify, reproduce and
 *    distribute the Sample Files (and/or any modified version) in
 *    any way you find useful, provided that you agree that
 *    Microsoft has no warranty obligations or liability for any
 *    Sample Application Files which are modified.
 *
 ***************************************************************************/

//#define USE_AVIFILE 1
#define JMK_HACK_DONTWRITE TRUE

#define INC_OLE2
#pragma warning(disable:4103)
#include <windows.h>
#include <windowsx.h>
#include <win32.h>
#include <mmsystem.h>
#include <msvideo.h>
#include <drawdib.h>
#include <mmreg.h>
#include <mmddk.h>

#include "ivideo32.h"
#include "mmdebug.h"

#ifdef USE_ACM
#include <msacm.h>
#endif

#include <avifmt.h>
#include "avicap.h"
#include "avicapi.h"
#include "time.h"

extern UINT GetSizeOfWaveFormat (LPWAVEFORMATEX lpwf);
STATICFN BOOL IndexVideo (LPCAPSTREAM lpcs, DWORD dwSize, BOOL bKeyFrame);
STATICFN BOOL IndexAudio (LPCAPSTREAM lpcs, DWORD dwSize);

#ifdef _DEBUG
    #define DSTATUS(lpcs, sz) statusUpdateStatus(lpcs, IDS_CAP_INFO, (LPTSTR) TEXT(sz))
#else
    #define DSTATUS(lpcs, sz)
#endif

#ifdef USE_AVIFILE
VOID WINAPI AVIPreloadFat (LPCAPSTREAM lpcs)
{
    return;
}

// Add an index entry for an audio buffer
// dwSize is the size of data ONLY, not including the chunk or junk
// Returns: TRUE if index space is not exhausted
//
STATICFN BOOL IndexAudio (LPCAPSTREAM lpcs, DWORD dwSize)
{
    ++lpcs->dwWaveChunkCount;
    return TRUE;
}

// Add an index entry for a video frame
// dwSize is the size of data ONLY, not including the chunk or junk
// Returns: TRUE if index space is not exhausted
//
STATICFN BOOL IndexVideo (LPCAPSTREAM lpcs, DWORD dwSize, BOOL bKeyFrame)
{
    ++lpcs->dwVideoChunkCount;
    return TRUE;
}

STATICFN void AVIFileCleanup(LPCAPSTREAM lpcs)
{
    if (lpcs->paudio)
        AVIStreamClose(lpcs->paudio), lpcs->paudio = NULL;
    if (lpcs->pvideo)
        AVIStreamClose(lpcs->pvideo), lpcs->pvideo = NULL;
    if (lpcs->pavifile)
        AVIFileClose(lpcs->pavifile), lpcs->pavifile = NULL;
}

/*
 * CapFileInit
 *
 *       Perform all initialization required to write a capture file.
 *
 *       We take a slightly strange approach: We don't write
 *       out the header until we're done capturing.  For now,
 *       we just seek 2K into the file, which is where all of
 *       the real data will go.
 *
 *       When we're done, we'll come back and write out the header,
 *       because then we'll know all of the values we need.
 *
 *      Also allocate and init the index.
 */

BOOL CapFileInit (
    LPCAPSTREAM lpcs)
{
    AVISTREAMINFOW si;
    LPBYTE ptr = NULL;
    UINT   cksize;
    RGBQUAD FAR * prgb;
    PALETTEENTRY aPalEntry[256];
    LPBITMAPINFO lpBitsInfoOut;    // Possibly compressed output format
    LONG lRet;
    UINT ii;

    // No special video format given -- use the default
    //
    lpBitsInfoOut = lpcs->CompVars.lpbiOut;
    if (lpcs->CompVars.hic == NULL)
        lpBitsInfoOut = lpcs->lpBitsInfo;

    // use avifile to access the data

    // create the avifile object, create a video and audio stream and
    // set the format for each stream.


    assert(lpcs->pavifile == NULL);

    /* if the capture file has not been set then error */
    if (!(*lpcs->achFile))
        goto error_exit;

    // !!! how to avoid truncating the file if already created ?

    lRet = AVIFileOpen(&lpcs->pavifile,
                    lpcs->achFile,
                    OF_WRITE | OF_CREATE,
                    NULL);
    if (lRet || !lpcs->pavifile)
        goto error_exit;

    // create video stream
    ZeroMemory (&si, sizeof(si));
    si.fccType = streamtypeVIDEO;

    if (lpcs->CompVars.hic)
        si.fccHandler = lpcs->CompVars.fccHandler;
    else
        si.fccHandler = lpBitsInfoOut->bmiHeader.biCompression;

    // A bit of history...
    // In VFW 1.0, we set fccHandler to 0 for BI_RLE8 formats
    // as a kludge to make Mplayer and Videdit play the files.
    // Just prior to 1.1 release, we found this broke Premiere,
    // so now (after AVICAP beta is on Compuserve), we change the
    // fccHandler to "MRLE".  Just ask Todd...
    // And now, at RC1, we change it again to "RLE ", Just ask Todd...
    if (si.fccHandler == BI_RLE8)
        si.fccHandler = mmioFOURCC('R', 'L', 'E', ' ');

    // !!!need to change this after capture
    si.dwScale = lpcs->sCapParms.dwRequestMicroSecPerFrame;



    si.dwRate = 1000000L;
    si.dwStart = 0L;
    si.dwQuality = (DWORD) -1L;         /* !!! ICQUALITY_DEFAULT */
    si.dwSampleSize = 0L;

    lRet = AVIFileCreateStream(lpcs->pavifile, &lpcs->pvideo, &si);
    if (lRet || !lpcs->pvideo)
        goto error_exit;

    // set format of video stream
    //  !!! dont write palette for full color?
    if (lpBitsInfoOut->bmiHeader.biBitCount > 8)
        lpBitsInfoOut->bmiHeader.biClrUsed = 0;

    // need to alloc a single block that we can fill with hdr + palette
    cksize = lpBitsInfoOut->bmiHeader.biSize
             + lpBitsInfoOut->bmiHeader.biClrUsed * sizeof(RGBQUAD);
    ptr = GlobalAllocPtr(GPTR, cksize);
    if (!ptr)
        goto error_exit;

    CopyMemory (ptr, (LPBYTE)&lpBitsInfoOut->bmiHeader,
                lpBitsInfoOut->bmiHeader.biSize);
    prgb = (RGBQUAD FAR *) &ptr[lpBitsInfoOut->bmiHeader.biSize];

    if (lpBitsInfoOut->bmiHeader.biClrUsed > 0) {
        // Get Palette info
        UINT nPalEntries = GetPaletteEntries(lpcs->hPalCurrent, 0,
                                             lpBitsInfoOut->bmiHeader.biClrUsed,
                                             aPalEntry);

        if (nPalEntries != lpBitsInfoOut->bmiHeader.biClrUsed)
            goto error_exit;

        for (ii = 0; ii < lpBitsInfoOut->bmiHeader.biClrUsed; ++ii)  {
            prgb[ii].rgbRed = aPalEntry[ii].peRed;
            prgb[ii].rgbGreen = aPalEntry[ii].peGreen;
            prgb[ii].rgbBlue = aPalEntry[ii].peBlue;
        }
    }
    if (AVIStreamSetFormat(lpcs->pvideo, 0, ptr, cksize))
        goto error_exit;

    GlobalFreePtr(ptr), ptr = NULL;


    // create audio stream if sound capture enabled
    if (lpcs->sCapParms.fCaptureAudio) {

         ZeroMemory (&si, sizeof(si));
         si.fccType = streamtypeAUDIO;
         si.fccHandler = 0L;
         si.dwScale = lpcs->lpWaveFormat->nBlockAlign;
         si.dwRate = lpcs->lpWaveFormat->nAvgBytesPerSec;
         si.dwStart = 0L;
         si.dwLength =  lpcs->dwWaveBytes / lpcs->lpWaveFormat->nBlockAlign;
         si.dwQuality = (DWORD)-1L;    /* !!! ICQUALITY_DEFAULT */
         si.dwSampleSize = lpcs->lpWaveFormat->nBlockAlign;

        lRet = AVIFileCreateStream(lpcs->pavifile, &lpcs->paudio, &si);
        if (lRet || !lpcs->paudio)
            goto error_exit;

        // write wave stream format

        cksize = GetSizeOfWaveFormat (lpcs->lpWaveFormat);

        if (AVIStreamSetFormat(lpcs->paudio, 0, lpcs->lpWaveFormat, cksize))
            goto error_exit;
    }

    // start streaming
    //
    // parameters are random for now, and are not used at all by current impl.
    // probably covered by above call but you never know
    //
    AVIStreamBeginStreaming(lpcs->pvideo, 0, 32000, 1000);
    if (lpcs->sCapParms.fCaptureAudio)
        AVIStreamBeginStreaming(lpcs->paudio, 0, 32000, 1000);

    // this is used for timing calcs, not just indexing
    //
    lpcs->dwVideoChunkCount = 0;
    lpcs->dwWaveChunkCount  = 0;

    // !!! write info chunks here

    return TRUE;

error_exit:
    if (ptr) {
        GlobalFreePtr(ptr); ptr = NULL;
    }
    AVIFileCleanup (lpcs);
    return FALSE;
}

/*
 * AVIFileFini
 *
 *       Write out the index, deallocate the index, and close the file.
 *
 */

BOOL AVIFileFini (LPCAPSTREAM lpcs, BOOL fWroteJunkChunks, BOOL fAbort)
{
    AVISTREAMINFOW si;

    DPF("AVICap32:    Start of AVIFileFini\n");

    AVIStreamEndStreaming(lpcs->pvideo);
    if (lpcs->sCapParms.fCaptureAudio)
        AVIStreamEndStreaming(lpcs->paudio);

    // if we got a good file, allow editing of it
    lpcs->fFileCaptured = !fAbort;

    // -----------------------------------------------------------
    // adjust audio & video streams to be the same length
    // -----------------------------------------------------------

   #if 0 // old technique - match video to audio unconditionally
    // share the captured frames out evenly over the captured audio

    if (lpcs->sCapParms.fCaptureAudio && lpcs->dwVideoChunkCount &&
            (lpcs->dwWaveBytes > 0)) {

         /* HACK HACK */
         /* Set rate that was captured based on length of audio data */

        lpcs->dwActualMicroSecPerFrame = (DWORD)
            MulDiv((LONG)lpcs->dwWaveBytes,
                     1000000,
                     (LONG)(lpcs->lpWaveFormat->nAvgBytesPerSec * lpcs->dwVideoChunkCount));
    } else {
        lpcs->dwActualMicroSecPerFrame = lpcs->sCapParms.dwRequestMicroSecPerFrame;
    }
   #else // new technique for stream length munging
    //
    // Init a value in case we're not capturing audio
    //
    lpcs->dwActualMicroSecPerFrame = lpcs->sCapParms.dwRequestMicroSecPerFrame;

    switch (lpcs->sCapParms.AVStreamMaster) {
        case AVSTREAMMASTER_NONE:
            lpcs->dwActualMicroSecPerFrame = lpcs->sCapParms.dwRequestMicroSecPerFrame;
            break;

        case AVSTREAMMASTER_AUDIO:
        default:
            // VFW 1.0 and 1.1 ALWAYS munged frame rate to match audio
            // duration.
            if (lpcs->sCapParms.fCaptureAudio && lpcs->dwVideoChunkCount) {
                // Modify the video framerate based on audio duration
                lpcs->dwActualMicroSecPerFrame = (DWORD)
                    ((double)lpcs->dwWaveBytes * 1000000. /
                    ((double)lpcs->lpWaveFormat->nAvgBytesPerSec *
                    lpcs->dwVideoChunkCount + 0.5));
            }
            break;
    }
   #endif

    // ------------------------------------------------------------
    // write corrected stream timing back to the file
    // ------------------------------------------------------------

   #ifdef CHICAGO
    AVIStreamInfo (lpcs->pvideo, (LPAVISTREAMINFOA) &si, sizeof(si));
   #else
    AVIStreamInfo (lpcs->pvideo, &si, sizeof(si));
   #endif

    si.dwRate = 1000000L;
    si.dwScale = lpcs->dwActualMicroSecPerFrame;

    // no api for this- have to call the member directly!
    //
    lpcs->pvideo->lpVtbl->SetInfo(lpcs->pvideo, &si, sizeof(si));

    // Add the info chunks
    // This includes the capture card driver name, client app, date and time

    if (lpcs->lpInfoChunks) {
        LPBYTE  lpInfo;
        DWORD   cbData;

        lpInfo = lpcs->lpInfoChunks;
        while (lpInfo < (LPBYTE) lpcs->lpInfoChunks + lpcs->cbInfoChunks) {
            cbData = * (LPDWORD) (lpInfo + sizeof(DWORD));
            AVIFileWriteData (lpcs->pavifile,
                        (DWORD) * (LPDWORD) lpInfo,        // FOURCC
                        lpInfo + sizeof (DWORD) * 2,       // lpData
                        cbData);                           // cbData
            lpInfo += cbData + sizeof (DWORD) * 2;
        }
    }


    AVIFileCleanup(lpcs);

    return lpcs->fFileCaptured;
}

//
// Prepends dummy frame entries to the current valid video frame.
// Bumps the index, but does not actually trigger a write operation.
// nCount is a count of the number of frames to write
// Returns: TRUE on a successful write

BOOL WINAPI AVIWriteDummyFrames (
    LPCAPSTREAM lpcs,
    UINT        nCount,
    LPUINT      lpuError,
    LPBOOL      lpbPending)
{
    LONG lRet;

    lpcs->dwVideoChunkCount += nCount;

    lRet = AVIStreamWrite(lpcs->pvideo,
                    -1,             // current position
                    nCount,         // this many samples
                    NULL,           // no actual data
                    0,              // no data
                    0,              // not keyframe
                    NULL, NULL);    // no return of samples or bytes
    *lpbPending = FALSE;
    *lpuError = 0;
    if (lRet)
        *lpuError = IDS_CAP_FILE_WRITE_ERROR;
    return !(*lpuError);
}

// Writes compressed or uncompressed frames to the AVI file
// returns TRUE if no error, FALSE if end of file.

BOOL WINAPI AVIWriteVideoFrame (
    LPCAPSTREAM lpcs,
    LPBYTE      lpData,
    DWORD       dwBytesUsed,
    BOOL        fKeyFrame,
    UINT        uIndex,
    UINT        nDropped,
    LPUINT      lpuError,
    LPBOOL      lpbPending)
{
    LONG   lRet;

    lRet = AVIStreamWrite(lpcs->pvideo,     // write to video stream
                    -1,                     // next sample
                    1,                      // 1 sample only
                    lpData,                 // video buffer (no riff header)
                    dwBytesUsed,            // length of data
                    fKeyFrame ? AVIIF_KEYFRAME : 0,
                    NULL, NULL);    // no return of sample or byte count

    *lpbPending = FALSE;
    *lpuError = 0;
    if (lRet)
    {
        dprintf("AVIStreamWrite returned 0x%x", lRet);
        *lpuError = IDS_CAP_FILE_WRITE_ERROR;
    }
    else
    {
        ++lpcs->dwVideoChunkCount;
        if (nDropped)
            AVIWriteDummyFrames (lpcs, nDropped, lpuError, lpbPending);
    }
    return !(*lpuError);
}

BOOL WINAPI AVIWriteAudio (
    LPCAPSTREAM lpcs,
    LPWAVEHDR   lpWaveHdr,
    UINT        uIndex,
    LPUINT      lpuError,
    LPBOOL      lpbPending)
{
    LONG lRet;

    lRet = AVIStreamWrite(lpcs->paudio,
                -1,                 // next sample
                lpWaveHdr->dwBytesRecorded /
                    lpcs->lpWaveFormat->nBlockAlign,    // nr samples
                lpWaveHdr->lpData,
                lpWaveHdr->dwBytesRecorded,
                0,
                NULL,
                NULL);

    *lpbPending = FALSE;
    *lpuError = 0;
    if (lRet)
    {
        dprintf("AVIStreamWrite returned 0x%x", lRet);
        *lpuError = IDS_CAP_FILE_WRITE_ERROR;
    }
    else
        ++lpcs->dwWaveChunkCount;

    return !(*lpuError);
}

#else //---------------- ! using Avifile ----------------------------

// The following are anded with the size in the index
#define IS_AUDIO_CHUNK        0x80000000
#define IS_KEYFRAME_CHUNK     0x40000000
#define IS_DUMMY_CHUNK        0x20000000
#define IS_GRANULAR_CHUNK     0x10000000
#define INDEX_MASK  (IS_AUDIO_CHUNK | IS_KEYFRAME_CHUNK | IS_DUMMY_CHUNK | IS_GRANULAR_CHUNK)


// Add an index entry for a video frame
// dwSize is the size of data ONLY, not including the chunk or junk
// Returns: TRUE if index space is not exhausted
//
STATICFN BOOL IndexVideo (LPCAPSTREAM lpcs, DWORD dwSize, BOOL bKeyFrame)
{
    if (lpcs->dwIndex < lpcs->sCapParms.dwIndexSize) {
        *lpcs->lpdwIndexEntry = dwSize | (bKeyFrame ? IS_KEYFRAME_CHUNK : 0);
        ++lpcs->lpdwIndexEntry;
        ++lpcs->dwIndex;
        ++lpcs->dwVideoChunkCount;
        return TRUE;
    }
    dprintf("\n***WARNING*** Indexvideo space exhausted\n");
    return FALSE;
}

// Add an index entry for an audio buffer
// dwSize is the size of data ONLY, not including the chunk or junk
// Returns: TRUE if index space is not exhausted
//
STATICFN BOOL IndexAudio (LPCAPSTREAM lpcs, DWORD dwSize)
{
    if (lpcs->dwIndex < lpcs->sCapParms.dwIndexSize) {
       *lpcs->lpdwIndexEntry = dwSize | IS_AUDIO_CHUNK;
       ++lpcs->lpdwIndexEntry;
       ++lpcs->dwIndex;
       ++lpcs->dwWaveChunkCount;
       return TRUE;
    }
    dprintf("\n***WARNING*** Indexaudio space exhausted\n");
    return FALSE;
}

DWORD CalcWaveBufferSize (LPCAPSTREAM lpcs)
{
    DWORD dw;

    if (!lpcs->lpWaveFormat)
        return 0L;

    // at least .5 second
    dw = lpcs->lpWaveFormat->nAvgBytesPerSec / 2;
    if (lpcs->sCapParms.wChunkGranularity) {
        if (dw % lpcs->sCapParms.wChunkGranularity) {
            dw += lpcs->sCapParms.wChunkGranularity -
                dw % lpcs->sCapParms.wChunkGranularity;
        }
    }
    dw = max ((1024L * 16), dw);                // at least 16K
    dw -= sizeof(RIFF);

    dprintf("Wave buffer size = %ld", dw);
    return dw;
}

/*
 * AVIPreloadFat
 *
 *   Force FAT for this file to be loaded into the FAT cache
 *
 */

VOID WINAPI AVIPreloadFat (LPCAPSTREAM lpcs)
{
    DWORD dw;
   #ifdef CHICAGO
    DWORD dwPos;

    assert (lpcs->lpDropFrame);

    // save the current file pointer then seek to the end of the file
    //
    dwPos = SetFilePointer (lpcs->hFile, 0, NULL, FILE_CURRENT);
    dw = SetFilePointer (lpcs->hFile, 0, NULL, FILE_END);
    if ((dw == (DWORD)-1) || (dw < lpcs->dwBytesPerSector)) {
        // put the file pointer back to where it was
        SetFilePointer (lpcs->hFile, dwPos, NULL, FILE_BEGIN);
        return;
    }

    // read the last sector of the file, just to force
    // the fat for the file to be loaded
    //
    ReadFile (lpcs->hFile, lpcs->lpDropFrame, lpcs->dwBytesPerSector, &dw, NULL);

    // put the file pointer back to where it was
    //
    SetFilePointer (lpcs->hFile, dwPos, NULL, FILE_BEGIN);
   #else
    // Load all the FAT information.   On NT this is sufficient for FAT
    // files.  On NTFS partitiions there is no way we can read in all the
    // mapping information.
    GetFileSize(lpcs->hFile, &dw);
   #endif
}


#ifdef JMK_HACK_DONTWRITE
static BOOL bDontWrite;
#endif

// Write data to the capture file
// Returns: TRUE on a successful write
//
UINT NEAR PASCAL AVIWrite (
    LPCAPSTREAM lpcs,
    LPVOID      pbuf,
    DWORD       dwSize,
    UINT        uIndex, // index of header for this buffer, -1 for step capture
    UINT        uType,
    LPBOOL      lpbPending)
{
    DWORD dwWritten;
    DWORD dwGran;

    // the buffer must be sector aligned if using non-buffered IO
    // and the size must be at least word aligned
    // uIndex == -1 if this is a dummy frame write
    // uIndex == Index into alpVideoHdr OR index in alpWaveHdr based on uType
    //
    assert (!lpcs->fUsingNonBufferedIO || (!((DWORD)pbuf & (lpcs->dwBytesPerSector - 1))));
    assert (!(dwSize & 1));
    assert (dwSize);

    assert (*lpbPending == FALSE);

    // if we are doing non-buffered io, we need to pad each write
    // to an even multiple of sector size bytes, we do this by adding
    // a junk riff chunk into the write buffer after dwSize bytes
    //
    dwGran = lpcs->sCapParms.wChunkGranularity;
    if (lpcs->fUsingNonBufferedIO)
       dwGran = max (lpcs->dwBytesPerSector,
                (DWORD) lpcs->sCapParms.wChunkGranularity);

    assert (dwGran);

    if (dwSize % dwGran)
    {
        DWORD dwSizeT = dwGran - (dwSize % dwGran);
        LPRIFF priff = (LPRIFF)((LPBYTE)pbuf + dwSize + (dwSize & 1));

        if (dwSizeT < sizeof(RIFF))
            dwSizeT += dwGran;

        // add the junk riff chunk to the end of the buffer
        //
        priff->dwType = ckidAVIPADDING;
        priff->dwSize = dwSizeT - sizeof(RIFF);
        dwSize += dwSizeT;
    }

   #ifdef _DEBUG
    if (dwSize)
    {
        volatile BYTE bt;
        AuxDebugEx (8, DEBUGLINE "touch test of AviWrite buffer %08X\r\n", pbuf);
        bt = ((LPBYTE)pbuf)[dwSize-1];
    }

    // List all of the RIFF chunks within the block being written
    //
    dwWritten = 0;
    while (dwWritten < dwSize)
    {
        LPRIFF priff = (LPVOID)((LPBYTE)pbuf + dwWritten);
        AuxDebugEx (4, DEBUGLINE "RIFF=%.4s size=%08X\r\n",
                   &priff->dwType, priff->dwSize);
        dwWritten += priff->dwSize + sizeof(RIFF);
    }
   #endif


    // BUGBUG, Remove the following line when done performance testing
   #ifdef JMK_HACK_DONTWRITE
    if (bDontWrite)
        return 0;
   #endif

    if (lpcs->pAsync)
    {
        struct _avi_async * lpah = &lpcs->pAsync[lpcs->iLastAsync];
        UINT  iLastAsync;

        // set iLastAsync to point to what lpcs->iLastAsync
        // would be if we were to increment it.  If we end up
        // with an i/o that does not complete synchronously
        // we will then update lpcs->iLastAsync so that we can
        // remember to check for completion later
        //
        if ((iLastAsync = lpcs->iLastAsync+1) >= lpcs->iNumAsync)
            iLastAsync = 0;

        // is the async buffer that we are trying to use
        // already in use?
        //
        if (iLastAsync == lpcs->iNextAsync) {
	    AuxDebugEx(1, DEBUGLINE "async buffer already in use\r\n");
            return IDS_CAP_FILE_WRITE_ERROR;
	}
        assert (!lpah->uType);

        // init the async buffer with the info that we will need
        // to release the buffer when the io is complete
        //
        ZeroMemory (&lpah->ovl, sizeof(lpah->ovl));
        lpah->ovl.hEvent = lpcs->hCaptureEvent;
        lpah->ovl.Offset = lpcs->dwAsyncWriteOffset;
        // attempt an async write.  if WriteFile fails, we then
        // need to check if it's a real failure, or just an instance
        // of delayed completion.  if delayed completion, we fill out
        // the lpah structure so that we know what buffer to re-use
        // when the io finally completes.
        //
	if ( ! WriteFile (lpcs->hFile, pbuf, dwSize, &dwWritten, &lpah->ovl))
        {
            if (GetLastError() == ERROR_IO_PENDING)
            {

                // if we are passed a index of -1, that means that
                // this buffer is not associated with any entry in the
                // header array.  in this case, we must have the io complete
                // before we return from this function.
               //
               if (uIndex == (UINT)-1)
               {
                   while (1) {
                    if ( ! GetOverlappedResult (lpcs->hFile, &lpah->ovl, &dwWritten, TRUE))
                    {
                        AuxDebugEx (0, DEBUGLINE "WriteFile failed %d\r\n", GetLastError());
                        return IDS_CAP_FILE_WRITE_ERROR;
                    }
			// if Internal is non-zero, the io is still pending
	    // NOTE: this needs to be changed as soon as possible.
	    //	The current state of play means that GetOverlappedResult CAN return
	    //	something other than ERROR_IO_COMPLETE and the IO will still be
	    //	pending.  This causes us to make the wrong decision.
                        if (lpah->ovl.Internal != 0) {
			    Sleep(100);  // release some cycles
			}
			else
			    break;
                   }
                   //#pragma message(SQUAWK "get rid of this hack when GetOverlappedResult is fixed!")
                }
                else
                {
                    // io is begun, but not yet completed. so setup info in
                    // the pending io array so that we can check later for completion
                    //
                    *lpbPending = TRUE;
                    lpah->uType = uType;
                    lpah->uIndex = uIndex;
		    AuxDebugEx(2, DEBUGLINE "IOPending... iLastAsync was %d, will be %d, uIndex=%d, Event=%d\r\n",lpcs->iLastAsync , iLastAsync, uIndex, lpah->ovl.hEvent);
                    lpcs->iLastAsync = iLastAsync;
                }
            }
            else
            {
                AuxDebugEx (0, DEBUGLINE "WriteFile failed %d\r\n", GetLastError());
                return IDS_CAP_FILE_WRITE_ERROR;
	    }
	}

        // we get to here only when the io succeeds or is pending
        // so update the seek offset for use in the next write operation
        //
        lpcs->dwAsyncWriteOffset += dwSize;
    }
    else
    {
	// We are writing synchronously to the file
        if (!WriteFile (lpcs->hFile, pbuf, dwSize, &dwWritten, NULL) ||
            !(dwWritten == dwSize))
            return IDS_CAP_FILE_WRITE_ERROR;
    }

    return 0;
}

/*
 * CapFileInit
 *
 *       Perform all initialization required to write a capture file.
 *
 *       We take a slightly strange approach: We don't write
 *       out the header until we're done capturing.  For now,
 *       we just seek 2K into the file, which is where all of
 *       the real data will go.
 *
 *       When we're done, we'll come back and write out the header,
 *       because then we'll know all of the values we need.
 *
 *      Also allocate and init the index.
 */
BOOL CapFileInit (LPCAPSTREAM lpcs)
{
    LONG l;
    LPBITMAPINFO lpBitsInfoOut;    // Possibly compressed output format
    DWORD dwOpenFlags;

    // No special video format given -- use the default
    lpBitsInfoOut = lpcs->CompVars.lpbiOut;
    if (lpcs->CompVars.hic == NULL)
        lpBitsInfoOut = lpcs->lpBitsInfo;


    assert (lpcs->hmmio == NULL);   // Should never have a file handle on entry

    // if the capture file has not been set then set it now
    if (!(*lpcs->achFile))
         goto INIT_FILE_OPEN_ERROR;

    // Get the Bytes per sector for the drive
    {
        DWORD dwSectorsPerCluster;
        DWORD dwFreeClusters;
        DWORD dwClusters;
        TCHAR szFullPathName[MAX_PATH];
        LPTSTR pszFilePart;

        GetFullPathName (lpcs->achFile,
                NUMELMS (szFullPathName),
                szFullPathName,
                &pszFilePart);

        if (szFullPathName[1] == TEXT(':') && szFullPathName[2] == TEXT('\\')) {
            szFullPathName[3] = TEXT('\0');  // Terminate after "x:\"

            GetDiskFreeSpace (szFullPathName,
                   &dwSectorsPerCluster,
                   &lpcs->dwBytesPerSector,
                   &dwFreeClusters,
                   &dwClusters);
            AuxDebugEx (3, DEBUGLINE "BytesPerSector=%d\r\n",
                lpcs->dwBytesPerSector);
        }
        else {
            // This handles cases where we do not have a "x:\" filename
            // Principally this will be "\\server\name\path..."
            lpcs->dwBytesPerSector = DEFAULT_BYTESPERSECTOR;
            AuxDebugEx (3, DEBUGLINE "FullPath=%s\r\n", szFullPathName);
            AuxDebugEx (3, DEBUGLINE "GetFullPath failed, Forcing dwBytesPerSector to %d\r\n",DEFAULT_BYTESPERSECTOR);
        }

    // bytes per sector must be non-zero and a power of two
    //
    assert (lpcs->dwBytesPerSector);
    assert (!(lpcs->dwBytesPerSector & (lpcs->dwBytesPerSector-1)));
    }

   #ifdef ZERO_THE_FILE_FOR_TESTING
    {
    char c[64 * 1024];
    DWORD dwSize;
    DWORD dwBW;
    // Open the file just to zero it

    lpcs->hFile = CreateFile (lpcs->achFile,
                        GENERIC_READ | GENERIC_WRITE,
                        FILE_SHARE_READ,
                        NULL,
                        OPEN_ALWAYS,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL);

    if (lpcs->hFile == INVALID_HANDLE_VALUE) {
        lpcs->hFile = 0;
        goto INIT_FILE_OPEN_ERROR;
    }

    ZeroMemory (c, sizeof(c));
    SetFilePointer (lpcs->hFile, 0, NULL, FILE_BEGIN);
    dwSize = GetFileSize (lpcs->hFile, NULL);

    while (SetFilePointer (lpcs->hFile, 0, NULL, FILE_CURRENT) < dwSize)
            WriteFile (lpcs->hFile, c, sizeof(c), &dwBW, NULL);
    }

    CloseHandle(lpcs->hFile);  // Close the "normal" open
   #endif

    // We can use non-buffered I/O if the ChunkGranularity is
    // a multiple of BytesPerSector.  Better check that wChunkGranularity
    // has indeed been set

    if (0 == lpcs->sCapParms.wChunkGranularity)
        lpcs->sCapParms.wChunkGranularity = lpcs->dwBytesPerSector;

    lpcs->fUsingNonBufferedIO =
            (lpcs->sCapParms.wChunkGranularity >= lpcs->dwBytesPerSector) &&
           ((lpcs->sCapParms.wChunkGranularity % lpcs->dwBytesPerSector) == 0) &&
            (lpcs->CompVars.hic == NULL) &&
            (!(lpcs->fCaptureFlags & CAP_fStepCapturingNow)) &&
            (!(lpcs->fCaptureFlags & CAP_fFrameCapturingNow));

    AuxDebugEx (3, DEBUGLINE "fUsingNonBufferedIO=%d\r\n", lpcs->fUsingNonBufferedIO);

    // setup CreateFile flags based on whether we are using
    // non-buffered io and/or overlapped io
    //
    dwOpenFlags = FILE_ATTRIBUTE_NORMAL;
    if (lpcs->fUsingNonBufferedIO)
    {
        dwOpenFlags |= FILE_FLAG_NO_BUFFERING;
       #ifdef CHICAGO
        #pragma message (SQUAWK "find a better way to set AsyncIO flag")
        if (GetProfileIntA ("Avicap32", "AsyncIO", FALSE))
       #else
            // give a way to override the async default option.
            if (!GetProfileIntA ("Avicap32", "AsyncIO", TRUE)) {
		AuxDebugEx (2, DEBUGLINE "NOT doing Async IO\r\n");
	    } else
       #endif
        {
            AuxDebugEx (3, DEBUGLINE "Doing Async IO\r\n");
            dwOpenFlags |= FILE_FLAG_OVERLAPPED;

            // We are requested to do async io.  Allocate an array
            // of async io headers and initialize the async io fields
            // in the CAPSTREAM structure
            //
            {
                UINT iNumAsync = NUMELMS(lpcs->alpVideoHdr) + NUMELMS(lpcs->alpWaveHdr) + 2;
		// This is quite a lot of buffers.  Perhaps we should limit
		// ourselves to lpcs->iNumVideo and lpcs->iNumAudio EXCEPT
		// these fields have not yet been set up.  We would need
		// to look in the cap stream structure to get the information.
		// It is simpler to assume the maximum numbers.
                lpcs->dwAsyncWriteOffset = lpcs->dwAVIHdrSize;
                lpcs->iNextAsync = lpcs->iLastAsync = 0;
                lpcs->pAsync = GlobalAllocPtr (GMEM_MOVEABLE | GMEM_ZEROINIT,
                                               sizeof(*lpcs->pAsync) * iNumAsync);
                if (lpcs->pAsync) {
                    lpcs->iNumAsync = iNumAsync;
                } else {
                    // cannot allocate the memory.  Go synchronous
                    dprintf("Failed to allocate async buffers");
                    dwOpenFlags &= ~(FILE_FLAG_OVERLAPPED);
		}
            }
        }
    }

    // Open the capture file, using Non Buffered I/O
    // if possible, given sector size, and buffer granularity
    //
    lpcs->hFile = CreateFile (lpcs->achFile,
                    GENERIC_READ | GENERIC_WRITE,
                    FILE_SHARE_READ,
                    NULL,
                    OPEN_ALWAYS,
                    dwOpenFlags,
                    NULL);

    if (lpcs->hFile == INVALID_HANDLE_VALUE) {
        lpcs->hFile = 0;
        goto INIT_FILE_OPEN_ERROR;
    }

    // BUGBUG, Remove the following line when done performance testing
   #ifdef JMK_HACK_DONTWRITE
    bDontWrite = GetProfileIntA("AVICAP32", "DontWrite", FALSE);
   #endif

    // Seek to a multiple of ChunkGranularity + AVIHEADERSIZE.
    // This is the point at which we'll start writing
    // Later, we'll come back and fill in the AVI header and index.

    // l is zero for standard wave and video formats
    l = (GetSizeOfWaveFormat ((LPWAVEFORMATEX) lpcs->lpWaveFormat) -
                sizeof (PCMWAVEFORMAT)) +
                (lpBitsInfoOut->bmiHeader.biSize -
                sizeof (BITMAPINFOHEADER));

    // (2K + size of wave and video stream headers) rounded to next 2K
    lpcs->dwAVIHdrSize = AVI_HEADERSIZE +
        (((lpcs->cbInfoChunks + l + lpcs->sCapParms.wChunkGranularity - 1)
        / lpcs->sCapParms.wChunkGranularity) * lpcs->sCapParms.wChunkGranularity);


    dprintf("AVIHdrSize = %ld", lpcs->dwAVIHdrSize);

    SetFilePointer (lpcs->hFile, lpcs->dwAVIHdrSize, NULL, FILE_BEGIN);
    if (lpcs->pAsync)
        lpcs->dwAsyncWriteOffset = lpcs->dwAVIHdrSize;

    // do all Index allocations
    if (!InitIndex (lpcs))
        CloseHandle (lpcs->hFile), lpcs->hFile = 0;

    lpcs->dwVideoChunkCount = 0;
    lpcs->dwWaveChunkCount = 0;

INIT_FILE_OPEN_ERROR:
    if (lpcs->hFile) {
	return(TRUE);
    }
    if (lpcs->pAsync) {
	GlobalFreePtr(lpcs->pAsync), lpcs->pAsync=NULL;
    }
    return (FALSE);
}

///////////////////////////////////////////////////////////////////////////
//  The index array is used to record the positions
//  of every chunk in the RIFF (avi) file.
//
//  what this array is:
//
//      each entry contains the size of the data
//      high order bits encode the type of data (audio / video)
//      and whether the video chunk is a key frame, dropped frame, etc.
///////////////////////////////////////////////////////////////////////////

// Allocate the index table
// Returns: TRUE if index can be allocated
//
BOOL InitIndex (LPCAPSTREAM lpcs)
{
    lpcs->dwIndex = 0;

    // we assume that we have not already allocated an index
    //
    assert (lpcs->lpdwIndexStart == NULL);

    // Limit index size between 1 minute at 30fps and 3 hours at 30fps
    lpcs->sCapParms.dwIndexSize = max (lpcs->sCapParms.dwIndexSize, 1800);
    lpcs->sCapParms.dwIndexSize = min (lpcs->sCapParms.dwIndexSize, 324000L);
    dprintf("Max Index Size = %ld", lpcs->sCapParms.dwIndexSize);

    if (lpcs->hIndex = GlobalAlloc (GMEM_MOVEABLE,
                lpcs->sCapParms.dwIndexSize * sizeof (DWORD))) {
        if (lpcs->lpdwIndexEntry =
            lpcs->lpdwIndexStart = (LPDWORD)GlobalLock (lpcs->hIndex))
            return TRUE;        // Success

        GlobalFree (lpcs->hIndex);
	lpcs->hIndex = NULL;
    }
    lpcs->lpdwIndexStart = NULL;
    return FALSE;
}

// Deallocate the index table
//
void FiniIndex (LPCAPSTREAM lpcs)
{
    if (lpcs->hIndex) {
        if (lpcs->lpdwIndexStart)
            GlobalUnlock (lpcs->hIndex);
        GlobalFree (lpcs->hIndex);
	lpcs->hIndex = NULL;
    }
    lpcs->lpdwIndexStart = NULL;
}



// Write out the index at the end of the capture file.
// The single frame capture methods do not append
// JunkChunks!  Audio chunks also now may have junk appended.
//
BOOL WriteIndex (LPCAPSTREAM lpcs, BOOL fJunkChunkWritten)
{
    BOOL  fChunkIsAudio;
    BOOL  fChunkIsKeyFrame;
    BOOL  fChunkIsDummy;
    BOOL  fChunkIsGranular;
    DWORD dwIndex;
    DWORD dw;
    DWORD dwJunk;
    DWORD off;
    AVIINDEXENTRY   avii;
    MMCKINFO    ck;
    LPDWORD lpdw;
    DWORD   dwGran;

    // determine which granularity (if any) to use
    // when calulating junk appended
    //
    dwGran = 0;
    if (fJunkChunkWritten)
    {
        dwGran = lpcs->sCapParms.wChunkGranularity;
        if (lpcs->fUsingNonBufferedIO)
           dwGran = max (lpcs->dwBytesPerSector, dwGran);
    }


    if (lpcs->dwIndex > lpcs->sCapParms.dwIndexSize)
        return TRUE;

    off        = lpcs->dwAVIHdrSize;

    ck.cksize  = 0;
    ck.ckid    = ckidAVINEWINDEX;
    ck.fccType = 0;

    if (mmioCreateChunk(lpcs->hmmio,&ck,0)) {
	dprintf("Failed to create chunk for index");
        return FALSE;
    }

    lpdw = lpcs->lpdwIndexStart;
    for (dwIndex= 0; dwIndex < lpcs->dwIndex; dwIndex++) {

        dw = *lpdw++;

        fChunkIsAudio      = (BOOL) ((dw & IS_AUDIO_CHUNK) != 0);
        fChunkIsKeyFrame   = (BOOL) ((dw & IS_KEYFRAME_CHUNK) != 0);
        fChunkIsDummy      = (BOOL) ((dw & IS_DUMMY_CHUNK) != 0);
        fChunkIsGranular   = (BOOL) ((dw & IS_GRANULAR_CHUNK) != 0);
        dw &= ~(INDEX_MASK);

        if (fChunkIsAudio) {
            avii.ckid         = MAKEAVICKID(cktypeWAVEbytes, 1);
            avii.dwFlags      = 0;
        } else {
            if (lpcs->lpBitsInfo->bmiHeader.biCompression == BI_RLE8)
                avii.ckid         = MAKEAVICKID(cktypeDIBcompressed, 0);
            else
                avii.ckid         = MAKEAVICKID(cktypeDIBbits, 0);
            avii.dwFlags      = fChunkIsKeyFrame ? AVIIF_KEYFRAME : 0;
        }
        avii.dwChunkLength    = dw;
        avii.dwChunkOffset    = off;

        if (mmioWrite(lpcs->hmmio, (LPVOID)&avii, sizeof(avii)) != sizeof(avii)) {
	    dprintf("Failed to write index chunk %d", dwIndex);
            return FALSE;
	}

        dw += sizeof (RIFF);
        // round to word boundary
        //
        dw += (dw & 1);
        off += dw;

        // If a Junk chunk was appended, move past it
        //
        if (fChunkIsGranular && dwGran && (off % dwGran)) {
            dwJunk = dwGran - (off % dwGran);

            if (dwJunk < sizeof (RIFF))
                off += dwGran;
            off += dwJunk;
        }
    }

    if (mmioAscend(lpcs->hmmio, &ck, 0)){
	dprintf("Failed to ascend at end of index writing");
        return FALSE;
    }

    return TRUE;
}

/*
 * AVIFileFini
 *
 *       Write out the index, deallocate the index, and close the file.
 *
 */
BOOL AVIFileFini (LPCAPSTREAM lpcs, BOOL fWroteJunkChunks, BOOL fAbort)
{
    MMCKINFO      ckRiff;
    MMCKINFO      ckList;
    MMCKINFO      ckStream;
    MMCKINFO      ck;
    UINT          ii;
    DWORD         dw;
    AVIStreamHeader        strhdr;
    DWORD         dwDataEnd;
    BOOL          fRet = TRUE;
    RGBQUAD       argbq[256];
    MainAVIHeader aviHdr;
    BOOL          fSound;
    LPBITMAPINFO  lpBitsInfoOut;    // Possibly compressed output format

    // No special video format given -- use the default
    //
    lpBitsInfoOut = lpcs->lpBitsInfo;
   #ifdef NEW_COMPMAN
    if (lpcs->CompVars.hic != NULL)
        lpBitsInfoOut = lpcs->CompVars.lpbiOut;
   #endif

    // if the capture file has not been opened, we have nothing to do
    //
    if (lpcs->hFile == 0)
        return FALSE;

    // save off the current seek position.  this is the end of the capture
    // data.  then close the capture file,  we will do the final work
    // on the capture file using mmio & buffered io.
    //
    if (lpcs->pAsync)
        dwDataEnd = lpcs->dwAsyncWriteOffset;
    else
        dwDataEnd = SetFilePointer (lpcs->hFile, 0, NULL, FILE_CURRENT);

    CloseHandle (lpcs->hFile), lpcs->hFile = 0;

    // if we had allocated space for async buffers, free them now
    //
    if (lpcs->pAsync)
    {
        GlobalFreePtr (lpcs->pAsync);
        lpcs->pAsync = NULL;
        lpcs->iNextAsync = lpcs->iLastAsync = lpcs->iNumAsync = 0;
    }

    // if we are aborting capture, we are done
    lpcs->hmmio = mmioOpen(lpcs->achFile, NULL, MMIO_WRITE);
    assert (lpcs->hmmio != NULL);

    //
    if (fAbort)
        goto FileError;

    if (!lpcs->dwWaveBytes)
        fSound = FALSE;
    else
        fSound = lpcs->sCapParms.fCaptureAudio && (!(lpcs->fCaptureFlags & CAP_fFrameCapturingNow));

    // Seek to beginning of file, so we can write the header.
    mmioSeek(lpcs->hmmio, 0, SEEK_SET);

    DSTATUS(lpcs, "Writing AVI header");

    // Create RIFF chunk
    ckRiff.cksize = 0;
    ckRiff.fccType = formtypeAVI;
    if (mmioCreateChunk(lpcs->hmmio,&ckRiff,MMIO_CREATERIFF))
         goto FileError;

    // Create header list
    ckList.cksize = 0;
    ckList.fccType = listtypeAVIHEADER;
    if (mmioCreateChunk(lpcs->hmmio,&ckList,MMIO_CREATELIST))
         goto FileError;

    // Create AVI header chunk
    ck.cksize = sizeof(MainAVIHeader);
    ck.ckid = ckidAVIMAINHDR;
    if (mmioCreateChunk(lpcs->hmmio,&ck,0))
         goto FileError;

    lpcs->dwAVIHdrPos = ck.dwDataOffset;

    // Calculate AVI header info
    //
    ZeroMemory (&aviHdr, sizeof(aviHdr));

    //
    // Set the stream lengths based on the Master stream
    //
   #if 0 // stream length calc with unconditional audio master
    aviHdr.dwMicroSecPerFrame = lpcs->sCapParms.dwRequestMicroSecPerFrame;
    if (fSound && lpcs->dwVideoChunkCount) {
         /* HACK HACK */
         /* Set rate that was captured based on length of audio data */

         aviHdr.dwMicroSecPerFrame = (DWORD) MulDiv ((LONG)lpcs->dwWaveBytes,
                   1000000,
                   (LONG)(lpcs->lpWaveFormat->nAvgBytesPerSec * lpcs->dwVideoChunkCount));
    }
   #else

    // Init a value in case we're not capturing audio
    aviHdr.dwMicroSecPerFrame = lpcs->sCapParms.dwRequestMicroSecPerFrame;

    switch (lpcs->sCapParms.AVStreamMaster) {
        case AVSTREAMMASTER_NONE:
            break;

        case AVSTREAMMASTER_AUDIO:
        default:
            // VFW 1.0 and 1.1 ALWAYS munged frame rate to match audio
            // duration.
            if (fSound && lpcs->sCapParms.fCaptureAudio && lpcs->dwVideoChunkCount) {
                // Modify the video framerate based on audio duration
                aviHdr.dwMicroSecPerFrame = (DWORD)
                    ((double)lpcs->dwWaveBytes * 1000000. /
                    ((double)lpcs->lpWaveFormat->nAvgBytesPerSec *
                    lpcs->dwVideoChunkCount + 0.5));
            }
            break;
    }
   #endif
    lpcs->dwActualMicroSecPerFrame = aviHdr.dwMicroSecPerFrame;

    aviHdr.dwMaxBytesPerSec = (DWORD) MulDiv (lpBitsInfoOut->bmiHeader.biSizeImage,
                                      1000000,
                                      lpcs->sCapParms.dwRequestMicroSecPerFrame) +
                                      (fSound ? lpcs->lpWaveFormat->nAvgBytesPerSec : 0);
    aviHdr.dwPaddingGranularity = 0L;
    aviHdr.dwFlags = AVIF_WASCAPTUREFILE | AVIF_HASINDEX;
    aviHdr.dwStreams = fSound ? 2 : 1;
    aviHdr.dwTotalFrames = lpcs->dwVideoChunkCount;
    aviHdr.dwInitialFrames = 0L;
    aviHdr.dwSuggestedBufferSize = 0L;
    aviHdr.dwWidth = lpBitsInfoOut->bmiHeader.biWidth;
    aviHdr.dwHeight = lpBitsInfoOut->bmiHeader.biHeight;

    aviHdr.dwReserved[0] = 0;
    aviHdr.dwReserved[1] = 0;
    aviHdr.dwReserved[2] = 0;
    aviHdr.dwReserved[3] = 0;
    //aviHdr.dwRate = 1000000L;
    //aviHdr.dwScale = aviHdr.dwMicroSecPerFrame;
    //aviHdr.dwStart = 0L;
    //aviHdr.dwLength = lpcs->dwVideoChunkCount;

    // Write AVI header info
    if (mmioWrite(lpcs->hmmio, (LPBYTE)&aviHdr, sizeof(aviHdr)) != sizeof(aviHdr) ||
        mmioAscend(lpcs->hmmio, &ck, 0))
        goto FileError;

    DSTATUS(lpcs, "Writing AVI Stream header");

    // Create stream header list
    ckStream.cksize = 0;
    ckStream.fccType = listtypeSTREAMHEADER;
    if (mmioCreateChunk(lpcs->hmmio,&ckStream,MMIO_CREATELIST))
        goto FileError;

    ZeroMemory (&strhdr, sizeof(strhdr));
    strhdr.fccType = streamtypeVIDEO;
    strhdr.fccHandler = lpBitsInfoOut->bmiHeader.biCompression;
   #ifdef NEW_COMPMAN
    if (lpcs->CompVars.hic)
        strhdr.fccHandler = lpcs->CompVars.fccHandler;
   #endif

    // A bit of history...
    // In VFW 1.0, we set fccHandler to 0 for BI_RLE8 formats
    // as a kludge to make Mplayer and Videdit play the files.
    // Just prior to 1.1 release, we found this broke Premiere,
    // so now (after AVICAP beta is on Compuserve), we change the
    // fccHandler to "MRLE".  Just ask Todd...
    // And now, at RC1, we change it again to "RLE ", Just ask Todd...
    if (strhdr.fccHandler == BI_RLE8)
        strhdr.fccHandler = mmioFOURCC('R', 'L', 'E', ' ');

    //strhdr.dwFlags = 0L;
   #ifdef NEW_COMPMAN
    //strhdr.wPriority = 0L;
    //strhdr.wLanguage = 0L;
   #else
    //strhdr.dwPriority = 0L;
   #endif

    //strhdr.dwInitialFrames = 0L;
    strhdr.dwScale = aviHdr.dwMicroSecPerFrame;
    strhdr.dwRate = 1000000L;
    //strhdr.dwStart = 0L;
    strhdr.dwLength = lpcs->dwVideoChunkCount;        /* Needs to get filled in! */
    strhdr.dwQuality = (DWORD) -1L;         /* !!! ICQUALITY_DEFAULT */
    //strhdr.dwSampleSize = 0L;

    //
    // Write stream header data
    //
    ck.ckid = ckidSTREAMHEADER;
    if (mmioCreateChunk(lpcs->hmmio,&ck,0) ||
        mmioWrite(lpcs->hmmio, (LPBYTE)&strhdr, sizeof(strhdr)) != sizeof(strhdr) ||
        mmioAscend(lpcs->hmmio, &ck, 0))
        goto FileError;

    /*
    **  !!! dont write palette for full color?
    */
    if (lpBitsInfoOut->bmiHeader.biBitCount > 8)
        lpBitsInfoOut->bmiHeader.biClrUsed = 0;

    /* Create DIB header chunk */
    ck.cksize = lpBitsInfoOut->bmiHeader.biSize +
                           lpBitsInfoOut->bmiHeader.biClrUsed *
                           sizeof(RGBQUAD);
    ck.ckid = ckidSTREAMFORMAT;
    if (mmioCreateChunk(lpcs->hmmio,&ck,0))
         goto FileError;

    /* Write DIB header data */
    if (mmioWrite(lpcs->hmmio, (LPBYTE)&lpBitsInfoOut->bmiHeader,
                               lpBitsInfoOut->bmiHeader.biSize) !=
             (LONG) lpBitsInfoOut->bmiHeader.biSize)
         goto FileError;

    if (lpBitsInfoOut->bmiHeader.biClrUsed > 0) {
        // Get Palette info
        if ((ii = GetPaletteEntries(lpcs->hPalCurrent, 0,
                                (UINT) lpBitsInfoOut->bmiHeader.biClrUsed,
                                (LPPALETTEENTRY) argbq)) !=
                    (UINT)lpBitsInfoOut->bmiHeader.biClrUsed)
            goto FileError;

	// Reorder the palette from PALETTEENTRY order to RGBQUAD order
	// by swapping the red and blue palette entries.
        //for (ii = 0; ii < lpBitsInfoOut->bmiHeader.biClrUsed; ++ii)
        while (ii--)
            SWAPTYPE(argbq[ii].rgbRed, argbq[ii].rgbBlue, BYTE);


        // Write Palette Info
        dw = sizeof(RGBQUAD) * lpBitsInfoOut->bmiHeader.biClrUsed;
        if (mmioWrite(lpcs->hmmio, (LPBYTE)argbq, dw) != (long)dw)
            goto FileError;
    }

    if (mmioAscend(lpcs->hmmio, &ck, 0))
         goto FileError;

    // ADD FOURCC stuff here!!! for Video stream

    // Ascend out of stream header
    if (mmioAscend(lpcs->hmmio, &ckStream, 0))
         goto FileError;

    /* If sound is enabled, then write WAVE header */
    if (fSound) {

         /* Create stream header list */
         ckStream.cksize = 0;
         ckStream.fccType = listtypeSTREAMHEADER;
         if (mmioCreateChunk(lpcs->hmmio,&ckStream,MMIO_CREATELIST))
             goto FileError;

         ZeroMemory (&strhdr, sizeof(strhdr));
         strhdr.fccType = streamtypeAUDIO;
         strhdr.fccHandler = 0L;
         strhdr.dwFlags = 0L;
        #ifdef NEW_COMPMAN
         strhdr.wPriority = 0L;
         strhdr.wLanguage = 0L;
        #else
         strhdr.dwPriority  = 0L;
        #endif
         strhdr.dwInitialFrames = 0L;
         strhdr.dwScale = lpcs->lpWaveFormat->nBlockAlign;
         strhdr.dwRate = lpcs->lpWaveFormat->nAvgBytesPerSec;
         strhdr.dwStart = 0L;
         strhdr.dwLength =  lpcs->dwWaveBytes /
                        lpcs->lpWaveFormat->nBlockAlign;
         strhdr.dwQuality = (DWORD)-1L;    /* !!! ICQUALITY_DEFAULT */
         strhdr.dwSampleSize = lpcs->lpWaveFormat->nBlockAlign;

         ck.ckid = ckidSTREAMHEADER;
         if (mmioCreateChunk(lpcs->hmmio,&ck,0) ||
             mmioWrite(lpcs->hmmio, (LPBYTE)&strhdr, sizeof(strhdr)) != sizeof(strhdr) ||
             mmioAscend(lpcs->hmmio, &ck, 0))
             goto FileError;

         ck.cksize = (LONG) GetSizeOfWaveFormat ((LPWAVEFORMATEX) lpcs->lpWaveFormat);
         ck.ckid = ckidSTREAMFORMAT;
         if (mmioCreateChunk(lpcs->hmmio,&ck,0) ||
             mmioWrite(lpcs->hmmio, (LPBYTE)lpcs->lpWaveFormat, ck.cksize) != (LONG) ck.cksize ||
             mmioAscend(lpcs->hmmio, &ck, 0))
             goto FileError;

         /* Ascend out of stream header */
         if (mmioAscend(lpcs->hmmio, &ckStream, 0))
             goto FileError;
    }

    // ADD FOURCC stuff here!!! for entire file
    DSTATUS(lpcs, "Writing Info chunks");
    if (lpcs->lpInfoChunks) {
        DSTATUS(lpcs, "Writing Info chunks");
        if (mmioWrite (lpcs->hmmio, lpcs->lpInfoChunks, lpcs->cbInfoChunks) !=
                lpcs->cbInfoChunks)
            goto FileError;
    }

    /* ascend from the Header list */
    if (mmioAscend(lpcs->hmmio, &ckList, 0))
         goto FileError;


    ck.ckid = ckidAVIPADDING;
    if (mmioCreateChunk(lpcs->hmmio,&ck,0))
         goto FileError;

    mmioSeek(lpcs->hmmio, lpcs->dwAVIHdrSize - 3 * sizeof(DWORD), SEEK_SET);

    if (mmioAscend(lpcs->hmmio, &ck, 0))
         goto FileError;

    DSTATUS(lpcs, "Writing Movie LIST");

    /* Start the movi list */
    ckList.cksize = 0;
    ckList.fccType = listtypeAVIMOVIE;
    if (mmioCreateChunk(lpcs->hmmio,&ckList,MMIO_CREATELIST))
         goto FileError;

    // Force the chunk to end on the next word boundary
    dprintf("IndexStartOffset = %8X\n", dwDataEnd);
    mmioSeek(lpcs->hmmio, dwDataEnd + (dwDataEnd & 1L), SEEK_SET);

    /* Ascend out of the movi list and the RIFF chunk so that */
    /* the sizes can be fixed */
    mmioAscend(lpcs->hmmio, &ckList, 0);

    /*
    ** Now write index out!
    */
    DSTATUS(lpcs, "Writing Index...");
    WriteIndex(lpcs, fWroteJunkChunks);

    lpcs->fFileCaptured = TRUE;     // we got a good file, allow editing of it
    goto Success;

FileError:
    lpcs->fFileCaptured = fRet = FALSE;      // bogus file - no editing allowed

Success:
    DSTATUS(lpcs, "Freeing Index...");
    FiniIndex (lpcs);
    mmioAscend(lpcs->hmmio, &ckRiff, 0);

    mmioSeek(lpcs->hmmio, 0, SEEK_END);

    mmioFlush(lpcs->hmmio, 0);

    // Close the file
    mmioClose(lpcs->hmmio, 0);
    lpcs->hmmio = NULL;

    return fRet;
}

//
// Prepends dummy frame entries to the current valid video frame.
// Bumps the index, but does not actually trigger a write operation.
// nCount is a count of the number of frames to write
// Returns: TRUE on a successful write

BOOL WINAPI AVIWriteDummyFrames (
    LPCAPSTREAM lpcs,
    UINT        nCount,
    LPUINT      lpuError,
    LPBOOL      lpbPending)
{
    DWORD  dwBytesToWrite;
    DWORD  dwType;
    LPRIFF priff;
    UINT   jj;

    *lpbPending = FALSE;
    *lpuError = 0;
    if ( ! nCount)
        return TRUE;

    // create a buffer full of dummy chunks to act as placeholders
    // for the dropped frames
    //
    dwType = MAKEAVICKID(cktypeDIBbits, 0);
    if (lpcs->lpBitsInfo->bmiHeader.biCompression == BI_RLE8)
        dwType = MAKEAVICKID(cktypeDIBcompressed, 0);

    // dont try to write more than 1 'sector' worth of dummy
    // frames
    //
    dwBytesToWrite = nCount * sizeof(RIFF);
    if (dwBytesToWrite > lpcs->dwBytesPerSector)
    {
#ifdef DEBUG
	UINT n = nCount;
#endif
        dwBytesToWrite = lpcs->dwBytesPerSector;
#ifdef DEBUG
        nCount = dwBytesToWrite / sizeof(RIFF);
	assert(nCount*sizeof(RIFF) == dwBytesToWrite);
	dprintf("Forced to reduce dummy frames from %d to %d", n, nCount);
#endif
    }

    // create index entries for the dummy chunks
    //
    for (jj = 0; jj < nCount-1; ++jj)
        IndexVideo (lpcs, IS_DUMMY_CHUNK, FALSE);
    IndexVideo (lpcs, IS_DUMMY_CHUNK | IS_GRANULAR_CHUNK, FALSE);

    // fill in the drop frame buffer with dummy frames
    //
    priff = (LPRIFF)lpcs->lpDropFrame;
    for (jj = 0; jj < nCount; ++jj, ++priff)
    {
        priff->dwSize  = 0;
        priff->dwType  = dwType;
    }

    //
    // cant use a single dummy frame buffer when we are doing async
    // write because we cant write 'n' dummy frames to the buffer
    // if it is currently already queued to an IO.
    //
    // perhaps several dummy frames?  1 frame, 2 frames, 3 frames, etc
    // create dynamically?
    //

    // write out the dummy frames
    //
    AuxDebugEx (3, DEBUGLINE "DummyFrames  Count=%d, ToWrite=%d\r\n",
                nCount, dwBytesToWrite);

    *lpuError = AVIWrite (lpcs,
                          lpcs->lpDropFrame,
                          dwBytesToWrite,
                          (UINT)-1,  // force sync completion
                          ASYNC_BUF_DROP,
                          lpbPending);
    return !(*lpuError);
}

// Writes compressed or uncompressed frames to the AVI file
// returns TRUE if no error, FALSE if end of file.
//
BOOL WINAPI AVIWriteVideoFrame (
    LPCAPSTREAM lpcs,
    LPBYTE      lpData,
    DWORD       dwBytesUsed,
    BOOL        fKeyFrame,
    UINT        uIndex,
    UINT        nDropped,
    LPUINT      lpuError,
    LPBOOL      lpbPending)
{
    DWORD  dwBytesToWrite;
    LPRIFF priff;

    *lpuError = 0;
    *lpbPending = FALSE;
    if (!IndexVideo (lpcs,
                dwBytesUsed | (nDropped ? 0 : IS_GRANULAR_CHUNK),
                fKeyFrame))
        return FALSE;

    // adjust the size field of the RIFF chunk that preceeds the
    // data to be written
    //
    priff = ((LPRIFF)lpData)-1;
    priff->dwSize = dwBytesUsed;
    dwBytesUsed += dwBytesUsed & 1;
    dwBytesToWrite = dwBytesUsed + sizeof(RIFF);

    if (nDropped)
    {
        UINT  jj;
        DWORD dwType;

        // determine the 'type' of the dummy chunks
        //
        //dwType = priff->dwType;
        dwType = MAKEAVICKID(cktypeDIBbits, 0);
        if (lpcs->lpBitsInfo->bmiHeader.biCompression == BI_RLE8)
            dwType = MAKEAVICKID(cktypeDIBcompressed, 0);

        // dont try to write more than 1 'sector' worth of dummy
        // frames
        //
        if (nDropped > (lpcs->dwBytesPerSector / sizeof(RIFF)))
            nDropped = lpcs->dwBytesPerSector / sizeof(RIFF);

        // create index entries for the dummy chunks
        //
        for (jj = 0; jj < nDropped-1; ++jj)
            IndexVideo (lpcs, IS_DUMMY_CHUNK, FALSE);

        IndexVideo (lpcs, IS_DUMMY_CHUNK | IS_GRANULAR_CHUNK, FALSE);

        // fill in the drop frame buffer with dummy frames
        //
        priff = (LPRIFF)(lpData + dwBytesToWrite - sizeof(RIFF));
        for (jj = 0; jj < nDropped; ++jj, ++priff)
        {
            priff->dwSize  = 0;
            priff->dwType  = dwType;
        }
        dwBytesToWrite += nDropped * sizeof(RIFF);
    }

    // AviWrite will write the data and create any trailing junk
    // that is necessary
    //

    // write out the chunk, video data, and possibly the junk chunk
    //
    AuxDebugEx (3, DEBUGLINE "Calling AVIWrite - Video=%8x dw=%8x\r\n",
                (LPBYTE)lpData - sizeof(RIFF), dwBytesToWrite);

    *lpuError = AVIWrite (lpcs,
                          (LPBYTE)lpData - sizeof(RIFF),
                          dwBytesToWrite,
                          uIndex,
                          ASYNC_BUF_VIDEO,
                          lpbPending);
    return !(*lpuError);
}

// New for Chicago, align audio buffers on wChunkGranularity boundaries!
//
BOOL WINAPI AVIWriteAudio (
    LPCAPSTREAM lpcs,
    LPWAVEHDR   lpwh,
    UINT        uIndex,
    LPUINT      lpuError,
    LPBOOL      lpbPending)
{
    DWORD  dwBytesToWrite;
    LPRIFF priff;

    *lpuError = 0;
    *lpbPending = FALSE;

    // change the dwSize field in the RIFF chunk
    priff = ((LPRIFF)lpwh->lpData) -1;
    priff->dwSize = lpwh->dwBytesRecorded;

    if ( ! IndexAudio (lpcs, lpwh->dwBytesRecorded | IS_GRANULAR_CHUNK))
        return FALSE;

    // update total bytes of wave audio recorded
    //
    lpcs->dwWaveBytes += lpwh->dwBytesRecorded;

    // pad the data to be written to a WORD (16 bit) boundary
    //
    lpwh->dwBytesRecorded += lpwh->dwBytesRecorded & 1;
    dwBytesToWrite = lpwh->dwBytesRecorded + sizeof(RIFF);

    // write out the chunk, audio data, and possibly the junk chunk
    AuxDebugEx (3, DEBUGLINE "Audio=%8x dw=%8x\r\n",
                lpwh->lpData - sizeof(RIFF), dwBytesToWrite);
    *lpuError = AVIWrite (lpcs,
                          lpwh->lpData - sizeof(RIFF),
                          dwBytesToWrite,
                          uIndex,
                          ASYNC_BUF_AUDIO,
                          lpbPending);
    return !(*lpuError);
}
    #endif  //---------------- USE_AVIFILE ----------------------------