//------------------------------------------------------------------------------
// File: AMCap.cpp
//
// Desc: Audio/Video Capture sample for DirectShow
//
// Copyright (c) 1993 - 2000, Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------

#include <windows.h>
#include <dbt.h>
#include <streams.h>
#include <mmreg.h>
#include <msacm.h>
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#include <commdlg.h>
#include "stdafx.h"
#include "amcap.h"
#include "status.h"
#include "crossbar.h"

// you can never have too many parentheses!
#define ABS(x) (((x) > 0) ? (x) : -(x))

//------------------------------------------------------------------------------
// Global data
//------------------------------------------------------------------------------
HINSTANCE ghInstApp;
HACCEL ghAccel;
HFONT  ghfontApp;
TEXTMETRIC gtm;
TCHAR gszAppName[]=TEXT("AMCAP");
HWND ghwndApp, ghwndStatus;
HDEVNOTIFY ghDevNotify;
PUnregisterDeviceNotification gpUnregisterDeviceNotification;
PRegisterDeviceNotification gpRegisterDeviceNotification;
struct _capstuff {
    TCHAR szCaptureFile[_MAX_PATH];
    WORD wCapFileSize;  // size in Meg
    ICaptureGraphBuilder2 *pBuilder;
    IVideoWindow *pVW;
    IMediaEventEx *pME;
    IAMDroppedFrames *pDF;
    IAMVideoCompression *pVC;
    IAMVfwCaptureDialogs *pDlg;
    IAMStreamConfig *pASC;      // for audio cap
    IAMStreamConfig *pVSC;      // for video cap
    IBaseFilter *pRender;
    IBaseFilter *pVCap, *pACap;
    IGraphBuilder *pFg;
    IFileSinkFilter *pSink;
    IConfigAviMux *pConfigAviMux;
    int  iMasterStream;
    BOOL fCaptureGraphBuilt;
    BOOL fPreviewGraphBuilt;
    BOOL fCapturing;
    BOOL fPreviewing;
    BOOL fCapAudio;
    BOOL fCapCC;
    BOOL fCCAvail;
    BOOL fCapAudioIsRelevant;
    bool fDeviceMenuPopulated;
    IMoniker *rgpmVideoMenu[10];
    IMoniker *rgpmAudioMenu[10];
    IMoniker *pmVideo;
    IMoniker *pmAudio;
    double FrameRate;
    BOOL fWantPreview;
    long lCapStartTime;
    long lCapStopTime;
    WCHAR wachFriendlyName[120];
    BOOL fUseTimeLimit;
    BOOL fUseFrameRate;
    DWORD dwTimeLimit;
    int iFormatDialogPos;
    int iSourceDialogPos;
    int iDisplayDialogPos;
    int iVCapDialogPos;
    int iVCrossbarDialogPos;
    int iTVTunerDialogPos;
    int iACapDialogPos;
    int iACrossbarDialogPos;
    int iTVAudioDialogPos;
    int iVCapCapturePinDialogPos;
    int iVCapPreviewPinDialogPos;
    int iACapCapturePinDialogPos;
    long lDroppedBase;
    long lNotBase;
    BOOL fPreviewFaked;
    CCrossbar *pCrossbar;
    int iVideoInputMenuPos;
    LONG NumberOfVideoInputs;
    HMENU hMenuPopup;
    int iNumVCapDevices;
} gcap;


// implements IAMCopyCaptureFileProgress
//
class CProgress : public CUnknown, public IAMCopyCaptureFileProgress
{
public:

    CProgress(TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr) :
    	CUnknown(pName, pUnk, phr) {};
    ~CProgress() {};

    STDMETHODIMP_(ULONG) AddRef() {return 1;};
    STDMETHODIMP_(ULONG) Release() {return 0;};
    STDMETHODIMP QueryInterface(REFIID iid, void **p) {
        CheckPointer(p, E_POINTER);
        if (iid == IID_IAMCopyCaptureFileProgress) {
            return GetInterface((IAMCopyCaptureFileProgress *)this, p);
        } else {
            return E_NOINTERFACE;
        }
    };
    STDMETHODIMP Progress(int i) {
        TCHAR tach[80];
        wsprintf(tach, TEXT("Save File Progress: %d%%"), i);
        statusUpdateStatus(ghwndStatus, tach);
        return S_OK;
    };
};

CComModule _Module;

//------------------------------------------------------------------------------
// Funciton Prototypes
//------------------------------------------------------------------------------
typedef LONG (PASCAL *LPWNDPROC)(HWND, UINT, WPARAM, LPARAM); // pointer to a window procedure
LONG WINAPI AppWndProc(HWND hwnd, UINT uiMessage, WPARAM wParam, LPARAM lParam);
LONG PASCAL AppCommand(HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
int ErrMsg (LPTSTR sz,...);
BOOL SetCaptureFile(HWND hWnd);
BOOL SaveCaptureFile(HWND hWnd);
BOOL AllocCaptureFile(HWND hWnd);
int DoDialog(HWND hwndParent, int DialogID, DLGPROC fnDialog, long lParam);
int FAR PASCAL AllocCapFileProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam);
int FAR PASCAL FrameRateProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam);
int FAR PASCAL TimeLimitProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam);
int FAR PASCAL PressAKeyProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam);
void TearDownGraph(void);
BOOL BuildCaptureGraph();
BOOL BuildPreviewGraph();
void UpdateStatus(BOOL fAllStats);
void AddDevicesToMenu();
void ChooseDevices(char *szVideo, char *szAudio);
void ChooseDevices(IMoniker *pmVideo, IMoniker *pmAudio);
void ChooseFrameRate();
BOOL InitCapFilters();
void FreeCapFilters();
BOOL StopPreview();
BOOL StartPreview();
BOOL StopCapture();
DWORDLONG GetSize(LPCTSTR tach);
void MakeMenuOptions();


//------------------------------------------------------------------------------
// Name: SetAppCaption()
// Desc: Set the caption to be the application name followed by the capture file
//------------------------------------------------------------------------------
void SetAppCaption()
{
    TCHAR tach[_MAX_PATH + 80];
    lstrcpy(tach, gszAppName);
    if (gcap.szCaptureFile[0] != 0) {
	lstrcat(tach, " - ");
	lstrcat(tach, gcap.szCaptureFile);
    }
    SetWindowText(ghwndApp, tach);
}


/*----------------------------------------------------------------------------*\
|   AppInit( hInst, hPrev)                                                     |
|                                                                              |
|   Description:                                                               |
|       This is called when the application is first loaded into               |
|       memory.  It performs all initialization that doesn't need to be done   |
|       once per instance.                                                     |
|                                                                              |
|   Arguments:                                                                 |
|       hInstance       instance handle of current instance                    |
|       hPrev           instance handle of previous instance                   |
|                                                                              |
|   Returns:                                                                   |
|       TRUE if successful, FALSE if not                                       |
|                                                                              |
\*----------------------------------------------------------------------------*/
BOOL AppInit(HINSTANCE hInst, HINSTANCE hPrev, int sw)
{
    WNDCLASS    cls;
    HDC         hdc;

    const DWORD  dwExStyle = 0;

    CoInitialize(NULL);
    DbgInitialise(hInst);

    /* Save instance handle for DialogBoxs */
    ghInstApp = hInst;

    ghAccel = LoadAccelerators(hInst, MAKEINTATOM(ID_APP));

    if (!hPrev) {
	/*
	 *  Register a class for the main application window
	 */
	cls.hCursor        = LoadCursor(NULL,IDC_ARROW);
	cls.hIcon          = LoadIcon(hInst, TEXT("AMCapIcon"));
	cls.lpszMenuName   = MAKEINTATOM(ID_APP);
	cls.lpszClassName  = MAKEINTATOM(ID_APP);
	cls.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
	cls.hInstance      = hInst;
	cls.style          = CS_BYTEALIGNCLIENT | CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
	cls.lpfnWndProc    = (LPWNDPROC)AppWndProc;
	cls.cbWndExtra     = 0;
	cls.cbClsExtra     = 0;

	if (!RegisterClass(&cls))
	    return FALSE;
    }

    // Is this necessary?
    ghfontApp = (HFONT)GetStockObject(ANSI_VAR_FONT);
    hdc = GetDC(NULL);
    SelectObject(hdc, ghfontApp);
    GetTextMetrics(hdc, &gtm);
    ReleaseDC(NULL, hdc);

    ghwndApp=CreateWindowEx(dwExStyle,
			    MAKEINTATOM(ID_APP),    // Class name
			    gszAppName,             // Caption
						    // Style bits
			    WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
			    CW_USEDEFAULT, 0,       // Position
			    320,300,                // Size
			    (HWND)NULL,             // Parent window (no parent)
			    (HMENU)NULL,            // use class menu
			    hInst,                  // handle to window instance
			    (LPSTR)NULL             // no params to pass on
			   );

    // create the status bar
    statusInit(hInst, hPrev);
    ghwndStatus = CreateWindowEx(
		    0,
		    szStatusClass,
		    NULL,
		    WS_CHILD|WS_BORDER|WS_VISIBLE|WS_CLIPSIBLINGS,
		    0, 0,
		    0, 0,
		    ghwndApp,
		    NULL,
		    hInst,
		    NULL);
    if (ghwndStatus == NULL) {
	return(FALSE);
    }
    ShowWindow(ghwndApp,sw);

    // get the capture file name from win.ini
    GetProfileString(TEXT("annie"), TEXT("CaptureFile"), TEXT(""),
                                        gcap.szCaptureFile,
					sizeof(gcap.szCaptureFile));

    // get which devices to use from win.ini
    ZeroMemory(gcap.rgpmAudioMenu, sizeof(gcap.rgpmAudioMenu));
    ZeroMemory(gcap.rgpmVideoMenu, sizeof(gcap.rgpmVideoMenu));
    gcap.pmVideo = 0;
    gcap.pmAudio = 0;

    TCHAR szVideoDisplayName[1024], szAudioDisplayName[1024];
    *szAudioDisplayName = *szVideoDisplayName = 0; // null terminate
    
    GetProfileString(TEXT("annie"), TEXT("VideoDevice2"), TEXT(""),
                    szVideoDisplayName,
                    sizeof(szVideoDisplayName));
    GetProfileString(TEXT("annie"), TEXT("AudioDevice2"), TEXT(""),
                    szAudioDisplayName,
                    sizeof(szAudioDisplayName));

    gcap.fDeviceMenuPopulated = false;
    AddDevicesToMenu();

    // do we want audio?
    gcap.fCapAudio = GetProfileInt(TEXT("annie"), TEXT("CaptureAudio"), TRUE);
    gcap.fCapCC = GetProfileInt(TEXT("annie"), TEXT("CaptureCC"), FALSE);

    // do we want preview?
    gcap.fWantPreview = GetProfileInt(TEXT("annie"), TEXT("WantPreview"), FALSE);
    // which stream should be the master? NONE(-1) means nothing special happens
    // AUDIO(1) means the video frame rate is changed before written out to keep
    // the movie in sync when the audio and video capture crystals are not the
    // same (and therefore drift out of sync after a few minutes).  VIDEO(0)
    // means the audio sample rate is changed before written out
    gcap.iMasterStream = GetProfileInt(TEXT("annie"), TEXT("MasterStream"), 1);


    // get the frame rate from win.ini before making the graph
    gcap.fUseFrameRate = GetProfileInt(TEXT("annie"), TEXT("UseFrameRate"), 1);
    int units_per_frame = GetProfileInt(TEXT("annie"), TEXT("FrameRate"), 666667);  // 15fps
    gcap.FrameRate = 10000000. / units_per_frame;
    gcap.FrameRate = (int)(gcap.FrameRate * 100) / 100.;
    // reasonable default
    if (gcap.FrameRate <= 0.)
	gcap.FrameRate = 15.0;
	 
    gcap.fUseTimeLimit = GetProfileInt(TEXT("annie"), TEXT("UseTimeLimit"), 0);
    gcap.dwTimeLimit = GetProfileInt(TEXT("annie"), TEXT("TimeLimit"), 0);

    // instantiate the capture filters we need to do the menu items
    // this will start previewing, if wanted
    // 
    // make these the official devices we're using
    
    ChooseDevices(szVideoDisplayName, szAudioDisplayName);
    // and builds a partial filtergraph.

	// Register for device add/remove notifications.
	DEV_BROADCAST_DEVICEINTERFACE filterData;
	ZeroMemory(&filterData, sizeof(DEV_BROADCAST_DEVICEINTERFACE));
	
	filterData.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
	filterData.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
	filterData.dbcc_classguid = AM_KSCATEGORY_CAPTURE;

    gpUnregisterDeviceNotification = NULL;
    gpRegisterDeviceNotification = NULL;
    // dynload device removal APIs
    {
        HMODULE hmodUser = GetModuleHandle(TEXT("user32.dll"));
        ASSERT(hmodUser);       // we link to user32
        gpUnregisterDeviceNotification = (PUnregisterDeviceNotification)
            GetProcAddress(hmodUser, "UnregisterDeviceNotification");

        // m_pRegisterDeviceNotification is prototyped differently in unicode
        gpRegisterDeviceNotification = (PRegisterDeviceNotification)
            GetProcAddress(hmodUser,
#ifdef UNICODE
                           "RegisterDeviceNotificationW"
#else
                           "RegisterDeviceNotificationA"
#endif
                           );
        // failures expected on older platforms.
        ASSERT(gpRegisterDeviceNotification && gpUnregisterDeviceNotification ||
               !gpRegisterDeviceNotification && !gpUnregisterDeviceNotification);
    }

    ghDevNotify = NULL;

	if (gpRegisterDeviceNotification)
    {
        ghDevNotify = gpRegisterDeviceNotification(ghwndApp, &filterData, DEVICE_NOTIFY_WINDOW_HANDLE);
		ASSERT(ghDevNotify != NULL);
    }

    SetAppCaption();
    return TRUE;
}

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

/*----------------------------------------------------------------------------*\
|   WinMain( hInst, hPrev, lpszCmdLine, cmdShow )                              |
|                                                                              |
|   Description:                                                               |
|       The main procedure for the App.  After initializing, it just goes      |
|       into a message-processing loop until it gets a WM_QUIT message         |
|       (meaning the app was closed).                                          |
|                                                                              |
|   Arguments:                                                                 |
|       hInst           instance handle of this instance of the app            |
|       hPrev           instance handle of previous instance, NULL if first    |
|       szCmdLine       ->null-terminated command line                         |
|       cmdShow         specifies how the window is initially displayed        |
|                                                                              |
|   Returns:                                                                   |
|       The exit code as specified in the WM_QUIT message.                     |
|                                                                              |
\*----------------------------------------------------------------------------*/
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
{
    MSG     msg;

    /* Call initialization procedure */
    if (!AppInit(hInst,hPrev,sw))
	return FALSE;

    /*
     * Polling messages from event queue
     */
    for (;;)
    {
	while (PeekMessage(&msg, NULL, 0, 0,PM_REMOVE))
	{
	    if (msg.message == WM_QUIT)
		return msg.wParam;

	    if (TranslateAccelerator(ghwndApp, ghAccel, &msg))
		continue;
	
	    TranslateMessage(&msg);
	    DispatchMessage(&msg);
	}

	WaitMessage();
    }

    // not reached
    return msg.wParam;
}


