// WebcamDirectShowDlg.cpp : 
// *** created by Orgler electronics "Bozen South Tyrol Italy" *** 

#include "stdafx.h"
#include "WebcamDirectShow.h"
#include "DlgWebcam.h"

#include "CaptureGraph.h"
#include <streams.h>

#pragma comment(lib, "strmiids")

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

CString strInfo[20];   // Infos into our Listbox
int iIndex=0;


HWND ghwndApp			=0; 

void TearDownGraph(void);
BOOL BuildPreviewGraph();
void EnumerateAllDevices();
void ChooseDevices(TCHAR *szVideo,IMoniker *pmVideo );
void StartThisDevice(IMoniker *pmVideo);
BOOL InitCapFilters();
void FreeCapFilters();
BOOL StopPreview();
BOOL StartPreview();

struct _capstuff
{
    IMyCaptureGraphBuilder	*pBuilder;
    IVideoWindow			*pVW;
    IMediaEventEx			*pME;
    IAMVideoCompression		*pVC;
    IAMStreamConfig			*pVSC;      
    IBaseFilter				*pRender;
    IBaseFilter				*pVCap; 
    IGraphBuilder			*pFg;
    IMoniker				*rgpmVideoMenu[5];
    IMoniker				*pmVideo;
    BOOL fCaptureGraphBuilt;
    BOOL fPreviewGraphBuilt;
    BOOL fCapturing;
    BOOL fPreviewing;
    bool fDeviceMenuPopulated;
    WCHAR wachFriendlyName[120];
    double FrameRate;
	BOOL fUseFrameRate;
    BOOL fPreviewFaked;     
    int iNumVCapDevices;    // number of devices 
} gx;

//========================== utility =====================================
void IMonRelease(IMoniker *&pm)
{
    if(pm)
    {
        pm->Release();
        pm = 0;
    }
}
//=========================================================================

CWebcamDirectShowDlg::CWebcamDirectShowDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CWebcamDirectShowDlg::IDD, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CWebcamDirectShowDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_LIST_DEVICES, m_ctrlListDevices);
}

BEGIN_MESSAGE_MAP(CWebcamDirectShowDlg, CDialog)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	//}}AFX_MSG_MAP
	ON_BN_CLICKED(IDC_BUT_SELECT_SOURCE, &CWebcamDirectShowDlg::OnBnClickedButSelectSource)
END_MESSAGE_MAP()



BOOL CWebcamDirectShowDlg::OnInitDialog()
{
	CDialog::OnInitDialog();
	SetIcon(m_hIcon, TRUE);			
	SetIcon(m_hIcon, FALSE);

	//=============================================
	ZeroMemory(gx.rgpmVideoMenu, sizeof(gx.rgpmVideoMenu));
	EnumerateAllDevices();
	m_ctrlListDevices.ResetContent();
	int ii=0;
	while(ii < iIndex)	m_ctrlListDevices.AddString(strInfo[ii++]);

	//=============================================
	ghwndApp = this->m_hWnd;
	gx.FrameRate = 15.0;
	gx.pmVideo   = 0;

	if(iIndex==1) 
		StartThisDevice(gx.rgpmVideoMenu[0]);	

	return TRUE;
 }


void CWebcamDirectShowDlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); 

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
	}
}

HCURSOR CWebcamDirectShowDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}


