//  DCAP16.C
//
//  Created 31-Jul-96 [JonT]

#include <windows.h>
#define NODRAWDIB
#define NOCOMPMAN
#define NOAVIFILE
#define NOMSACM
#define NOAVIFMT
#define NOMCIWND
#define NOAVICAP
#include <vfw.h>
#include "..\inc\idcap.h"
#include "..\inc\msviddrv.h"

#define FP_SEG(fp) (*((unsigned *)&(fp) + 1))
#define FP_OFF(fp) (*((unsigned *)&(fp)))

// Equates
#define DCAP16API   __far __pascal __loadds
#define DCAP16LOCAL __near __pascal
#define DLL_PROCESS_ATTACH  1       // Not in 16-bit windows.h



#ifdef DEBUG_SPEW_VERBOSE
#define DEBUGSPEW(str)	DebugSpew((str))
#else
#define DEBUGSPEW(str)
#endif


// Structures thunked down
typedef struct _CAPTUREPALETTE
{
    WORD wVersion;
    WORD wcEntries;
    PALETTEENTRY pe[256];
} CAPTUREPALETTE, FAR* LPCAPTUREPALETTE;

// Special thunking prototypes
BOOL DCAP16API __export DllEntryPoint(DWORD dwReason,
         WORD  hInst, WORD  wDS, WORD  wHeapSize, DWORD dwReserved1,
         WORD  wReserved2);
BOOL __far __pascal thk_ThunkConnect16(LPSTR pszDll16, LPSTR pszDll32,
    WORD  hInst, DWORD dwReason);

// Helper functions
WORD DCAP16LOCAL    ReturnSel(BOOL fCS);
DWORD DCAP16LOCAL   GetVxDEntrypoint(void);
int DCAP16LOCAL     SetWin32Event(DWORD dwEvent);
void DCAP16API      FrameCallback(HVIDEO hvideo, WORD wMsg, LPLOCKEDINFO lpli,
                        LPVIDEOHDR lpvh, DWORD dwParam2);
void DCAP16LOCAL    ZeroMemory(LPSTR lp, WORD wSize);

// Globals
    HANDLE g_hInst;
    DWORD g_dwEntrypoint;

    LPLOCKEDINFO g_lpli;

//  LibMain

int
CALLBACK
LibMain(
    HINSTANCE hinst,
    WORD wDataSeg,
    WORD cbHeapSize,
    LPSTR lpszCmdLine
    )
{
    // Save global hinst
    g_hInst = hinst;
	
    // Still necessary?
    if (cbHeapSize)
        UnlockData(wDataSeg);

    return TRUE;
}


//  DllEntryPoint

BOOL
__far __pascal __export __loadds
DllEntryPoint(
    DWORD dwReason,
    WORD  hInst,
    WORD  wDS,
    WORD  wHeapSize,
    DWORD dwReserved1,
    WORD  wReserved2
    )
{
    if (!thk_ThunkConnect16("DCAP16.DLL", "DCAP32.DLL", hInst, dwReason))
    {
        DebugSpew("DllEntrypoint: thk_ThunkConnect16 failed!");
        return FALSE;
    }

    switch (dwReason)
    {
    case DLL_PROCESS_ATTACH:
        g_dwEntrypoint = GetVxDEntrypoint();
        break;
    }

    return TRUE;
}


// APIs


//  _InitializeExternalVideoStream
//      Initializes a video stream for the external channel. We don't
//      have to deal with locking or ever set a callback on this channel.

BOOL
DCAP16API
_InitializeExternalVideoStream(
    HANDLE hvideo
	)
{
    VIDEO_STREAM_INIT_PARMS vsip;

    vsip.dwMicroSecPerFrame = 0;    // Ignored by driver for this channel
    vsip.dwCallback = NULL;         // No callback for now
    vsip.dwCallbackInst = NULL;
    vsip.dwFlags = 0;
    vsip.hVideo = (DWORD)hvideo;

    return !SendDriverMessage(hvideo, DVM_STREAM_INIT,
        (DWORD) (LPVIDEO_STREAM_INIT_PARMS) &vsip,
        (DWORD) sizeof (VIDEO_STREAM_INIT_PARMS));
}