/*----------------------------------------------------------------------------*\
|   AppWndProc( hwnd, uiMessage, wParam, lParam )                              |
|                                                                              |
|   Description:                                                               |
|       The window proc for the app's main (tiled) window.  This processes all |
|       of the parent window's messages.                                       |
|                                                                              |
|   Arguments:                                                                 |
|       hwnd            window handle for the window                           |
|       msg             message number                                         |
|       wParam          message-dependent                                      |
|       lParam          message-dependent                                      |
|                                                                              |
|   Returns:                                                                   |
|       0 if processed, nonzero if ignored                                     |
|                                                                              |
\*----------------------------------------------------------------------------*/
LONG WINAPI  AppWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    PAINTSTRUCT ps;
    HDC hdc;
    RECT rc;
    int cxBorder, cyBorder, cy;

    switch (msg) {

	//
	//
	case WM_CREATE:
	    break;

	case WM_COMMAND:
	    return AppCommand(hwnd,msg,wParam,lParam);

	case WM_INITMENU:
	    // we can start capture if not capturing already
	    EnableMenuItem((HMENU)wParam, MENU_START_CAP, 
			(!gcap.fCapturing) ? MF_ENABLED :
			MF_GRAYED);
	    // we can stop capture if it's currently capturing
	    EnableMenuItem((HMENU)wParam, MENU_STOP_CAP, 
			(gcap.fCapturing) ? MF_ENABLED : MF_GRAYED);

	    // We can bring up a dialog if the graph is stopped
	    EnableMenuItem((HMENU)wParam, MENU_DIALOG0, !gcap.fCapturing ?
		MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_DIALOG1, !gcap.fCapturing ?
		MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_DIALOG2, !gcap.fCapturing ?
		MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_DIALOG3, !gcap.fCapturing ?
		MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_DIALOG4, !gcap.fCapturing ?
		MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_DIALOG5, !gcap.fCapturing ?
		MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_DIALOG6, !gcap.fCapturing ?
		MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_DIALOG7, !gcap.fCapturing ?
		MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_DIALOG8, !gcap.fCapturing ?
		MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_DIALOG9, !gcap.fCapturing ?
		MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_DIALOGA, !gcap.fCapturing ?
		MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_DIALOGB, !gcap.fCapturing ?
		MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_DIALOGC, !gcap.fCapturing ?
		MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_DIALOGD, !gcap.fCapturing ?
		MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_DIALOGE, !gcap.fCapturing ?
		MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_DIALOGF, !gcap.fCapturing ?
		MF_ENABLED : MF_GRAYED);

	    // toggles capturing audio or not - can't be capturing right now
	    // and we must have an audio capture device created
	    EnableMenuItem((HMENU)wParam, MENU_CAP_AUDIO, 
		    (!gcap.fCapturing && gcap.pACap) ? MF_ENABLED : MF_GRAYED);
	    // are we capturing audio?
	    CheckMenuItem((HMENU)wParam, MENU_CAP_AUDIO, 
		    (gcap.fCapAudio) ? MF_CHECKED : MF_UNCHECKED);
	    // are we doing closed captioning?
	    CheckMenuItem((HMENU)wParam, MENU_CAP_CC, 
		    (gcap.fCapCC) ? MF_CHECKED : MF_UNCHECKED);
	    EnableMenuItem((HMENU)wParam, MENU_CAP_CC, 
		    (gcap.fCCAvail) ? MF_ENABLED : MF_GRAYED);
	    // change audio formats when not capturing
	    EnableMenuItem((HMENU)wParam, MENU_AUDIOFORMAT, (gcap.fCapAudio &&
			!gcap.fCapturing) ? MF_ENABLED : MF_GRAYED);
	    // change frame rate when not capturing, and only if the video
	    // filter captures a VIDEOINFO type format
	    EnableMenuItem((HMENU)wParam, MENU_FRAMERATE,
			(!gcap.fCapturing && gcap.fCapAudioIsRelevant) ?
			 MF_ENABLED : MF_GRAYED);
	    // change time limit when not capturing
	    EnableMenuItem((HMENU)wParam, MENU_TIMELIMIT,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    // change capture file name when not capturing
	    EnableMenuItem((HMENU)wParam, MENU_SET_CAP_FILE,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    // pre-allocate capture file when not capturing
	    EnableMenuItem((HMENU)wParam, MENU_ALLOC_CAP_FILE,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    // can save capture file when not capturing
	    EnableMenuItem((HMENU)wParam, MENU_SAVE_CAP_FILE,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    // do we want preview?
	    CheckMenuItem((HMENU)wParam, MENU_PREVIEW, 
			(gcap.fWantPreview) ? MF_CHECKED : MF_UNCHECKED);
	    // can toggle preview if not capturing
	    EnableMenuItem((HMENU)wParam, MENU_PREVIEW,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);

	    // which is the master stream? Not applicable unless we're also
	    // capturing audio
	    EnableMenuItem((HMENU)wParam, MENU_NOMASTER,
		(!gcap.fCapturing && gcap.fCapAudio) ? MF_ENABLED : MF_GRAYED);
	    CheckMenuItem((HMENU)wParam, MENU_NOMASTER, 
		    (gcap.iMasterStream == -1) ? MF_CHECKED : MF_UNCHECKED);
	    EnableMenuItem((HMENU)wParam, MENU_AUDIOMASTER,
		(!gcap.fCapturing && gcap.fCapAudio) ? MF_ENABLED : MF_GRAYED);
	    CheckMenuItem((HMENU)wParam, MENU_AUDIOMASTER, 
		    (gcap.iMasterStream == 1) ? MF_CHECKED : MF_UNCHECKED);
	    EnableMenuItem((HMENU)wParam, MENU_VIDEOMASTER,
		(!gcap.fCapturing && gcap.fCapAudio) ? MF_ENABLED : MF_GRAYED);
	    CheckMenuItem((HMENU)wParam, MENU_VIDEOMASTER, 
		    (gcap.iMasterStream == 0) ? MF_CHECKED : MF_UNCHECKED);

	    // can't select a new capture device when capturing
	    EnableMenuItem((HMENU)wParam, MENU_VDEVICE0,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_VDEVICE1,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_VDEVICE2,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_VDEVICE3,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_VDEVICE4,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_VDEVICE5,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_VDEVICE6,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_VDEVICE7,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_VDEVICE8,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_VDEVICE9,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_ADEVICE0,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_ADEVICE1,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_ADEVICE2,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_ADEVICE3,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_ADEVICE4,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_ADEVICE5,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_ADEVICE6,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_ADEVICE7,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_ADEVICE8,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    EnableMenuItem((HMENU)wParam, MENU_ADEVICE9,
			!gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
	    
	    break;


      case WM_INITMENUPOPUP:
            if(GetSubMenu(GetMenu(ghwndApp), 1) == (HMENU)wParam) {
                AddDevicesToMenu();
            }
	    
            break;
	//
	// We're out of here!
	//
	case WM_DESTROY:
	    DbgTerminate();

        IMonRelease(gcap.pmVideo);
        IMonRelease(gcap.pmAudio);
        {
            for(int i = 0; i < NUMELMS(gcap.rgpmVideoMenu); i++) {
                IMonRelease(gcap.rgpmVideoMenu[i]);
            }
            for( i = 0; i < NUMELMS(gcap.rgpmAudioMenu); i++) {
                IMonRelease(gcap.rgpmAudioMenu[i]);
            }
        }

		CoUninitialize();
	    PostQuitMessage(0);
	    break;

	//
	//
	case WM_CLOSE:
		// Unregister device notifications
		if (ghDevNotify != NULL)
		{
			ASSERT(gpUnregisterDeviceNotification);
            gpUnregisterDeviceNotification(ghDevNotify);
			ghDevNotify = NULL;
		}

	    StopPreview();
	    StopCapture();
	    TearDownGraph();
	    FreeCapFilters();

	    // store current settings in win.ini for next time
	    WriteProfileString(TEXT("annie"), TEXT("CaptureFile"),
                                                gcap.szCaptureFile);
            
        TCHAR tach[120];
        WCHAR *szDisplayName;
        szDisplayName = 0;
	    wsprintf(tach, TEXT("%S"), L"");
        if(gcap.pmVideo) 
        {
            if (SUCCEEDED(gcap.pmVideo->GetDisplayName(0, 0, &szDisplayName)))
            {
                wsprintf(tach, TEXT("%S"), szDisplayName ? szDisplayName : L"");
                CoTaskMemFree(szDisplayName);
            }
        }
	    WriteProfileString(TEXT("annie"), TEXT("VideoDevice2"), tach);
	    wsprintf(tach, TEXT("%S"), L"");
        szDisplayName = 0;
        if(gcap.pmAudio) 
        {
            if (SUCCEEDED(gcap.pmAudio->GetDisplayName(0, 0, &szDisplayName)))
            {
                wsprintf(tach, TEXT("%S"), szDisplayName ? szDisplayName : L"");
                CoTaskMemFree(szDisplayName);
            }
        }
	    WriteProfileString(TEXT("annie"), TEXT("AudioDevice2"), tach);
            
	    wsprintf(tach, TEXT("%d"), (int)(10000000 / gcap.FrameRate));
	    WriteProfileString(TEXT("annie"), TEXT("FrameRate"), tach);
	    wsprintf(tach, TEXT("%d"), gcap.fUseFrameRate);
	    WriteProfileString(TEXT("annie"), TEXT("UseFrameRate"), tach);
	    wsprintf(tach, TEXT("%d"), gcap.fCapAudio);
	    WriteProfileString(TEXT("annie"), TEXT("CaptureAudio"), tach);
	    wsprintf(tach, TEXT("%d"), gcap.fCapCC);
	    WriteProfileString(TEXT("annie"), TEXT("CaptureCC"), tach);
	    wsprintf(tach, TEXT("%d"), gcap.fWantPreview);
	    WriteProfileString(TEXT("annie"), TEXT("WantPreview"), tach);
	    wsprintf(tach, TEXT("%d"), gcap.iMasterStream);
	    WriteProfileString(TEXT("annie"), TEXT("MasterStream"), tach);
	    wsprintf(tach, TEXT("%d"), gcap.fUseTimeLimit);
	    WriteProfileString(TEXT("annie"), TEXT("UseTimeLimit"), tach);
	    wsprintf(tach, TEXT("%d"), gcap.dwTimeLimit);
	    WriteProfileString(TEXT("annie"), TEXT("TimeLimit"), tach);

	    break;

	case WM_ERASEBKGND:
	    break;

	// ESC will stop capture
	case WM_KEYDOWN:
	    if ((GetAsyncKeyState(VK_ESCAPE) & 0x01) && gcap.fCapturing) {
		StopCapture();
		if (gcap.fWantPreview) {
		    BuildPreviewGraph();
		    StartPreview();
		}
	    }
	    break;
		
	case WM_PAINT:
	    hdc = BeginPaint(hwnd,&ps);

	    // nothing to do

	    EndPaint(hwnd,&ps);
	    break;

	case WM_TIMER:
	    // update our status bar with #captured, #dropped
	    // if we've stopped capturing, don't do it anymore.  Some WM_TIMER
	    // messages may come late, after we've destroyed the graph and
	    // we'll get invalid numbers.
	    if (gcap.fCapturing)
	        UpdateStatus(FALSE);

	    // is our time limit up?
	    if (gcap.fUseTimeLimit) {
		if ((timeGetTime() - gcap.lCapStartTime) / 1000 >=
							gcap.dwTimeLimit) {
		    StopCapture();
		    if (gcap.fWantPreview) {
			BuildPreviewGraph();
			StartPreview();
		    }
		}
	    }
	    break;

	case WM_SIZE:
	    // make the preview window fit inside our window, taking up
	    // all of our client area except for the status window at the
	    // bottom
	    GetClientRect(ghwndApp, &rc);
	    cxBorder = GetSystemMetrics(SM_CXBORDER);
	    cyBorder = GetSystemMetrics(SM_CYBORDER);
	    cy = statusGetHeight() + cyBorder;
	    MoveWindow(ghwndStatus, -cxBorder, rc.bottom - cy,
			rc.right + (2 * cxBorder), cy + cyBorder, TRUE);
	    rc.bottom -= cy;
	    // this is the video renderer window showing the preview
	    if (gcap.pVW)
		gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom);
	    break;

	case WM_FGNOTIFY:
	    // uh-oh, something went wrong while capturing - the filtergraph
	    // will send us events like EC_COMPLETE, EC_USERABORT and the one
	    // we care about, EC_ERRORABORT.
	    if (gcap.pME) {
		    LONG event, l1, l2;
            BOOL bAbort = FALSE;
		    while (gcap.pME->GetEvent(&event, &l1, &l2, 0) == S_OK) 
            {
			    gcap.pME->FreeEventParams(event, l1, l2);
			    if (event == EC_ERRORABORT) {
                    StopCapture();
                    bAbort = TRUE;
			        continue;
			    }
                else if (event == EC_DEVICE_LOST)
                {
				    // Check if we have lost a capture filter being used.
				    // lParam2 of EC_DEVICE_LOST event == 1 indicates device added
				    //								   == 0 indicates device removed
				    if (l2 == 0)
				    {
					    IBaseFilter *pf;
					    IUnknown *punk = (IUnknown *) l1;
					    if (S_OK == punk->QueryInterface(IID_IBaseFilter, (void **) &pf))
					    {
						    if (::IsEqualObject(gcap.pVCap, pf))
						    {
								pf->Release();
                                bAbort = FALSE;
                                StopCapture();
							    char szError[1000];
							    wsprintf(szError, "Stopping Capture (Device Lost). Select New Capture Device", l1);
							    ErrMsg(szError);
                                break;
						    }
							pf->Release();
					    }
				    }
                }
            } // end while
            if (bAbort)
            {
			    if (gcap.fWantPreview) {
				BuildPreviewGraph();
				StartPreview();
			    }
                char szError[1000];
                wsprintf(szError, "ERROR during capture, error code=%08x", l1);
			    ErrMsg(szError);
            }
	    }
	    break;
	
	case WM_DEVICECHANGE:
		// We are interested in only device arrival & removal events
		if (DBT_DEVICEARRIVAL != wParam && DBT_DEVICEREMOVECOMPLETE != wParam)
			break;
		PDEV_BROADCAST_HDR pdbh = (PDEV_BROADCAST_HDR) lParam;
		if (pdbh->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE)
		{
			break;
		}
		PDEV_BROADCAST_DEVICEINTERFACE pdbi = (PDEV_BROADCAST_DEVICEINTERFACE) lParam;
		// Check for capture devices.
		if (pdbi->dbcc_classguid != AM_KSCATEGORY_CAPTURE)
		{
			break;
		}

		// Check for device arrival/removal.
		if (DBT_DEVICEARRIVAL == wParam || DBT_DEVICEREMOVECOMPLETE == wParam)
        {
			gcap.fDeviceMenuPopulated = false;
        }
		break;

    }
    return DefWindowProc(hwnd,msg,wParam,lParam);
}


// Make a graph builder object we can use for capture graph building
//
BOOL MakeBuilder()
{
    // we have one already
    if (gcap.pBuilder)
	return TRUE;

    HRESULT hr = CoCreateInstance((REFCLSID)CLSID_CaptureGraphBuilder2,
			NULL, CLSCTX_INPROC, (REFIID)IID_ICaptureGraphBuilder2,
			(void **)&gcap.pBuilder);
    return (hr == NOERROR) ? TRUE : FALSE;
}


// Make a graph object we can use for capture graph building
//
BOOL MakeGraph()
{
    // we have one already
    if (gcap.pFg)
	return TRUE;

    HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC,
			       IID_IGraphBuilder, (LPVOID *)&gcap.pFg);
    return (hr == NOERROR) ? TRUE : FALSE;
}


// make sure the preview window inside our window is as big as the
// dimensions of captured video, or some capture cards won't show a preview.
// (Also, it helps people tell what size video they're capturing)
// We will resize our app's window big enough so that once the status bar
// is positioned at the bottom there will be enough room for the preview
// window to be w x h
//

int gnRecurse = 0;

void ResizeWindow(int w, int h)
{
    RECT rcW, rcC;
    int xExtra, yExtra;
    int cyBorder = GetSystemMetrics(SM_CYBORDER);

    gnRecurse++;

    GetWindowRect(ghwndApp, &rcW);
    GetClientRect(ghwndApp, &rcC);
    xExtra = rcW.right - rcW.left - rcC.right;
    yExtra = rcW.bottom - rcW.top - rcC.bottom + cyBorder + statusGetHeight();
    
    rcC.right = w;
    rcC.bottom = h;
    SetWindowPos(ghwndApp, NULL, 0, 0, rcC.right + xExtra,
				rcC.bottom + yExtra, SWP_NOZORDER | SWP_NOMOVE);

    // we may need to recurse once.  But more than that means the window cannot
    // be made the size we want, trying will just stack fault.
    //
    if (gnRecurse == 1 && ((rcC.right + xExtra != rcW.right - rcW.left && w > GetSystemMetrics(SM_CXMIN)) ||
		(rcC.bottom + yExtra != rcW.bottom - rcW.top)))
	ResizeWindow(w,h);

    gnRecurse--;
}


// Tear down everything downstream of a given filter
void NukeDownstream(IBaseFilter *pf)
{
    //DbgLog((LOG_TRACE,1,TEXT("Nuking...")));

    IPin *pP, *pTo;
    ULONG u;
    IEnumPins *pins = NULL;
    PIN_INFO pininfo;
    HRESULT hr = pf->EnumPins(&pins);
    pins->Reset();
    while (hr == NOERROR) {
        hr = pins->Next(1, &pP, &u);
	if (hr == S_OK && pP) {
	    pP->ConnectedTo(&pTo);
	    if (pTo) {
	        hr = pTo->QueryPinInfo(&pininfo);
	        if (hr == NOERROR) {
		    if (pininfo.dir == PINDIR_INPUT) {
		        NukeDownstream(pininfo.pFilter);
		        gcap.pFg->Disconnect(pTo);
		        gcap.pFg->Disconnect(pP);
	                gcap.pFg->RemoveFilter(pininfo.pFilter);
		    }
	            pininfo.pFilter->Release();
		}
		pTo->Release();
	    }
	    pP->Release();
	}
    }
    if (pins)
        pins->Release();
}


// Tear down everything downstream of the capture filters, so we can build
// a different capture graph.  Notice that we never destroy the capture filters
// and WDM filters upstream of them, because then all the capture settings
// we've set would be lost.
//
void TearDownGraph()
{
    if (gcap.pSink)
	gcap.pSink->Release();
    gcap.pSink = NULL;
    if (gcap.pConfigAviMux)
	gcap.pConfigAviMux->Release();
    gcap.pConfigAviMux = NULL;
    if (gcap.pRender)
	gcap.pRender->Release();
    gcap.pRender = NULL;
    if (gcap.pVW) {
	// stop drawing in our window, or we may get wierd repaint effects
	gcap.pVW->put_Owner(NULL);
	gcap.pVW->put_Visible(OAFALSE);
	gcap.pVW->Release();
    }
    gcap.pVW = NULL;
    if (gcap.pME)
	gcap.pME->Release();
    gcap.pME = NULL;
    if (gcap.pDF)
	gcap.pDF->Release();
    gcap.pDF = NULL;

    // destroy the graph downstream of our capture filters
    if (gcap.pVCap)
	NukeDownstream(gcap.pVCap);
    if (gcap.pACap)
	NukeDownstream(gcap.pACap);

    // potential debug output - what the graph looks like
    // if (gcap.pFg) DumpGraph(gcap.pFg, 1);

    gcap.fCaptureGraphBuilt = FALSE;
    gcap.fPreviewGraphBuilt = FALSE;
    gcap.fPreviewFaked = FALSE;
}


// create the capture filters of the graph.  We need to keep them loaded from
// the beginning, so we can set parameters on them and have them remembered
//
BOOL InitCapFilters()
{
    HRESULT hr;
    BOOL f;
    UINT uIndex = 0;

    gcap.fCCAvail = FALSE;	// assume no closed captioning support

    f = MakeBuilder();
    if (!f) {
	ErrMsg("Cannot instantiate graph builder");
	return FALSE;
    }

//
// First, we need a Video Capture filter, and some interfaces
//

    gcap.pVCap = NULL;
    if(gcap.pmVideo != 0)
    {
        IPropertyBag *pBag;
        gcap.wachFriendlyName[0] = 0;
        hr = gcap.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) {
                lstrcpyW(gcap.wachFriendlyName, var.bstrVal);
                SysFreeString(var.bstrVal);
            }
            pBag->Release();
        }
        hr = gcap.pmVideo->BindToObject(0, 0, IID_IBaseFilter, (void**)&gcap.pVCap);
    }

    if (gcap.pVCap == NULL) {
	ErrMsg("Error %x: Cannot create video capture filter", hr);
	goto InitCapFiltersFail;
    }

    //
    // make a filtergraph, give it to the graph builder and put the video
    // capture filter in the graph
    //

    f = MakeGraph();
    if (!f) {
	ErrMsg("Cannot instantiate filtergraph");
	goto InitCapFiltersFail;
    }
    hr = gcap.pBuilder->SetFiltergraph(gcap.pFg);
    if (hr != NOERROR) {
	ErrMsg("Cannot give graph to builder");
	goto InitCapFiltersFail;
    }

    hr = gcap.pFg->AddFilter(gcap.pVCap, NULL);
    if (hr != NOERROR) {
	ErrMsg("Error %x: Cannot add vidcap to filtergraph", hr);
	goto InitCapFiltersFail;
    }

    // Calling FindInterface below will result in building the upstream
    // section of the capture graph (any WDM TVTuners or Crossbars we might
    // need).

    // we use this interface to get the name of the driver
    // Don't worry if it doesn't work:  This interface may not be available
    // until the pin is connected, or it may not be available at all.
    // (eg: interface may not be available for some DV capture)
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
		    &MEDIATYPE_Interleaved,
		    gcap.pVCap, IID_IAMVideoCompression, (void **)&gcap.pVC);
    if (hr != S_OK) {
        hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
		    &MEDIATYPE_Video,
		    gcap.pVCap, IID_IAMVideoCompression, (void **)&gcap.pVC);
    }

    // !!! What if this interface isn't supported?
    // we use this interface to set the frame rate and get the capture size
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
			&MEDIATYPE_Interleaved,
			gcap.pVCap, IID_IAMStreamConfig, (void **)&gcap.pVSC);
    if (hr != NOERROR) {
        hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Video, gcap.pVCap,
				IID_IAMStreamConfig, (void **)&gcap.pVSC);
        if (hr != NOERROR) {
	    // this means we can't set frame rate (non-DV only)
	    ErrMsg("Error %x: Cannot find VCapture:IAMStreamConfig", hr);
	}
    }

    gcap.fCapAudioIsRelevant = TRUE;

    AM_MEDIA_TYPE *pmt;
    // default capture format
    if (gcap.pVSC && gcap.pVSC->GetFormat(&pmt) == S_OK) {
        // DV capture does not use a VIDEOINFOHEADER
	if (pmt->formattype == FORMAT_VideoInfo) {
            // resize our window to the default capture size
            ResizeWindow(HEADER(pmt->pbFormat)->biWidth,
				ABS(HEADER(pmt->pbFormat)->biHeight));
	}
	if (pmt->majortype != MEDIATYPE_Video) {
	    // This capture filter captures something other that pure video.
	    // Maybe it's DV or something?  Anyway, chances are we shouldn't
	    // allow capturing audio separately, since our video capture    
	    // filter may have audio combined in it already!
    	    gcap.fCapAudioIsRelevant = FALSE;
    	    gcap.fCapAudio = FALSE;
	}
        DeleteMediaType(pmt);
    }

    // we use this interface to bring up the 3 dialogs
    // NOTE:  Only the VfW capture filter supports this.  This app only brings
    // up dialogs for legacy VfW capture drivers, since only those have dialogs
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Video, gcap.pVCap,
				IID_IAMVfwCaptureDialogs, (void **)&gcap.pDlg);


    // Use the crossbar class to help us sort out all the possible video inputs
    // The class needs to be given the capture filters ANALOGVIDEO input pin
    {
        IPin        *pP = 0;
        IEnumPins   *pins;
        ULONG        n;
        PIN_INFO     pinInfo;
        BOOL         Found = FALSE;
	IKsPropertySet *pKs;
	GUID guid;
	DWORD dw;
	BOOL fMatch = FALSE;
        
        gcap.pCrossbar = NULL;

        if(SUCCEEDED(gcap.pVCap->EnumPins(&pins))) {            
            while (!Found && (S_OK == pins->Next(1, &pP, &n))) {
                if (S_OK == pP->QueryPinInfo(&pinInfo)) {
                    if (pinInfo.dir == PINDIR_INPUT) {
			
			// is this pin an ANALOGVIDEOIN input pin?
    			if (pP->QueryInterface(IID_IKsPropertySet,
						(void **)&pKs) == S_OK) {
			    if (pKs->Get(AMPROPSETID_Pin,
					AMPROPERTY_PIN_CATEGORY, NULL, 0,
					&guid, sizeof(GUID), &dw) == S_OK) {
	    			if (guid == PIN_CATEGORY_ANALOGVIDEOIN)
					fMatch = TRUE;
			    }
			    pKs->Release();
    			}

			if (fMatch) {
                            gcap.pCrossbar = new CCrossbar (pP);
                            hr = gcap.pCrossbar->GetInputCount
						(&gcap.NumberOfVideoInputs);
                            Found = TRUE;
			}
                    }
                    pinInfo.pFilter->Release();
                }
                pP->Release();
            }
            pins->Release();
        }
    }

    // there's no point making an audio capture filter
    if (gcap.fCapAudioIsRelevant == FALSE)
	goto SkipAudio;

