/** \file
 * \brief Video Capture Using Direct Show 9
 *
 * See Copyright Notice in im.h
 * $Id: 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".
  The error functions if used, need the library "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 the WDM Source Dialog is not "live" 
  just because it is a modal dialog and 
  it does not use the application message loop.
  It can be live 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 "imvideocapture.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 REGISTER_FILTERGRAPH // Use this to allow GraphEdit to spy the graph
//#define INCLUDE_VFW_DEVICES  // Use this to allow old video for windows devices
//#define PRINT_ERROR_MESSAGES // Use this to display a system custom error message

#if defined(_DEBUG) | defined(DEBUG)
#define REGISTER_FILTERGRAPH
#define 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(BYTE* data, int timeout);

protected:
  int  m_Width, m_Height;
  bool	m_newImageFlag;
  BYTE *m_Image;
  CRITICAL_SECTION m_sect;
  HANDLE m_imageReady;
};

imTrackingGrabberCB::imTrackingGrabberCB()
{
  InitializeCriticalSection(&m_sect);
  m_newImageFlag = 0;
  m_Image = NULL;
  m_imageReady = CreateEvent(NULL, FALSE, TRUE, NULL);
}

imTrackingGrabberCB::~imTrackingGrabberCB() 
{
  CloseHandle(m_imageReady);
  EnterCriticalSection(&m_sect);
  DeleteCriticalSection(&m_sect);
  if (m_Image) delete m_Image;
}

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);

  int new_size = width * height * 3;

  if (!m_Image) 
  {
    m_Image = (BYTE*)calloc(new_size, 1);
    m_Width = width; 
    m_Height = height;
  }

  if (m_Width*m_Height < new_size)
    m_Image = (BYTE*)realloc(m_Image, new_size);

  m_Width = width; 
  m_Height = height;

  LeaveCriticalSection(&m_sect);
}

STDMETHODIMP imTrackingGrabberCB::SampleCB(double, IMediaSample *pSample)
{
  if (!m_Image) 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);
  memcpy(m_Image, pData, size);
  m_newImageFlag = 1;

  LeaveCriticalSection(&m_sect);

  SetEvent(m_imageReady);
  
  return S_OK;
}

int imTrackingGrabberCB::GetImage(BYTE* data, 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)
  {
    memcpy(data, m_Image, m_Width*m_Height*3);
    m_newImageFlag = 0;

    LeaveCriticalSection(&m_sect);
    return 1;
  }

  LeaveCriticalSection(&m_sect);
  return 0;
}


/**************************************************************************
                       Direct Show Only
***************************************************************************/


static int vc_DeviceCount = 0;
static char vc_DeviceDesc[VC_MAXVIDDEVICES][160];

#ifdef 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);
    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 HARDFAILED(_x) vc_ShowError(_x)
#else
#define HARDFAILED FAILED
#endif

static char* 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 INCLUDE_VFW_DEVICES
#define CATEGORY_FLAG 0
#else
#define CATEGORY_FLAG CDEF_DEVMON_FILTER|CDEF_DEVMON_PNP_DEVICE
#endif

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, CATEGORY_FLAG);
  if (FAILED(hr) || !pEnum)
  {
    pDevEnum->Release();
    return;
  }

  IMoniker *pMoniker = NULL;
  while (pEnum->Next(1, &pMoniker, NULL) == S_OK)
  {
    IPropertyBag *pPropBag;
    hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void**)(&pPropBag));
    if (FAILED(hr))
    {
      pMoniker->Release();
      continue;  // Skip this one, maybe the next one will work.
    } 

    // Find the friendly name.
    VARIANT varName;
    VariantInit(&varName);

    hr = pPropBag->Read(L"FriendlyName", &varName, 0);
    if (SUCCEEDED(hr))
    {
      char* str = Wide2Char(varName.bstrVal);
      sprintf(vc_DeviceDesc[vc_DeviceCount], "%d - %s", vc_DeviceCount, str);
      free(str);
      VariantClear(&varName); 
      vc_DeviceCount++;
    }

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

  pEnum->Release();
  pDevEnum->Release();
}

