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

#include <streams.h>

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

//------------------------------------------------------------------------------
// Macros
//------------------------------------------------------------------------------
#define ABS(x) (((x) > 0) ? (x) : -(x))

// An application can advertise the existence of its filter graph
// by registering the graph with a global Running Object Table (ROT).
// The GraphEdit application can detect and remotely view the running
// filter graph, allowing you to 'spy' on the graph with GraphEdit.
//
// To enable registration in this sample, define REGISTER_FILTERGRAPH.
//
#ifdef  DEBUG
#define REGISTER_FILTERGRAPH
#endif


//------------------------------------------------------------------------------
// Global data
//------------------------------------------------------------------------------
HINSTANCE ghInstApp=0;
HACCEL ghAccel=0;
HFONT  ghfontApp=0;
TEXTMETRIC gtm={0};
TCHAR gszAppName[]=TEXT("AMCAP");
HWND ghwndApp=0, ghwndStatus=0;
HDEVNOTIFY ghDevNotify=0;
PUnregisterDeviceNotification gpUnregisterDeviceNotification=0;
PRegisterDeviceNotification gpRegisterDeviceNotification=0;
DWORD g_dwGraphRegister=0;

struct _capstuff
{
    TCHAR szCaptureFile[_MAX_PATH];
    WORD wCapFileSize;  // size in Meg
    ISampleCaptureGraphBuilder *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 fMPEG2;
    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%%\0"), i);
        statusUpdateStatus(ghwndStatus, tach);
        return S_OK;
    };
};

CComModule _Module;

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

void 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(TCHAR *szVideo, TCHAR *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();
void OnClose();

// Adds/removes a DirectShow filter graph from the Running Object Table,
// allowing GraphEdit to "spy" on a remote filter graph if enabled.
HRESULT AddGraphToRot(IUnknown *pUnkGraph, DWORD *pdwRegister);
void RemoveGraphFromRot(DWORD pdwRegister);


//------------------------------------------------------------------------------
// Name: SetAppCaption()
// Desc: Set the caption to be the application name followed by the capture file
//------------------------------------------------------------------------------
void SetAppCaption()
{
    TCHAR tach[_MAX_PATH + 80];

    lstrcpyn(tach, gszAppName, NUMELMS(tach));
    if(gcap.szCaptureFile[0] != 0)
    {
        lstrcat(tach, TEXT(" - "));
        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;

    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
    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    = (WNDPROC) 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);

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

    // Read the list of devices to use from win.ini
    ZeroMemory(gcap.rgpmAudioMenu, sizeof(gcap.rgpmAudioMenu));
    ZeroMemory(gcap.rgpmVideoMenu, sizeof(gcap.rgpmVideoMenu));
    gcap.pmVideo = 0;
    gcap.pmAudio = 0;
    gcap.fMPEG2  = FALSE;

    TCHAR szVideoDisplayName[1024], szAudioDisplayName[1024];
    *szAudioDisplayName = *szVideoDisplayName = 0; // null terminate

    GetProfileString(TEXT("annie"), TEXT("VideoDevice2"), TEXT(""),
        szVideoDisplayName, NUMELMS(szVideoDisplayName));
    GetProfileString(TEXT("annie"), TEXT("AudioDevice2"), TEXT(""),
        szAudioDisplayName, NUMELMS(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 desired
    // 
    // Make these the official devices we're using

    ChooseDevices(szVideoDisplayName, szAudioDisplayName);

    // 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)
                break;  // Leave the PeekMessage while() loop

            if(TranslateAccelerator(ghwndApp, ghAccel, &msg))
                continue;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        if(msg.message == WM_QUIT)
            break;  // Leave the for() loop

        WaitMessage();
    }

    // Reached on WM_QUIT message
    CoUninitialize();
    return ((int) 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);

            // MPEG2 enabled ?
            CheckMenuItem((HMENU)wParam, MENU_MPEG2, 
                (gcap.fMPEG2) ? MF_CHECKED : MF_UNCHECKED);

            // can toggle MPEG2 if not capturing
            EnableMenuItem((HMENU)wParam, MENU_MPEG2,
                !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]);
                }
            }

            PostQuitMessage(0);
            break;


        case WM_CLOSE:
            OnClose();
            break;

        case WM_ENDSESSION:
            if(wParam || (lParam & ENDSESSION_LOGOFF))
            {
                OnClose();
            }
            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;
                HRESULT hrAbort = S_OK;
                BOOL bAbort = FALSE;
                while(gcap.pME->GetEvent(&event, (LONG_PTR *) &l1, 
                    (LONG_PTR *) &l2, 0) == S_OK)
                {
                    gcap.pME->FreeEventParams(event, l1, l2);
                    if(event == EC_ERRORABORT)
                    {
                        StopCapture();
                        bAbort = TRUE;
                        hrAbort = l1;
                        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 *) (LONG_PTR) l1;
                                if(S_OK == punk->QueryInterface(IID_IBaseFilter, (void **) &pf))
                                {
                                    if(::IsEqualObject(gcap.pVCap, pf))
                                    {
                                        pf->Release();
                                        bAbort = FALSE;
                                        StopCapture();
                                        TCHAR szError[100];
                                        _tcscpy(szError, 
                                            TEXT("Stopping Capture (Device Lost). Select New Capture Device\0"));
                                        ErrMsg(szError);
                                        break;
                                    }
                                    pf->Release();
                                }
                            }
                    }
                } // end while
                if(bAbort)
                {
                        if(gcap.fWantPreview)
                        {
                            BuildPreviewGraph();
                            StartPreview();
                        }
                        TCHAR szError[100];
                        wsprintf(szError, TEXT("ERROR during capture, error code=%08x\0"), hrAbort);
                        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 (LONG) 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;

    gcap.pBuilder = new ISampleCaptureGraphBuilder( );
    if( NULL == gcap.pBuilder )
    {
        return FALSE;
    }
    
    return TRUE;
}


