/****************************************************************************
 *
 *   capavi.c
 *
 *   Main video capture module.
 *
 *   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 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"

#define JMK_HACK_TIMERS    TRUE

#ifdef JMK_HACK_TIMERS
 #define _INC_MMTIMERS_CODE_ TRUE
 #define CLIPBOARDLOGSIZE 1000

 #ifndef MAKEFOURCC
  #define MAKEFOURCC(a,b,c,d) ((DWORD)(a) | ((DWORD)(b) << 8) | ((DWORD)(c) << 16) | ((DWORD)(d) << 24))
 #endif

 #define RIFFTYPE(dw) (((dw & 0xFF) << 24) | ((dw & 0xFF00) << 8) | ((dw & 0xFF0000) >> 8) | ((dw & 0xFF000000) >> 24))

 #include "mmtimers.h"

typedef struct _timerstuff {
     DWORD dwFrameTickTime;	// What we think the current frame time should be
     DWORD dwFrameStampTime;	// Stamped in the VIDEOHDR
     DWORD dwTimeWritten;       // Time WriteFile called
     DWORD dwTimeToWrite;       // Time WriteFile returned
     WORD  nFramesAppended;	// Accumulated appended dropped frames
     WORD  nDummyFrames;	// frames calc'ed as dropped
     DWORD dwVideoChunkCount;   // current 'frame'
     WORD  nAudioIndex;         // next audio buffer
     WORD  nVideoIndex;         // next video buffer
     WORD  bPending;
     WORD  nSleepCount;
     DWORD dwSleepBegin;
     DWORD dwSleepEnd;
     };

 STATICDT PCTIMER  pctWriteBase;
 STATICDT struct _timerstuff * pCurTimerStuff;
 STATICDT struct _timerstuff * pTimerStuff;
 STATICDT HGLOBAL  hMemTimers;

 STATICDT struct _timerriff {
     FOURCC   fccRIFF;       // 'RIFF'
     DWORD    cbTotal;       // total (inclusive) size of riff data
     FOURCC   fccJMKD;       // 'JMKD' data type identifier
     DWORD    fccVCHD;       // 'VCHD' capture data header
     DWORD    cbVCHD;        // sizeof vchd data
     struct _vchd {
         DWORD            nPrio;
         DWORD            dwFramesCaptured;
         DWORD            dwFramesDropped;
         DWORD            dwDropFramesAppended;
         DWORD            dwDropFramesNotAppended;
         DWORD            dwTimerFrequency;
         DWORD            dwSpare[2];
         CAPTUREPARMS     cap;
         BITMAPINFOHEADER bmih;
         DWORD            nMaxVideoBuffers;
         struct _thkvideohdr {
             VIDEOHDR vh;
             LPBYTE   p32Buff;
             DWORD    p16Alloc;
             DWORD    dwMemHandle;
             DWORD    dwReserved;
             }            atvh[64];
         }    vchd;
     DWORD    fccChunk;      // chunk data type tag
     DWORD    cbChunk;       // non-inclusive size of chunk data
     } * pTimerRiff;

 STATICDT UINT nTimerIndex;
 STATICDT UINT nSleepCount;
#endif

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

// Allocate memory which can be page aligned

LPVOID FAR PASCAL AllocSectorAlignedMem (DWORD dwRequest, DWORD dwAlign)
{
    LPVOID pbuf;

    dwRequest = ROUNDUPTOSECTORSIZE (dwRequest, dwAlign) + dwAlign;      // round up to next page boundary

    pbuf = VirtualAlloc (NULL, dwRequest,
                        MEM_COMMIT | MEM_RESERVE,
                        PAGE_READWRITE);
    AuxDebugEx(4, DEBUGLINE "Allocated %d bytes of sector aligned memory at %8x\r\n", dwRequest, pbuf);
    return pbuf;
}

void FAR PASCAL FreeSectorAlignedMem (LPVOID pbuf)
{
   // the pointer we free had better be aligned on at least a 256 byte
   // boundary
   //
   assert (!((DWORD)pbuf & 255));
   (DWORD)pbuf &= ~255;
   VirtualFree (pbuf, 0, MEM_RELEASE);
}

#define ONEMEG (1024L * 1024L)

DWORDLONG GetFreePhysicalMemory(void)
{
	MEMORYSTATUSEX ms;

	ms.dwLength = sizeof(ms);
    
    GlobalMemoryStatusEx(&ms);

    if (ms.ullTotalPhys > 8L * ONEMEG)
        return ms.ullTotalPhys - ONEMEG * 4;

    return(ms.ullTotalPhys /2);
}

// ****************************************************************
// ******************** Audio Buffer Control **********************
// ****************************************************************

// Audio buffers are always allocated under the presumption that
// audio capture may be enabled at any time.
// AVIAudioInit must be matched with AVIAudioFini (both only called once)
// AVIAudioPrepare must be matched with AVIAudioUnPrepare
//      (which may be called multiple times to enable and disable audio)


// AVI AudioInit - Allocate and initialize buffers for audio capture.
//                 This routine is also used by MCI capture.
//                 Returns: 0 on success, otherwise an error code.

UINT AVIAudioInit (LPCAPSTREAM lpcs)
{
    int         i;
    LPVOID      pHdr;
    LPVOID      p;

    if (lpcs->sCapParms.wNumAudioRequested == 0)
        lpcs->sCapParms.wNumAudioRequested = DEF_WAVE_BUFFERS;

    // .5 second of audio per buffer (or 10K, whichever is larger)
    if (lpcs->sCapParms.dwAudioBufferSize == 0)
        lpcs->dwWaveSize = CalcWaveBufferSize (lpcs);
    else {
        lpcs->dwWaveSize = 0;
        if (lpcs->lpWaveFormat)
            lpcs->dwWaveSize = lpcs->sCapParms.dwAudioBufferSize;
    }

    // Alloc the wave memory
    for(i = 0; i < (int)lpcs->sCapParms.wNumAudioRequested; i++) {

        pHdr = GlobalAllocPtr(GPTR, sizeof(WAVEHDR));

        if (pHdr == NULL)
            break;

        lpcs->alpWaveHdr[i] = pHdr;

        p = AllocSectorAlignedMem( sizeof(RIFF) + lpcs->dwWaveSize, lpcs->dwBytesPerSector);
        if (p == NULL) {
            GlobalFreePtr (pHdr);
            lpcs->alpWaveHdr[i] = NULL;
            break;
        }

        lpcs->alpWaveHdr[i]->lpData          = (LPBYTE)p + sizeof(RIFF);
        lpcs->alpWaveHdr[i]->dwBufferLength  = lpcs->dwWaveSize;
        lpcs->alpWaveHdr[i]->dwBytesRecorded = 0;
        lpcs->alpWaveHdr[i]->dwUser          = 0;
        lpcs->alpWaveHdr[i]->dwFlags         = 0;
        lpcs->alpWaveHdr[i]->dwLoops         = 0;

        ((LPRIFF)p)->dwType = MAKEAVICKID(cktypeWAVEbytes, 1);
        ((LPRIFF)p)->dwSize = lpcs->dwWaveSize;
    }

    lpcs->iNumAudio = i;

    return ((lpcs->iNumAudio == 0) ? IDS_CAP_WAVE_ALLOC_ERROR : 0);
}


//
// AVI AudioFini    - UnPrepares headers
//                      This routine is also used by MCI capture.
//                      Returns: 0 on success, otherwise an error code.

UINT AVIAudioFini (LPCAPSTREAM lpcs)
{
    int ii;

    /* free headers and data */
    for (ii=0; ii < MAX_WAVE_BUFFERS; ++ii) {
        if (lpcs->alpWaveHdr[ii]) {
            if (lpcs->alpWaveHdr[ii]->lpData)
                FreeSectorAlignedMem((LPBYTE)lpcs->alpWaveHdr[ii]->lpData - sizeof (RIFF));
            GlobalFreePtr(lpcs->alpWaveHdr[ii]);
            lpcs->alpWaveHdr[ii] = NULL;
        }
    }

    return 0;
}

//
// AVI AudioPrepare - Opens the wave device and adds the buffers
//                    Prepares headers and adds buffers to the device
//                    This routine is also used by MCI capture.
//                    Returns: 0 on success, otherwise an error code.

UINT AVIAudioPrepare (LPCAPSTREAM lpcs)
{
    UINT uiError;
    int  ii;

    /* See if we can open that format for input */

    // register event callback to avoid polling

    uiError = waveInOpen(&lpcs->hWaveIn,
        WAVE_MAPPER, lpcs->lpWaveFormat,
        (DWORD) lpcs->hCaptureEvent,  0, CALLBACK_EVENT );

    if (uiError != MMSYSERR_NOERROR)
        return IDS_CAP_WAVE_OPEN_ERROR;

    lpcs->fAudioYield = FALSE; // ACM is separate thread, don't yield
    lpcs->fAudioBreak = FALSE;

    DPF("AudioYield = %d", lpcs->fAudioYield);

    for (ii = 0; ii < (int)lpcs->sCapParms.wNumAudioRequested; ++ii) {

        if (waveInPrepareHeader (lpcs->hWaveIn, lpcs->alpWaveHdr[ii],
                                 sizeof(WAVEHDR)))
            return IDS_CAP_WAVE_ALLOC_ERROR;

        if (waveInAddBuffer (lpcs->hWaveIn, lpcs->alpWaveHdr[ii],
                             sizeof(WAVEHDR)))
            return IDS_CAP_WAVE_ALLOC_ERROR;
	AuxDebugEx(3, DEBUGLINE "Added wave buffer %d (%8x)\r\n", ii, lpcs->alpWaveHdr[ii]);
    }

    lpcs->iNextWave = 0;        // current wave
    lpcs->dwWaveBytes = 0L;     // number of wave bytes
    lpcs->dwWaveChunkCount = 0; // number of wave frames

    return 0;
}

//
// AVI AudioUnPrepare - UnPrepares headers and closes the wave device.
//                      This routine is also used by MCI capture.
//                      Returns: 0 on success, otherwise an error code.

UINT AVIAudioUnPrepare (LPCAPSTREAM lpcs)
{
    int ii;

    if (lpcs->hWaveIn)
    {
        waveInReset(lpcs->hWaveIn);

        // unprepare any headers that have been prepared
        //
        for (ii=0; ii < lpcs->iNumAudio; ++ii)
            if (lpcs->alpWaveHdr[ii] &&
                (lpcs->alpWaveHdr[ii]->dwFlags & WHDR_PREPARED))
                waveInUnprepareHeader (lpcs->hWaveIn,
                                       lpcs->alpWaveHdr[ii],
                                       sizeof(WAVEHDR));

        waveInClose(lpcs->hWaveIn);
        lpcs->hWaveIn = NULL;
    }

    return 0;
}

// ****************************************************************
// ******************** Video Buffer Control **********************
// ****************************************************************

// AVIVideoInit -  Allocates, and initialize buffers for video capture.
//                 This routine is also used by MCI capture.
//                 Returns: 0 on success, otherwise an error code.

#if defined CHICAGO