static IBaseFilter* vc_GetDevice(int device)
{
  // Selecting a Capture Device
  ICreateDevEnum *pDevEnum = NULL;
  IEnumMoniker *pEnum = 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 NULL;
                                       
  // Create an enumerator for the video capture category.
  hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, CATEGORY_FLAG);
  if (FAILED(hr) || !pEnum)
  {
    pDevEnum->Release();
    return NULL;
  }

  IMoniker *pMoniker = NULL;
  int d = 0;
  while (pEnum->Next(1, &pMoniker, NULL) == S_OK)
  {
    if (d == device)
    {
      //create the capture filter for the device 
      //add the filter to the filter graph
      IBaseFilter *capture_filter = NULL;
      hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&capture_filter);
      if (SUCCEEDED(hr))
      {
        pMoniker->Release();
        pEnum->Release();
        pDevEnum->Release();
        return capture_filter;
      }
      else
      {
        pMoniker->Release();
        break;
      }
    }

    pMoniker->Release();
    d++;
  }

  pEnum->Release();
  pDevEnum->Release();
  return NULL;
}

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 (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 (HARDFAILED(hr))
  {
    if (pOut) pOut->Release();
    return 0;
  }

  if (pOut)
  {
    hr = filter_builder->Disconnect(pOut);
    pOut->Release();

    if (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 (HARDFAILED(hr)) return 0;
  return 1;
}

static DWORD vc_AddGraphToRot(IUnknown *pUnkGraph) 
{
  IMoniker * pMoniker;
  IRunningObjectTable *pROT;
  WCHAR wsz[128];
  HRESULT hr;
  DWORD dwRegister;

  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)) 
  {
    hr = pROT->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, pUnkGraph, pMoniker, &dwRegister);
    pMoniker->Release();
  }

  pROT->Release();
  if (SUCCEEDED(hr)) 
    return dwRegister;
  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. */

  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 imVideoCaptureDeviceCount(void)
{
  return vc_DeviceCount;
}

int imVideoCaptureReloadDevices(void)
{
  vc_DeviceCount = 0;
  vc_EnumerateDevices();
  return vc_DeviceCount;
}

const char* imVideoCaptureDeviceDesc(int device)
{
  // List available Devices once
  if (vc_DeviceCount == 0)
  {
    vc_EnumerateDevices();

    if (vc_DeviceCount == 0)
      return NULL;
  }

  if (device >= vc_DeviceCount)
    return NULL;

  return vc_DeviceDesc[device];
}

#define SafeRelease(p) { if( (p) != 0 ) { (p)->Release(); (p)= NULL; } }

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
  mt.formattype = FORMAT_VideoInfo;
  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 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))
  {
    SafeRelease(vc->grabber_filter);
    SafeRelease(vc->filter_builder);
    SafeRelease(vc->capture_graph_builder);
    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);

  SafeRelease(vc->capture_filter);
  SafeRelease(vc->video_prop);
  SafeRelease(vc->camera_prop);
  SafeRelease(vc->videoctrl_prop);

  vc->dialog_count = 0;
  vc->live = 0;
  vc->device = -1;
}

void imVideoCaptureDestroy(imVideoCapture* vc)
{
  assert(vc);

#ifdef REGISTER_FILTERGRAPH
  if (vc->registered_graph) vc_RemoveGraphFromRot(vc->registered_graph);
#endif

  imVideoCaptureDisconnect(vc);

  delete vc->sample_callback;

  SafeRelease(vc->media_control);
  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 (HARDFAILED(hr))
    {
      vc->live = 0;
      return 0;
    }

    Sleep(VC_CAMERADELAY);
  }

  return 1;
}

int imVideoCaptureOneFrame(imVideoCapture* vc, unsigned char* data)
{
  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, -1);

  vc_StopLive(vc);
  vc->live = 0;

  vc->sample_grabber->SetOneShot(FALSE);

  return ret;
}

int imVideoCaptureFrame(imVideoCapture* vc, unsigned char* data, int timeout)
{
  assert(vc);
  assert(vc->device != -1);
  assert(vc->live);
  return vc->sample_callback->GetImage(data, timeout);
}