//=======================================================================
void EnumerateAllDevices()
{
UINT    uIndex = 0;
HRESULT hr;

    if(gx.fDeviceMenuPopulated)  return;
    gx.fDeviceMenuPopulated = true;
    gx.iNumVCapDevices      = 0;
   
     for(int i = 0; i < NUMELMS(gx.rgpmVideoMenu); i++)
						IMonRelease(gx.rgpmVideoMenu[i]);
    
	hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);

    ICreateDevEnum *pDevEnum=0;
    hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,IID_ICreateDevEnum, (void**)&pDevEnum);
    if(hr != NOERROR)
    {
       strInfo[iIndex++].Format(TEXT("Error Creating Device Enumerator"));
       return;
    }

    IEnumMoniker *pEnum=0;
    hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnum, 0);
    if(hr != NOERROR)
    {
        strInfo[iIndex++].Format(TEXT("!!!  No webcam found !!!"));
        goto ENUM_EXIT;
    }

    
    pEnum->Reset();
    ULONG cFetched;
    IMoniker *pM;

    while(1)
    {
		hr = pEnum->Next(1, &pM, &cFetched);
		if(hr != S_OK) goto ENUM_EXIT;

        IPropertyBag *pBag=0;

        hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);
        if(SUCCEEDED(hr))
        {
            VARIANT var;
            var.vt = VT_BSTR;
            hr = pBag->Read(L"FriendlyName", &var, NULL);
            if(hr == NOERROR)
            {
 				strInfo[iIndex++].Format("%S", var.bstrVal);
                SysFreeString(var.bstrVal);

                ASSERT(gx.rgpmVideoMenu[uIndex] == 0);
                gx.rgpmVideoMenu[uIndex]        = pM;
                pM->AddRef();
            }
            pBag->Release();
        }
        pM->Release();
        uIndex++;
    }
    pEnum->Release();

	gx.iNumVCapDevices = uIndex;

ENUM_EXIT:
	pDevEnum->Release();
}

//========================== STEP_04 =============================================


void StartThisDevice(IMoniker *pmVideo)  
{
     if(gx.pmVideo != pmVideo)
    {
        if(pmVideo)
        {
            pmVideo->AddRef();
        }

        IMonRelease(gx.pmVideo);
 
        gx.pmVideo = pmVideo;

       if(gx.fPreviewing)
            StopPreview();

        if(gx.fCaptureGraphBuilt || gx.fPreviewGraphBuilt)
            TearDownGraph();

		FreeCapFilters();

        InitCapFilters();

        BuildPreviewGraph();
        StartPreview();                  
    }
}



BOOL InitCapFilters()
{
    HRESULT hr=S_OK;

    if(gx.pBuilder)    return TRUE;

	//====================== STEP_05 ==============================

    gx.pBuilder = new IMyCaptureGraphBuilder();
    if(gx.pBuilder == NULL )
    {
		strInfo[iIndex++].Format("Cannot instantiate graph builder");
        return FALSE;
    }

     gx.pVCap = NULL;
	 int ii,xx=0;
	 CString strFriendlyName;

	
    if(gx.pmVideo != 0)
    {
        IPropertyBag *pBag;
        gx.wachFriendlyName[0] = 0;

        hr = gx.pmVideo->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);
        if(SUCCEEDED(hr))
        {
            VARIANT var;
            var.vt = VT_BSTR;

            hr = pBag->Read(L"FriendlyName", &var, NULL);
            if(hr == NOERROR)
            {
             strFriendlyName.Format("%s",var.bstrVal);
			 ii = strFriendlyName.GetLength();
			 while(xx < ii) {gx.wachFriendlyName[xx]= (WCHAR) strFriendlyName.GetAt(xx); xx++;}
             SysFreeString(var.bstrVal);
            }
            pBag->Release();
        }
        gx.pmVideo->BindToObject(0, 0, IID_IBaseFilter, (void**)&gx.pVCap);
    }

    if(gx.pVCap == NULL)
    {
     strInfo[iIndex++].Format(TEXT("Error %x: Cannot create video capture filter"), hr);
     goto INIT_FILTERS_FAIL;
    }


  
    if(gx.pFg)     return TRUE;

    hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC,IID_IGraphBuilder, (LPVOID *)&gx.pFg);

    hr = gx.pBuilder->SetFiltergraph(gx.pFg);
    if(hr != NOERROR)
    {
    strInfo[iIndex++].Format(TEXT("Cannot give graph to builder"));
    goto INIT_FILTERS_FAIL;
    }

    // Add the video capture filter to the graph with its friendly name
    hr = gx.pFg->AddFilter(gx.pVCap, gx.wachFriendlyName);
    if(hr != NOERROR)
    {
    strInfo[iIndex++].Format(TEXT("Error %x: Cannot add vidcap to filtergraph"), hr);
    goto INIT_FILTERS_FAIL;
    }

       
    hr = gx.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,&MEDIATYPE_Video, gx.pVCap,
                                          IID_IAMStreamConfig, (void **)&gx.pVSC);
  	if(hr == S_OK)  return TRUE;