UINT AVIVideoInit (LPCAPSTREAM lpcs)
{
    UINT           iMaxVideo;
    DWORD          mmr;
    LPTHKVIDEOHDR  ptvh;
    UINT           ii;
    DWORD          cbVideo;

    lpcs->iNumVideo = 0;
    lpcs->iNextVideo = 0;
    lpcs->dwVideoChunkCount = 0;
    lpcs->dwFramesDropped = 0;
    lpcs->fBuffersOnHardware = FALSE;

    // When performing MCI step capture, buffer array is not used
    if (lpcs->sCapParms.fStepMCIDevice)
        return 0;

    // If the user hasn't specified the number of video buffers to use,
    // assume the minimum

    if (lpcs->sCapParms.wNumVideoRequested == 0)
        lpcs->sCapParms.wNumVideoRequested = MIN_VIDEO_BUFFERS;

    cbVideo = ROUNDUPTOSECTORSIZE (lpcs->lpBitsInfo->bmiHeader.biSizeImage
                                   + sizeof(RIFF),
                                   lpcs->dwBytesPerSector)
              + lpcs->dwBytesPerSector;

    // use the number of video buffers that the user requested
    // or the maximum that will fit in memory.
    //
    iMaxVideo = min (MAX_VIDEO_BUFFERS, lpcs->sCapParms.wNumVideoRequested);

    if (iMaxVideo > 1)
    {
        DWORDLONG dwFreeMem;
        DWORDLONG dwUserRequests;
        DWORDLONG dwAudioMem;

        // How much actual free physical memory exists?
        dwFreeMem = GetFreePhysicalMemory();
        dwAudioMem = lpcs->dwWaveSize * lpcs->sCapParms.wNumAudioRequested;

        #define FOREVER_FREE 32768L   // Always keep this free for swap space

        // How much memory will be used if we allocate per the request?
        //
        dwUserRequests = dwAudioMem
                         + cbVideo * iMaxVideo
                         + FOREVER_FREE;

        // If request is greater than available memory, force fewer buffers
        //
        if (dwUserRequests > dwFreeMem)
        {
            if (dwFreeMem > dwAudioMem)
                dwFreeMem -= dwAudioMem;
            iMaxVideo = (int)(((dwFreeMem * 8) / 10) / cbVideo);
            iMaxVideo = min (MAX_VIDEO_BUFFERS, iMaxVideo);
            dprintf("iMaxVideo = %d\n", iMaxVideo);
        }
    }

    mmr = vidxAllocHeaders(lpcs->hVideoIn, iMaxVideo, &ptvh);
    if (mmr != MMSYSERR_NOERROR)
        return IDS_CAP_VIDEO_ALLOC_ERROR;

    AuxDebugEx (3, DEBUGLINE "vidxAllocHdrs returned ptvh=%X\r\n", ptvh);
    AuxDebugDump (8, ptvh, sizeof(*ptvh) * iMaxVideo);

    for (ii = 0; ii < iMaxVideo; ++ii)
    {
        LPVIDEOHDR pvh = NULL;
        LPRIFF     priff;

        // in chicago we let the thunk layer allocate memory
        // so that we can be assured that the memory can be easily
        // thunked.
        //
        // the pointer will be rounded up to a sector size boundary
        //
        mmr = vidxAllocBuffer (lpcs->hVideoIn, ii, &ptvh, cbVideo);
        if ((mmr != MMSYSERR_NOERROR) || (ptvh == NULL))
            break;

        lpcs->alpVideoHdr[ii] = pvh = &ptvh->vh;

        // vidxAllocBuffer actually returns a couple of extra fields
        // after the video header. the first of these holds the
        // linear address of the buffer.
        //
        priff = (LPVOID) ROUNDUPTOSECTORSIZE (ptvh->p32Buff, lpcs->dwBytesPerSector);

       #ifdef DEBUG
        {
        LPBYTE pb = (LPVOID)ptvh->p32Buff;
        AuxDebugEx (4, DEBUGLINE "buffer[%d] at %x linear. Doing touch test\r\n",
                    ii, ptvh->p32Buff);
        pb[0] = 0;
        pb[cbVideo-1] = 0;
        }
       #endif

        // write the riff header for this chunk.
        //
        priff->dwType = MAKEAVICKID(cktypeDIBbits, 0);
        if (lpcs->lpBitsInfo->bmiHeader.biCompression == BI_RLE8)
            priff->dwType = MAKEAVICKID(cktypeDIBcompressed, 0);
        priff->dwSize = lpcs->lpBitsInfo->bmiHeader.biSizeImage;

        // init the video header
        //
        pvh->lpData = (LPVOID)(priff + 1);
        pvh->dwBufferLength  = priff->dwSize;
        pvh->dwBytesUsed     = 0;
        pvh->dwTimeCaptured  = 0;
        pvh->dwUser          = 0;
        pvh->dwFlags         = 0;

        AuxDebugEx (4, DEBUGLINE "lpVideoHdr[%d]==%X\r\n", ii, lpcs->alpVideoHdr[ii]);
        AuxDebugDump (8, lpcs->alpVideoHdr[ii], sizeof(*ptvh));
    }
    lpcs->iNumVideo = ii;
    lpcs->cbVideoAllocation = cbVideo;

    dprintf("cbVideo = %ld \n", cbVideo);
    dprintf("iNumVideo Allocated = %d \n", lpcs->iNumVideo);
    return lpcs->iNumVideo ? 0 : IDS_CAP_VIDEO_ALLOC_ERROR;
}

//
// AVIVideoPrepare -  Prepares headers and adds buffers to the device
//                    This routine is also used by MCI capture.
//                    Returns: 0 on success, otherwise an error code.

UINT AVIVideoPrepare (LPCAPSTREAM lpcs)
{
    int ii;

    // When performing MCI step capture, buffer array is not used
    if (lpcs->sCapParms.fStepMCIDevice)
        return 0;

   #ifdef JMK_HACK_CHECKHDR
    {
    LPTHKVIDEOHDR lptvh = (LPVOID)lpcs->alpVideoHdr[0];

    if (HIWORD(lptvh->vh.lpData) != HIWORD(lptvh->p32Buff))
        {
        AuxDebugEx (0, DEBUGLINE "before stream init: hdr trouble\r\n");

        AuxDebugEx (0, DEBUGLINE "iNext=%d, ptvh=%X\r\n", lpcs->iNextVideo, lptvh);
        AuxDebugDump (0, lptvh, sizeof(*lptvh));
        AuxDebugEx (0, DEBUGLINE "alpVideoHdrs=%X\r\n", lpcs->alpVideoHdr);
        AuxDebugDump (0, lpcs->alpVideoHdr, sizeof(lpcs->alpVideoHdr[0]) * 8);

        INLINE_BREAK;
        return IDS_CAP_VIDEO_OPEN_ERROR;
        }
    }
   #endif

    // Open the video stream, setting the capture rate
    //
    if (videoStreamInit(lpcs->hVideoIn,
                        lpcs->sCapParms.dwRequestMicroSecPerFrame,
                        lpcs->hRing0CapEvt,
                        0,
                        CALLBACK_EVENT))
    {
        dprintf("cant open video device!\n");
        return IDS_CAP_VIDEO_OPEN_ERROR;
    }

   #ifdef JMK_HACK_CHECKHDR
    {
    LPTHKVIDEOHDR lptvh = (LPVOID)lpcs->alpVideoHdr[0];

    if (HIWORD(lptvh->vh.lpData) != HIWORD(lptvh->p32Buff))
        {
        AuxDebugEx (0, DEBUGLINE "after stream init: hdr trouble\r\n");

        AuxDebugEx (0, DEBUGLINE "iNext=%d, ptvh=%X\r\n", lpcs->iNextVideo, lptvh);
        AuxDebugDump (0, lptvh, sizeof(*lptvh));
        AuxDebugEx (0, DEBUGLINE "alpVideoHdrs=%X\r\n", lpcs->alpVideoHdr);
        AuxDebugDump (0, lpcs->alpVideoHdr, sizeof(lpcs->alpVideoHdr[0]) * 8);

        INLINE_BREAK;
        return IDS_CAP_VIDEO_OPEN_ERROR;
        }
    }
   #endif

    // Prepare (lock) the buffers, and give them to the device
    //
    for (ii = 0; ii < lpcs->iNumVideo; ++ii)
    {
        if (vidxAddBuffer (lpcs->hVideoIn,
                           lpcs->alpVideoHdr[ii],
                           sizeof(VIDEOHDR)))
        {
            lpcs->iNumVideo = ii;
            dprintf("**** could only prepare %d Video buffers!\n", lpcs->iNumVideo);
            break;
        }
    }

   #ifdef JMK_HACK_CHECKHDR
    {
    LPTHKVIDEOHDR lptvh = (LPVOID)lpcs->alpVideoHdr[0];

    if (IsBadWritePtr (lptvh, sizeof(*lptvh)) ||
        HIWORD(lptvh->vh.lpData) != HIWORD(lptvh->p16Alloc))
        {
        AuxDebugEx (0, DEBUGLINE "after add buffers: hdr trouble\r\n");

        AuxDebugEx (0, DEBUGLINE "iNext=%d, ptvh=%X\r\n", lpcs->iNextVideo, lptvh);
        AuxDebugDump (0, lptvh, sizeof(*lptvh));
        AuxDebugEx (0, DEBUGLINE "alpVideoHdrs=%X\r\n", lpcs->alpVideoHdr);
        AuxDebugDump (0, lpcs->alpVideoHdr, sizeof(lpcs->alpVideoHdr[0]) * 8);

        INLINE_BREAK;
        return IDS_CAP_VIDEO_OPEN_ERROR;
        }
    }
   #endif

    return 0;
}

#else // code below is !CHICAGO

// this structure is used to keep track of memory allocation
// for video buffers used in capture.  it is allocated when
// allocating a videohdr would be called for
//
typedef struct _cap_videohdr {
    VIDEOHDR vh;
    LPBYTE   pAlloc;      // address of allocated buffer
    DWORD    dwMemIdent;  // identity of allocation (used in Chicago)
    DWORD    dwReserved;  // used in chicago
    BOOL     bHwBuffer;   // TRUE if buffer is allocated using videoStreamAllocBuffer
} CAPVIDEOHDR, FAR *LPCAPVIDEOHDR;

// AVIVideoInit -  Allocates, and initialize buffers for video capture.
//                 This routine is also used by MCI capture.
//                 Returns: 0 on success, otherwise an error code.

