
/****************************************************************************
 *  @doc INTERNAL DIALOGS
 *
 *  @module WDMStrmr.cpp | Source file for <c CWDMStreamer> class used to get a
 *    stream of video data flowing from WDM devices.
 *
 *  @comm This code is based on the VfW to WDM mapper code written by
 *    FelixA and E-zu Wu. The original code can be found on
 *    \\redrum\slmro\proj\wdm10\\src\image\vfw\win9x\raytube.
 *
 *    Documentation by George Shaw on kernel streaming can be found in
 *    \\popcorn\razzle1\src\spec\ks\ks.doc.
 *
 *    WDM streaming capture is discussed by Jay Borseth in
 *    \\blues\public\jaybo\WDMVCap.doc.
 ***************************************************************************/

#include "Precomp.h"


/****************************************************************************
 *  @doc INTERNAL CWDMSTREAMERMETHOD
 *
 *  @mfunc void | CWDMStreamer | CWDMStreamer | WDM filter class constructor.
 *
 *  @parm CWDMPin * | pWDMVideoPin | Pointer to the kernel streaming
 *    object we will get the frames from.
 ***************************************************************************/
CWDMStreamer::CWDMStreamer(CWDMPin * pWDMVideoPin)
{
	m_pWDMVideoPin = pWDMVideoPin;
	m_lpVHdrFirst = (LPVIDEOHDR)NULL;
	m_lpVHdrLast = (LPVIDEOHDR)NULL;
	m_fVideoOpen = FALSE;
	m_fStreamingStarted = FALSE;
	m_pBufTable = (PBUFSTRUCT)NULL;
	m_cntNumVidBuf = 0UL;
	m_idxNextVHdr = 0UL;
    m_hThread = NULL;
	m_bKillThread = FALSE;
}

/****************************************************************************
 *  @doc INTERNAL CWDMSTREAMERMETHOD
 *
 *  @mfunc void | CWDMStreamer | videoCallback | This function calls the
 *    callback function provided by the appplication.
 *
 *  @parm WORD | msg | Message value.
 *
 *  @parm DWORD | dwParam1 | 32-bit message-dependent parameter.
 ***************************************************************************/
void CWDMStreamer::videoCallback(WORD msg, DWORD_PTR dwParam1)
{
    if (m_CaptureStreamParms.dwCallback)
        DriverCallback (m_CaptureStreamParms.dwCallback, HIWORD(m_CaptureStreamParms.dwFlags), (HDRVR) m_CaptureStreamParms.hVideo, msg, m_CaptureStreamParms.dwCallbackInst, dwParam1, 0UL);
}


/****************************************************************************
 *  @doc INTERNAL CWDMSTREAMERMETHOD
 *
 *  @mfunc LPVIDEOHDR | CWDMStreamer | DeQueueHeader | This function dequeues a
 *    video buffer from the list of video buffers used for streaming.
 *
 *  @rdesc Returns a valid pointer if successful, or NULL otherwise.
 ***************************************************************************/
