// This file adds native support for streaming WDM video capture
// PhilF-: This needs to be rewritten. You should have two classes
// (CVfWCap & WDMCap) that derive from the same capture class instead
// of those C-like functions...

#include "Precomp.h"

void
WDMFrameCallback(
    HVIDEO hvideo,
    WORD wMsg,
    HCAPDEV hcd,            // (Actually refdata)
    LPCAPBUFFER lpcbuf,     // (Actually LPVIDEOHDR) Only returned from MM_DRVM_DATA!
    DWORD dwParam2
    );

// Globals
extern HINSTANCE g_hInst;


/****************************************************************************
 * @doc EXTERNAL WDMFUNC
 *
 * @func BOOL | WDMGetDevices | This function enumerates the installed WDM video
 *   capture devices and adds them to the list of VfW capture devices.
 *
 * @parm PDWORD | [OUT] pdwOverallCPUUsage | Specifies a pointer to a DWORD to
 *   receive the current CPU usage.
 *
 * @rdesc Returns TRUE on success, and FALSE otherwise.
 *
 * @devnote MSDN references:
 *   DirectX 5, DirectX Media, DirectShow, Application Developer's Guide
 *   "Enumerate and Access Hardware Devices in DirectShow Applications"
 ***************************************************************************/
BOOL WDMGetDevices(void)
{
	HRESULT hr;
	ICreateDevEnum *pCreateDevEnum;
	IEnumMoniker *pEm;

	FX_ENTRY("WDMGetDevices");

	// First, create a system hardware enumerator
	// This call loads the following DLLs - total 1047 KBytes!!!:
	//   'C:\WINDOWS\SYSTEM\DEVENUM.DLL' = 60 KBytes
	//   'C:\WINDOWS\SYSTEM\RPCRT4.DLL' = 316 KBytes
	//   'C:\WINDOWS\SYSTEM\CFGMGR32.DLL' = 44 KBytes
	//   'C:\WINDOWS\SYSTEM\WINSPOOL.DRV' = 23 KBytes
	//   'C:\WINDOWS\SYSTEM\COMDLG32.DLL' = 180 KBytes
	//   'C:\WINDOWS\SYSTEM\LZ32.DLL' = 24 KBytes
	//   'C:\WINDOWS\SYSTEM\SETUPAPI.DLL' = 400 KBytes
	// According to LonnyM, there's no way to go around SETUPAPI.DLL
	// when dealing with PnP device interfaces....
	if ((CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)&pCreateDevEnum)) != S_OK)
	{
		return FALSE;
	}

	// Second, create an enumerator for a specific type of hardware device: video capture cards only
    hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEm, CDEF_BYPASS_CLASS_MANAGER);
    pCreateDevEnum->Release();

	// Third, enumerate the list itself
    if (hr == S_OK)
	{
		ULONG cFetched;
		IMoniker *pM;
		IPropertyBag *pPropBag = 0;

		hr = pEm->Reset();

        while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)
		{

			pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);

			if (pPropBag)
			{
				VARIANT var;
				LPINTERNALCAPDEV lpcd;

				if (!(lpcd = (LPINTERNALCAPDEV)LocalAlloc(LPTR, sizeof (INTERNALCAPDEV))))
				{
					ERRORMESSAGE(("%s: Failed to allocate an INTERNALCAPDEV buffer\r\n", _fx_));
					break;  // break from the WHILE loop
				}

				// Get friendly name of the device
				var.vt = VT_BSTR;
				if ((hr = pPropBag->Read(L"FriendlyName", &var, 0)) == S_OK)
				{
					WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, lpcd->szDeviceDescription, MAX_PATH, 0, 0);
					SysFreeString(var.bstrVal);
				}
				else
					LoadString(g_hInst, IDS_UNKNOWN_DEVICE_NAME, lpcd->szDeviceDescription, CCHMAX(lpcd->szDeviceDescription));

				// Get DevicePath of the device
				hr = pPropBag->Read(L"DevicePath", &var, 0);
				if (hr == S_OK)
				{
					WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, lpcd->szDeviceName, MAX_PATH, 0, 0);
					SysFreeString(var.bstrVal);

					// There's no reg key for version information for WDM devices

					// Those devices can't be disabled from the MM control panel
					// lpcd->dwFlags |= CAPTURE_DEVICE_DISABLED;

					// Mark device as a WDM device
					lpcd->dwFlags |= WDM_CAPTURE_DEVICE;

					g_aCapDevices[g_cDevices] = lpcd;
					g_aCapDevices[g_cDevices]->nDeviceIndex = g_cDevices;
					g_cDevices++;
				}
            }

            pPropBag->Release();

            pM->Release();
        }

        pEm->Release();
    }

	return TRUE;

}