// create the audio capture filter, even if we are not capturing audio right
// now, so we have all the filters around all the time.

    //
    // We want an audio capture filter and some interfaces
    //

    if(gcap.pmAudio == 0)
    {
	// there are no audio capture devices. We'll only allow video capture
	gcap.fCapAudio = FALSE;
	goto SkipAudio;
    }
    gcap.pACap = NULL;


    hr = gcap.pmAudio->BindToObject(0, 0, IID_IBaseFilter, (void**)&gcap.pACap);

    if (gcap.pACap == NULL) {
	// there are no audio capture devices. We'll only allow video capture
	gcap.fCapAudio = FALSE;
	ErrMsg("Cannot create audio capture filter");
	goto SkipAudio;
    }

    //
    // put the audio capture filter in the graph
    //

    // We'll need this in the graph to get audio property pages
    hr = gcap.pFg->AddFilter(gcap.pACap, NULL);
    if (hr != NOERROR) {
        ErrMsg("Error %x: Cannot add audcap to filtergraph", hr);
        goto InitCapFiltersFail;
    }

    // Calling FindInterface below will result in building the upstream
    // section of the capture graph (any WDM TVAudio's or Crossbars we might
    // need).

    // !!! What if this interface isn't supported?
    // we use this interface to set the captured wave format
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio,
			gcap.pACap, IID_IAMStreamConfig, (void **)&gcap.pASC);
    if (hr != NOERROR) {
	    ErrMsg("Cannot find ACapture:IAMStreamConfig");
    }

SkipAudio:

    // Can this filter do closed captioning?
    IPin *pPin;
    hr = gcap.pBuilder->FindPin(gcap.pVCap, PINDIR_OUTPUT, &PIN_CATEGORY_VBI,
				NULL, FALSE, 0, &pPin);
    if (hr != S_OK)
        hr = gcap.pBuilder->FindPin(gcap.pVCap, PINDIR_OUTPUT, &PIN_CATEGORY_CC,
				NULL, FALSE, 0, &pPin);
    if (hr == S_OK) {
	pPin->Release();
	gcap.fCCAvail = TRUE;
    } else {
	gcap.fCapCC = FALSE;	// can't capture it, then
    }

    // potential debug output - what the graph looks like
    // DumpGraph(gcap.pFg, 1);

    return TRUE;

InitCapFiltersFail:
    FreeCapFilters();
    return FALSE;
}


// all done with the capture filters and the graph builder
//
void FreeCapFilters()
{
    if (gcap.pFg)
	gcap.pFg->Release();
    gcap.pFg = NULL;
    if (gcap.pBuilder)
	gcap.pBuilder->Release();
    gcap.pBuilder = NULL;
    if (gcap.pVCap)
	gcap.pVCap->Release();
    gcap.pVCap = NULL;
    if (gcap.pACap)
	gcap.pACap->Release();
    gcap.pACap = NULL;
    if (gcap.pASC)
	gcap.pASC->Release();
    gcap.pASC = NULL;
    if (gcap.pVSC)
	gcap.pVSC->Release();
    gcap.pVSC = NULL;
    if (gcap.pVC)
	gcap.pVC->Release();
    gcap.pVC = NULL;
    if (gcap.pDlg)
	gcap.pDlg->Release();
    gcap.pDlg = NULL;
    if (gcap.pCrossbar) {
       delete gcap.pCrossbar;
       gcap.pCrossbar = NULL;
    }
}


// build the capture graph!
//
BOOL BuildCaptureGraph()
{
    USES_CONVERSION;
    int cy, cyBorder;
    HRESULT hr;
    BOOL f;
    AM_MEDIA_TYPE *pmt;

    // we have one already
    if (gcap.fCaptureGraphBuilt)
	return TRUE;

    // No rebuilding while we're running
    if (gcap.fCapturing || gcap.fPreviewing)
	return FALSE;

    // We don't have the necessary capture filters
    if (gcap.pVCap == NULL)
	return FALSE;
    if (gcap.pACap == NULL && gcap.fCapAudio)
	return FALSE;

    // no capture file name yet... we need one first
    if (gcap.szCaptureFile[0] == 0) {
	f = SetCaptureFile(ghwndApp);
	if (!f)
	    return f;
    }

    // we already have another graph built... tear down the old one
    if (gcap.fPreviewGraphBuilt)
	TearDownGraph();

//
// We need a rendering section that will write the capture file out in AVI
// file format
//

    GUID guid = MEDIASUBTYPE_Avi;
    hr = gcap.pBuilder->SetOutputFileName(&guid, T2W(gcap.szCaptureFile),
                                                &gcap.pRender, &gcap.pSink);
    if (hr != NOERROR) {
	ErrMsg("Cannot set output file");
	goto SetupCaptureFail;
    }

// Now tell the AVIMUX to write out AVI files that old apps can read properly.
// If we don't, most apps won't be able to tell where the keyframes are,
// slowing down editing considerably
// Doing this will cause one seek (over the area the index will go) when
// you capture past 1 Gig, but that's no big deal.
// NOTE: This is on by default, so it's not necessary to turn it on

// Also, set the proper MASTER STREAM

    hr = gcap.pRender->QueryInterface(IID_IConfigAviMux,
						(void **)&gcap.pConfigAviMux);
    if (hr == NOERROR && gcap.pConfigAviMux) {
	gcap.pConfigAviMux->SetOutputCompatibilityIndex(TRUE);
	if (gcap.fCapAudio) {
	    hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream);
	    if (hr != NOERROR)
		ErrMsg("SetMasterStream failed!");
	}
    }

//
// Render the video capture and preview pins - even if the capture filter only
// has a capture pin (and no preview pin) this should work... because the
// capture graph builder will use a smart tee filter to provide both capture
// and preview.  We don't have to worry.  It will just work.
//

// NOTE that we try to render the interleaved pin before the video pin, because
// if BOTH exist, it's a DV filter and the only way to get the audio is to use
// the interleaved pin.  Using the Video pin on a DV filter is only useful if
// you don't want the audio.

    hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE,
					&MEDIATYPE_Interleaved,
					gcap.pVCap, NULL, gcap.pRender);
    if (hr != NOERROR) {
        hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE,
					&MEDIATYPE_Video,
					gcap.pVCap, NULL, gcap.pRender);
        if (hr != NOERROR) {
	    ErrMsg("Cannot render video capture stream");
	    goto SetupCaptureFail;
	}
    }

    if (gcap.fWantPreview) {
        hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW,
				&MEDIATYPE_Interleaved, gcap.pVCap, NULL, NULL);
        if (hr == VFW_S_NOPREVIEWPIN) {
	    // preview was faked up for us using the (only) capture pin
	    gcap.fPreviewFaked = TRUE;
	} else if (hr != S_OK) {
            hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW,
				&MEDIATYPE_Video, gcap.pVCap, NULL, NULL);
            if (hr == VFW_S_NOPREVIEWPIN) {
	        // preview was faked up for us using the (only) capture pin
	        gcap.fPreviewFaked = TRUE;
	    } else if (hr != S_OK) {
	        ErrMsg("Cannot render video preview stream");
	        goto SetupCaptureFail;
	    }
        }
    }