LPVIDEOHDR CWDMStreamer::DeQueueHeader()
{
	FX_ENTRY("CWDMStreamer::DeQueueHeader");

    LPVIDEOHDR lpVHdr;

    if (m_pBufTable)
	{
        if (m_pBufTable[m_idxNextVHdr].fReady)
		{
			DEBUGMSG(ZONE_STREAMING, ("  %s: DeQueuing idxNextVHdr (idx=%d) with data to be filled at lpVHdr=0x%08lX\r\n", _fx_, m_idxNextVHdr, m_pBufTable[m_idxNextVHdr].lpVHdr));

            lpVHdr = m_pBufTable[m_idxNextVHdr].lpVHdr;
            lpVHdr->dwFlags &= ~VHDR_INQUEUE;
            m_pBufTable[m_idxNextVHdr].fReady = FALSE;
        }
		else
		{
            m_idxNextVHdr++;
            if (m_idxNextVHdr >= m_cntNumVidBuf)
                m_idxNextVHdr = 0;

			if (m_pBufTable[m_idxNextVHdr].fReady)
			{
				DEBUGMSG(ZONE_STREAMING, ("  %s: DeQueuing idxNextVHdr (idx=%d) with data to be filled at lpVHdr=0x%08lX\r\n", _fx_, m_idxNextVHdr, m_pBufTable[m_idxNextVHdr].lpVHdr));

				lpVHdr = m_pBufTable[m_idxNextVHdr].lpVHdr;
				lpVHdr->dwFlags &= ~VHDR_INQUEUE;
				m_pBufTable[m_idxNextVHdr].fReady = FALSE;
			}
			else
			{
				DEBUGMSG(ZONE_STREAMING, ("  %s: idxNextVHdr (idx=%d) has not been returned by client\r\n", _fx_, m_idxNextVHdr));
				lpVHdr = NULL;
			}
		}
    }
	else
	{
        lpVHdr = m_lpVHdrFirst;

        if (lpVHdr) {

            lpVHdr->dwFlags &= ~VHDR_INQUEUE;

            m_lpVHdrFirst = (LPVIDEOHDR)(lpVHdr->dwReserved[0]);

            if (m_lpVHdrFirst == NULL)
                m_lpVHdrLast = NULL;
        }
    }

    return lpVHdr;
}


/****************************************************************************
 *  @doc INTERNAL CWDMSTREAMERMETHOD
 *
 *  @mfunc void | CWDMStreamer | QueueHeader | This function actually adds the
 *    video buffer to the list of video buffers used for streaming.
 *
 *  @parm LPVIDEOHDR | lpVHdr | Pointer to a <t VIDEOHDR> structure describing
 *    a video buffer to add to the list of streaming buffers.
 ***************************************************************************/
void CWDMStreamer::QueueHeader(LPVIDEOHDR lpVHdr)
{
	FX_ENTRY("CWDMStreamer::QueHeader");

	// Initialize status flags
    lpVHdr->dwFlags &= ~VHDR_DONE;
    lpVHdr->dwFlags |= VHDR_INQUEUE;
    lpVHdr->dwBytesUsed = 0;

    // Add buffer to list
    if (m_pBufTable)
	{
		if (lpVHdr->dwReserved[1] < m_cntNumVidBuf)
		{
			if (m_pBufTable[lpVHdr->dwReserved[1]].lpVHdr != lpVHdr)
			{
				DEBUGMSG(ZONE_STREAMING, ("        %s: index (%d) Match but lpVHdr does not(%x)\r\n", _fx_, lpVHdr->dwReserved[1], lpVHdr));
			}
			m_pBufTable[lpVHdr->dwReserved[1]].fReady = TRUE;
			DEBUGMSG(ZONE_STREAMING, ("        %s: Buffer lpVHdr=0x%08lX was succesfully queued\r\n", _fx_, lpVHdr));
		}
		else
		{
			DEBUGMSG(ZONE_STREAMING, ("        %s: lpVHdr->dwReserved[1](%d) >= m_cntNumVidBuf (%d)\r\n", _fx_, lpVHdr->dwReserved[1], m_cntNumVidBuf));
		}
	}
	else
	{
		*(lpVHdr->dwReserved) = NULL;

		if (m_lpVHdrLast)
			*(m_lpVHdrLast->dwReserved) = (DWORD_PTR)lpVHdr;
		else
			m_lpVHdrFirst = lpVHdr;

		m_lpVHdrLast = lpVHdr;
	}
}


/****************************************************************************
 *  @doc INTERNAL CWDMSTREAMERMETHOD
 *
 *  @mfunc BOOL | CWDMStreamer | AddBuffer | This function adds a buffer to the
 *    list of video buffers to be used when streaming video data from the WDM
 *    device.
 *
 *  @parm LPVIDEOHDR | lpVHdr | Pointer to a <t VIDEOHDR> structure describing
 *    a video buffer to add to the list of streaming buffers.
 *
 *  @rdesc Returns TRUE if successful, or FALSE otherwise.
 *
 *  @devnote This function handles what was the DVM_STREAM_ADDBUFFER message in VfW.
 ***************************************************************************/