/****************************************************************************
 * @doc EXTERNAL WDMFUNC
 *
 * @func BOOL | WDMOpenDevice | This function opens a WDM video capture
 * devices and adds them to the list of VfW capture devices.
 *
 * @parm DWORD | [IN] dwDeviceID | Specifies the ID of the device to open.
 *
 * @rdesc Returns TRUE on success, and FALSE otherwise.
 ***************************************************************************/
BOOL WDMOpenDevice(DWORD dwDeviceID)
{
	FX_ENTRY("WDMOpenDevice");

	DEBUGMSG(ZONE_INIT, ("%s: dwDeviceID=%ld\r\n", _fx_, dwDeviceID));

	ASSERT(g_cDevices && (dwDeviceID <= (DWORD)g_cDevices) && (lstrlen(g_aCapDevices[dwDeviceID]->szDeviceName) != 0));

    // Validate globals and parameters
    if (!g_cDevices)
    {
        SetLastError(ERROR_DCAP_BAD_INSTALL);
        return FALSE;
    }
    if ((dwDeviceID > (DWORD)g_cDevices) || (lstrlen(g_aCapDevices[dwDeviceID]->szDeviceName) == 0))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

	// Open streaming class driver
	CWDMPin *pCWDMPin;
	if (!(pCWDMPin = new CWDMPin(dwDeviceID)))
	{
		ERRORMESSAGE(("%s: Insufficient resource or fail to create CWDMPin class\r\n", _fx_));
		return FALSE;
	}
	else
	{
		// Open the WDM driver and create a video pin
		if (!pCWDMPin->OpenDriverAndPin())
		{
			goto Error0;
		}
	}

	// Create video stream on the pin
    CWDMStreamer *pCWDMStreamer;
	if (!(pCWDMStreamer = new CWDMStreamer(pCWDMPin)))
	{
		ERRORMESSAGE(("%s: Insufficient resource or fail to create CWDMStreamer\r\n", _fx_));
		goto Error0;
	}

	g_aCapDevices[dwDeviceID]->pCWDMPin = (PVOID)pCWDMPin;
	g_aCapDevices[dwDeviceID]->pCWDMStreamer = (PVOID)pCWDMStreamer;

	return TRUE;

Error0:
	delete pCWDMPin;
	g_aCapDevices[dwDeviceID]->pCWDMPin = (PVOID)NULL;
	g_aCapDevices[dwDeviceID]->pCWDMStreamer = (PVOID)NULL;

	return FALSE;
}


/****************************************************************************
 * @doc EXTERNAL WDMFUNC
 *
 * @func BOOL | WDMCloseDevice | This function closes a WDM video capture
 *   device.
 *
 * @parm DWORD | [IN] dwDeviceID | Specifies the ID of the device to close.
 *
 * @rdesc Returns TRUE on success, and FALSE otherwise.
 ***************************************************************************/
BOOL WDMCloseDevice(DWORD dwDeviceID)
{
	FX_ENTRY("WDMCloseDevice");

	DEBUGMSG(ZONE_INIT, ("%s: dwDeviceID=%ld\r\n", _fx_, dwDeviceID));

	ASSERT(g_cDevices && (dwDeviceID <= (DWORD)g_cDevices));

    // Validate globals and parameters
    if (!g_cDevices)
    {
        SetLastError(ERROR_DCAP_BAD_INSTALL);
        return FALSE;
    }
    if ((dwDeviceID > (DWORD)g_cDevices))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

	// Close video channel
	if (g_aCapDevices[dwDeviceID]->pCWDMStreamer)
	{
		delete ((CWDMStreamer *)g_aCapDevices[dwDeviceID]->pCWDMStreamer);
		g_aCapDevices[dwDeviceID]->pCWDMStreamer = (PVOID)NULL;
	}

	// Close driver and pin
	if (g_aCapDevices[dwDeviceID]->pCWDMPin)
	{
		delete ((CWDMPin *)g_aCapDevices[dwDeviceID]->pCWDMPin);
		g_aCapDevices[dwDeviceID]->pCWDMPin = (PVOID)NULL;
	}

	return TRUE;
}