//
// Render the audio capture pin?
//

    if (gcap.fCapAudio) {
	hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE,
			&MEDIATYPE_Audio, gcap.pACap, NULL, gcap.pRender);
	if (hr != NOERROR) {
	    ErrMsg("Cannot render audio capture stream");
	    goto SetupCaptureFail;
	}
    }

//
// Render the closed captioning pin? It could be a CC or a VBI category pin,
// depending on the capture driver
//

    if (gcap.fCapCC) {
	hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CC, NULL,
					gcap.pVCap, NULL, gcap.pRender);
	if (hr != NOERROR) {
	    hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_VBI, NULL,
					gcap.pVCap, NULL, gcap.pRender);
	    if (hr != NOERROR) {
	        ErrMsg("Cannot render closed captioning");
	        // so what? goto SetupCaptureFail;
            }
	}
 	// To preview and capture VBI at the same time, we can call this twice
        if (gcap.fWantPreview) {
	    hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_VBI, NULL,
					gcap.pVCap, NULL, NULL);
	}
    }

//
// Get the preview window to be a child of our app's window
//

    // This will find the IVideoWindow interface on the renderer.  It is 
    // important to ask the filtergraph for this interface... do NOT use
    // ICaptureGraphBuilder2::FindInterface, because the filtergraph needs to
    // know we own the window so it can give us display changed messages, etc.

    // NOTE: We do this even if we didn't ask for a preview, because rendering
    // the capture pin may have rendered the preview pin too (WDM overlay 
    // devices) because they must have a preview going.  So we better always
    // put the preview window in our app, or we may get a top level window
    // appearing out of nowhere!

   	hr = gcap.pFg->QueryInterface(IID_IVideoWindow, (void **)&gcap.pVW);
        if (hr != NOERROR && gcap.fWantPreview) {
	    ErrMsg("This graph cannot preview");
        } else if (hr == NOERROR) {
	    RECT rc;
	    gcap.pVW->put_Owner((long)ghwndApp);    // We own the window now
	    gcap.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 = statusGetHeight() + cyBorder;
	    rc.bottom -= cy;
	    gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); // be this big
	    gcap.pVW->put_Visible(OATRUE);
        }

    // now tell it what frame rate to capture at.  Just find the format it
    // is capturing with, and leave everything alone but change the frame rate
    hr = gcap.fUseFrameRate ? E_FAIL : NOERROR;
    if (gcap.pVSC && gcap.fUseFrameRate) {
	hr = gcap.pVSC->GetFormat(&pmt);
	// DV capture does not use a VIDEOINFOHEADER
        if (hr == NOERROR) {
	    if (pmt->formattype == FORMAT_VideoInfo) {
	        VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)pmt->pbFormat;
	        pvi->AvgTimePerFrame = (LONGLONG)(10000000 / gcap.FrameRate);
	        hr = gcap.pVSC->SetFormat(pmt);
	    }
	    DeleteMediaType(pmt);
        }
    }
    if (hr != NOERROR)
	ErrMsg("Cannot set frame rate for capture");

    // now ask the filtergraph to tell us when something is completed or aborted
    // (EC_COMPLETE, EC_USERABORT, EC_ERRORABORT).  This is how we will find out
    // if the disk gets full while capturing
    hr = gcap.pFg->QueryInterface(IID_IMediaEventEx, (void **)&gcap.pME);
    if (hr == NOERROR) {
	gcap.pME->SetNotifyWindow((LONG)ghwndApp, WM_FGNOTIFY, 0);
    }

// All done.

    // potential debug output - what the graph looks like
    // DumpGraph(gcap.pFg, 1);

    gcap.fCaptureGraphBuilt = TRUE;
    return TRUE;

SetupCaptureFail:
    TearDownGraph();
    return FALSE;
}



// build the preview graph!
//
// !!! PLEASE NOTE !!!  Some new WDM devices have totally separate capture
// and preview settings.  An application that wishes to preview and then 
// capture may have to set the preview pin format using IAMStreamConfig on the
// preview pin, and then again on the capture pin to capture with that format.
// In this sample app, there is a separate page to set the settings on the 
// capture pin and one for the preview pin.  To avoid the user
// having to enter the same settings in 2 dialog boxes, an app can have its own
// UI for choosing a format (the possible formats can be enumerated using
// IAMStreamConfig) and then the app can programmatically call IAMStreamConfig
// to set the format on both pins.
//
BOOL BuildPreviewGraph()
{
    int cy, cyBorder;
    HRESULT hr;
    AM_MEDIA_TYPE *pmt;
    BOOL fPreviewUsingCapturePin = FALSE;

    // we have one already
    if (gcap.fPreviewGraphBuilt)
	return TRUE;

    // No rebuilding while we're running
    if (gcap.fCapturing || gcap.fPreviewing)
	return FALSE;

    // We don't have the necessary capture filters
    if (gcap.pVCap == NULL)
	return FALSE;
    if (gcap.pACap == NULL && gcap.fCapAudio)
	return FALSE;

    // we already have another graph built... tear down the old one
    if (gcap.fCaptureGraphBuilt)
	TearDownGraph();

//
// Render the preview pin - even if there is not preview pin, the capture
// graph builder will use a smart tee filter and provide a preview.
//
// !!! what about latency/buffer issues?

// NOTE that we try to render the interleaved pin before the video pin, because
// if BOTH exist, it's a DV filter and the only way to get the audio is to use
// the interleaved pin.  Using the Video pin on a DV filter is only useful if
// you don't want the audio.

    hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW,
				&MEDIATYPE_Interleaved, gcap.pVCap, NULL, NULL);
    if (hr == VFW_S_NOPREVIEWPIN) {
	// preview was faked up for us using the (only) capture pin
	gcap.fPreviewFaked = TRUE;
    } else if (hr != S_OK) {
	// maybe it's DV?
        hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW,
				&MEDIATYPE_Video, gcap.pVCap, NULL, NULL);
        if (hr == VFW_S_NOPREVIEWPIN) {
	    // preview was faked up for us using the (only) capture pin
	    gcap.fPreviewFaked = TRUE;
        } else if (hr != S_OK) {
	    ErrMsg("This graph cannot preview!");
	}
    }

//
// Render the closed captioning pin? It could be a CC or a VBI category pin,
// depending on the capture driver
//

    if (gcap.fCapCC) {
	hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CC, NULL,
					gcap.pVCap, NULL, NULL);
	if (hr != NOERROR) {
	    hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_VBI, NULL,
					gcap.pVCap, NULL, NULL);
	    if (hr != NOERROR) {
	        ErrMsg("Cannot render closed captioning");
	        // so what? goto SetupCaptureFail;
            }
        }
    }

//
// Get the preview window to be a child of our app's window
//

    // This will find the IVideoWindow interface on the renderer.  It is 
    // important to ask the filtergraph for this interface... do NOT use
    // ICaptureGraphBuilder2::FindInterface, because the filtergraph needs to
    // know we own the window so it can give us display changed messages, etc.

    hr = gcap.pFg->QueryInterface(IID_IVideoWindow, (void **)&gcap.pVW);
    if (hr != NOERROR) {
	ErrMsg("This graph cannot preview properly");
    } else {
	RECT rc;
	gcap.pVW->put_Owner((long)ghwndApp);    // We own the window now
	gcap.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 = statusGetHeight() + cyBorder;
	rc.bottom -= cy;
	gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); // be this big
	gcap.pVW->put_Visible(OATRUE);
    }

    // now tell it what frame rate to capture at.  Just find the format it
    // is capturing with, and leave everything alone but change the frame rate
    // No big deal if it fails.  It's just for preview
    // !!! Should we then talk to the preview pin?
    if (gcap.pVSC && gcap.fUseFrameRate) {
	hr = gcap.pVSC->GetFormat(&pmt);
	// DV capture does not use a VIDEOINFOHEADER
        if (hr == NOERROR) {
	    if (pmt->formattype == FORMAT_VideoInfo) {
	        VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)pmt->pbFormat;
	        pvi->AvgTimePerFrame = (LONGLONG)(10000000 / gcap.FrameRate);
	        hr = gcap.pVSC->SetFormat(pmt);
		if (hr != NOERROR)
		    ErrMsg("%x: Cannot set frame rate for preview", hr);
	    }
	    DeleteMediaType(pmt);
	}
    }

    // make sure we process events while we're previewing!
    hr = gcap.pFg->QueryInterface(IID_IMediaEventEx, (void **)&gcap.pME);
    if (hr == NOERROR) {
        gcap.pME->SetNotifyWindow((LONG)ghwndApp, WM_FGNOTIFY, 0);
    }


// All done.

    // potential debug output - what the graph looks like
    // DumpGraph(gcap.pFg, 1);

    gcap.fPreviewGraphBuilt = TRUE;
    return TRUE;
}


// Start previewing
//
BOOL StartPreview()
{
    BOOL f = TRUE;

    // way ahead of you
    if (gcap.fPreviewing)
	return TRUE;

    if (!gcap.fPreviewGraphBuilt)
	return FALSE;

    // run the graph
    IMediaControl *pMC = NULL;
    HRESULT hr = gcap.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)) {
	ErrMsg("Error %x: Cannot run preview graph", hr);
	return FALSE;
    }

    gcap.fPreviewing = TRUE;
    return TRUE;
}


// stop the preview graph
//
BOOL StopPreview()
{
    // way ahead of you
    if (!gcap.fPreviewing) {
	return FALSE;
    }

    // stop the graph
    IMediaControl *pMC = NULL;
    HRESULT hr = gcap.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC);
    if (SUCCEEDED(hr)) {
	hr = pMC->Stop();
	pMC->Release();
    }
    if (FAILED(hr)) {
	ErrMsg("Error %x: Cannot stop preview graph", hr);
	return FALSE;
    }

    gcap.fPreviewing = FALSE;

    // !!! get rid of menu garbage
    InvalidateRect(ghwndApp, NULL, TRUE);

    return TRUE;
}


// start the capture graph
//
BOOL StartCapture()
{
    BOOL f, fHasStreamControl;
    HRESULT hr;

    // way ahead of you
    if (gcap.fCapturing)
	return TRUE;

    // or we'll get confused
    if (gcap.fPreviewing)
	StopPreview();

    // or we'll crash
    if (!gcap.fCaptureGraphBuilt)
	return FALSE;

    // This amount will be subtracted from the number of dropped and not 
    // dropped frames reported by the filter.  Since we might be having the
    // filter running while the pin is turned off, we don't want any of the
    // frame statistics from the time the pin is off interfering with the
    // statistics we gather while the pin is on
    gcap.lDroppedBase = 0;
    gcap.lNotBase = 0;

    REFERENCE_TIME start = MAX_TIME, stop = MAX_TIME;

    // don't capture quite yet...
    hr = gcap.pBuilder->ControlStream(&PIN_CATEGORY_CAPTURE, NULL,
				NULL, &start, NULL, 0, 0);
    //DbgLog((LOG_TRACE,1,TEXT("Capture OFF returns %x"), hr));

    // Do we have the ability to control capture and preview separately?
    fHasStreamControl = SUCCEEDED(hr);

    // prepare to run the graph
    IMediaControl *pMC = NULL;
    hr = gcap.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC);
    if (FAILED(hr)) {
	ErrMsg("Error %x: Cannot get IMediaControl", hr);
	return FALSE;
    }

    // If we were able to keep capture off, then we can
    // run the graph now for frame accurate start later yet still showing a
    // preview.   Otherwise, we can't run the graph yet without capture
    // starting too, so we'll pause it so the latency between when they
    // press a key and when capture begins is still small (but they won't have
    // a preview while they wait to press a key)

    if (fHasStreamControl)
	hr = pMC->Run();
    else
	hr = pMC->Pause();
    if (FAILED(hr)) {
	// stop parts that started
	pMC->Stop();
	pMC->Release();
	ErrMsg("Error %x: Cannot start graph", hr);
	return FALSE;
    }

    // press a key to start capture
    f = DoDialog(ghwndApp, IDD_PressAKeyDialog, (DLGPROC)PressAKeyProc, 0);
    if (!f) {
	pMC->Stop();
	pMC->Release();
	if (gcap.fWantPreview) {
	    BuildPreviewGraph();
	    StartPreview();
	}
	return f;
    }

    // Start capture NOW!
    if (fHasStreamControl) {
	// we may not have this yet
        if (!gcap.pDF) {
	    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				   &MEDIATYPE_Interleaved, gcap.pVCap,
				   IID_IAMDroppedFrames, (void **)&gcap.pDF);
	    if (hr != NOERROR)
	        hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				   &MEDIATYPE_Video, gcap.pVCap,
				   IID_IAMDroppedFrames, (void **)&gcap.pDF);
	}

	// turn the capture pin on now!
	hr = gcap.pBuilder->ControlStream(&PIN_CATEGORY_CAPTURE, NULL,
				NULL, NULL, &stop, 0, 0);
    	//DbgLog((LOG_TRACE,0,TEXT("Capture ON returns %x"), hr));
	// make note of the current dropped frame counts
	if (gcap.pDF) {
	    gcap.pDF->GetNumDropped(&gcap.lDroppedBase);
	    gcap.pDF->GetNumNotDropped(&gcap.lNotBase);
    	    //DbgLog((LOG_TRACE,0,TEXT("Dropped counts are %ld and %ld"),
	    //		gcap.lDroppedBase, gcap.lNotBase));
        } 
    } else {
	hr = pMC->Run();
	if (FAILED(hr)) {
	    // stop parts that started
	    pMC->Stop();
	    pMC->Release();
	    ErrMsg("Error %x: Cannot run graph", hr);
	    return FALSE;
	}
    }

    pMC->Release();

    // when did we start capture?
    gcap.lCapStartTime = timeGetTime();

    // 30 times a second I want to update my status bar - #captured, #dropped
    SetTimer(ghwndApp, 1, 33, NULL);

    gcap.fCapturing = TRUE;
    return TRUE;
}


// stop the capture graph
//
BOOL StopCapture()
{
    // way ahead of you
    if (!gcap.fCapturing) {
	return FALSE;
    }

    // stop the graph
    IMediaControl *pMC = NULL;
    HRESULT hr = gcap.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC);
    if (SUCCEEDED(hr)) {
	hr = pMC->Stop();
	pMC->Release();
    }
    if (FAILED(hr)) {
	ErrMsg("Error %x: Cannot stop graph", hr);
	return FALSE;
    }

    // when the graph was stopped
    gcap.lCapStopTime = timeGetTime();

    // no more status bar updates
    KillTimer(ghwndApp, 1);

    // one last time for the final count and all the stats
    UpdateStatus(TRUE);

    gcap.fCapturing = FALSE;

    // !!! get rid of menu garbage
    InvalidateRect(ghwndApp, NULL, TRUE);

    return TRUE;
}