BOOL CWDMStreamer::AddBuffer(LPVIDEOHDR lpVHdr)
{
	FX_ENTRY("CWDMStreamer::AddBuffer");

	ASSERT(m_fVideoOpen && lpVHdr && !(lpVHdr->dwFlags & VHDR_INQUEUE));

	// Make sure this is a valid call
    if (!m_fVideoOpen)
	{
		DEBUGMSG(ZONE_STREAMING, ("%s: Buffer lpVHdr=0x%08lX can't be queued because m_fVideoOpen=FALSE\r\n", _fx_, lpVHdr));
        return FALSE;
	}

    if (!lpVHdr)
	{
		DEBUGMSG(ZONE_STREAMING, ("%s: Buffer lpVHdr=0x%08lX can't be queued because lpVHdr=NULL\r\n", _fx_, lpVHdr));
		return FALSE;
	}

    if (lpVHdr->dwFlags & VHDR_INQUEUE)
	{
		DEBUGMSG(ZONE_STREAMING, ("%s: Buffer lpVHdr=0x%08lX can't be queued because buffer is already queued\r\n", _fx_, lpVHdr));
		return FALSE;
	}

	// Does the size of the buffer match the size of the buffers the streaming pin will generate?
    if (lpVHdr->dwBufferLength < m_pWDMVideoPin->GetFrameSize())
	{
		ERRORMESSAGE(("%s: Buffer lpVHdr=0x%08lX can't be queued because the length of that buffer is too small\r\n", _fx_, lpVHdr));
        return FALSE;
	}

    if (!m_pBufTable)
	{
        lpVHdr->dwReserved[1] = m_cntNumVidBuf;
        m_cntNumVidBuf++;
		DEBUGMSG(ZONE_STREAMING, ("%s: Queue buffer (%d) lpVHdr=0x%08lX\r\n", _fx_, lpVHdr->dwReserved[1], lpVHdr));
    }

    QueueHeader(lpVHdr);

    return TRUE;
}


/****************************************************************************
 *  @doc INTERNAL CWDMSTREAMERMETHOD
 *
 *  @mfunc BOOL | CWDMStreamer | Stop | This function stops a stream of
 *    video data coming from the WDM device.
 *
 *  @rdesc Returns TRUE if successful, or FALSE otherwise.
 *
 *  @devnote This function handles what was the DVM_STREAM_STOP message in VfW.
 ***************************************************************************/
BOOL CWDMStreamer::Stop()
{
	FX_ENTRY("CWDMStreamer::Stop");

	ASSERT(m_fVideoOpen);

	// Make sure this is a valid call
	if (!m_fVideoOpen)
	{
		DEBUGMSG(ZONE_STREAMING, ("%s: Stream is not even opened\r\n", _fx_));
		return FALSE;
	}

	DEBUGMSG(ZONE_STREAMING, ("%s()\r\n", _fx_));

	// Reset data members - stop streaming thread
    m_fStreamingStarted = FALSE;

    if (m_hThread)
    {

		DEBUGMSG(ZONE_STREAMING, ("%s: Stopping the thread\r\n", _fx_));

        // Signal the streaming thread to stop
		m_bKillThread = TRUE;

        // wait until thread has self-terminated, and clear the event.
		DEBUGMSG(ZONE_STREAMING, ("%s: WaitingForSingleObject...\r\n", _fx_));

        WaitForSingleObject(m_hThread, INFINITE);

		DEBUGMSG(ZONE_STREAMING, ("%s: ...thread stopped\r\n", _fx_));

		// Close the thread handle
		CloseHandle(m_hThread);
		m_hThread = NULL;

		// Ask the pin to stop streaming.
		m_pWDMVideoPin->Stop();

		for (UINT i=0; i<m_cntNumVidBuf; i++)
		{
			if (m_pWDMVideoBuff[i].Overlap.hEvent)
			{
				SetEvent(m_pWDMVideoBuff[i].Overlap.hEvent);
				CloseHandle(m_pWDMVideoBuff[i].Overlap.hEvent);
				m_pWDMVideoBuff[i].Overlap.hEvent = NULL;
			}
		}

		if (m_pWDMVideoBuff)
		{
			delete []m_pWDMVideoBuff;
			m_pWDMVideoBuff = (WDMVIDEOBUFF *)NULL;
		}

    }

    return TRUE;
}