/****************************************************************************
 * @doc EXTERNAL WDMFUNC
 *
 * @func BOOL | WDMGetVideoFormatSize | This function returns the size of the
 *   structure used to describe the video format.
 *
 * @parm DWORD | [IN] dwDeviceID | Specifies the ID of the device to query.
 *
 * @rdesc Always returns the size of a BITMAPINFOHEADER structure.
 ***************************************************************************/
DWORD WDMGetVideoFormatSize(DWORD dwDeviceID)
{
	FX_ENTRY("WDMGetVideoFormatSize");

	DEBUGMSG(ZONE_INIT, ("%s: dwDeviceID=%ld\r\n", _fx_, dwDeviceID));

	ASSERT(g_cDevices && (dwDeviceID <= (DWORD)g_cDevices) && g_aCapDevices[dwDeviceID]->pCWDMPin);

    // Validate globals and parameters
    if (!g_cDevices)
    {
        SetLastError(ERROR_DCAP_BAD_INSTALL);
        return FALSE;
    }
    if ((dwDeviceID > (DWORD)g_cDevices) || (!g_aCapDevices[dwDeviceID]->pCWDMPin))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

	DEBUGMSG(ZONE_INIT, ("%s: return size=%ld\r\n", _fx_, (DWORD)sizeof(BITMAPINFOHEADER)));

	// Return size of BITMAPINFOHEADER structure
	return (DWORD)sizeof(BITMAPINFOHEADER);
}


/****************************************************************************
 * @doc EXTERNAL WDMFUNC
 *
 * @func BOOL | WDMGetVideoFormat | This function returns the structure used
 *   to describe the video format.
 *
 * @parm DWORD | [IN] dwDeviceID | Specifies the ID of the device to query.
 *
 * @parm DWORD | [OUT] pbmih | Specifies a pointer to a BITMAPINFOHEADER
 *   structure to receive the video format.
 *
 * @rdesc Returns TRUE on success, and FALSE otherwise.
 ***************************************************************************/
BOOL WDMGetVideoFormat(DWORD dwDeviceID, PBITMAPINFOHEADER pbmih)
{
	FX_ENTRY("WDMGetVideoFormat");

	DEBUGMSG(ZONE_INIT, ("%s: dwDeviceID=%ld, pbmih=0x%08lX\r\n", _fx_, dwDeviceID, pbmih));

	ASSERT(g_cDevices && (dwDeviceID <= (DWORD)g_cDevices) && g_aCapDevices[dwDeviceID]->pCWDMPin && pbmih);

    // Validate globals and parameters
    if (!g_cDevices)
    {
        SetLastError(ERROR_DCAP_BAD_INSTALL);
        return FALSE;
    }
    if ((dwDeviceID > (DWORD)g_cDevices) || (!g_aCapDevices[dwDeviceID]->pCWDMPin) || !pbmih)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

	// Make sure the size information is correct
	if (!pbmih->biSize)
		pbmih->biSize = WDMGetVideoFormatSize(dwDeviceID);

	// Get the BITMAPINFOHEADER structure
	if ((((CWDMPin *)g_aCapDevices[dwDeviceID]->pCWDMPin)->GetBitmapInfo((PKS_BITMAPINFOHEADER)pbmih, (WORD)pbmih->biSize)))
	{
		DEBUGMSG(ZONE_INIT, ("%s: return\r\n    biSize=%ld\r\n    biWidth=%ld\r\n    biHeight=%ld\r\n    biPlanes=%ld\r\n    biBitCount=%ld\r\n    biCompression=%ld\r\n    biSizeImage=%ld\r\n", _fx_, pbmih->biSize, pbmih->biWidth, pbmih->biHeight, pbmih->biPlanes, pbmih->biBitCount, pbmih->biCompression, pbmih->biSizeImage));
		return TRUE;
	}
	else
	{
		ERRORMESSAGE(("%s: failed!!!\r\n", _fx_));
		return FALSE;
	}
}


/****************************************************************************
 * @doc EXTERNAL WDMFUNC
 *
 * @func BOOL | WDMSetVideoFormat | This function sets the video format on
 *   a WDM video capture device.
 *
 * @parm DWORD | [IN] dwDeviceID | Specifies the ID of the device to initialize.
 *
 * @parm DWORD | [OUT] pbmih | Specifies a pointer to a BITMAPINFOHEADER
 *   structure describing the video format.
 *
 * @rdesc Returns TRUE on success, and FALSE otherwise.
 ***************************************************************************/
