VIDCAP.CPP

//==========================================================================; 
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
// PURPOSE.
//
// Copyright (c) 1992 - 1997 Microsoft Corporation. All Rights Reserved.
//
//--------------------------------------------------------------------------;

//
// Video capture stream source filter
//

// Uses the AVICap window to capture video data to pass downstream.
// By using the video callback it avoids AVICap sending data to a file
// - AVICap is merely capturing buffers for us to pass on.
//

// Caveats
//
// ** Should reject going active when the user has format dialogs up.

#include <streams.h>
#include <initguid.h>
#include <olectl.h>
#include <mmsystem.h>
#include <vfw.h>
#include <string.h>
#include <stddef.h> // for offsetof macro

#include "vidcap.h"

// setup data

const AMOVIESETUP_MEDIATYPE sudOpPinTypes =
{ &MEDIATYPE_Video // clsMajorType
, &MEDIASUBTYPE_NULL }; // clsMinorType

const AMOVIESETUP_PIN sudOpPin =
{ L"Output" // strName
, FALSE // bRendered
, TRUE // bOutput
, FALSE // bZero
, FALSE // bMany
, &CLSID_NULL // clsConnectsToFilter
, NULL // strConnectsToPin
, 1 // nMediaTypes
, &sudOpPinTypes }; // lpMediaType

const AMOVIESETUP_FILTER sudVidCapax =
{ &CLSID_VidCap // clsID
, L"Video Capture (AVICap)" // strName
, MERIT_UNLIKELY // dwMerit
, 1 // nPins
, &sudOpPin }; // lpPin

// COM global table of objects available in this dll
CFactoryTemplate g_Templates[1] = {

{ L"Video Capture (AVICap)"
, &CLSID_VidCap
, CVidCap::CreateInstance
, NULL
, NULL }
};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);


// exported entry points for registration and
// unregistration (in this case they only call
// through to default implmentations).
//
STDAPI DllRegisterServer()
{
HRESULT hr = AMovieDllRegisterServer2( TRUE );
if(SUCCEEDED(hr))
{

// locate each installed VFW capture driver and register an
// entry in the Video Capture entry that maps to this filter. We
// expect other capture filters to register just once
//
IFilterMapper2 *pFm2;
HRESULT hr = CoCreateInstance(
CLSID_FilterMapper2, NULL, CLSCTX_INPROC_SERVER,
IID_IFilterMapper2, (void **)&pFm2);
if(SUCCEEDED(hr))
{

for(UINT i = 0; i < 10; i++)
{
TCHAR szName[100], szDesc[100];
if(capGetDriverDescription(
i, szName, sizeof(szName), szDesc, sizeof(szDesc)))
{
// generate unique instance value (avicap - name). This
// should be something that doesn't change from machine
// to machine as it's used to persist this particular
// device (see documentation for
// IMoniker::GetDisplayName). Behind the scenes, this is
// the name of the registry key for this device. We'll
// use this name for both the instance name and the
// friendly-name
TCHAR szUniq[200];
wsprintf(szUniq, "avicap SDK Sample - %s", szName);

WCHAR wszUniq[200];
MultiByteToWideChar(CP_ACP, 0, szUniq, -1, wszUniq, NUMELMS(wszUniq));

// RegisterFilter returns a moniker here.
IMoniker *pMoniker = 0;

REGFILTER2 rf2;
rf2.dwVersion = 1;
rf2.dwMerit = MERIT_DO_NOT_USE;
rf2.cPins = 0;
rf2.rgPins = 0;


hr = pFm2->RegisterFilter(
CLSID_VidCap,
wszUniq,
&pMoniker,
&CLSID_VideoInputDeviceCategory,
wszUniq,
&rf2);

if(SUCCEEDED(hr))
{
// write out the device number. when the device
// is picked this filter needs to know which
// device to open. It does that by reading the
// AviCapIndex value.
IPropertyBag *pPropBag;
hr = pMoniker->BindToStorage(
0, 0, IID_IPropertyBag, (void **)&pPropBag);
if(SUCCEEDED(hr))
{
VARIANT var;
var.vt = VT_I4;
var.lVal = i;
hr = pPropBag->Write(L"AviCapIndex", &var);

pPropBag->Release();
}
pMoniker->Release();
}

} //capGetDriverDescription

if(FAILED(hr))
break;

} // for loop

pFm2->Release();
} // CoCreateInstance

} // AMovieDllRegisterServer2

return hr;
}

STDAPI DllUnregisterServer()
{
HRESULT hr = AMovieDllRegisterServer2( FALSE );
if(SUCCEEDED(hr))
{

// remove each entry

IFilterMapper2 *pFm2;
HRESULT hr = CoCreateInstance(
CLSID_FilterMapper2, NULL, CLSCTX_INPROC_SERVER,
IID_IFilterMapper2, (void **)&pFm2);
if(SUCCEEDED(hr))
{

for(UINT i = 0; i < 10; i++)
{
TCHAR szName[100], szDesc[100];
if(capGetDriverDescription(
i, szName, sizeof(szName), szDesc, sizeof(szDesc)))
{
// generate unique instance value (avicap - name). This
// should be something that doesn't change from machine
// to machine as it's used to persist this particular
// device (see documentation for
// IMoniker::GetDisplayName). Behind the scenes, this is
// the name of the registry key for this device. We'll
// use this name for both the instance name and the
// friendly-name
TCHAR szUniq[200];
wsprintf(szUniq, "avicap SDK Sample - %s", szName);

WCHAR wszUniq[200];
MultiByteToWideChar(CP_ACP, 0, szUniq, -1, wszUniq, NUMELMS(wszUniq));

hr = pFm2->UnregisterFilter(
&CLSID_VideoInputDeviceCategory,
wszUniq,
CLSID_VidCap);

} //capGetDriverDescription

} // for loop

pFm2->Release();
} // CoCreateInstance

} // AMovieDllRegisterServer2

return hr;
}


//
// CVidCap::Constructor
//
// don't create any pins yet, until we are told which device to use
// (through IPersistPropertyBag or CPersistStream)
CVidCap::CVidCap(TCHAR *pName, LPUNKNOWN lpunk, HRESULT *phr)
: CSource(pName, lpunk, CLSID_VidCap),
CPersistStream(lpunk, phr),
m_pCapturePin(NULL),
m_pOverlayPin(NULL),
m_pPreviewPin(NULL),
m_iVideoId(-1)
{

CAutoLock l(&m_cStateLock);

DbgLog((LOG_TRACE, 1, TEXT("CVidCap filter created")));
}


//
// CVidCap::Destructor
//
CVidCap::~CVidCap(void)
{
DbgLog((LOG_TRACE, 1, TEXT("CVidCap filter destroyed")) );

if (m_pCapturePin)
delete m_pCapturePin;
if (m_pOverlayPin)
delete m_pOverlayPin;
if (m_pPreviewPin)
delete m_pPreviewPin;
}


//
// CreateInstance
//
// Called by CoCreateInstance to create a vidcap filter.
CUnknown * WINAPI CVidCap::CreateInstance(LPUNKNOWN lpunk, HRESULT *phr)
{

CUnknown *punk = new CVidCap(TEXT("Video capture filter"), lpunk, phr);
if (punk == NULL) {
*phr = E_OUTOFMEMORY;
}
return punk;
}


// give out our interfaces
STDMETHODIMP CVidCap::NonDelegatingQueryInterface(REFIID riid, void ** ppv)
{
if (riid == IID_IAMVfwCaptureDialogs) {
return GetInterface((LPUNKNOWN)(IAMVfwCaptureDialogs *)this, ppv);
} else if (riid == IID_IPersistPropertyBag) {
return GetInterface((IPersistPropertyBag *)this, ppv);
} else if(riid == IID_IPersistStream) {
return GetInterface((IPersistStream *)this, ppv);
}

return CSource::NonDelegatingQueryInterface(riid, ppv);
}


// how many pins do we have? maybe 2, maybe 1, maybe 0
//
int CVidCap::GetPinCount()
{
DbgLog((LOG_TRACE,5,TEXT("CVidCap::GetPinCount")));

// we have a preview pin (one or the other)
if (m_pOverlayPin || m_pPreviewPin)
return 2;
else if (m_pCapturePin)
return 1;
else
return 0;
}


// Give out pointers to our pins. We might have an overlay preview pin
// or a non-overlay preview pin
//
CBasePin * CVidCap::GetPin(int ii)
{
DbgLog((LOG_TRACE,5,TEXT("CVidCap::GetPin")));

if (ii == 0 && m_pCapturePin)
return m_pCapturePin;
if (ii == 1 && m_pOverlayPin)
return m_pOverlayPin;
if (ii == 1 && m_pPreviewPin)
return m_pPreviewPin;
return NULL;
}


// IPersistPropertyBag stuff
//
// Load is called to tell us what device to use. There may be several
// capture cards on the system that we could use
STDMETHODIMP CVidCap::Load(LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog)
{
HRESULT hr;
CAutoLock l(pStateLock());

DbgLog((LOG_TRACE,1,TEXT("Load...")));

// We already have some pins, thank you
if (m_pCapturePin)
return E_UNEXPECTED;

// Default to capture device #0
if (pPropBag == NULL) {
m_iVideoId = 0;
DbgLog((LOG_TRACE,1,TEXT("Using default device ID=%d"), m_iVideoId));
CreatePins(&hr);
return hr;
}

// find out what device to use
// different filters look in different places to find this info
VARIANT var;
var.vt = VT_I4;
hr = pPropBag->Read(L"AviCapIndex", &var, 0);
if(SUCCEEDED(hr))
{
hr = S_OK;
m_iVideoId = var.lVal;
DbgLog((LOG_TRACE,1,TEXT("Using device ID=%d"), m_iVideoId));
CreatePins(&hr);
}
return hr;
}