INIT_FILTERS_FAIL:
    FreeCapFilters();
    return FALSE;
}

void FreeCapFilters()
{
  	if(gx.pFg) gx.pFg->Release();	gx.pFg = 0;

    if( gx.pBuilder )
    {
        delete gx.pBuilder;
        gx.pBuilder = NULL;
    }

	
    if(gx.pVCap) gx.pVCap->Release();  gx.pVCap = 0;
    if(gx.pVSC)  gx.pVSC->Release();   gx.pVSC  = 0;
    if(gx.pVC)   gx.pVC->Release();    gx.pVC   = 0;
}


void TearDownGraph()
{
  	if(gx.pRender) gx.pRender->Release(); gx.pRender = 0;
    if(gx.pME) gx.pME->Release();	gx.pME = 0;

    if(gx.pVW)
    {
        gx.pVW->put_Owner(NULL);			// stop drawing in our window
        gx.pVW->put_Visible(OAFALSE);
        gx.pVW->Release();
        gx.pVW = NULL;
    }

    if(gx.pVCap)
        gx.pBuilder->ReleaseFilters();

    gx.fCaptureGraphBuilt = FALSE;
    gx.fPreviewGraphBuilt = FALSE;
}



BOOL BuildPreviewGraph()
{
    int cy, cyBorder;
    HRESULT hr;
 			
            // maybe it's DV?
            hr = gx.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW,&MEDIATYPE_Video, gx.pVCap, NULL, NULL);
            if(hr == VFW_S_NOPREVIEWPIN)
            {
                // preview was faked up for us using the (only) capture pin
                gx.fPreviewFaked = TRUE;
            }
            else if(hr != S_OK)
            {
                strInfo[iIndex++].Format(TEXT("!!! This graph cannot preview !!!"));
                gx.fPreviewGraphBuilt = FALSE;
                return FALSE;
            }


  
    hr = gx.pFg->QueryInterface(IID_IVideoWindow, (void **)&gx.pVW);
    if(hr != NOERROR)
    {
        strInfo[iIndex++].Format(TEXT("!!! This graph cannot preview properly !!!"));
    }
    else
    {
        RECT rc;
        gx.pVW->put_Owner((OAHWND)ghwndApp);    // We own the window now
        gx.pVW->put_WindowStyle(WS_CHILD);      // you are now a child

        // give the preview window all our space but where the status bar is
        GetClientRect(ghwndApp, &rc);
        cyBorder = GetSystemMetrics(SM_CYBORDER);
        cy = 150 + cyBorder;
        rc.bottom -= cy;

        gx.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); // be this big
        gx.pVW->put_Visible(OATRUE);
    }


    gx.fPreviewGraphBuilt = TRUE;
    return TRUE;
}



BOOL StartPreview()
{
    if(gx.fPreviewing)
        return TRUE;

    if(!gx.fPreviewGraphBuilt)
        return FALSE;

    // run the graph
    IMediaControl *pMC = NULL;
    HRESULT hr = gx.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC);
    if(SUCCEEDED(hr))
    {
        hr = pMC->Run();
        if(FAILED(hr))
        {
            // stop parts that ran
            pMC->Stop();
        }
        pMC->Release();
    }
    if(FAILED(hr))
    {
        strInfo[iIndex++].Format(TEXT("Error %x: Cannot run preview graph"), hr);
        return FALSE;
    }

    gx.fPreviewing = TRUE;
    return TRUE;
}



BOOL StopPreview()
{
    if(!gx.fPreviewing)
		return FALSE;
 
    // stop the graph
    IMediaControl *pMC = NULL;
    HRESULT hr = gx.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC);
    if(SUCCEEDED(hr))
    {
        hr = pMC->Stop();
        pMC->Release();
    }
    if(FAILED(hr))
    {
        strInfo[iIndex++].Format(TEXT("Error %x: Cannot stop preview graph"), hr);
        return FALSE;
    }

    gx.fPreviewing = FALSE;
    return TRUE;
}


void CWebcamDirectShowDlg::OnBnClickedButSelectSource()
{
  int ii = m_ctrlListDevices.GetCurSel();

   StartThisDevice(gx.rgpmVideoMenu[ii]);	
}