BOOL WDMSetVideoFormat(DWORD dwDeviceID, PBITMAPINFOHEADER pbmih)
{
	FX_ENTRY("WDMSetVideoFormat");

	DEBUGMSG(ZONE_INIT, ("%s: dwDeviceID=%ld, pbmih=0x%08lX\r\n", _fx_, dwDeviceID, pbmih));

	ASSERT(g_cDevices && (dwDeviceID <= (DWORD)g_cDevices) && g_aCapDevices[dwDeviceID]->pCWDMPin && pbmih && pbmih->biSize);

    // Validate globals and parameters
    if (!g_cDevices)
    {
        SetLastError(ERROR_DCAP_BAD_INSTALL);
        return FALSE;
    }
    if ((dwDeviceID > (DWORD)g_cDevices) || (!g_aCapDevices[dwDeviceID]->pCWDMPin) || !pbmih ||!pbmih->biSize)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

	// Set the BITMAPINFOHEADER on the device
	if (((CWDMPin *)g_aCapDevices[dwDeviceID]->pCWDMPin)->SetBitmapInfo((PKS_BITMAPINFOHEADER)pbmih))
	{
		DEBUGMSG(ZONE_INIT, ("%s: return\r\n    biSize=%ld\r\n    biWidth=%ld\r\n    biHeight=%ld\r\n    biPlanes=%ld\r\n    biBitCount=%ld\r\n    biCompression=%ld\r\n    biSizeImage=%ld\r\n", _fx_, pbmih->biSize, pbmih->biWidth, pbmih->biHeight, pbmih->biPlanes, pbmih->biBitCount, pbmih->biCompression, pbmih->biSizeImage));
		return TRUE;
	}
	else
	{
		// PhilF-: This sometimes fail, but we keep on streaming... fix that
		ERRORMESSAGE(("%s: failed!!!\r\n", _fx_));
		return FALSE;
	}
}


/****************************************************************************
 * @doc EXTERNAL WDMFUNC
 *
 * @func BOOL | WDMGetVideoFormat | This function returns the structure used
 *   to describe the video format.
 *
 * @parm DWORD | [IN] dwDeviceID | Specifies the ID of the device to query.
 *
 * @parm DWORD | [OUT] pbmih | Specifies a pointer to a BITMAPINFOHEADER
 *   structure to receive the video format.
 *
 * @rdesc Returns TRUE on success, and FALSE otherwise.
 ***************************************************************************/
BOOL WDMGetVideoPalette(DWORD dwDeviceID, CAPTUREPALETTE* lpcp, DWORD dwcbSize)
{
	FX_ENTRY("WDMGetVideoPalette");

	DEBUGMSG(ZONE_INIT, ("%s: dwDeviceID=%ld, lpcp=0x%08lX\r\n", _fx_, dwDeviceID, lpcp));

	ASSERT(g_cDevices && (dwDeviceID <= (DWORD)g_cDevices) && g_aCapDevices[dwDeviceID]->pCWDMPin && lpcp);

    // Validate globals and parameters
    if (!g_cDevices)
    {
        SetLastError(ERROR_DCAP_BAD_INSTALL);
        return FALSE;
    }
    if ((dwDeviceID > (DWORD)g_cDevices) || (!g_aCapDevices[dwDeviceID]->pCWDMPin) || !lpcp)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

	// Get the palette information
	if ((((CWDMPin *)g_aCapDevices[dwDeviceID]->pCWDMPin)->GetPaletteInfo(lpcp, dwcbSize)))
	{
		DEBUGMSG(ZONE_INIT, ("%s: succeeded\r\n", _fx_));
		return TRUE;
	}
	else
	{
		ERRORMESSAGE(("%s: failed!!!\r\n", _fx_));
		return FALSE;
	}
}


/****************************************************************************
 * @doc EXTERNAL WDMFUNC
 *
 * @func BOOL | WDMInitializeExternalVideoStream | This function initializes
 *   an input video stream on the external video channel of a WDM video
 *   capture device.
 *
 * @parm DWORD | [IN] dwDeviceID | Specifies the ID of the device to initialize.
 *
 * @rdesc Returns TRUE on success, and FALSE otherwise.
 ***************************************************************************/
BOOL WDMInitializeExternalVideoStream(DWORD dwDeviceID)
{
	FX_ENTRY("WDMInitializeExternalVideoStream");

	DEBUGMSG(ZONE_INIT, ("%s: dwDeviceID=%ld\r\n", _fx_, dwDeviceID));
	DEBUGMSG(ZONE_INIT, ("%s: succeeded\r\n", _fx_));
	return TRUE;
}