STDMETHODIMP CVidCap::Save(
LPPROPERTYBAG pPropBag, BOOL fClearDirty,
BOOL fSaveAllProperties)
{
// E_NOTIMPL is not really a valid return code as any object implementing
// this interface must support the entire functionality of the
// interface.
return E_NOTIMPL;
}


// have we been initialized yet? (Has somebody called Load)
STDMETHODIMP CVidCap::InitNew()
{
if(m_pCapturePin)
{
ASSERT(m_iVideoId != -1);
return HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED);
}
else
{
return S_OK;
}
}


// CPersistStream stuff
//
// what is our class ID?
STDMETHODIMP CVidCap::GetClassID(CLSID *pClsid)
{
CheckPointer(pClsid, E_POINTER);
*pClsid = CLSID_VidCap;
return S_OK;
}

// CSource expects all its pins to derive from CSourceStream. Since
// this sample doesn't do this, we implement QueryId and FindPin to
// provide matching implementations

STDMETHODIMP CVidCap::FindPin(
LPCWSTR Id, IPin ** ppPin)
{
return CBaseFilter::FindPin(Id, ppPin);
}


HRESULT CVidCap::WriteToStream(IStream *pStream)
{
ASSERT(m_iVideoId >= -1 && m_iVideoId < 10);
return pStream->Write(&m_iVideoId, sizeof(LONG), 0);
}


// what device should we use? Used to re-create a .GRF file that we
// are in
HRESULT CVidCap::ReadFromStream(IStream *pStream)
{
if(m_pCapturePin)
{
ASSERT(m_iVideoId != -1);
return HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED);
}

ASSERT(m_iVideoId == -1);

LONG iVideoId;
HRESULT hr = pStream->Read(&iVideoId, sizeof(LONG), 0);
if(FAILED(hr))
return hr;

m_iVideoId = iVideoId;
DbgLog((LOG_TRACE,1,TEXT("Using device ID=%d"), m_iVideoId));

hr = S_OK;
CreatePins(&hr);
return hr;
}


// How long is our data? Just a long int (m_iVideoId)
int CVidCap::SizeMax()
{
return sizeof(LONG);
}


// Now we can create our output pins, after a device is chosen
//
void CVidCap::CreatePins(HRESULT *phr)
{
if (FAILED(*phr))
return;

CAutoLock l(pStateLock());

if (m_pCapturePin)
*phr = HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED);

ASSERT(m_iVideoId != -1);// no device chosen yet?!

// Our capture pin MUST be called L"Capture"
m_pCapturePin = new CVidStream(NAME("Video capture stream"),
phr, this, m_iVideoId, L"Capture");
if (m_pCapturePin == NULL) {
*phr = E_OUTOFMEMORY;
return;
}

if (FAILED(*phr)) {
delete m_pCapturePin;
m_pCapturePin = NULL;
return;
}

// We can do overlay, so let's make a preview pin that does overlay
// Otherwise, do a preview pin that will fake up a preview
if (m_pCapturePin->m_HasOverlay)
m_pOverlayPin = CreateOverlayPin(phr);
else
m_pPreviewPin = CreatePreviewPin(phr);
}


// tell CBaseStreamControl what clock to use
//
STDMETHODIMP CVidCap::SetSyncSource(IReferenceClock *pClock)
{
if (m_pCapturePin)
m_pCapturePin->SetSyncSource(pClock);
if (m_pPreviewPin)
m_pPreviewPin->SetSyncSource(pClock);
return CSource::SetSyncSource(pClock);
}


// tell CBaseStreamControl what sink to use
//
STDMETHODIMP CVidCap::JoinFilterGraph(IFilterGraph * pGraph, LPCWSTR pName)
{
HRESULT hr = CSource::JoinFilterGraph(pGraph, pName);
if (hr == S_OK && m_pCapturePin)
m_pCapturePin->SetFilterGraph(m_pSink);
if (hr == S_OK && m_pPreviewPin)
m_pPreviewPin->SetFilterGraph(m_pSink);
return hr;
}


// we don't send any data during PAUSE, so to avoid hanging renderers, we
// need to return VFW_S_CANT_CUE when paused
//
STDMETHODIMP CVidCap::GetState(DWORD dwMSecs, FILTER_STATE *State)
{
UNREFERENCED_PARAMETER(dwMSecs);
CheckPointer(State,E_POINTER);
ValidateReadWritePtr(State,sizeof(FILTER_STATE));

*State = m_State;
if (m_State == State_Paused)
return VFW_S_CANT_CUE;
else
return S_OK;
}


// Run
//
// Activate the pin, letting it know that we are moving to State_Running
//
STDMETHODIMP CVidCap::Run(REFERENCE_TIME tStart) {
CAutoLock l(pStateLock());

DbgLog((LOG_TRACE,2,TEXT("::Run")));

HRESULT hr;

m_tStart = tStart; // remember the stream time offset

hr = CSource::Run(tStart);

// Tell CBaseStreamControl what's going on
m_pCapturePin->NotifyFilterState(State_Running, tStart);
if (m_pPreviewPin)
m_pPreviewPin->NotifyFilterState(State_Running, tStart);

if (SUCCEEDED(hr)) {
// start us running
m_pCapturePin->Run();
// overlay pin wants to know too
if (m_pOverlayPin && m_pOverlayPin->IsConnected())
m_pOverlayPin->ActiveRun(tStart);
// preview pin wants to know too
if (m_pPreviewPin && m_pPreviewPin->IsConnected())
m_pPreviewPin->ActiveRun(tStart);
}

return S_OK;
}


//
// Pause
//
// Activate the pin, letting it know that Paused will
// be the next state
STDMETHODIMP CVidCap::Pause(void) {
CAutoLock l(pStateLock());

BOOL fWasStopped = FALSE;

if (m_State == State_Paused) {
return S_OK;
}

DbgLog((LOG_TRACE,2,TEXT("::Pause")));

// The video renderer will start blocking Deliver() when it goes from
// run to pause (or any filter could potentially do that) making us
// hang, so we need to tell it to release the sample by flushing
// it before we tell the thread to pause, or the thread will be hung
// and never get our message
if (m_State == State_Running) {
m_pCapturePin->DeliverBeginFlush();
m_pCapturePin->DeliverEndFlush();

// our overlay pin wants to know when we stop running
if (m_pOverlayPin)
m_pOverlayPin->ActivePause();
// our preview pin wants to know when we stop running
if (m_pPreviewPin)
m_pPreviewPin->ActivePause();
}

// we're streaming the graph now, so we better close our temporary
// window, so we can open it for real later, with the real # of buffers
if (m_State == State_Stopped && m_pCapturePin->m_hwCapCapturing) {
m_pCapturePin->DestroyCaptureWindow(m_pCapturePin->m_hwCapCapturing);
m_pCapturePin->m_hwCapCapturing = NULL;
}

// the capture pin is in fact going to start streaming... tell the
// preview pin to release the hardware
if (m_State == State_Stopped && m_pCapturePin->IsConnected()) {
fWasStopped = TRUE;
if (m_pPreviewPin)
m_pPreviewPin->CapturePinActive(TRUE);
}

// need to change state before sending the pause request
// or the thread will not be created when we try to signal it.
HRESULT hr = CSource::Pause();
if (FAILED(hr)) {
// error, never mind
if (fWasStopped && m_pPreviewPin)
m_pPreviewPin->CapturePinActive(FALSE);
return hr;
}

// the source stream base class seems to freak out if we pause it when not
// connected
if (m_pCapturePin->IsConnected()) {
// Tell CBaseStreamControl what's going on
m_pCapturePin->NotifyFilterState(State_Paused, 0);
if (m_pPreviewPin)
m_pPreviewPin->NotifyFilterState(State_Paused, 0);

hr = m_pCapturePin->Pause();
// error, never mind
if (FAILED(hr) && fWasStopped && m_pPreviewPin)
m_pPreviewPin->CapturePinActive(FALSE);
return hr;
}

return NOERROR;
}


//
// Stop
//
// Pass the current state to the pins Inactive method
STDMETHODIMP CVidCap::Stop(void) {
HRESULT hr;
CAutoLock l(pStateLock());

DbgLog((LOG_TRACE,2,TEXT("::Stop")));

// Shame on the base classes, they don't take care of this, and it's very
// important for us that we go through pause on our way to stop
if (m_State == State_Running) {
hr = Pause();
if (FAILED(hr))
return hr;
}

// Tell stream control we're stopped, so he will stop blocking the thread
// that captures the video (if we're in discarding mode). Calling
// CSource::Stop below will hang if the AVICAP capture thread is blocked,
// because it's going to destroy that thread.
m_pCapturePin->NotifyFilterState(State_Stopped, 0);
if (m_pPreviewPin)
m_pPreviewPin->NotifyFilterState(State_Stopped, 0);

hr = CSource::Stop();

// tell our preview pin that it can have the h/w if it wants it.
// we're through
if (m_pPreviewPin)
m_pPreviewPin->CapturePinActive(FALSE);

m_tStart = CRefTime(0L);

return hr;
}


// create the preview pin we use if we have overlay hardware
//
CVidOverlay * CVidCap::CreateOverlayPin(HRESULT * phr)
{
DbgLog((LOG_TRACE,2,TEXT("CreateOverlayPin")));

WCHAR wszPinName[16];
lstrcpyW(wszPinName, L"Preview");

CVidOverlay * pOverlay = new CVidOverlay(NAME("Video Overlay Stream"),
this, phr, wszPinName);
if (!pOverlay)
*phr = E_OUTOFMEMORY;

// if initialization failed, delete the stream array
// and return the error
//
if (FAILED(*phr) && pOverlay)
delete pOverlay, pOverlay = NULL;

return pOverlay;
}


