/**@file camera.cpp: Windows capturing device function implementation. */

#include "../OsSupp.h"
#include "camera.h"
#include "../TimeTool.h"

#include <vfwmsgs.h>
#include <Dvdmedia.h>			// VIDEOINFOHEADER2


extern int ComInitialized;	///< This variable is Windows specific from OsSupp.
#define HookCallbacks		///< Use callbacks, when this define is active

#ifndef N_
  #define N_(EnString) EnString
#endif

wxString TranslateVideoMode(const GUID & MediaSubType);
EXTERN_C const GUID MEDIASUBTYPE_A16R16G16B16;
EXTERN_C const GUID MEDIASUBTYPE_A16B16G16R16;
EXTERN_C const GUID MEDIASUBTYPE_A2B10G10R10;
EXTERN_C const GUID MEDIASUBTYPE_A2R10G10B10;
EXTERN_C const GUID MEDIASUBTYPE_I420;
EXTERN_C const GUID MEDIASUBTYPE_NV12;
EXTERN_C const GUID MEDIASUBTYPE_YUV420P;
EXTERN_C const GUID MEDIASUBTYPE_v210;
EXTERN_C const GUID MEDIASUBTYPE_R16G16B16;
EXTERN_C const GUID MEDIASUBTYPE_B16G16R16;
EXTERN_C const GUID MEDIASUBTYPE_Y422;
EXTERN_C const GUID MEDIASUBTYPE_R210;


EXTERN_C const GUID CLSID_KsDataTypeHandlerVideo2;

#ifdef _DEBUG
EXTERN_C const GUID CLSID_ColorSpaceConverter;
#endif


ISampleGrabber *CreateInternalFrameGrabber(IUnknown *pUnkOuter);


//_COM_SMARTPTR_TYPEDEF(IPinPtr, __uuidof(IPin));
//_COM_SMARTPTR_TYPEDEF(IEnumPinsPtr, __uuidof(IEnumPins));



/////////////////////////////////////////////////////

CameraInput::CameraInput(const CameraInfo& ci): 
   Width(0), Height(0), FrameDuration(0), MinFrameDuration(0), MaxFrameDuration(0),
   GraphIsBuilt(false), m_cRef(1), m_MediaSample(NULL)
{
HRESULT hr;

  filter_base = NULL;
  pWxObj = NULL;

	// create the filter graph and necessary interfaces  
  graph.CreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC);
  if (graph==NULL) throw(WinAPIException(E_NOINTERFACE, N_("Failed to create filter graph - CLSID_FilterGraph")));
  IGraphBuilderPtr graphbuilder(graph);		// Another IFace from a same object.
  if (graphbuilder==NULL) throw(WinAPIException(E_NOINTERFACE, N_("Failed to get IGraphBuilder interface for the filter graph")));

	// create the capture graph builder
  hr = capbuilder.CreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC);
  if(FAILED(hr)) capbuilder=NULL;
  // CapBuilder2 is no longer mandatory!
  //if(capbuilder==NULL) throw(WinAPIException(E_NOINTERFACE, N_("Failed to create CaptureGraphBuilder2")));
  //capbuilder=NULL; //!!!!! for testing only
  

	// attach the filter graph to the capture graph builder
  if(capbuilder != NULL)
  {
    hr = capbuilder->SetFiltergraph(graphbuilder);
    if (FAILED(hr)) throw(WinAPIException(hr, N_("Failed to attach filter graph to the capture graph builder")));
  }

  // instantiate the camera filter object  
  hr = ci.moniker->BindToObject(0, 0, IID_IBaseFilter, reinterpret_cast<void**>(&camera_base));
  if(FAILED(hr)) throw(WinAPIException(hr, N_("Failed to instantiate camera filter")));

  // add the camera filter to the filter graph  
  hr = graphbuilder->AddFilter(camera_base, L"Camera");
  if(FAILED(hr)) throw(WinAPIException(hr, N_("Failed to add camera filter to filter graph")));

    // create the sample grabber and necessary interfaces
  grabber = CreateInternalFrameGrabber(NULL);
  // This seems to be deprecated and removed from future releases of Windows:
  //     https://learn.microsoft.com/en-us/windows/win32/directshow/isamplegrabber
  //grabber.CreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC);
  if(grabber==NULL) throw(WinAPIException(E_NOINTERFACE, N_("Failed to create sample grabber")));

   // Hook callbacks
#ifdef HookCallbacks    
  grabber->SetCallback(this,0);