/****************************************************************************
 *  @doc INTERNAL CWDMSTREAMERMETHOD
 *
 *  @mfunc BOOL | CWDMStreamer | Reset | This function resets a stream of
 *    video data coming from the WDM device so that prepared buffer may be
 *    freed correctly.
 *
 *  @rdesc Returns TRUE if successful, or FALSE otherwise.
 *
 *  @devnote This function handles what was the DVM_STREAM_RESET message in VfW.
 ***************************************************************************/
BOOL CWDMStreamer::Reset()
{
	LPVIDEOHDR lpVHdr;

	FX_ENTRY("CWDMStreamer::Reset");

	ASSERT(m_fVideoOpen);

	// Make sure this is a valid call
	if (!m_fVideoOpen)
	{
		DEBUGMSG(ZONE_STREAMING, ("%s: Stream is not even opened\r\n", _fx_));
		return FALSE;
	}

	DEBUGMSG(ZONE_STREAMING, ("%s()\r\n", _fx_));

	// Terminate streaming thread
    Stop();

	// Return all buffers to the application one last time
	while (lpVHdr = DeQueueHeader ())
	{
		lpVHdr->dwFlags |= VHDR_DONE;
		videoCallback(MM_DRVM_DATA, (DWORD_PTR) lpVHdr);
	}

	// Reset data members
    m_lpVHdrFirst = (LPVIDEOHDR)NULL;
    m_lpVHdrLast = (LPVIDEOHDR)NULL;
    if (m_pBufTable)
	{
		delete []m_pBufTable;
		m_pBufTable = NULL;
    }

    return TRUE;
}


/****************************************************************************
 *  @doc INTERNAL CWDMSTREAMERMETHOD
 *
 *  @mfunc BOOL | CWDMStreamer | Open | This function opens a stream of
 *    video data coming from the WDM device.
 *
 *  @parm LPVIDEO_STREAM_INIT_PARMS | lpStreamInitParms | Pointer to
 *    initialization data.
 *
 *  @rdesc Returns TRUE if successful, or FALSE otherwise.
 *
 *  @devnote This function handles what was the DVM_STREAM_INIT message in VfW.
 ***************************************************************************/
BOOL CWDMStreamer::Open(LPVIDEO_STREAM_INIT_PARMS lpStreamInitParms)
{
	FX_ENTRY("CWDMStreamer::Open");

	ASSERT(!m_fVideoOpen);

	// Make sure this is a valid call
	if (m_fVideoOpen)
	{
		DEBUGMSG(ZONE_STREAMING, ("%s: Stream is already opened\r\n", _fx_));
		return FALSE;
	}

	DEBUGMSG(ZONE_STREAMING, ("%s()\r\n", _fx_));

	// Initialize data memmbers
	m_CaptureStreamParms	= *lpStreamInitParms;
	m_fVideoOpen			= TRUE;
	m_lpVHdrFirst			= (LPVIDEOHDR)NULL;
	m_lpVHdrLast			= (LPVIDEOHDR)NULL;
	m_cntNumVidBuf			= 0UL;

	// Set frame rate on the pin
	m_pWDMVideoPin->SetAverageTimePerFrame(lpStreamInitParms->dwMicroSecPerFrame * 10);

	// Let the app know we just opened a stream
	videoCallback(MM_DRVM_OPEN, 0L);

	if (lpStreamInitParms->dwMicroSecPerFrame != 0)
	{
		DEBUGMSG(ZONE_STREAMING, ("%s: Capturing at %d frames/sec\r\n", _fx_, 100000 / lpStreamInitParms->dwMicroSecPerFrame));
	}

	return TRUE;
}


