//*****************************************************************************************
// File: WebCamLib.cpp
// Project: WebcamLib
// Author(s): John Conwell
// Gary Caldwell
//
// Defines the webcam DirectShow wrapper used by TouchlessLib
//*****************************************************************************************
#include <dshow.h>
#include <strsafe.h>
#define __IDxtCompositor_INTERFACE_DEFINED__
#define __IDxtAlphaSetter_INTERFACE_DEFINED__
#define __IDxtJpeg_INTERFACE_DEFINED__
#define __IDxtKey_INTERFACE_DEFINED__
#pragma include_alias( "dxtrans.h", "qedit.h" )
#include <qedit.h>
#include "WebCamLib.h"
using namespace System;
using namespace System::Reflection;
using namespace WebCamLib;
// Private variables
#define MAX_CAMERAS 10
// Structure to hold camera information
struct CameraInfoStruct
{
BSTR bstrName;
IMoniker* pMoniker;
};
// Private global variables
IGraphBuilder* g_pGraphBuilder = NULL;
IMediaControl* g_pMediaControl = NULL;
ICaptureGraphBuilder2* g_pCaptureGraphBuilder = NULL;
IBaseFilter* g_pIBaseFilterCam = NULL;
IBaseFilter* g_pIBaseFilterSampleGrabber = NULL;
IBaseFilter* g_pIBaseFilterNullRenderer = NULL;
CameraInfoStruct g_aCameraInfo[MAX_CAMERAS] = {0};
/// <summary>
/// Initializes information about all web cams connected to machine
/// </summary>
CameraMethods::CameraMethods()
{
// Set to not disposed
this->disposed = false;
// Get and cache camera info
RefreshCameraList();
}
/// <summary>
/// IDispose
/// </summary>
CameraMethods::~CameraMethods()
{
Cleanup();
disposed = true;
}
/// <summary>
/// Finalizer
/// </summary>
CameraMethods::!CameraMethods()
{
if (!disposed)
{
Cleanup();
}
}
/// <summary>
/// Initialize information about webcams installed on machine
/// </summary>
void CameraMethods::RefreshCameraList()
{
IEnumMoniker* pclassEnum = NULL;
ICreateDevEnum* pdevEnum = NULL;
int count = 0;
CleanupCameraInfo();
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum,
NULL,
CLSCTX_INPROC,
IID_ICreateDevEnum,
(LPVOID*)&pdevEnum);
if (SUCCEEDED(hr))
{
hr = pdevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pclassEnum, 0);
}
if (pdevEnum != NULL)
{
pdevEnum->Release();
pdevEnum = NULL;
}
if (pclassEnum != NULL)
{
IMoniker* apIMoniker[1];
ULONG ulCount = 0;
while (SUCCEEDED(hr) && (count) < MAX_CAMERAS && pclassEnum->Next(1, apIMoniker, &ulCount) == S_OK)
{
g_aCameraInfo[count].pMoniker = apIMoniker[0];
g_aCameraInfo[count].pMoniker->AddRef();
IPropertyBag *pPropBag;
hr = apIMoniker[0]->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag);
if (SUCCEEDED(hr))
{
// Retrieve the filter's friendly name
VARIANT varName;
VariantInit(&varName);
hr = pPropBag->Read(L"FriendlyName", &varName, 0);
if (SUCCEEDED(hr) && varName.vt == VT_BSTR)
{
g_aCameraInfo[count].bstrName = SysAllocString(varName.bstrVal);
}
VariantClear(&varName);
pPropBag->Release();
}
count++;
}
pclassEnum->Release();
}
this->Count = count;
if (!SUCCEEDED(hr))
throw gcnew COMException("Error Refreshing Camera List", hr);
}
/// <summary>
/// Retrieve information about a specific camera
/// Use the count property to determine valid indicies to pass in
/// </summary>
CameraInfo^ CameraMethods::GetCameraInfo(int camIndex)
{
if (camIndex >= Count)
throw gcnew ArgumentException("Camera index is out of bounds: " + Count.ToString());
if (g_aCameraInfo[camIndex].pMoniker == NULL)
throw gcnew ArgumentException("There is no camera at index: " + camIndex.ToString());
CameraInfo^ camInfo = gcnew CameraInfo();
camInfo->index = camIndex;
camInfo->name = Marshal::PtrToStringBSTR((IntPtr)g_aCameraInfo[camIndex].bstrName);
return camInfo;
}
/// <summary>
/// Start the camera associated with the input handle
/// </summary>
void CameraMethods::StartCamera(int camIndex, interior_ptr<int> width, interior_ptr<int> height)
{
if (camIndex >= Count)
throw gcnew ArgumentException("Camera index is out of bounds: " + Count.ToString());
if (g_aCameraInfo[camIndex].pMoniker == NULL)
throw gcnew ArgumentException("There is no camera at index: " + camIndex.ToString());
if (g_pGraphBuilder != NULL)
throw gcnew ArgumentException("Graph Builder was null");
// Setup up function callback -- through evil reflection on private members
Type^ baseType = this->GetType();
FieldInfo^ field = baseType->GetField("<backing_store>OnImageCapture", BindingFlags::NonPublic | BindingFlags::Instance | BindingFlags::IgnoreCase);
if (field != nullptr)
{
Object^ obj = field->GetValue(this);
if (obj != nullptr)
{
CameraMethods::CaptureCallbackDelegate^ del = (CameraMethods::CaptureCallbackDelegate^)field->GetValue(this);
if (del != nullptr)
{
ppCaptureCallback = GCHandle::Alloc(del);
g_pfnCaptureCallback =
static_cast<PFN_CaptureCallback>(Marshal::GetFunctionPointerForDelegate(del).ToPointer());
}
}
}
IMoniker *pMoniker = g_aCameraInfo[camIndex].pMoniker;
pMoniker->AddRef();
HRESULT hr = S_OK;
// Build all the necessary interfaces to start the capture
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(CLSID_FilterGraph,
NULL,
CLSCTX_INPROC,
IID_IGraphBuilder,
(LPVOID*)&g_pGraphBuilder);
}
if (SUCCEEDED(hr))
{
hr = g_pGraphBuilder->QueryInterface(IID_IMediaControl, (LPVOID*)&g_pMediaControl);
}
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(CLSID_CaptureGraphBuilder2,
NULL,
CLSCTX_INPROC,
IID_ICaptureGraphBuilder2,
(LPVOID*)&g_pCaptureGraphBuilder);
}
// Setup the filter graph
if (SUCCEEDED(hr))
{
hr = g_pCaptureGraphBuilder->SetFiltergraph(g_pGraphBuilder);
}
// Build the camera from the moniker
if (SUCCEEDED(hr))
{
hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (LPVOID*)&g_pIBaseFilterCam);
}
// Add the camera to the filter graph
if (SUCCEEDED(hr))
{
hr = g_pGraphBuilder->AddFilter(g_pIBaseFilterCam, L"WebCam");
}
// Create a SampleGrabber
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&g_pIBaseFilterSampleGrabber);
}
// Configure the Sample Grabber
if (SUCCEEDED(hr))
{
hr = ConfigureSampleGrabber(g_pIBaseFilterSampleGrabber);
}
// Add Sample Grabber to the filter graph
if (SUCCEEDED(hr))
{
hr = g_pGraphBuilder->AddFilter(g_pIBaseFilterSampleGrabber, L"SampleGrabber");
}
// Create the NullRender
if (SUCCEEDED(hr))
{
hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&g_pIBaseFilterNullRenderer);
}
// Add the Null Render to the filter graph
if (SUCCEEDED(hr))
{
hr = g_pGraphBuilder->AddFilter(g_pIBaseFilterNullRenderer, L"NullRenderer");
}
// Configure the render stream
if (SUCCEEDED(hr))
{
hr = g_pCaptureGraphBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, g_pIBaseFilterCam, g_pIBaseFilterSampleGrabber, g_pIBaseFilterNullRenderer);
}
// Grab the capture width and height
if (SUCCEEDED(hr))
{
ISampleGrabber* pGrabber = NULL;
hr = g_pIBaseFilterSampleGrabber->QueryInterface(IID_ISampleGrabber, (LPVOID*)&pGrabber);
if (SUCCEEDED(hr))
{
AM_MEDIA_TYPE mt;
hr = pGrabber->GetConnectedMediaType(&mt);
if (SUCCEEDED(hr))
{
VIDEOINFOHEADER *pVih;
if ((mt.formattype == FORMAT_VideoInfo) &&
(mt.cbFormat >= sizeof(VIDEOINFOHEADER)) &&
(mt.pbFormat != NULL) )
{
pVih = (VIDEOINFOHEADER*)mt.pbFormat;
*width = pVih->bmiHeader.biWidth;
*height = pVih->bmiHeader.biHeight;
}
else
{
hr = E_FAIL; // Wrong format
}
// FreeMediaType(mt); (from MSDN)
if (mt.cbFormat != 0)
{
CoTaskMemFree((PVOID)mt.pbFormat);
mt.cbFormat = 0;
mt.pbFormat = NULL;
}
if (mt.pUnk != NULL)
{
// Unecessary because pUnk should not be used, but safest.
mt.pUnk->Release();
mt.pUnk = NULL;
}
}
}
if (pGrabber != NULL)
{
pGrabber->Release();
pGrabber = NULL;
}
}
// Start the capture
if (SUCCEEDED(hr))
{
hr = g_pMediaControl->Run();
}
// If init fails then ensure that you cleanup
if (FAILED(hr))
{
StopCamera();
}
else
{
hr = S_OK; // Make sure we return S_OK for success
}
// Cleanup
if (pMoniker != NULL)
{
pMoniker->Release();
pMoniker = NULL;
}
if (SUCCEEDED(hr))
this->activeCameraIndex = camIndex;
else
throw gcnew COMException("Error Starting Camera", hr);
}
/// <summary>
/// Closes any open webcam and releases all unmanaged resources
/// </summary>
void CameraMethods::Cleanup()
{
StopCamera();
CleanupCameraInfo();
// Clean up pinned pointer to callback delegate
if (ppCaptureCallback.IsAllocated)
{
ppCaptureCallback.Free();
}
}
/// <summary>
/// Stops the current open webcam
/// </summary>
void CameraMethods::StopCamera()
{
if (g_pMediaControl != NULL)
{
g_pMediaControl->Stop();
g_pMediaControl->Release();
g_pMediaControl = NULL;
}
g_pfnCaptureCallback = NULL;
if (g_pIBaseFilterNullRenderer != NULL)
{
g_pIBaseFilterNullRenderer->Release();
g_pIBaseFilterNullRenderer = NULL;
}
if (g_pIBaseFilterSampleGrabber != NULL)
{
g_pIBaseFilterSampleGrabber->Release();
g_pIBaseFilterSampleGrabber = NULL;
}
if (g_pIBaseFilterCam != NULL)
{
g_pIBaseFilterCam->Release();
g_pIBaseFilterCam = NULL;
}
if (g_pGraphBuilder != NULL)
{
g_pGraphBuilder->Release();
g_pGraphBuilder = NULL;
}
if (g_pCaptureGraphBuilder != NULL)
{
g_pCaptureGraphBuilder->Release();
g_pCaptureGraphBuilder = NULL;
}
this->activeCameraIndex = -1;
}
/// <summary>
/// Show the properties dialog for the specified webcam
/// </summary>
void CameraMethods::DisplayCameraPropertiesDialog(int camIndex)
{
if (camIndex >= Count)
throw gcnew ArgumentException("Camera index is out of bounds: " + Count.ToString());
if (g_aCameraInfo[camIndex].pMoniker == NULL)
throw gcnew ArgumentException("There is no camera at index: " + camIndex.ToString());
HRESULT hr = S_OK;
IBaseFilter *pFilter = NULL;
ISpecifyPropertyPages *pProp = NULL;
IMoniker *pMoniker = g_aCameraInfo[camIndex].pMoniker;
pMoniker->AddRef();
// Create a filter graph for the moniker
if (SUCCEEDED(hr))
{
hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (LPVOID*)&pFilter);
}
// See if it implements a property page
if (SUCCEEDED(hr))
{
hr = pFilter->QueryInterface(IID_ISpecifyPropertyPages, (LPVOID*)&pProp);
}
// Show the property page
if (SUCCEEDED(hr))
{
FILTER_INFO filterinfo;
hr = pFilter->QueryFilterInfo(&filterinfo);
IUnknown *pFilterUnk = NULL;
if (SUCCEEDED(hr))
{
hr = pFilter->QueryInterface(IID_IUnknown, (LPVOID*)&pFilterUnk);
}
if (SUCCEEDED(hr))
{
CAUUID caGUID;
pProp->GetPages(&caGUID);
OleCreatePropertyFrame(
NULL, // Parent window
0, 0, // Reserved
filterinfo.achName, // Caption for the dialog box
1, // Number of objects (just the filter)
&pFilterUnk, // Array of object pointers.
caGUID.cElems, // Number of property pages
caGUID.pElems, // Array of property page CLSIDs
0, // Locale identifier
0, NULL // Reserved
);
}
if (pFilterUnk != NULL)
{
pFilterUnk->Release();
pFilterUnk = NULL;
}
}
if (pProp != NULL)
{
pProp->Release();
pProp = NULL;
}
if (pMoniker != NULL)
{
pMoniker->Release();
pMoniker = NULL;
}
if (pFilter != NULL)
{
pFilter->Release();
pFilter = NULL;
}
if (!SUCCEEDED(hr))
throw gcnew COMException("Error displaying camera properties dialog", hr);
}
/// <summary>
/// Releases all unmanaged resources
/// </summary>
void CameraMethods::CleanupCameraInfo()
{
for (int n = 0; n < MAX_CAMERAS; n++)
{
SysFreeString(g_aCameraInfo[n].bstrName);
g_aCameraInfo[n].bstrName = NULL;
if (g_aCameraInfo[n].pMoniker != NULL)
{
g_aCameraInfo[n].pMoniker->Release();
g_aCameraInfo[n].pMoniker = NULL;
}
}
}
/// <summary>
/// Setup the callback functionality for DirectShow
/// </summary>
HRESULT CameraMethods::ConfigureSampleGrabber(IBaseFilter *pIBaseFilter)
{
HRESULT hr = S_OK;
ISampleGrabber *pGrabber = NULL;
hr = pIBaseFilter->QueryInterface(IID_ISampleGrabber, (void**)&pGrabber);
if (SUCCEEDED(hr))
{
AM_MEDIA_TYPE mt;
ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE));
mt.majortype = MEDIATYPE_Video;
mt.subtype = MEDIASUBTYPE_RGB24;
mt.formattype = FORMAT_VideoInfo;
hr = pGrabber->SetMediaType(&mt);
}
if (SUCCEEDED(hr))
{
hr = pGrabber->SetCallback(new SampleGrabberCB(), 1);
}
if (pGrabber != NULL)
{
pGrabber->Release();
pGrabber = NULL;
}
return hr;
}