// 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)
{
    IPin *pP=0, *pTo=0;
    ULONG u;
    IEnumPins *pins = NULL;
    PIN_INFO pininfo;

    if (!pf)
        return;

    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()
{
    SAFE_RELEASE(gcap.pSink);
    SAFE_RELEASE(gcap.pConfigAviMux);
    SAFE_RELEASE(gcap.pRender);
    SAFE_RELEASE(gcap.pME);
    SAFE_RELEASE(gcap.pDF);

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

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

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

#ifdef REGISTER_FILTERGRAPH
    // Remove filter graph from the running object table   
    if(g_dwGraphRegister)
    {
        RemoveGraphFromRot(g_dwGraphRegister);
        g_dwGraphRegister = 0;
    }
#endif

    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=S_OK;
    BOOL f;

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

    f = MakeBuilder();
    if(!f)
    {
        ErrMsg(TEXT("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(TEXT("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(TEXT("Cannot instantiate filtergraph"));
        goto InitCapFiltersFail;
    }

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

    // Add the video capture filter to the graph with its friendly name
    hr = gcap.pFg->AddFilter(gcap.pVCap, gcap.wachFriendlyName);
    if(hr != NOERROR)
    {
        ErrMsg(TEXT("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(TEXT("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=0;
        ULONG        n;
        PIN_INFO     pinInfo;
        BOOL         Found = FALSE;
        IKsPropertySet *pKs=0;
        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)
                        {
                            HRESULT hrCreate=S_OK;
                            gcap.pCrossbar = new CCrossbar(pP, &hrCreate);
                            if (!gcap.pCrossbar || FAILED(hrCreate))
                                break;
                                
                            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(TEXT("Cannot create audio capture filter"));
        goto SkipAudio;
    }

    //
    // put the audio capture filter in the graph
    //
    {
        WCHAR wachAudioFriendlyName[256];
        IPropertyBag *pBag;

        wachAudioFriendlyName[0] = 0;

        // Read the friendly name of the filter to assist with remote graph
        // viewing through GraphEdit
        hr = gcap.pmAudio->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(wachAudioFriendlyName, var.bstrVal);
                SysFreeString(var.bstrVal);
            }

            pBag->Release();
        }

        // We'll need this in the graph to get audio property pages
        hr = gcap.pFg->AddFilter(gcap.pACap, wachAudioFriendlyName);
        if(hr != NOERROR)
        {
            ErrMsg(TEXT("Error %x: Cannot add audio capture filter 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(TEXT("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()
{
    SAFE_RELEASE(gcap.pFg);
    if( gcap.pBuilder )
    {
        delete gcap.pBuilder;
        gcap.pBuilder = NULL;
    }
    SAFE_RELEASE(gcap.pVCap);
    SAFE_RELEASE(gcap.pACap);
    SAFE_RELEASE(gcap.pASC);
    SAFE_RELEASE(gcap.pVSC);
    SAFE_RELEASE(gcap.pVC);
    SAFE_RELEASE(gcap.pDlg);

    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=0;

    // 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;
    if( gcap.fMPEG2 )
    {
        guid = MEDIASUBTYPE_Mpeg2;
    }
    else
    {
        guid = MEDIASUBTYPE_Avi;
    }

    hr = gcap.pBuilder->SetOutputFileName(&guid, T2W(gcap.szCaptureFile),
                                          &gcap.pRender, &gcap.pSink);
    if(hr != NOERROR)
    {
        ErrMsg(TEXT("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

    if( !gcap.fMPEG2 )
    {
        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(TEXT("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.

    if( !gcap.fMPEG2 )
    {
        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(TEXT("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(TEXT("Cannot render video preview stream"));
                    goto SetupCaptureFail;
                }
            }
        }
    }
    else
    {
        CComPtr< IBaseFilter > sink;
        if( &gcap.pSink )
        {
            gcap.pSink->QueryInterface( IID_IBaseFilter, reinterpret_cast<void **>( &sink ) );
        }

        hr = gcap.pBuilder->RenderStream(NULL,
                                         &MEDIATYPE_Stream,
                                         gcap.pVCap, NULL, sink);
    }

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

    if(!gcap.fMPEG2 && gcap.fCapAudio)
    {
        hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio, 
                                         gcap.pACap, NULL, gcap.pRender);
        if(hr != NOERROR)
        {
            ErrMsg(TEXT("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.fMPEG2  && 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(TEXT("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.

    if(!gcap.fMPEG2 && gcap.fWantPreview)
    {
        hr = gcap.pFg->QueryInterface(IID_IVideoWindow, (void **)&gcap.pVW);
        if(hr != NOERROR && gcap.fWantPreview)
        {
            ErrMsg(TEXT("This graph cannot preview"));
        }
        else if(hr == NOERROR)
        {
            RECT rc;
            gcap.pVW->put_Owner((OAHWND)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
    if( !gcap.fMPEG2 )
    {
        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(TEXT("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((OAHWND)ghwndApp, WM_FGNOTIFY, 0);
    }

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

    // Add our graph to the running object table, which will allow
    // the GraphEdit application to "spy" on our graph
#ifdef REGISTER_FILTERGRAPH
    hr = AddGraphToRot(gcap.pFg, &g_dwGraphRegister);
    if(FAILED(hr))
    {
        ErrMsg(TEXT("Failed to register filter graph with ROT!  hr=0x%x"), hr);
        g_dwGraphRegister = 0;
    }
#endif

    // All done.
    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;

    // 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.

    if( gcap.fMPEG2 )
    {
        hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW,
                                         &MEDIATYPE_Stream, gcap.pVCap, NULL, NULL);
        if( FAILED( hr ) )
        {
            ErrMsg(TEXT("Cannot build MPEG2 preview graph!"));
        }

    }
    else
    {
        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(TEXT("This graph cannot preview!"));
                gcap.fPreviewGraphBuilt = FALSE;
                return FALSE;
            }
        }

        //
        // 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(TEXT("Cannot render closed captioning"));
                }
            }
        }
    }

    //
    // 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(TEXT("This graph cannot preview properly"));
    }
    else
    {
        //Find out if this is a DV stream
        AM_MEDIA_TYPE * pmtDV;

        if(gcap.pVSC && SUCCEEDED(gcap.pVSC->GetFormat(&pmtDV)))
        {
            if(pmtDV->formattype == FORMAT_DvInfo)
            {
                // in this case we want to set the size of the parent window to that of 
                // current DV resolution.
                // We get that resolution from the IVideoWindow.
                CComQIPtr <IBasicVideo, &IID_IBasicVideo> pBV(gcap.pVW);

                if(pBV != NULL)
                {   
                    HRESULT hr1, hr2;
                    long lWidth, lHeight;

                    hr1 = pBV->get_VideoHeight(&lHeight);
                    hr2 = pBV->get_VideoWidth(&lWidth);
                    if(SUCCEEDED(hr1) && SUCCEEDED(hr2))
                    {
                        ResizeWindow(lWidth, abs(lHeight));
                    }
                }   
            }
        }

        RECT rc;
        gcap.pVW->put_Owner((OAHWND)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(TEXT("%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((OAHWND)ghwndApp, WM_FGNOTIFY, 0);
    }

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

    // Add our graph to the running object table, which will allow
    // the GraphEdit application to "spy" on our graph
#ifdef REGISTER_FILTERGRAPH
    hr = AddGraphToRot(gcap.pFg, &g_dwGraphRegister);
    if(FAILED(hr))
    {
        ErrMsg(TEXT("Failed to register filter graph with ROT!  hr=0x%x"), hr);
        g_dwGraphRegister = 0;
    }
#endif

    // All done.
    gcap.fPreviewGraphBuilt = TRUE;
    return TRUE;
}


// Start previewing
//
BOOL StartPreview()
{
    // 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(TEXT("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(TEXT("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);

    // 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(TEXT("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(TEXT("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);
        // make note of the current dropped frame counts

        if(gcap.pDF)
        {
            gcap.pDF->GetNumDropped(&gcap.lDroppedBase);
            gcap.pDF->GetNumNotDropped(&gcap.lNotBase);
        } 
    }
    else
    {
        hr = pMC->Run();
        if(FAILED(hr))
        {
            // stop parts that started
            pMC->Stop();
            pMC->Release();
            ErrMsg(TEXT("Error %x: Cannot run graph"), hr);
            return FALSE;
        }
    }

    pMC->Release();

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

    // update status bar 30 times per second - #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(TEXT("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 && SUCCEEDED(gcap.pDlg->HasDialog(VfwCaptureDialog_Format)))
    {
        AppendMenu(hMenuSub, MF_STRING, MENU_DIALOG0 + zz, TEXT("Video Format..."));
        gcap.iFormatDialogPos = zz++;
    }
    if(gcap.pDlg && SUCCEEDED(gcap.pDlg->HasDialog(VfwCaptureDialog_Source)))
    {
        AppendMenu(hMenuSub, MF_STRING, MENU_DIALOG0 + zz, TEXT("Video Source..."));
        gcap.iSourceDialogPos = zz++;
    }
    if(gcap.pDlg && SUCCEEDED(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_PTR)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=0, 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\0"), 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\0"),
             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)
{
#define VERSIZE 40
#define DESCSIZE 80

    USES_CONVERSION;
    int versize = VERSIZE;
    int descsize = DESCSIZE;
    WCHAR wachVer[VERSIZE]={0}, wachDesc[DESCSIZE]={0};
    TCHAR tachStatus[VERSIZE + DESCSIZE + 5]={0};


    // 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)
        {
            // It's possible that the call succeeded without actually filling
            // in information for description and version.  If these strings
            // are empty, just display the device's friendly name.
            if(wcslen(wachDesc) && wcslen(wachVer))
            {
                wsprintf(tachStatus, TEXT("%s - %s\0"), W2T(wachDesc), W2T(wachVer));
                statusUpdateStatus(ghwndStatus, tachStatus);
                return;
            }
        }
    }

    // Since the GetInfo method failed (or the interface did not exist),
    // display the device's friendly name.
    statusUpdateStatus(ghwndStatus, W2T(gcap.wachFriendlyName));
}

void ChooseDevices(TCHAR *szVideo, TCHAR *szAudio)
{
    WCHAR wszVideo[1024],  wszAudio[1024];

#ifndef UNICODE
    MultiByteToWideChar(CP_ACP, 0, szVideo, -1, wszVideo, NUMELMS(wszVideo));
    MultiByteToWideChar(CP_ACP, 0, szAudio, -1, wszAudio, NUMELMS(wszAudio));
#else
    wcsncpy(wszVideo, T2W(szVideo), NUMELMS(wszVideo)-1);
    wcsncpy(wszAudio, T2W(szAudio), NUMELMS(wszAudio)-1);
#endif
    wszVideo[1023] = wszAudio[1023] = 0;    // Null-terminate

    IBindCtx *lpBC=0;
    IMoniker *pmVideo = 0, *pmAudio = 0;

    HRESULT hr = CreateBindCtx(0, &lpBC);
    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(TEXT("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=0;
    hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
                          IID_ICreateDevEnum, (void**)&pCreateDevEnum);
    if(hr != NOERROR)
    {
        ErrMsg(TEXT("Error Creating Device Enumerator"));
        return;
    }

    IEnumMoniker *pEm=0;
    hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEm, 0);
    if(hr != NOERROR)
    {
        ErrMsg(TEXT("Sorry, you have no video capture hardware.\r\n\r\n")
               TEXT("Video capture will not function properly."));
        goto EnumAudio;
    }

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

    while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)
    {
        IPropertyBag *pBag=0;

        hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);
        if(SUCCEEDED(hr))
        {
            VARIANT var;
            var.vt = VT_BSTR;
            hr = pBag->Read(L"FriendlyName", &var, NULL);
            if(hr == NOERROR)
            {
                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));
                EnableMenuItem(hMenuSub, MENU_VDEVICE0 + uIndex,
                    (gcap.fCapturing ? MF_DISABLED : MF_ENABLED));
                bCheck = FALSE;

                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));
                EnableMenuItem(hMenuSub, MENU_ADEVICE0 + uIndex,
                    (gcap.fCapturing ? MF_DISABLED : MF_ENABLED));
                bCheck = FALSE;

                SysFreeString(var.bstrVal);

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

    pEm->Release();
}


// Allow the user to choose 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(TEXT("%x: Cannot set new frame rate"), hr);
                }
                DeleteMediaType(pmt);
            }
        }

        if(hr != NOERROR)
            ErrMsg(TEXT("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 = (DWORD) (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
    lpwfx = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, dwSize);
    if(lpwfx)
    {
        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;

        // Exit the application
        //
        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;

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

            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(TEXT("SetMasterStream failed!"));
            }
            break;

        case MENU_AUDIOMASTER:
            gcap.iMasterStream = 1;
            if(gcap.pConfigAviMux)
            {
                hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream);
                if(hr != NOERROR)
                    ErrMsg(TEXT("SetMasterStream failed!"));
            }
            break;

        case MENU_VIDEOMASTER:
            gcap.iMasterStream = 0;
            if(gcap.pConfigAviMux)
            {
                hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream);
                if(hr != NOERROR)
                    ErrMsg(TEXT("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);

                // 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                                  |
\*----------------------------------------------------------------------------*/
void ErrMsg(LPTSTR szFormat,...)
{
    static TCHAR szBuffer[2048]={0};
    const size_t NUMCHARS = sizeof(szBuffer) / sizeof(szBuffer[0]);
    const int LASTCHAR = NUMCHARS - 1;

    // Format the input string
    va_list pArgs;
    va_start(pArgs, szFormat);

    // Use a bounded buffer size to prevent buffer overruns.  Limit count to
    // character size minus one to allow for a NULL terminating character.
    _vsntprintf(szBuffer, NUMCHARS - 1, szFormat, pArgs);
    va_end(pArgs);

    // Ensure that the formatted string is NULL-terminated
    szBuffer[LASTCHAR] = TEXT('\0');

    MessageBox(ghwndApp, szBuffer, NULL, 
               MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL);
}


/* 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;
    lstrcpyn(szFileName, gcap.szCaptureFile, NUMELMS(szFileName));

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

    ZeroMemory(&ofn, sizeof(OPENFILENAME)) ;
    ofn.lStructSize   = sizeof(OPENFILENAME) ;
    ofn.hwndOwner     = hWnd ;
    ofn.lpstrFilter   = TEXT("Microsoft AVI\0*.avi\0\0");
    ofn.nFilterIndex  = 0 ;
    ofn.lpstrFile     = szFileName;
    ofn.nMaxFile      = sizeof(szFileName) ;
    ofn.lpstrFileTitle = NULL;
    ofn.lpstrTitle    = TEXT("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
#ifdef UNICODE
        // Convert Multibyte string to ANSI
        char szCaptureFile[STR_MAX_LENGTH];
        int rc = WideCharToMultiByte(CP_ACP, 0, gcap.szCaptureFile, -1, 
                                     szCaptureFile, STR_MAX_LENGTH, NULL, NULL);
#else
        TCHAR *szCaptureFile = gcap.szCaptureFile;
#endif

        if(OpenFile(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();

    // 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, TEXT("Capture file saved"));
        else if(hr == S_FALSE)
            statusUpdateStatus(ghwndStatus, TEXT("Capture file save aborted"));
        else
            statusUpdateStatus(ghwndStatus, TEXT("Capture file save ERROR"));

        return (hr == NOERROR ? TRUE : FALSE); 
    }
    else
    {
        return TRUE;    // they cancelled or something
    }
}


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

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

    return result;
}


//
// This function can be used when loading libraries in the Windows System
// folder.  It queries for the system folder and prepends the path to the
// name of the target library.  This helps to prevent spoofing of system DLLs.
//
// NOTE: Using this function may break apps who use Windows Fusion or who
// explicitly replace Windows DLLs in the application's local folder.
// If security is paramount, you can use this as a replacement for LoadLibrary().
//
HMODULE UtilLoadLibrary(LPCTSTR lpFileName)
{
    TCHAR szFullPath[MAX_PATH];
    BOOL fSuccess = FALSE;

    // Prevent buffer overflow by limiting size of library name
    int nLibLength = lstrlen(lpFileName);
    if((nLibLength > MAX_PATH) || (NULL == lpFileName))
        return NULL;

    int nSpaceAllowed = MAX_PATH - nLibLength - 4; // Allow for '\' and '\0'
    int nSpaceUsed = 0;

    // Initialize to a NULL string
    szFullPath[0] = TEXT('\0');

    // Read the Windows System directory
    nSpaceUsed = GetSystemDirectory(szFullPath, nSpaceAllowed);

    // If the function fails, the return value will be zero.
    // If the buffer isn't large enough, the function will return the size
    // of the buffer required to hold the path.  Check both failures.
    if((nSpaceUsed != 0) && (nSpaceUsed <= nSpaceAllowed))
    {
        // Now we have the Windows/System path with enough space left
        // to add the libary name and a terminating NULL character
        lstrcat(szFullPath, TEXT("\\\0"));
        lstrcat(szFullPath, lpFileName);
        lstrcat(szFullPath, TEXT("\0"));
        fSuccess = TRUE;
    }

    if(!fSuccess)
    {
        // An error occurred, so fallback to the default behavior by using 
        // the NULL-terminated library name provided as a parameter.
        lstrcpyn(szFullPath, lpFileName, NUMELMS(szFullPath)-1);
        szFullPath[MAX_PATH - 1] = TEXT('\0');
    }

    return ((HMODULE) LoadLibrary(szFullPath));
}


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

    // need to find path for root directory on drive containing this file.
    GetFullPathName(pFile, NUMELMS(RootName), RootName, &ptmp);

    // truncate this to the name of the root directory (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\0"));
    if(h)
    {
        typedef BOOL(WINAPI *MyFunc)(LPCTSTR RootName, PULARGE_INTEGER pulA, PULARGE_INTEGER pulB, PULARGE_INTEGER pulFreeBytes);

#ifdef UNICODE
        MyFunc pfnGetDiskFreeSpaceEx = (MyFunc)GetProcAddress(h, "GetDiskFreeSpaceExW");
#else
        MyFunc pfnGetDiskFreeSpaceEx = (MyFunc)GetProcAddress(h, "GetDiskFreeSpaceExA");
#endif
        FreeLibrary(h);

        if(pfnGetDiskFreeSpaceEx)
        {
            if(!pfnGetDiskFreeSpaceEx(RootName, &ulA, &ulB, &ulFreeBytes))
                return -1;
            else
                return (long)(ulFreeBytes.QuadPart / 1024);
        }
    }

    if(!GetDiskFreeSpace(RootName, &dwSectorsPerCluster, &dwBytesPerSector,
                         &dwFreeClusters, &dwClusters))
        return (-1);
    else
        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)/sizeof(tachBuffer[0]));

                    l = _ttol(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\0"), 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];
    USES_CONVERSION;

    switch(msg)
    {
        case WM_INITDIALOG:
            /* put the current frame rate in the box */
            wsprintf(tach, TEXT("%d\0"), (int) gcap.FrameRate);
            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)/sizeof(tach[0]));

#ifdef UNICODE
                int rc;

                // Convert Multibyte string to ANSI
                char szANSI[STR_MAX_LENGTH];
                rc = WideCharToMultiByte(CP_ACP, 0, tach, -1, szANSI, 
                    STR_MAX_LENGTH, NULL, NULL);
                double frameRate = atof(szANSI);
#else
                double frameRate = atof(tach);
#endif

                if(frameRate <= 0.)
                {
                    ErrMsg(TEXT("Invalid frame rate."));
                    break;
                }
                else
                    gcap.FrameRate = frameRate;

                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 */
            wsprintf(tach, TEXT("%d\0"), gcap.dwTimeLimit);
            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\0"), 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;
}

void OnClose()
{
    TCHAR szBuf[512];
    WCHAR *wszDisplayName = NULL;

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

    // Destroy the filter graph and cleanup
    StopPreview();
    StopCapture();
    TearDownGraph();
    FreeCapFilters();

    //
    // Store current settings in win.ini for next time
    //

    // Save the name of the current capture file
    WriteProfileString(TEXT("annie"), TEXT("CaptureFile"), gcap.szCaptureFile);

    wszDisplayName = 0;
    szBuf[0] = NULL;

    if(gcap.pmVideo)
    {
        if(SUCCEEDED(gcap.pmVideo->GetDisplayName(0, 0, &wszDisplayName)))
        {   
            if(wszDisplayName)
            {
                USES_CONVERSION;
                _tcsncpy(szBuf, W2T(wszDisplayName), NUMELMS(szBuf));
                CoTaskMemFree(wszDisplayName);
            }
        }
    }

    // Save the current video device string
    WriteProfileString(TEXT("annie"), TEXT("VideoDevice2"), szBuf);

    wszDisplayName = 0;
    szBuf[0] = NULL;
    
    if(gcap.pmAudio)
    {
        if(SUCCEEDED(gcap.pmAudio->GetDisplayName(0, 0, &wszDisplayName)))
        {
            if(wszDisplayName)
            {
                USES_CONVERSION;
                _tcsncpy(szBuf, W2T(wszDisplayName), NUMELMS(szBuf));
                CoTaskMemFree(wszDisplayName);
            }
        }
    }

    // Save the current audio device string
    WriteProfileString(TEXT("annie"), TEXT("AudioDevice2"), szBuf);

    // Save the integer settings
    wsprintf(szBuf, TEXT("%d"), (int)(10000000 / gcap.FrameRate));
    WriteProfileString(TEXT("annie"), TEXT("FrameRate"), szBuf);

    wsprintf(szBuf, TEXT("%d"), gcap.fUseFrameRate);
    WriteProfileString(TEXT("annie"), TEXT("UseFrameRate"), szBuf);

    wsprintf(szBuf, TEXT("%d"), gcap.fCapAudio);
    WriteProfileString(TEXT("annie"), TEXT("CaptureAudio"), szBuf);

    wsprintf(szBuf, TEXT("%d"), gcap.fCapCC);
    WriteProfileString(TEXT("annie"), TEXT("CaptureCC"), szBuf);

    wsprintf(szBuf, TEXT("%d"), gcap.fWantPreview);
    WriteProfileString(TEXT("annie"), TEXT("WantPreview"), szBuf);

    wsprintf(szBuf, TEXT("%d"), gcap.iMasterStream);
    WriteProfileString(TEXT("annie"), TEXT("MasterStream"), szBuf);

    wsprintf(szBuf, TEXT("%d"), gcap.fUseTimeLimit);
    WriteProfileString(TEXT("annie"), TEXT("UseTimeLimit"), szBuf);

    wsprintf(szBuf, TEXT("%d"), gcap.dwTimeLimit);
    WriteProfileString(TEXT("annie"), TEXT("TimeLimit"), szBuf);
}



// Adds a DirectShow filter graph to the Running Object Table,
// allowing GraphEdit to "spy" on a remote filter graph.
HRESULT AddGraphToRot(IUnknown *pUnkGraph, DWORD *pdwRegister)
{
    IMoniker * pMoniker;
    IRunningObjectTable *pROT;
    WCHAR wsz[128];
    HRESULT hr;

    if (!pUnkGraph || !pdwRegister)
        return E_POINTER;

    if(FAILED(GetRunningObjectTable(0, &pROT)))
        return E_FAIL;

    wsprintfW(wsz, L"FilterGraph %08x pid %08x\0", (DWORD_PTR)pUnkGraph, 
        GetCurrentProcessId());

    hr = CreateItemMoniker(L"!", wsz, &pMoniker);
    if(SUCCEEDED(hr))
    {
        // Use the ROTFLAGS_REGISTRATIONKEEPSALIVE to ensure a strong reference
        // to the object.  Using this flag will cause the object to remain
        // registered until it is explicitly revoked with the Revoke() method.
        //
        // Not using this flag means that if GraphEdit remotely connects
        // to this graph and then GraphEdit exits, this object registration 
        // will be deleted, causing future attempts by GraphEdit to fail until
        // this application is restarted or until the graph is registered again.
        hr = pROT->Register(ROTFLAGS_REGISTRATIONKEEPSALIVE, pUnkGraph, 
                            pMoniker, pdwRegister);
        pMoniker->Release();
    }

    pROT->Release();
    return hr;
}

// Removes a filter graph from the Running Object Table
void RemoveGraphFromRot(DWORD pdwRegister)
{
    IRunningObjectTable *pROT;

    if(SUCCEEDED(GetRunningObjectTable(0, &pROT)))
    {
        pROT->Revoke(pdwRegister);
        pROT->Release();
    }
}