static int vc_CaptureDisconnect(imVideoCapture* vc)
{
	vc->sample_grabber->SetCallback(NULL, 0);

  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
  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 int vc_CaptureConnect(imVideoCapture* 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_UpdateSize(vc);
  vc->sample_grabber->SetCallback(vc->sample_callback, 0);

  return 1;
}

int imVideoCaptureConnect(imVideoCapture* vc, int device)
{
  assert(vc);

  if (device == IMVC_QUERY)
    return vc->device;

  if (device == vc->device)
    return 1;

  if (vc->device != -1)
    imVideoCaptureDisconnect(vc);

  vc->capture_filter = vc_GetDevice(device);
  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 (vc->device == -1)
    return 0;

  if (live == vc->live)
    return 1;

  if (live == IMVC_QUERY)
    return vc->live;

  if (live)
  {
    vc->live = 1;
    if (!vc_StartLive(vc))
      return 0;
  }
  else
  {
    vc_StopLive(vc); 
    vc->live = 0;
  }

  return 1;
}

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 = 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 int vc_SetStreamSize(IAMStreamConfig *pSC, int width, int height)
{
  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();

  CoTaskMemFree((PVOID)pmt->pbFormat);
  CoTaskMemFree(pmt);

  return SUCCEEDED(hr);
}

int imVideoCaptureSetImageSize(imVideoCapture* vc, int width, int height)
{
  assert(vc);
  assert(vc->device != -1);

  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;

    vc_StopLive(vc);
    int ret = SUCCEEDED(pDV->put_IPDisplay(size));
    vc_StartLive(vc);

    if (ret) vc_UpdateSize(vc);

    return ret;
  }


  IAMStreamConfig *pSC = vc_GetStreamConfig(vc);
  if (!pSC) return 0;

  vc_StopLive(vc);
  int ret = vc_SetStreamSize(pSC, width, height);
  vc_StartLive(vc);

  if (ret) vc_UpdateSize(vc);

  return ret;
}

/**************************************************************************
                            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);

  // VFW Filter must be disconnected
  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);

  int width, height;
  imVideoCaptureGetImageSize(vc, &width, &height);

  // Format dialog must be disconnected
  vc_CaptureDisconnect(vc);

  int ok = vc_ShowPropertyPages(parent, (IUnknown*)pSC, L"Format");
  pSC->Release();

  if (!vc_CaptureConnect(vc))
    ok = 0;

  if (ok)
  {
    // Force the filters update
    int new_width, new_height;
    imVideoCaptureGetImageSize(vc, &new_width, &new_height);
    if (new_width != width || new_height != height)
      imVideoCaptureSetImageSize(vc, new_width, new_height);
  }

  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);
}


/**************************************************************************
                            Attributes
***************************************************************************/


static double vc_Value2Percent(long Min, long Max, long Val)
{
  return ((Val - Min)*100.)/((double)(Max - Min));
}

static long vc_Percent2Value(long Min, long Max, long Step, double 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, double 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, double *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, double 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, double *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, double 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, double *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, double 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, double *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 = (double)i;
      return 1;
    }
  }
  return 0;
}

#define UH_SIZE 101
#define UH_MULTIPLIER 31

/** Unique Hash index for a key
 * We use the hash function described in "The Pratice of Programming" of Kernighan & Pike. */
