windows-nt/Source/XPSP1/NT/enduser/netmeeting/av/dcap/32/wdmpin.cpp

635 lines
20 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
/****************************************************************************
* @doc INTERNAL WDMPIN
*
* @module WDMPin.cpp | Include file for <c CWDMPin> class used to access
* video data on a video streaming pin exposed by the WDM class driver.
*
* @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 CWDMPINMETHOD
*
* @mfunc void | CWDMPin | CWDMPin | Video pin class constructor.
*
* @parm DWORD | dwDeviceID | Capture device ID.
***************************************************************************/
CWDMPin::CWDMPin(DWORD dwDeviceID) : CWDMDriver(dwDeviceID)
{
m_hKS = (HANDLE)NULL;
m_fStarted = FALSE;
m_hKsUserDLL = (HINSTANCE)NULL;
m_pKsCreatePin = (LPFNKSCREATEPIN)NULL;
ZeroMemory(&m_biHdr, sizeof(KS_BITMAPINFOHEADER));
}
/****************************************************************************
* @doc INTERNAL CWDMPINMETHOD
*
* @mfunc void | CWDMPin | ~CWDMPin | Video pin class destructor. Closes
* the video pin and releases the video buffers allocated.
***************************************************************************/
CWDMPin::~CWDMPin()
{
FX_ENTRY("CWDMPin::~CWDMPin");
DEBUGMSG(ZONE_INIT, ("%s: Destroying the video pin, m_hKS=0x%08lX\r\n", _fx_, m_hKS));
// Nuke the video streaming pin
DestroyPin();
// Close the driver
if (GetDriverHandle())
CloseDriver();
// Release kernel streaming DLL (KSUSER.DLL)
if (m_hKsUserDLL)
FreeLibrary(m_hKsUserDLL);
}
/****************************************************************************
* @doc INTERNAL CWDMPINMETHOD
*
* @mfunc BOOL | CWDMPin | GetFrame | This function gets a frame from the
* video streaming pin.
*
* @parm LPVIDEOHDR | lpVHdr | Pointer to the destination buffer to receive
* the video frame and information.
*
* @parm PDWORD | pdwBytesUsed | Pointer to the number of bytes used to
* read the video frame.
*
* @rdesc Returns TRUE if successful, or FALSE otherwise.
***************************************************************************/
BOOL CWDMPin::GetFrame(LPVIDEOHDR lpVHdr)
{
FX_ENTRY("CWDMPin::GetFrame");
ASSERT(lpVHdr && lpVHdr->lpData && GetDriverHandle() && m_hKS && (lpVHdr->dwBufferLength >= m_biHdr.biSizeImage));
DWORD bRtn;
// Check input params and state
if (!lpVHdr || !lpVHdr->lpData || !GetDriverHandle() || !m_hKS || (lpVHdr->dwBufferLength < m_biHdr.biSizeImage))
{
ERRORMESSAGE(("%s: No buffer, no driver, no PIN connection, or buffer too small\r\n", _fx_));
goto MyError0;
}
// Put the pin in streaming mode
if (!Start())
{
ERRORMESSAGE(("%s: Cannot set streaming state to KSSTATE_RUN\r\n", _fx_));
goto MyError0;
}
// Initialize structure to do a read on the video pin
DWORD cbBytesReturned;
KS_HEADER_AND_INFO SHGetImage;
ZeroMemory(&SHGetImage,sizeof(SHGetImage));
SHGetImage.StreamHeader.Data = (LPDWORD)lpVHdr->lpData;
SHGetImage.StreamHeader.Size = sizeof (KS_HEADER_AND_INFO);
SHGetImage.StreamHeader.FrameExtent = m_biHdr.biSizeImage;
SHGetImage.FrameInfo.ExtendedHeaderSize = sizeof (KS_FRAME_INFO);
// Read a frame on the video pin
bRtn = DeviceIoControl(m_hKS, IOCTL_KS_READ_STREAM, &SHGetImage, sizeof(SHGetImage), &SHGetImage, sizeof(SHGetImage), &cbBytesReturned);
if (!bRtn)
{
ERRORMESSAGE(("%s: DevIo rtn (%d), GetLastError=%d. StreamState->STOP\r\n", _fx_, bRtn, GetLastError()));
// Stop streaming on the video pin
Stop();
goto MyError0;
}
// Sanity check
ASSERT(SHGetImage.StreamHeader.FrameExtent >= SHGetImage.StreamHeader.DataUsed);
if (SHGetImage.StreamHeader.FrameExtent < SHGetImage.StreamHeader.DataUsed)
{
ERRORMESSAGE(("%s: We've corrupted memory!\r\n", _fx_));
goto MyError0;
}
lpVHdr->dwTimeCaptured = timeGetTime();
lpVHdr->dwBytesUsed = SHGetImage.StreamHeader.DataUsed;
lpVHdr->dwFlags |= VHDR_KEYFRAME;
return TRUE;
MyError0:
if (lpVHdr)
{
lpVHdr->dwBytesUsed = 0UL;
lpVHdr->dwTimeCaptured = timeGetTime();
}
return FALSE;
}
/****************************************************************************
* @doc INTERNAL CWDMPINMETHOD
*
* @mfunc BOOL | CWDMPin | Start | This function puts the video
* pin in streaming mode.
*
* @rdesc Returns TRUE if successful, or FALSE otherwise.
***************************************************************************/
BOOL CWDMPin::Start()
{
if (m_fStarted)
return TRUE;
if (SetState(KSSTATE_PAUSE))
m_fStarted = SetState(KSSTATE_RUN);
return m_fStarted;
}
/****************************************************************************
* @doc INTERNAL CWDMPINMETHOD
*
* @mfunc BOOL | CWDMPin | Stop | This function stops streaming on the
* video pin.
*
* @rdesc Returns TRUE if successful, or FALSE otherwise.
***************************************************************************/
BOOL CWDMPin::Stop()
{
if (m_fStarted)
{
if (SetState(KSSTATE_PAUSE))
if (SetState(KSSTATE_STOP))
m_fStarted = FALSE;
}
return (BOOL)(m_fStarted == FALSE);
}
/****************************************************************************
* @doc INTERNAL CWDMPINMETHOD
*
* @mfunc BOOL | CWDMPin | SetState | This function sets the state of the
* video streaming pin.
*
* @parm KSSTATE | ksState | New state.
*
* @rdesc Returns TRUE if successful, or FALSE otherwise.
***************************************************************************/
BOOL CWDMPin::SetState(KSSTATE ksState)
{
KSPROPERTY ksProp = {0};
DWORD cbRet;
ksProp.Set = KSPROPSETID_Connection;
ksProp.Id = KSPROPERTY_CONNECTION_STATE;
ksProp.Flags = KSPROPERTY_TYPE_SET;
return DeviceIoControl(m_hKS, IOCTL_KS_PROPERTY, &ksProp, sizeof(ksProp), &ksState, sizeof(KSSTATE), &cbRet);
}
/****************************************************************************
* @doc INTERNAL CWDMPINMETHOD
*
* @mfunc BOOL | CWDMPin | SetState | This function either finds a video
* data range compatible with the bitamp info header passed in, of the
* prefered video data range.
*
* @parm PKS_BITMAPINFOHEADER | pbiHdr | Bitmap info header to match.
*
* @parm BOOL | pfValidMatch | Set to TRUE if a match was found, FALSE
* otherwise.
*
* @rdesc Returns a valid pointer to a <t KS_DATARANGE_VIDEO> structure if
* successful, or a NULL pointer otherwise.
*
* @comm \\redrum\slmro\proj\wdm10\src\dvd\amovie\proxy\filter\ksutil.cpp(207):KsGetMediaTypes(
***************************************************************************/
PKS_DATARANGE_VIDEO CWDMPin::FindMatchDataRangeVideo(PKS_BITMAPINFOHEADER pbiHdr, BOOL *pfValidMatch)
{
FX_ENTRY("CWDMPin::FindMatchDataRangeVideo");
ASSERT(pfValidMatch && pbiHdr);
// Check input params and state
if (!pbiHdr || !pfValidMatch)
{
ERRORMESSAGE(("%s: Bad input params\r\n", _fx_));
return (PKS_DATARANGE_VIDEO)NULL;
}
// Default
*pfValidMatch = FALSE;
PDATA_RANGES pDataRanges = GetDriverSupportedDataRanges();
ASSERT(pDataRanges != 0);
if (!pDataRanges)
return (PKS_DATARANGE_VIDEO)NULL;
PKS_DATARANGE_VIDEO pSelDRVideo, pDRVideo = &pDataRanges->Data, pFirstDRVideo = 0;
KS_BITMAPINFOHEADER * pbInfo;
// PhilF-: This code assumes that all structures are KS_DATARANGE_VIDEO. This
// may not be a valid assumption foir palettized data types. Check with JayBo
for (ULONG i = 0; i < pDataRanges->Count; i++)
{
// Meaningless unless it is *_VIDEOINFO
if (pDRVideo->DataRange.Specifier == KSDATAFORMAT_SPECIFIER_VIDEOINFO)
{
// We don't care about TV Tuner like devices
if (pDRVideo->ConfigCaps.VideoStandard == KS_AnalogVideo_None)
{
// Save first useable data range
if (!pFirstDRVideo)
pFirstDRVideo = pDRVideo;
pbInfo = &((pDRVideo->VideoInfoHeader).bmiHeader);
if ( (pbInfo->biBitCount == pbiHdr->biBitCount) && (pbInfo->biCompression == pbiHdr->biCompression) &&
( (((pDRVideo->ConfigCaps.OutputGranularityX == 0) || (pDRVideo->ConfigCaps.OutputGranularityY == 0))
&& (pDRVideo->ConfigCaps.InputSize.cx == pbiHdr->biWidth) && (pDRVideo->ConfigCaps.InputSize.cy == pbiHdr->biHeight)) ||
((pDRVideo->ConfigCaps.MinOutputSize.cx <= pbiHdr->biWidth) && (pbiHdr->biWidth <= pDRVideo->ConfigCaps.MaxOutputSize.cx) &&
(pDRVideo->ConfigCaps.MinOutputSize.cy <= pbiHdr->biHeight) && (pbiHdr->biHeight <= pDRVideo->ConfigCaps.MaxOutputSize.cy) &&
((pbiHdr->biWidth % pDRVideo->ConfigCaps.OutputGranularityX) == 0) && ((pbiHdr->biHeight % pDRVideo->ConfigCaps.OutputGranularityY) == 0)) ) )
{
*pfValidMatch = TRUE;
pSelDRVideo = pDRVideo;
break;
}
} // VideoStandard
} // Specifier
pDRVideo++; // Next KS_DATARANGE_VIDEO
}
// If no valid match, use the first range found
if (!*pfValidMatch)
pSelDRVideo = pFirstDRVideo;
return (pSelDRVideo);
}
/****************************************************************************
* @doc INTERNAL CWDMPINMETHOD
*
* @mfunc BOOL | CWDMPin | CreatePin | This function actually creates a
* video streaming pin on the class driver.
*
* @parm PKS_BITMAPINFOHEADER | pbiNewHdr | This pointer to a bitmap info
* header specifies the format of the video data we want from the pin.
*
* @parm DWORD | dwAvgTimePerFrame | This parameter specifies the frame
* at which we want video frames to be produced on the pin.
*
* @rdesc Returns TRUE is successful, FALSE otherwise.
***************************************************************************/
BOOL CWDMPin::CreatePin(PKS_BITMAPINFOHEADER pbiNewHdr, DWORD dwAvgTimePerFrame)
{
FX_ENTRY("CWDMPin::CreatePin");
ASSERT(m_pKsCreatePin);
PKS_BITMAPINFOHEADER pbiHdr;
BOOL bMustMatch, bValidMatch;
#ifdef _DEBUG
char szFourCC[5] = {0};
#endif
if (pbiNewHdr)
{
// We need to find a video data range that matches the bitmap info header passed in
bMustMatch = TRUE;
pbiHdr = pbiNewHdr;
}
else
{
// We'll use the preferred video data range and default bitmap format
bMustMatch = FALSE;
pbiHdr = &m_biHdr;
}
PKS_DATARANGE_VIDEO pSelDRVideo = FindMatchDataRangeVideo(pbiHdr, &bValidMatch);
if (!pSelDRVideo)
return FALSE;
if (bMustMatch && !bValidMatch)
return FALSE;
// If we already have a pin, nuke it
if (GetPinHandle())
DestroyPin();
// Connect to a new PIN.
DATAPINCONNECT DataConnect;
ZeroMemory(&DataConnect, sizeof(DATAPINCONNECT));
DataConnect.Connect.PinId = 0; // CODEC0 sink
DataConnect.Connect.PinToHandle = NULL; // no "connect to"
DataConnect.Connect.Interface.Set = KSINTERFACESETID_Standard;
DataConnect.Connect.Interface.Id = KSINTERFACE_STANDARD_STREAMING; // STREAMING
DataConnect.Connect.Medium.Set = KSMEDIUMSETID_Standard;
DataConnect.Connect.Medium.Id = KSMEDIUM_STANDARD_DEVIO;
DataConnect.Connect.Priority.PriorityClass = KSPRIORITY_NORMAL;
DataConnect.Connect.Priority.PrioritySubClass = 1;
CopyMemory(&(DataConnect.Data.DataFormat), &(pSelDRVideo->DataRange), sizeof(KSDATARANGE));
CopyMemory(&(DataConnect.Data.VideoInfoHeader), &pSelDRVideo->VideoInfoHeader, sizeof(KS_VIDEOINFOHEADER));
// Adjust the image sizes if necessary
if (bValidMatch)
{
DataConnect.Data.VideoInfoHeader.bmiHeader.biWidth = pbiHdr->biWidth;
DataConnect.Data.VideoInfoHeader.bmiHeader.biHeight = abs(pbiHdr->biHeight); // Support only +biHeight!
DataConnect.Data.VideoInfoHeader.bmiHeader.biSizeImage = pbiHdr->biSizeImage;
}
// Overwrite the default frame rate if non-zero
if (dwAvgTimePerFrame > 0)
DataConnect.Data.VideoInfoHeader.AvgTimePerFrame = (REFERENCE_TIME)dwAvgTimePerFrame;
#ifdef _DEBUG
*((DWORD*)&szFourCC) = DataConnect.Data.VideoInfoHeader.bmiHeader.biCompression;
#endif
DEBUGMSG(ZONE_INIT, ("%s: Request image format: FourCC(%s) %d * %d * %d bits = %d bytes\r\n", _fx_, szFourCC, DataConnect.Data.VideoInfoHeader.bmiHeader.biWidth, DataConnect.Data.VideoInfoHeader.bmiHeader.biHeight, DataConnect.Data.VideoInfoHeader.bmiHeader.biBitCount, DataConnect.Data.VideoInfoHeader.bmiHeader.biSizeImage));
DEBUGMSG(ZONE_INIT, ("%s: Request frame rate: %d fps\r\n", _fx_, 10000000/dwAvgTimePerFrame));
DEBUGMSG(ZONE_INIT, ("%s: m_hKS was=0x%08lX\r\n", _fx_, m_hKS));
#ifndef HIDE_WDM_DEVICES
DWORD dwErr = (*m_pKsCreatePin)(GetDriverHandle(), (PKSPIN_CONNECT)&DataConnect, GENERIC_READ | GENERIC_WRITE, &m_hKS);
#else
DWORD dwErr = 0UL;
m_hKS = NULL;
#endif
if (dwAvgTimePerFrame != 0)
{
DEBUGMSG(ZONE_INIT, ("%s: m_hKS is now=0x%08lX set to stream at %d fps\r\n", _fx_, m_hKS, 10000000/dwAvgTimePerFrame));
}
else
{
DEBUGMSG(ZONE_INIT, ("%s: m_hKS is now=0x%08lX\r\n", _fx_, m_hKS));
}
if (dwErr || (m_hKS == NULL))
{
ERRORMESSAGE(("%s: KsCreatePin returned 0x%08lX failure and m_hKS=0x%08lX\r\n", _fx_, dwErr, m_hKS));
if (m_hKS == INVALID_HANDLE_VALUE)
{
m_hKS = (HANDLE)NULL;
}
return FALSE;
}
// Cache the bitmap info header
CopyMemory(&m_biHdr, &DataConnect.Data.VideoInfoHeader.bmiHeader, sizeof(KS_BITMAPINFOHEADER));
m_dwAvgTimePerFrame = (DWORD)DataConnect.Data.VideoInfoHeader.AvgTimePerFrame;
DEBUGMSG(ZONE_INIT, ("%s: New m_biHdr:\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_, m_biHdr.biSize, m_biHdr.biWidth, m_biHdr.biHeight, m_biHdr.biPlanes, m_biHdr.biBitCount, m_biHdr.biCompression, m_biHdr.biSizeImage));
DEBUGMSG(ZONE_INIT, ("%s: New m_dwAvgTimePerFrame=%ld (%fd fps)\r\n", _fx_, m_dwAvgTimePerFrame, 10000000/m_dwAvgTimePerFrame));
return TRUE;
}
/****************************************************************************
* @doc INTERNAL CWDMPINMETHOD
*
* @mfunc BOOL | CWDMPin | DestroyPin | This function nukes a video
* streaming pin.
*
* @rdesc Returns TRUE is successful, FALSE otherwise.
***************************************************************************/
BOOL CWDMPin::DestroyPin()
{
BOOL fRet = TRUE;
FX_ENTRY("CWDMPin::DestroyPin");
DEBUGMSG(ZONE_INIT, ("%s: Destroy PIN m_hKS=0x%08lX\r\n", _fx_, m_hKS));
if (m_hKS)
{
Stop();
if (!(fRet = CloseHandle(m_hKS)))
{
ERRORMESSAGE(("%s: CloseHandle(m_hKS=0x%08lX) failed with GetLastError()=0x%08lX\r\n", _fx_, m_hKS, GetLastError()));
}
m_hKS = NULL;
}
return fRet;
}
/****************************************************************************
* @doc INTERNAL CWDMPINMETHOD
*
* @mfunc BOOL | CWDMPin | SetBitmapInfo | This function sets the video
* format of video streaming pin.
*
* @parm PKS_BITMAPINFOHEADER | pbiHdrNew | This pointer to a bitmap info
* header specifies the format of the video data we want from the pin.
*
* @rdesc Returns TRUE is successful, FALSE otherwise.
***************************************************************************/
BOOL CWDMPin::SetBitmapInfo(PKS_BITMAPINFOHEADER pbiHdrNew)
{
FX_ENTRY("CWDMPin::SetBitmapInfo");
// Validate call
if (!GetDriverHandle())
{
ERRORMESSAGE(("%s: Driver hasn't been opened yet\r\n", _fx_));
return FALSE;
}
DEBUGMSG(ZONE_INIT, ("%s: New pbiHdrNew:\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_, pbiHdrNew->biSize, pbiHdrNew->biWidth, pbiHdrNew->biHeight, pbiHdrNew->biPlanes, pbiHdrNew->biBitCount, pbiHdrNew->biCompression, pbiHdrNew->biSizeImage));
// Check if we need to change anything
if ( GetPinHandle() && (m_biHdr.biHeight == pbiHdrNew->biHeight) && (m_biHdr.biWidth == pbiHdrNew->biWidth) &&
(m_biHdr.biBitCount == pbiHdrNew->biBitCount) && (m_biHdr.biSizeImage == pbiHdrNew->biSizeImage) &&
(m_biHdr.biCompression == pbiHdrNew->biCompression) )
return TRUE;
else
return CreatePin(pbiHdrNew, m_dwAvgTimePerFrame);
}
/****************************************************************************
* @doc INTERNAL CWDMPINMETHOD
*
* @mfunc BOOL | CWDMPin | GetBitmapInfo | This function gets the video
* format of a video streaming pin.
*
* @parm PKS_BITMAPINFOHEADER | pbInfo | This parameter points to a bitmap
* info header structure to receive the video format.
*
* @parm WORD | wSize | This parameter specifies the size of the bitmap
* info header structure.
*
* @rdesc Returns TRUE is successful, FALSE otherwise.
***************************************************************************/
BOOL CWDMPin::GetBitmapInfo(PKS_BITMAPINFOHEADER pbInfo, WORD wSize)
{
FX_ENTRY("CWDMPin::GetBitmapInfo");
// Validate call
if (!m_hKS && !m_biHdr.biSizeImage)
{
ERRORMESSAGE(("%s: No existing PIN handle or no available format\r\n", _fx_));
return FALSE;
}
CopyMemory(pbInfo, &m_biHdr, wSize);
// Support only positive +biHeight.
if (pbInfo->biHeight < 0)
{
pbInfo->biHeight = -pbInfo->biHeight;
DEBUGMSG(ZONE_INIT, ("%s: Changed biHeight from -%ld to %ld\r\n", _fx_, pbInfo->biHeight, pbInfo->biHeight));
}
return TRUE;
}
/****************************************************************************
* @doc INTERNAL CWDMPINMETHOD
*
* @mfunc BOOL | CWDMPin | GetPaletteInfo | This function gets the video
* palette of a video streaming pin.
*
* @parm CAPTUREPALETTE * | pPal | This parameter points to a palette
* structure to receive the video palette.
*
* @parm DWORD | dwcbSize | This parameter specifies the size of the video
* palette.
*
* @rdesc Returns TRUE is successful, FALSE otherwise.
***************************************************************************/
BOOL CWDMPin::GetPaletteInfo(CAPTUREPALETTE *pPal, DWORD dwcbSize)
{
FX_ENTRY("CWDMPin::GetBitmapInfo");
// Validate call
if (!m_hKS && !m_biHdr.biSizeImage && (m_biHdr.biBitCount > 8))
{
ERRORMESSAGE(("%s: No existing PIN handle, no available format, or bad biBitCount\r\n", _fx_));
return FALSE;
}
// PhilF-: Copy some real bits there
// CopyMemory(pbInfo, &m_biHdr, wSize);
return TRUE;
}
/****************************************************************************
* @doc INTERNAL CWDMPINMETHOD
*
* @mfunc BOOL | CWDMPin | SetAverageTimePerFrame | This function sets the
* video frame rate of a video streaming pin.
*
* @parm DWORD | dwAvgTimePerFrame | This parameter specifies the rate
* at which we want video frames to be produced on the pin.
*
* @rdesc Returns TRUE is successful, FALSE otherwise.
***************************************************************************/
BOOL CWDMPin::SetAverageTimePerFrame(DWORD dwNewAvgTimePerFrame)
{
FX_ENTRY("CWDMPin::SetAverageTimePerFrame");
// Validate call
if (!GetDriverHandle())
{
ERRORMESSAGE(("%s: Driver hasn't been opened yet\r\n", _fx_));
return FALSE;
}
DEBUGMSG(ZONE_INIT, ("%s: Current frame interval=%d; new frame intercal=%d\r\n", _fx_, m_dwAvgTimePerFrame, dwNewAvgTimePerFrame));
if (m_dwAvgTimePerFrame != dwNewAvgTimePerFrame)
return CreatePin(&m_biHdr, dwNewAvgTimePerFrame);
else
{
DEBUGMSG(ZONE_INIT, ("%s: No need to change frame rate\r\n", _fx_));
return TRUE;
}
}
/****************************************************************************
* @doc INTERNAL CWDMPINMETHOD
*
* @mfunc BOOL | CWDMPin | OpenDriverAndPin | This function opens the class
* driver and creates a video streaming pin.
*
* @rdesc Returns TRUE is successful, FALSE otherwise.
***************************************************************************/
BOOL CWDMPin::OpenDriverAndPin()
{
FX_ENTRY("CWDMPin::OpenDriverAndPin");
// Load KSUSER.DLL and get a proc address
if (m_hKsUserDLL = LoadLibrary("KSUSER"))
{
if (m_pKsCreatePin = (LPFNKSCREATEPIN)GetProcAddress(m_hKsUserDLL, "KsCreatePin"))
{
// Open the class driver
if (OpenDriver())
{
// Create a video streaming pin on the driver
if (CreatePin((PKS_BITMAPINFOHEADER)NULL))
{
return TRUE;
}
else
{
DEBUGMSG(ZONE_INIT, ("%s: Pin connection creation failed!\r\n", _fx_));
if (GetDriverHandle())
CloseDriver();
}
}
}
FreeLibrary(m_hKsUserDLL);
}
return FALSE;
}