/****************************************************************************
 *  @doc INTERNAL CWDMSTREAMERMETHOD
 *
 *  @mfunc BOOL | CWDMStreamer | Close | This function closes the stream of
 *    video data coming from the WDM device.
 *
 *  @rdesc Returns TRUE if successful, or FALSE otherwise.
 *
 *  @devnote This function handles what was the DVM_STREAM_FINI message in VfW.
 ***************************************************************************/
BOOL CWDMStreamer::Close()
{
	FX_ENTRY("CWDMStreamer::Close");

	ASSERT(m_fVideoOpen && !m_lpVHdrFirst);

	// Make sure this is a valid call
	if (!m_fVideoOpen || m_lpVHdrFirst)
	{
		DEBUGMSG(ZONE_STREAMING, ("%s: Invalid parameters\r\n", _fx_));
		return FALSE;
	}

	DEBUGMSG(ZONE_STREAMING, ("%s()\r\n", _fx_));

	// Terminate streaming thread
	Stop();

	// Reset data members
	m_fVideoOpen = FALSE;
	m_lpVHdrFirst = m_lpVHdrLast = (LPVIDEOHDR)NULL;
	m_idxNextVHdr = 0UL;

	// Release table of pointers to video buffers
	if (m_pBufTable)
	{
		delete []m_pBufTable;
		m_pBufTable = NULL;
	}

	// Let the app know that we just closed the stream
	videoCallback(MM_DRVM_CLOSE, 0L);

	return TRUE;
}


/****************************************************************************
 *  @doc INTERNAL CWDMSTREAMERMETHOD
 *
 *  @mfunc void | CWDMStreamer | BufferDone | This function lets the application
 *    know that there is video data available coming from the WDM device.
 *
 *  @devnote This method is called by the kernel streaming object (Pin)
 ***************************************************************************/
void CWDMStreamer::BufferDone(LPVIDEOHDR lpVHdr)
{
	FX_ENTRY("CWDMStreamer::BufferDone");

	// Make sure this is a valid call
	if (!m_fStreamingStarted)
	{
		DEBUGMSG(ZONE_STREAMING, ("%s: Video has not been started or just been stopped\r\n", _fx_));
		return;
	}

    if (lpVHdr == NULL)
	{
		// No buffers available - the app hasn't returned the buffers to us yet
		DEBUGMSG(ZONE_STREAMING, ("  %s: Let the app know that we don't have any buffers anymore since lpVHdr=NULL\r\n", _fx_));

		// Let the app know something wrong happened
        videoCallback(MM_DRVM_ERROR, 0UL);
        return;
    }

    lpVHdr->dwFlags |= VHDR_DONE;

	// Sanity check
    if (lpVHdr->dwBytesUsed == 0)
	{
		DEBUGMSG(ZONE_STREAMING, ("  %s: Let the app know that there is no valid data available in lpVHdr=0x%08lX\r\n", _fx_, lpVHdr));

		// Return frame to the pool before notifying app
		AddBuffer(lpVHdr);
        videoCallback(MM_DRVM_ERROR, 0UL);
    }
	else
	{
		DEBUGMSG(ZONE_STREAMING, ("  %s: Let the app know that there is data available in lpVHdr=0x%08lX\r\n", _fx_, lpVHdr));

        lpVHdr->dwTimeCaptured = timeGetTime() - m_dwTimeStart;

		// Let the app know there's some valid video data available
        videoCallback(MM_DRVM_DATA, (DWORD_PTR)lpVHdr);
    }
}


/****************************************************************************
 *  @doc INTERNAL CWDMSTREAMERMETHOD
 *
 *  @mfunc BOOL | CWDMStreamer | Start | This function starts streaming
 *    video data coming from the WDM device.
 *
 *  @rdesc Returns TRUE if successful, or FALSE otherwise.
 *
 *  @devnote This function handles what was the DVM_STREAM_START message in VfW.
 ***************************************************************************/