UINT AVIVideoInit (LPCAPSTREAM lpcs)
{
    int            iMaxVideo;
    int            ii;
    LPCAPVIDEOHDR  pcvh;
    LPVOID         pbuf;
    DWORD          cbVideo;

    lpcs->iNumVideo = 0;
    lpcs->iNextVideo = 0;
    lpcs->dwVideoChunkCount = 0;
    lpcs->dwFramesDropped = 0;
    lpcs->fBuffersOnHardware = FALSE;

    // When performing MCI step capture, buffer array is not used
    if (lpcs->sCapParms.fStepMCIDevice)
        return 0;

    // If the user hasn't specified the number of video buffers to use,
    // assume the minimum

    if (lpcs->sCapParms.wNumVideoRequested == 0)
        lpcs->sCapParms.wNumVideoRequested = MIN_VIDEO_BUFFERS;

    cbVideo = ROUNDUPTOSECTORSIZE (lpcs->lpBitsInfo->bmiHeader.biSizeImage
                                   + sizeof(RIFF),
                                   lpcs->dwBytesPerSector)
              + lpcs->dwBytesPerSector;

    // use the number of video buffers that the user requested
    // or the maximum array size in CAPSTREAM
    //
    iMaxVideo = min (MAX_VIDEO_BUFFERS, lpcs->sCapParms.wNumVideoRequested);

    // Post VFW 1.1a, see if the driver can allocate memory
    //
   #ifdef ALLOW_HW_BUFFERS
    if (videoStreamAllocBuffer (lpcs->hVideoIn, (LPVOID *) &pbuf, cbVideo)
        == DV_ERR_OK)
    {
        lpcs->fBuffersOnHardware = TRUE;
        videoStreamFreeBuffer (lpcs->hVideoIn, pbuf);
    }
    else
   #endif
    {
        DWORDLONG dwFreeMem;
        DWORDLONG dwUserRequests;
        DWORDLONG dwAudioMem;

        lpcs->fBuffersOnHardware = FALSE;

        // How much actual free physical memory exists?
        dwFreeMem = GetFreePhysicalMemory();
        dwAudioMem = lpcs->dwWaveSize * lpcs->sCapParms.wNumAudioRequested;

        #define FOREVER_FREE 32768L   // Always keep this free for swap space

        // How much memory will be used if we allocate per the request?
        //
        dwUserRequests = dwAudioMem
                         + cbVideo * iMaxVideo
                         + FOREVER_FREE;

        // If request is greater than available memory, force fewer buffers
        //
        if (dwUserRequests > dwFreeMem)
        {
            if (dwFreeMem > dwAudioMem)
                dwFreeMem -= dwAudioMem;
            iMaxVideo = (int)(((dwFreeMem * 8) / 10) / cbVideo);
            iMaxVideo = min (MAX_VIDEO_BUFFERS, iMaxVideo);
            dprintf("iMaxVideo = %d\n", iMaxVideo);
        }
    }

    pcvh = GlobalAllocPtr (GMEM_MOVEABLE, iMaxVideo * sizeof(CAPVIDEOHDR));
    // note: pcvh is freed by referencing through alpVideoHdr[0]
    if ( ! pcvh)
        return IDS_CAP_VIDEO_ALLOC_ERROR;

    AuxDebugEx (3, DEBUGLINE "allocated video headers pcvh=%X\r\n", pcvh);

    // Set up the buffers presuming fixed size DIBs and Junk chunks
    // These will be modified later if the device provides compressed data

    for (ii = 0; ii < iMaxVideo; ++ii)
    {
        LPVIDEOHDR pvh = NULL;
        LPRIFF     priff;

       #ifdef ALLOW_HW_BUFFERS
        //
        // for the first buffer, always try to allocate on hardware,
        // if that fails, grab virtual memory for the buffer.
        // for all but the first buffer, we use whatever worked for
        // the first buffer, and if that fails.  we stop allocating buffers
        //
        if ((0 == ii) || lpcs->fBuffersOnHardware)
        {
            pbuf = NULL;
            mmr = videoStreamAllocBuffer (lpcs->hVideoIn, (LPVOID) &pbuf, cbVideo);
            if ((mmr != MMSYSERR_NOERROR) || (pbuf == NULL))
            {
                if (0 == ii)
                    break;

                lpcs->fBuffersOnHardware = FALSE;
                pbuf = AllocSectorAlignedMem (cbVideo, lpcs->dwBytesPerSector);
            }
            else
                lpcs->fBuffersOnHardware = TRUE;
        }
        else
            pbuf = AllocSectorAlignedMem (cbVideo, lpcs->dwBytesPerSector);

       #else ! dont allow hw buffers

        pbuf = AllocSectorAlignedMem (cbVideo, lpcs->dwBytesPerSector);

       #endif // ALLOW_HW_BUFFERS

        if (pbuf == NULL)
            break;

        // save the original allocation pointer to the buffer
        // in the extra fields of the capture header. also remember
        // whether we got the buffer from the driver or not
        //
        pcvh[ii].pAlloc = pbuf;
        pcvh[ii].bHwBuffer = lpcs->fBuffersOnHardware;
        lpcs->alpVideoHdr[ii] = pvh = &pcvh[ii].vh;
        priff = (LPVOID) ROUNDUPTOSECTORSIZE (pbuf, lpcs->dwBytesPerSector);

        // write the riff header for this frame
        //
        priff->dwType = MAKEAVICKID(cktypeDIBbits, 0);
        if (lpcs->lpBitsInfo->bmiHeader.biCompression == BI_RLE8)
            priff->dwType = MAKEAVICKID(cktypeDIBcompressed, 0);
        priff->dwSize = lpcs->lpBitsInfo->bmiHeader.biSizeImage;

        // fill in the video hdr for this frame
        //
        pvh->lpData          = (LPVOID)(priff + 1);
        pvh->dwBufferLength  = priff->dwSize;
        pvh->dwBytesUsed     = 0;
        pvh->dwTimeCaptured  = 0;
        pvh->dwUser          = 0;
        pvh->dwFlags         = 0;

        AuxDebugEx (4, DEBUGLINE "lpVideoHdr[%d]==%X\r\n", ii, lpcs->alpVideoHdr[ii]);
        AuxDebugDump (8, lpcs->alpVideoHdr[ii], sizeof(*pcvh));
    }
    lpcs->iNumVideo = ii;
    lpcs->cbVideoAllocation = cbVideo;

    // if we did not create even a single buffer, free the headers
    //
    if ( ! lpcs->iNumVideo)
        GlobalFreePtr (pcvh);

   #ifdef ALLOW_HW_BUFFERS
    if (lpcs->fBuffersOnHardware)
        dprintf("HARDWARE iNumVideo Allocated = %d \n", lpcs->iNumVideo);
    else
   #endif
        dprintf("HIGH iNumVideo Allocated = %d \n", lpcs->iNumVideo);

    return lpcs->iNumVideo ? 0 : IDS_CAP_VIDEO_ALLOC_ERROR;
}

void CALLBACK
VideoCallback(
    HVIDEO hvideo,
    UINT msg,
    DWORD dwInstance,
    DWORD lParam1,
    DWORD lParam2
)
{
    LPCAPSTREAM lpcs = (LPCAPSTREAM) dwInstance;

    if (lpcs && lpcs->hCaptureEvent) {
        SetEvent(lpcs->hCaptureEvent);
    } else {
	AuxDebugEx(1, DEBUGLINE "VideoCallback with NO instance data\r\n");
    }
}

//
// AVIVideoPrepare -  Prepares headers and adds buffers to the device
//                    This routine is also used by MCI capture.
//                    Returns: 0 on success, otherwise an error code.
UINT AVIVideoPrepare (LPCAPSTREAM lpcs)
{
    MMRESULT mmr;
    int      ii;

    // When performing MCI step capture, buffer array is not used
    //
    if (lpcs->sCapParms.fStepMCIDevice)
        return 0;

    // Open the video stream, setting the capture rate
    //
    mmr = videoStreamInit (lpcs->hVideoIn,
                           lpcs->sCapParms.dwRequestMicroSecPerFrame,
                           (DWORD) VideoCallback,
                           (DWORD) lpcs,
                           CALLBACK_FUNCTION);
    if (mmr) {
        dprintf("cannot open video device!  Error is %d\n", mmr);
        return IDS_CAP_VIDEO_OPEN_ERROR;
    }

    // Prepare (lock) the buffers, and give them to the device
    //
    for (ii = 0; ii < lpcs->iNumVideo; ++ii)
    {
        mmr = videoStreamPrepareHeader (lpcs->hVideoIn,
                                        lpcs->alpVideoHdr[ii],
                                        sizeof(VIDEOHDR));
        if (mmr)
        {
            lpcs->iNumVideo = ii;
            dprintf("**** could only prepare %d Video!\n", lpcs->iNumVideo);
            break;
        }

        mmr = videoStreamAddBuffer (lpcs->hVideoIn,
                                    lpcs->alpVideoHdr[ii],
                                    sizeof(VIDEOHDR));
        if (mmr)
             return IDS_CAP_VIDEO_ALLOC_ERROR;
	AuxDebugEx(3, DEBUGLINE "Added video buffer %d (%8x)\r\n", ii, lpcs->alpVideoHdr[ii]);
    }
    return 0;
}

#endif // not chicago

//
// AVI VideoUnPrepare - UnPrepares headers, frees memory, and
//                      resets the video in device.
//                      This routine is also used by MCI capture.
//                      Returns: 0 on success, otherwise an error code.

UINT AVIVideoUnPrepare (LPCAPSTREAM lpcs)
{
    // When performing MCI step capture, buffer array is not used
    //
    if (lpcs->sCapParms.fStepMCIDevice)
        return 0;

    // Reset the buffers so they can be freed
    //
    if (lpcs->hVideoIn) {
        videoStreamReset(lpcs->hVideoIn);

        // unprepare headers
        // Unlock and free headers and data

       #if defined CHICAGO
        vidxFreeHeaders (lpcs->hVideoIn);
        ZeroMemory (lpcs->alpVideoHdr, sizeof(lpcs->alpVideoHdr));
       #else
        {
            int ii;
            LPCAPVIDEOHDR pcvhAll = (LPVOID)lpcs->alpVideoHdr[0];

            for (ii = 0; ii < lpcs->iNumVideo; ++ii)
            {
                LPCAPVIDEOHDR pcvh = (LPVOID)lpcs->alpVideoHdr[ii];
                if (pcvh)
                {
                    if (pcvh->vh.dwFlags & VHDR_PREPARED)
                        videoStreamUnprepareHeader (lpcs->hVideoIn,
                                                    &pcvh->vh,
                                                    sizeof(VIDEOHDR));

                   #ifdef ALLOW_HW_BUFFERS
                    if (pcvh->bHwBuffer)
                    {
                        if (pcvh->pAlloc)
                            videoStreamFreeBuffer (lpcs->hVideoIn, (LPVOID)pcvh->pAlloc);
			}
                    else
                   #endif
                    {
                        if (pcvh->pAlloc)
                            FreeSectorAlignedMem (pcvh->pAlloc);
			}

                lpcs->alpVideoHdr[ii] = NULL;
                }
            }

            // free the array of video headers
            //
            if (pcvhAll)
                GlobalFreePtr (pcvhAll);
	    }
       #endif
        // Shut down the video stream
        videoStreamFini(lpcs->hVideoIn);
    }

    return 0;
}

/*
 *  AVI Fini    - undo the mess that AVIInit did.
 *
 */
void AVIFini(LPCAPSTREAM lpcs)
{
    AuxDebugEx (2, "AVIFini(%08x)\r\n", lpcs);

    if (lpcs->lpDropFrame) {
        FreeSectorAlignedMem (lpcs->lpDropFrame), lpcs->lpDropFrame = NULL;
    }

    AVIVideoUnPrepare (lpcs);           // Free the video device and buffers
    AVIAudioUnPrepare (lpcs);           // Free the audio device
    AVIAudioFini (lpcs);                // Free the audio buffers

    if (lpcs->hCaptureEvent) {
        CloseHandle (lpcs->hCaptureEvent), lpcs->hCaptureEvent = NULL;
    }

    AuxDebugEx (2, "AVIFini(...) exits\r\n");
}

//
// AVI Init
//     This routine does all the non-File initalization for AVICapture.
//     Returns: 0 on success, Error string value on failure.
//