#endif
  
  // add the sample grabber to the filter graph
  grabber_base = grabber;
  if(grabber_base==NULL) throw(WinAPIException(E_NOINTERFACE, N_("Failed to get BaseFilter interface for the sample grabber")));
  hr = graphbuilder->AddFilter(grabber_base, L"Grabber");
  if(FAILED(hr)) throw(WinAPIException(hr, N_("Failed to add sample grabber filter to filter graph")));

  // find the Capture pin on the camera
  if(camera!=NULL) camera=NULL;
  if(capbuilder!=NULL)
  {
    hr = capbuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, camera_base, IID_IAMStreamConfig, reinterpret_cast<void**>(&camera));
    if(FAILED(hr)) throw(WinAPIException(E_NOINTERFACE, N_("Failed to get the IAMStreamConfig the camera output pin")));
  }
  else
  {
    IEnumPins *pEnum = NULL;
    hr = camera_base->EnumPins(&pEnum);
    if(SUCCEEDED(hr))
    {
      IPin *CamPIN = NULL;      
      while(SUCCEEDED(hr) && CamPIN==NULL)
      {
        hr = pEnum->Next(1,&CamPIN,NULL);
        if(hr==S_FALSE) break;			// Did not retrieve as many pins as requested.
        if(SUCCEEDED(hr) && CamPIN!=NULL)
        {
          PIN_DIRECTION PinDir;
          hr = CamPIN->QueryDirection(&PinDir);
          if(FAILED(hr) || PinDir!=PINDIR_OUTPUT)
	    {CamPIN->Release(); CamPIN=NULL;}
          else
          {
            hr = CamPIN->QueryInterface(IID_IAMStreamConfig,reinterpret_cast<void**>(&camera));
            if(FAILED(hr) || camera==NULL)
                {CamPIN->Release(); CamPIN=NULL;}
            else
            {
/*
	      AM_MEDIA_TYPE *pMT = NULL;
              if(SUCCEEDED(camera->GetFormat(&pMT)) && pMT!=NULL)
              {
                if(pMT->majortype != MEDIATYPE_Video)	// Check for video capability.
                {
                  CamPIN->Release(); CamPIN=NULL;
                  camera->Release(); camera=NULL;
                }
		// The caller must release the memory, including the format block. 
                if(pMT->pbFormat) {CoTaskMemFree(pMT->pbFormat);pMT->pbFormat=NULL;}
                CoTaskMemFree(pMT);
              }
*/
            }
          }
        }
      }
      pEnum->Release();
      if(CamPIN)
      {       
        CamPIN->Release();
      }
    }
  }

  // render the graph stream
  //     - removed from here and moved to the method Start();  

  //SelectVideoMode(); don't select video mode just here, select it from application

  // store real camera name, do it as a last action, otherwise leak occurs during exception.
  std::wstring str = ci.GetName();
  IntName = str.c_str();
}


CameraInput::~CameraInput()
{
  pWxObj = NULL;
  ClearImediaSample();
}


/** Start grabbing and build a chain when needed. */
bool CameraInput::Start(void)
{
  ClearImediaSample();  
  if(!BuildGraph()) return false;    

  grabber->SetBufferSamples(TRUE);    
  graph->Run();
  return true;
}


/** Stops grabbing and optionally break a chain inside chart. 
 *  @param[in] StopGraph - optionally break a capture graph. Default value 'false'.
 *  @return Exception _com_error when COM fails. */
void CameraInput::Stop(bool StopGraph)
{
  ClearImediaSample();
  if(!GraphIsBuilt) return;	//if a graph is not built, it make no sense to stop it

  grabber->SetBufferSamples(FALSE);	// No Media sample will be cached after this.
  ClearImediaSample();
  camera_base->Stop();		// Stop videosource first for safety.
  graph->StopWhenReady();	//   otherwise this call might sometimes hang.

  if(StopGraph)
    {
    HRESULT hr;

    GraphIsBuilt=false;

    IFilterGraphPtr ifg(graph);
    hr=ifg->RemoveFilter(grabber_base);		//remove all components from a chart
    hr=ifg->RemoveFilter(camera_base);		//   this breaks a chain as a side effect
    if(filter_base!=NULL)
      hr=ifg->RemoveFilter(filter_base);

    IGraphBuilderPtr graphbuilder(graph);	//add them again and don't build chain
    hr = graphbuilder->AddFilter(camera_base, L"Camera");
    hr = graphbuilder->AddFilter(grabber_base, L"Grabber");
    if(filter_base!=NULL)
      hr = graphbuilder->AddFilter(filter_base, L"Filter");
    }
}


/** Build direct view graph. A graph is fully build only before capturing, otherwise
 *    it blocks device.
 *  @return Exception _com_error when COM fails. */