BOOL CWDMStreamer::Start()
{
	FX_ENTRY("CWDMStreamer::Start");

    ULONG i;
    LPVIDEOHDR lpVHdr;
	DWORD dwThreadID;

	ASSERT(m_fVideoOpen && m_pWDMVideoPin->GetAverageTimePerFrame() && !m_hThread);

	// Make sure this is a valid call
	if (!m_fVideoOpen || !m_pWDMVideoPin->GetAverageTimePerFrame() || m_hThread)
	{
		DEBUGMSG(ZONE_STREAMING, ("%s: Invalid parameters\r\n", _fx_));
		return FALSE;
	}

	DEBUGMSG(ZONE_STREAMING, ("%s: Streaming in %d video buffers at %d frames/sec\r\n", _fx_, m_cntNumVidBuf, 1000000 / m_pWDMVideoPin->GetAverageTimePerFrame()));

	// Allocate and initialize the video buffer structures
    m_pBufTable = (PBUFSTRUCT) new BUFSTRUCT[m_cntNumVidBuf];
    if (m_pBufTable)
	{
		lpVHdr = m_lpVHdrFirst;
		for (i = 0; i < m_cntNumVidBuf && lpVHdr; i++)
		{
			m_pBufTable[i].fReady = TRUE;
			m_pBufTable[i].lpVHdr = lpVHdr;
			lpVHdr = (LPVIDEOHDR) lpVHdr->dwReserved[0];
		}
	}
	else
	{
		DEBUGMSG(ZONE_STREAMING, ("%s: m_pBufTable allocation failed! AsynIO may be out of sequence\r\n", _fx_));
	}

    m_idxNextVHdr		= 0UL;  // 0..m_cntNumVidBuf-1
    m_dwTimeStart		= timeGetTime();
    m_fStreamingStarted	= TRUE;
	m_bKillThread = FALSE;

	DEBUGMSG(ZONE_STREAMING, ("%s: Creating %d read video buffers\r\n", _fx_, m_cntNumVidBuf));

	if (!(m_pWDMVideoBuff = (WDMVIDEOBUFF *) new WDMVIDEOBUFF[m_cntNumVidBuf]))
	{
		DEBUGMSG(ZONE_STREAMING, ("%s: m_Overlap allocation failed!\r\n", _fx_));
		return FALSE;
	}

	for(i=0; i<m_cntNumVidBuf; i++)
	{
		// Create the overlapped structures
		ZeroMemory( &(m_pWDMVideoBuff[i].Overlap), sizeof(OVERLAPPED) );
		m_pWDMVideoBuff[i].Overlap.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

		DEBUGMSG(ZONE_STREAMING, ("%s: Event %d is handle 0x%08lX\r\n", _fx_, i, m_pWDMVideoBuff[i].Overlap.hEvent));
	}

	m_dwNextToComplete=0;

    // Create the streaming thread
    m_hThread = CreateThread((LPSECURITY_ATTRIBUTES)NULL,
                                0,
                                (LPTHREAD_START_ROUTINE)ThreadStub,
                                this,
                                CREATE_SUSPENDED,
                                &dwThreadID);

    if (m_hThread == NULL)
    {
		ERRORMESSAGE(("%s: Couldn't create the thread\r\n", _fx_));

		for (UINT i=0; i<m_cntNumVidBuf; i++)
		{
			if (m_pWDMVideoBuff[i].Overlap.hEvent)
				CloseHandle(m_pWDMVideoBuff[i].Overlap.hEvent);
		}

		delete []m_pWDMVideoBuff;
		m_pWDMVideoBuff = (WDMVIDEOBUFF *)NULL;

		m_lpVHdrFirst = (LPVIDEOHDR)NULL;
		m_lpVHdrLast = (LPVIDEOHDR)NULL;
		if (m_pBufTable)
		{
			delete []m_pBufTable;
			m_pBufTable = NULL;
		}

        return FALSE;
    }

    SetThreadPriority(m_hThread, THREAD_PRIORITY_ABOVE_NORMAL);

    ResumeThread(m_hThread);

	DEBUGMSG(ZONE_STREAMING, ("%s: Thread created OK\r\n", _fx_));

    return TRUE;

}