UINT AVIInit (LPCAPSTREAM lpcs)
{
    UINT         wError = 0;    // Success
    LPBITMAPINFO lpBitsInfoOut;    // Possibly compressed output format

    // Allocate a DropFrame buffer
    if (lpcs->lpDropFrame == NULL) {
        assert (lpcs->dwBytesPerSector);
        lpcs->lpDropFrame = AllocSectorAlignedMem (lpcs->dwBytesPerSector, lpcs->dwBytesPerSector);
    }

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

    // -------------------------------------------------------
    // figure out buffer sizes
    // -------------------------------------------------------

    // Init all pointers to NULL
    ZeroMemory (lpcs->alpVideoHdr, sizeof(lpcs->alpVideoHdr));
    ZeroMemory (lpcs->alpWaveHdr, sizeof(lpcs->alpWaveHdr));

    // -------------------------------------------------------
    //                    Init Sound
    // -------------------------------------------------------

    if (lpcs->sCapParms.fCaptureAudio) {
        if (wError = AVIAudioInit (lpcs)) {
            dprintf("can't init audio buffers!\n");
            goto AVIInitFailed;
        }
    }

    // -------------------------------------------------------
    //                    Init Video
    // -------------------------------------------------------

    if (wError = AVIVideoInit (lpcs)) {
        dprintf("AVIVideoInitFailed (no buffers alloc'd)!\n");
        goto AVIInitFailed;
    }

    // --------------------------------------------------------------
    //  Prepare audio buffers (lock em down) and give them to the device
    // --------------------------------------------------------------

    if (lpcs->sCapParms.fCaptureAudio) {
        if (wError = AVIAudioPrepare (lpcs)) {
            dprintf("can't prepare audio buffers!\n");
            goto AVIInitFailed;
        }
    }

    // --------------------------------------------------------------
    //  Prepare video buffers (lock em down) and give them to the device
    // --------------------------------------------------------------

    if (wError = AVIVideoPrepare (lpcs)) {
        dprintf("can't prepare video buffers!\n");
        goto AVIInitFailed;
    }

    // -------------------------------------------------------
    //   all done, return success
    // -------------------------------------------------------

    return (0);            // SUCCESS !

    // -------------------------------------------------------
    //   we got a error, return string ID of error message
    // -------------------------------------------------------
AVIInitFailed:
    AVIFini(lpcs);      // Shutdown everything
    return wError;
}

// Maintains info chunks which are written to the AVI header
//
BOOL FAR PASCAL SetInfoChunk(LPCAPSTREAM lpcs, LPCAPINFOCHUNK lpcic)
{
    DWORD       ckid   = lpcic->fccInfoID;
    LPVOID      lpData = lpcic->lpData;
    LONG        cbData = lpcic->cbData;
    LPBYTE      lp;
    LPBYTE      lpw;
    LPBYTE      lpEnd;
    LPBYTE      lpNext;
    LONG        cbSizeThis;
    BOOL        fOK = FALSE;

    // Delete all info chunks?
    if (ckid == 0) {
        if (lpcs->lpInfoChunks) {
            GlobalFreePtr (lpcs->lpInfoChunks);
            lpcs->lpInfoChunks = NULL;
            lpcs->cbInfoChunks = 0;
        }
        return TRUE;
    }

    // Try removing an entry if it already exists...
    // Also used if lpData is NULL to just remove an entry
    //
    lpw   = (LPBYTE)lpcs->lpInfoChunks;           // always points at fcc
    lpEnd = (LPBYTE)lpcs->lpInfoChunks + lpcs->cbInfoChunks;
    while (lpw < lpEnd) {
        cbSizeThis = ((DWORD UNALIGNED FAR *)lpw)[1];
        cbSizeThis += cbSizeThis & 1;           // force WORD (16 bit) alignment
        lpNext = lpw + cbSizeThis + sizeof (DWORD) * 2;
        if ((*(DWORD UNALIGNED FAR *) lpw) == ckid) {
            lpcs->cbInfoChunks -= cbSizeThis + sizeof (DWORD) * 2;
            if (lpNext <= lpEnd) {
                if (lpEnd - lpNext)
                    CopyMemory (lpw, lpNext, lpEnd - lpNext);
                if (lpcs->cbInfoChunks) {
                    lpcs->lpInfoChunks = (LPBYTE) GlobalReAllocPtr( // shrink it
                        lpcs->lpInfoChunks,
                        lpcs->cbInfoChunks,
                        GMEM_MOVEABLE);
                }
                else {
                    if (lpcs->lpInfoChunks)
                        GlobalFreePtr (lpcs->lpInfoChunks);
                    lpcs->lpInfoChunks = NULL;
                }
                fOK = TRUE;
            }
            break;
        }
        else
            lpw = lpNext;
    }

    if (lpData == NULL || cbData == 0)         // Only deleting, get out
        return fOK;

    // Add a new entry
    cbData += cbData & 1;               // force WORD (16 bit) alignment
    cbData += sizeof(RIFF);             // add sizeof RIFF
    if (lpcs->lpInfoChunks)
        lp = GlobalReAllocPtr(lpcs->lpInfoChunks, lpcs->cbInfoChunks + cbData, GMEM_MOVEABLE);
    else
        lp = GlobalAllocPtr(GMEM_MOVEABLE, cbData);

    if (!lp)
        return FALSE;

    // build RIFF chunk in block
    //
    ((LPRIFF)(lp + lpcs->cbInfoChunks))->dwType = ckid;
    ((LPRIFF)(lp + lpcs->cbInfoChunks))->dwSize = lpcic->cbData;

    CopyMemory (lp + lpcs->cbInfoChunks + sizeof(RIFF),
                lpData,
                cbData - sizeof(RIFF));
    lpcs->lpInfoChunks = lp;
    lpcs->cbInfoChunks += cbData;

    return TRUE;
}

/*+ ProcessNextVideoBuffer
 *
 *-===============================================================*/

STATICFN BOOL _inline ProcessNextVideoBuffer (
    LPCAPSTREAM  lpcs,
    BOOL         fStopping,
    LPUINT       lpuError,
    LPVIDEOHDR * plpvhDraw,
    LPBOOL       lpbPending)
{
    LPVIDEOHDR lpvh;

    *lpuError = 0;
    *plpvhDraw = NULL;
    *lpbPending = FALSE;

    lpvh = lpcs->alpVideoHdr[lpcs->iNextVideo];
    if (!(lpvh->dwFlags & VHDR_DONE)) {
        return fStopping;
    }

   #if defined CHICAGO
    {
    LPTHKVIDEOHDR lptvh = (LPVOID)lpvh;

    #ifdef JMK_HACK_CHECKHDR
     if (IsBadWritePtr (lptvh, sizeof(*lptvh)) ||
        HIWORD(lptvh->vh.lpData) != HIWORD(lptvh->p16Alloc))
        {
        OutputDebugStringA(DEBUGLINE "trouble with video hdr\r\n");

        AuxDebugEx (0, DEBUGLINE "iNext=%d, ptvh=%X\r\n", lpcs->iNextVideo, lptvh);
        AuxDebugDump (0, lptvh, sizeof(*lptvh));
        AuxDebugEx (0, DEBUGLINE "alpVideoHdrs=%X\r\n", lpcs->alpVideoHdr);
        AuxDebugDump (0, lpcs->alpVideoHdr, sizeof(lpcs->alpVideoHdr[0]) * 8);

        INLINE_BREAK;

        return TRUE;
        }
    #endif

    // Swap the linear pointer back (was swapped in vidxAddBuffer)
    //
    lptvh->vh.lpData = (LPVOID)(ROUNDUPTOSECTORSIZE(lptvh->p32Buff, lpcs->dwBytesPerSector) + sizeof(RIFF));
    }
   #endif

    if (lpvh->dwBytesUsed)
    {
        DWORD  dwTime;
        DWORD  dwBytesUsed = lpvh->dwBytesUsed;
        BOOL   fKeyFrame = lpvh->dwFlags & VHDR_KEYFRAME;
        LPVOID lpData = lpvh->lpData;

        // get expected time for this frame in milliseconds
        //
        dwTime = MulDiv (lpcs->dwVideoChunkCount + 1,
                         lpcs->sCapParms.dwRequestMicroSecPerFrame,
                         1000);

       #ifdef NEW_COMPMAN
        //
        // We are automatically compressing during capture, so
        // compress the frame before we pass it on to be written
        //
        if (lpcs->CompVars.hic)
        {
            LPRIFF priff;

            dwBytesUsed = 0;
            lpData = ICSeqCompressFrame(&lpcs->CompVars, 0,
                                        lpvh->lpData,
                                        &fKeyFrame,
                                        &dwBytesUsed);

            priff = ((LPRIFF)lpData) -1;
            priff->dwType = MAKEAVICKID(cktypeDIBbits, 0);
            priff->dwSize = dwBytesUsed;
        }
       #endif // NEW_COMPMAN

        // do video stream callback for this frame
        //
        if (lpcs->CallbackOnVideoStream)
            lpcs->CallbackOnVideoStream (lpcs->hwnd, lpvh);
        lpvh->dwFlags &= ~VHDR_DONE;

        // if we are not capturing to disk, just increment
        // the 'chunk count' (i.e. frame count?) and go on
        // otherwise we want to queue the frame up to write
        // here
        //
        if ( ! (lpcs->fCaptureFlags & CAP_fCapturingToDisk))
        {
            // Warning: Kludge to create frame chunk count when net capture
            // follows.
            ++lpcs->dwVideoChunkCount;
        }
        else
        {
            int nAppendDummyFrames = 0;

            // if the expected time for this frame is less than the
            // timestamp for the frame.  we may have dropped some frames
            // before this frame.
            //
            if (lpcs->dwVideoChunkCount && (dwTime < lpvh->dwTimeCaptured))
            {
                int  nDropCount;
                BOOL bPending;

                // calculate how many frames have been dropped.
                // NOTE: this number may be zero if dwTimeCaptured is just
                // a little bit late.
                //
                nDropCount = MulDiv(lpvh->dwTimeCaptured - dwTime,
                                    1000,
                                    lpcs->sCapParms.dwRequestMicroSecPerFrame);

               #ifdef JMK_HACK_TIMERS
                if (pTimerRiff)
                    pTimerRiff->vchd.dwDropFramesNotAppended += nDropCount;
               #endif

                // If any frames have been dropped, write them out before
                // we get back to writing the current frame.
                //
                if (nDropCount > 0)
                {
                    lpcs->dwFramesDropped += nDropCount;
                    if (! AVIWriteDummyFrames (lpcs, nDropCount, lpuError, &bPending))
                        fStopping = TRUE;
                }
            }

           #ifdef JMK_HACK_TIMERS
	    if (pTimerRiff) {
	        if (nTimerIndex == CLIPBOARDLOGSIZE)
		    nTimerIndex = 0;
	
// nTimerIndex will be OK	if ((nTimerIndex < CLIPBOARDLOGSIZE) && pTimerStuff)
		if (pTimerStuff)
		{
	
		    pCurTimerStuff = &pTimerStuff[nTimerIndex];
                    ++nTimerIndex;

		    pCurTimerStuff->nFramesAppended = 0;
		    pCurTimerStuff->nDummyFrames  = (WORD)lpcs->dwFramesDropped;
		    pCurTimerStuff->dwFrameTickTime = dwTime;
		    pCurTimerStuff->dwFrameStampTime = lpvh->dwTimeCaptured;
		    pCurTimerStuff->dwVideoChunkCount = lpcs->dwVideoChunkCount;
                    pCurTimerStuff->dwTimeWritten = pcDeltaTicks(&pctWriteBase);
		    pCurTimerStuff->dwTimeToWrite = 0;
		    pCurTimerStuff->nVideoIndex = lpcs->iNextVideo;
		    pCurTimerStuff->nAudioIndex = lpcs->iNextWave;
		}
	    } // fClipboardLogging
           #endif // JMK_HACK_TIMERS

           // look ahead for dummy frames and try to
           // append them to the current frame
           //
           nAppendDummyFrames = 0;

           #define LOOKAHEAD_FOR_DUMMYS 1
           #ifdef LOOKAHEAD_FOR_DUMMYS
           {
            int        iNext;
            LPVIDEOHDR lpvhNext;
            iNext = lpcs->iNextVideo+1;
            if (iNext >= lpcs->iNumVideo)
                iNext = 0;

            // is the next frame done already?  if so
            // we can append any dropped frames to the end of
            // this frame before we write it out
            //
            lpvhNext = lpcs->alpVideoHdr[iNext];
            if (lpvhNext->dwFlags & VHDR_DONE)
            {
		// Recalculate the current time, which may have
		// changed if dummy frames were inserted above
		dwTime = MulDiv (lpcs->dwVideoChunkCount + 1,
                         lpcs->sCapParms.dwRequestMicroSecPerFrame,
                         1000);
	
                nAppendDummyFrames =
                    MulDiv (lpvhNext->dwTimeCaptured - dwTime,
                            1000,
                            lpcs->sCapParms.dwRequestMicroSecPerFrame);

                if ((--nAppendDummyFrames) < 0)
                    nAppendDummyFrames = 0;
		else {
		    AuxDebugEx(3, DEBUGLINE "Appending %d dummy frames", nAppendDummyFrames);
		}

                lpcs->dwFramesDropped += nAppendDummyFrames;

               #ifdef JMK_HACK_TIMERS
                if (pTimerRiff) {
                    pTimerRiff->vchd.dwDropFramesAppended += nAppendDummyFrames;
		    pCurTimerStuff->nFramesAppended = nAppendDummyFrames;
		}
               #endif
            }
           }
           #endif

            if ( ! AVIWriteVideoFrame (lpcs,
                                       lpData,
                                       dwBytesUsed,
                                       fKeyFrame,
                                       lpcs->iNextVideo,
                                       nAppendDummyFrames,
                                       lpuError, lpbPending))
                fStopping = TRUE;

           #ifdef JMK_HACK_TIMERS
            if (pCurTimerStuff)
            {
                pCurTimerStuff->dwTimeToWrite = pcDeltaTicks(&pctWriteBase);
                pCurTimerStuff->bPending = *lpbPending;
            }
           #endif
        }

    }

    // return lpvh to the caller so that the frame can be
    // drawn (time permitting)
    //
    *plpvhDraw = lpvh;

    // increment the next Video buffer pointer
    //
    if (++lpcs->iNextVideo >= lpcs->iNumVideo)
        lpcs->iNextVideo = 0;

    return fStopping;
}