bool CameraInput::BuildGraph(void)
{
HRESULT hr;

  if(GraphIsBuilt) return true;  

	// This piece of code provides same functionality as "RenderStream".
  {
    IGraphBuilderPtr graphbuilder(graph);     
    IPin *CamPIN = NULL;
    IPin *GrabPIN = NULL;
    IPin *FilterIn = NULL;
    IPin *FilterOut = NULL;
    IEnumPins *pEnum = NULL;    

    if(graphbuilder==NULL)
    {
      hr = E_POINTER;
      goto FinishConnect;
    }

	// Get IPin from proven camera pin.
    if(camera != NULL)
    {
      hr = camera->QueryInterface(IID_IPin,reinterpret_cast<void**>(&CamPIN));
      if(FAILED(hr)) CamPIN=NULL;
    }
    if(CamPIN==NULL)
    {
      hr = camera_base->EnumPins(&pEnum);
      if(FAILED(hr)) goto FinishConnect;
      while(SUCCEEDED(hr) && CamPIN==NULL)
      {
        hr = pEnum->Next(1,&CamPIN,NULL);
        if(hr==S_FALSE) break;
        if(SUCCEEDED(hr))
        {
          PIN_DIRECTION PinDir;
          hr = CamPIN->QueryDirection(&PinDir);
          if(PinDir!=PINDIR_OUTPUT)
	    {CamPIN->Release(); CamPIN=NULL;}
        }
      }
      pEnum->Release();
      if(FAILED(hr) || CamPIN==NULL) goto FinishConnect;
    }
    graphbuilder->Disconnect(CamPIN);		// not needed

    hr = grabber_base->EnumPins(&pEnum);
    if(FAILED(hr)) goto FinishConnect;
    hr = pEnum->Next(1,&GrabPIN,NULL);  
    pEnum->Release();
    if(FAILED(hr)) goto FinishConnect;
    graphbuilder->Disconnect(GrabPIN);		// not needed

    if(filter_base)
    {
      hr = filter_base->EnumPins(&pEnum);
      while(SUCCEEDED(hr) && (FilterIn==NULL || FilterOut==NULL))
      {
        IPin *pPin = NULL;
        hr = pEnum->Next(1,&pPin,NULL);
        if(hr==S_FALSE) break;
        if(SUCCEEDED(hr) && pPin!=NULL)
        {
          PIN_DIRECTION PinDir;	  
          hr = pPin->QueryDirection(&PinDir);
	  if(SUCCEEDED(hr))
          {            
            if(PinDir==PINDIR_INPUT && FilterIn==NULL) {FilterIn=pPin;pPin=NULL;}
            if(PinDir==PINDIR_OUTPUT && FilterOut==NULL) {FilterOut=pPin;pPin=NULL;}
          }
          if(pPin!=NULL) pPin->Release();
        }
      }
    }

    if(FilterIn!=NULL && FilterOut!=NULL)
    {
      hr = graphbuilder->Connect(CamPIN,FilterIn);
      if(SUCCEEDED(hr))
        hr = graphbuilder->Connect(FilterOut,GrabPIN);
    }
    else
      hr = graphbuilder->Connect(CamPIN,GrabPIN);
    //hr = graphbuilder->Render(GrabPIN); - not neccessary

FinishConnect:
    if(CamPIN) CamPIN->Release();
    if(GrabPIN) GrabPIN->Release();
    if(FilterIn) FilterIn->Release();
    if(FilterOut) FilterOut->Release();
  }

// This function sometimes adds unwanted SmartTeeFilter that we do not want. Use it only as a fallback.
// https://learn.microsoft.com/en-us/windows/win32/directshow/using-the-smart-tee-filter
  if(FAILED(hr) && capbuilder!=NULL)
    hr = capbuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, camera_base, filter_base, grabber_base);  

  if(FAILED(hr)) return false;
	  //throw(WinAPIException(hr, "The capture device may already be in use by another application."));

  GraphIsBuilt = true;
return true;
}


// CLSID_MjpegDec; CLSID_DVVideoCodec
void CameraInput::UseFilter(const CLSID & FilterCleId)
{
  if(filter_base==NULL)
    {
    try {
        filter_base.CreateInstance(FilterCleId, NULL, CLSCTX_INPROC);// Create the DV Decoder (CLSID_DVVideoCodec)
        }
    catch(...) {filter_base=NULL;}
    if(filter_base!=NULL)
        {
        if(GraphIsBuilt) Stop(true);
        IGraphBuilderPtr graphbuilder(graph);
        graphbuilder->AddFilter(filter_base, L"Filter");
        }
    } 
}


/** Read currently selected frame rate, minimal and maximal available frame rate. 
 *  @param[in] Specify which value to read, default 0. */
double CameraInput::GetFrameRate(int What)
{
switch(What)
  {
  case 1: if(MinFrameDuration<=0) return 0;
	  return 10000000/(double)MinFrameDuration;
  case 2: if(MaxFrameDuration<=0) return 0;
	  return 10000000/(double)MaxFrameDuration;
  default:if(FrameDuration<=0) return 0;
          return 10000000/(double)FrameDuration;
  }
}


/** Get friendly camera name. */
const wxChar *CameraInput::GetName(void) const
{
  return IntName.c_str();
}