// create the preview pin we use if we DO NOT have overlay hardware
//
CVidPreview * CVidCap::CreatePreviewPin(HRESULT * phr)
{
DbgLog((LOG_TRACE,2,TEXT("CreatePreviewPin")));

WCHAR wszPinName[16];
lstrcpyW(wszPinName, L"Preview");

CVidPreview * pPreview = new CVidPreview(NAME("Video Preview Stream"),
this, phr, wszPinName);
if (!pPreview)
*phr = E_OUTOFMEMORY;

// if initialization failed, delete the stream array
// and return the error
//
if (FAILED(*phr) && pPreview)
delete pPreview, pPreview = NULL;

return pPreview;
}



//
// IAMVfwCaptureDialogs implementation
//

// Does this driver support a particular dialog box?
//
HRESULT CVidCap::HasDialog(int iDialog)
{
if (iDialog == VfwCaptureDialog_Source)
return (m_pCapturePin->m_SupportsVideoSourceDialog ? S_OK : S_FALSE);
else if (iDialog == VfwCaptureDialog_Format)
return (m_pCapturePin->m_SupportsVideoFormatDialog ? S_OK : S_FALSE);
else if (iDialog == VfwCaptureDialog_Display)
return (m_pCapturePin->m_SupportsVideoDisplayDialog ? S_OK : S_FALSE);
else
return E_INVALIDARG;
}


// Show a particular dialog box of the driver
//
HRESULT CVidCap::ShowDialog(int iDialog, HWND hwnd)
{
HRESULT hr;

// we can't hold any critical sections while the dialog box is up

// before bringing up a dialog that could change our format, make
// sure we're not streaming
if (m_State != State_Stopped) {
return E_UNEXPECTED;
}

// !!! If the filter starts streaming while the dialog is up, this
// could cause problems! We aren't protecting against this

// open the device temporarily - if we don't put the hwnd in our
// m_hwCapCapturing variable, GetMediaType below won't get the
// format set by the dialog box
if (m_pCapturePin->m_hwCapCapturing == NULL)
m_pCapturePin->m_hwCapCapturing = m_pCapturePin->CreateCaptureWindow(0);
if (m_pCapturePin->m_hwCapCapturing == NULL)
return E_FAIL;

if (iDialog == VfwCaptureDialog_Source)
hr = capDlgVideoSource(m_pCapturePin->m_hwCapCapturing) == TRUE ?
NOERROR : E_FAIL;
else if (iDialog == VfwCaptureDialog_Format)
hr = capDlgVideoFormat(m_pCapturePin->m_hwCapCapturing) == TRUE ?
NOERROR : E_FAIL;
else if (iDialog == VfwCaptureDialog_Display)
hr = capDlgVideoDisplay(m_pCapturePin->m_hwCapCapturing) == TRUE ?
NOERROR : E_FAIL;
else {
hr = E_INVALIDARG;
}

// bringing up the Format Dialog can change the format we are capturing
// with.
if (hr == NOERROR && iDialog == VfwCaptureDialog_Format) {
DbgLog((LOG_TRACE,1,TEXT("Dialog changed our output format")));

// now get the new format chosen in the dialog. First of all,
// forget any remembered type in m_mt, so that GetMediaType will
// quiz the driver for the format set by the dialog
m_pCapturePin->m_mt.SetType(&GUID_NULL);
CMediaType cmt;
m_pCapturePin->GetMediaType(&cmt);
// now put the media type back in case this new type doesn't take
if (m_pCapturePin->IsConnected())
m_pCapturePin->m_mt.SetType(&MEDIATYPE_Video);

// If we are connected to somebody, make sure they like it
if (m_pCapturePin->IsConnected())
hr = m_pCapturePin->GetConnected()->QueryAccept(&cmt);

if (hr == NOERROR) {
hr = m_pCapturePin->SetMediaType(&cmt);
// Now reconnect us so the graph starts using the new format
if (hr == NOERROR)
m_pCapturePin->Reconnect(TRUE);
}
}

// leave the driver open so we can connect quickly. We'll close the driver
// before we start streaming

return hr;
}


// used to send secret messages to a capture driver. Use at your own risk
//
HRESULT CVidCap::SendDriverMessage(int iDialog, int uMsg, long dw1, long dw2)
{
return E_NOTIMPL;// too scary
}




// *
// * Implements CVidStream - manages the output pin
// *


//
// CVidStream::Constructor
//
// keep the driver index to open.
// Well behaved filters are supposed to only hold resources (like opening
// the capture driver) when they are streaming, so we will open the
// capture driver temporarily only (until we are streaming), and then close
// it again right away. Drivers usually have no memory across opens, so
// we'll have to remember what format we want to capture with and when we
// finally open it for real, send that format to the driver, as it will
// have long forgotten.
// !!! If we really cared about performance, we wouldn't open and close
// the driver so much. That takes forever. Also, we do extra memory copies
// that slow things down. But this is only a sample driver
//
CVidStream::CVidStream(TCHAR *pObjectName
, HRESULT *phr
, CVidCap *pParentFilter
, unsigned int uiDriverIndex
, LPCWSTR pPinName
)
:CSourceStream(pObjectName, phr, pParentFilter, pPinName),
m_uiDriverIndex(uiDriverIndex),
m_plFilled(NULL),
m_hwCapCapturing(NULL),
m_fSetFormatCalled(FALSE),
m_dwMicroSecPerFrame(66667), // default to 15 fps
m_uiFramesCaptured(0),
m_uiFramesSkipped(0),
m_llTotalFrameSize(0),
m_uiFramesDelivered(0)
{

CAutoLock lock(m_pFilter->pStateLock());

// for IAMBufferNegotiation - no suggestions so far
m_propSuggested.cBuffers = -1;
m_propSuggested.cbBuffer = -1;
m_propSuggested.cbAlign = -1;
m_propSuggested.cbPrefix = -1;

// open the driver temporarily
m_hwCapCapturing = CreateCaptureWindow(0);
if (m_hwCapCapturing == NULL) {
*phr = E_FAIL;
return;
}

// get the name and version of the driver

#ifdef UNICODE
capDriverGetName(m_hwCapCapturing, m_szName, sizeof(m_szName));
capDriverGetVersion(m_hwCapCapturing, m_szVersion, sizeof(m_szVersion));
#else
char sz[giDriverNameStrLen];
capDriverGetName(m_hwCapCapturing, sz, sizeof(sz));
MultiByteToWideChar(CP_ACP, 0,
sz, -1,
m_szName, giDriverNameStrLen);

capDriverGetVersion(m_hwCapCapturing, sz, sizeof(sz));
MultiByteToWideChar(CP_ACP, 0,
sz, -1,
m_szVersion, giDriverVerStrLen);
#endif
// Establish what dialogs this driver can display.

CAPDRIVERCAPS DriverCaps;
capDriverGetCaps(m_hwCapCapturing, &DriverCaps, sizeof(DriverCaps) );
m_SupportsVideoSourceDialog = DriverCaps.fHasDlgVideoSource;
m_SupportsVideoDisplayDialog = DriverCaps.fHasDlgVideoDisplay;
m_SupportsVideoFormatDialog = DriverCaps.fHasDlgVideoFormat;
m_HasOverlay = DriverCaps.fHasOverlay;
#if 0
m_SuppliesPalettes = DriverCaps.fDriverSuppliesPalettes;
#endif

// leave the driver open so we can connect quickly. We'll close the driver
// before we start streaming

DbgLog( (LOG_TRACE, 1, TEXT("CVidStream created") ) );
}


//
// CVidStream::Destructor
//
// we should be inactive before this is called.
CVidStream::~CVidStream(void) {

CAutoLock lock(m_pFilter->pStateLock());

ASSERT(!m_pFilter->IsActive());

if (m_hwCapCapturing)
DestroyCaptureWindow(m_hwCapCapturing);

DbgLog( (LOG_TRACE, 1, TEXT("CVidStream destroyed") ) );

}


// set the new media type
//
HRESULT CVidStream::SetMediaType(const CMediaType* pmt)
{
DbgLog((LOG_TRACE,2,TEXT("SetMediaType %x %dbit %dx%d"),
HEADER(pmt->Format())->biCompression,
HEADER(pmt->Format())->biBitCount,
HEADER(pmt->Format())->biWidth,
HEADER(pmt->Format())->biHeight));

ASSERT(((CVidCap *)m_pFilter)->m_State == State_Stopped);

// We are being told the frame rate to use. It is in the VIDEOINFOHEADER
if (((VIDEOINFOHEADER *)(pmt->pbFormat))->AvgTimePerFrame) {
m_dwMicroSecPerFrame = (DWORD)(((VIDEOINFOHEADER *)(pmt->pbFormat))->
AvgTimePerFrame / 10);
DbgLog((LOG_TRACE,2,TEXT("SetMediaType: New frame rate is %d us per frame"),
m_dwMicroSecPerFrame));
}

// !!! The bit rate to use is in the VIDEOINFOHEADER too, but we can't
// obey it... we have no programmatic way of setting it, only through
// a dialog box

// now reconnect our preview pin to use the same format as us
Reconnect(FALSE);

// this will remember the media type in m_mt, and when we open the
// driver for real, we'll send it this format to use
return CSourceStream::SetMediaType(pmt);
}


// stop remembering what media type we are supposed to use once we start
// streaming - all bets are off until we connect again... unless somebody
// called SetFormat... we will always use that format from now on
//
HRESULT CVidStream::BreakConnect()
{
if (m_fSetFormatCalled == FALSE)
m_mt.SetType(&GUID_NULL);

return CSourceStream::BreakConnect(); 
}