static int 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*UH_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[UH_SIZE];
  static int first = 1;
  if (first)
  {
    memset(prop_table, 0, UH_SIZE*sizeof(long));
    prop_table[HashIndex("CameraPanAngle", UH_SIZE)] = (long)CameraControl_Pan + VC_CAMERASHIFT + 1;
    prop_table[HashIndex("CameraTiltAngle", UH_SIZE)] = (long)CameraControl_Tilt + VC_CAMERASHIFT + 1;
    prop_table[HashIndex("CameraRollAngle", UH_SIZE)] = (long)CameraControl_Roll + VC_CAMERASHIFT + 1;
    prop_table[HashIndex("CameraLensZoom", UH_SIZE)] = (long)CameraControl_Zoom + VC_CAMERASHIFT + 1;
    prop_table[HashIndex("CameraExposure", UH_SIZE)] = (long)CameraControl_Exposure + VC_CAMERASHIFT + 1;
    prop_table[HashIndex("CameraIris", UH_SIZE)] = (long)CameraControl_Iris + VC_CAMERASHIFT + 1;
    prop_table[HashIndex("CameraFocus", UH_SIZE)] = (long)CameraControl_Focus + VC_CAMERASHIFT + 1;
    prop_table[HashIndex("VideoBrightness", UH_SIZE)] = (long)VideoProcAmp_Brightness + 1;
    prop_table[HashIndex("VideoContrast", UH_SIZE)] = (long)VideoProcAmp_Contrast + 1;
    prop_table[HashIndex("VideoHue", UH_SIZE)] = (long)VideoProcAmp_Hue + 1;
    prop_table[HashIndex("VideoSaturation", UH_SIZE)] = (long)VideoProcAmp_Saturation + 1;
    prop_table[HashIndex("VideoSharpness", UH_SIZE)] = (long)VideoProcAmp_Sharpness + 1;
    prop_table[HashIndex("VideoGamma", UH_SIZE)] = (long)VideoProcAmp_Gamma + 1;
    prop_table[HashIndex("VideoColorEnable", UH_SIZE)] = (long)VideoProcAmp_ColorEnable + 1;
    prop_table[HashIndex("VideoWhiteBalance", UH_SIZE)] = (long)VideoProcAmp_WhiteBalance + 1;
    prop_table[HashIndex("VideoBacklightCompensation", UH_SIZE)] = (long)VideoProcAmp_BacklightCompensation + 1;
    prop_table[HashIndex("VideoGain", UH_SIZE)] = (long)VideoProcAmp_Gain + 1;
    prop_table[HashIndex("FlipHorizontal", UH_SIZE)] = (long)VideoControlFlag_FlipHorizontal + VC_VIDEOSHIFT + 1;
    prop_table[HashIndex("FlipVertical", UH_SIZE)] = (long)VideoControlFlag_FlipVertical + VC_VIDEOSHIFT + 1;
    prop_table[HashIndex("AnalogFormat", UH_SIZE)] = (long)0 + VC_ANALOGSHIFT + 1;
    first = 0;
  }
  long prop = prop_table[HashIndex(attrib, UH_SIZE)];
  if (!prop)
    return 0;
  return prop-1;
}

int imVideoCaptureSetAttribute(imVideoCapture* vc, const char* attrib, double 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, double *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 VIDEOPROC_MAX 10
#define CAMERACONTROL_MAX 7
#define VIDEOCONTROL_MAX 2
#define VIDEODECODER_MAX 1
#define NUM_ATTRIB_MAX (VIDEOPROC_MAX+CAMERACONTROL_MAX+VIDEOCONTROL_MAX+VIDEODECODER_MAX)
  static char* attrib_list[NUM_ATTRIB_MAX];
  static char* all_attrib_list[NUM_ATTRIB_MAX] = {
    "VideoBrightness",
    "VideoContrast",
    "VideoHue",
    "VideoSaturation",
    "VideoSharpness",
    "VideoGamma",
    "VideoColorEnable",
    "VideoWhiteBalance",
    "VideoBacklightCompensation",
    "VideoGain",
    "CameraPanAngle",
    "CameraTiltAngle",
    "CameraRollAngle",
    "CameraLensZoom",
    "CameraExposure",
    "CameraIris",
    "CameraFocus",
    "FlipHorizontal",
    "FlipVertical",
    "AnalogFormat"};

  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 < VIDEOPROC_MAX; i++)
      attrib_list[i] = all_attrib_list[i];
    *num_attrib = 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 < CAMERACONTROL_MAX; i++)
      attrib_list[i+*num_attrib] = all_attrib_list[i+VIDEOPROC_MAX];
    *num_attrib += 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 < VIDEOCONTROL_MAX; i++)
      attrib_list[i+*num_attrib] = all_attrib_list[i+VIDEOPROC_MAX+CAMERACONTROL_MAX];
    *num_attrib += 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 < VIDEODECODER_MAX; i++)
      attrib_list[i+*num_attrib] = all_attrib_list[i+VIDEOPROC_MAX+CAMERACONTROL_MAX+VIDEOCONTROL_MAX];
    *num_attrib += VIDEODECODER_MAX;
    video_decoder->Release();
  }

  return (const char**)attrib_list;
}

/*
Pre-calcultated 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)
*/

