diff options
Diffstat (limited to 'src/im_capture_dx.cpp')
-rw-r--r-- | src/im_capture_dx.cpp | 2255 |
1 files changed, 2255 insertions, 0 deletions
diff --git a/src/im_capture_dx.cpp b/src/im_capture_dx.cpp new file mode 100644 index 0000000..cb4749b --- /dev/null +++ b/src/im_capture_dx.cpp @@ -0,0 +1,2255 @@ +/** \file + * \brief Video Capture Using Direct Show 9 + * + * See Copyright Notice in im.h + * $Id: im_capture_dx.cpp,v 1.1 2008/10/17 06:10:16 scuri Exp $ + */ + +/* + The Direct Show Graph is composed by 3 components: + capture source, sample grabber and null renderer. + + Filters are connected: + capture_filter(out)->(int)grabber_filter(out)->(in)null_filter + + But when the graph is rendered other transform filters + can be inserted to connect the capture and the grabber. + + We do not use MFC, ATL and the Direct Show Base Classes. + This module only needs the library "strmiids.lib". + If the extra error functions were used, you will need to link with "quartz.lib" or "dxerr9.lib". + + We use the buffer of the ISampleGrabber. But this can not be done in a user callback, + so we leave the grab loop for the application, it can also be done in the idle function. + + If you use the idle function for the grab loop, then WDM Source Dialog will interrupt the "live" mode. + Just because it is a modal dialog and it does not use the application message loop. + It can be solved if the grab loop is implemented using a timer. + + Since there is no gray format, bpp is always 24bpp. +*/ + +#define _WIN32_WINNT 0x0500 // Because of TryEnterCriticalSection + +#include <dshow.h> +#include <qedit.h> + +#include <memory.h> +#include <stdio.h> +#include <assert.h> + +#include <im.h> +#include <im_util.h> + +#include "im_capture.h" + +#define VC_CAMERADELAY 200 // This vary from camera to camera, so we use a reasonable value and hope it will work for all. +#define VC_MAXVIDDEVICES 30 // Maximum number of devices to list + +//#define VC_REGISTER_FILTERGRAPH // Use this to allow GraphEdit to spy the graph +//#define VC_INCLUDE_VFW_DEVICES // Use this to allow old video for windows devices +//#define VC_PRINT_ERROR_MESSAGES // Use this to display a system custom error message + +#if defined(_DEBUG) | defined(DEBUG) +#define VC_REGISTER_FILTERGRAPH +#define VC_PRINT_ERROR_MESSAGES +#endif + + +/************************************************************************** + imTrackingGrabberCB +***************************************************************************/ + +// This is better than using the sample grabber internal buffer +// because we have a more precise control of the data flow. + +class imTrackingGrabberCB: public ISampleGrabberCB +{ +public: + imTrackingGrabberCB(); + ~imTrackingGrabberCB(); + + STDMETHODIMP SampleCB(double SampleTime, IMediaSample *pSample); + STDMETHODIMP BufferCB(double SampleTime, BYTE *pBuffer, long BufferLen) {return E_NOTIMPL;} + STDMETHODIMP_(ULONG) AddRef() {return 2;} + STDMETHODIMP_(ULONG) Release() {return 1;} + STDMETHODIMP QueryInterface(REFIID riid, void ** ppv); + + void SetImageSize(int width, int height); + int GetImage(unsigned char* data, int color_mode, int timeout); + +protected: + int m_Width, m_Height; + bool m_newImageFlag; + unsigned char *m_ImageData; + CRITICAL_SECTION m_sect; + HANDLE m_imageReady; +}; + +imTrackingGrabberCB::imTrackingGrabberCB() +{ + InitializeCriticalSection(&m_sect); + m_newImageFlag = 0; + m_ImageData = NULL; + m_imageReady = CreateEvent(NULL, FALSE, TRUE, NULL); +} + +imTrackingGrabberCB::~imTrackingGrabberCB() +{ + CloseHandle(m_imageReady); + EnterCriticalSection(&m_sect); + DeleteCriticalSection(&m_sect); + if (m_ImageData) delete m_ImageData; +} + +STDMETHODIMP imTrackingGrabberCB::QueryInterface(REFIID riid, void ** ppv) +{ + if( riid == IID_ISampleGrabberCB || riid == IID_IUnknown ) { + *ppv = (void *) static_cast<ISampleGrabberCB*> (this); + return NOERROR; + } + return E_NOINTERFACE; +} + +void imTrackingGrabberCB::SetImageSize(int width, int height) +{ + EnterCriticalSection(&m_sect); + + // This can be done because the capture system always returns + // images that are a multiple of 4. + int new_size = width * height * 3; + + if (!m_ImageData) + { + m_ImageData = (BYTE*)calloc(new_size, 1); + m_Width = width; + m_Height = height; + } + + if (m_Width*m_Height < new_size) + m_ImageData = (BYTE*)realloc(m_ImageData, new_size); + + m_Width = width; + m_Height = height; + + LeaveCriticalSection(&m_sect); +} + +STDMETHODIMP imTrackingGrabberCB::SampleCB(double, IMediaSample *pSample) +{ + if (!m_ImageData) return S_OK; + + EnterCriticalSection(&m_sect); + + int size = pSample->GetSize(); + if (size > m_Width*m_Height*3) + { + LeaveCriticalSection(&m_sect); + return S_OK; + } + + BYTE *pData; + pSample->GetPointer(&pData); + CopyMemory(m_ImageData, pData, size); + m_newImageFlag = 1; + + LeaveCriticalSection(&m_sect); + + SetEvent(m_imageReady); + + return S_OK; +} + +int imTrackingGrabberCB::GetImage(unsigned char* data, int color_mode, int timeout) +{ + if (timeout != 0) + { + DWORD ret = WaitForSingleObject(m_imageReady, timeout); + if (ret != WAIT_OBJECT_0) + return 0; + } + + if (!TryEnterCriticalSection(&m_sect)) + return 0; + + if (m_newImageFlag == 1) + { + int count = m_Width*m_Height; + unsigned char* src_data = m_ImageData; + + if (imColorModeSpace(color_mode) == IM_RGB) + { + if (imColorModeIsPacked(color_mode)) + { + unsigned char* dst_data = data; + for (int i = 0; i < count; i++) + { + *(dst_data+2) = *src_data++; + *(dst_data+1) = *src_data++; + *dst_data = *src_data++; + dst_data += 3; + } + } + else + { + unsigned char* red = data; + unsigned char* green = data + count; + unsigned char* blue = data + 2*count; + for (int i = 0; i < count; i++) + { + *blue++ = *src_data++; + *green++ = *src_data++; + *red++ = *src_data++; + } + } + } + else + { + unsigned char* map = data; + for (int i = 0; i < count; i++) + { + *map++ = *src_data; + src_data += 3; + } + } + + m_newImageFlag = 0; + + LeaveCriticalSection(&m_sect); + return 1; + } + + LeaveCriticalSection(&m_sect); + return 0; +} + + +/************************************************************************** + Direct Show Only +***************************************************************************/ + + +struct vcDevice +{ + IBaseFilter *filter; + char vendorinfo[128]; + char desc[128]; + char ex_desc[256]; + char path[512]; +}; +static vcDevice vc_DeviceList[VC_MAXVIDDEVICES]; +static int vc_DeviceCount = 0; + +static void vc_AddDevice(IBaseFilter *filter, char* desc, char* ex_desc, char* path, char* vendorinfo) +{ + int i = vc_DeviceCount; + vcDevice* device = &vc_DeviceList[i]; + + memset(device, 0, sizeof(vcDevice)); + + device->filter = filter; + + if (!desc) desc = "device"; + sprintf(device->desc, "%d - %s", i, desc); + + if (ex_desc) strcpy(device->ex_desc, ex_desc); + if (path) strcpy(device->path, path); + if (vendorinfo) strcpy(device->vendorinfo, vendorinfo); + + vc_DeviceCount++; +} + + +#ifdef VC_PRINT_ERROR_MESSAGES +//#include <dxerr9.h> + +static int vc_ShowError(HRESULT hr) +{ + if (FAILED(hr)) + { + TCHAR szErr[MAX_ERROR_TEXT_LEN]; + DWORD res = AMGetErrorText(hr, szErr, MAX_ERROR_TEXT_LEN); // Must link with quartz.lib + if (res == 0) wsprintf(szErr, "Unknown Error: 0x%2x", hr); + MessageBox(0, szErr, "imCapture Error!", MB_OK | MB_ICONERROR); +// MessageBox(NULL, DXGetErrorDescription9(hr), DXGetErrorString9(hr), MB_OK | MB_ICONERROR); + return 1; + } + + return 0; +} + +#define VC_HARDFAILED(_x) vc_ShowError(_x) +#else +#define VC_HARDFAILED FAILED +#endif + +static char* vc_Wide2Char(WCHAR* wstr) +{ + if (wstr) + { + int n = wcslen(wstr)+1; + char* str = (char*)malloc(n); + WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, n, NULL, NULL); + return str; + } + + return NULL; +} + +#ifdef VC_INCLUDE_VFW_DEVICES +#define VC_CATEGORY_FLAG 0 +#else +#define VC_CATEGORY_FLAG CDEF_DEVMON_FILTER|CDEF_DEVMON_PNP_DEVICE +#endif + +static char* vc_GetDeviceProp(IPropertyBag *pPropBag, const WCHAR* PropName) +{ + VARIANT varProp; + VariantInit(&varProp); + HRESULT hr = pPropBag->Read(PropName, &varProp, 0); + if (SUCCEEDED(hr)) + { + char* str = vc_Wide2Char(varProp.bstrVal); + VariantClear(&varProp); + return str; + } + return NULL; +} + +static void vc_EnumerateDevices(void) +{ + // Selecting a Capture Device + ICreateDevEnum *pDevEnum = NULL; + IEnumMoniker *pEnum = NULL; + + CoInitialize(NULL); + + // Create the System Device Enumerator. + HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, + CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, + reinterpret_cast<void**>(&pDevEnum)); + if (FAILED(hr)) return; + + // Create an enumerator for the video capture category. + hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, VC_CATEGORY_FLAG); + if (FAILED(hr) || !pEnum) + { + pDevEnum->Release(); + return; + } + + IMoniker *pMoniker = NULL; + while (pEnum->Next(1, &pMoniker, NULL) == S_OK) + { + IBaseFilter *capture_filter = NULL; + hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&capture_filter); + if (FAILED(hr)) + { + pMoniker->Release(); + continue; // Skip this one, maybe the next one will work. + } + + IPropertyBag *pPropBag; + hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void**)(&pPropBag)); + if (FAILED(hr)) + { + capture_filter->Release(); + pMoniker->Release(); + continue; // Skip this one, maybe the next one will work. + } + + char* desc = vc_GetDeviceProp(pPropBag, L"FriendlyName"); + char* ex_desc = vc_GetDeviceProp(pPropBag, L"Description"); + char* path = vc_GetDeviceProp(pPropBag, L"DevicePath"); + + char* vendorinfo = NULL; + LPWSTR VendorInfo; + if (capture_filter->QueryVendorInfo(&VendorInfo) == S_OK) + { + vendorinfo = vc_Wide2Char(VendorInfo); + CoTaskMemFree(VendorInfo); + } + + vc_AddDevice(capture_filter, desc, ex_desc, path, vendorinfo); + + if (desc) free(desc); + if (ex_desc) free(ex_desc); + if (path) free(path); + if (vendorinfo) free(vendorinfo); + + pPropBag->Release(); + pMoniker->Release(); + } + + pEnum->Release(); + pDevEnum->Release(); +} + +static IPin* vc_GetPin(IBaseFilter* pFilter, PIN_DIRECTION dir) +{ + IEnumPins* pEnumPins = NULL; + IPin* pPin = NULL; + + pFilter->EnumPins(&pEnumPins); + if(!pEnumPins) + return NULL; + + for(;;) + { + ULONG cFetched = 0; + PIN_DIRECTION pinDir = PIN_DIRECTION(-1); + pPin = 0; + + if (FAILED(pEnumPins->Next(1, &pPin, &cFetched))) + { + pEnumPins->Release(); + return NULL; + } + + if(cFetched == 1 && pPin != 0) + { + pPin->QueryDirection(&pinDir); + if(pinDir == dir) break; + pPin->Release(); + } + } + + pEnumPins->Release(); + return pPin; +} + +static void vc_NukeDownstream(IGraphBuilder* filter_builder, IBaseFilter *filter) +{ + IPin *pPin=0, *pPinTo=0; + IEnumPins *pEnumPins = NULL; + PIN_INFO pininfo; + + HRESULT hr = filter->EnumPins(&pEnumPins); + if (FAILED(hr)) return; + + pEnumPins->Reset(); + + while(hr == NOERROR) + { + hr = pEnumPins->Next(1, &pPin, NULL); + if(hr == S_OK && pPin) + { + pPin->ConnectedTo(&pPinTo); + if(pPinTo) + { + hr = pPinTo->QueryPinInfo(&pininfo); + if(hr == NOERROR) + { + if(pininfo.dir == PINDIR_INPUT) + { + vc_NukeDownstream(filter_builder, pininfo.pFilter); + filter_builder->Disconnect(pPinTo); + filter_builder->Disconnect(pPin); + filter_builder->RemoveFilter(pininfo.pFilter); + } + + pininfo.pFilter->Release(); + } + + pPinTo->Release(); + } + + pPin->Release(); + } + } + + pEnumPins->Release(); +} + +static int vc_DisconnectFilters(IGraphBuilder* filter_builder, IBaseFilter* source, IBaseFilter* destiny) +{ + IPin *pOut = vc_GetPin(source, PINDIR_OUTPUT); + IPin *pIn = vc_GetPin(destiny, PINDIR_INPUT); + HRESULT hr = filter_builder->Disconnect(pOut); + hr = filter_builder->Disconnect(pIn); + pOut->Release(); + pIn->Release(); + if (VC_HARDFAILED(hr)) return 0; + return 1; +} + +static int vc_DisconnectFilterPin(IGraphBuilder* filter_builder, IBaseFilter* filter, PIN_DIRECTION dir) +{ + IPin *pIn = vc_GetPin(filter, dir); + IPin *pOut; + pIn->ConnectedTo(&pOut); + + HRESULT hr = filter_builder->Disconnect(pIn); + pIn->Release(); + if (VC_HARDFAILED(hr)) + { + if (pOut) pOut->Release(); + return 0; + } + + if (pOut) + { + hr = filter_builder->Disconnect(pOut); + pOut->Release(); + + if (VC_HARDFAILED(hr)) + return 0; + } + + return 1; +} + +static int vc_ConnectFilters(IGraphBuilder* filter_builder, IBaseFilter* source, IBaseFilter* destiny, int direct) +{ + HRESULT hr; + IPin *pOut = vc_GetPin(source, PINDIR_OUTPUT); + IPin *pIn = vc_GetPin(destiny, PINDIR_INPUT); + if (direct) + hr = filter_builder->ConnectDirect(pOut, pIn, NULL); + else + hr = filter_builder->Connect(pOut, pIn); + pOut->Release(); + pIn->Release(); + if (VC_HARDFAILED(hr)) return 0; + return 1; +} + +static DWORD vc_AddGraphToRot(IUnknown *pUnkGraph) +{ + IMoniker * pMoniker; + IRunningObjectTable *pROT; + WCHAR wsz[128]; + HRESULT hr; + + if (FAILED(GetRunningObjectTable(0, &pROT))) + return 0; + + wsprintfW(wsz, L"FilterGraph %08x pid %08x\0", (DWORD_PTR)pUnkGraph, GetCurrentProcessId()); + + hr = CreateItemMoniker(L"!", wsz, &pMoniker); + if (SUCCEEDED(hr)) + { + DWORD dwRegister; + hr = pROT->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, pUnkGraph, pMoniker, &dwRegister); + pROT->Release(); + + pMoniker->Release(); + + if (SUCCEEDED(hr)) + return dwRegister; + } + + pROT->Release(); + return 0; +} + +static void vc_RemoveGraphFromRot(DWORD pdwRegister) +{ + IRunningObjectTable *pROT; + + if (SUCCEEDED(GetRunningObjectTable(0, &pROT))) + { + pROT->Revoke(pdwRegister); + pROT->Release(); + } +} + +/************************************************************************** + imVideoCapture +***************************************************************************/ + +typedef int (*vcDialogFunc)(imVideoCapture* vc, HWND parent); + +struct _imVideoCapture +{ + int registered_graph, + live, + device; /* current connected device. -1 if not connected. */ + + char* dialog_desc[6]; + vcDialogFunc dialog_func[6]; + int dialog_count; /* number of available configuration dialogs for the current connection. */ + + IGraphBuilder* filter_builder; /* The Filter Graph Manager */ + ICaptureGraphBuilder2* capture_graph_builder; /* Helps the Filter Graph Manager */ + IBaseFilter* capture_filter; /* the capture device (can vary), it's a source filter. */ + IBaseFilter* grabber_filter; /* returns the capture data, it's a transform filter */ + IBaseFilter* null_filter; /* does nothing, act as a terminator, it's a rendering filter */ + ISampleGrabber* sample_grabber; /* Used to access the ISampleGrabber interface, since grabber_filter is a generic IBaseFilter interface based on ISampleGrabber. */ + IMediaControl* media_control; /* Used to Run and Stop the graph flow. */ + IBaseFilter* overlay_renderer; /* Used when there is a video port without a preview */ + IBaseFilter *overlay_mixer; + + IAMVideoProcAmp* video_prop; /* Used to set/get video properties */ + IAMCameraControl* camera_prop; /* Used to set/get camera properties */ + IAMVideoControl* videoctrl_prop; /* Used to set/get video properties */ + + imTrackingGrabberCB* sample_callback; /* Used to intercept the samples. */ + + int format_count; /* number of supported formats */ + int format_current; /* current format */ + int format_map[50]; /* table to map returned formats to direct X formats */ +}; + +int imVideoCaptureDeviceCount(void) +{ + return vc_DeviceCount; +} + +int imVideoCaptureReloadDevices(void) +{ + for (int i = 0; i < vc_DeviceCount; i++) + { + vc_DeviceList[i].filter->Release(); + } + + vc_DeviceCount = 0; + vc_EnumerateDevices(); + return vc_DeviceCount; +} + +static int vc_CheckDeviceList(int device) +{ + // List available Devices once + if (vc_DeviceCount == 0) + { + vc_EnumerateDevices(); + + if (vc_DeviceCount == 0) + return 0; + } + + if (device < 0 || device >= vc_DeviceCount) + return 0; + + return 1; +} + +const char* imVideoCaptureDeviceDesc(int device) +{ + if (!vc_CheckDeviceList(device)) + return NULL; + + return vc_DeviceList[device].desc; +} + +const char* imVideoCaptureDeviceExDesc(int device) +{ + if (!vc_CheckDeviceList(device)) + return NULL; + + return vc_DeviceList[device].ex_desc; +} + +const char* imVideoCaptureDevicePath(int device) +{ + if (!vc_CheckDeviceList(device)) + return NULL; + + return vc_DeviceList[device].path; +} + +const char* imVideoCaptureDeviceVendorInfo(int device) +{ + if (!vc_CheckDeviceList(device)) + return NULL; + + return vc_DeviceList[device].vendorinfo; +} + +#define vc_SafeRelease(_p) { if( (_p) != 0 ) { (_p)->Release(); (_p)= NULL; } } + +static void vc_CheckVideoPort(imVideoCapture* vc) +{ +/* + If the video capture card supports the video port pin without a video preview pin this will not work. + The DirectShow architecture requires that the video port pin be connected to the Overlay Mixer Filter. + If this pin is not connected, data cannot be captured in DirectShow. +*/ + HRESULT hr; + + IPin *pPreviewPin = NULL; + hr = vc->capture_graph_builder->FindPin( + vc->capture_filter, // Pointer to the capture filter. + PINDIR_OUTPUT, // Look for an output pin. + &PIN_CATEGORY_PREVIEW, // Look for a preview pin. + NULL, // Any media type. + FALSE, // Pin can be connected. + 0, // Retrieve the first matching pin. + &pPreviewPin // Receives a pointer to the pin. + ); + if (hr == S_OK) + { + pPreviewPin->Release(); + return; + } + + IPin *pVideoPortPin = NULL; + hr = vc->capture_graph_builder->FindPin( + vc->capture_filter, // Pointer to the capture filter. + PINDIR_OUTPUT, // Look for an output pin. + &PIN_CATEGORY_VIDEOPORT, // Look for a video port pin. + NULL, // Any media type. + FALSE, // Pin can be connected. + 0, // Retrieve the first matching pin. + &pVideoPortPin // Receives a pointer to the pin. + ); + if (FAILED(hr)) return; + + // Create the overlay mixer. + CoCreateInstance(CLSID_OverlayMixer, NULL, CLSCTX_INPROC, + IID_IBaseFilter, (void **)&vc->overlay_mixer); + + // Add it to the filter graph. + vc->filter_builder->AddFilter(vc->overlay_mixer, L"Overlay Mixer"); + + IPin *pOverlayPin = NULL; + vc->capture_graph_builder->FindPin(vc->overlay_mixer, PINDIR_INPUT, NULL, NULL, TRUE, 0, &pOverlayPin); + + vc->filter_builder->Connect(pVideoPortPin, pOverlayPin); + if (FAILED(hr)) return; + + vc_SafeRelease(pVideoPortPin); + vc_SafeRelease(pOverlayPin); + + CoCreateInstance(CLSID_VideoRenderer, NULL, CLSCTX_INPROC_SERVER, + IID_IBaseFilter, reinterpret_cast<void**>(&vc->overlay_renderer)); + vc->filter_builder->AddFilter(vc->overlay_renderer, L"Overlay Renderer"); + + vc_ConnectFilters(vc->filter_builder, vc->overlay_mixer, vc->overlay_renderer, 1); + + IVideoWindow* pVideoWindow = NULL; + vc->overlay_renderer->QueryInterface(IID_IVideoWindow,(void**)&pVideoWindow); + pVideoWindow->put_AutoShow(OAFALSE); + pVideoWindow->Release(); +} + +static void vc_ReleaseMixer(imVideoCapture* vc) +{ + IPin *pOverlayPin = vc_GetPin(vc->overlay_mixer, PINDIR_INPUT); + IPin *pVideoPortPin = NULL; + pOverlayPin->ConnectedTo(&pVideoPortPin); + vc->filter_builder->Disconnect(pOverlayPin); + vc->filter_builder->Disconnect(pVideoPortPin); + vc_SafeRelease(pVideoPortPin); + vc_SafeRelease(pOverlayPin); + + vc_DisconnectFilters(vc->filter_builder, vc->overlay_mixer, vc->overlay_renderer); + + vc->filter_builder->RemoveFilter(vc->overlay_renderer); + vc->filter_builder->RemoveFilter(vc->overlay_mixer); + vc_SafeRelease(vc->overlay_renderer); + vc_SafeRelease(vc->overlay_mixer); +} + +static int vc_InitCaptureGraphBuilder(imVideoCapture* vc) +{ + HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER, + IID_ICaptureGraphBuilder2, reinterpret_cast<void**>(&vc->capture_graph_builder)); + if (FAILED(hr)) return 0; + + hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, + IID_IGraphBuilder, reinterpret_cast<void**>(&vc->filter_builder)); + if (FAILED(hr)) return 0; + + hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, + IID_IBaseFilter, reinterpret_cast<void**>(&vc->grabber_filter)); + if (FAILED(hr)) return 0; + + hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER, + IID_IBaseFilter, reinterpret_cast<void**>(&vc->null_filter)); + if (FAILED(hr)) return 0; + + // Initialize the Capture Graph Builder. + vc->capture_graph_builder->SetFiltergraph(vc->filter_builder); + + hr = vc->filter_builder->QueryInterface(IID_IMediaControl,(void**)&vc->media_control); + hr = vc->grabber_filter->QueryInterface(IID_ISampleGrabber, (void **)&vc->sample_grabber); + + AM_MEDIA_TYPE mt; + ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE)); + mt.majortype = MEDIATYPE_Video; + mt.subtype = MEDIASUBTYPE_RGB24; // Force 24 bpp + vc->sample_grabber->SetMediaType(&mt); + vc->sample_grabber->SetOneShot(FALSE); + vc->sample_grabber->SetBufferSamples(FALSE); + + vc->sample_callback = new imTrackingGrabberCB(); + + hr = vc->filter_builder->AddFilter(vc->grabber_filter, L"imSampleGrabber"); + hr = vc->filter_builder->AddFilter(vc->null_filter, L"imNullRenderer"); + + // Remove clock to speed up things + IMediaFilter* pMediaFilter = NULL; + vc->filter_builder->QueryInterface(IID_IMediaFilter, (void**)&pMediaFilter); + pMediaFilter->SetSyncSource(NULL); + pMediaFilter->Release(); + +#ifdef VC_REGISTER_FILTERGRAPH + vc->registered_graph = vc_AddGraphToRot(vc->filter_builder); +#endif + + return 1; +} + +imVideoCapture* imVideoCaptureCreate(void) +{ + imVideoCapture* vc = (imVideoCapture*)malloc(sizeof(imVideoCapture)); + memset(vc, 0, sizeof(imVideoCapture)); + + // List available Devices once + if (vc_DeviceCount == 0) + { + vc_EnumerateDevices(); + + if (vc_DeviceCount == 0) + { + free(vc); + return NULL; + } + } + + if (!vc_InitCaptureGraphBuilder(vc)) + { + vc_SafeRelease(vc->grabber_filter); + vc_SafeRelease(vc->filter_builder); + vc_SafeRelease(vc->capture_graph_builder); + vc_SafeRelease(vc->null_filter); + free(vc); + return NULL; + } + + vc->device = -1; + + return vc; +} + +static void vc_CaptureRemove(imVideoCapture* vc) +{ + vc->filter_builder->RemoveFilter(vc->capture_filter); + + vc->capture_filter = NULL; /* do not release here */ + vc_SafeRelease(vc->video_prop); + vc_SafeRelease(vc->camera_prop); + vc_SafeRelease(vc->videoctrl_prop); + + vc->dialog_count = 0; + vc->live = 0; + vc->device = -1; +} + +void imVideoCaptureDestroy(imVideoCapture* vc) +{ + assert(vc); + +#ifdef VC_REGISTER_FILTERGRAPH + if (vc->registered_graph) vc_RemoveGraphFromRot(vc->registered_graph); +#endif + + imVideoCaptureDisconnect(vc); + + delete vc->sample_callback; + + vc_SafeRelease(vc->overlay_mixer); + vc_SafeRelease(vc->overlay_renderer); + vc_SafeRelease(vc->media_control); + vc_SafeRelease(vc->sample_grabber); + + vc->null_filter->Release(); + vc->grabber_filter->Release(); + vc->filter_builder->Release(); + vc->capture_graph_builder->Release(); + + free(vc); +} + +static void vc_StopLive(imVideoCapture* vc) +{ + if (vc->live) // If it is live, stop it + { + vc->media_control->Stop(); + Sleep(VC_CAMERADELAY); + } +} + +static int vc_StartLive(imVideoCapture* vc) +{ + if (vc->live) // If it should be started, start it + { + HRESULT hr = vc->media_control->Run(); + if (VC_HARDFAILED(hr)) + { + vc->live = 0; + return 0; + } + + Sleep(VC_CAMERADELAY); + } + + return 1; +} + +int imVideoCaptureOneFrame(imVideoCapture* vc, unsigned char* data, int color_mode) +{ + assert(vc); + assert(vc->device != -1); + + vc_StopLive(vc); + vc->live = 0; + + vc->sample_grabber->SetOneShot(TRUE); + + vc->live = 1; + if (!vc_StartLive(vc)) + { + vc->sample_grabber->SetOneShot(FALSE); + return 0; + } + + int ret = imVideoCaptureFrame(vc, data, color_mode, -1); + + vc_StopLive(vc); + vc->live = 0; + + vc->sample_grabber->SetOneShot(FALSE); + + return ret; +} + +int imVideoCaptureFrame(imVideoCapture* vc, unsigned char* data, int color_mode, int timeout) +{ + assert(vc); + assert(vc->device != -1); + assert(vc->live); + return vc->sample_callback->GetImage(data, color_mode, timeout); +} + +static int vc_CaptureDisconnect(imVideoCapture* vc) +{ + vc->sample_grabber->SetCallback(NULL, 0); + + if (vc->overlay_mixer) + vc_ReleaseMixer(vc); + + vc_DisconnectFilters(vc->filter_builder, vc->grabber_filter, vc->null_filter); + + // Disconnect the grabber to preserve it + if (!vc_DisconnectFilterPin(vc->filter_builder, vc->grabber_filter, PINDIR_INPUT)) + return 0; + + // Remove everything downstream the capture filter, except the null renderer + vc_NukeDownstream(vc->filter_builder, vc->capture_filter); + + return 1; +} + +void imVideoCaptureDisconnect(imVideoCapture* vc) +{ + assert(vc); + + if (vc->device == -1) + return; + + vc_StopLive(vc); + vc->live = 0; + + vc_CaptureDisconnect(vc); + vc_CaptureRemove(vc); +} + +static void vc_UpdateSize(imVideoCapture* vc) +{ + int width, height; + imVideoCaptureGetImageSize(vc, &width, &height); + vc->sample_callback->SetImageSize(width, height); +} + +static void vc_UpdateDialogs(imVideoCapture* vc); +static void vc_UpdateFormatList(imVideoCapture* vc); + +static int vc_CaptureConnect(imVideoCapture* vc) +{ + vc_CheckVideoPort(vc); + + if (!vc_ConnectFilters(vc->filter_builder, vc->capture_filter, vc->grabber_filter, 0)) + { + vc_CaptureRemove(vc); + return 0; + } + + vc_ConnectFilters(vc->filter_builder, vc->grabber_filter, vc->null_filter, 1); + + vc_UpdateDialogs(vc); + vc_UpdateFormatList(vc); + vc_UpdateSize(vc); + vc->sample_grabber->SetCallback(vc->sample_callback, 0); // associate the sample_grabber with the sample_callback + + return 1; +} + +int imVideoCaptureConnect(imVideoCapture* vc, int device) +{ + assert(vc); + + if (device == -1) + return vc->device; + + if (device == vc->device) + return 1; + + if (device < -1 || device > vc_DeviceCount) + return 0; + + if (vc->device != -1) + imVideoCaptureDisconnect(vc); + + vc->capture_filter = vc_DeviceList[device].filter; + if (!vc->capture_filter) + return 0; + + vc->filter_builder->AddFilter(vc->capture_filter, L"imCaptureSource"); + vc->device = device; + + if (!vc_CaptureConnect(vc)) + return 0; + + return 1; +} + +int imVideoCaptureLive(imVideoCapture* vc, int live) +{ + assert(vc); + + if (live == -1) + return vc->live; + + if (vc->device == -1) + return 0; + + if (live == vc->live) + return 1; + + if (live) + { + vc->live = 1; + if (!vc_StartLive(vc)) + return 0; + } + else + { + vc_StopLive(vc); + vc->live = 0; + } + + return 1; +} + + +/************************************************************************** + Format and Size +***************************************************************************/ + + +void imVideoCaptureGetImageSize(imVideoCapture* vc, int *width, int *height) +{ + assert(vc); + assert(vc->device != -1); + + AM_MEDIA_TYPE mt; + ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE)); + HRESULT hr = vc->sample_grabber->GetConnectedMediaType(&mt); + + if ( SUCCEEDED(hr) && + (mt.majortype == MEDIATYPE_Video) && + (mt.formattype == FORMAT_VideoInfo) && + (mt.cbFormat >= sizeof (VIDEOINFOHEADER)) && + (mt.pbFormat != NULL)) + { + VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)mt.pbFormat; + *width = pVih->bmiHeader.biWidth; + *height = abs(pVih->bmiHeader.biHeight); + CoTaskMemFree((PVOID)mt.pbFormat); + } + else + { + *width = 0; + *height = 0; + } +} + +static IIPDVDec* vc_GetDVDecoder(imVideoCapture* vc) +{ + IIPDVDec *pDV = NULL; + HRESULT hr = vc->capture_graph_builder->FindInterface(NULL, + &MEDIATYPE_Video, vc->capture_filter, IID_IIPDVDec, (void **)&pDV); + if(FAILED(hr)) + return NULL; + + return pDV; +} + +static IAMStreamConfig* vc_GetStreamConfig(imVideoCapture* vc) +{ + IAMStreamConfig *pSC = NULL; + if (FAILED(vc->capture_graph_builder->FindInterface(&PIN_CATEGORY_CAPTURE, + &MEDIATYPE_Video, vc->capture_filter, IID_IAMStreamConfig, (void **)&pSC))) + return NULL; + + return pSC; +} + +static void vc_DeleteMediaType(AM_MEDIA_TYPE *pmt) +{ + CoTaskMemFree((PVOID)pmt->pbFormat); + CoTaskMemFree(pmt); +} + +static int vc_SetStreamSize(imVideoCapture* vc, int width, int height) +{ + IAMStreamConfig *pSC = vc_GetStreamConfig(vc); + if (!pSC) return 0; + + AM_MEDIA_TYPE *pmt; + HRESULT hr = pSC->GetFormat(&pmt); + if (FAILED(hr)) return 0; + + VIDEOINFOHEADER* vih = (VIDEOINFOHEADER*)pmt->pbFormat; + BITMAPINFOHEADER* bih = &vih->bmiHeader; + + /* dibs are DWORD aligned */ + int data_size = height * ((width * bih->biBitCount + 31) / 32) * 4; /* 4 bytes boundary */ + + bih->biSize = sizeof(BITMAPINFOHEADER); + bih->biHeight = height; + bih->biWidth = width; + bih->biSizeImage = data_size; + + int fps = 30; // desired frame rate + vih->dwBitRate = fps * data_size; + vih->AvgTimePerFrame = 10000000 / fps; + + pmt->cbFormat = sizeof(VIDEOINFOHEADER); + pmt->lSampleSize = data_size; + + hr = pSC->SetFormat(pmt); + pSC->Release(); + + vc_DeleteMediaType(pmt); + + return SUCCEEDED(hr); +} + +static int vc_SetImageSize(imVideoCapture* vc, int width, int height) +{ + IIPDVDec* pDV = vc_GetDVDecoder(vc); + if (pDV) + { + int size = 0; + + switch(width) + { + case 720: + size = DVRESOLUTION_FULL; + break; + case 360: + size = DVRESOLUTION_HALF; + break; + case 180: + size = DVRESOLUTION_QUARTER; + break; + case 88: + size = DVRESOLUTION_DC; + break; + } + + if (!size) + return 0; + + int ret = SUCCEEDED(pDV->put_IPDisplay(size)); + if (ret) + vc->sample_callback->SetImageSize(width, height); + + return ret; + } + + int ret = vc_SetStreamSize(vc, width, height); + if (ret) + vc->sample_callback->SetImageSize(width, height); + + return ret; +} + +int imVideoCaptureSetImageSize(imVideoCapture* vc, int width, int height) +{ + assert(vc); + assert(vc->device != -1); + + vc_StopLive(vc); + + // must be disconnected to change size or format + vc_CaptureDisconnect(vc); + + int ret = vc_SetImageSize(vc, width, height); + + if (!vc_CaptureConnect(vc)) + ret = 0; + + vc_StartLive(vc); + + return ret; +} + +static void vc_UpdateFormatList(imVideoCapture* vc) +{ + vc->format_count = 0; + vc->format_current = -1; + + IAMStreamConfig *pSC = vc_GetStreamConfig(vc); + if (!pSC) return; + + int iCount = 0, iSize = 0; + if (FAILED(pSC->GetNumberOfCapabilities(&iCount, &iSize))) + { + pSC->Release(); + return; + } + + AM_MEDIA_TYPE *curr_pmt; + HRESULT hr = pSC->GetFormat(&curr_pmt); + if (FAILED(hr)) + { + pSC->Release(); + return; + } + + for (int iFormat = 0; iFormat < iCount; iFormat++) + { + VIDEO_STREAM_CONFIG_CAPS scc; + AM_MEDIA_TYPE *pmt; + if (SUCCEEDED(pSC->GetStreamCaps(iFormat, &pmt, (BYTE*)&scc))) + { + if (scc.guid == FORMAT_VideoInfo) + { + VIDEOINFOHEADER* vih = (VIDEOINFOHEADER*)curr_pmt->pbFormat; + BITMAPINFOHEADER* bih = &vih->bmiHeader; + int width = bih->biWidth; + int height = abs(bih->biHeight); + + if (curr_pmt->subtype == pmt->subtype && + width == scc.InputSize.cx && + height == scc.InputSize.cy) + { + vc->format_current = vc->format_count; + } + + vc->format_map[vc->format_count] = iFormat; + vc->format_count++; + } + + vc_DeleteMediaType(pmt); + } + } + + vc_DeleteMediaType(curr_pmt); + pSC->Release(); +} + +int imVideoCaptureFormatCount(imVideoCapture* vc) +{ + assert(vc); + assert(vc->device != -1); + + return vc->format_count; +} + +static void vc_GetFormatName(GUID subtype, char* desc) +{ +#define VC_NUM_FORMATS 7 + typedef struct _guid2name { + char* name; + const GUID* subtype; + } guid2name; + static guid2name map_table[VC_NUM_FORMATS] = { + {"RGB1",&MEDIASUBTYPE_RGB1}, + {"RGB4",&MEDIASUBTYPE_RGB4}, + {"RGB8",&MEDIASUBTYPE_RGB8}, + {"RGB565",&MEDIASUBTYPE_RGB565}, + {"RGB555",&MEDIASUBTYPE_RGB555}, + {"RGB24",&MEDIASUBTYPE_RGB24}, + {"RGB32",&MEDIASUBTYPE_RGB32} + }; + + for (int i = 0; i < VC_NUM_FORMATS; i++) + { + if (*(map_table[i].subtype) == subtype) + { + strcpy(desc, map_table[i].name); + return; + } + } + + desc[0] = (char)(subtype.Data1); + desc[1] = (char)(subtype.Data1 >> 8); + desc[2] = (char)(subtype.Data1 >> 16); + desc[3] = (char)(subtype.Data1 >> 32); + desc[4] = 0; +} + +int imVideoCaptureGetFormat(imVideoCapture* vc, int format, int *width, int *height, char* desc) +{ + assert(vc); + assert(vc->device != -1); + assert(vc->format_count); + + if (format >= vc->format_count) + return 0; + + IAMStreamConfig *pSC = vc_GetStreamConfig(vc); + if (!pSC) return 0; + + VIDEO_STREAM_CONFIG_CAPS scc; + AM_MEDIA_TYPE *pmt; + if (SUCCEEDED(pSC->GetStreamCaps(vc->format_map[format], &pmt, (BYTE*)&scc))) + { + *width = scc.InputSize.cx; + *height = scc.InputSize.cy; + vc_GetFormatName(pmt->subtype, desc); + + pSC->Release(); + vc_DeleteMediaType(pmt); + return 1; + } + + pSC->Release(); + return 0; +} + +static int vc_SetStreamFormat(imVideoCapture* vc, int format) +{ + IAMStreamConfig *pSC = vc_GetStreamConfig(vc); + if (!pSC) return 0; + + VIDEO_STREAM_CONFIG_CAPS scc; + AM_MEDIA_TYPE *pmt; + if (FAILED(pSC->GetStreamCaps(vc->format_map[format], &pmt, (BYTE*)&scc))) + { + pSC->Release(); + return 0; + } + + pSC->SetFormat(pmt); + pSC->Release(); + + vc->sample_callback->SetImageSize(scc.InputSize.cx, scc.InputSize.cy); + + vc_DeleteMediaType(pmt); + + return 1; +} + +int imVideoCaptureSetFormat(imVideoCapture* vc, int format) +{ + assert(vc); + assert(vc->device != -1); + + if (format == -1) + return vc->format_current; + + if (format >= vc->format_count) + return 0; + + vc_StopLive(vc); + + // must be disconnected to change size or format + vc_CaptureDisconnect(vc); + + int ok = vc_SetStreamFormat(vc, format); + + if (!vc_CaptureConnect(vc)) + ok = 0; + + if (ok) + vc->format_current = format; + + vc_StartLive(vc); + + return ok; +} + + +/************************************************************************** + Dialogs +***************************************************************************/ + + +static ISpecifyPropertyPages* vc_GetPropertyPages(IUnknown* obj) +{ + ISpecifyPropertyPages *pSpec = NULL; + + HRESULT hr = obj->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); + if (FAILED(hr)) return NULL; + + CAUUID cauuid; + hr = pSpec->GetPages(&cauuid); + CoTaskMemFree(cauuid.pElems); + + if (FAILED(hr)) + { + pSpec->Release(); + return NULL; + } + + return pSpec; +} + +static int vc_ShowPropertyPages(HWND parent, IUnknown* obj, WCHAR* title) +{ + ISpecifyPropertyPages *pSpec = vc_GetPropertyPages(obj); + + CAUUID cauuid; + pSpec->GetPages(&cauuid); + + HRESULT hr = OleCreatePropertyFrame(parent, 30, 30, title, 1, + &obj, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); + + CoTaskMemFree(cauuid.pElems); + pSpec->Release(); + + if (FAILED(hr)) return 0; + return 1; +} + +static IAMVfwCaptureDialogs* vc_getVfwDialogs(imVideoCapture* vc) +{ + IAMVfwCaptureDialogs* pDlg = NULL; + HRESULT hr = vc->capture_graph_builder->FindInterface(&PIN_CATEGORY_CAPTURE, + &MEDIATYPE_Video, vc->capture_filter, IID_IAMVfwCaptureDialogs, (void **)&pDlg); + + if (FAILED(hr)) + return NULL; + + return pDlg; +} + +static int vc_ShowVfwDialog(imVideoCapture* vc, HWND parent, VfwCaptureDialogs dialog) +{ + assert(vc); + assert(vc->device != -1); + + IAMVfwCaptureDialogs *pDlg = vc_getVfwDialogs(vc); + if(!pDlg) return 0; + + HRESULT hr = pDlg->HasDialog(dialog); + if (FAILED(hr)) + { + pDlg->Release(); + return 0; + } + + int ret = 0; + vc_StopLive(vc); + + // must be disconnected to change size or format + vc_CaptureDisconnect(vc); + + hr = pDlg->ShowDialog(dialog, parent); + if (SUCCEEDED(hr)) + ret = 1; + + if (!vc_CaptureConnect(vc)) + ret = 0; + + vc_StartLive(vc); + + pDlg->Release(); + return ret; +} + +static int vc_ShowVfwFormatDialog(imVideoCapture* vc, HWND parent) +{ + return vc_ShowVfwDialog(vc, parent, VfwCaptureDialog_Format); +} + +static int vc_ShowVfwSourceDialog(imVideoCapture* vc, HWND parent) +{ + return vc_ShowVfwDialog(vc, parent, VfwCaptureDialog_Source); +} + +static int vc_ShowVfwDisplayDialog(imVideoCapture* vc, HWND parent) +{ + return vc_ShowVfwDialog(vc, parent, VfwCaptureDialog_Display); +} + +static int vc_ShowFormatDialog(imVideoCapture* vc, HWND parent) +{ + assert(vc); + assert(vc->device != -1); + + IAMStreamConfig *pSC = vc_GetStreamConfig(vc); + if (!pSC) return 0; + + vc_StopLive(vc); + + // must be disconnected to change size or format + vc_CaptureDisconnect(vc); + + int ok = vc_ShowPropertyPages(parent, (IUnknown*)pSC, L"Format"); + pSC->Release(); + + if (!vc_CaptureConnect(vc)) + ok = 0; + + vc_StartLive(vc); + + return ok; +} + +static int vc_ShowSourceDialog(imVideoCapture* vc, HWND parent) +{ + assert(vc); + assert(vc->device != -1); + + return vc_ShowPropertyPages(parent, (IUnknown*)vc->capture_filter, L"Source"); +} + +static IAMTVTuner* vc_GetTVTuner(imVideoCapture* vc) +{ + IAMTVTuner *pTVT = NULL; + + HRESULT hr = vc->capture_graph_builder->FindInterface(&PIN_CATEGORY_CAPTURE, + &MEDIATYPE_Video, vc->capture_filter, IID_IAMTVTuner, (void **)&pTVT); + if(FAILED(hr)) + return NULL; + + return pTVT; +} + +static int vc_ShowTVTunerDialog(imVideoCapture* vc, HWND parent) +{ + assert(vc); + assert(vc->device != -1); + + IAMTVTuner* pTVT = vc_GetTVTuner(vc); + if (!pTVT) + return 0; + + int ret = vc_ShowPropertyPages(parent, (IUnknown*)pTVT, L"TV Turner"); + pTVT->Release(); + return ret; +} + +static IAMCrossbar* vc_GetCrossBar(imVideoCapture* vc) +{ + IAMCrossbar *pX = NULL; + HRESULT hr = vc->capture_graph_builder->FindInterface(&PIN_CATEGORY_CAPTURE, + &MEDIATYPE_Video, vc->capture_filter, IID_IAMCrossbar, (void **)&pX); + if(FAILED(hr)) + return NULL; + + return pX; +} + +static int vc_ShowCrossbarDialog(imVideoCapture* vc, HWND parent) +{ + assert(vc); + assert(vc->device != -1); + + IAMCrossbar* pX = vc_GetCrossBar(vc); + if (!pX) + return 0; + + int ret = vc_ShowPropertyPages(parent, (IUnknown*)pX, L"Crossbar"); + pX->Release(); + return ret; +} + +static IAMCrossbar* vc_GetSecondCrossBar(imVideoCapture* vc, IAMCrossbar *pX) +{ + IAMCrossbar *pX2 = NULL; + IBaseFilter *pXF; + HRESULT hr = pX->QueryInterface(IID_IBaseFilter, (void **)&pXF); + if(hr != S_OK) return NULL; + + hr = vc->capture_graph_builder->FindInterface(&LOOK_UPSTREAM_ONLY, + NULL, pXF, IID_IAMCrossbar, (void **)&pX2); + pXF->Release(); + if(FAILED(hr)) return NULL; + + return pX2; +} + +static int vc_ShowSecondCrossbarDialog(imVideoCapture* vc, HWND parent) +{ + assert(vc); + assert(vc->device != -1); + + IAMCrossbar* pX = vc_GetCrossBar(vc); + if (!pX) + return 0; + + IAMCrossbar* pX2 = vc_GetSecondCrossBar(vc, pX); + if (!pX2) + { + pX->Release(); + return 0; + } + + int ret = vc_ShowPropertyPages(parent, (IUnknown*)pX2, L"Second Crossbar"); + pX->Release(); + pX2->Release(); + return ret; +} + +static int vc_ShowDVDecDialog(imVideoCapture* vc, HWND parent) +{ + assert(vc); + assert(vc->device != -1); + + IIPDVDec* pDV = vc_GetDVDecoder(vc); + if (!pDV) + return 0; + + vc_StopLive(vc); + + int ret = vc_ShowPropertyPages(parent, (IUnknown*)pDV, L"DV Decoder"); + pDV->Release(); + + vc_StartLive(vc); + + return ret; +} + +static void vc_UpdateDialogs(imVideoCapture* vc) +{ + vc->dialog_count = 0; + + IAMVfwCaptureDialogs *pDlg = vc_getVfwDialogs(vc); + if(pDlg) + { + if(pDlg->HasDialog(VfwCaptureDialog_Format) == S_OK) + { + vc->dialog_desc[vc->dialog_count] = "Format... (VFW)"; + vc->dialog_func[vc->dialog_count] = vc_ShowVfwFormatDialog; + vc->dialog_count++; + } + + if(pDlg->HasDialog(VfwCaptureDialog_Source) == S_OK) + { + vc->dialog_desc[vc->dialog_count] = "Source... (VFW)"; + vc->dialog_func[vc->dialog_count] = vc_ShowVfwSourceDialog; + vc->dialog_count++; + } + + if(pDlg->HasDialog(VfwCaptureDialog_Display) == S_OK) + { + vc->dialog_desc[vc->dialog_count] = "Display... (VFW)"; + vc->dialog_func[vc->dialog_count] = vc_ShowVfwDisplayDialog; + vc->dialog_count++; + } + + return; + } + + ISpecifyPropertyPages *pSpec; + IAMStreamConfig *pSC = vc_GetStreamConfig(vc); + if (pSC) + { + pSpec = vc_GetPropertyPages((IUnknown*)pSC); + if (pSpec) + { + vc->dialog_desc[vc->dialog_count] = "Format..."; + vc->dialog_func[vc->dialog_count] = vc_ShowFormatDialog; + vc->dialog_count++; + pSpec->Release(); + } + + pSC->Release(); + } + + pSpec = vc_GetPropertyPages((IUnknown*)vc->capture_filter); + if (pSpec) + { + vc->dialog_desc[vc->dialog_count] = "Source..."; + vc->dialog_func[vc->dialog_count] = vc_ShowSourceDialog; + vc->dialog_count++; + pSpec->Release(); + } + + IIPDVDec* pDV = vc_GetDVDecoder(vc); + if (pDV) + { + pSpec = vc_GetPropertyPages((IUnknown*)pDV); + if (pSpec) + { + vc->dialog_desc[vc->dialog_count] = "DV Decoder..."; + vc->dialog_func[vc->dialog_count] = vc_ShowDVDecDialog; + vc->dialog_count++; + pSpec->Release(); + } + + pDV->Release(); + } + + IAMCrossbar* pX = vc_GetCrossBar(vc); + if (pX) + { + pSpec = vc_GetPropertyPages((IUnknown*)pX); + if (pSpec) + { + vc->dialog_desc[vc->dialog_count] = "Crossbar..."; + vc->dialog_func[vc->dialog_count] = vc_ShowCrossbarDialog; + vc->dialog_count++; + pSpec->Release(); + } + + IAMCrossbar* pX2 = vc_GetSecondCrossBar(vc, pX); + if (pX2) + { + pSpec = vc_GetPropertyPages((IUnknown*)pX2); + if (pSpec) + { + vc->dialog_desc[vc->dialog_count] = "Second Crossbar..."; + vc->dialog_func[vc->dialog_count] = vc_ShowSecondCrossbarDialog; + vc->dialog_count++; + pSpec->Release(); + } + + pX2->Release(); + } + + pX->Release(); + } + + IAMTVTuner* pTVT = vc_GetTVTuner(vc); + if (pTVT) + { + pSpec = vc_GetPropertyPages((IUnknown*)pTVT); + if (pSpec) + { + vc->dialog_desc[vc->dialog_count] = "TV Tuner..."; + vc->dialog_func[vc->dialog_count] = vc_ShowTVTunerDialog; + vc->dialog_count++; + pSpec->Release(); + } + + pTVT->Release(); + } +} + +int imVideoCaptureDialogCount(imVideoCapture* vc) +{ + assert(vc); + assert(vc->device != -1); + + return vc->dialog_count; +} + +const char* imVideoCaptureDialogDesc(imVideoCapture* vc, int dialog) +{ + assert(vc); + assert(vc->device != -1); + + if (dialog >= vc->dialog_count) + return NULL; + + return vc->dialog_desc[dialog]; +} + +int imVideoCaptureShowDialog(imVideoCapture* vc, int dialog, void* parent) +{ + assert(vc); + assert(vc->device != -1); + + if (dialog >= vc->dialog_count) + return 0; + + return vc->dialog_func[dialog](vc, (HWND)parent); +} + +int imVideoCaptureSetInOut(imVideoCapture* vc, int input, int output, int cross) +{ + assert(vc); + assert(vc->device != -1); + + IAMCrossbar* pX = vc_GetCrossBar(vc); + if (pX) + { + HRESULT hr = S_FALSE; + + if (cross == 1) + hr = pX->Route(output, input); + else + { + IAMCrossbar* pX2 = vc_GetSecondCrossBar(vc, pX); + if (pX2) + { + hr = pX2->Route(output, input); + pX2->Release(); + } + } + + pX->Release(); + if (hr == S_OK) + return 1; + } + + return 0; +} + +/************************************************************************** + Attributes +***************************************************************************/ + + +static float vc_Value2Percent(long Min, long Max, long Val) +{ + return ((Val - Min)*100.0f)/((float)(Max - Min)); +} + +static long vc_Percent2Value(long Min, long Max, long Step, float Per) +{ + long Val = (long)((Per/100.)*(Max - Min) + Min); + if (Step == 1) + return Val; + + long num_step = (Val - Min + Step-1) / Step; + return num_step*Step + Min; +} + +static IAMVideoProcAmp* vc_InitVideoProcAmp(IBaseFilter* capture_filter, IAMVideoProcAmp* *video_prop) +{ + if (*video_prop) + return *video_prop; + + HRESULT hr = capture_filter->QueryInterface(IID_IAMVideoProcAmp, (void**)video_prop); + if (FAILED(hr)) + return NULL; + + return *video_prop; +} + +static int vc_SetVideoProcAmpProperty(IBaseFilter* capture_filter, IAMVideoProcAmp* *video_prop, long property, float percent) +{ + IAMVideoProcAmp *pProp = vc_InitVideoProcAmp(capture_filter, video_prop); + if (!pProp) return 0; + HRESULT hr; + VideoProcAmpProperty prop = (VideoProcAmpProperty)property; + long Min, Max, Step, Default, Flags; + hr = pProp->GetRange(prop, &Min, &Max, &Step, &Default, &Flags); + hr = pProp->Set(prop, vc_Percent2Value(Min, Max, Step, percent), VideoProcAmp_Flags_Manual); + if (FAILED(hr)) return 0; + return 1; +} + +static int vc_GetVideoProcAmpProperty(IBaseFilter* capture_filter, IAMVideoProcAmp* *video_prop, long property, float *percent) +{ + IAMVideoProcAmp *pProp = vc_InitVideoProcAmp(capture_filter, video_prop); + if (!pProp) return 0; + + HRESULT hr; + VideoProcAmpProperty prop = (VideoProcAmpProperty)property; + long Min, Max, Step, Default, Flags, Val; + hr = pProp->GetRange(prop, &Min, &Max, &Step, &Default, &Flags); + hr = pProp->Get(prop, &Val, &Flags); + + if (FAILED(hr)) return 0; + *percent = vc_Value2Percent(Min, Max, Val); + return 1; +} + +static int vc_ResetVideoProcAmpProperty(IBaseFilter* capture_filter, IAMVideoProcAmp* *video_prop, long property, int fauto) +{ + IAMVideoProcAmp *pProp = vc_InitVideoProcAmp(capture_filter, video_prop); + if (!pProp) return 0; + + HRESULT hr; + VideoProcAmpProperty prop = (VideoProcAmpProperty)property; + long Min, Max, Step, Default, Flags; + hr = pProp->GetRange(prop, &Min, &Max, &Step, &Default, &Flags); + + if (fauto && (Flags & VideoProcAmp_Flags_Auto)) + hr = pProp->Set(prop, Default, VideoProcAmp_Flags_Auto); + else + hr = pProp->Set(prop, Default, VideoProcAmp_Flags_Manual); + + if (FAILED(hr)) return 0; + return 1; +} + +static IAMCameraControl* vc_InitCameraControl(IBaseFilter* capture_filter, IAMCameraControl* *camera_prop) +{ + if (*camera_prop) + return *camera_prop; + + HRESULT hr = capture_filter->QueryInterface(IID_IAMCameraControl, (void**)camera_prop); + if (FAILED(hr)) + return NULL; + + return *camera_prop; +} + +static int vc_SetCameraControlProperty(IBaseFilter* capture_filter, IAMCameraControl* *camera_prop, long property, float percent) +{ + IAMCameraControl *pProp = vc_InitCameraControl(capture_filter, camera_prop); + if (!pProp) return 0; + + HRESULT hr; + CameraControlProperty prop = (CameraControlProperty)property; + long Min, Max, Step, Default, Flags; + hr = pProp->GetRange(prop, &Min, &Max, &Step, &Default, &Flags); + hr = pProp->Set(prop, vc_Percent2Value(Min, Max, Step, percent), CameraControl_Flags_Manual); + + if (FAILED(hr)) return 0; + return 1; +} + +static int vc_GetCameraControlProperty(IBaseFilter* capture_filter, IAMCameraControl* *camera_prop, long property, float *percent) +{ + IAMCameraControl *pProp = vc_InitCameraControl(capture_filter, camera_prop); + if (!pProp) return 0; + + HRESULT hr; + CameraControlProperty prop = (CameraControlProperty)property; + long Min, Max, Step, Default, Flags, Val; + hr = pProp->GetRange(prop, &Min, &Max, &Step, &Default, &Flags); + hr = pProp->Get(prop, &Val, &Flags); + + if (FAILED(hr)) return 0; + *percent = vc_Value2Percent(Min, Max, Val); + return 1; +} + +static int vc_ResetCameraControlProperty(IBaseFilter* capture_filter, IAMCameraControl* *camera_prop, long property, int fauto) +{ + IAMCameraControl *pProp = vc_InitCameraControl(capture_filter, camera_prop); + if (!pProp) return 0; + + HRESULT hr; + CameraControlProperty prop = (CameraControlProperty)property; + long Min, Max, Step, Default, Flags; + hr = pProp->GetRange(prop, &Min, &Max, &Step, &Default, &Flags); + + if (fauto && (Flags & CameraControl_Flags_Auto)) + hr = pProp->Set(prop, Default, CameraControl_Flags_Auto); + else + hr = pProp->Set(prop, Default, CameraControl_Flags_Manual); + + if (FAILED(hr)) return 0; + return 1; +} + +static IAMVideoControl* vc_InitVideoControl(IBaseFilter* capture_filter, IAMVideoControl* *video_prop) +{ + if (*video_prop) + return *video_prop; + + HRESULT hr = capture_filter->QueryInterface(IID_IAMVideoControl, (void**)video_prop); + if (FAILED(hr)) + return NULL; + + return *video_prop; +} + +static int vc_SetVideoControlProperty(IBaseFilter* capture_filter, IAMVideoControl* *video_prop, long property, float percent) +{ + IAMVideoControl *pProp = vc_InitVideoControl(capture_filter, video_prop); + if (!pProp) return 0; + + HRESULT hr; + IPin *pOutPin = vc_GetPin(capture_filter, PINDIR_OUTPUT); + long Mode; + hr = pProp->GetMode(pOutPin, &Mode); + if (percent) + Mode = Mode | property; + else + Mode = Mode & ~property; + hr = pProp->SetMode(pOutPin, Mode); + pOutPin->Release(); + + if (FAILED(hr)) return 0; + return 1; +} + +static int vc_GetVideoControlProperty(IBaseFilter* capture_filter, IAMVideoControl* *video_prop, long property, float *percent) +{ + IAMVideoControl *pProp = vc_InitVideoControl(capture_filter, video_prop); + if (!pProp) return 0; + + HRESULT hr; + long Mode; + IPin *pOutPin = vc_GetPin(capture_filter, PINDIR_OUTPUT); + hr = pProp->GetMode(pOutPin, &Mode); + pOutPin->Release(); + + if (FAILED(hr)) return 0; + if (Mode & property) + *percent = 100.; + else + *percent = 0.; + return 1; +} + +static int vc_ResetVideoControlProperty(IBaseFilter* capture_filter, IAMVideoControl* *video_prop, long property, int fauto) +{ + IAMVideoControl *pProp = vc_InitVideoControl(capture_filter, video_prop); + if (!pProp) return 0; + + HRESULT hr; + long Mode; + IPin *pOutPin = vc_GetPin(capture_filter, PINDIR_OUTPUT); + hr = pProp->GetMode(pOutPin, &Mode); + if (Mode & property) + Mode = Mode & ~property; + else + Mode = Mode | property; + hr = pProp->SetMode(pOutPin, Mode); + pOutPin->Release(); + + if (FAILED(hr)) return 0; + return 1; +} + +static long vc_AnalogFormat[19] = +{ + AnalogVideo_NTSC_M, + AnalogVideo_NTSC_M_J, + AnalogVideo_NTSC_433, + AnalogVideo_PAL_B, + AnalogVideo_PAL_D, + AnalogVideo_PAL_H, + AnalogVideo_PAL_I, + AnalogVideo_PAL_M, + AnalogVideo_PAL_N, + AnalogVideo_PAL_60, + AnalogVideo_SECAM_B, + AnalogVideo_SECAM_D, + AnalogVideo_SECAM_G, + AnalogVideo_SECAM_H, + AnalogVideo_SECAM_K, + AnalogVideo_SECAM_K1, + AnalogVideo_SECAM_L, + AnalogVideo_SECAM_L1, + AnalogVideo_PAL_N_COMBO +}; + +static int vc_SetAnalogFormat(IBaseFilter* capture_filter, float percent) +{ + IAMAnalogVideoDecoder* video_decoder = NULL; + HRESULT hr = capture_filter->QueryInterface(IID_IAMAnalogVideoDecoder, (void**)video_decoder); + if (FAILED(hr)) + return 0; + + hr = video_decoder->put_TVFormat(vc_AnalogFormat[(int)percent]); + video_decoder->Release(); + + if (FAILED(hr)) return 0; + return 1; +} + +static int vc_GetAnalogFormat(IBaseFilter* capture_filter, float *percent) +{ + IAMAnalogVideoDecoder* video_decoder = NULL; + HRESULT hr = capture_filter->QueryInterface(IID_IAMAnalogVideoDecoder, (void**)video_decoder); + if (FAILED(hr)) + return 0; + + long format; + hr = video_decoder->get_TVFormat(&format); + video_decoder->Release(); + + if (FAILED(hr)) return 0; + for (int i = 0; i < 19; i++) + { + if (vc_AnalogFormat[i] == format) + { + *percent = (float)i; + return 1; + } + } + return 0; +} + +#define VC_HASH_SIZE 101 +#define VC_HASH_MULTIPLIER 31 + +/** Unique Hash index for a key + * We use the hash function described in "The Pratice of Programming" of Kernighan & Pike. */ +static int vc_HashIndex(const char *key, int hash_size) +{ + unsigned short hash = 0; + const unsigned char *p_key = (const unsigned char*)key; + + for(; *p_key; p_key++) + hash = hash*VC_HASH_MULTIPLIER + *p_key; + + return hash % hash_size; +} + +#define VC_CAMERASHIFT 20 +#define VC_VIDEOSHIFT 40 +#define VC_ANALOGSHIFT 60 + +static long vc_Attrib2Property(const char* attrib) +{ + static long prop_table[VC_HASH_SIZE]; + static int first = 1; + if (first) + { + memset(prop_table, 0, VC_HASH_SIZE*sizeof(long)); + prop_table[vc_HashIndex("CameraPanAngle", VC_HASH_SIZE)] = (long)CameraControl_Pan + VC_CAMERASHIFT + 1; + prop_table[vc_HashIndex("CameraTiltAngle", VC_HASH_SIZE)] = (long)CameraControl_Tilt + VC_CAMERASHIFT + 1; + prop_table[vc_HashIndex("CameraRollAngle", VC_HASH_SIZE)] = (long)CameraControl_Roll + VC_CAMERASHIFT + 1; + prop_table[vc_HashIndex("CameraLensZoom", VC_HASH_SIZE)] = (long)CameraControl_Zoom + VC_CAMERASHIFT + 1; + prop_table[vc_HashIndex("CameraExposure", VC_HASH_SIZE)] = (long)CameraControl_Exposure + VC_CAMERASHIFT + 1; + prop_table[vc_HashIndex("CameraIris", VC_HASH_SIZE)] = (long)CameraControl_Iris + VC_CAMERASHIFT + 1; + prop_table[vc_HashIndex("CameraFocus", VC_HASH_SIZE)] = (long)CameraControl_Focus + VC_CAMERASHIFT + 1; + prop_table[vc_HashIndex("VideoBrightness", VC_HASH_SIZE)] = (long)VideoProcAmp_Brightness + 1; + prop_table[vc_HashIndex("VideoContrast", VC_HASH_SIZE)] = (long)VideoProcAmp_Contrast + 1; + prop_table[vc_HashIndex("VideoHue", VC_HASH_SIZE)] = (long)VideoProcAmp_Hue + 1; + prop_table[vc_HashIndex("VideoSaturation", VC_HASH_SIZE)] = (long)VideoProcAmp_Saturation + 1; + prop_table[vc_HashIndex("VideoSharpness", VC_HASH_SIZE)] = (long)VideoProcAmp_Sharpness + 1; + prop_table[vc_HashIndex("VideoGamma", VC_HASH_SIZE)] = (long)VideoProcAmp_Gamma + 1; + prop_table[vc_HashIndex("VideoColorEnable", VC_HASH_SIZE)] = (long)VideoProcAmp_ColorEnable + 1; + prop_table[vc_HashIndex("VideoWhiteBalance", VC_HASH_SIZE)] = (long)VideoProcAmp_WhiteBalance + 1; + prop_table[vc_HashIndex("VideoBacklightCompensation", VC_HASH_SIZE)] = (long)VideoProcAmp_BacklightCompensation + 1; + prop_table[vc_HashIndex("VideoGain", VC_HASH_SIZE)] = (long)VideoProcAmp_Gain + 1; + prop_table[vc_HashIndex("FlipHorizontal", VC_HASH_SIZE)] = (long)VideoControlFlag_FlipHorizontal + VC_VIDEOSHIFT + 1; + prop_table[vc_HashIndex("FlipVertical", VC_HASH_SIZE)] = (long)VideoControlFlag_FlipVertical + VC_VIDEOSHIFT + 1; + prop_table[vc_HashIndex("AnalogFormat", VC_HASH_SIZE)] = (long)0 + VC_ANALOGSHIFT + 1; + first = 0; + } + long prop = prop_table[vc_HashIndex(attrib, VC_HASH_SIZE)]; + if (!prop) + return 0; + return prop-1; +} + +int imVideoCaptureSetAttribute(imVideoCapture* vc, const char* attrib, float percent) +{ + assert(vc); + assert(vc->device != -1); + + long property = vc_Attrib2Property(attrib); + if (property == -1) return 0; + if (property < VC_CAMERASHIFT) + return vc_SetVideoProcAmpProperty(vc->capture_filter, &vc->video_prop, property, percent); + else if (property < VC_VIDEOSHIFT) + return vc_SetCameraControlProperty(vc->capture_filter, &vc->camera_prop, property-VC_CAMERASHIFT, percent); + else if (property < VC_ANALOGSHIFT) + return vc_SetVideoControlProperty(vc->capture_filter, &vc->videoctrl_prop, property-VC_VIDEOSHIFT, percent); + else + return vc_SetAnalogFormat(vc->capture_filter, percent); +} + +int imVideoCaptureGetAttribute(imVideoCapture* vc, const char* attrib, float *percent) +{ + assert(vc); + assert(vc->device != -1); + + long property = vc_Attrib2Property(attrib); + if (property == -1) return 0; + if (property < VC_CAMERASHIFT) + return vc_GetVideoProcAmpProperty(vc->capture_filter, &vc->video_prop, property, percent); + else if (property < VC_VIDEOSHIFT) + return vc_GetCameraControlProperty(vc->capture_filter, &vc->camera_prop, property-VC_CAMERASHIFT, percent); + else if (property < VC_ANALOGSHIFT) + return vc_GetVideoControlProperty(vc->capture_filter, &vc->videoctrl_prop, property-VC_VIDEOSHIFT, percent); + else + return vc_GetAnalogFormat(vc->capture_filter, percent); +} + +int imVideoCaptureResetAttribute(imVideoCapture* vc, const char* attrib, int fauto) +{ + assert(vc); + assert(vc->device != -1); + + long property = vc_Attrib2Property(attrib); + if (property == -1) return 0; + if (property < VC_CAMERASHIFT) + return vc_ResetVideoProcAmpProperty(vc->capture_filter, &vc->video_prop, property, fauto); + else if (property < VC_VIDEOSHIFT) + return vc_ResetCameraControlProperty(vc->capture_filter, &vc->camera_prop, property-VC_CAMERASHIFT, fauto); + else if (property < VC_ANALOGSHIFT) + return vc_ResetVideoControlProperty(vc->capture_filter, &vc->videoctrl_prop, property-VC_VIDEOSHIFT, fauto); + return 0; +} + +const char** imVideoCaptureGetAttributeList(imVideoCapture* vc, int *num_attrib) +{ +#define VC_VIDEOPROC_MAX 10 +#define VC_CAMERACONTROL_MAX 7 +#define VC_VIDEOCONTROL_MAX 2 +#define VC_VIDEODECODER_MAX 1 +#define VC_NUM_ATTRIB_MAX (VC_VIDEOPROC_MAX+VC_CAMERACONTROL_MAX+VC_VIDEOCONTROL_MAX+VC_VIDEODECODER_MAX) + static char* attrib_list[VC_NUM_ATTRIB_MAX]; + static char* all_attrib_list[VC_NUM_ATTRIB_MAX] = + { //Pre-calculated Hash Index: + "VideoBrightness", // (97) + "VideoContrast", // (80) + "VideoHue", // (98) + "VideoSaturation", // (4) + "VideoSharpness", // (56) + "VideoGamma", // (67) + "VideoColorEnable", // (91) + "VideoWhiteBalance", // (26) + "VideoBacklightCompensation", // (50) + "VideoGain", // (36) + "CameraPanAngle", // (64) + "CameraTiltAngle", // (54) + "CameraRollAngle", // (85) + "CameraLensZoom", // (57) + "CameraExposure", // (84) + "CameraIris", // (20) + "CameraFocus", // (62) + "FlipHorizontal", // (21) + "FlipVertical", // (28) + "AnalogFormat"}; // (89) + + int i; + *num_attrib = 0; + + IAMVideoProcAmp *video_prop; + HRESULT hr = vc->capture_filter->QueryInterface(IID_IAMVideoProcAmp, (void**)&video_prop); + if (SUCCEEDED(hr)) + { + for (i = 0; i < VC_VIDEOPROC_MAX; i++) + attrib_list[i] = all_attrib_list[i]; + *num_attrib = VC_VIDEOPROC_MAX; + video_prop->Release(); + } + + IAMCameraControl *camera_prop; + hr = vc->capture_filter->QueryInterface(IID_IAMCameraControl, (void**)&camera_prop); + if (SUCCEEDED(hr)) + { + for (i = 0; i < VC_CAMERACONTROL_MAX; i++) + attrib_list[i+*num_attrib] = all_attrib_list[i+VC_VIDEOPROC_MAX]; + *num_attrib += VC_CAMERACONTROL_MAX; + camera_prop->Release(); + } + + IAMVideoControl* video_ctrl; + hr = vc->capture_filter->QueryInterface(IID_IAMVideoControl, (void**)&video_ctrl); + if (SUCCEEDED(hr)) + { + for (i = 0; i < VC_VIDEOCONTROL_MAX; i++) + attrib_list[i+*num_attrib] = all_attrib_list[i+VC_VIDEOPROC_MAX+VC_CAMERACONTROL_MAX]; + *num_attrib += VC_VIDEOCONTROL_MAX; + video_ctrl->Release(); + } + + IAMAnalogVideoDecoder* video_decoder = NULL; + hr = vc->capture_filter->QueryInterface(IID_IAMAnalogVideoDecoder, (void**)&video_decoder); + if (SUCCEEDED(hr)) + { + for (i = 0; i < VC_VIDEODECODER_MAX; i++) + attrib_list[i+*num_attrib] = all_attrib_list[i+VC_VIDEOPROC_MAX+VC_CAMERACONTROL_MAX+VC_VIDEOCONTROL_MAX]; + *num_attrib += VC_VIDEODECODER_MAX; + video_decoder->Release(); + } + + return (const char**)attrib_list; +} + +//VIDEOINFOHEADER +// AvgTimePerFrame |