//
// CheckMediaType
//
// Queries the video driver to see if the format is acceptable
// The only way to query if we support a given format is to set the driver
// to use that format and see if it succeeds or fails. So we better only
// accept queries until we start streaming, because then we'd actually
// affect the capture!
//
HRESULT CVidStream::CheckMediaType(const CMediaType *pmt) {

DbgLog((LOG_TRACE,3,TEXT("CheckMediaType")));

CAutoLock l(&m_cSharedState);
CAutoLock lock(m_pFilter->pStateLock());

// bad idea to set the capture format while capturing...
if (((CVidCap *)m_pFilter)->m_State != State_Stopped)
return E_UNEXPECTED;

if (pmt == NULL || pmt->Format() == NULL) {
DbgLog((LOG_TRACE,3,TEXT("Rejecting: type/format is NULL")));
return E_INVALIDARG;
}

// we only support MEDIATYPE_Video
if (*pmt->Type() != MEDIATYPE_Video) {
DbgLog((LOG_TRACE,3,TEXT("Rejecting: not VIDEO")));
return E_INVALIDARG;
}

// check this is a VIDEOINFOHEADER type
if (*pmt->FormatType() != FORMAT_VideoInfo) {
DbgLog((LOG_TRACE,3,TEXT("Rejecting: format not VIDINFO")));
return E_INVALIDARG;
}

RECT rcS = ((VIDEOINFOHEADER *)pmt->Format())->rcSource;
RECT rcT = ((VIDEOINFOHEADER *)pmt->Format())->rcTarget;
if (!IsRectEmpty(&rcT) && (rcT.left != 0 || rcT.top != 0 ||
HEADER(pmt->Format())->biWidth != rcT.right ||
HEADER(pmt->Format())->biHeight != rcT.bottom)) {
DbgLog((LOG_TRACE,3,TEXT("Rejecting: can't use funky rcTarget")));
return VFW_E_INVALIDMEDIATYPE;
}
// We don't know what this would be relative to... reject everything
if (!IsRectEmpty(&rcS)) {
DbgLog((LOG_TRACE,3,TEXT("Rejecting: can't use funky rcSource")));
return VFW_E_INVALIDMEDIATYPE;
}

// open the driver temporarily
if (m_hwCapCapturing == NULL)
m_hwCapCapturing = CreateCaptureWindow(0);
if (m_hwCapCapturing == NULL)
return E_FAIL;

// the only way we can see if the driver supports a given format is to
// try and set it to use that format.
LPBITMAPINFOHEADER lpbiCheck = HEADER(pmt->Format());
DWORD dw = capSetVideoFormat(m_hwCapCapturing, lpbiCheck,
lpbiCheck->biSize +
((lpbiCheck->biBitCount > 8 || lpbiCheck->biClrUsed) ?
(lpbiCheck->biClrUsed * sizeof(PALETTEENTRY)) :
2 ^ lpbiCheck->biBitCount * sizeof(PALETTEENTRY)));

// leave the driver open so we can connect quickly. We'll close the driver
// before we start streaming

return (dw == TRUE ? NOERROR : VFW_E_INVALIDMEDIATYPE);
}


//
// GetMediaType
//
// Queries the video driver and places an appropriate media type in *pmt
// If we have remembered a type that we are supposed to be using,
// return that one
//
HRESULT CVidStream::GetMediaType(CMediaType *pmt) {

CAutoLock l(&m_cSharedState);

// We've been told by somebody to use a particular media type.
// That's what we'll return
if (m_mt.IsValid()) {
*pmt = m_mt;
return NOERROR;
}

// We may or may not be streaming and have the driver open already
if (m_hwCapCapturing == NULL)
m_hwCapCapturing = CreateCaptureWindow(0);
if (m_hwCapCapturing == NULL)
return E_FAIL;

pmt->SetType(&MEDIATYPE_Video);
pmt->SetFormatType(&FORMAT_VideoInfo);

DWORD dwFormatSize;
VIDEOINFOHEADER *pvi;

dwFormatSize = capGetVideoFormatSize(m_hwCapCapturing);

ASSERT(dwFormatSize > 0);

// Find out how big we need to allocate the buffer
#define AllocBufferSize (max(sizeof(VIDEOINFOHEADER) + sizeof(TRUECOLORINFO), \
dwFormatSize+offsetof(VIDEOINFOHEADER,bmiHeader)))

// Set up the format section of the mediatype to be the right size
pvi = (VIDEOINFOHEADER *) pmt->AllocFormatBuffer(AllocBufferSize);
#undef AllocBufferSize
if (pvi == NULL) {
return E_OUTOFMEMORY;
}

// make sure all fields are initially zero
ZeroMemory((void *)pvi, sizeof(VIDEOINFOHEADER));

// make a note of the current fps we're doing
pvi->AvgTimePerFrame = m_dwMicroSecPerFrame * 10;

// grab the BITMAPINFOHEADER straight in
// will leave the memory after the last palette entry as zeros.
capGetVideoFormat(m_hwCapCapturing, &(pvi->bmiHeader), dwFormatSize);

const GUID SubTypeGUID = GetBitmapSubtype(&pvi->bmiHeader);
pmt->SetSubtype(&SubTypeGUID);
pmt->SetSampleSize(GetSampleSize(&pvi->bmiHeader));
pmt->SetTemporalCompression(FALSE);

// leave the driver open so we can connect quickly. We'll close the driver
// before we start streaming

return NOERROR;
}


//
// OnThreadCreate
//
// Start streaming & reset time samples are stamped with.
HRESULT CVidStream::OnThreadCreate(void) {

CAutoLock l(&m_cSharedState);

m_ThreadState = Stopped;

// we are starting to stream now. Open the capture driver FOR REAL!
// Use however many buffers we're supposed to use
ASSERT(m_hwCapCapturing == NULL);
m_hwCapCapturing = CreateCaptureWindow(m_propActual.cBuffers);
if (m_hwCapCapturing == NULL) {
return E_FAIL;
}

// m_mt is the format we connected with. Tell the driver to use
// that format as its capture format
LPBITMAPINFOHEADER lpbi = HEADER(m_mt.Format());
DWORD dwSize = lpbi->biSize +
((lpbi->biBitCount > 8 || lpbi->biClrUsed) ?
(lpbi->biClrUsed * sizeof(PALETTEENTRY)) :
(2 ^ lpbi->biBitCount * sizeof(PALETTEENTRY)));
DWORD dw = capSetVideoFormat(m_hwCapCapturing, lpbi, dwSize);
ASSERT(dw == TRUE);// we were promised this would work!

m_plFilled = new CVideoBufferList( m_mt.lSampleSize
, m_dwMicroSecPerFrame
, (CVidCap *)m_pFilter
, m_propActual.cBuffers
);
if (m_plFilled == NULL) {
return E_OUTOFMEMORY;
}

// IAMDroppedFrames: every time you start streaming, reset your stats
m_uiFramesCaptured = 0;
m_uiFramesSkipped = 0;
m_llTotalFrameSize = 0;
m_uiFramesDelivered = 0;

return NOERROR;
}