// Let's talk about UI for a minute.  There are many programmatic interfaces
// you can use to program a capture filter or related filter to capture the
// way you want it to.... eg:  IAMStreamConfig, IAMVideoCompression,
// IAMCrossbar, IAMTVTuner, IAMTVAudio, IAMAnalogVideoDecoder, IAMCameraControl,
// IAMVideoProcAmp, etc.
//
// But you probably want some UI to let the user play with all these settings.
// For new WDM-style capture devices, we offer some default UI you can use.
// The code below shows how to bring up all of the dialog boxes supported 
// by any capture filters.
//
// The following code shows you how you can bring up all of the
// dialogs supported by a particular object at once on a big page with lots
// of thumb tabs.  You do this by starting with an interface on the object that
// you want, and using ISpecifyPropertyPages to get the whole list, and
// OleCreatePropertyFrame to bring them all up.  This way you will get custom
// property pages a filter has, too, that are not one of the standard pages that
// you know about.  There are at least 9 objects that may have property pages.
// Your app already has 2 of the object pointers, the video capture filter and
// the audio capture filter (let's call them pVCap and pACap)
// 1.  The video capture filter - pVCap
// 2.  The video capture filter's capture pin - get this by calling
//     FindInterface(&PIN_CATEGORY_CAPTURE, pVCap, IID_IPin, &pX);
// 3.  The video capture filter's preview pin - get this by calling
//     FindInterface(&PIN_CATEGORY_PREVIEW, pVCap, IID_IPin, &pX);
// 4.  The audio capture filter - pACap
// 5.  The audio capture filter's capture pin - get this by calling
//     FindInterface(&PIN_CATEGORY_CAPTURE, pACap, IID_IPin, &pX);
// 6.  The crossbar connected to the video capture filter - get this by calling
//     FindInterface(NULL, pVCap, IID_IAMCrossbar, &pX);
// 7.  There is a possible second crossbar to control audio - get this by 
//     looking upstream of the first crossbar like this:
//     FindInterface(&LOOK_UPSTREAM_ONLY, pX, IID_IAMCrossbar, &pX2);
// 8.  The TV Tuner connected to the video capture filter - get this by calling
//     FindInterface(NULL, pVCap, IID_IAMTVTuner, &pX);
// 9.  The TV Audio connected to the audio capture filter - get this by calling
//     FindInterface(NULL, pACap, IID_IAMTVAudio, &pX);
// 10. We have a helper class, CCrossbar, which makes the crossbar issue less
//     confusing.  In fact, although not supported here, there may be more than
//     two crossbars, arranged in many different ways.  An application may not
//     wish to have separate dialogs for each crossbar, but instead hide the
//     complexity and simply offer the user a list of inputs that can be chosen.
//     This list represents all the unique inputs from all the crossbars.
//     The crossbar helper class does this and offers that list as #10.  It is
//     expected that an application will either provide the crossbar dialogs
//     above (#6 and #7) OR provide the input list (this #10), but not both.
//     That would be confusing because if you select an input using dialog 6 or
//     7 the input list here in #10 won't know about your choice.
//
// Your last choice for UI is to make your own pages, and use the results of 
// your custom page to call the interfaces programmatically.


void MakeMenuOptions()
{
    HRESULT hr;
    HMENU hMenuSub = GetSubMenu(GetMenu(ghwndApp), 2); // Options menu

    // remove any old choices from the last device
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
    RemoveMenu(hMenuSub, 4, MF_BYPOSITION);

    int zz = 0;
    gcap.iFormatDialogPos = -1;
    gcap.iSourceDialogPos = -1;
    gcap.iDisplayDialogPos = -1;
    gcap.iVCapDialogPos = -1;
    gcap.iVCrossbarDialogPos = -1;
    gcap.iTVTunerDialogPos = -1;
    gcap.iACapDialogPos = -1;
    gcap.iACrossbarDialogPos = -1;
    gcap.iTVAudioDialogPos = -1;
    gcap.iVCapCapturePinDialogPos = -1;
    gcap.iVCapPreviewPinDialogPos = -1;
    gcap.iACapCapturePinDialogPos = -1;

    // If this device supports the old legacy UI dialogs, offer them

    if (gcap.pDlg && !gcap.pDlg->HasDialog(VfwCaptureDialog_Format)) {
	AppendMenu(hMenuSub, MF_STRING, MENU_DIALOG0 + zz, TEXT("Video Format..."));
	gcap.iFormatDialogPos = zz++;
    }
    if (gcap.pDlg && !gcap.pDlg->HasDialog(VfwCaptureDialog_Source)) {
	AppendMenu(hMenuSub, MF_STRING, MENU_DIALOG0 + zz, TEXT("Video Source..."));
	gcap.iSourceDialogPos = zz++;
    }
    if (gcap.pDlg && !gcap.pDlg->HasDialog(VfwCaptureDialog_Display)) {
	AppendMenu(hMenuSub, MF_STRING, MENU_DIALOG0 + zz, TEXT("Video Display..."));
	gcap.iDisplayDialogPos = zz++;
    }

    // Also check the audio capture filter at this point, since even non wdm devices
    // may support an IAMAudioInputMixer property page (we'll also get any wdm filter
    // properties here as well). We'll get any audio capture pin property pages just
    // a bit later.
    if (gcap.pACap != NULL)
    {
        ISpecifyPropertyPages *pSpec;
        CAUUID cauuid;
        
        hr = gcap.pACap->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
        if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
            if (hr == S_OK && cauuid.cElems > 0) {
                AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("Audio Capture Filter..."));
                gcap.iACapDialogPos = zz++;
                CoTaskMemFree(cauuid.pElems);
            }
            pSpec->Release();
        }
    }

    // don't bother looking for new property pages if the old ones are supported
    // or if we don't have a capture filter
    if (gcap.pVCap == NULL || gcap.iFormatDialogPos != -1)
	return;

    // New WDM devices support new UI and new interfaces.
    // Your app can use some default property
    // pages for UI if you'd like (like we do here) or if you don't like our
    // dialog boxes, feel free to make your own and programmatically set 
    // the capture options through interfaces like IAMCrossbar, IAMCameraControl
    // etc.

    // There are 9 objects that might support property pages.  Let's go through
    // them.

    ISpecifyPropertyPages *pSpec;
    CAUUID cauuid;

    // 1. the video capture filter itself

    hr = gcap.pVCap->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
    if (hr == S_OK) {
        hr = pSpec->GetPages(&cauuid);
        if (hr == S_OK && cauuid.cElems > 0) {
	    AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("Video Capture Filter..."));
	    gcap.iVCapDialogPos = zz++;
	    CoTaskMemFree(cauuid.pElems);
	}
	pSpec->Release();
    }

    // 2.  The video capture capture pin

    IAMStreamConfig *pSC;
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
			&MEDIATYPE_Interleaved,
			gcap.pVCap, IID_IAMStreamConfig, (void **)&pSC);
    if (hr != S_OK)
        hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Video, gcap.pVCap,
				IID_IAMStreamConfig, (void **)&pSC);
    if (hr == S_OK) {
        hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
        if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
            if (hr == S_OK && cauuid.cElems > 0) {
	        AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("Video Capture Pin..."));
	        gcap.iVCapCapturePinDialogPos = zz++;
	        CoTaskMemFree(cauuid.pElems);
	    }
	    pSpec->Release();
        }
	pSC->Release();
    }

    // 3.  The video capture preview pin.
    // This basically sets the format being previewed.  Typically, you
    // want to capture and preview using the SAME format, instead of having to
    // enter the same value in 2 dialog boxes.  For a discussion on this, see
    // the comment above the MakePreviewGraph function.

    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW,
				&MEDIATYPE_Interleaved, gcap.pVCap,
				IID_IAMStreamConfig, (void **)&pSC);
    if (hr != NOERROR)
        hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW,
				&MEDIATYPE_Video, gcap.pVCap,
				IID_IAMStreamConfig, (void **)&pSC);
    if (hr == S_OK) {
        hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
        if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
            if (hr == S_OK && cauuid.cElems > 0) {
	        AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz,TEXT("Video Preview Pin..."));
	        gcap.iVCapPreviewPinDialogPos = zz++;
		CoTaskMemFree(cauuid.pElems);
	    }
	    pSpec->Release();
        }
	pSC->Release();
    }

    // 4 & 5.  The video crossbar, and a possible second crossbar

    IAMCrossbar *pX, *pX2;
    IBaseFilter *pXF;
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Interleaved, gcap.pVCap,
				IID_IAMCrossbar, (void **)&pX);
    if (hr != S_OK)
        hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Video, gcap.pVCap,
				IID_IAMCrossbar, (void **)&pX);
    if (hr == S_OK) {
        hr = pX->QueryInterface(IID_IBaseFilter, (void **)&pXF);
        if (hr == S_OK) {
            hr = pX->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
            if (hr == S_OK) {
                hr = pSpec->GetPages(&cauuid);
                if (hr == S_OK && cauuid.cElems > 0) {
	            AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz,
						TEXT("Video Crossbar..."));
	            gcap.iVCrossbarDialogPos = zz++;
		    CoTaskMemFree(cauuid.pElems);
	        }
	        pSpec->Release();
            }
            hr = gcap.pBuilder->FindInterface(&LOOK_UPSTREAM_ONLY, NULL, pXF,
				IID_IAMCrossbar, (void **)&pX2);
            if (hr == S_OK) {
                hr = pX2->QueryInterface(IID_ISpecifyPropertyPages,
							(void **)&pSpec);
                if (hr == S_OK) {
                    hr = pSpec->GetPages(&cauuid);
                    if (hr == S_OK && cauuid.cElems > 0) {
	                AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz,
						TEXT("Second Crossbar..."));
	                gcap.iACrossbarDialogPos = zz++;
		        CoTaskMemFree(cauuid.pElems);
	            }
	            pSpec->Release();
                }
	        pX2->Release();
	    }
 	    pXF->Release();
        }
	pX->Release();
    }

    // 6.  The TVTuner

    IAMTVTuner *pTV;
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Interleaved, gcap.pVCap,
				IID_IAMTVTuner, (void **)&pTV);
    if (hr != S_OK)
        hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Video, gcap.pVCap,
				IID_IAMTVTuner, (void **)&pTV);
    if (hr == S_OK) {
        hr = pTV->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
        if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
            if (hr == S_OK && cauuid.cElems > 0) {
	        AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("TV Tuner..."));
	        gcap.iTVTunerDialogPos = zz++;
		CoTaskMemFree(cauuid.pElems);
	    }
	    pSpec->Release();
        }
 	pTV->Release();
    }

    // no audio capture, we're done
    if (gcap.pACap == NULL)
	return;

    // 7.  The Audio capture filter itself... Thanks anyway, but we got these already

    // 8.  The Audio capture pin

    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Audio, gcap.pACap,
				IID_IAMStreamConfig, (void **)&pSC);
    if (hr == S_OK) {
        hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
        if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
            if (hr == S_OK && cauuid.cElems > 0) {
	        AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("Audio Capture Pin..."));
	        gcap.iACapCapturePinDialogPos = zz++;
		CoTaskMemFree(cauuid.pElems);
	    }
	    pSpec->Release();
        }
 	pSC->Release();
    }

    // 9.  The TV Audio filter

    IAMTVAudio *pTVA;
    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, 
				&MEDIATYPE_Audio, gcap.pACap,
				IID_IAMTVAudio, (void **)&pTVA);
    if (hr == S_OK) {
        hr = pTVA->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
        if (hr == S_OK) {
            hr = pSpec->GetPages(&cauuid);
            if (hr == S_OK && cauuid.cElems > 0) {
	        AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("TV Audio..."));
	        gcap.iTVAudioDialogPos = zz++;
		CoTaskMemFree(cauuid.pElems);
	    }
	    pSpec->Release();
        }
 	pTVA->Release();
    }

    // 10.  Crossbar class helper menu item, to let you choose an input

    if (gcap.pCrossbar && gcap.NumberOfVideoInputs) {
        gcap.hMenuPopup = CreatePopupMenu();
        LONG j;
        LONG  PhysicalType;
        TCHAR buf[MAX_PATH];
        LONG InputToEnable = -1;

	gcap.iVideoInputMenuPos = zz++;
        AppendMenu(hMenuSub, MF_SEPARATOR, 0, NULL);

        for (j = 0; j < gcap.NumberOfVideoInputs; j++) {
            EXECUTE_ASSERT (S_OK == gcap.pCrossbar->GetInputType (j, &PhysicalType));
            EXECUTE_ASSERT (S_OK == gcap.pCrossbar->GetInputName (j, buf, sizeof (buf)));
            AppendMenu(gcap.hMenuPopup,MF_STRING,MENU_DIALOG0+zz, buf);
            zz++;

            // Route the first TVTuner by default
            if ((PhysicalType == PhysConn_Video_Tuner) && InputToEnable == -1) {
                InputToEnable = j;
            }
        }
            
        AppendMenu(hMenuSub, MF_STRING | MF_POPUP, (UINT)gcap.hMenuPopup, TEXT("Video Input"));

        if (InputToEnable == -1) {
            InputToEnable = 0;
        }
        CheckMenuItem(gcap.hMenuPopup, InputToEnable, MF_BYPOSITION | MF_CHECKED); 

        gcap.pCrossbar->SetInputIndex (InputToEnable);
    }
    // !!! anything needed to delete the popup when selecting a new input?
}


// how many captured/dropped so far
//
void UpdateStatus(BOOL fAllStats)
{
    HRESULT hr;
    LONG lDropped, lNot, lAvgFrameSize;
    TCHAR tach[160];

    // we use this interface to get the number of captured and dropped frames
    // NOTE:  We cannot query for this interface earlier, as it may not be
    // available until the pin is connected
    if (!gcap.pDF) {
	hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Interleaved, gcap.pVCap,
				IID_IAMDroppedFrames, (void **)&gcap.pDF);
	if (hr != S_OK)
	    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Video, gcap.pVCap,
				IID_IAMDroppedFrames, (void **)&gcap.pDF);
    }

    // this filter can't tell us dropped frame info.
    if (!gcap.pDF) {
	statusUpdateStatus(ghwndStatus,
			TEXT("Filter cannot report capture information"));
	return;
    }

    hr = gcap.pDF->GetNumDropped(&lDropped);
    if (hr == S_OK)
	hr = gcap.pDF->GetNumNotDropped(&lNot);
    if (hr != S_OK)
	return;

    lDropped -= gcap.lDroppedBase;
    lNot -= gcap.lNotBase;

    if (!fAllStats) {
	LONG lTime = timeGetTime() - gcap.lCapStartTime;
	wsprintf(tach, TEXT("Captured %d frames (%d dropped) %d.%dsec"), lNot,
				lDropped, lTime / 1000, 
				lTime / 100 - lTime / 1000 * 10);
	statusUpdateStatus(ghwndStatus, tach);
	return;
    }

    // we want all possible stats, including capture time and actual acheived
    // frame rate and data rate (as opposed to what we tried to get).  These
    // numbers are an indication that though we dropped frames just now, if we
    // chose a data rate and frame rate equal to the numbers I'm about to
    // print, we probably wouldn't drop any frames.
    
    // average size of frame captured
    hr = gcap.pDF->GetAverageFrameSize(&lAvgFrameSize);
    if (hr != S_OK)
	return;

    // how long capture lasted
    LONG lDurMS = gcap.lCapStopTime - gcap.lCapStartTime;
    double flFrame;     // acheived frame rate
    LONG lData;         // acheived data rate

    if (lDurMS > 0) {
	flFrame = (double)(LONGLONG)lNot * 1000. /
						(double)(LONGLONG)lDurMS;
	lData = (LONG)(LONGLONG)(lNot / (double)(LONGLONG)lDurMS *
				1000. * (double)(LONGLONG)lAvgFrameSize);
    } else {
	flFrame = 0.;
	lData = 0;
    }

    wsprintf(tach, TEXT("Captured %d frames in %d.%d sec (%d dropped): %d.%d fps %d.%d Meg/sec"),
		lNot, lDurMS / 1000, lDurMS / 100 - lDurMS / 1000 * 10,
		lDropped, (int)flFrame,
		(int)(flFrame * 10.) - (int)flFrame * 10,
		lData / 1000000,
		lData / 1000 - (lData / 1000000 * 1000));
    statusUpdateStatus(ghwndStatus, tach);
}