EVideoFormats CameraInput::GetVideoformat(const GUID *MediaSubtype)
{
 if(MediaSubtype != NULL)
 {
    if(*MediaSubtype==MEDIASUBTYPE_AYUV) return vfAYUV;
    if(*MediaSubtype==MEDIASUBTYPE_RGB565) return vfRGB565;
    if(*MediaSubtype==MEDIASUBTYPE_RGB555) return vfRGB555;
    if(*MediaSubtype==MEDIASUBTYPE_NV12) return vfNV12;	// NV12(NV12: YYYYYYYYUVUV =>YUV420SP);
    if(*MediaSubtype==MEDIASUBTYPE_v210) return vfV210;
    if(*MediaSubtype==MEDIASUBTYPE_R210) return vfR210;
    if(*MediaSubtype==MEDIASUBTYPE_A2B10G10R10) return vfA2B10G10R10;
    if(*MediaSubtype==MEDIASUBTYPE_A2R10G10B10) return vfA2R10G10B10;
    if(*MediaSubtype==MEDIASUBTYPE_R16G16B16) return vfR16G16B16;
    if(*MediaSubtype==MEDIASUBTYPE_B16G16R16) return vfB16G16R16;
    if(*MediaSubtype==MEDIASUBTYPE_A16R16G16B16) return vfA16R16G16B16;
    if(*MediaSubtype==MEDIASUBTYPE_A16B16G16R16) return vfA16B16G16R16;
    if(*MediaSubtype==MEDIASUBTYPE_Y422) return vfY422;	// Known as UYVY, Y422 or UYNV: https://wiki.videolan.org/YUV
    if(*MediaSubtype==MEDIASUBTYPE_UYVY) return vfUYVY;
    if(*MediaSubtype==MEDIASUBTYPE_YVYU) return vfYVYU;
    if(*MediaSubtype==MEDIASUBTYPE_YUYV) return vfYUYV;
    if(*MediaSubtype==MEDIASUBTYPE_YUY2) return vfYUYV;
    if(*MediaSubtype==MEDIASUBTYPE_YV12) return vfI420;	// YUV 4:2:0 (I420/J420/YV12)
    if(*MediaSubtype==MEDIASUBTYPE_I420) return vfI420;
    if(*MediaSubtype==MEDIASUBTYPE_YUV420P) return vfI420;
    if(*MediaSubtype==MEDIASUBTYPE_RGB8) return vfRGB8;

    if(*MediaSubtype==MEDIASUBTYPE_MJPG) return vfMJPG;
    if(*MediaSubtype==MEDIASUBTYPE_dvsl) return vfdvsl;
    if(*MediaSubtype==MEDIASUBTYPE_dvsd) return vfdvsd;
    if(*MediaSubtype==MEDIASUBTYPE_dvhd) return vfdvhd;
 }
 return vfUnspecified;
}


/** Scan camera capabilities and select best video mode. 
  * @return Exception _com_error when COM fails. */