void
DCAP16API
FrameCallback(
    HVIDEO hvideo,
    WORD wMsg,
    LPLOCKEDINFO lpli,      // Note that this is our instance data
    LPVIDEOHDR lpvh,
    DWORD dwParam2
    )
{
    LPCAPBUFFER lpcbuf;
    
    if (!lpli) {
        // Connectix hack: driver doesn't pass our instance data, so we keep it global        
        lpli = g_lpli;
    }
    
    // The client can put us in shutdown mode. This means that we will not queue
    // any more buffers onto the ready queue, even if they were ready.
    // This keeps the buffers from being given back to the driver, so it will eventually
    // stop streaming. Of course, it will spew errors, but we just ignore these.
    // Shutdown mode is defined when there is no event ready to signal.
    if (!lpli->pevWait)
        return;

    // If it's not a data ready message, just set the event and get out.
    // The reason we do this is that if we get behind and start getting a stream
    // of MM_DRVM_ERROR messages (usually because we're stopped in the debugger),
    // we want to make sure we are getting events so we get restarted to handle
    // the frames that are 'stuck.'
    if (wMsg != MM_DRVM_DATA)
    {
		DEBUGSPEW("Setting hcd->hevWait - no data\r\n");
        SetWin32Event(lpli->pevWait);
        return;
    }

    //--------------------
    // Buffer ready queue:
    // We maintain a doubly-linked list of our buffers so that we can buffer up
    // multiple ready frames when the app isn't ready to handle them. Two things
    // complicate what ought to be a very simple thing: (1) Thunking issues: the pointers
    // used on the 16-bit side are 16:16 (2) Interrupt time issues: the FrameCallback
    // gets called at interrupt time. GetNextReadyBuffer must handle the fact that
    // buffers get added to the list asynchronously.
    //
    // To handle this, the scheme implemented here is to have a double-linked list
    // of buffers with all insertions and deletions happening in FrameCallback
    // (interrupt time). This allows the GetNextReadyBuffer routine to simply
    // find the previous block on the list any time it needs a new buffer without
    // fear of getting tromped (as would be the case if it had to dequeue buffers).
    // The FrameCallback routine is responsible to dequeue blocks that GetNextReadyBuffer
    // is done with. Dequeueing is simple since we don't need to unlink the blocks:
    // no code ever walks the list! All we have to do is move the tail pointer back up
    // the list. All the pointers, head, tail, next, prev, are all 16:16 pointers
    // since all the list manipulation is on the 16-bit side AND because MapSL is
    // much more efficient and safer than MapLS since MapLS has to allocate selectors.
    //--------------------

    // Move the tail back to skip all buffers already used.
    // Note that there is no need to actually unhook the buffer pointers since no one
    // ever walks the list!
    // This makes STRICT assumptions that the current pointer will always be earlier in
    // the list than the tail and that the tail will never be NULL unless the
    // current pointer is too.
    while (lpli->lp1616Tail != lpli->lp1616Current)
        lpli->lp1616Tail = lpli->lp1616Tail->lp1616Prev;

    // If all buffers have been used, then the tail pointer will fall off the list.
    // This is normal and the most common code path. In this event, just set the head
    // to NULL as the list is now empty.
    if (!lpli->lp1616Tail)
        lpli->lp1616Head = NULL;

    // Add the new buffer to the ready queue
    lpcbuf = (LPCAPBUFFER)((LPBYTE)lpvh - ((LPBYTE)&lpcbuf->vh - (LPBYTE)lpcbuf));

    lpcbuf->lp1616Next = lpli->lp1616Head;
    lpcbuf->lp1616Prev = NULL;
    if (lpli->lp1616Head)
        lpli->lp1616Head->lp1616Prev = lpcbuf;
    else
        lpli->lp1616Tail = lpcbuf;
    lpli->lp1616Head = lpcbuf;

#if 1
    if (lpli->lp1616Current) {
    	if (!(lpli->dwFlags & LIF_STOPSTREAM)) {
    	    // if client hasn't consumed last frame, then release it
    	    lpvh = &lpli->lp1616Current->vh;
    	    lpli->lp1616Current = lpli->lp1616Current->lp1616Prev;
    		DEBUGSPEW("Sending DVM_STREAM_ADDBUFFER");
			// Signal that the application is done with the buffer
			lpvh->dwFlags &= ~VHDR_DONE;
    	    if (SendDriverMessage(hvideo, DVM_STREAM_ADDBUFFER, *((DWORD*)&lpvh), sizeof(VIDEOHDR)) != 0)
    		DebugSpew("attempt to reuse unconsumed buffer failed");
    	}
    }
    else {
#else
    if (!lpli->lp1616Current) {
        // If there was no current buffer before, we have one now, so set it to the end.
#endif
        lpli->lp1616Current = lpli->lp1616Tail;
    }

    // Now set the event saying it's time to process the ready frame
	DEBUGSPEW("Setting hcd->hevWait - some data\r\n");
    SetWin32Event(lpli->pevWait);
}


//  _InitializeVideoStream
//      Initializes a driver's video stream for the video in channel.
//      This requires us to pagelock the memory for everything that will
//      be touched at interrupt time.

BOOL
DCAP16API
_InitializeVideoStream(
	HANDLE hvideo,
    DWORD dwMicroSecPerFrame,
    LPLOCKEDINFO lpli
	)
{
    DWORD dwRet;
    WORD wsel;
    VIDEO_STREAM_INIT_PARMS vsip;

    ZeroMemory((LPSTR)&vsip, sizeof (VIDEO_STREAM_INIT_PARMS));
    vsip.dwMicroSecPerFrame = dwMicroSecPerFrame;
    vsip.dwCallback = (DWORD)FrameCallback;
    vsip.dwCallbackInst = (DWORD)lpli;      // LOCKEDINFO* is instance data for callback
    vsip.dwFlags = CALLBACK_FUNCTION;
    vsip.hVideo = (DWORD)hvideo;

    g_lpli = lpli;
    
    dwRet = SendDriverMessage(hvideo, DVM_STREAM_INIT,
        (DWORD) (LPVIDEO_STREAM_INIT_PARMS) &vsip,
        (DWORD) sizeof (VIDEO_STREAM_INIT_PARMS));

    // If we succeeded, we now lock down our code and data
    if (dwRet == 0)
    {
        // Lock CS
        wsel = ReturnSel(TRUE);
        GlobalSmartPageLock(wsel);

        // Lock DS
        wsel = ReturnSel(FALSE);
        GlobalSmartPageLock(wsel);

        return TRUE;
    }

    return FALSE;
}


//  _UninitializeVideoStream
//      Tells the driver we are done streaming. It also unlocks the memory
//      we locked for interrupt time access.

BOOL
DCAP16API
_UninitializeVideoStream(
	HANDLE hvideo
	)
{
    DWORD dwRet;
    WORD wsel;

    dwRet = SendDriverMessage(hvideo, DVM_STREAM_FINI, 0L, 0L);

    // Unlock our code and data
    if (dwRet == 0)
    {
        // Unlock CS
        wsel = ReturnSel(TRUE);
        GlobalSmartPageUnlock(wsel);

        // Unlock DS
        wsel = ReturnSel(FALSE);
        GlobalSmartPageUnlock(wsel);

        return TRUE;
    }

    return FALSE;
}


//  _GetVideoPalette
//      Get the current palette from the driver

HPALETTE
DCAP16API
_GetVideoPalette(
    HANDLE hvideo,
    LPCAPTUREPALETTE lpcp,
    DWORD dwcbSize
    )
{
    VIDEOCONFIGPARMS vcp;

    vcp.lpdwReturn = NULL;
    vcp.lpData1 = (LPVOID)lpcp;
    vcp.dwSize1 = dwcbSize;
    vcp.lpData2 = NULL;
    vcp.dwSize2 = 0;

    return !SendDriverMessage(hvideo, DVM_PALETTE,
        (DWORD)(VIDEO_CONFIGURE_GET | VIDEO_CONFIGURE_CURRENT),
        (DWORD)(LPVIDEOCONFIGPARMS)&vcp);
}


//  _GetVideoFormatSize
//        Gets the current format header size required by driver

DWORD
DCAP16API
_GetVideoFormatSize(
    HANDLE hvideo
    )
{
	DWORD bufsize;
    VIDEOCONFIGPARMS vcp;

    vcp.lpdwReturn = &bufsize;
    vcp.lpData1 = NULL;
    vcp.dwSize1 = 0L;
    vcp.lpData2 = NULL;
    vcp.dwSize2 = 0L;

#if 0
    // it makes sense to query if DVM_FORMAT is available, but not all drivers support it!
	if (SendDriverMessage(hvideo, DVM_FORMAT,
							(LPARAM)(DWORD)(VIDEO_CONFIGURE_GET | VIDEO_CONFIGURE_QUERY),
							(LPARAM)(LPVOID)&vcp) == DV_ERR_OK) {
#endif
		SendDriverMessage(hvideo, DVM_FORMAT,
							(LPARAM)(DWORD)(VIDEO_CONFIGURE_GET | VIDEO_CONFIGURE_QUERYSIZE),
							(LPARAM)(LPVOID)&vcp);
        if (!bufsize)
            bufsize = sizeof(BITMAPINFOHEADER);
		return bufsize;
#if 0
    } else
        return sizeof(BITMAPINFOHEADER);
#endif
}
        
//  _GetVideoFormat
//      Gets the current format (dib header) the capture device is blting to

BOOL
DCAP16API
_GetVideoFormat(
    HANDLE hvideo,
    LPBITMAPINFOHEADER lpbmih
    )
{
	BOOL res;
    VIDEOCONFIGPARMS vcp;

    if (!lpbmih->biSize)
        lpbmih->biSize = sizeof (BITMAPINFOHEADER);
        
    vcp.lpdwReturn = NULL;
    vcp.lpData1 = lpbmih;
    vcp.dwSize1 = lpbmih->biSize;
    vcp.lpData2 = NULL;
    vcp.dwSize2 = 0L;

    res = !SendDriverMessage(hvideo, DVM_FORMAT,
			(LPARAM)(DWORD)(VIDEO_CONFIGURE_GET | VIDEO_CONFIGURE_CURRENT),
			(LPARAM)(LPVOID)&vcp);
	if (res) {
	    // hack for Connectix QuickCam - set format needs to be called
		//   to set internal globals so that streaming can be enabled
		SendDriverMessage(hvideo, DVM_FORMAT, (LPARAM)(DWORD)VIDEO_CONFIGURE_SET,
		        	        (LPARAM)(LPVOID)&vcp);
	}
	return res;
}


//  _SetVideoFormat
//      Sets the format (dib header) the capture device is blting to.

BOOL
DCAP16API
_SetVideoFormat(
    HANDLE hvideoExtIn,
    HANDLE hvideoIn,
    LPBITMAPINFOHEADER lpbmih
    )
{
    RECT rect;
    VIDEOCONFIGPARMS vcp;

    vcp.lpdwReturn = NULL;
    vcp.lpData1 = lpbmih;
    vcp.dwSize1 = lpbmih->biSize;
    vcp.lpData2 = NULL;
    vcp.dwSize2 = 0L;

    // See if the driver likes the format
    if (SendDriverMessage(hvideoIn, DVM_FORMAT, (LPARAM)(DWORD)VIDEO_CONFIGURE_SET,
        (LPARAM)(LPVOID)&vcp))
        return FALSE;

    // Set the rectangles
    rect.left = rect.top = 0;
    rect.right = (WORD)lpbmih->biWidth;
    rect.bottom = (WORD)lpbmih->biHeight;
    SendDriverMessage(hvideoExtIn, DVM_DST_RECT, (LPARAM)(LPVOID)&rect, VIDEO_CONFIGURE_SET);
    SendDriverMessage(hvideoIn, DVM_SRC_RECT, (LPARAM)(LPVOID)&rect, VIDEO_CONFIGURE_SET);

    return TRUE;
}


//  _AllocateLockableBuffer
//      Allocates memory that can be page locked. Just returns the selector.

WORD
DCAP16API
_AllocateLockableBuffer(
    DWORD dwSize
    )
{
    return GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, dwSize);
}


//  _LockBuffer
//      Page locks (if necessary) a buffer allocated with _AllocateLockableBuffer.

BOOL
DCAP16API
_LockBuffer(
    WORD wBuffer
    )
{
    return GlobalSmartPageLock(wBuffer);
}

//  _UnlockBuffer
//      Unlocks a buffer locked with _LockBuffer.

void
DCAP16API
_UnlockBuffer(
    WORD wBuffer
    )
{
    GlobalSmartPageUnlock(wBuffer);
}


//  _FreeLockableBuffer
//      Frees a buffer allocated with _AllocateLockableBuffer.

void
DCAP16API
_FreeLockableBuffer(
    WORD wBuffer
    )
{
    GlobalFree(wBuffer);
}


//  _SendDriverMessage
//      Sends a generic, dword only parameters, message to the driver channel of choice

DWORD
DCAP16API
_SendDriverMessage(
    HVIDEO hvideo,
    DWORD wMessage,
    DWORD param1,
    DWORD param2
    )
{
    return SendDriverMessage(hvideo, (WORD)wMessage, param1, param2);
}