// Inactive - overridden to replace the call to CAMThread::Close with
// different code that will dispatch messages waiting for the thread to
// die. (explained below)
//
HRESULT CVidStream::Inactive(void) {

CAutoLock lock(m_pFilter->pStateLock());

HRESULT hr;

// do nothing if not connected - its ok not to connect to
// all pins of a source filter
if (!IsConnected()) {
return NOERROR;
}

// !!! need to do this before trying to stop the thread, because
// we may be stuck waiting for our own allocator!!!

hr = CBaseOutputPin::Inactive(); // call this first to Decommit the allocator
if (FAILED(hr)) {
return hr;
}

if (ThreadExists()) {
hr = Stop();

if (FAILED(hr)) {
return hr;
}

hr = Exit();
if (FAILED(hr)) {
return hr;
}

// when our main thread shuts down the capture thread, the capture
// thread will destroy the capture window it made, and this will cause
// USER to send messages to our main thread, and if we're blocked
// waiting for the capture thread to go away, we will deadlock
// preventing user from sending us the messages, and thus our thread
// will never go away. We need to dispatch messages while waiting
if (m_hThread) {
// !!! It's more efficient to use MsgWaitForMultipleObjects, I know.
while (WaitForSingleObject(m_hThread, 50) == WAIT_TIMEOUT) {
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
CloseHandle(m_hThread);
m_hThread = 0;
}
}

// hr = CBaseOutputPin::Inactive(); // call this first to Decommit the allocator
//if (FAILED(hr)) {
//return hr;
//}

return NOERROR;
}


//
// OnThreadDestroy
//
// Free the list of completed buffers and stop streaming
// Attempts to stop streaming and destroy the window, even in error
// cases.
HRESULT CVidStream::OnThreadDestroy(void) {

CAutoLock l(&m_cSharedState);

ASSERT(m_ThreadState == Stopped);

// no longer streaming. close the driver
BOOL fWindowGone = DestroyCaptureWindow(m_hwCapCapturing);
m_hwCapCapturing = NULL;

delete m_plFilled, m_plFilled = NULL;

DbgLog((LOG_TRACE, 1, TEXT("Frames Captured: %d"), m_uiFramesCaptured));
DbgLog((LOG_TRACE, 1, TEXT("Frames Skipped: %d"), m_uiFramesSkipped));
DbgLog((LOG_TRACE, 1, TEXT("Frames Delivered: %d"), m_uiFramesDelivered));

if (!fWindowGone) {
return E_UNEXPECTED;
} else {
return NOERROR;
}
}


//
// DecideBufferSize
//
// Get an allocator that we have been asked to get (through
// IAMBufferNegotiation) or that we will be happy with
// Check that the allocator can give us appropriately sized buffers
// Always called after format negotiation.
//
HRESULT CVidStream::DecideBufferSize(IMemAllocator *pAlloc,
ALLOCATOR_PROPERTIES *pProperties)
{
CAutoLock lock(m_pFilter->pStateLock());
CAutoLock l(&m_cSharedState);
ASSERT(pAlloc);
ASSERT(pProperties);
HRESULT hr = NOERROR;

// use the app's numbers, if we were given some through IAMBufferNegotiation
// otherwise, use some default

if (m_propSuggested.cBuffers > 0)
pProperties->cBuffers = m_propSuggested.cBuffers;
else
pProperties->cBuffers = 32;// !!!or whatever

if (m_propSuggested.cbBuffer > 0)
pProperties->cbBuffer = m_propSuggested.cbBuffer;
else
pProperties->cbBuffer = m_mt.lSampleSize;

if (m_propSuggested.cbAlign > 0)
pProperties->cbAlign = m_propSuggested.cbAlign;

if (m_propSuggested.cbPrefix > 0)
pProperties->cbPrefix = m_propSuggested.cbPrefix;

ASSERT(pProperties->cbBuffer);

// Ask the allocator to reserve us some sample memory, NOTE the function
// can succeed (that is return NOERROR) but still not have allocated the
// memory that we requested, so we must check we got whatever we wanted

ALLOCATOR_PROPERTIES Actual;
hr = pAlloc->SetProperties(pProperties,&Actual);
if (SUCCEEDED(hr)) {

// Is this allocator unsuitable
if (Actual.cbBuffer < pProperties->cbBuffer) {
hr = E_FAIL;
}

// remember what properties the allocator is using... somebody
// might ask later
m_propActual = Actual;
}
return hr;
}


//
// GetSampleSize
//
// Given a BITMAPINFOHEADER, calculates the sample size needed.
long CVidStream::GetSampleSize(LPBITMAPINFOHEADER pbmi) {

long lSize;

if (pbmi->biSizeImage > 0) {

lSize = pbmi->biSizeImage;

}
else { // biSizeImage is allowed to be zero for uncompressed formats,
// so do the maths ourselves...

lSize = (pbmi->biWidth *
pbmi->biHeight *
pbmi->biBitCount) / 8 + 1;
if (lSize < 0) { // biHeight was negative
lSize *= -1;
}
}

// round up to nearest DWORD
int rem = lSize % sizeof(DWORD);
if (rem)
lSize += sizeof(DWORD) - rem;

return lSize;
}


//
// CreateCaptureWindow
//
// Create a hidden AVICap window, and make sure it is configured appropriately
// Returns NULL on failure.
// successful calls should be balanced with calls to DestroyCaptureWindow()
// Creates the AVICap window with (up to) lBufferCount no. of buffers.
// use lBufferCount = 0, when you only wish to interrgoate the the driver, or
// specify a number of buffers, if you actually want to capture.
//
HWND CVidStream::CreateCaptureWindow(long lBufferCount)
{

CAutoLock lock(&m_cSharedState);

BOOL bErr; //return code of capXXX calls

HWND hwndCapture; // The window to return

hwndCapture = capCreateCaptureWindow(NULL, // No name
0, // no style.
// defaults to invisible
0, 0, 150, 150, // an arbitrary size
0, // no parent
0); // don't care about the id

if (!hwndCapture) {
DbgLog((LOG_ERROR|LOG_TRACE, 1, TEXT("Window could not be created") ));
return NULL;
}

bErr = capDriverConnect(hwndCapture, m_uiDriverIndex);
if (!bErr) {
DestroyWindow(hwndCapture);
DbgLog((LOG_ERROR|LOG_TRACE, 1, TEXT("Driver failed to connect") ) );
return NULL;
}
DbgLog((LOG_TRACE, 2, TEXT("Driver Connected") ));

CAPTUREPARMS cp;
capCaptureGetSetup(hwndCapture, &cp, sizeof(cp) ); // get the current defaults

cp.dwRequestMicroSecPerFrame = m_dwMicroSecPerFrame; // Set desired frame rate
cp.fMakeUserHitOKToCapture = FALSE;
cp.fYield = TRUE; // we want capture on a
// background thread.
cp.wNumVideoRequested = (WORD) lBufferCount; // we may get less than
// this - no problem
cp.fCaptureAudio = FALSE;
cp.vKeyAbort = 0; // If no key is provided,
// it won't stop...
cp.fAbortLeftMouse = FALSE;
cp.fAbortRightMouse = FALSE;
cp.fLimitEnabled = FALSE; // we want to stop
cp.fMCIControl = FALSE;

capCaptureSetSetup(hwndCapture, &cp, sizeof(cp) );

capSetCallbackOnVideoStream(hwndCapture, &VideoCallback);
capSetCallbackOnFrame(hwndCapture, &VideoCallback); // also use for single
// frame capture

#if 0
CAPSTATUS cs;
ZeroMemory(&cs, sizeof(cs));
capGetStatus(hwndCapture, &cs, sizeof(cs));

// try to see if the driver uses palettes
if (((cs.hPalCurrent != NULL) || (cs.fUsingDefaultPalette))) {
m_UsesPalettes = TRUE;
} else {
m_UsesPalettes = FALSE;
}
if (m_UsesPalettes && m_SuppliesPalettes) {
capPaletteAuto(hwndCapture, 10, 236);
}
#endif

SetWindowLong(hwndCapture, GWL_USERDATA, (LONG) this);

return hwndCapture;
}


//
// DestroyCaptureWindow()
//
// Disconnect the driver before destroying the window.
BOOL CVidStream::DestroyCaptureWindow(HWND hwnd)
{

ASSERT(hwnd != NULL);

// !!! why is this failing?
BOOL bDriverDisconnected = capDriverDisconnect(hwnd);
DbgLog(( LOG_ERROR|LOG_TRACE, 2
, TEXT("Driver disconnect: %x"), bDriverDisconnected) );

BOOL bWindowGone = DestroyWindow(hwnd);
DbgLog((LOG_ERROR|LOG_TRACE, 2, TEXT("Window destroy: %x"), bWindowGone) );

return (bDriverDisconnected && bWindowGone);
}


//
// VideoCallback
//
// The AVICap Video callback. Keep a copy of the buffer we are given
// May be called after the worker thread, or even the pin has gone away,
// depending on AVICap's internal timing. Therefore be very careful with the
// pointers we use.
//
LRESULT CALLBACK CVidStream::VideoCallback(HWND hwnd, LPVIDEOHDR lpVHdr)
{

CVidStream *pThis = (CVidStream *) GetWindowLong(hwnd, GWL_USERDATA);

ASSERT(pThis);

if (pThis->m_plFilled == NULL) { // The filled list has gone away.
// ignore this buffer
return (LRESULT) TRUE;
}
else {
pThis->m_plFilled->Add(lpVHdr);
}

//DbgLog((LOG_TRACE,5,TEXT("Got a buffer back!")));

return (LRESULT) TRUE;
}



// Override to handle quality messages
STDMETHODIMP CVidStream::Notify(IBaseFilter * pSender, Quality q)
{
// if q.Late.RefTime.QuadPart >0 then skip ahead that much.
// thereafter adjust the time per frame by a factor of
// 1000/q.Proportion (watch for truncation of fractions
// do the multiply first!

// Not Yet Implemented :-)

return NOERROR;
}

// CSource expects all its pins to derive from CSourceStream. Since
// this sample doesn't do this, we implment QueryId here and FindPin
// on the filter to provide matching implementations

STDMETHODIMP CVidStream::QueryId(
LPWSTR * Id)
{
return CBaseOutputPin::QueryId(Id);
}




//
// DoBufferProcessingLoop
//
// Replace the loop in CSourceStream with something of my own, so that I can
// wait for buffers & commands.
HRESULT CVidStream::DoBufferProcessingLoop(void) {

DbgLog((LOG_TRACE,2,TEXT("*** Entering DoBufferProcessingLoop")));

HANDLE haWaitObjects[2];
{
CAutoLock l(&m_cSharedState);

haWaitObjects[0] = GetRequestHandle(); // command handle first so that
// it has priority over buffers
haWaitObjects[1] = m_plFilled->GetWaitHandle();
}

for (;;) {

// wait for commands or buffers. This thread created the capture
// window, so we have to dispatch messages frequently on this thread
// or any other thread sending any message to the capture window will
// hang
// !!! It's more efficient to use MsgWaitForMultipleObjects, I know
DWORD dwWaitObject
= WaitForMultipleObjects(2, haWaitObjects, FALSE, 50);

if (dwWaitObject == WAIT_TIMEOUT) {
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
} else if (dwWaitObject == WAIT_OBJECT_0) { // thread command request

Command com;

EXECUTE_ASSERT(CheckRequest(&com));
switch (com) {
case CMD_RUN:

com = GetRequest();

if (m_ThreadState != Running) {
DbgLog((LOG_TRACE,1,TEXT("*** STARTING CAPTURE")));
// !!! This function may take a while to initialize
// capture, so we may not start seeing frames for 1/3 of
// a second or so. Unfortunately, unless we want a dialog
// box to pop up saying "press OK to start" there's no good
// way of starting capture at any known point in time
capCaptureSequenceNoFile(m_hwCapCapturing);
}
m_ThreadState = Running;
Reply(NOERROR);
break;

case CMD_PAUSE:

DbgLog((LOG_TRACE,1,TEXT("CMD_PAUSE")));

com = GetRequest();

switch (m_ThreadState) {
case Stopped:
// first thing sent will be a discontinuity
m_plFilled->m_fLastSampleDiscarded = TRUE;

// init our time stamping variables
m_plFilled->m_rtLastStartTime = -1;
m_plFilled->m_llLastFrame = -1;
m_plFilled->m_llFrameOffset = 0;
m_plFilled->m_fReRun = FALSE;

m_ThreadState = Paused; // mark that we are paused
break;
case Running:
DbgLog((LOG_TRACE,1,TEXT("*** STOPPING CAPTURE")));
// we just went from RUN to PAUSE. If we go to
// RUN again without STOPping first, we will be in
// a situation where the graph was run->pause->run
m_plFilled->m_fReRun = TRUE;
capCaptureStop(m_hwCapCapturing);
break;
default:
// null op
break;
}
m_ThreadState = Paused;
Reply(NOERROR);
break;

case CMD_STOP:

if (m_ThreadState == Running) {
DbgLog((LOG_TRACE,1,TEXT("*** STOPPING CAPTURE")));
capCaptureStop(m_hwCapCapturing);
}
m_ThreadState = Stopped;
//DbgLog((LOG_TRACE, 1, TEXT("Seen Stop command")));
// don't reply here as that is done by CSourceStream
return NOERROR;

default:

ASSERT(!"Unexpected thread command");
com = GetRequest();
break;
}

} else if (dwWaitObject == (WAIT_OBJECT_0 + 1)) { // m_plFilled

// Process the buffer we've just been signalled on
IMediaSample *pSample;

// get a buffer to put this video data in
HRESULT hr = GetDeliveryBuffer(&pSample,NULL,NULL,0);
if (FAILED(hr)) {
continue;
}

hr = FillBuffer(pSample);

// !!! If this fails, we're supposed to stop delivering
Deliver(pSample);

pSample->Release();

} else { // get out - otherwise we will infinite loop...

DbgLog((LOG_TRACE, 1
, TEXT("Unexpected buffer/command wait return: %d")
, dwWaitObject));
return E_UNEXPECTED;
}
}

ASSERT(m_ThreadState == Stopped);

return NOERROR;
}


//
// FillBuffer
//
// Take the buffer from the head of the video buffer list.
// We will only be called when there is such a buffer
//
HRESULT CVidStream::FillBuffer(IMediaSample *pSample) {

CAutoLock l(&m_cSharedState);

HRESULT hr = m_plFilled->RemoveHeadIntoSample(pSample);

return hr;
}



//
// NonDelegatingQueryInterface
//
// Expose all of our interfaces on the pin
//
STDMETHODIMP CVidStream::NonDelegatingQueryInterface(REFIID riid, void ** ppv)
{
CheckPointer(ppv,E_POINTER);

if (riid == IID_IAMStreamControl) {
return GetInterface((LPUNKNOWN)(IAMStreamControl *)this, ppv);
} else if (riid == IID_IAMStreamConfig) {
return GetInterface((LPUNKNOWN)(IAMStreamConfig *)this, ppv);
} else if (riid == IID_IAMVideoCompression) {
return GetInterface((LPUNKNOWN)(IAMVideoCompression *)this, ppv);
} else if (riid == IID_IAMDroppedFrames) {
return GetInterface((LPUNKNOWN)(IAMDroppedFrames *)this, ppv);
} else if (riid == IID_IAMBufferNegotiation) {
return GetInterface((LPUNKNOWN)(IAMBufferNegotiation *)this, ppv);
} else if (riid == IID_IKsPropertySet) {
return GetInterface((LPUNKNOWN)(IKsPropertySet *)this, ppv);
}

return CSourceStream::NonDelegatingQueryInterface(riid, ppv);
}


// *
// * CVideoBufferList
// *


//
// CVideoBufferList::Constructor
//
CVideoBufferList::CVideoBufferList( int iBufferSize
, DWORD dwMicroSecPerFrame
, CVidCap *pFilter
, int iBuffers
)
:m_dwMicroSecPerFrame(dwMicroSecPerFrame),
m_FirstBuffer(TRUE),
m_pFilter(pFilter),
m_evList(TRUE),
m_iPreviewCount(0),
m_lFilled(NAME("Pending, full, buffers"),
DEFAULTCACHE), // default cache
m_lFree(NAME("Empty buffers"),
DEFAULTCACHE) // default cache
{
for (int i=0; i < iBuffers; i++) {

CBuffer *pBuffer = new CBuffer(iBufferSize);
if (pBuffer == NULL) {
return;
}
m_lFree.AddTail(pBuffer);

}
}


//
// CVideoBufferList::Destructor
//
CVideoBufferList::~CVideoBufferList() {

while (m_lFree.GetCount() > 0) { // free buffers on the free list...

CBuffer *pBuff = m_lFree.RemoveHead();
delete pBuff;
}


DbgLog((LOG_TRACE, 1
, TEXT("Filled frames not sent before stop issued: %d")
, m_lFilled.GetCount()));

while (m_lFilled.GetCount() > 0) { //... then free buffers on filled list
// - we don't care about the data they hold.

CBuffer *pBuff = m_lFilled.RemoveHead();
delete pBuff;
}
}


//
// Add
//
// Add a video buffer to this list. gets a free buffer from m_lFree, copies
// the video data into it and then puts it on m_lFilled.
// if m_lFree is empty, fail silently, effectively skipping the buffer.
HRESULT CVideoBufferList::Add(LPVIDEOHDR lpVHdr) {

CAutoLock lck(&m_ListCrit);
CRefTime rt;

//DbgLog((LOG_TRACE,4,TEXT("driver time: %dms"), lpVHdr->dwTimeCaptured));

// Time stamp this sample with the graph's clock time when this sample
// is captured. (no clock? use the driver time)
// !!! hopefully the driver latency isn't too bad! It should really be
// compensated for
if (FAILED(m_pFilter->StreamTime(rt)))// current time
rt = CRefTime((LONG)lpVHdr->dwTimeCaptured); // init with ms

// now ask IAMStreamControl if we need to bother delivering this sample
// I don't have a sample around right now, so I'll make up one. All it
// needs is the time stamp.
// !!! this is kinda hacky
CMemAllocator all(TEXT("Test"), NULL, NULL);
CMediaSample Sample(NULL, &all, NULL);
CRefTime rtEnd = rt + CRefTime((LONGLONG)m_dwMicroSecPerFrame * 10);
Sample.SetTime((REFERENCE_TIME *)&rt, (REFERENCE_TIME *)&rtEnd);
int iStreamState = m_pFilter->m_pCapturePin->CheckStreamState(&Sample);
if (iStreamState == m_pFilter->m_pCapturePin->STREAM_FLOWING) {
// DbgLog((LOG_TRACE,3,TEXT("*VIDCAP ON")));
} else {
// DbgLog((LOG_TRACE,3,TEXT("*VIDCAP OFF")));
m_fLastSampleDiscarded = TRUE;// next one is discontinuity
}

// we are faking up a preview pin and supposed to send it a frame to
// give out every once in a while when we have free time and copying
// the data for preview will NOT HURT CAPTURE PERFORMANCE. Well, if
// our capture pin is currently off, we have nothing to lose. If
// we have no frames on the filled list pending delivery, we'll assume
// we have spare time and can afford to send a preview frame. In any
// case, send a preview frame at least once every 30 frames. (!!!)
// !!! Ideally, we don't necessarily want to send this frame as a preview,
// it may be many seconds old if we have lots of buffering, we want to
// send the most recently captured frame. I don't do that here.
if (m_pFilter->m_pPreviewPin) {
if (iStreamState == m_pFilter->m_pCapturePin->STREAM_DISCARDING ||
m_lFilled.GetCount() == 0 || m_iPreviewCount++ == 30) {
m_iPreviewCount = 0;// reset
m_pFilter->m_pPreviewPin->ReceivePreviewFrame(lpVHdr->lpData,
lpVHdr->dwBufferLength);
}
}

// what frame number is this (based on the time captured)? Round
// such that if frames 1 and 2 are expected at 33 and 66ms, anything
// from 17 to 49 will be considered frame 1.
//
// frame = (us + 1/2(us per frame)) / (us / frame)
//
// then we add an offset if we so desire
//
LONGLONG llFrame = ((lpVHdr->dwTimeCaptured * 1000 +
m_dwMicroSecPerFrame / 2) / m_dwMicroSecPerFrame)
+ m_llFrameOffset;

// NOTE: If the graph is RUN->PAUSE->RUN again we need to
// continue sending frame numbers (in the MediaTime) where we
// left off before pausing. BUT... the driver has been stopped
// and started and is numbering the frames back at zero again!
// So we need to notice that the graph has been paused and re-run
// without stopping in between, and then add as an offset to all
// future frame numbers, the last frame number sent before the
// pause.
if (m_fReRun && llFrame < m_llLastFrame) {
m_llFrameOffset = m_llLastFrame;
llFrame += m_llFrameOffset;
m_fReRun = FALSE;
}

// the time stamps we get from the drivers are not always accurate,
// and we may think we see frame 0, 2, 2, 3, 5, 5, 6, 7, but if
// we just pretended the first 2 was frame 1 and the first 5 was
// frame 4, we wouldn't skip any frames or send the same frame
// twice
if (llFrame == m_llLastFrame + 2)
llFrame -= 1;

// we're supposed to deliver this frame, and we can
if (iStreamState == m_pFilter->m_pCapturePin->STREAM_FLOWING &&
m_lFree.GetCount() > 0) {

// Never send something backwards in time from the last thing
// sent. This can happen with live sources if you run, then
// pause, then run the graph again. The first time stamp after
// the second run might have a time less than the last time stamp
// delivered before the pause. Also, because the graph is run
// 50ms or so before time 0, it's possible for a frame captured
// right away to have a time stamp less than zero. We don't want
// to deliver such samples, but we don't want to consider them
// dropped frames either. We just pretend they never existed.

if (rt >= 0 && rt >= m_rtLastStartTime) { 

// Just like above, where we didn't deliver or skip any frame
// whose time stampes seemed to go backwards in time, we can't
// deliver a frame whose MediaTime stamps don't increase
// monotonically. We don't want to deliver a duplicate media time
// that we already sent, but we don't want to consider them
// dropped frames either. We just pretend they never existed.
if (llFrame > m_llLastFrame) {

// oops, this frame number is more than one frame later than
// the last frame; we've skipped some frames, probably due
// to starving the driver who couldn't capture in time
if (llFrame > m_llLastFrame + 1) {
m_pFilter->m_pCapturePin->m_uiFramesSkipped +=
(DWORD)(llFrame - m_llLastFrame - 1);
}

CBuffer *pBuff = m_lFree.RemoveHead();
pBuff->CopyBuffer(lpVHdr, rt, llFrame);

// remember how to set the sample flags later
pBuff->m_fSyncPoint = lpVHdr->dwFlags & VHDR_KEYFRAME;
// !!! Technically speaking, you should set a discontinuity
// after you drop (skip) a frame, too, but I'm probably dealing
// with all keyframes and it doesn't matter so much if a frame
// is skipped
pBuff->m_fDiscontinuity = m_fLastSampleDiscarded;

m_lFilled.AddTail(pBuff);
if (m_lFilled.GetCount() == 1) {
m_evList.Set();
}
m_pFilter->m_pCapturePin->m_uiFramesCaptured++;
m_fLastSampleDiscarded = FALSE;

// remember the last Time and MediaTime we used
m_rtLastStartTime = rt;
m_llLastFrame = llFrame;
}
}

// we're supposed to deliver this frame, but we can't... it's skipped
} else if (iStreamState == m_pFilter->m_pCapturePin->STREAM_FLOWING) {

m_pFilter->m_pCapturePin->m_uiFramesSkipped++;

// we are discarding this frame. Don't count it as captured or skipped
// We must make a note that we've seen this frame, so we don't think that
// we skipped all the frames we didn't send because stream control was off
} else {
m_llLastFrame = llFrame;
}

return NOERROR;
}


//
// RemoveHeadIntoSample
//
// Copy the head of the filled list into the supplied IMediaSample.
// Fail with E_UNEXPECTED if called on an empty m_lFilled;
// return S_FALSE if you don't want the sample delivered
//
HRESULT CVideoBufferList::RemoveHeadIntoSample(IMediaSample *pSample)
{
HRESULT hr;
CAutoLock lck(&m_ListCrit);

if (m_lFilled.GetCount() < 1) {
hr = E_UNEXPECTED;
} else {

CBuffer *pBuff = m_lFilled.RemoveHead();
if (m_lFilled.GetCount() == 0) {
m_evList.Reset();
}

BYTE *pSampleBuffer;
hr = pSample->GetPointer(&pSampleBuffer);
if (SUCCEEDED(hr)) {

LONG lSampleSize = pBuff->GetSize();
ASSERT(pBuff->GetSize() <= pSample->GetSize());

// Copy the captured data
CopyMemory((void *)pSampleBuffer, pBuff->GetPointer(), lSampleSize);

// set all the sample flags
pSample->SetActualDataLength(lSampleSize);
pSample->SetSyncPoint(pBuff->m_fSyncPoint);
pSample->SetDiscontinuity(pBuff->m_fDiscontinuity);
pSample->SetPreroll(FALSE);


FILTER_STATE State;
m_pFilter->GetState(0, &State);

CRefTime rtStart;

// set the time stamp of this sample
rtStart = pBuff->GetCaptureTime();
CRefTime rtEnd = rtStart + CRefTime((LONGLONG)m_dwMicroSecPerFrame *
10);
DbgLog((LOG_TRACE,4,TEXT("Sample Time: %dms %dms"),
rtStart.Millisecs(), rtEnd.Millisecs()));
ASSERT(rtStart <= rtEnd);
pSample->SetTime((REFERENCE_TIME*)&rtStart,
(REFERENCE_TIME*)&rtEnd);

// also set the media time... which is supposed to be the
// frame number
LONGLONG llFrame = pBuff->GetCaptureFrame();
LONGLONG llFrameE = llFrame + 1;
pSample->SetMediaTime(&llFrame, &llFrameE);
DbgLog((LOG_TRACE,4,TEXT("Media Time: %d"),llFrame));

m_pFilter->m_pCapturePin->m_uiFramesDelivered++;
m_pFilter->m_pCapturePin->m_llTotalFrameSize += lSampleSize;
m_lFree.AddTail(pBuff);
}
}

return hr;
}


//
// CBuffer::Constructor
//
//Get a new buffer of the maximum size we will handle
CVideoBufferList::CBuffer::CBuffer(int iBufferSize) {

m_pData = new BYTE[iBufferSize];

// Set the two length fields to the maximum size
m_iCaptureDataLength = m_iDataLength = iBufferSize;

}


//
// CVideoDataBuffer::Destructor
//
CVideoBufferList::CBuffer::~CBuffer() {

delete m_pData;
}


//
// CopyBuffer
//
// Copy the supplied data in lpVHdr->lpData to this Buffer
// rt is the time stamp
void CVideoBufferList::CBuffer::CopyBuffer(LPVIDEOHDR lpVHdr, CRefTime& rt, LONGLONG llFrame) {

ASSERT((DWORD) m_iDataLength >= lpVHdr->dwBufferLength);

m_rt = rt;// time stamp
m_llFrame = llFrame;// frame number

// Copy the captured video buffer, and remember its length
CopyMemory(m_pData, lpVHdr->lpData, (m_iCaptureDataLength = lpVHdr->dwBufferLength));

}



// Reconnect our pin. We would like to change our output format
// Sometimes extra filters have to be placed in between, like when you
// switch from 8 bit to 16 bit RGB and you have to put a colour converter
// between you and the renderer you were connected to. In this case,
// reconnect won't work, you have to explicitly intelligently connect
// them yourself.
//
void CVidStream::Reconnect(BOOL fCapturePinToo)
{
if (IsConnected() && fCapturePinToo) {
DbgLog((LOG_TRACE,1,TEXT("Need to reconnect our streaming pin")));
CMediaType cmt;
GetMediaType(&cmt);
if (S_OK == GetConnected()->QueryAccept(&cmt)) {
((CVidCap *)m_pFilter)->m_pGraph->Reconnect(this);
} else {
// This will fail if we switch from 8 bit to 16 bit RGB connected
// to a renderer that needs a colour converter inserted to do 16 bit
// Oh boy. We're going to have to get clever and insert some
// filters between us to help us reconnect
DbgLog((LOG_TRACE,1,TEXT("Whoa! We *really* need to reconnect!")));
IPin *pCon = GetConnected();
pCon->AddRef();// or it will go away in Disconnect
((CVidCap *)m_pFilter)->m_pGraph->Disconnect(GetConnected());
((CVidCap *)m_pFilter)->m_pGraph->Disconnect(this);
IGraphBuilder *pFG;
HRESULT hr = ((CVidCap *)m_pFilter)->m_pGraph->QueryInterface(
IID_IGraphBuilder, (void **)&pFG);
if (hr == NOERROR) {
hr = pFG->Connect(this, pCon);
pFG->Release();
}
pCon->Release();
if (hr != NOERROR)
DbgLog((LOG_ERROR,1,TEXT("*** RECONNECT FAILED! ***")));
// !!! We need to notify application that graph is different
}
// when this pin gets reconnected it will call us again to do the
// other two pins
return;
}

// Now reconnect the overlay pin
CVidOverlay *pOverlayPin = ((CVidCap *)m_pFilter)->m_pOverlayPin;
if (pOverlayPin && pOverlayPin->IsConnected()) {
DbgLog((LOG_TRACE,1,TEXT("Need to reconnect our overlay pin")));
CMediaType cmt;
pOverlayPin->GetMediaType(0, &cmt);
if (S_OK == pOverlayPin->GetConnected()->QueryAccept(&cmt)) {
((CVidCap *)m_pFilter)->m_pGraph->Reconnect(pOverlayPin);
} else {
// Huh?
ASSERT(FALSE);
}
}

// Now reconnect the non-overlay preview pin
CVidPreview *pPreviewPin = ((CVidCap *)m_pFilter)->m_pPreviewPin;
if (pPreviewPin && pPreviewPin->IsConnected()) {
DbgLog((LOG_TRACE,1,TEXT("Need to reconnect our preview pin")));
CMediaType cmt;
pPreviewPin->GetMediaType(0, &cmt);
if (S_OK == pPreviewPin->GetConnected()->QueryAccept(&cmt)) {
((CVidCap *)m_pFilter)->m_pGraph->Reconnect(pPreviewPin);
} else {
// Oh boy. We're going to have to get clever and insert some
// filters between us to help us reconnect
DbgLog((LOG_TRACE,1,TEXT("Whoa! We *really* need to reconnect!")));
IPin *pCon = pPreviewPin->GetConnected();
pCon->AddRef();// or it will go away in Disconnect
((CVidCap *)m_pFilter)->m_pGraph->Disconnect(
pPreviewPin->GetConnected());
((CVidCap *)m_pFilter)->m_pGraph->Disconnect(pPreviewPin);
IGraphBuilder *pFG;
HRESULT hr = ((CVidCap *)m_pFilter)->m_pGraph->QueryInterface(
IID_IGraphBuilder, (void **)&pFG);
if (hr == NOERROR) {
hr = pFG->Connect(pPreviewPin, pCon);
pFG->Release();
}
pCon->Release();
if (hr != NOERROR)
DbgLog((LOG_ERROR,1,TEXT("*** RECONNECT FAILED! ***")));
// !!! We need to notify application that graph is different
}
}
}




//=============================================================================

//
// IAMStreamConfig stuff
//


// Tell the capture card to capture a specific format. If it isn't connected,
// then it will use that format to connect when it does. If already connected,
// then it will reconnect with the new format.
//
HRESULT CVidStream::SetFormat(AM_MEDIA_TYPE *pmt)
{
HRESULT hr;

if (pmt == NULL)
return E_POINTER;

// To make sure we're not in the middle of start/stop streaming
CAutoLock lock(m_pFilter->pStateLock());
CAutoLock l(&m_cSharedState);

DbgLog((LOG_TRACE,2,TEXT("IAMStreamConfig::SetFormat %x %dbit %dx%d"),
HEADER(pmt->pbFormat)->biCompression,
HEADER(pmt->pbFormat)->biBitCount,
HEADER(pmt->pbFormat)->biWidth,
HEADER(pmt->pbFormat)->biHeight));

if (((CVidCap *)m_pFilter)->m_State != State_Stopped)
return E_UNEXPECTED;

// If this is the same format as we already are using, don't bother
CMediaType mt;
GetMediaType(&mt);
if (mt == *pmt) {
return NOERROR;
}

// see if we like this type
if ((hr = CheckMediaType((CMediaType *)pmt)) != NOERROR) {
DbgLog((LOG_TRACE,2,TEXT("SetFormat rejected")));
return hr;
}

// If we are connected to somebody, make sure they like it
// !!! A video renderer might reject going to 16 bit from 8 bit, but
// reconnecting would still work, by pulling in a colour converter
if (IsConnected()) {
hr = GetConnected()->QueryAccept(pmt);
if (hr != NOERROR) {
DbgLog((LOG_TRACE,2,TEXT("SetFormat rejected by the other pin")));
return E_INVALIDARG;
}
}

// OK, we're using it
hr = SetMediaType((CMediaType *)pmt);

// Changing the format means reconnecting if necessary
if (hr == NOERROR) {
m_fSetFormatCalled = TRUE;// from now on, this is the format
// we must use
Reconnect(TRUE);
}

return hr;
}


// What format is the capture card capturing right now?
// The caller must free it with DeleteMediaType(*ppmt)
//
HRESULT CVidStream::GetFormat(AM_MEDIA_TYPE **ppmt)
{
DbgLog((LOG_TRACE,3,TEXT("IAMStreamConfig::GetFormat")));

if (ppmt == NULL)
return E_POINTER;

*ppmt = (AM_MEDIA_TYPE *)CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE));
if (*ppmt == NULL)
return E_OUTOFMEMORY;
ZeroMemory(*ppmt, sizeof(AM_MEDIA_TYPE));
HRESULT hr = GetMediaType((CMediaType *)*ppmt);
if (hr != NOERROR) {
CoTaskMemFree(*ppmt);
*ppmt = NULL;
return hr;
}
return NOERROR;
}