// Check the devices we're currently using and make filters for them
//
void ChooseDevices(IMoniker *pmVideo, IMoniker *pmAudio)
{
    USES_CONVERSION;
    #define VERSIZE 40
    #define DESCSIZE 80
    int versize = VERSIZE;
    int descsize = DESCSIZE;
    WCHAR wachVer[VERSIZE], wachDesc[DESCSIZE];
    TCHAR tachStatus[VERSIZE + DESCSIZE + 5];


      // they chose a new device. rebuild the graphs
    if (gcap.pmVideo != pmVideo || gcap.pmAudio != pmAudio)
    {
        if(pmVideo) {
            pmVideo->AddRef();
        }
        if(pmAudio) {
            pmAudio->AddRef();
        }
        IMonRelease(gcap.pmVideo);
        IMonRelease(gcap.pmAudio);
        gcap.pmVideo = pmVideo;
        gcap.pmAudio = pmAudio;
        if (gcap.fPreviewing)
	        StopPreview();
	    if (gcap.fCaptureGraphBuilt || gcap.fPreviewGraphBuilt)
	        TearDownGraph();
	    FreeCapFilters();
	    InitCapFilters();
	    if (gcap.fWantPreview) { // were we previewing?
	        BuildPreviewGraph();
	        StartPreview();
	    }
	    MakeMenuOptions();	// the UI choices change per device
    }

	// Set the check marks for the devices menu.
	int i;
    for(i = 0; i < NUMELMS(gcap.rgpmVideoMenu); i++) {
		if (gcap.rgpmVideoMenu[i] == NULL)
			break;
		CheckMenuItem(  GetMenu(ghwndApp), 
                MENU_VDEVICE0 + i, 
                (S_OK == gcap.rgpmVideoMenu[i]->IsEqual(gcap.pmVideo)) ? MF_CHECKED : MF_UNCHECKED); 

    }

    for(i = 0; i < NUMELMS(gcap.rgpmAudioMenu); i++) {
		if (gcap.rgpmAudioMenu[i] == NULL)
			break;
		CheckMenuItem(  GetMenu(ghwndApp), 
                MENU_ADEVICE0 + i, 
                (S_OK == gcap.rgpmAudioMenu[i]->IsEqual(gcap.pmAudio)) ? MF_CHECKED : MF_UNCHECKED); 

    }



    // Put the video driver name in the status bar - if the filter supports
    // IAMVideoCompression::GetInfo, that's the best way to get the name and
    // the version.  Otherwise use the name we got from device enumeration
    // as a fallback.
    if (gcap.pVC) {
	HRESULT hr = gcap.pVC->GetInfo(wachVer, &versize, wachDesc, &descsize,
							NULL, NULL, NULL, NULL);
	if (hr == S_OK) {
	    wsprintf(tachStatus, TEXT("%s - %s"), W2T(wachDesc), W2T(wachVer));
	    statusUpdateStatus(ghwndStatus, tachStatus);
	    return;
	}
    }
    statusUpdateStatus(ghwndStatus, W2T(gcap.wachFriendlyName));
}

void ChooseDevices(char *szVideo, char *szAudio)
{
    WCHAR wszVideo[1024],  wszAudio[1024];
    MultiByteToWideChar(CP_ACP, 0, szVideo, -1, wszVideo, NUMELMS(wszVideo));
    MultiByteToWideChar(CP_ACP, 0, szAudio, -1, wszAudio, NUMELMS(wszAudio));

    IBindCtx *lpBC;
    HRESULT hr = CreateBindCtx(0, &lpBC);
    IMoniker *pmVideo = 0, *pmAudio = 0;
    if (SUCCEEDED(hr))
    {
        DWORD dwEaten;
        hr = MkParseDisplayName(lpBC, wszVideo, &dwEaten,
                                &pmVideo);
        hr = MkParseDisplayName(lpBC, wszAudio, &dwEaten,
                                &pmAudio);

        lpBC->Release();
    }

    // Handle the case where the video capture device used for the previous session
    // is not available now.
    BOOL bFound = FALSE;
    if (pmVideo != NULL)
    {
            for(int i = 0; i < NUMELMS(gcap.rgpmVideoMenu); i++) 
            {
                if (gcap.rgpmVideoMenu[i] != NULL &&
                    S_OK == gcap.rgpmVideoMenu[i]->IsEqual(pmVideo))
                {
                    bFound = TRUE;
                    break;
                }
            }
    }

    if (!bFound)
    {
        if (gcap.iNumVCapDevices > 0)
        {
            IMonRelease(pmVideo);
            ASSERT(gcap.rgpmVideoMenu[0] != NULL);
            pmVideo = gcap.rgpmVideoMenu[0];
            pmVideo->AddRef();
        }
        else
            goto CleanUp;
    }
        
    ChooseDevices(pmVideo, pmAudio);

CleanUp:
    IMonRelease(pmVideo);
    IMonRelease(pmAudio);
}


// put all installed video and audio devices in the menus
//
void AddDevicesToMenu()
{
    USES_CONVERSION;

    if(gcap.fDeviceMenuPopulated) {
        return;
    }
    gcap.fDeviceMenuPopulated = true;
    gcap.iNumVCapDevices = 0;
    
    UINT    uIndex = 0;
    HMENU   hMenuSub;
    HRESULT hr;
	BOOL bCheck = FALSE;

    hMenuSub = GetSubMenu(GetMenu(ghwndApp), 1);        // Devices menu

    // Clean the sub menu
	int iMenuItems = GetMenuItemCount(hMenuSub);
	if (iMenuItems == -1)
	{
		ErrMsg("Error Cleaning Devices Menu");
		return;
	}
	else if (iMenuItems > 0)
	{
		for (int i = 0; i < iMenuItems; i++)
		{
			RemoveMenu(hMenuSub, 0, MF_BYPOSITION);
		}

	}

	{
        for(int i = 0; i < NUMELMS(gcap.rgpmVideoMenu); i++) {
            IMonRelease(gcap.rgpmVideoMenu[i]);
        }
        for( i = 0; i < NUMELMS(gcap.rgpmAudioMenu); i++) {
            IMonRelease(gcap.rgpmAudioMenu[i]);
        }
    }


    // enumerate all video capture devices
    ICreateDevEnum *pCreateDevEnum;
    hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
			  IID_ICreateDevEnum, (void**)&pCreateDevEnum);
    if (hr != NOERROR)
	{
		ErrMsg("Error Creating Device Enumerator");
		return;
	}

    IEnumMoniker *pEm;
    hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
								&pEm, 0);
    if (hr != NOERROR) {
	ErrMsg("Sorry, you have no video capture hardware");
	goto EnumAudio;
    }
    pEm->Reset();
    ULONG cFetched;
    IMoniker *pM;
    while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)
    {
	IPropertyBag *pBag;
	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) {
		AppendMenu(hMenuSub, MF_STRING, MENU_VDEVICE0 + uIndex,
							W2T(var.bstrVal));

		if (gcap.pmVideo != 0 && (S_OK == gcap.pmVideo->IsEqual(pM)))
			bCheck = TRUE;
		CheckMenuItem(	hMenuSub, 
						MENU_VDEVICE0 + uIndex, 
						(bCheck ? MF_CHECKED : MF_UNCHECKED));
		bCheck = FALSE;
		EnableMenuItem(	hMenuSub,
						MENU_VDEVICE0 + uIndex,
						(gcap.fCapturing ? MF_DISABLED : MF_ENABLED));
						

		SysFreeString(var.bstrVal);

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

    gcap.iNumVCapDevices = uIndex;

    // separate the video and audio devices
    AppendMenu(hMenuSub, MF_SEPARATOR, 0, NULL);

EnumAudio:

    // enumerate all audio capture devices
    uIndex = 0;
	bCheck = FALSE;
	
	ASSERT(pCreateDevEnum != NULL);

    hr = pCreateDevEnum->CreateClassEnumerator(CLSID_AudioInputDeviceCategory,
								&pEm, 0);
    pCreateDevEnum->Release();
    if (hr != NOERROR)
	return;
    pEm->Reset();
    while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)
    {
	IPropertyBag *pBag;
	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) {
		AppendMenu(hMenuSub, MF_STRING, MENU_ADEVICE0 + uIndex,
							W2T(var.bstrVal));

		if (gcap.pmAudio != 0 && (S_OK == gcap.pmAudio->IsEqual(pM)))
			bCheck = TRUE;
		CheckMenuItem(	hMenuSub, 
						MENU_ADEVICE0 + uIndex, 
						(bCheck ? MF_CHECKED : MF_UNCHECKED));
		bCheck = FALSE;
		EnableMenuItem(	hMenuSub,
						MENU_ADEVICE0 + uIndex,
						(gcap.fCapturing ? MF_DISABLED : MF_ENABLED));
						
		SysFreeString(var.bstrVal);

                ASSERT(gcap.rgpmAudioMenu[uIndex] == 0);
                gcap.rgpmAudioMenu[uIndex] = pM;
                pM->AddRef();                
	    }
	    pBag->Release();
	}
	pM->Release();
	uIndex++;
    }
    pEm->Release();
}



// let them pick a frame rate
//
void ChooseFrameRate()
{
    double rate = gcap.FrameRate;

    DoDialog(ghwndApp, IDD_FrameRateDialog, (DLGPROC)FrameRateProc, 0);

    HRESULT hr = E_FAIL;

    // If somebody unchecks "use frame rate" it means we will no longer
    // tell the filter what frame rate to use... it will either continue
    // using the last one, or use some default, or if you bring up a dialog
    // box that has frame rate choices, it will obey them.

    // new frame rate?
    if (gcap.fUseFrameRate && gcap.FrameRate != rate) {
	if (gcap.fPreviewing)
	    StopPreview();
	// now tell it what frame rate to capture at.  Just find the format it
	// is capturing with, and leave everything else alone
	if (gcap.pVSC) {
	    AM_MEDIA_TYPE *pmt;
	    hr = gcap.pVSC->GetFormat(&pmt);
	    // DV capture does not use a VIDEOINFOHEADER
            if (hr == NOERROR) {
		if (pmt->formattype == FORMAT_VideoInfo) {
		    VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)pmt->pbFormat;
		    pvi->AvgTimePerFrame =(LONGLONG)(10000000 / gcap.FrameRate);
                    hr = gcap.pVSC->SetFormat(pmt);
                    if (hr != S_OK)
		        ErrMsg("%x: Cannot set new frame rate", hr);
		}
		DeleteMediaType(pmt);
	    }
	}
	if (hr != NOERROR)
	    ErrMsg("Cannot set frame rate for capture");
	if (gcap.fWantPreview)  // we were previewing
	    StartPreview();
    }
}


// let them set a capture time limit
//
void ChooseTimeLimit()
{
    DoDialog(ghwndApp, IDD_TimeLimitDialog, (DLGPROC)TimeLimitProc, 0);
}


// choose an audio capture format using ACM
//
void ChooseAudioFormat()
{
    ACMFORMATCHOOSE cfmt;
    DWORD dwSize;
    LPWAVEFORMATEX lpwfx;
    AM_MEDIA_TYPE *pmt;

    // there's no point if we can't set a new format
    if (gcap.pASC == NULL)
	return;

    // What's the largest format size around?
    acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &dwSize);
    HRESULT hr = gcap.pASC->GetFormat(&pmt);
    if (hr != NOERROR)
	return;
    lpwfx = (LPWAVEFORMATEX)pmt->pbFormat;
    dwSize = max(dwSize, lpwfx->cbSize + sizeof(WAVEFORMATEX));

    // !!! This doesn't really map to the supported formats of the filter.
    // We should be using a property page based on IAMStreamConfig

    // Put up a dialog box initialized with the current format
    if (lpwfx = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, dwSize)) {
	CopyMemory(lpwfx, pmt->pbFormat, pmt->cbFormat);
	_fmemset(&cfmt, 0, sizeof(ACMFORMATCHOOSE));
	cfmt.cbStruct = sizeof(ACMFORMATCHOOSE);
	cfmt.fdwStyle = ACMFORMATCHOOSE_STYLEF_INITTOWFXSTRUCT;
	// show only formats we can capture
	cfmt.fdwEnum = ACM_FORMATENUMF_HARDWARE | ACM_FORMATENUMF_INPUT;
	cfmt.hwndOwner = ghwndApp;
	cfmt.pwfx = lpwfx;
	cfmt.cbwfx = dwSize;

	// we chose a new format... so give it to the capture filter
	if (!acmFormatChoose(&cfmt)) {
	    if (gcap.fPreviewing)
		StopPreview();  // can't call IAMStreamConfig::SetFormat
				// while streaming
	    ((CMediaType *)pmt)->SetFormat((LPBYTE)lpwfx,
					lpwfx->cbSize + sizeof(WAVEFORMATEX));
	    gcap.pASC->SetFormat(pmt);  // filter will reconnect
	    if (gcap.fWantPreview)
		StartPreview();
	}
	GlobalFreePtr(lpwfx) ;
    }
    DeleteMediaType(pmt);
}