/****************************************************************************
 * @doc EXTERNAL WDMFUNC
 *
 * @func BOOL | WDMInitializeVideoStream | This function initializes
 *   an input video stream on the videoin channel of a WDM video capture
 *   device.
 *
 * @parm DWORD | [IN] dwDeviceID | Specifies the ID of the device to initialize.
 *
 * @rdesc Returns TRUE on success, and FALSE otherwise.
 ***************************************************************************/
BOOL WDMInitializeVideoStream(HCAPDEV hcd, DWORD dwDeviceID, DWORD dwMicroSecPerFrame)
{
	FX_ENTRY("WDMInitializeVideoStream");

	DEBUGMSG(ZONE_INIT, ("%s: dwDeviceID=%ld, FPS=%ld\r\n", _fx_, dwDeviceID, 1000000UL / dwMicroSecPerFrame));

    VIDEO_STREAM_INIT_PARMS vsip = {0};

	ASSERT(g_cDevices && (dwDeviceID <= (DWORD)g_cDevices) && g_aCapDevices[dwDeviceID]->pCWDMStreamer);

    // Validate globals and parameters
    if (!g_cDevices)
    {
        SetLastError(ERROR_DCAP_BAD_INSTALL);
        return FALSE;
    }
    if ((dwDeviceID > (DWORD)g_cDevices) || (!g_aCapDevices[dwDeviceID]->pCWDMStreamer))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

	// Initialize channel
    vsip.dwMicroSecPerFrame = dwMicroSecPerFrame;
    vsip.dwCallback = (DWORD_PTR)WDMFrameCallback;
    vsip.dwCallbackInst = (DWORD_PTR)hcd;
    vsip.dwFlags = CALLBACK_FUNCTION;
    // vsip.hVideo = (DWORD)hvideo;

	if ((((CWDMStreamer *)g_aCapDevices[dwDeviceID]->pCWDMStreamer)->Open(&vsip)))
	{
		DEBUGMSG(ZONE_INIT, ("%s: succeeded\r\n", _fx_));
		return TRUE;
	}
	else
	{
		ERRORMESSAGE(("%s: failed!!!\r\n", _fx_));
		return FALSE;
	}
}


/****************************************************************************
 * @doc EXTERNAL WDMFUNC
 *
 * @func BOOL | WDMUnInitializeVideoStream | This function requests a WDM
 *   video capture device to close a capture stream on the videoin channel.
 *
 * @parm DWORD | [IN] dwDeviceID | Specifies the ID of the device to initialize.
 *
 * @rdesc Returns TRUE on success, and FALSE otherwise.
 ***************************************************************************/
BOOL WDMUnInitializeVideoStream(DWORD dwDeviceID)
{
	FX_ENTRY("WDMUnInitializeVideoStream");

	DEBUGMSG(ZONE_INIT, ("%s: dwDeviceID=%ld\r\n", _fx_, dwDeviceID));

	ASSERT(g_cDevices && (dwDeviceID <= (DWORD)g_cDevices) && g_aCapDevices[dwDeviceID]->pCWDMStreamer);

    // Validate globals and parameters
    if (!g_cDevices)
    {
        SetLastError(ERROR_DCAP_BAD_INSTALL);
        return FALSE;
    }
    if ((dwDeviceID > (DWORD)g_cDevices) || (!g_aCapDevices[dwDeviceID]->pCWDMStreamer))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

	// Close streaming on channel
	if ((((CWDMStreamer *)g_aCapDevices[dwDeviceID]->pCWDMStreamer)->Close()))
	{
		DEBUGMSG(ZONE_INIT, ("%s: succeeded\r\n", _fx_));
		return TRUE;
	}
	else
	{
		ERRORMESSAGE(("%s: failed!!!\r\n", _fx_));
		return FALSE;
	}
}


/****************************************************************************
 * @doc EXTERNAL WDMFUNC
 *
 * @func BOOL | WDMVideoStreamStart | This function requests a WDM video
 *   capture device to start a video stream.
 *
 * @parm DWORD | [IN] dwDeviceID | Specifies the ID of the device to start.
 *
 * @rdesc Returns TRUE on success, and FALSE otherwise.
 ***************************************************************************/