//
//
HRESULT CVidStream::GetNumberOfCapabilities(int *piCount, int *piSize)
{
DbgLog((LOG_TRACE,3,TEXT("IAMStreamConfig::GetNumberOfCapabilities")));

if (piCount == NULL || piSize == NULL)
return E_POINTER;

*piCount = 0;
*piSize = 0;

return NOERROR;
}


// find out some capabilities of this capture device
//
HRESULT CVidStream::GetStreamCaps(int i, AM_MEDIA_TYPE **ppmt, LPBYTE pSCC)
{
DbgLog((LOG_TRACE,3,TEXT("IAMStreamConfig::GetStreamCaps")));

// sorry, I have no clue what to say
return E_NOTIMPL;
}


//=============================================================================

// IAMVideoCompression stuff

// Get some information about the driver
//
HRESULT CVidStream::GetInfo(LPWSTR pszVersion, int *pcbVersion, LPWSTR pszDescription, int *pcbDescription, long *pDefaultKeyFrameRate, long *pDefaultPFramesPerKey, double *pDefaultQuality, long *pCapabilities)
{
DbgLog((LOG_TRACE,3,TEXT("IAMVideoCompression::GetInfo")));

// we can't do anything programmatically
if (pCapabilities)
*pCapabilities = 0;
if (pDefaultKeyFrameRate)
*pDefaultKeyFrameRate = 0;
if (pDefaultPFramesPerKey)
*pDefaultPFramesPerKey = 0;
if (pDefaultQuality)
*pDefaultQuality = 0;

// we can give them a driver name and version
if (pszVersion && pcbVersion)
lstrcpynW(pszVersion, m_szVersion, *pcbVersion / 2);
if (pszDescription && pcbDescription)
lstrcpynW(pszDescription, m_szName, *pcbDescription / 2);

// return the number of bytes this unicode string is, incl. NULL char
if (pcbVersion)
*pcbVersion = lstrlenW(m_szVersion) * 2 + 2;
if (pcbDescription)
*pcbDescription = lstrlenW(m_szName) * 2 + 2;

return NOERROR;
}