/*----------------------------------------------------------------------------*\
|    AppCommand()
|
|    Process all of our WM_COMMAND messages.
\*----------------------------------------------------------------------------*/
LONG PASCAL AppCommand (HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam)
{
    HRESULT hr;
    int id = GET_WM_COMMAND_ID(wParam, lParam);
    switch(id)
    {
	//
	// Our about box
	//
	case MENU_ABOUT:
	    DialogBox(ghInstApp, MAKEINTRESOURCE(IDD_ABOUT), hwnd, 
							(DLGPROC)AboutDlgProc);
	    break;

	//
	// We want out of here!
	//
	case MENU_EXIT:
	    PostMessage(hwnd,WM_CLOSE,0,0L);
	    break;

	// choose a capture file
	//
	case MENU_SET_CAP_FILE:
	    SetCaptureFile(hwnd);
	    break;

	// pre-allocate the capture file
	//
	case MENU_ALLOC_CAP_FILE:
	    AllocCaptureFile(hwnd);
	    break;

	// save the capture file
	//
	case MENU_SAVE_CAP_FILE:
	    SaveCaptureFile(hwnd);
	    break;

	// start capturing
	//
	case MENU_START_CAP:
	    if (gcap.fPreviewing)
		StopPreview();
	    if (gcap.fPreviewGraphBuilt)
		TearDownGraph();
	    BuildCaptureGraph();
	    StartCapture();
	    break;

	// toggle preview
	// 
	case MENU_PREVIEW:
	    gcap.fWantPreview = !gcap.fWantPreview;
	    if (gcap.fWantPreview) {
		BuildPreviewGraph();
		StartPreview();
	    } else
		StopPreview();
	    break;

	// stop capture
	//
	case MENU_STOP_CAP:
	    StopCapture();
	    if (gcap.fWantPreview) {
		BuildPreviewGraph();
		StartPreview();
	    }
	    break;

	// select the master stream
	//
	case MENU_NOMASTER:
	    gcap.iMasterStream = -1;
	    if (gcap.pConfigAviMux) {
		hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream);
		if (hr != NOERROR)
		    ErrMsg("SetMasterStream failed!");
	    }
	    break;
	case MENU_AUDIOMASTER:
	    gcap.iMasterStream = 1;
	    if (gcap.pConfigAviMux) {
		hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream);
		if (hr != NOERROR)
		    ErrMsg("SetMasterStream failed!");
	    }
	    break;
	case MENU_VIDEOMASTER:
	    gcap.iMasterStream = 0;
	    if (gcap.pConfigAviMux) {
		hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream);
		if (hr != NOERROR)
		    ErrMsg("SetMasterStream failed!");
	    }
	    break;

	// toggle capturing audio
	case MENU_CAP_AUDIO:
	    if (gcap.fPreviewing)
		StopPreview();
	    gcap.fCapAudio = !gcap.fCapAudio;
	    // when we capture we'll need a different graph now
	    if (gcap.fCaptureGraphBuilt || gcap.fPreviewGraphBuilt)
		TearDownGraph();
	    if (gcap.fWantPreview) {
		BuildPreviewGraph();
		StartPreview();
	    }
	    break;

	// toggle closed captioning
	case MENU_CAP_CC:
	    if (gcap.fPreviewing)
		StopPreview();
	    gcap.fCapCC = !gcap.fCapCC;
	    // when we capture we'll need a different graph now
	    if (gcap.fCaptureGraphBuilt || gcap.fPreviewGraphBuilt)
		TearDownGraph();
	    if (gcap.fWantPreview) {
		BuildPreviewGraph();
		StartPreview();
	    }
	    break;

	// choose the audio capture format
	//
	case MENU_AUDIOFORMAT:
	    ChooseAudioFormat();
	    break;

	// pick a frame rate
	//
	case MENU_FRAMERATE:
	    ChooseFrameRate();
	    break;

	// pick a time limit
	//
	case MENU_TIMELIMIT:
	    ChooseTimeLimit();
	    break;

	// pick which video capture device to use
	// pick which video capture device to use
	//
	case MENU_VDEVICE0:
	case MENU_VDEVICE1:
	case MENU_VDEVICE2:
	case MENU_VDEVICE3:
	case MENU_VDEVICE4:
	case MENU_VDEVICE5:
	case MENU_VDEVICE6:
	case MENU_VDEVICE7:
	case MENU_VDEVICE8:
	case MENU_VDEVICE9:
	    ChooseDevices(gcap.rgpmVideoMenu[id - MENU_VDEVICE0], gcap.pmAudio);
	    break;

	// pick which audio capture device to use
	//
	case MENU_ADEVICE0:
	case MENU_ADEVICE1:
	case MENU_ADEVICE2:
	case MENU_ADEVICE3:
	case MENU_ADEVICE4:
	case MENU_ADEVICE5:
	case MENU_ADEVICE6:
	case MENU_ADEVICE7:
	case MENU_ADEVICE8:
	case MENU_ADEVICE9:
	    ChooseDevices(gcap.pmVideo, gcap.rgpmAudioMenu[id - MENU_ADEVICE0]);
	    break;

	// video format dialog
	//
	case MENU_DIALOG0:
	case MENU_DIALOG1:
	case MENU_DIALOG2:
	case MENU_DIALOG3:
	case MENU_DIALOG4:
	case MENU_DIALOG5:
	case MENU_DIALOG6:
	case MENU_DIALOG7:
	case MENU_DIALOG8:
	case MENU_DIALOG9:
	case MENU_DIALOGA:
	case MENU_DIALOGB:
	case MENU_DIALOGC:
	case MENU_DIALOGD:
	case MENU_DIALOGE:
	case MENU_DIALOGF:

 	    // they want the VfW format dialog
	    if (id - MENU_DIALOG0 == gcap.iFormatDialogPos) {
		// this dialog will not work while previewing
		if (gcap.fWantPreview)
		    StopPreview();
		HRESULT hrD;
	        hrD = gcap.pDlg->ShowDialog(VfwCaptureDialog_Format, ghwndApp);
		// Oh uh!  Sometimes bringing up the FORMAT dialog can result
		// in changing to a capture format that the current graph 
		// can't handle.  It looks like that has happened and we'll
		// have to rebuild the graph.
		if (hrD == VFW_E_CANNOT_CONNECT) {
    		    DbgLog((LOG_TRACE,1,TEXT("DIALOG CORRUPTED GRAPH!")));
		    TearDownGraph();	// now we need to rebuild
		    // !!! This won't work if we've left a stranded h/w codec
		}

		// Resize our window to be the same size that we're capturing
	        if (gcap.pVSC) {
		    AM_MEDIA_TYPE *pmt;
		    // get format being used NOW
		    hr = gcap.pVSC->GetFormat(&pmt);
	    	    // DV capture does not use a VIDEOINFOHEADER
            	    if (hr == NOERROR) {
	 		if (pmt->formattype == FORMAT_VideoInfo) {
		            // resize our window to the new capture size
		            ResizeWindow(HEADER(pmt->pbFormat)->biWidth,
					abs(HEADER(pmt->pbFormat)->biHeight));
			}
		        DeleteMediaType(pmt);
		    }
	        }

	        if (gcap.fWantPreview) {
		    BuildPreviewGraph();
		    StartPreview();
		}
	    } else if (id - MENU_DIALOG0 == gcap.iSourceDialogPos) {
		// this dialog will not work while previewing
		if (gcap.fWantPreview)
		    StopPreview();
	        gcap.pDlg->ShowDialog(VfwCaptureDialog_Source, ghwndApp);
	        if (gcap.fWantPreview)
		    StartPreview();
	    } else if (id - MENU_DIALOG0 == gcap.iDisplayDialogPos) {
		// this dialog will not work while previewing
		if (gcap.fWantPreview)
		    StopPreview();
	        gcap.pDlg->ShowDialog(VfwCaptureDialog_Display, ghwndApp);
	        if (gcap.fWantPreview)
		    StartPreview();

	    // now the code for the new dialogs

	    } else if (id - MENU_DIALOG0 == gcap.iVCapDialogPos) {
		ISpecifyPropertyPages *pSpec;
		CAUUID cauuid;
    	        hr = gcap.pVCap->QueryInterface(IID_ISpecifyPropertyPages,
							(void **)&pSpec);
    		if (hr == S_OK) {
        	    hr = pSpec->GetPages(&cauuid);
                    hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
                    (IUnknown **)&gcap.pVCap, cauuid.cElems,
		    (GUID *)cauuid.pElems, 0, 0, NULL);
		    CoTaskMemFree(cauuid.pElems);
		    pSpec->Release();
		}

	    } else if (id - MENU_DIALOG0 == gcap.iVCapCapturePinDialogPos) {
		// You can change this pin's output format in these dialogs.
		// If the capture pin is already connected to somebody who's 
		// fussy about the connection type, that may prevent using 
		// this dialog(!) because the filter it's connected to might not
		// allow reconnecting to a new format. (EG: you switch from RGB
                // to some compressed type, and need to pull in a decoder)
		// I need to tear down the graph downstream of the
		// capture filter before bringing up these dialogs.
		// In any case, the graph must be STOPPED when calling them.
		if (gcap.fWantPreview)
		    StopPreview();	// make sure graph is stopped
			// The capture pin that we are trying to set the format on is connected if
			// one of these variable is set to TRUE. The pin should be disconnected for
			// the dialog to work properly.
			if (gcap.fCaptureGraphBuilt || gcap.fPreviewGraphBuilt) {
				DbgLog((LOG_TRACE,1,TEXT("Tear down graph for dialog")));
				TearDownGraph();	// graph could prevent dialog working
			}
    		IAMStreamConfig *pSC;
    		hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Interleaved, gcap.pVCap,
				IID_IAMStreamConfig, (void **)&pSC);
	 	if (hr != NOERROR)
    		    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Video, gcap.pVCap,
				IID_IAMStreamConfig, (void **)&pSC);
		ISpecifyPropertyPages *pSpec;
		CAUUID cauuid;
    	        hr = pSC->QueryInterface(IID_ISpecifyPropertyPages,
							(void **)&pSpec);
    		if (hr == S_OK) {
        	    hr = pSpec->GetPages(&cauuid);
                    hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
                    (IUnknown **)&pSC, cauuid.cElems,
		    (GUID *)cauuid.pElems, 0, 0, NULL);

		    // !!! What if changing output formats couldn't reconnect
		    // and the graph is broken?  Shouldn't be possible...
		
	            if (gcap.pVSC) {
		        AM_MEDIA_TYPE *pmt;
		        // get format being used NOW
		        hr = gcap.pVSC->GetFormat(&pmt);
	    	        // DV capture does not use a VIDEOINFOHEADER
            	        if (hr == NOERROR) {
	 		    if (pmt->formattype == FORMAT_VideoInfo) {
		                // resize our window to the new capture size
		                ResizeWindow(HEADER(pmt->pbFormat)->biWidth,
					  abs(HEADER(pmt->pbFormat)->biHeight));
			    }
		            DeleteMediaType(pmt);
		        }
	            }

		    CoTaskMemFree(cauuid.pElems);
		    pSpec->Release();
		}
		pSC->Release();
	        if (gcap.fWantPreview) {
		    BuildPreviewGraph();
		    StartPreview();
		}


	    } else if (id - MENU_DIALOG0 == gcap.iVCapPreviewPinDialogPos) {
		// this dialog may not work if the preview pin is connected
		// already, because the downstream filter may reject a format
		// change, so we better kill the graph. (EG: We switch from 
                // capturing RGB to some compressed fmt, and need to pull in
                // a decompressor)
		if (gcap.fWantPreview) {
		    StopPreview();
		    TearDownGraph();
		}
    		IAMStreamConfig *pSC;
		// This dialog changes the preview format, so it might affect
		// the format being drawn.  Our app's window size is taken
		// from the size of the capture pin's video, not the preview
		// pin, so changing that here won't have any effect. All in all,
		// this probably won't be a terribly useful dialog in this app.
    		hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW,
				&MEDIATYPE_Interleaved, gcap.pVCap,
				IID_IAMStreamConfig, (void **)&pSC);
		if (hr != NOERROR)
    		    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW,
				&MEDIATYPE_Video, gcap.pVCap,
				IID_IAMStreamConfig, (void **)&pSC);
		ISpecifyPropertyPages *pSpec;
		CAUUID cauuid;
    	        hr = pSC->QueryInterface(IID_ISpecifyPropertyPages,
							(void **)&pSpec);
    		if (hr == S_OK) {
        	    hr = pSpec->GetPages(&cauuid);
                    hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
                    (IUnknown **)&pSC, cauuid.cElems,
		    (GUID *)cauuid.pElems, 0, 0, NULL);
		    CoTaskMemFree(cauuid.pElems);
		    pSpec->Release();
		}
		pSC->Release();
		if (gcap.fWantPreview) {
		    BuildPreviewGraph();
		    StartPreview();
		}

	    } else if (id - MENU_DIALOG0 == gcap.iVCrossbarDialogPos) {
    		IAMCrossbar *pX;
    		hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Interleaved, gcap.pVCap,
				IID_IAMCrossbar, (void **)&pX);
		if (hr != NOERROR)
    		    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Video, gcap.pVCap,
				IID_IAMCrossbar, (void **)&pX);
		ISpecifyPropertyPages *pSpec;
		CAUUID cauuid;
    	        hr = pX->QueryInterface(IID_ISpecifyPropertyPages,
							(void **)&pSpec);
    		if (hr == S_OK) {
        	    hr = pSpec->GetPages(&cauuid);
                    hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
                    (IUnknown **)&pX, cauuid.cElems,
		    (GUID *)cauuid.pElems, 0, 0, NULL);
		    CoTaskMemFree(cauuid.pElems);
		    pSpec->Release();
		}
		pX->Release();

	    } else if (id - MENU_DIALOG0 == gcap.iTVTunerDialogPos) {
    		IAMTVTuner *pTV;
    		hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Interleaved, gcap.pVCap,
				IID_IAMTVTuner, (void **)&pTV);
		if (hr != NOERROR)
    		    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Video, gcap.pVCap,
				IID_IAMTVTuner, (void **)&pTV);
		ISpecifyPropertyPages *pSpec;
		CAUUID cauuid;
    	        hr = pTV->QueryInterface(IID_ISpecifyPropertyPages,
							(void **)&pSpec);
    		if (hr == S_OK) {
        	    hr = pSpec->GetPages(&cauuid);
                    hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
                    (IUnknown **)&pTV, cauuid.cElems,
		    (GUID *)cauuid.pElems, 0, 0, NULL);
		    CoTaskMemFree(cauuid.pElems);
		    pSpec->Release();
		}
		pTV->Release();

	    } else if (id - MENU_DIALOG0 == gcap.iACapDialogPos) {
		ISpecifyPropertyPages *pSpec;
		CAUUID cauuid;
    	        hr = gcap.pACap->QueryInterface(IID_ISpecifyPropertyPages,
							(void **)&pSpec);
    		if (hr == S_OK) {
        	    hr = pSpec->GetPages(&cauuid);
                    hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
                    (IUnknown **)&gcap.pACap, cauuid.cElems,
		    (GUID *)cauuid.pElems, 0, 0, NULL);
		    CoTaskMemFree(cauuid.pElems);
		    pSpec->Release();
		}

	    } else if (id - MENU_DIALOG0 == gcap.iACapCapturePinDialogPos) {
		// this dialog will not work while previewing - it might change
		// the output format!
		if (gcap.fWantPreview)
		    StopPreview();
    		IAMStreamConfig *pSC;
    		hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Audio, gcap.pACap,
				IID_IAMStreamConfig, (void **)&pSC);
		ISpecifyPropertyPages *pSpec;
		CAUUID cauuid;
    	        hr = pSC->QueryInterface(IID_ISpecifyPropertyPages,
							(void **)&pSpec);
    		if (hr == S_OK) {
        	    hr = pSpec->GetPages(&cauuid);
                    hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
                    (IUnknown **)&pSC, cauuid.cElems,
		    (GUID *)cauuid.pElems, 0, 0, NULL);
		    CoTaskMemFree(cauuid.pElems);
		    pSpec->Release();
		}
		pSC->Release();
	        if (gcap.fWantPreview)
		    StartPreview();

	    } else if (id - MENU_DIALOG0 == gcap.iACrossbarDialogPos) {
    		IAMCrossbar *pX, *pX2;
		IBaseFilter *pXF;
		// we could use better error checking here... I'm assuming
		// this won't fail
    		hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Interleaved, gcap.pVCap,
				IID_IAMCrossbar, (void **)&pX);
		if (hr != NOERROR)
    		    hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Video, gcap.pVCap,
				IID_IAMCrossbar, (void **)&pX);
		hr = pX->QueryInterface(IID_IBaseFilter, (void **)&pXF);
    		hr = gcap.pBuilder->FindInterface(&LOOK_UPSTREAM_ONLY, NULL,
				pXF, IID_IAMCrossbar, (void **)&pX2);
		ISpecifyPropertyPages *pSpec;
		CAUUID cauuid;
    	        hr = pX2->QueryInterface(IID_ISpecifyPropertyPages,
							(void **)&pSpec);
    		if (hr == S_OK) {
        	    hr = pSpec->GetPages(&cauuid);
                    hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
                    (IUnknown **)&pX2, cauuid.cElems,
		    (GUID *)cauuid.pElems, 0, 0, NULL);
		    CoTaskMemFree(cauuid.pElems);
		    pSpec->Release();
		}
		pX2->Release();
		pXF->Release();
		pX->Release();

	    } else if (id - MENU_DIALOG0 == gcap.iTVAudioDialogPos) {
    		IAMTVAudio *pTVA;
    		hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
				&MEDIATYPE_Audio, gcap.pACap,
				IID_IAMTVAudio, (void **)&pTVA);
		ISpecifyPropertyPages *pSpec;
		CAUUID cauuid;
    	        hr = pTVA->QueryInterface(IID_ISpecifyPropertyPages,
							(void **)&pSpec);
    		if (hr == S_OK) {
        	    hr = pSpec->GetPages(&cauuid);
                    hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
                    (IUnknown **)&pTVA, cauuid.cElems,
		    (GUID *)cauuid.pElems, 0, 0, NULL);
		    CoTaskMemFree(cauuid.pElems);
		    pSpec->Release();
		}
		pTVA->Release();

        } else if (((id - MENU_DIALOG0) >  gcap.iVideoInputMenuPos) && 
                    (id - MENU_DIALOG0) <= gcap.iVideoInputMenuPos + gcap.NumberOfVideoInputs) {
            // Remove existing checks
            for (int j = 0; j < gcap.NumberOfVideoInputs; j++) {

                CheckMenuItem(gcap.hMenuPopup, j, MF_BYPOSITION | 
                              ((j == (id - MENU_DIALOG0) - gcap.iVideoInputMenuPos - 1) ?
                              MF_CHECKED : MF_UNCHECKED )); 
            }

            if (gcap.pCrossbar) {
                EXECUTE_ASSERT (S_OK == gcap.pCrossbar->SetInputIndex ((id - MENU_DIALOG0) - gcap.iVideoInputMenuPos - 1));
            }

	}

	break;

    }
    return 0L;
}


/*----------------------------------------------------------------------------*\
|   ErrMsg - Opens a Message box with a error message in it.  The user can     |
|            select the OK button to continue                                  |
\*----------------------------------------------------------------------------*/
int ErrMsg (LPTSTR sz,...)
{
    static TCHAR tach[2000];
    va_list va;

    va_start(va, sz);
    wvsprintf (tach, sz, va);
    va_end(va);
    MessageBox(ghwndApp, tach, NULL, MB_OK|MB_ICONEXCLAMATION|MB_TASKMODAL);
    return FALSE;
}


/* AboutDlgProc()
 *
 * Dialog Procedure for the "about" dialog box.
 *
 */

BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg) {
	case WM_COMMAND:
		EndDialog(hwnd, TRUE);
		return TRUE;
	case WM_INITDIALOG:
		return TRUE;
	}
	return FALSE;
}