/*+ ProcessAudioBuffers
 *
 *-===============================================================*/

STATICFN BOOL _inline ProcessAudioBuffers (
    LPCAPSTREAM lpcs,
    BOOL        fStopping,
    LPUINT      lpuError)
{
    int       iLastWave;
    UINT      ii;
    LPWAVEHDR lpwh;

    *lpuError = 0;
    assert (lpcs->sCapParms.fCaptureAudio);

    // if all buffers are done, we have broke audio.
    //
    iLastWave = lpcs->iNextWave == 0 ? lpcs->iNumAudio -1 : lpcs->iNextWave-1;
    if (!fStopping && lpcs->alpWaveHdr[iLastWave]->dwFlags & WHDR_DONE)
        lpcs->fAudioBreak = TRUE;

    // process all done buffers, but no more than iNumAudio at one
    // pass (to avoid getting stuck here forever)
    //
    for (ii = 0; ii < (UINT)lpcs->iNumAudio; ++ii)
    {
        BOOL bPending;

        // if the next buffer is not done, break out of the loop
        // and return to the caller
        //
        lpwh = lpcs->alpWaveHdr[lpcs->iNextWave];
        if (!(lpwh->dwFlags & WHDR_DONE))
            break;
        lpwh->dwFlags &= ~WHDR_DONE;

        // is there any data in the buffer ?
        // if so, do wave stream callback, then write the
        // buffer
        //
        bPending = FALSE;
        if (lpwh->dwBytesRecorded)
        {
            if (lpcs->CallbackOnWaveStream)
               lpcs->CallbackOnWaveStream (lpcs->hwnd, lpwh);

            if ( ! (lpcs->fCaptureFlags & CAP_fCapturingToDisk))
            {
                lpcs->dwWaveChunkCount++;
                lpcs->dwWaveBytes += lpwh->dwBytesRecorded;
            }
            else
            {
                // write the audio buffer, bPending will be true
                // if the write will complete asynchronously
                //
                if ( ! AVIWriteAudio (lpcs, lpwh, lpcs->iNextWave,
                                      lpuError, &bPending))
                    fStopping = TRUE;
            }
        }

        // if we are not writing async, we can put the buffer
        // back on the wave driver's queue now
        //
        if ( ! bPending)
        {
            lpwh->dwBytesRecorded = 0;
	    AuxDebugEx(3, DEBUGLINE "Calling waveInAddBuffer for address %8x", lpwh);
            if (waveInAddBuffer(lpcs->hWaveIn, lpwh, sizeof(WAVEHDR)))
            {
                fStopping = TRUE;
                *lpuError = IDS_CAP_WAVE_ADD_ERROR;
            }
        }

        // increment the next wave buffer pointer
        //
        if (++lpcs->iNextWave >= lpcs->iNumAudio)
            lpcs->iNextWave = 0;
    }

    return fStopping;
}

/*+ ProcessAsyncIOBuffers
 *
 *-===============================================================*/

STATICFN BOOL _inline ProcessAsyncIOBuffers (
    LPCAPSTREAM lpcs,
    BOOL        fStopping,
    LPUINT      lpuError)
{
    UINT      ii;
    struct _avi_async * lpah;

    // if there are no async buffer headers, there is nothing to do!
    //
    *lpuError = 0;
    assert (lpcs->pAsync);

    // process all done buffers, but no more than iNumAsync
    //
    for (ii = 0; ii < lpcs->iNumAsync; ++ii)
    {
        DWORD dwUsed;

        // if this async header has never been used,
        // we are done
        //
        lpah = &lpcs->pAsync[lpcs->iNextAsync];
        assert (lpah->uType);

        AuxDebugEx (2, DEBUGLINE "processing async io buffer %d off=%x\r\n",
                    lpcs->iNextAsync, lpah->ovl.Offset);

        // if the next buffer is not done, or failed break
        // out of the loop
        //
        if ( ! GetOverlappedResult (lpcs->hFile, &lpah->ovl, &dwUsed, FALSE))
        {
            if (GetLastError() != ERROR_IO_INCOMPLETE)
            {
                *lpuError = IDS_CAP_FILE_WRITE_ERROR;
                fStopping = TRUE;
            }
            break;
        }
	//#pragma message (SQUAWK "get rid of this hack when GetOverlappedResult is fixed!")	
	if (lpah->ovl.Internal != 0) {
	    // if internal is non-zero 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.
	    break;
	}

        // the buffer is done, so now we need to queue the wave/video
        // buffer back to the wave/video driver
        //

        switch (lpah->uType)
        {
            case ASYNC_BUF_VIDEO:
            {
                LPVIDEOHDR lpvh = lpcs->alpVideoHdr[lpah->uIndex];
               #if defined CHICAGO
                if (vidxAddBuffer(lpcs->hVideoIn, lpvh, sizeof (VIDEOHDR)))
               #else
		AuxDebugEx(3, DEBUGLINE "Queueing video buffer lpvh=%x (index %d)\r\n", lpvh, lpah->uIndex);
                if (videoStreamAddBuffer(lpcs->hVideoIn, lpvh, sizeof (VIDEOHDR)))
               #endif
                {
                    fStopping = TRUE;
                    *lpuError = IDS_CAP_VIDEO_ADD_ERROR;
                }
            }
            break;

            case ASYNC_BUF_AUDIO:
            {
                LPWAVEHDR lpwh = lpcs->alpWaveHdr[lpah->uIndex];
                lpwh->dwBytesRecorded = 0;
		AuxDebugEx(3, DEBUGLINE "Queueing audio buffer lpwh=%x (index %d)\r\n", lpwh, lpah->uIndex);
                if (waveInAddBuffer (lpcs->hWaveIn, lpwh, sizeof(WAVEHDR)))
                {
                    fStopping = TRUE;
                    *lpuError = IDS_CAP_WAVE_ADD_ERROR;
                }
            }
            break;

            //case ASYNC_BUF_DROP:
            //{
            //}
            //break;
        }

        lpah->uType = 0;
        lpah->uIndex = 0;

        // increment to the next async io buffer
        //
        if (++lpcs->iNextAsync >= lpcs->iNumAsync)
            lpcs->iNextAsync = 0;
    }

    return fStopping;
}

/*+ ShowCompletionStatus
 *
 *-===============================================================*/

STATICFN void ShowCompletionStatus (
    LPCAPSTREAM lpcs,
    BOOL        fCapturedOK)
{
    // Notify if there was an error while recording
    //
    if ( ! fCapturedOK)
        errorUpdateError (lpcs, IDS_CAP_RECORDING_ERROR);

    // put up completion message on status line
    //
    if (lpcs->fCaptureFlags & CAP_fCapturingToDisk)
    {
        DWORD dw;

        // The muldiv32 doesn't give 0 if numerator is zero
        dw = 0;
        if (lpcs->dwVideoChunkCount)
            dw = muldiv32(lpcs->dwVideoChunkCount,1000000,lpcs->dwTimeElapsedMS);

        if (lpcs->sCapParms.fCaptureAudio)
        {
            // "Captured %d.%03d sec.  %ld frames (%ld dropped) (%d.%03d fps).  %ld audio bytes (%d.%03d sps)"
            statusUpdateStatus(lpcs, IDS_CAP_STAT_VIDEOAUDIO,
                  (UINT)(lpcs->dwTimeElapsedMS/1000),
                  (UINT)(lpcs->dwTimeElapsedMS%1000),
                  lpcs->dwVideoChunkCount,
                  lpcs->dwFramesDropped,
                  (UINT)(dw / 1000),
                  (UINT)(dw % 1000),
                  lpcs->dwWaveBytes,
                  (UINT) lpcs->lpWaveFormat->nSamplesPerSec / 1000,
                  (UINT) lpcs->lpWaveFormat->nSamplesPerSec % 1000);
        }
        else
        {
            // "Captured %d.%03d sec.  %ld frames (%ld dropped) (%d.%03d fps)."
            statusUpdateStatus(lpcs, IDS_CAP_STAT_VIDEOONLY,
                  (UINT)(lpcs->dwTimeElapsedMS/1000),
                  (UINT)(lpcs->dwTimeElapsedMS%1000),
                  lpcs->dwVideoChunkCount,
                  lpcs->dwFramesDropped,
                  (UINT)(dw / 1000),
                  (UINT)(dw % 1000));
        }
    } // endif capturing to disk (no warnings or errors if to net)

    // if capture was successful, warn the user about various abnormal
    // conditions.
    //
    if (fCapturedOK)
    {
        if (lpcs->dwVideoChunkCount == 0)
        {
            // No frames captured, warn user that interrupts are probably not enabled.
            errorUpdateError (lpcs, IDS_CAP_NO_FRAME_CAP_ERROR);
        }
        else if (lpcs->sCapParms.fCaptureAudio && lpcs->dwWaveBytes == 0)
        {
            // No audio captured, warn user audio card is hosed
            errorUpdateError (lpcs, IDS_CAP_NO_AUDIO_CAP_ERROR);
        }
        else if (lpcs->sCapParms.fCaptureAudio && lpcs->fAudioBreak)
        {
            // some of the audio was dropped
            if(lpcs->CompVars.hic) {
		errorUpdateError (lpcs, IDS_CAP_AUDIO_DROP_COMPERROR);
	    } else {
		errorUpdateError (lpcs, IDS_CAP_AUDIO_DROP_ERROR);
	    }
        }
        else if (lpcs->fCaptureFlags & CAP_fCapturingToDisk)
        {
            DWORD dwPctDropped;

            assert (lpcs->dwVideoChunkCount);
            dwPctDropped = 100 * lpcs->dwFramesDropped / lpcs->dwVideoChunkCount;
            //
            // dropped > 10% (default) of the frames
            //
            if (dwPctDropped > lpcs->sCapParms.wPercentDropForError)
                errorUpdateError (lpcs, IDS_CAP_STAT_FRAMESDROPPED,
                      lpcs->dwFramesDropped,
                      lpcs->dwVideoChunkCount,
                      (UINT)(muldiv32(lpcs->dwFramesDropped,10000,lpcs->dwVideoChunkCount)/100),
                      (UINT)(muldiv32(lpcs->dwFramesDropped,10000,lpcs->dwVideoChunkCount)%100)/10
                      );
        }
    }
}