/****************************************************************************
 *  @doc INTERNAL CWDMSTREAMERMETHOD
 *
 *  @mfunc BOOL | CWDMStreamer | Stream | This function does the actual
 *    streaming.
 ***************************************************************************/
void CWDMStreamer::Stream()
{
	FX_ENTRY("CWDMStreamer::Stream");

	DEBUGMSG(ZONE_STREAMING, ("%s: Starting to process StreamingThread\r\n", _fx_));

	// Put the pin in streaming mode
	m_pWDMVideoPin->Start();

	// Queue all the reads
	for (UINT i = 0; i<m_cntNumVidBuf; i++)
	{
		QueueRead(i);
	}

	m_dwNextToComplete=0;
#ifdef _DEBUG
	m_dwFrameCount=0;
#endif
	BOOL  bGotAFrame=FALSE;
	DWORD dwRes;

	DEBUGMSG(ZONE_STREAMING, ("\r\n%s: Starting to wait on reads to complete\r\n", _fx_));

	while (!m_bKillThread)
	{
		bGotAFrame = FALSE;

		if (m_pWDMVideoBuff[m_dwNextToComplete].fBlocking)
		{
			DEBUGMSG(ZONE_STREAMING, ("\r\n%s: Waiting on read to complete...\r\n", _fx_));

			// Waiting for the asynchronous read to complete
			dwRes = WaitForSingleObject(m_pWDMVideoBuff[m_dwNextToComplete].Overlap.hEvent, 1000*1);

			if (dwRes == WAIT_FAILED)
			{
				DEBUGMSG(ZONE_STREAMING, ("%s: ...we couldn't perform the wait as requested\r\n", _fx_));
			}

			if (dwRes == WAIT_OBJECT_0)
			{
				DEBUGMSG(ZONE_STREAMING, ("%s: ...wait is over - we now have a frame\r\n", _fx_));
				bGotAFrame = TRUE;
			}
			else
			{
				// time out waiting for frames.
				if (dwRes == WAIT_TIMEOUT)
				{
					DEBUGMSG(ZONE_STREAMING, ("%s: Waiting failed with timeout, last error=%d\r\n", _fx_, GetLastError()));
				}
			}
		}
		else
		{
			// We didn't have to wait - this means the read executed synchronously
			bGotAFrame = TRUE;
		}

		if (bGotAFrame)
		{
			DEBUGMSG(ZONE_STREAMING, ("%s: Trying to give frame #%ld to the client\r\n", _fx_, m_dwFrameCount++));

			LPVIDEOHDR lpVHdr;

			lpVHdr = m_pWDMVideoBuff[m_dwNextToComplete].pVideoHdr;

			if (lpVHdr)
			{
				lpVHdr->dwBytesUsed = m_pWDMVideoBuff[m_dwNextToComplete].SHGetImage.StreamHeader.DataUsed;

				if ((m_pWDMVideoBuff[m_dwNextToComplete].SHGetImage.FrameInfo.dwFrameFlags & 0x00f0) == KS_VIDEO_FLAG_I_FRAME)
					lpVHdr->dwFlags |= VHDR_KEYFRAME;
			}

			// Mark the buffer as done - signal the app
			BufferDone(lpVHdr);

			// Queue a new read
			QueueRead(m_dwNextToComplete);
		}

		m_dwNextToComplete++;
		m_dwNextToComplete %= m_cntNumVidBuf;
	}

	DEBUGMSG(ZONE_STREAMING, ("%s: End of the streaming thread\r\n", _fx_));

	ExitThread(0);
}


