|
|
// 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); }
|