void CameraInput::SelectVideoMode(int ReqWidth, int ReqHeight, int ReqDepth, double NewFrameRate)
{
TAudVidConfCaps *PAudVidConfCaps;

  if(camera==NULL) return;
  if(!BuildGraph()) return;
 
  int count = 0;
  int size = 0;
  switch(camera->GetNumberOfCapabilities(&count, &size))
  {
    case S_OK:
    case VFW_E_NOT_CONNECTED: break;
    default: count=0;
  }
  if(count <= 1)	// Provide format unlock, only AV object camera needs this.
  {
    camera->SetFormat(NULL);
    if(FAILED(camera->GetNumberOfCapabilities(&count, &size))) return;
  }
  if(size > 0x10000) return;	// More than 64kiB is suspicious.

  AM_MEDIA_TYPE* mt=NULL;  
  int planes;

  int BestI=0;	
  double qualityI=-1000;
  double quality=-10000;   ///< negative quality means unsatisfied contraint, 0 is full satisfaction, positive denotes bigger resolution

  if(size<sizeof(TAudVidConfCaps)) size=sizeof(TAudVidConfCaps);
  PAudVidConfCaps = (TAudVidConfCaps *)CoTaskMemAlloc(size);
  if(PAudVidConfCaps==NULL) return;

  if(count<=0) count = 1;		// Do at least one iteration.
  for(int i=0; i<count; i++)		// traverse all available resolutions
  {
    mt = NULL;
    if(FAILED(camera->GetStreamCaps(i, &mt, (BYTE*)PAudVidConfCaps)))
        continue;
    if(mt==NULL) continue;   

    const BITMAPINFOHEADER *pBmi = NULL;
    if(mt->formattype == FORMAT_VideoInfo)
    {
      VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)mt->pbFormat;
      if(pVIH) pBmi=&pVIH->bmiHeader;
    } else if(mt->formattype == FORMAT_VideoInfo2)
    {
      VIDEOINFOHEADER2 *pVIH2 = (VIDEOINFOHEADER2*)mt->pbFormat;
      if(pVIH2) pBmi=&pVIH2->bmiHeader;
    }
    if(pBmi==NULL) continue;

    planes = (8*mt->lSampleSize)/labs(pBmi->biWidth*pBmi->biHeight);
    if(planes<8)
    {
      planes = pBmi->biPlanes * pBmi->biBitCount;
    }

    if(ReqWidth>0 && ReqHeight!=0)
    {
      if(labs(pBmi->biHeight)>=labs(ReqHeight))
      {
        if(pBmi->biWidth>=ReqWidth)
        {
          quality = labs(labs(pBmi->biHeight)-labs(ReqHeight)) +
                    labs(labs(pBmi->biWidth)-labs(ReqWidth));
          if(planes!=ReqDepth)
	    switch(planes)
	    {
	         case 32: quality+=11; break;
                 case 24: quality+=10; break;
                 case 12: quality+=5; break;
                 case 8: quality+=1; break;
                 default: quality+=30;		//unknown depth
	    }
          }
          else
             quality = pBmi->biWidth - ReqWidth;   //not satisfied width
        }
        else
        {
          if(pBmi->biWidth>=ReqWidth)
              quality = labs(labs(pBmi->biHeight)-labs(ReqHeight));  //not satisfied height
          else
              quality = labs(labs(pBmi->biHeight)-labs(ReqHeight)) +
                        labs(labs(pBmi->biWidth)-ReqWidth);   //not satisfied both
        }

        wxString str = TranslateVideoMode(mt->subtype);
        if(!str.empty())
        {
          if(str[0]=='{') quality+=50;		//unknown format
          if(str[0]=='!') quality+=50;		//unsupportedn format
        }
    
        //if(mt->formattype==CLSID_KsDataTypeHandlerVideo2) quality+=80;    
      }
         

      if((quality>=0 && (quality<qualityI || qualityI<0) )  ||
         (quality<0 && quality>qualityI) )
      {
	qualityI=quality;
	BestI=i;
      }

      if(mt->pbFormat != NULL)		// There was a leak!
      {
	CoTaskMemFree(mt->pbFormat);
	mt->pbFormat = NULL;
      }
      CoTaskMemFree(mt); mt=NULL;
    }

	//setup a best format found;   
   if(SUCCEEDED(camera->GetStreamCaps(BestI, &mt, (BYTE*)PAudVidConfCaps)))
     if(mt!=NULL)
     {
       BITMAPINFOHEADER *pBmi = NULL;
       if(mt->formattype == FORMAT_VideoInfo)
       {
         VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)mt->pbFormat;
         if(pVIH)
         {
           FrameDuration = pVIH->AvgTimePerFrame;
           if(NewFrameRate>0) pVIH->AvgTimePerFrame = (REFERENCE_TIME)(10000000/NewFrameRate);
           pBmi = &pVIH->bmiHeader;
         }
       }
       else if(mt->formattype == FORMAT_VideoInfo2)
       {
         VIDEOINFOHEADER2 *pVIH2 = (VIDEOINFOHEADER2*)mt->pbFormat;
         if(pVIH2)
         {
           FrameDuration = pVIH2->AvgTimePerFrame;        
           if(NewFrameRate>0) pVIH2->AvgTimePerFrame = (REFERENCE_TIME)(10000000/NewFrameRate);
           pBmi = &pVIH2->bmiHeader;
         }
       }

       MinFrameDuration = PAudVidConfCaps->v.MinFrameInterval;
       MaxFrameDuration = PAudVidConfCaps->v.MaxFrameInterval;
       VideoFormat = vfUnspecified;

       camera->SetFormat(mt);

       VideoFormat = GetVideoformat(&mt->subtype);
       
       if(VideoFormat == vfRGB8)
       {
	 if(mt->pbFormat!=NULL)
	 {
           if(mt->cbFormat>=sizeof(VIDEOINFO) && mt->formattype==FORMAT_VideoInfo)
           {
	     VIDEOINFO *pvi = (VIDEOINFO*)mt->pbFormat;
             if(pvi->bmiHeader.biSize == sizeof(BITMAPINFOHEADER))	// WARNING!!! DO NOT use this structure unless you are sure that the BITMAPINFOHEADER
             {								// has a normal biSize == sizeof(BITMAPINFOHEADER)!
	       if(pCameraPalette==NULL) pCameraPalette=(TCameraPalette*)malloc(sizeof(TCameraPalette)*256);
	       if(pCameraPalette != NULL)
	       {
	         for(int i=0; i<256; i++)
	         {
	           pCameraPalette[i].R = pvi->TrueColorInfo.bmiColors[i].rgbRed;
	           pCameraPalette[i].G = pvi->TrueColorInfo.bmiColors[i].rgbGreen;
	           pCameraPalette[i].B = pvi->TrueColorInfo.bmiColors[i].rgbBlue;
                 }
	       }
             }
	   }
/*
           else if(mt->cbFormat>=sizeof(VIDEOINFO2) && mt->formattype==FORMAT_VideoInfo2)	// not known VIDEOINFO2 structure
           {
	     VIDEOINFO2 *pvi = (VIDEOINFO2*)mt->pbFormat;
	     if(pCameraPalette==NULL) pCameraPalette=(TCameraPalette*)malloc(sizeof(TCameraPalette)*256);
	     if(pCameraPalette != NULL)
	     {
	       for(int i=0; i<256; i++)
	       {
	         pCameraPalette[i].R = pvi->TrueColorInfo.bmiColors[i].rgbRed;
	         pCameraPalette[i].G = pvi->TrueColorInfo.bmiColors[i].rgbGreen;
	         pCameraPalette[i].B = pvi->TrueColorInfo.bmiColors[i].rgbBlue;
	       }
             }
	   }
*/
	 }	    
       }

     if(VideoFormat==vfdvsl || VideoFormat==vfdvsd || VideoFormat==vfdvhd 
         //mt->subtype==MEDIASUBTYPE_dv25 || mt->subtype==MEDIASUBTYPE_dv50 ||
         //mt->subtype==MEDIASUBTYPE_dvh1   //proffessional types, CLSID not found.
        )
	{  
	UseFilter(CLSID_DVVideoCodec);
        //if(filter_base==NULL)   emit some error
        mt->subtype = MEDIASUBTYPE_RGB24;
	grabber->SetMediaType(mt);
	}
     else if(VideoFormat == vfMJPG)
       {
       UseFilter(CLSID_MjpegDec);
       //if(filter_base==NULL)   emit some error
       mt->subtype = MEDIASUBTYPE_RGB24;
       grabber->SetMediaType(mt);
       }
     else
     {
       if(filter_base != NULL)		// Filter should not be allocated.
       {
         IFilterGraphPtr ifg(graph);
         if(ifg!=NULL) ifg->RemoveFilter(filter_base);
	 filter_base=NULL;
       }
     }