// pre-allocate the capture file
//
BOOL AllocCaptureFile(HWND hWnd)
{
    USES_CONVERSION;

    // we'll get into an infinite loop in the dlg proc setting a value
    if (gcap.szCaptureFile[0] == 0)
	return FALSE;

    /*
     * show the allocate file space dialog to encourage
     * the user to pre-allocate space
     */
    if (DoDialog(hWnd, IDD_AllocCapFileSpace, (DLGPROC)AllocCapFileProc, 0)) {

	// ensure repaint after dismissing dialog before
	// possibly lengthy operation
	UpdateWindow(ghwndApp);

	// User has hit OK. Alloc requested capture file space
	BOOL f = MakeBuilder();
	if (!f)
	    return FALSE;
	if (gcap.pBuilder->AllocCapFile(T2W(gcap.szCaptureFile),
		(DWORDLONG)gcap.wCapFileSize * 1024L * 1024L) != NOERROR) {
	    MessageBox(ghwndApp, TEXT("Error"),
			TEXT("Failed to pre-allocate capture file space"),
			MB_OK | MB_ICONEXCLAMATION);
	    return FALSE;
	}
	return TRUE;
    } else {
	return FALSE;
    }
}


/*
 * Put up the open file dialog
 */
BOOL OpenFileDialog(HWND hWnd, LPTSTR pszName, int cb)
{
    OPENFILENAME ofn;
    LPTSTR p;
    TCHAR        szFileName[_MAX_PATH];
    TCHAR        szBuffer[_MAX_PATH] ;

    if (pszName == NULL || cb <= 0)
	return FALSE;

    // start with capture file as current file name
    szFileName[0] = 0;
    lstrcpy(szFileName, gcap.szCaptureFile);

    // Get just the path info
    // Terminate the full path at the last backslash
    lstrcpy(szBuffer, szFileName);
    for (p = szBuffer + lstrlen(szBuffer); p > szBuffer; p--) {
	if (*p == '\\') {
	    *(p+1) = '\0';
	    break;
	}
    }

    _fmemset(&ofn, 0, sizeof(OPENFILENAME)) ;
    ofn.lStructSize = sizeof(OPENFILENAME) ;
    ofn.hwndOwner = hWnd ;
    ofn.lpstrFilter = "Microsoft AVI\0*.avi\0\0";
    ofn.nFilterIndex = 0 ;
    ofn.lpstrFile = szFileName;
    ofn.nMaxFile = sizeof(szFileName) ;
    ofn.lpstrFileTitle = NULL;
    ofn.lpstrTitle = "Set Capture File";
    ofn.nMaxFileTitle = 0 ;
    ofn.lpstrInitialDir = szBuffer;
    ofn.Flags = OFN_HIDEREADONLY | OFN_NOREADONLYRETURN | OFN_PATHMUSTEXIST ;

    if (GetOpenFileName(&ofn)) {
	// We have a capture file name
	lstrcpyn(pszName, szFileName, cb);
	return TRUE;
    } else {
	return FALSE;
    }
}


/*
 * Put up a dialog to allow the user to select a capture file.
 */
BOOL SetCaptureFile(HWND hWnd)
{
    USES_CONVERSION;

    if (OpenFileDialog(hWnd, gcap.szCaptureFile, _MAX_PATH)) {
	OFSTRUCT os;

	// We have a capture file name

	/*
	 * if this is a new file, then invite the user to
	 * allocate some space
	 */
	if (OpenFile(gcap.szCaptureFile, &os, OF_EXIST) == HFILE_ERROR) {

	    // bring up dialog, and set new file size
	    BOOL f = AllocCaptureFile(hWnd);
	    if (!f)
		return FALSE;
	}
    } else {
	return FALSE;
    }

    SetAppCaption();    // new a new app caption

    // tell the file writer to use the new filename
    if (gcap.pSink) {
	gcap.pSink->SetFileName(T2W(gcap.szCaptureFile), NULL);
    }

    return TRUE;
}


/*
 * Put up a dialog to allow the user to save the contents of the capture file
 * elsewhere
 */
BOOL SaveCaptureFile(HWND hWnd)
{
    USES_CONVERSION;
    HRESULT hr;
    TCHAR tachDstFile[_MAX_PATH];

    if (gcap.pBuilder == NULL)
	return FALSE;

    if (OpenFileDialog(hWnd, tachDstFile, _MAX_PATH)) {

	// We have a capture file name
	statusUpdateStatus(ghwndStatus, TEXT("Saving capture file - please wait..."));

	// we need our own graph builder because the main one might not exist
	ICaptureGraphBuilder2 *pBuilder;
	hr = CoCreateInstance((REFCLSID)CLSID_CaptureGraphBuilder2,
			NULL, CLSCTX_INPROC, (REFIID)IID_ICaptureGraphBuilder2,
			(void **)&pBuilder);

	if (hr == NOERROR) {
	    // allow the user to press ESC to abort... ask for progress
        CProgress *pProg = new CProgress(TEXT(""), NULL, &hr);
        IAMCopyCaptureFileProgress *pIProg = NULL;
        if (pProg) {
            hr = pProg->QueryInterface(IID_IAMCopyCaptureFileProgress,
                                            (void **)&pIProg);
        }
	    hr = pBuilder->CopyCaptureFile(T2W(gcap.szCaptureFile),
                                       T2W(tachDstFile), TRUE, pIProg);
        if (pIProg)
            pIProg->Release();
	    pBuilder->Release();
	}

	if (hr == S_OK)
	    statusUpdateStatus(ghwndStatus, "Capture file saved");
	else if (hr == S_FALSE)
	    statusUpdateStatus(ghwndStatus, "Capture file save aborted");
	else
	    statusUpdateStatus(ghwndStatus, "Capture file save ERROR");
	return (hr == NOERROR ? TRUE : FALSE); 

    } else {
	return TRUE;    // they cancelled or something
    }
}

// brings up a dialog box
//
int DoDialog(HWND hwndParent, int DialogID, DLGPROC fnDialog, long lParam)
{
    DLGPROC fn;
    int result;

    fn = (DLGPROC)MakeProcInstance(fnDialog, ghInstApp);
    result = DialogBoxParam(
		ghInstApp,
		MAKEINTRESOURCE(DialogID),
		hwndParent,
		fn,
		lParam);
    FreeProcInstance(fn);

    return result;
}


//
// GetFreeDiskSpace: Function to Measure Available Disk Space
//
static long GetFreeDiskSpaceInKB(LPSTR pFile)
{
    DWORD dwFreeClusters, dwBytesPerSector, dwSectorsPerCluster, dwClusters;
    char RootName[MAX_PATH];
    LPSTR ptmp;    //required arg
    ULARGE_INTEGER ulA, ulB, ulFreeBytes;

    // need to find path for root directory on drive containing
    // this file.

    GetFullPathName(pFile, sizeof(RootName), RootName, &ptmp);

    // truncate this to the name of the root directory (god how tedious)
    if (RootName[0] == '\\' && RootName[1] == '\\') {

	// path begins with  \\server\share\path so skip the first
	// three backslashes
	ptmp = &RootName[2];
	while (*ptmp && (*ptmp != '\\')) {
	    ptmp++;
	}
	if (*ptmp) {
	    // advance past the third backslash
	    ptmp++;
	}
    } else {
	// path must be drv:\path
	ptmp = RootName;
    }

    // find next backslash and put a null after it
    while (*ptmp && (*ptmp != '\\')) {
	ptmp++;
    }
    // found a backslash ?
    if (*ptmp) {
	// skip it and insert null
	ptmp++;
	*ptmp = '\0';
    }

    // the only real way of finding out free disk space is calling
    // GetDiskFreeSpaceExA, but it doesn't exist on Win95

    HINSTANCE h = LoadLibrary(TEXT("kernel32.dll"));
    if (h) {
	typedef BOOL (WINAPI *MyFunc)(LPCSTR RootName, PULARGE_INTEGER pulA, PULARGE_INTEGER pulB, PULARGE_INTEGER pulFreeBytes);
	MyFunc pfnGetDiskFreeSpaceEx = (MyFunc)GetProcAddress(h,
						"GetDiskFreeSpaceExA");
	FreeLibrary(h);
	if (pfnGetDiskFreeSpaceEx) {
	    if (!pfnGetDiskFreeSpaceEx(RootName, &ulA, &ulB, &ulFreeBytes))
		return -1;
	    return (long)(ulFreeBytes.QuadPart / 1024);
	}
    }

    if (!GetDiskFreeSpace(RootName, &dwSectorsPerCluster, &dwBytesPerSector,
					&dwFreeClusters, &dwClusters))
	return (-1);
    return(MulDiv(dwSectorsPerCluster * dwBytesPerSector,
		   dwFreeClusters,
		   1024));
}



// AllocCapFileProc: Capture file Space Allocation Dialog Box Procedure
//
int FAR PASCAL AllocCapFileProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam)
{
    static int      nFreeMBs = 0 ;

    switch (Message) {
	case WM_INITDIALOG:
	{
	    DWORDLONG        dwlFileSize = 0;
	    long             lFreeSpaceInKB;

	    // Get current capture file name and measure its size
	    dwlFileSize = GetSize(gcap.szCaptureFile);

	    // Get free disk space and add current capture file size to that.
	    // Convert the available space to MBs.
	    if ((lFreeSpaceInKB =
			GetFreeDiskSpaceInKB(gcap.szCaptureFile)) != -1L) {
		lFreeSpaceInKB += (long)(dwlFileSize / 1024);
		nFreeMBs = lFreeSpaceInKB / 1024 ;
		SetDlgItemInt(hDlg, IDD_SetCapFileFree, nFreeMBs, TRUE) ;
	    } else {
		EnableWindow(GetDlgItem(hDlg, IDD_SetCapFileFree), FALSE);
	    }

	    gcap.wCapFileSize = (WORD) (dwlFileSize / (1024L * 1024L));

	    SetDlgItemInt(hDlg, IDD_SetCapFileSize, gcap.wCapFileSize, TRUE) ;
	    return TRUE ;
	}

	case WM_COMMAND :
	    switch (GET_WM_COMMAND_ID(wParam, lParam)) {
		case IDOK :
		{
		    int         iCapFileSize ;

		    iCapFileSize = (int) GetDlgItemInt(hDlg, IDD_SetCapFileSize, NULL, TRUE) ;
		    if (iCapFileSize <= 0 || iCapFileSize > nFreeMBs) {
			// You are asking for more than we have !! Sorry, ...
			SetDlgItemInt(hDlg, IDD_SetCapFileSize, iCapFileSize, TRUE) ;
			SetFocus(GetDlgItem(hDlg, IDD_SetCapFileSize)) ;
			MessageBeep(MB_ICONEXCLAMATION) ;
			return FALSE ;
		    }
		    gcap.wCapFileSize = (WORD)iCapFileSize ;

		    EndDialog(hDlg, TRUE) ;
		    return TRUE ;
		}

		case IDCANCEL :
		    EndDialog(hDlg, FALSE) ;
		    return TRUE ;

		case IDD_SetCapFileSize:
		{
		    long l;
		    BOOL bchanged;
		    TCHAR tachBuffer[21];

		    // check that entered size is a valid number
		    GetDlgItemText(hDlg, IDD_SetCapFileSize, tachBuffer,
                                                        sizeof(tachBuffer));
		    l = atol(tachBuffer);
		    bchanged = FALSE;
		    if (l < 1) {
			l = 1;
			bchanged = TRUE;
		    // don't infinite loop if there's < 1 Meg free
		    } else if (l > nFreeMBs && nFreeMBs > 0) {
			l = nFreeMBs;
			bchanged = TRUE;
		    } else {
			// make sure there are no non-digit chars
			// atol() will ignore trailing non-digit characters
			int c = 0;
			while (tachBuffer[c]) {
			    if (IsCharAlpha(tachBuffer[c]) ||
				!IsCharAlphaNumeric(tachBuffer[c])) {

				// string contains non-digit chars - reset
				l = 1;
				bchanged = TRUE;
				break;
			    }
			    c++;
			}
		    }
		    if (bchanged) {
			wsprintf(tachBuffer, TEXT("%ld"), l);
			SetDlgItemText(hDlg, IDD_SetCapFileSize, tachBuffer);
		    }
		    break;
		}
	    }
	    break ;
    }

    return FALSE ;
}


//
// FrameRateProc: Choose a frame rate
//
int FAR PASCAL FrameRateProc(HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
  TCHAR  tach[32];
  
  switch (msg) {
    case WM_INITDIALOG:
	/* put the current frame rate in the box */
	sprintf(tach, TEXT("%f"), gcap.FrameRate, tach);
	SetDlgItemText(hwnd, IDC_FRAMERATE, tach);
	CheckDlgButton(hwnd, IDC_USEFRAMERATE, gcap.fUseFrameRate);
	break;
	
    case WM_COMMAND:
	switch(wParam){
	    case IDCANCEL:
		EndDialog(hwnd, FALSE);
		break;
		
	    case IDOK:
		/* get the new frame rate */
		GetDlgItemText(hwnd, IDC_FRAMERATE, tach, sizeof(tach));
		if (atof(tach) <= 0.) {
		    ErrMsg("Invalid frame rate.");
		    break;
		}
		gcap.FrameRate = atof(tach);
		gcap.fUseFrameRate = IsDlgButtonChecked(hwnd, IDC_USEFRAMERATE);
		EndDialog(hwnd, TRUE);
		break;
	}
	break;
	
    default:
	return FALSE;
  }
  return TRUE;
}


//
// TimeLimitProc: Choose a capture time limit
//
int FAR PASCAL TimeLimitProc(HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
  TCHAR   tach[32];
  DWORD   dwTimeLimit;
  
  switch (msg) {
    case WM_INITDIALOG:
	/* put the current time limit info in the boxes */
	sprintf(tach, TEXT("%d"), gcap.dwTimeLimit, tach);
	SetDlgItemText(hwnd, IDC_TIMELIMIT, tach);
	CheckDlgButton(hwnd, IDC_USETIMELIMIT, gcap.fUseTimeLimit);
	break;
	
    case WM_COMMAND:
	switch(wParam){
	    case IDCANCEL:
		EndDialog(hwnd, FALSE);
		break;
		
	    case IDOK:
		/* get the new time limit */
		dwTimeLimit = GetDlgItemInt(hwnd, IDC_TIMELIMIT, NULL, FALSE);
		gcap.dwTimeLimit = dwTimeLimit;
		gcap.fUseTimeLimit = IsDlgButtonChecked(hwnd, IDC_USETIMELIMIT);
		EndDialog(hwnd, TRUE);
		break;
	}
	break;
	
    default:
	return FALSE;
  }
  return TRUE;
}


//
// PressAKeyProc: Press OK to capture
//
int FAR PASCAL PressAKeyProc(HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
  TCHAR tach[_MAX_PATH];

  switch (msg) {
    case WM_INITDIALOG:
	/* set the current file name in the box */
	wsprintf(tach, TEXT("%s"), gcap.szCaptureFile);
	SetDlgItemText(hwnd, IDC_CAPFILENAME, tach);
	break;
	
    case WM_COMMAND:
	switch(wParam){
	    case IDCANCEL:
		EndDialog(hwnd, FALSE);
		break;
		
	    case IDOK:
		EndDialog(hwnd, TRUE);
		break;
	}
	break;
	
    default:
	return FALSE;
  }
  return TRUE;
}

DWORDLONG GetSize(LPCTSTR tach)
{
    HANDLE hFile = CreateFile(tach, GENERIC_READ, FILE_SHARE_READ, 0,
				OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

    if (hFile == INVALID_HANDLE_VALUE) {
	return 0;
    }

    DWORD dwSizeHigh;
    DWORD dwSizeLow = GetFileSize(hFile, &dwSizeHigh);

    DWORDLONG dwlSize = dwSizeLow + ((DWORDLONG)dwSizeHigh << 32);

    if (!CloseHandle(hFile)) {
	dwlSize = 0;
    }

    return dwlSize;
}