BOOL WDMVideoStreamStart(DWORD dwDeviceID)
{
	FX_ENTRY("WDMVideoStreamStart");

	DEBUGMSG(ZONE_INIT, ("%s: dwDeviceID=%ld\r\n", _fx_, dwDeviceID));

	ASSERT(g_cDevices && (dwDeviceID <= (DWORD)g_cDevices) && g_aCapDevices[dwDeviceID]->pCWDMStreamer);

    // Validate globals and parameters
    if (!g_cDevices)
    {
        SetLastError(ERROR_DCAP_BAD_INSTALL);
        return FALSE;
    }
    if ((dwDeviceID > (DWORD)g_cDevices) || (!g_aCapDevices[dwDeviceID]->pCWDMStreamer))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

	// Start streaming
	if ((((CWDMStreamer *)g_aCapDevices[dwDeviceID]->pCWDMStreamer)->Start()))
	{
		DEBUGMSG(ZONE_INIT, ("%s: succeeded\r\n", _fx_));
		return TRUE;
	}
	else
	{
		ERRORMESSAGE(("%s: failed!!!\r\n", _fx_));
		return FALSE;
	}
}


/****************************************************************************
 * @doc EXTERNAL WDMFUNC
 *
 * @func BOOL | WDMVideoStreamStop | This function requests a WDM video
 *   capture device to stop a video stream.
 *
 * @parm DWORD | [IN] dwDeviceID | Specifies the ID of the device to freeze.
 *
 * @rdesc Returns TRUE on success, and FALSE otherwise.
 ***************************************************************************/
BOOL WDMVideoStreamStop(DWORD dwDeviceID)
{
	FX_ENTRY("WDMVideoStreamStop");

	DEBUGMSG(ZONE_INIT, ("%s: dwDeviceID=%ld\r\n", _fx_, dwDeviceID));

	ASSERT(g_cDevices && (dwDeviceID <= (DWORD)g_cDevices) && g_aCapDevices[dwDeviceID]->pCWDMStreamer);

    // Validate globals and parameters
    if (!g_cDevices)
    {
        SetLastError(ERROR_DCAP_BAD_INSTALL);
        return FALSE;
    }
    if ((dwDeviceID > (DWORD)g_cDevices) || (!g_aCapDevices[dwDeviceID]->pCWDMStreamer))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

	// Stop streaming
	if ((((CWDMStreamer *)g_aCapDevices[dwDeviceID]->pCWDMStreamer)->Stop()))
	{
		DEBUGMSG(ZONE_INIT, ("%s: succeeded\r\n", _fx_));
		return TRUE;
	}
	else
	{
		ERRORMESSAGE(("%s: failed!!!\r\n", _fx_));
		return FALSE;
	}
}


/****************************************************************************
 * @doc EXTERNAL WDMFUNC
 *
 * @func BOOL | WDMVideoStreamReset | This function resets a WDM video capture
 *   devie to stop input of a capture stream and return all buffers to the
 *   client.
 *
 * @parm DWORD | [IN] dwDeviceID | Specifies the ID of the device to reset.
 *
 * @rdesc Returns TRUE on success, and FALSE otherwise.
 ***************************************************************************/
BOOL WDMVideoStreamReset(DWORD dwDeviceID)
{
	FX_ENTRY("WDMVideoStreamReset");

	DEBUGMSG(ZONE_INIT, ("%s: dwDeviceID=%ld\r\n", _fx_, dwDeviceID));

	ASSERT(g_cDevices && (dwDeviceID <= (DWORD)g_cDevices) && g_aCapDevices[dwDeviceID]->pCWDMStreamer);

    // Validate globals and parameters
    if (!g_cDevices)
    {
        SetLastError(ERROR_DCAP_BAD_INSTALL);
        return FALSE;
    }
    if ((dwDeviceID > (DWORD)g_cDevices) || (!g_aCapDevices[dwDeviceID]->pCWDMStreamer))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

	// Reset streaming
	if ((((CWDMStreamer *)g_aCapDevices[dwDeviceID]->pCWDMStreamer)->Reset()))
	{
		DEBUGMSG(ZONE_INIT, ("%s: succeeded\r\n", _fx_));
		return TRUE;
	}
	else
	{
		ERRORMESSAGE(("%s: failed!!!\r\n", _fx_));
		return FALSE;
	}
}


/****************************************************************************
 * @doc EXTERNAL WDMFUNC
 *
 * @func BOOL | WDMVideoStreamAddBuffer | This function requests a WDM video
 *   capture device to add an empty input buffer to its input buffer queue.
 *
 * @parm DWORD | [IN] dwDeviceID | Specifies the ID of the device to initialize.
 *
 * @rdesc Returns TRUE on success, and FALSE otherwise.
 ***************************************************************************/