/*
 *  AVI Capture
 *      This is the main streaming capture loop for both audio and
 * video.  It will first init all buffers and drivers and then go into a
 * loop checking for buffers to be filled.  When a buffer is filled then
 * the data for it is written out.
 * Afterwards it cleans up after itself (frees buffers etc...)
 * Returns: 0 on success, else error code
 */
void FAR PASCAL _LOADDS AVICapture1(LPCAPSTREAM lpcs)
{
    BOOL        fCapturedOK = TRUE;
    BOOL        fStopping;         // True when finishing capture
    BOOL        fStopped;          // True if driver notified to stop
    TCHAR       ach[128];
    TCHAR       achMsg[128];
    UINT        wError;         // Error String ID
    LPVIDEOHDR  lpVidHdr;
    LPWAVEHDR   lpWaveHdr;
    DWORD       dwTimeStarted;  // When did we start in milliseconds
    DWORD       dwTimeStopped;
    DWORD       dwTimeToStop;   // Lesser of MCI capture time or frame limit
    BOOL        fTryToPaint = FALSE;
    BOOL        fTryToPaintAgain = FALSE;
    HDC         hdc;
    HPALETTE    hpalT;
    HCURSOR     hOldCursor;
    RECT        rcDrawRect;
    CAPINFOCHUNK cic;
    DWORD       dwOldPrio;
    BOOL        bVideoWritePending;
    LPVIDEOHDR  lpvhDraw;

    lpcs->fCaptureFlags |= CAP_fCapturingNow;
    lpcs->dwReturn = DV_ERR_OK;

    hOldCursor = SetCursor(lpcs->hWaitCursor);

    statusUpdateStatus(lpcs, IDS_CAP_BEGIN);  // Always the first message

    // If not 1 Meg. free, give it up!!!
    if (GetFreePhysicalMemory () < (1024L * 1024L)) {
        errorUpdateError (lpcs, IDS_CAP_OUTOFMEM);
        goto EarlyExit;
    }

    statusUpdateStatus(lpcs, IDS_CAP_STAT_CAP_INIT);

    // Try painting the DIB only if Live window
    fTryToPaintAgain = fTryToPaint = lpcs->fLiveWindow;

    if (fTryToPaint) {
        hdc = GetDC(lpcs->hwnd);
        SetWindowOrgEx(hdc, lpcs->ptScroll.x, lpcs->ptScroll.y, NULL);
        hpalT = DrawDibGetPalette (lpcs->hdd);
        if (hpalT)
            hpalT = SelectPalette( hdc, hpalT, FALSE);
        RealizePalette(hdc);
        if (lpcs->fScale)
            GetClientRect (lpcs->hwnd, &rcDrawRect);
        else
            SetRect (&rcDrawRect, 0, 0, lpcs->dxBits, lpcs->dyBits);
    }

    // -------------------------------------------------------
    //   When should capture stop?
    // -------------------------------------------------------

    // If using MCI, capture for the shorter of the MCI period,
    // or the capture limit

    if (lpcs->sCapParms.fLimitEnabled)
        dwTimeToStop = (DWORD) ((DWORD) 1000 * lpcs->sCapParms.wTimeLimit);
    else
        dwTimeToStop = (DWORD) -1L; // very large

    if (lpcs->sCapParms.fMCIControl) {
        DWORD dwTime;

        // if MCI stop time not given, use lpcs->sCapParms.wTimeLimit
        if (lpcs->sCapParms.dwMCIStopTime == lpcs->sCapParms.dwMCIStartTime)
                    lpcs->sCapParms.dwMCIStopTime = lpcs->sCapParms.dwMCIStartTime +
                    (DWORD) ((DWORD)1000 * lpcs->sCapParms.wTimeLimit);

        dwTime = lpcs->sCapParms.dwMCIStopTime - lpcs->sCapParms.dwMCIStartTime;

        if (lpcs->sCapParms.fLimitEnabled)
            dwTimeToStop = min (dwTime, dwTimeToStop);
        else
            dwTimeToStop = dwTime;
    }

    //
    // never ever try to capture more than the index size!
    //
    if (lpcs->fCaptureFlags & CAP_fCapturingToDisk)
    {
        DWORD dwTime = MulDiv (lpcs->sCapParms.dwIndexSize,
                               lpcs->sCapParms.dwRequestMicroSecPerFrame,
                               1000l);

        dwTimeToStop = min (dwTime, dwTimeToStop);
    }

    // if doing MCI capture, initialize MCI device.  if init fails
    // go straight to the exit code
    //
    if (lpcs->sCapParms.fMCIControl)
    {
        if ( ! MCIDeviceOpen (lpcs) ||
             ! MCIDeviceSetPosition (lpcs, lpcs->sCapParms.dwMCIStartTime))
        {
            fCapturedOK = FALSE;
            errorUpdateError (lpcs, IDS_CAP_MCI_CONTROL_ERROR);
            statusUpdateStatus(lpcs, 0);    // Clear status
            goto EarlyExit;
        }
    }

    //
    // If we're compressing while capturing, warm up the compressor
    //
   #ifdef NEW_COMPMAN
    if (lpcs->CompVars.hic)
    {
        if ( ! ICSeqCompressFrameStart (&lpcs->CompVars, lpcs->lpBitsInfo))
        {
            // !!! We're in trouble here!
            dprintf("ICSeqCompressFrameStart failed !!!\n");
            errorUpdateError (lpcs, IDS_CAP_COMPRESSOR_ERROR);
            goto EarlyExit;
        }

        // Kludge, offset the lpBitsOut ptr
        // Compman allocates the compress buffer too large by
        // 2048 + 16 so we will still have room
        //
        ((LPBYTE)lpcs->CompVars.lpBitsOut) += 8;

        assert(lpcs->CompVars.lpbiOut != NULL);
    }
   #endif

    // -------------------------------------------------------
    //  Open the output file
    // -------------------------------------------------------

    if (lpcs->fCaptureFlags & CAP_fCapturingToDisk) {
	if (!CapFileInit(lpcs))
	{
	    errorUpdateError (lpcs, IDS_CAP_FILE_OPEN_ERROR);
	    goto EarlyExit;
	}
    } else {
        AuxDebugEx (3, DEBUGLINE "Setting dwBytesPerSector to %d\r\n",DEFAULT_BYTESPERSECTOR);
        lpcs->dwBytesPerSector=DEFAULT_BYTESPERSECTOR;
    }

   #ifdef JMK_HACK_TIMERS
    // Allocate memory for logging capture results to the clipboard if requested
    if (GetProfileIntA ("Avicap32", "ClipboardLogging", FALSE))
    {
        AuxDebugEx (2, DEBUGLINE "ClipboardLogging Enabled\r\n");
        InitPerformanceCounters();
        pcBegin(), pctWriteBase = pc.base;

	hMemTimers = GlobalAlloc(GHND | GMEM_ZEROINIT,
                             sizeof(struct _timerriff) +
                             sizeof(struct _timerstuff) * CLIPBOARDLOGSIZE);

	if (hMemTimers && (pTimerRiff = GlobalLock (hMemTimers)))
	    ;
	else if (hMemTimers)
	{
	    GlobalFree(hMemTimers);
	    pTimerRiff = 0;
	    pTimerStuff = 0;
	    hMemTimers = 0;
	}
	nTimerIndex = 0;
	nSleepCount = 0;
    }  // if ClipboardLogging
   #endif  // JMK_HACK_TIMERS

    // Make sure the parent has been repainted
    //
    UpdateWindow(lpcs->hwnd);

    //
    // call AVIInit() to get all the capture memory we will need
    //

    wError = IDS_CAP_AVI_INIT_ERROR;
    lpcs->hCaptureEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (lpcs->hCaptureEvent)
    {
       #ifdef CHICAGO
        lpcs->hRing0CapEvt = OpenVxDHandle (lpcs->hCaptureEvent);
        if ( ! lpcs->hRing0CapEvt)
            CloseHandle (lpcs->hCaptureEvent), lpcs->hCaptureEvent = NULL;
        else
       #endif
            wError = AVIInit(lpcs);

    }

    // if avifile init failed, cleanup and return error.
    //
    if (wError)
    {
        // Error in initalization - return
        //
        errorUpdateError (lpcs, wError);
        AVIFini(lpcs);
        AVIFileFini(lpcs, TRUE, TRUE);
        statusUpdateStatus(lpcs, 0);    // Clear status
        goto EarlyExit;
    }

    // Click OK to capture string (must follow AVIInit)
    //
    LoadString(lpcs->hInst, IDS_CAP_SEQ_MSGSTART, ach, NUMELMS(ach));
    wsprintf(achMsg, ach, (LPBYTE)lpcs->achFile);

    // clear status
    //
    statusUpdateStatus(lpcs, 0);

    // -------------------------------------------------------
    //   Ready to go, make the user click OK?
    // -------------------------------------------------------

    if (lpcs->sCapParms.fMakeUserHitOKToCapture && (lpcs->fCaptureFlags & CAP_fCapturingToDisk))
    {
        UINT idBtn;

        idBtn = MessageBox (lpcs->hwnd, achMsg, TEXT(""),
                            MB_OKCANCEL | MB_ICONEXCLAMATION);

        if (idBtn == IDCANCEL)
        {
            AVIFini(lpcs);
            AVIFileFini (lpcs, TRUE, TRUE);
            statusUpdateStatus (lpcs, 0);
            goto EarlyExit;
        }
    }

    // update the status, so the user knows how to stop
    //
    statusUpdateStatus(lpcs, IDS_CAP_SEQ_MSGSTOP);
    UpdateWindow(lpcs->hwnd);

    lpcs->fCaptureFlags |= CAP_fCapturingNow;

    // query async key states to 'reset' them to current values
    //
    GetAsyncKeyState(lpcs->sCapParms.vKeyAbort);
    GetAsyncKeyState(VK_ESCAPE);
    GetAsyncKeyState(VK_LBUTTON);
    GetAsyncKeyState(VK_RBUTTON);

    // Insert the digitization time
    // strings written to the file should be ascii, since this is
    // an ascii file format.
    //
    //
    // no point in pulling in the whole C runtime just to get a silly
    // timestamp, so we just cook the system time into ascii right here.
    //
    {
    SYSTEMTIME time;
    static char szDay[] = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat\0";
    static char szMonth[] = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0Sep\0Oct\0Nov\0Dec\0";
    char sz[30];

    GetLocalTime (&time);

    //example: Fri Apr 29 12:25:12 1994
    wsprintfA(sz, "%s %s %2d %2d:%02d:%02d %4d",
              szDay + time.wDayOfWeek * 4,
              szMonth + time.wMonth * 4,
              time.wDay, time.wHour, time.wMinute, time.wSecond, time.wYear);

    cic.fccInfoID = mmioFOURCC ('I','D','I','T');
    cic.lpData = sz;
    cic.cbData  = 26;
    SetInfoChunk (lpcs, &cic);
    }

    // -------------------------------------------------------
    //   Start MCI, Audio, and video streams
    // -------------------------------------------------------

    // Callback will preroll, then return on frame accurate postion
    // The 1 indicates recording is about to start
    // Callback can return FALSE to exit without capturing
    //
    if (lpcs->CallbackOnControl &&
        !lpcs->CallbackOnControl(lpcs->hwnd, CONTROLCALLBACK_PREROLL))
    {
        AVIFini(lpcs);
        AVIFileFini(lpcs, TRUE, TRUE);
        statusUpdateStatus(lpcs, 0);
        goto EarlyExit;
    }

    dwOldPrio = GetThreadPriority(GetCurrentThread());
    if (dwOldPrio != THREAD_PRIORITY_HIGHEST)
        SetThreadPriority (GetCurrentThread(), THREAD_PRIORITY_HIGHEST);

   #ifdef JMK_HACK_TIMERS
    if (pTimerRiff)
    {
	UINT ii;

        pTimerRiff->fccRIFF = RIFFTYPE('RIFF'); //MAKEFOURCC('R','I','F','F');
	pTimerRiff->cbTotal = sizeof(struct _timerriff) - 8 +
	    		  sizeof(struct _timerstuff) * CLIPBOARDLOGSIZE;
        pTimerRiff->fccJMKD = RIFFTYPE('JMKD'); //MAKEFOURCC('J','M','K','D');
        pTimerRiff->fccVCHD = RIFFTYPE('VCHD'); //MAKEFOURCC('V','C','H','D');
	
	pTimerRiff->cbVCHD  = sizeof(struct _vchd);
	pTimerRiff->vchd.nPrio = GetThreadPriority(GetCurrentThread());
	pTimerRiff->vchd.bmih = lpcs->lpBitsInfo->bmiHeader;
	pTimerRiff->vchd.cap  = lpcs->sCapParms;
	pTimerRiff->vchd.dwDropFramesAppended = 0;
	pTimerRiff->vchd.dwDropFramesNotAppended = 0;
        pTimerRiff->vchd.dwTimerFrequency = pcGetTickRate();
	
	for (ii = 0; ii < NUMELMS(pTimerRiff->vchd.atvh); ++ii)
	{
	    if (lpcs->alpVideoHdr[ii])
            {
	        struct _thkvideohdr * ptvh = (LPVOID)lpcs->alpVideoHdr[ii];
               #ifndef CHICAGO
                assert (sizeof(CAPVIDEOHDR) == sizeof(*ptvh));
               #endif
                pTimerRiff->vchd.atvh[ii] = *ptvh;
                pTimerRiff->vchd.nMaxVideoBuffers = ii;
            }
        }
	
        pTimerRiff->fccChunk = RIFFTYPE('VCAP'); //MAKEFOURCC('V','C','A','P');
	pTimerRiff->cbChunk = pTimerRiff->cbTotal - sizeof(*pTimerRiff);
	
	pTimerStuff = (LPVOID)(pTimerRiff + 1);
	pCurTimerStuff = &pTimerStuff[0];
    }  // fClipboardLogging
   #endif  // JMK_HACK_TIMERS

    // make sure that the fat is loaded before we begin capturing
    //
    AVIPreloadFat (lpcs);

    // start the MCI device
    //
    if (lpcs->sCapParms.fMCIControl)
        MCIDevicePlay (lpcs);

    dwTimeStarted = timeGetTime();

    // start audio & video streams
    //
    if (lpcs->sCapParms.fCaptureAudio)
        waveInStart(lpcs->hWaveIn);
    videoStreamStart(lpcs->hVideoIn);

    // -------------------------------------------------------
    //   MAIN CAPTURE LOOP
    // -------------------------------------------------------

    fCapturedOK=TRUE;
    fStopping = FALSE;    // TRUE when we need to stop
    fStopped = FALSE;     // TRUE if drivers notified we have stopped
    lpcs->dwTimeElapsedMS = 0;

    assert (lpcs->iNextVideo == 0);
    if (lpcs->sCapParms.fCaptureAudio) {
	assert (lpcs->iNextWave == 0);
	lpWaveHdr = lpcs->alpWaveHdr[lpcs->iNextWave];
	// lpWaveHdr is only interesting when we capture audio
    }

    lpVidHdr = lpcs->alpVideoHdr[lpcs->iNextVideo];

    DPF("Start of main capture loop");

    for (;;)
    {

        // The INTEL driver uses the GetError message to
        // process buffers, so call it often...
        // FIX JAYBO        videoStreamGetError (lpcs->hVideoIn, &dwStreamError, &dwDriverDropCount);


        // if there are no buffers to process, we either wait
        // or leave the loop forever (depending on whether we expect
        // more buffers to be done in the future)
        //
        if (!(lpVidHdr->dwFlags & VHDR_DONE) &&
            !(lpcs->sCapParms.fCaptureAudio
	        && (lpWaveHdr->dwFlags & WHDR_DONE)))
        {
            if (fStopped)
                break;

           #ifdef JMK_HACK_TIMERS
            if (pCurTimerStuff)
            {
               pCurTimerStuff->nSleepCount = ++nSleepCount;
               pCurTimerStuff->dwSleepBegin = pcGetTicks();
            }
           #endif

	    AuxDebugEx(2,DEBUGLINE "***** Waiting for something interesting to happen while capturing\r\n");
            WaitForSingleObject (lpcs->hCaptureEvent, 300);

           #ifdef JMK_HACK_TIMERS
            if (pCurTimerStuff)
	    {
               pCurTimerStuff->dwSleepEnd = pcGetTicks();
	    }
           #endif
        }

        // What time is it?
        lpcs->dwTimeElapsedMS = timeGetTime() - dwTimeStarted;

        // -------------------------------------------------------
        //        Is video buffer ready to be written?
        // -------------------------------------------------------

        if (fStopping = ProcessNextVideoBuffer (lpcs,
                                                fStopping,
                                                &wError,
                                                &lpvhDraw,  // captured frame to draw if time permits
                                                &bVideoWritePending)) // TRUE if Write pending on lpvhDraw
        {
            AuxDebugEx (1, DEBUGLINE "ProcessVideo stopping\r\n");
            if (wError)
            {
                AuxDebugEx (1, DEBUGLINE "ProcessVideo return error %d\r\n", wError);
                errorUpdateError (lpcs, wError);
                fCapturedOK = FALSE;
                break;
            }
        }
        lpVidHdr = lpcs->alpVideoHdr[lpcs->iNextVideo];

        // if there is still more time, (or at least every 100 frames)
        // show status if we're not ending the capture
        //
        if (!fStopping &&
            (lpcs->fCaptureFlags & CAP_fCapturingToDisk) &&
            (!(lpVidHdr->dwFlags & VHDR_DONE) ||
              (lpcs->dwVideoChunkCount && (lpcs->dwVideoChunkCount % 100 == 0))))
        {
            // Captured %ld frames (Dropped %ld) %d.%03d sec. Hit Escape to Stop
            //
            statusUpdateStatus(lpcs, IDS_CAP_STAT_VIDEOCURRENT,
                               lpcs->dwVideoChunkCount,
                               lpcs->dwFramesDropped,
                               (UINT)(lpcs->dwTimeElapsedMS/1000),
                               (UINT)(lpcs->dwTimeElapsedMS%1000));
        }

        // If the yield callback returns FALSE, abort
        //
        if (lpcs->CallbackOnYield && !lpcs->CallbackOnYield (lpcs->hwnd))
            fStopping = TRUE;

       #if 0 // this is a 16 bit ism??
        // Don't do peekMessage yield for ACM
        if (lpcs->sCapParms.fYield) {
            MSG msg;

            if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) {
                // Kludge to get rid of timers from lpcs->hwnd
                if (msg.message == WM_TIMER && msg.hwnd == lpcs->hwnd)
                    ;
                else {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            }
        }
       #endif

        // Outside routine is handling when to stop
        // The CONTROLCALLBACK_CAPTURING indicates we're asking when to stop
        //
        if (lpcs->CallbackOnControl &&
            !lpcs->CallbackOnControl (lpcs->hwnd, CONTROLCALLBACK_CAPTURING))
            fStopping = TRUE;

        // -------------------------------------------------------
        //        Is audio buffer ready to be written?
        // -------------------------------------------------------

        if (lpcs->sCapParms.fCaptureAudio) {
            if (fStopping = ProcessAudioBuffers (lpcs, fStopping, &wError))
            {
                AuxDebugEx (1, DEBUGLINE "ProcessAudio stopping\r\n");
                if (wError)
                {
                    AuxDebugEx (1, DEBUGLINE "ProcessAudio return error %d\r\n", wError);
                    errorUpdateError (lpcs, wError);
                    fCapturedOK = FALSE;
                    break;
                }
            }
	    lpWaveHdr = lpcs->alpWaveHdr[lpcs->iNextWave];
	}

        // if we are not writing the frame async, we can put the video buffer
        // back on the video driver's queue now
        //
        if (lpvhDraw)
        {

            // if the next video header is not ready yet, and
            // we have no outstanding io buffers (in async mode) draw
            // the current one
            //
            if ( !(lpVidHdr->dwFlags & VHDR_DONE) &&
                (!lpcs->pAsync || (lpcs->iNextAsync == lpcs->iLastAsync)) &&
                lpvhDraw->dwBytesUsed)
            {
                AuxDebugEx (4, DEBUGLINE "time enough to draw!\r\n");
                if (fTryToPaintAgain &&
                    lpcs->dwVideoChunkCount &&
                    lpvhDraw->dwFlags & VHDR_KEYFRAME)
                {
                    fTryToPaintAgain = DrawDibDraw(lpcs->hdd, hdc,
                            0, 0,
                            rcDrawRect.right - rcDrawRect.left,
                            rcDrawRect.bottom - rcDrawRect.top,
                            /*lpcs->dxBits, lpcs->dyBits, */
                            (LPBITMAPINFOHEADER)lpcs->lpBitsInfo,
                            lpvhDraw->lpData, 0, 0, -1, -1,
                            DDF_SAME_HDC | DDF_SAME_DIB | DDF_SAME_SIZE);
                }
            }

            // if there is not a write pending for the draw frame
            // put it back into the video drivers queue now
            //
            if ( ! bVideoWritePending)
            {
		AuxDebugEx(3, DEBUGLINE "Queueing video buffer, lpvh=%8x", lpvhDraw);

                // return the emptied buffer to the que
                //
               #if defined CHICAGO
                if (vidxAddBuffer(lpcs->hVideoIn, lpvhDraw, sizeof (VIDEOHDR)))
               #else

                if (videoStreamAddBuffer(lpcs->hVideoIn, lpvhDraw, sizeof (VIDEOHDR)))
               #endif
                {
                    AuxDebugEx (2, DEBUGLINE "Failed to Queue Video buffer %08x\r\n", lpvhDraw);
                    errorUpdateError (lpcs, IDS_CAP_VIDEO_ADD_ERROR);
                    fCapturedOK = FALSE;
                    fStopping = TRUE;
                    break;
                }
            }
        }

        // ------------------------------------------------------------
        //        Any completed I/O buffers?
        // ------------------------------------------------------------

        if (lpcs->pAsync)
            if (fStopping = ProcessAsyncIOBuffers (lpcs, fStopping, &wError))
            {
                if (wError)
                {
                    errorUpdateError (lpcs, wError);
                    fCapturedOK = FALSE;
                    break;
                }
            }

        // -------------------------------------------------------
        //        is there any reason to stop?
        // -------------------------------------------------------

        if (!fStopping)
        {
            if (lpcs->sCapParms.vKeyAbort &&
                (GetAsyncKeyState(lpcs->sCapParms.vKeyAbort & 0x00ff) & 0x0001))
            {
                BOOL fT = TRUE;
                if (lpcs->sCapParms.vKeyAbort & 0x8000)  // Ctrl?
                    fT = fT && (GetAsyncKeyState(VK_CONTROL) & 0x8000);
                if (lpcs->sCapParms.vKeyAbort & 0x4000)  // Shift?
                    fT = fT && (GetAsyncKeyState(VK_SHIFT) & 0x8000);
                fStopping = fT;      // User aborts
            }
            if (lpcs->sCapParms.fAbortLeftMouse && (GetAsyncKeyState(VK_LBUTTON) & 0x0001))
                fStopping = TRUE;      // User aborts
            if (lpcs->sCapParms.fAbortRightMouse && (GetAsyncKeyState(VK_RBUTTON) & 0x0001))
                fStopping = TRUE;      // User aborts
            if ((lpcs->fCaptureFlags & CAP_fAbortCapture) || (lpcs->fCaptureFlags & CAP_fStopCapture))
                fStopping = TRUE;          // Somebody above wants us to quit
            if (lpcs->dwTimeElapsedMS > dwTimeToStop)
                fStopping = TRUE;      // all done

           #ifdef DEBUG
            if (fStopping)
               AuxDebugEx (1, DEBUGLINE "user stop\r\n");
           #endif
        }

        // -------------------------------------------------------
        //        Tell all the devices to stop
        // -------------------------------------------------------

        if (fStopping)
        {
            if ( ! fStopped)
            {
                fStopped = TRUE;
                DSTATUS(lpcs, "Stopping....");

                if (lpcs->sCapParms.fCaptureAudio)
                {
                    DSTATUS(lpcs, "Stopping Audio");
                    waveInStop(lpcs->hWaveIn);
                }

                DSTATUS(lpcs, "Stopping Video");
                videoStreamStop(lpcs->hVideoIn);         // Stop everybody

                dwTimeStopped = timeGetTime ();

                if (lpcs->sCapParms.fMCIControl)
                {
                    DSTATUS(lpcs, "Stopping MCI");
                    MCIDevicePause (lpcs);
                }
                DSTATUS(lpcs, "Stopped");

                // Force cursor back to hourglass
                //
                SetCursor(lpcs->hWaitCursor);
            }

            // "Finished capture, now writing frame %ld"
            //
            if (fCapturedOK)
                statusUpdateStatus(lpcs, IDS_CAP_STAT_CAP_FINI, lpcs->dwVideoChunkCount);
            else
            {
                statusUpdateStatus(lpcs, IDS_CAP_RECORDING_ERROR2);
                break;
            }

	    // Wait for all the async IO to complete ??
	    //
        }

    // -------------------------------------------------------
    //   END OF MAIN CAPTURE LOOP
    // -------------------------------------------------------
    }

    DPF("End of main capture loop");

    // eat any keys that have been pressed
    //
    while(GetKey(FALSE))
        ;

    // flush stuff to disk, close everything etc.
    //
    AVIFini(lpcs);
    AVIFileFini(lpcs, TRUE, !fCapturedOK);

    // This is the corrected capture duration, based on audio samples
    lpcs->dwTimeElapsedMS = lpcs->dwActualMicroSecPerFrame *
                            lpcs->dwVideoChunkCount / 1000;

    // update the status line with information about the completed
    // capture, or with erroor information
    //
    ShowCompletionStatus (lpcs, fCapturedOK);

EarlyExit:
    DPF("at EarlyExit\n");

    //
    // If we we're compressing while capturing, close it down
    //
   #ifdef NEW_COMPMAN
    if (lpcs->CompVars.hic) {
        // Kludge, reset the lpBitsOut pointer
        if (lpcs->CompVars.lpBitsOut)
            ((LPBYTE) lpcs->CompVars.lpBitsOut) -= 8;
        ICSeqCompressFrameEnd(&lpcs->CompVars);
    }
   #endif

    if (fTryToPaint) {
        if (hpalT)
             SelectPalette(hdc, hpalT, FALSE);
        ReleaseDC (lpcs->hwnd, hdc);
    }

    if (lpcs->sCapParms.fMCIControl)
        MCIDeviceClose (lpcs);

    // Let the user see where capture stopped
    if ((!lpcs->fLiveWindow) && (!lpcs->fOverlayWindow))
        videoFrame( lpcs->hVideoIn, &lpcs->VidHdr );
    InvalidateRect( lpcs->hwnd, NULL, TRUE);

    SetThreadPriority (GetCurrentThread(), dwOldPrio);
    SetCursor(hOldCursor);

    lpcs->fCapFileExists = (lpcs->dwReturn == DV_ERR_OK);
    lpcs->fCaptureFlags &= ~CAP_fCapturingNow;

    statusUpdateStatus(lpcs, IDS_CAP_END);      // Always the last message

   #ifdef JMK_HACK_TIMERS
    if (pTimerRiff)
    {
        UINT    ii;
	UINT	kk;
        LPSTR   psz;
        HGLOBAL hMem;

        kk = (lpcs->dwVideoChunkCount >= CLIPBOARDLOGSIZE) ?
			CLIPBOARDLOGSIZE : nTimerIndex;

        hMem = GlobalAlloc (GHND, (16 * 5 + 2) * kk + 80);
	
        if (hMem && (psz = GlobalLock (hMem)))
        {
            pTimerRiff->vchd.dwFramesCaptured = lpcs->dwVideoChunkCount;
            pTimerRiff->vchd.dwFramesDropped = lpcs->dwFramesDropped;

            pTimerRiff->cbTotal = sizeof(struct _timerriff) - 8 +
                                  sizeof(struct _timerstuff) * nTimerIndex;
            pTimerRiff->cbChunk = pTimerRiff->cbTotal - sizeof(*pTimerRiff);

            lstrcpyA(psz, "Slot#, VideoIndex, ExpectedTime, DriverTime, AccumulatedDummyFrames, CurrentAppendedDummies");
            for (ii = 0; ii < kk; ++ii)
            {
                psz += lstrlenA(psz);
                wsprintfA(psz, "\r\n%d, %ld, %ld, %ld, %d, %d",
			  ii,
			  pTimerStuff[ii].dwVideoChunkCount,
                          pTimerStuff[ii].dwFrameTickTime,
                          pTimerStuff[ii].dwFrameStampTime,
                          pTimerStuff[ii].nDummyFrames,
			  pTimerStuff[ii].nFramesAppended
                          );
            }

            GlobalUnlock (hMem);
            GlobalUnlock (hMemTimers);

            if (OpenClipboard (lpcs->hwnd))
            {
                EmptyClipboard ();
                SetClipboardData (CF_RIFF, hMemTimers);
                SetClipboardData (CF_TEXT, hMem);
                CloseClipboard ();
            }
            else
            {
                GlobalFree (hMem);
                GlobalFree (hMemTimers);
            }
        }
        else
        {
            // Failed to allocate or lock hMem.  Cleanup.
            //
            if (hMem)
                GlobalFree(hMem);

            // Free off the timer block.  (We have not set the
            // clipboard data.)
            //
            if (hMemTimers)
            {
                GlobalUnlock(hMemTimers);
                GlobalFree(hMemTimers);
            }
        }

        hMemTimers = NULL;
        pTimerRiff = NULL;
	pTimerStuff = NULL;
	pCurTimerStuff = NULL;
    }
   #endif

    return;
}