//=============================================================================

/* IAMDroppedFrames stuff */

// How many frames did we drop?
//
HRESULT CVidStream::GetNumDropped(long *plDropped)
{
DbgLog((LOG_TRACE,5,TEXT("IAMDroppedFrames::GetNumDropped - %d dropped"),
(int)m_uiFramesSkipped));

if (plDropped == NULL)
return E_POINTER;

*plDropped = (long)m_uiFramesSkipped;
return NOERROR;
}


// How many frames did we not drop?
//
HRESULT CVidStream::GetNumNotDropped(long *plNotDropped)
{
DbgLog((LOG_TRACE,5,TEXT("IAMDroppedFrames::GetNumNotDropped - %d not dropped"),
(int)m_uiFramesDelivered));

if (plNotDropped == NULL)
return E_POINTER;

*plNotDropped = (long)m_uiFramesDelivered;
return NOERROR;
}


// Which frames did we drop (give me up to lSize of them - we got lNumCopied)
//
HRESULT CVidStream::GetDroppedInfo(long lSize, long *plArray, long *plNumCopied)
{
DbgLog((LOG_TRACE,5,TEXT("IAMDroppedFrames::GetDroppedInfo")));

return E_NOTIMPL;// !!! Do this someday?

#if 0
if (lSize <= 0)
return E_INVALIDARG;
if (plArray == NULL || plNumCopied == NULL)
return E_POINTER;

*plNumCopied = min(lSize, NUM_DROPPED);
*plNumCopied = (long)min(*plNumCopied, m_capstats.dwlNumDropped);

LONG l;
for (l = 0; l < *plNumCopied; l++) {
plArray[l] = (long)m_capstats.dwlDropped[l];
}

return NOERROR;
#endif
}