BOOL WDMVideoStreamAddBuffer(DWORD dwDeviceID, PVOID pBuff)
{
	FX_ENTRY("WDMVideoStreamAddBuffer");

	DEBUGMSG(ZONE_STREAMING, ("      %s: dwDeviceID=%ld, pBuff=0x%08lX\r\n", _fx_, dwDeviceID, pBuff));

	ASSERT(g_cDevices && pBuff && (dwDeviceID <= (DWORD)g_cDevices) && g_aCapDevices[dwDeviceID]->pCWDMStreamer);

    // Validate globals and parameters
    if (!g_cDevices)
    {
        SetLastError(ERROR_DCAP_BAD_INSTALL);
        return FALSE;
    }
    if (!pBuff || (dwDeviceID > (DWORD)g_cDevices) || (!g_aCapDevices[dwDeviceID]->pCWDMStreamer))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

	// Reset streaming
	if ((((CWDMStreamer *)g_aCapDevices[dwDeviceID]->pCWDMStreamer)->AddBuffer((LPVIDEOHDR)pBuff)))
	{
		DEBUGMSG(ZONE_STREAMING, ("      %s: succeeded\r\n", _fx_));
		return TRUE;
	}
	else
	{
		ERRORMESSAGE(("      %s: failed!!!\r\n", _fx_));
		return FALSE;
	}
}


/****************************************************************************
 * @doc EXTERNAL WDMFUNC
 *
 * @func BOOL | WDMGetFrame | This function requests a WDM video
 *   capture device to transfer a single frame to or from the video device.
 *
 * @parm DWORD | [IN] dwDeviceID | Specifies the ID of the device to request.
 *
 * @parm PVOID | [OUT] pBuff | Specifies a pointer to a <t VIDEOHDR> structure.
 *
 * @rdesc Returns TRUE on success, and FALSE otherwise.
 ***************************************************************************/
BOOL WDMGetFrame(DWORD dwDeviceID, PVOID pBuff)
{
	FX_ENTRY("WDMGetFrame");

	DEBUGMSG(ZONE_STREAMING, ("%s: dwDeviceID=%ld, pBuff=0x%08lX\r\n", _fx_, dwDeviceID, pBuff));

	LPVIDEOHDR lpVHdr = (LPVIDEOHDR)pBuff;

	ASSERT(g_cDevices && pBuff && (dwDeviceID <= (DWORD)g_cDevices) && g_aCapDevices[dwDeviceID]->pCWDMPin);

    // Validate globals and parameters
    if (!g_cDevices)
    {
        SetLastError(ERROR_DCAP_BAD_INSTALL);
        return FALSE;
    }
    if (!pBuff || (dwDeviceID > (DWORD)g_cDevices) || (!g_aCapDevices[dwDeviceID]->pCWDMPin))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

	// Get the frame from the device
	if (((CWDMPin *)g_aCapDevices[dwDeviceID]->pCWDMPin)->GetFrame(lpVHdr))
		return TRUE;
	else
		return FALSE;

}


/****************************************************************************
 * @doc EXTERNAL WDMFUNC
 *
 * @func BOOL | WDMShowSettingsDialog | This function puts up a property
 *   sheet with a VideoProcAmp and CameraControl page for a WDM video capture
 *   device.
 *
 * @parm DWORD | [IN] dwDeviceID | Specifies the ID of the device to request.
 *
 * @rdesc Returns TRUE on success, and FALSE otherwise.
 ***************************************************************************/