/****************************************************************************
 *  @doc INTERNAL CWDMSTREAMERMETHOD
 *
 *  @mfunc BOOL | CWDMStreamer | QueueRead | This function queues a read
 *    operation on a video streaming pin.
 *
 *  @parm DWORD | dwIndex | Index of the video structure in read buffer.
 *
 *  @rdesc Returns TRUE if successful, or FALSE otherwise.
 ***************************************************************************/
BOOL CWDMStreamer::QueueRead(DWORD dwIndex)
{
	FX_ENTRY("CWDMStreamer::QueueRead");

	DWORD cbReturned;
	BOOL  bShouldBlock = FALSE;

	DEBUGMSG(ZONE_STREAMING, ("\r\n%s: Queue read buffer %d on pin handle 0x%08lX\r\n", _fx_, dwIndex, m_pWDMVideoPin->GetPinHandle()));

	// Get a buffer from the queue of video buffers
	m_pWDMVideoBuff[dwIndex].pVideoHdr = DeQueueHeader();

	if (m_pWDMVideoBuff[dwIndex].pVideoHdr)
	{
		ZeroMemory(&m_pWDMVideoBuff[dwIndex].SHGetImage, sizeof(m_pWDMVideoBuff[dwIndex].SHGetImage));
		m_pWDMVideoBuff[dwIndex].SHGetImage.StreamHeader.Size				= sizeof (KS_HEADER_AND_INFO);
		m_pWDMVideoBuff[dwIndex].SHGetImage.FrameInfo.ExtendedHeaderSize	= sizeof (KS_FRAME_INFO);
		m_pWDMVideoBuff[dwIndex].SHGetImage.StreamHeader.Data				= m_pWDMVideoBuff[dwIndex].pVideoHdr->lpData;
		m_pWDMVideoBuff[dwIndex].SHGetImage.StreamHeader.FrameExtent		= m_pWDMVideoPin->GetFrameSize();

		// Submit the read
		BOOL bRet = DeviceIoControl(m_pWDMVideoPin->GetPinHandle(), IOCTL_KS_READ_STREAM, &m_pWDMVideoBuff[dwIndex].SHGetImage, sizeof(m_pWDMVideoBuff[dwIndex].SHGetImage), &m_pWDMVideoBuff[dwIndex].SHGetImage, sizeof(m_pWDMVideoBuff[dwIndex].SHGetImage), &cbReturned, &m_pWDMVideoBuff[dwIndex].Overlap);

		if (!bRet)
		{
			DWORD dwErr = GetLastError();
			switch(dwErr)
			{
				case ERROR_IO_PENDING:
					DEBUGMSG(ZONE_STREAMING, ("%s: An overlapped IO is going to take place\r\n", _fx_));
					bShouldBlock = TRUE;
					break;

				// Something bad happened
				default:
					DEBUGMSG(ZONE_STREAMING, ("%s: DeviceIoControl() failed badly dwErr=%d\r\n", _fx_, dwErr));
					break;
			}
		}
		else
		{
			DEBUGMSG(ZONE_STREAMING, ("%s: Overlapped IO won't take place - no need to wait\r\n", _fx_));
		}
	}
	else
	{
		DEBUGMSG(ZONE_STREAMING, ("%s: We won't queue the read - no buffer available\r\n", _fx_));
	}

	m_pWDMVideoBuff[dwIndex].fBlocking = bShouldBlock;

	return bShouldBlock;
}


/****************************************************************************
 *  @doc INTERNAL CWDMSTREAMERMETHOD
 *
 *  @mfunc BOOL | CWDMStreamer | ThreadStub | Thread stub.
 ***************************************************************************/
LPTHREAD_START_ROUTINE CWDMStreamer::ThreadStub(CWDMStreamer *pCWDMStreamer)
{
	FX_ENTRY("CWDMStreamer::ThreadStub");

	DEBUGMSG(ZONE_STREAMING, ("%s: Thread stub called, starting streaming...\r\n", _fx_));

    pCWDMStreamer->Stream();

	DEBUGMSG(ZONE_STREAMING, ("%s: ...capture thread has stopped\r\n", _fx_));

    return(0);
}