// Returns TRUE if the capture task was created, or
// capture completed OK.

BOOL AVICapture (LPCAPSTREAM lpcs)
{
    CAPINFOCHUNK cic;
    TCHAR szSMPTE[40];
    void (WINAPI _LOADDS * pfnCapture) (LPCAPSTREAM lpcs);

    if (lpcs->fCaptureFlags & CAP_fCapturingNow)
        return IDS_CAP_VIDEO_OPEN_ERROR;

    // if there is a previous capture thread, wait for it to finish and
    // clean it up
    // -it has set fCapturingNow to FALSE, so it will end 'soon' !
    if (lpcs->hThreadCapture) {
        WaitForSingleObject(lpcs->hThreadCapture, INFINITE);

	CloseHandle(lpcs->hThreadCapture);
	lpcs->hThreadCapture = NULL;
    }

    // Turn off the STOP and ABORT capture bits
    lpcs->fCaptureFlags &= ~(CAP_fStopCapture | CAP_fAbortCapture);
    lpcs->dwReturn      = 0;

#if DONT_CLEAR_SMPTE_JAYBO
    // Prior to Win95, we always cleared out old SMPTE chunks,
    // but since Adobe may have created a chunk manually, don't
    // zap existing chunks.
    cic.fccInfoID = mmioFOURCC ('I','S','M','T');
    cic.lpData = NULL;
    cic.cbData = 0;
    SetInfoChunk (lpcs, &cic);
#endif

    // And get ready to write a SMPTE info chunk
    if (lpcs->sCapParms.fMCIControl) {
        // create SMPTE string
        TimeMSToSMPTE (lpcs->sCapParms.dwMCIStartTime, (LPTSTR) szSMPTE);
        cic.lpData = szSMPTE;
        cic.cbData = lstrlen (szSMPTE) + 1;
        cic.fccInfoID = mmioFOURCC ('I','S','M','T');
        SetInfoChunk (lpcs, &cic);
    }

    // set pfnCapture to point to the capture function of choice.
    // Use an MCI device to do step capture capture???
    // assume No MCI device, just a normal streaming capture
    //
    pfnCapture = AVICapture1;
    if (lpcs->sCapParms.fStepMCIDevice && lpcs->sCapParms.fMCIControl)
        pfnCapture = MCIStepCapture;

    // if the fYield flag is true, create a thread to do the
    // capture loop.  otherwise do the capture loop inline
    //
    if (lpcs->sCapParms.fYield)
    {
        DWORD tid;

        lpcs->hThreadCapture = CreateThread (NULL,
                                             0,
                                             (LPVOID) pfnCapture,
                                             lpcs,
                                             0,
                                             &tid);

        // if thread creation failed, turn off the capturing flag
        //
        if ( ! lpcs->hThreadCapture)
            lpcs->fCaptureFlags &= ~CAP_fCapturingNow;

        return (lpcs->hThreadCapture != NULL);
    }
    else
    {
        pfnCapture (lpcs);
        return (0 == lpcs->dwReturn);
    }
}