BOOL WDMShowSettingsDialog(DWORD dwDeviceID, HWND hWndParent)
{
	PROPSHEETHEADER Psh;
	HPROPSHEETPAGE	Pages[MAX_PAGES];

	FX_ENTRY("WDMShowSettingsDialog");

	DEBUGMSG(ZONE_STREAMING, ("%s: dwDeviceID=%ld\r\n", _fx_, dwDeviceID));

	ASSERT(g_cDevices && (dwDeviceID <= (DWORD)g_cDevices) && g_aCapDevices[dwDeviceID]->pCWDMPin);

    // Validate globals and parameters
    if (!g_cDevices)
    {
        SetLastError(ERROR_DCAP_BAD_INSTALL);
        return FALSE;
    }
    if ((dwDeviceID > (DWORD)g_cDevices) || (!g_aCapDevices[dwDeviceID]->pCWDMPin))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return FALSE;
    }

	// Initialize property sheet header	and common controls
	Psh.dwSize		= sizeof(Psh);
	Psh.dwFlags		= PSH_DEFAULT;
	Psh.hInstance	= g_hInst;
	Psh.hwndParent	= hWndParent;
	Psh.pszCaption	= g_aCapDevices[dwDeviceID]->szDeviceDescription;
	Psh.nPages		= 0;
	Psh.nStartPage	= 0;
	Psh.pfnCallback	= NULL;
	Psh.phpage		= Pages;

    // Create the video settings property page and add it to the video settings sheet
    CWDMDialog VideoSettings(IDD_VIDEO_SETTINGS, NumVideoSettings, PROPSETID_VIDCAP_VIDEOPROCAMP, g_VideoSettingControls, g_VideoSettingsHelpIDs, (CWDMPin *)g_aCapDevices[dwDeviceID]->pCWDMPin);
	if (Pages[Psh.nPages] = VideoSettings.Create())
		Psh.nPages++;

    // Create the camera control property page and add it to the video settings sheet
    CWDMDialog CamControl(IDD_CAMERA_CONTROL, NumCameraControls, PROPSETID_VIDCAP_CAMERACONTROL, g_CameraControls, g_CameraControlsHelpIDs, (CWDMPin *)g_aCapDevices[dwDeviceID]->pCWDMPin);
	if (Pages[Psh.nPages] = CamControl.Create())
		Psh.nPages++;

	// Put up the property sheet
	if (Psh.nPages && PropertySheet(&Psh) >= 0)
		return TRUE;
	else
		return FALSE;

}


void
WDMFrameCallback(
    HVIDEO hvideo,
    WORD wMsg,
    HCAPDEV hcd,            // (Actually refdata)
    LPCAPBUFFER lpcbuf,     // (Actually LPVIDEOHDR) Only returned from MM_DRVM_DATA!
    DWORD dwParam2
    )
{
	FX_ENTRY("WDMFrameCallback");

	DEBUGMSG(ZONE_CALLBACK, ("    %s: wMsg=%s, hcd=0x%08lX, lpcbuf=0x%08lX, hcd->hevWait=0x%08lX\r\n", _fx_, (wMsg == MM_DRVM_OPEN) ? "MM_DRVM_OPEN" : (wMsg == MM_DRVM_CLOSE) ? "MM_DRVM_CLOSE" : (wMsg == MM_DRVM_ERROR) ? "MM_DRVM_ERROR" : (wMsg == MM_DRVM_DATA) ? "MM_DRVM_DATA" : "MM_DRVM_?????", hcd, lpcbuf, hcd->hevWait));

    // 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)
    {
		DEBUGMSG(ZONE_CALLBACK, ("    %s: Setting hcd->hevWait - no data\r\n", _fx_));
	    SetEvent(hcd->hevWait);
	    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 (hcd->lpTail != hcd->lpCurrent)
	    hcd->lpTail = hcd->lpTail->lpPrev;

    // 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 (!hcd->lpTail)
	    hcd->lpHead = NULL;

    // Add the new buffer to the ready queue
    lpcbuf->lpNext = hcd->lpHead;
    lpcbuf->lpPrev = NULL;
    if (hcd->lpHead)
	    hcd->lpHead->lpPrev = lpcbuf;
    else
	    hcd->lpTail = lpcbuf;
    hcd->lpHead = lpcbuf;

#if 1
    if (hcd->lpCurrent) {
        if (!(hcd->dwFlags & HCAPDEV_STREAMING_PAUSED)) {
    	    // if client hasn't consumed last frame, then release it
			lpcbuf = hcd->lpCurrent;
    	    hcd->lpCurrent = hcd->lpCurrent->lpPrev;
			DEBUGMSG(ZONE_CALLBACK, ("    %s: We already have current buffer (lpcbuf=0x%08lX). Returning this buffer to driver. Set new current buffer hcd->lpCurrent=0x%08lX\r\n", _fx_, lpcbuf, hcd->lpCurrent));
    	    if (!WDMVideoStreamAddBuffer(hcd->nDeviceIndex, (PVOID)lpcbuf))
			{
				ERRORMESSAGE(("    %s: Attempt to reuse unconsumed buffer failed\r\n", _fx_));
			}
    	}
    }
    else {
#else
    if (!hcd->lpCurrent) {
        // If there was no current buffer before, we have one now, so set it to the end.
#endif
	    hcd->lpCurrent = hcd->lpTail;
    }

    // Now set the event saying it's time to process the ready frame
	DEBUGMSG(ZONE_CALLBACK, ("    %s: Setting hcd->hevWait - some data\r\n", _fx_));
    SetEvent(hcd->hevWait);
}