#ifdef _DEBUG /////////////////
/*
     {
       UseFilter(CLSID_ColorSpaceConverter);
       mt->subtype = MEDIASUBTYPE_RGB24;
       grabber->SetMediaType(mt);
     }
*/
#endif /////////////////

     if(filter_base==NULL) grabber->SetMediaType(NULL);		// Reset forced media type.

     if(pBmi)
     {
       Width = pBmi->biWidth;
       Height = pBmi->biHeight;
     }
     else
     {
       Width = PAudVidConfCaps->v.InputSize.cx;
       Height = PAudVidConfCaps->v.InputSize.cy;
     }
     if(filter_base==NULL)
     {
       Planes = (8*mt->lSampleSize)/labs(Width*Height);
       if(Planes<8)
       {
         if(pBmi) Planes = pBmi->biPlanes*pBmi->biBitCount;
       }
     }
     else		// Grabber is ussing a different video type than camera.
     {   
       if(mt->subtype==MEDIASUBTYPE_RGB24) Planes=24;
       else if(mt->subtype==MEDIASUBTYPE_RGB32) Planes=32;
       else Planes=24;
     }

	/* Read a Frame rate actually set. */
     if(NewFrameRate>0)
     {
       if(mt->pbFormat != NULL)		// There was a leak!
       {
	  CoTaskMemFree(mt->pbFormat);
	  mt->pbFormat = NULL;
       }
       CoTaskMemFree(mt); mt=NULL;

       if(SUCCEEDED(camera->GetFormat(&mt)))
         if(mt!=NULL)
         {
	   if(mt->formattype == FORMAT_VideoInfo)
           {
             const VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)mt->pbFormat;
             FrameDuration = pVIH->AvgTimePerFrame;         
           }
	   else if(mt->formattype == FORMAT_VideoInfo2)
           {
             const VIDEOINFOHEADER2 *pVIH2 = (VIDEOINFOHEADER2*)mt->pbFormat;
             FrameDuration = pVIH2->AvgTimePerFrame;         
           }
         }
     }
  }

  if(PAudVidConfCaps) {CoTaskMemFree(PAudVidConfCaps);PAudVidConfCaps=NULL;}
  if(mt) 
  {
    if(mt->pbFormat != NULL)		// There was a leak!
    {
      CoTaskMemFree(mt->pbFormat);
      mt->pbFormat = NULL;
    }
    CoTaskMemFree(mt);mt=NULL;
  }
}


/** Display a generic property page that is camera dependent. */
void CameraInput::ShowPropertyPage(size_t hOwner)
{
    // Get the list of property pages to display
  ISpecifyPropertyPages* piPages;

  if(camera_base==NULL) return;
  HRESULT hResult = camera_base->QueryInterface(__uuidof(piPages), reinterpret_cast<void**>(&piPages));
  if(SUCCEEDED(hResult))
  {
    CAUUID Pages;
    hResult = piPages->GetPages(&Pages);

    if(SUCCEEDED(hResult) && Pages.cElems > 0)
    {
      IUnknown *pIU = (IUnknown*)camera_base;
      hResult = OleCreatePropertyFrame(
                            (HWND)hOwner,	//m_hWnd,
                            0,
                            0,
                            L"Camera Properties",
                            1,
                            &pIU,
                            Pages.cElems,
                            Pages.pElems,
                            LANG_USER_DEFAULT,
                            0,
                            NULL);

      CoTaskMemFree(Pages.pElems);
    }
    piPages->Release();
  }
}


void CameraInput::ProcessMediaType(AM_MEDIA_TYPE *mediatype)
{
  if(mediatype==NULL) return;

  if(mediatype->pbFormat)
  {
    const BITMAPINFOHEADER *pBmi = NULL;
    if(mediatype->formattype == FORMAT_VideoInfo)
    {
      VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)mediatype->pbFormat;
      if(pVIH)
      {
        pBmi = &pVIH->bmiHeader;
      }
    }
    else if(mediatype->formattype == FORMAT_VideoInfo2)
    {
      VIDEOINFOHEADER2 *pVIH2 = (VIDEOINFOHEADER2*)mediatype->pbFormat;
      if(pVIH2)
      {
        pBmi = &pVIH2->bmiHeader;
      }      
    }
    if(pBmi)
    {
      Height = pBmi->biHeight;
      Width = pBmi->biWidth;
    }
    CoTaskMemFree(mediatype->pbFormat);	// The caller must free the format block of the media type. 
  }
  CoTaskMemFree(mediatype);
}