HRESULT CVidStream::GetAverageFrameSize(long *plAverageSize)
{
DbgLog((LOG_TRACE,5,TEXT("IAMDroppedFrames::GetAverageFrameSize - %d"),
m_uiFramesDelivered ?
(long)(m_llTotalFrameSize / m_uiFramesDelivered)
: 0));

if (plAverageSize == NULL)
return E_POINTER;

*plAverageSize = m_uiFramesDelivered ?
(long)(m_llTotalFrameSize / m_uiFramesDelivered)
: 0;

return NOERROR;
}


///////////////////////////////
// IAMBufferNegotiation methods
///////////////////////////////

// Somebody wants us to use allocator properties like these when we
// connect
//
HRESULT CVidStream::SuggestAllocatorProperties(const ALLOCATOR_PROPERTIES *pprop)
{
DbgLog((LOG_TRACE,2,TEXT("SuggestAllocatorProperties")));

// to make sure we're not in the middle of connecting
CAutoLock lock(m_pFilter->pStateLock());
CAutoLock l(&m_cSharedState);

// sorry, too late, we've made up our mind already
if (IsConnected())
return E_UNEXPECTED;

m_propSuggested = *pprop;

DbgLog((LOG_TRACE,2,TEXT("cBuffers-%d cbBuffer-%d cbAlign-%d cbPrefix-%d"),
pprop->cBuffers, pprop->cbBuffer, pprop->cbAlign, pprop->cbPrefix));

return NOERROR;
}


// what properties is the allocator using right now?
//
HRESULT CVidStream::GetAllocatorProperties(ALLOCATOR_PROPERTIES *pprop)
{
DbgLog((LOG_TRACE,2,TEXT("GetAllocatorProperties")));

// to make sure we're not in the middle of connecting
CAutoLock lock(m_pFilter->pStateLock());
CAutoLock l(&m_cSharedState);

// we are not connected... we have no allocator!
if (!IsConnected())
return E_UNEXPECTED;

if (pprop == NULL)
return E_POINTER;

// tell them what they've won, Johnny
*pprop = m_propActual;

return NOERROR;
}


//
// PIN CATEGORIES - let the world know that we are a CAPTURE pin
//

HRESULT CVidStream::Set(REFGUID guidPropSet, DWORD dwPropID, LPVOID pInstanceData, DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData)
{
return E_NOTIMPL;
}

// To get a property, the caller allocates a buffer which the called
// function fills in. To determine necessary buffer size, call Get with
// pPropData=NULL and cbPropData=0.
HRESULT CVidStream::Get(REFGUID guidPropSet, DWORD dwPropID, LPVOID pInstanceData, DWORD cbInstanceData, LPVOID pPropData, DWORD cbPropData, DWORD *pcbReturned)
{
if (guidPropSet != AMPROPSETID_Pin)
return E_PROP_SET_UNSUPPORTED;

if (dwPropID != AMPROPERTY_PIN_CATEGORY)
return E_PROP_ID_UNSUPPORTED;

if (pPropData == NULL && pcbReturned == NULL)
return E_POINTER;

if (pcbReturned)
*pcbReturned = sizeof(GUID);

if (pPropData == NULL)
return S_OK;

if (cbPropData < sizeof(GUID))
return E_UNEXPECTED;

*(GUID *)pPropData = PIN_CATEGORY_CAPTURE;
return S_OK;
}


// QuerySupported must either return E_NOTIMPL or correctly indicate
// if getting or setting the property set and property is supported.
// S_OK indicates the property set and property ID combination is
HRESULT CVidStream::QuerySupported(REFGUID guidPropSet, DWORD dwPropID, DWORD *pTypeSupport)
{
if (guidPropSet != AMPROPSETID_Pin)
return E_PROP_SET_UNSUPPORTED;

if (dwPropID != AMPROPERTY_PIN_CATEGORY)
return E_PROP_ID_UNSUPPORTED;

if (pTypeSupport)
*pTypeSupport = KSPROPERTY_SUPPORT_GET;
return S_OK;
}