/** This function gets a new reference to a cached IMediaSample and increments reference counter.
 * When no IMediaSample is cached, NULL is returned. It is fully thread safe.
 * @param[out]  tmpMediaSample Reference to processed MediaSample, caller must release it. Warning
 *		reference release will invalidate datablock returned!
 * @param[out]  Size	Size of datablock returned.
 * @return	Data block of media sample returned.  */
void *CameraInput::PeekImediaSample(int *Size, IMediaSample **tmpMediaSample)
{
  if(m_MediaSample==NULL && grabber!=NULL)
  {
    *tmpMediaSample = NULL;
    grabber->GetCurrentSample(tmpMediaSample);		// 1 reference is granted
    if(*tmpMediaSample!=NULL) 
    {
      (*tmpMediaSample)->AddRef();			// add second reference
      m_MediaSample = *tmpMediaSample;		// Referrece should be added BEFORE assigned to m_MediaSample.
    }
  }
  else
  {
    *tmpMediaSample = (IMediaSample*)m_MediaSample;	// m_MediaSample could be released at any time
    if(*tmpMediaSample!=NULL) (*tmpMediaSample)->AddRef();  // Here could be small asynchronous problem.	
  }
		//	*tmpMediaSample is safe to use, it has additional reference.

  if(*tmpMediaSample==NULL)
  {
    if(Size!=NULL) *Size=0;
    return NULL;
  }

  if(Size!=NULL)
      *Size = (*tmpMediaSample)->GetActualDataLength();

  BYTE *ptr = NULL;
  if(FAILED((*tmpMediaSample)->GetPointer(&ptr)))
  {
    return NULL;
  }

  AM_MEDIA_TYPE *mediatype = NULL;
  if(SUCCEEDED((*tmpMediaSample)->GetMediaType(&mediatype)))
  {
    ProcessMediaType(mediatype);
  }

  return ptr;
}


void CameraInput::ClearImediaSample(void)
{				 // Create a local copy first before release.
  IMediaSample * const tmpMediaSample = (IMediaSample*)InterlockedExchangePointer((PVOID*)&m_MediaSample,NULL);
  if(tmpMediaSample) tmpMediaSample->Release();				// Now release a local copy.
}


//////////// Interface IUnknown - procedures////////////
ULONG CameraInput::AddRef()
{
  return(InterlockedIncrement(&m_cRef));
}


ULONG CameraInput::Release()
{
 if(InterlockedDecrement(&m_cRef)!=0)
 		return(m_cRef);
// delete(this); - this object should not be deleted from COMs
 m_cRef=0;
 return(0);
}


HRESULT CameraInput::QueryInterface(REFIID riid, void **ppv)
{
  if(ppv==NULL) return E_POINTER;
  if(riid==IID_IUnknown) 
  {
	*ppv=(IUnknown*)(CameraInput*)(this);
  }
  else if(riid==IID_ISampleGrabberCB)
  {
	*ppv=(CameraInput*)this;
  }
  else 	{
	*ppv=NULL;
	return E_NOINTERFACE;
	}

  AddRef();

  return(S_OK);
}



// Interface ISampleGrabberCB - procedure prototypes
HRESULT __stdcall CameraInput::BufferCB(double /*SampleTime*/, BYTE * /*pBuffer*/, long /*BufferLen*/)
{
  return E_NOTIMPL;
}


HRESULT __stdcall CameraInput::SampleCB(double SampleTime, IMediaSample *pSample)
{
	// Notification about format change when SampleTime==-1 and pSample is NULL.
  if(SampleTime==-1 && pSample==NULL)
  {
    if(filter_base!=NULL)
      return E_NOTIMPL;			// We cannot change format when intermetiate filter is inside.

    AM_MEDIA_TYPE *pType = (AM_MEDIA_TYPE*)CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE));
    if(pType==NULL) return E_OUTOFMEMORY;
    memset(pType,0,sizeof(AM_MEDIA_TYPE));
    HRESULT hr =  grabber->GetConnectedMediaType(pType);
    if(FAILED(hr))
    {
      CoTaskMemFree(pType);
      return hr;
    }
    if(pType->subtype==MEDIASUBTYPE_RGB8 ||
       pType->subtype==MEDIASUBTYPE_dvsl || pType->subtype==MEDIASUBTYPE_dvsd || pType->subtype==MEDIASUBTYPE_dvhd ||
       pType->subtype==MEDIASUBTYPE_MJPG)
    {
      CoTaskMemFree(pType);
      return E_NOTIMPL;
    }

    VideoFormat = GetVideoformat(&pType->subtype);

    ProcessMediaType(pType);
    return S_OK;
  }

  TickCount = GetTickCount_ms();
  FrameCounter++;

#ifdef HookCallbacks
  try 
    {FireSampleCB(pWxObj);}
  catch(...)
    {}
#endif

  return S_OK;
}



///////////////////////////////////////////////////////

/** Empty camera placeholder for not working cameras. */
class NoCamera: public CameraDevice
{
public: 
  NoCamera() {AssignStr();}
  NoCamera(const wxChar *Message) {AssignStr();str+=' ';str+=Message;}
  //NoCamera(const char *Message) {AssignStr();str+=' ';str+=Message;}

  virtual const wxChar *GetName(void) const {return str.c_str();}

protected:
  void AssignStr(void) {str=N_("NOT WORKING!");}
  wxString str;
};


///////////////////////////////////////////////////////


/** Class factory for Logitech Quick Cam. */
class LogitechQCamManagerDriver: public BazeCameraDriver
{
protected:  
  std::vector<CameraInfo> camerainfo;   ///< Video input device monikers, used for drivers object build
  std::vector<CameraDevice *>drivers;   ///< List of all active driver objects

  void ScanForDevices(void);		///< Get device list
  void Erase(void);			///< Erase all devices
  bool DCOMsInit;			///< DCOM's initialisation flag

public:
  virtual ~LogitechQCamManagerDriver();
  LogitechQCamManagerDriver(void): DCOMsInit(false) {};

  virtual const wxChar *GetName(void) const {return N_("Camera: ");}
  virtual CameraDevice *GetDriver(int Index, const char *Name=NULL);
};


/** Deallocate all drivers from destructor and optionally shut down DCOMs */
LogitechQCamManagerDriver::~LogitechQCamManagerDriver()
{  
  Erase();
  if(DCOMsInit)
    {
    DoneDCOM();
    DCOMsInit=false;
    }
}


/** Get working object of camera driver on n'th position. 
      - no exception should be passed during normal behaviour.
 *  @param[in] Index index of camera driver
 *  @param[in] Name  Name of driver, currently unused
 *  @return CameraDevice object on given index
	    NoCamera     Placeholder object when camera exists, but don't work
	    NULL         Index is out of camera list, or camera list is empty.
      - no exception should be passed during normal behaviour.  */
CameraDevice *LogitechQCamManagerDriver::GetDriver(int Index, const char *Name)
{
  if(Index==ERASE_ALL)
    {
    Erase();
    return NULL;
    }
  if(camerainfo.size()<=0) 
    {
    try {ScanForDevices();}	//no device has been found - find any now
    catch(...) {return(NULL);}
    }

  if((int)camerainfo.size()<=Index) return NULL;

  if(Index>=(int)drivers.size())
    drivers.resize(Index+1);  

  if(drivers[Index]!=NULL) return drivers[Index];
  
  try {
      CameraInput *CDx;
      CDx = new CameraInput(camerainfo[Index]);
      drivers[Index] = CDx;
      }
  catch(WinAPIException &ex)
      {drivers[Index] = new NoCamera(ex.what());}
  catch(...) 
      {drivers[Index] = new NoCamera();}	// when constructor raises exception, create camera placeholder
  
  return drivers[Index];
}


/** Cleanup whole list of cameras and delete all camera objects. */
void LogitechQCamManagerDriver::Erase(void)
{
  for(std::vector<CameraDevice *>::iterator i=drivers.begin(); i!=drivers.end(); ++i)
    {
    if(*i)
      {
      delete *i;	//erase cached driver
      *i = NULL;
      }      
    } 
  drivers.clear();
  camerainfo.clear();
}


/** Get a list of all cameras available. 
   @return WinAPIException on failure. */
void LogitechQCamManagerDriver::ScanForDevices(void)
{
HRESULT hr;

  if(!DCOMsInit) 
    {
    InitDCOM();
    DCOMsInit=true;
    }

	// destroy the existing device entries
  camerainfo.clear();

  // create the system device enumerator
  _COM_SMARTPTR_TYPEDEF(ICreateDevEnum, __uuidof(ICreateDevEnum));
  try
    {
    ICreateDevEnumPtr devenum(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC);
    if (devenum==NULL) throw(WinAPIException(E_NOINTERFACE, N_("Failed to create system enumerator")));

      // create an enumerator for the video capture devices
    IEnumMonikerPtr classenum;
    hr = devenum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &classenum, 0);
    if (FAILED(hr)) throw(WinAPIException(hr, N_("Failed to create video input class enumerator")));

      // return if there are no enumerators - this means no camera devices
    if (classenum == NULL) return;

      // enumerate the video input device monikers.
    IMonikerPtr moniker;
    ULONG numfetched;
    while(classenum->Next(1, &moniker, &numfetched) == S_OK)
      {
      IPropertyBagPtr pbag;
      hr = moniker->BindToStorage(0, 0, IID_IPropertyBag, reinterpret_cast<void**>(&pbag));
      if (FAILED(hr)) continue;

      // retrieve the camera filter's friendly name
      _variant_t name;
      hr = pbag->Read(L"FriendlyName", &name, 0);
      if (SUCCEEDED(hr))
        {
        camerainfo.push_back(CameraInfo(_bstr_t(name), moniker));
        }
      }
   }
 catch(...) 
   {
   throw(WinAPIException(E_NOINTERFACE, N_("Occured exception during camera enumeration")));
   }  

}


/** static instance that generates drivers
 *  Drivers are not generated from constructor, they are created during first 
 *  call 'ScanForDevices()'. */
LogitechQCamManagerDriver LogitechQCam;	

