//------------------------------------------------------------------------------
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
//  PURPOSE.
// File: Wmcap.cpp
//
// Desc: Audio/Video Capture sample for DirectShow
//
//@@BEGIN_MSINTERNAL
//
//8/20/99 - BrianMar - Initial port from AMovie Tree
//TODO - test UNICODE and 64BIT builds
//TODO - Clean up all comments
//TODO - Possible rewrite using MFC
//
//@@END_MSINTERNAL
// Copyright (c) 1998-2003, Microsoft Corporation. All rights reserved.
//------------------------------------------------------------------------------



#define WINVER 0x0500

#include <windows.h>
#include <dbt.h>
#include <streams.h>
#include <mmreg.h>
#include <msacm.h>
#include <fcntl.h>
#include <io.h>
#include <stdio.h>
#include <commdlg.h>
//#include "stdafx.h"
#include <atlbase.h>
#include <KS.h>
#include <Ksmedia.h>
#include "amcap.h"
#include "status.h"
#include "crossbar.h"
#include "resource.h"
#include <initguid.h>
#include "autpguids.h"
#include "iaudiotp.h"

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

#define SAFERELEASE( p )    \
{                           \
    if ( p ) {              \
        p->Release();       \
        p = NULL;           \
    }                       \
}

#define FL      0x00000001
#define FR      0x00000002
#define FC      0x00000004
#define SUBW    0x00000008
#define BL      0x00000010
#define BR      0x00000020
#define FLC     0x00000040
#define FRC     0x00000080


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

} gcap;


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

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

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

CComModule _Module;

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


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


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

    const DWORD  dwExStyle = 0;

    CoInitialize(NULL);
    DbgInitialise(hInst);

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

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

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

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

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

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

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

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

    // get which devices to use from win.ini
    ZeroMemory(gcap.rgpmAudioMenu, sizeof(gcap.rgpmAudioMenu));
    ZeroMemory(gcap.rgpmVideoMenu, sizeof(gcap.rgpmVideoMenu));
    gcap.pmVideo = 0;
    gcap.pmAudio = 0;
	gcap.fWaveFormatEx = TRUE; // Default
	ZeroMemory(&(gcap.WaveExIn), sizeof(WAVEFORMATEX));
	ZeroMemory(&(gcap.WaveExtIn), sizeof(WAVEFORMATEXTENSIBLE));

	gcap.ConversionFilter = NULL;
    gcap.ConversionProperties = NULL;
    gcap.ConversionFilterID = &(CLSID_AudioTap);
    gcap.ConversionInterfaceID = &(IID_IAudioTap);
    gcap.ConversionName = "AudioTap";
    gcap.isXP = FALSE;


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

    gcap.fDeviceMenuPopulated = false;
    AddDevicesToMenu();

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

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


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

    // Getting the operating system
    ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
    if( !(bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi)) )
    {
        // If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO.

          osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
        if (! GetVersionEx ( (OSVERSIONINFO *) &osvi) )
        {
            ErrMsg("Can't get OS version. Defaulting to XP");
            gcap.isXP = TRUE;
        }
     }

    if (VER_PLATFORM_WIN32_NT == osvi.dwPlatformId)
    {
         if ( osvi.dwMajorVersion >= 5 && osvi.dwMinorVersion >= 1 )
            gcap.isXP = TRUE;
    }
    else
        gcap.isXP = FALSE;



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

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

	ghDevNotify = NULL;
	ghDevNotify = RegisterDeviceNotification(ghwndApp, &filterData, DEVICE_NOTIFY_WINDOW_HANDLE);
	
	ASSERT(ghDevNotify != NULL);

    SetAppCaption();
    return TRUE;
}

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

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

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

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

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

	WaitMessage();
    }

    // not reached
    return msg.wParam;
}


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

    switch (msg) {

	//
	//
	case WM_CREATE:
	    break;

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

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

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

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

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

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


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

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

		CoUninitialize();
	    PostQuitMessage(0);
	    break;

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

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

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

	    break;

	case WM_ERASEBKGND:
	    break;

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

	    // nothing to do

	    EndPaint(hwnd,&ps);
	    break;

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

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

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

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

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

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


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

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


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

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


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

int gnRecurse = 0;

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

    gnRecurse++;

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

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

    gnRecurse--;
}


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

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


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

//	if (gcap.ConversionFilter)
//		gcap.ConversionFilter->Release();
//	gcap.ConversionFilter = NULL;


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

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

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


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

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

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

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

    gcap.pVCap = NULL;
    if(gcap.pmVideo != 0)
    {
        IPropertyBag *pBag;
        gcap.wachFriendlyName[0] = 0;
        hr = gcap.pmVideo->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);
        if(SUCCEEDED(hr))
        {
            VARIANT var;
            var.vt = VT_BSTR;
            hr = pBag->Read(L"FriendlyName", &var, NULL);
            if (hr == NOERROR) {
                lstrcpyW(gcap.wachFriendlyName, var.bstrVal);
                SysFreeString(var.bstrVal);
            }
            pBag->Release();
        }
        hr = gcap.pmVideo->BindToObject(0, 0, IID_IBaseFilter, (void**)&gcap.pVCap);
    }

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

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

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

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

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

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

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

    gcap.fCapAudioIsRelevant = TRUE;

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

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


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

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

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

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

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

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

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


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

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

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


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

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

	if (gcap.fWaveFormatEx == FALSE)
	{
		DWORD dwSize;
		LPWAVEFORMATEX lpwfx;
		WAVEFORMATEXTENSIBLE *pExt = NULL;

		AM_MEDIA_TYPE *pmt;

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

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

            if (gcap.isXP)
            {
			    pExt = (WAVEFORMATEXTENSIBLE *)GlobalAllocPtr(GHND, dwSize);
			    if (pExt)
			    {
				    CopyMemory((void*)(&(pExt->Format)), (void*)(pmt->pbFormat), pmt->cbFormat);
				    pExt->Format.cbSize = 22;
				    pExt->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
				    pExt->Format.nSamplesPerSec = 48000;
                    pExt->Format.nChannels =6;
				    pExt->Format.wBitsPerSample = 32;
				    pExt->Format.nBlockAlign  = pExt->Format.nChannels * pExt->Format.wBitsPerSample /8;
				    pExt->Format.nAvgBytesPerSec = pExt->Format.nBlockAlign*pExt->Format.nSamplesPerSec;
				    pExt->Samples.wValidBitsPerSample = 24;
				    pExt->dwChannelMask = 63;
				    pExt->SubFormat =  KSDATAFORMAT_SUBTYPE_PCM;
			        
				    if(gcap.fPreviewing)
					    StopPreview();  // can't call IAMStreamConfig::SetFormat

				    // while streaming
				    ((CMediaType *)pmt)->SetFormat((LPBYTE)pExt,
						    sizeof(WAVEFORMATEXTENSIBLE));

				    hr =  gcap.pASC->SetFormat(pmt);  // filter will reconnect
				    if (hr == NOERROR)
					    CopyMemory(&(gcap.WaveExtIn),pExt, sizeof(WAVEFORMATEXTENSIBLE));
				    else
					    gcap.fWaveFormatEx = TRUE;

				    GlobalFreePtr(pExt) ;
				    if(gcap.fWantPreview)
					    StartPreview();
			    }
            }
            else
            {
				gcap.fWaveFormatEx = TRUE;
				GlobalFreePtr(pExt) ;
				if(gcap.fWantPreview)
    				StartPreview();
            }
		}

		DeleteMediaType(pmt);
	}

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




SkipAudio:

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

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

    return TRUE;

InitCapFiltersFail:
    FreeCapFilters();
    return FALSE;
}


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


// build the capture graph!
//
BOOL BuildCaptureGraph()
{
    USES_CONVERSION;
    int cy, cyBorder;
    HRESULT hr;
    BOOL f;
    AM_MEDIA_TYPE *pmt;    
//	WCHAR wszPinName[MAX_FILTER_NAME];
    IPin *InPin = NULL;
	IPin *OutPin = NULL;
	IPin *RenderPin = NULL;
	IPin *MuxPin = NULL;
	PIN_INFO PinInfoCaptureOut;
	PIN_INFO PinInfoTapIn;
	PIN_INFO PinInfoTapOut;
//	PIN_INFO PinInfoMUX;
    IEnumPins *pEnumPins = NULL;
    ULONG Fetched;
    IPin *pPin;
	BOOL fFound = FALSE;
//    AM_MEDIA_TYPE mediaType;
    IAudioTap *pAudioTap = NULL;


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

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

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

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

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

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

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

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

// Also, set the proper MASTER STREAM

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

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

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

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

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

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

    if (gcap.fCapAudio) 
	{
	
	// Create the conversion filter
		hr = CoCreateInstance( *(gcap.ConversionFilterID), NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void **) &(gcap.ConversionFilter) );
    	if ( FAILED( hr ) )
		{
			ErrMsg("Cannot create Tap filter");
			goto SetupCaptureFail;
		}

		if( *(gcap.ConversionInterfaceID) != GUID_NULL )
		{
			hr = gcap.ConversionFilter->QueryInterface( *(gcap.ConversionInterfaceID), (void **) &(gcap.ConversionProperties) );
			if ( FAILED( hr ) )
			{
				ErrMsg("Cannot get Tap filter Interface");
				goto SetupCaptureFail;
			}
		}
		
	
		hr = gcap.pFg->AddFilter(gcap.ConversionFilter, NULL);
		if (hr != NOERROR) 
		{
			ErrMsg("Cannot Add Tap filter");
			goto SetupCaptureFail;
		}
		
		
		hr = gcap.pACap->EnumPins( &pEnumPins );
		if( FAILED( hr ) )
		{
			ErrMsg("Cannot Enum Audio stream Pins");
			goto SetupCaptureFail;
		}

		do 
		{
			hr = pEnumPins->Next( 1, &pPin, &Fetched );
			if( hr != NOERROR && hr != E_FAIL )
				break;
			if( !pPin )
			{
				hr = S_OK;
				break;
			}
			PIN_DIRECTION PinDir;
			hr = pPin->QueryDirection( &PinDir );
			if( FAILED( hr ) )
			{
				ErrMsg("Cannot Query Audio stream Pins");
				goto SetupCaptureFail;
			}
			if( PinDir == PINDIR_OUTPUT )
			{
				fFound = TRUE;
				hr = pPin->QueryPinInfo(&PinInfoCaptureOut) ;
				if( FAILED( hr ) )
				{
					ErrMsg("Cannot Enum Audio stream Pins");
					goto SetupCaptureFail;
				}
				OutPin = pPin;
				break;
			}
			SAFERELEASE(pPin);
		} while( Fetched );
		SAFERELEASE( pEnumPins );
		if (fFound != TRUE )
		{
			ErrMsg("Cannot Find Audio capture pin");
			goto SetupCaptureFail;
		}

		hr = gcap.ConversionFilter->EnumPins( &pEnumPins );
		if( FAILED( hr ) )
		{
			ErrMsg("Cannot Enum Audio TAP Pins");
			goto SetupCaptureFail;
		}

		do 
		{
			hr = pEnumPins->Next( 1, &pPin, &Fetched );
			if( hr != NOERROR && hr != E_FAIL )
				break;
			if( !pPin )
			{
				hr = S_OK;
				break;
			}
			PIN_DIRECTION PinDir;
			hr = pPin->QueryDirection( &PinDir );
			if( FAILED( hr ) )
			{
				ErrMsg("Cannot Query Audio stream Pins");
				goto SetupCaptureFail;
			}
			if( PinDir == PINDIR_OUTPUT )
			{
				hr = pPin->QueryPinInfo(&PinInfoTapOut) ;
				if( FAILED( hr ) )
				{
					ErrMsg("Cannot Query Tap Pins");
					goto SetupCaptureFail;
				}

				RenderPin = pPin;
			}
			else if(PinDir == PINDIR_INPUT)
			{
				hr = pPin->QueryPinInfo(&PinInfoTapIn) ;
				if( FAILED( hr ) )
				{
					ErrMsg("Cannot Query Tap Pins");
					goto SetupCaptureFail;
				}

				InPin = pPin;
			}
			else
                SAFERELEASE(pPin);
		} while( Fetched );
		SAFERELEASE( pEnumPins );


/*		hr = gcap.pRender->EnumPins( &pEnumPins );
		if( FAILED( hr ) )
		{
			ErrMsg("Cannot Enum MUX Pins");
			goto SetupCaptureFail;
		}

		do 
		{
			hr = pEnumPins->Next( 1, &pPin, &Fetched );
			if( hr != NOERROR && hr != E_FAIL )
				break;
			if( !pPin )
			{
				hr = S_OK;
				break;
			}
			PIN_DIRECTION PinDir;
			hr = pPin->QueryDirection( &PinDir );
			if( FAILED( hr ) )
			{
				ErrMsg("Cannot Query MUX Pins");
				goto SetupCaptureFail;
			}
			if(PinDir == PINDIR_INPUT)
			{
                hr = pPin->ConnectionMediaType(&mediaType);  
                if( FAILED( hr ) )
                {
					ErrMsg("Cannot get MUX Pin Media Type");
					goto SetupCaptureFail;
                }
				if (mediaType.majortype == MEDIATYPE_Audio)
				{	
					MuxPin = pPin;
					hr = pPin->QueryPinInfo(&PinInfoMUX) ;
					if( FAILED( hr ) )
					{
						ErrMsg("Cannot Query MUX Pins");
						goto SetupCaptureFail;
					}
					break;
				}
			}
			SAFERELEASE(pPin);

		} while( Fetched );
		SAFERELEASE( pEnumPins );


		hr = gcap.pBuilder->FindPin(gcap.pRender,PINDIR_INPUT,NULL,&MEDIATYPE_Audio,FALSE, 0,	&MuxPin	);
		if (FAILED(hr))
		{
			ErrMsg("Cannot Get MUX audio Input pin");
			goto SetupCaptureFail;
		}

*/		
		
		
		
		
		if (InPin == NULL || OutPin == NULL || RenderPin == NULL /*|| MuxPin == NULL*/ )
		{
			ErrMsg("Cannot Find Audio capture pins");
			goto SetupCaptureFail;
		}

		hr = OutPin->Connect(InPin, NULL);
		if (FAILED(hr))
		{
			ErrMsg("Cannot Connect Audio Capture Filter to Tap Filter");
			goto SetupCaptureFail;
		}
		SAFERELEASE(InPin);
		SAFERELEASE(OutPin);




		hr = gcap.pBuilder->RenderStream(NULL,/* &PIN_CATEGORY_CAPTURE*/
				&MEDIATYPE_Audio, RenderPin, NULL, gcap.pRender);

		if (hr != NOERROR) 
		{
			ErrMsg("Cannot render audio capture stream");
			goto SetupCaptureFail;
		}
/*		hr = RenderPin->Connect(MuxPin, NULL);
		if (hr != NOERROR) 
		{
			ErrMsg("Cannot render audio capture stream");
			goto SetupCaptureFail;
		}*/
		SAFERELEASE(RenderPin);
//		SAFERELEASE(MuxPin);

        hr = gcap.ConversionFilter->QueryInterface(IID_IAudioTap, (void **)&pAudioTap);
		if (hr != NOERROR) 
		{
			ErrMsg("Cannot Query Tap filter");
			goto SetupCaptureFail;
		}
		hr = pAudioTap->put_Force_24BPS(TRUE);
		if (hr != NOERROR) 
		{
			ErrMsg("Cannot set 24bps");
			goto SetupCaptureFail;
		}


        SAFERELEASE(pAudioTap);


    }

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

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

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

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

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

   	hr = gcap.pFg->QueryInterface(IID_IVideoWindow, (void **)&gcap.pVW);
        if (hr != NOERROR && gcap.fWantPreview) {
	    ErrMsg("This graph cannot preview");
        } else if (hr == NOERROR) {
	    RECT rc;
	    gcap.pVW->put_Owner((long)ghwndApp);    // We own the window now
	    gcap.pVW->put_WindowStyle(WS_CHILD);    // you are now a child
	    // give the preview window all our space but where the status bar is
	    GetClientRect(ghwndApp, &rc);
	    cyBorder = GetSystemMetrics(SM_CYBORDER);
	    cy = statusGetHeight() + cyBorder;
	    rc.bottom -= cy;
	    gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); // be this big
	    gcap.pVW->put_Visible(OATRUE);
        }

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

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

// All done.

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

    gcap.fCaptureGraphBuilt = TRUE;
    return TRUE;

SetupCaptureFail:
    TearDownGraph();
    return FALSE;
}



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

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

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

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

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

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

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

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

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

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

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

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

    hr = gcap.pFg->QueryInterface(IID_IVideoWindow, (void **)&gcap.pVW);
    if (hr != NOERROR) {
	ErrMsg("This graph cannot preview properly");
    } else {
	RECT rc;
	gcap.pVW->put_Owner((long)ghwndApp);    // We own the window now
	gcap.pVW->put_WindowStyle(WS_CHILD);    // you are now a child
	// give the preview window all our space but where the status bar is
	GetClientRect(ghwndApp, &rc);
	cyBorder = GetSystemMetrics(SM_CYBORDER);
	cy = statusGetHeight() + cyBorder;
	rc.bottom -= cy;
	gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); // be this big
	gcap.pVW->put_Visible(OATRUE);
    }

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

// All done.

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

    gcap.fPreviewGraphBuilt = TRUE;
    return TRUE;
}


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

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

    if (!gcap.fPreviewGraphBuilt)
	return FALSE;

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

    gcap.fPreviewing = TRUE;
    return TRUE;
}


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

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

    gcap.fPreviewing = FALSE;

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

    return TRUE;
}


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

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

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

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

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

    REFERENCE_TIME start = MAX_TIME, stop = MAX_TIME;

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

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

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

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

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

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

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

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

    pMC->Release();

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

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

    gcap.fCapturing = TRUE;
    return TRUE;
}


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

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

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

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

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

    gcap.fCapturing = FALSE;

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

    return TRUE;
}


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


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

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

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

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

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

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

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

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

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

    ISpecifyPropertyPages *pSpec;
    CAUUID cauuid;

    // 1. the video capture filter itself

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

    // 2.  The video capture capture pin

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

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

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

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

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

    // 6.  The TVTuner

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

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

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

    // 8.  The Audio capture pin

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

    // 9.  The TV Audio filter

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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


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

    if(pmVideo) {
        pmVideo->AddRef();
    }
    if(pmAudio) {
        pmAudio->AddRef();
    }

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

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

    }

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

    }



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

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

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

        lpBC->Release();
    }

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

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

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


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

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

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

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

	}

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


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

    IEnumMoniker *pEm;
    hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
								&pEm, 0);
    if (hr != NOERROR) {
	ErrMsg("Sorry, you have no video capture hardware");
	goto EnumAudio;
    }
    pEm->Reset();
    ULONG cFetched;
    IMoniker *pM;
    while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)
    {
	IPropertyBag *pBag;
	hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);
	if(SUCCEEDED(hr)) {
	    VARIANT var;
	    var.vt = VT_BSTR;
	    hr = pBag->Read(L"FriendlyName", &var, NULL);
	    if (hr == NOERROR) {
		AppendMenu(hMenuSub, MF_STRING, MENU_VDEVICE0 + uIndex,
							W2T(var.bstrVal));

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

		SysFreeString(var.bstrVal);

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

    gcap.iNumVCapDevices = uIndex;

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

EnumAudio:

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

    hr = pCreateDevEnum->CreateClassEnumerator(CLSID_AudioInputDeviceCategory,
								&pEm, 0);
    pCreateDevEnum->Release();
    if (hr != NOERROR)
	return;
    pEm->Reset();
    while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)
    {
	IPropertyBag *pBag;
	hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);
	if(SUCCEEDED(hr)) {
	    VARIANT var;
	    var.vt = VT_BSTR;
	    hr = pBag->Read(L"FriendlyName", &var, NULL);
	    if (hr == NOERROR) {
		AppendMenu(hMenuSub, MF_STRING, MENU_ADEVICE0 + uIndex,
							W2T(var.bstrVal));

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

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



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

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

    HRESULT hr = E_FAIL;

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

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


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


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

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

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

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

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

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

void ApplyAudioFormat() 
{
//    ACMFORMATCHOOSE cfmt;
    DWORD dwSize;
    LPWAVEFORMATEX lpwfx;
	WAVEFORMATEXTENSIBLE *pExt = NULL;

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

    acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &dwSize);
    HRESULT hr = gcap.pASC->GetFormat(&pmt);
    if(hr != NOERROR)
        return;

	if (TRUE == gcap.fWaveFormatEx)
	{
		lpwfx = (LPWAVEFORMATEX)pmt->pbFormat;
	    dwSize = max(dwSize, lpwfx->cbSize + sizeof(WAVEFORMATEX));
		lpwfx = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, dwSize);
		if(lpwfx) 
		{
			CopyMemory(lpwfx, &(gcap.WaveExIn), sizeof(WAVEFORMATEX));
			if(gcap.fPreviewing)
				StopPreview();  // can't call IAMStreamConfig::SetFormat

			((CMediaType *)pmt)->SetFormat((LPBYTE)lpwfx,
				lpwfx->cbSize + sizeof(WAVEFORMATEX));

			hr =  gcap.pASC->SetFormat(pmt);  // filter will reconnect

			if(gcap.fWantPreview)
				StartPreview();
	
			GlobalFreePtr(lpwfx) ;
			if(hr != NOERROR)
				ErrMsg(TEXT("Cannot Set audio properties!"));
     	}
	}
	else
	{
		pExt = (WAVEFORMATEXTENSIBLE *)pmt->pbFormat;
		dwSize = max(dwSize, sizeof(WAVEFORMATEXTENSIBLE));
		pExt = (WAVEFORMATEXTENSIBLE *)GlobalAllocPtr(GHND, dwSize);
		if (pExt)
		{
			CopyMemory(pExt, &(gcap.WaveExtIn), sizeof(WAVEFORMATEXTENSIBLE));
			if(gcap.fPreviewing)
				StopPreview();  // can't call IAMStreamConfig::SetFormat

			// while streaming
			((CMediaType *)pmt)->SetFormat((LPBYTE)pExt,
					sizeof(WAVEFORMATEXTENSIBLE));

			hr =  gcap.pASC->SetFormat(pmt);  // filter will reconnect
		
			GlobalFreePtr(pExt) ;

			if(gcap.fWantPreview)
				StartPreview();

			if(hr != NOERROR)
				ErrMsg(TEXT("Cannot Set audio properties!"));
		}

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

void ChooseAudioFormat() 
{
    if(gcap.pASC == NULL)
        return;

    if (FALSE == DoDialog(ghwndApp, IDD_AudioPropDialog, (DLGPROC)AudioPropertiesProc, 0))
		return;
	
	ApplyAudioFormat(); 
}



/*----------------------------------------------------------------------------*\
|    AppCommand()
|
|    Process all of our WM_COMMAND messages.
\*----------------------------------------------------------------------------*/
LONG PASCAL AppCommand (HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam)
{
    HRESULT hr;
	HINSTANCE hinst = 0;
    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;

	case MENU_LAUNCHHELP:
		hinst = ShellExecute(
			hwnd, 
			"open",
			"wm9cap.chm", 
			NULL, 
			NULL,
			SW_SHOWNORMAL
		);

		if (hinst <= (HINSTANCE)32)
		    ErrMsg("Error launching help file");


	    break;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	    // now the code for the new dialogs

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

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

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

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


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

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

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

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

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

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

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

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

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

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

	}

	break;

    }
    return 0L;
}


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

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


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

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


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

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

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

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

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


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

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

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

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

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

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


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

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

	// We have a capture file name

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

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

    SetAppCaption();    // new a new app caption

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

    return TRUE;
}


// return S_OK if successful
// return S_FALSE if the user aborts, or the callback aborts
// return E_ if something goes wrong
//
HRESULT CopyCaptureFile(LPOLESTR lpwstrOld, LPOLESTR lpwstrNew, int fAllowEscAbort, IAMCopyCaptureFileProgress *lpCallback)
{
    if (lpwstrOld == NULL || lpwstrNew == NULL)
    return E_POINTER;

    if (0 == lstrcmpiW(lpwstrOld, lpwstrNew)) {
        // IF the source and destination names are the same, then there
        // is nothing to do
        return S_OK;
    }

    CComPtr <IGraphBuilder> pGraph;
    HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
              IID_IGraphBuilder, (void **)&pGraph);

    CComPtr <IBaseFilter> pMux, pWriter;
    hr = CoCreateInstance(CLSID_AviDest, NULL, CLSCTX_INPROC_SERVER,
              IID_IBaseFilter, (void **)&pMux);
    hr = CoCreateInstance(CLSID_FileWriter, NULL, CLSCTX_INPROC_SERVER,
              IID_IBaseFilter, (void **)&pWriter);

    if (pGraph == NULL || pMux == NULL || pWriter == NULL) {
        return E_OUTOFMEMORY;
    }

    CComQIPtr<IFileSinkFilter, &IID_IFileSinkFilter> pFS = pWriter;
    if (pFS) {
        hr = pFS->SetFileName(lpwstrNew, NULL);
        if (FAILED(hr)) {
            return hr;
        }
    }
    
    hr = pGraph->AddFilter(pMux, L"Mux");
    if (FAILED(hr)) {
        return hr;
    }

    // fully interleave the file so it's ready to play efficiently
    CComQIPtr<IConfigInterleaving, &IID_IConfigInterleaving> pConfigInterleaving
                                                        = pMux;
    if (pConfigInterleaving) {
    hr = pConfigInterleaving->put_Mode(INTERLEAVE_FULL);
        if (FAILED(hr)) {
            return hr;
        }
    }

    CComQIPtr<IConfigAviMux, &IID_IConfigAviMux> pCfgMux = pMux;
    CComQIPtr<IFileSinkFilter2, &IID_IFileSinkFilter2> pCfgFw = pWriter;

    if(pCfgMux) {
        // waste less space. The Compatiblity Index is for VFW
        // playback support. We only care about DShow
        hr = pCfgMux->SetOutputCompatibilityIndex(FALSE);
        if (FAILED(hr)) {
            return hr;
        }
    }

    // create new files each time
    if(pCfgFw) {
        hr = pCfgFw->SetMode(AM_FILE_OVERWRITE);
        if (FAILED(hr)) {
            return hr;
        }
    }
    
    hr = pGraph->AddFilter(pWriter, L"Writer");
    if (FAILED(hr)) {
        return hr;
    }

    // Keep a useless clock from being instantiated....
    CComQIPtr <IMediaFilter, &IID_IMediaFilter> pGraphF = pGraph;
    if (pGraphF) {
    hr = pGraphF->SetSyncSource(NULL);
        if (FAILED(hr)) {
            return hr;
        }
    }

    CComPtr <IPin> pMuxOut, pWriterIn;
    hr = gcap.pBuilder->FindPin(pMux, PINDIR_OUTPUT, NULL, NULL, TRUE, 0, &pMuxOut);
    hr = gcap.pBuilder->FindPin(pWriter, PINDIR_INPUT, NULL, NULL, TRUE, 0, &pWriterIn);
    if (pMuxOut == NULL || pWriterIn == NULL) {
        return E_UNEXPECTED;
    }

    hr = pGraph->ConnectDirect(pMuxOut, pWriterIn, NULL);
    if (FAILED(hr)) {
        return hr;
    }
    
    hr = pGraph->RenderFile(lpwstrOld, NULL);
    if (FAILED(hr)) {
        return hr;
    }

    CComQIPtr <IMediaControl, &IID_IMediaControl> pGraphC = pGraph;
    if (pGraphC == NULL) {
        return E_UNEXPECTED;
    }
    
    hr = pGraphC->Run();
    if (FAILED(hr)) {
        return hr;
    }

    // now wait for completion....
    CComQIPtr <IMediaEvent, &IID_IMediaEvent> pEvent = pGraph;
    if (pEvent == NULL) {
        return E_UNEXPECTED;
    }

    CComQIPtr <IMediaSeeking, &IID_IMediaSeeking> pSeek = pMux;
    if (lpCallback && pSeek == NULL) {
        return E_NOINTERFACE;
    }

    LONG lEvCode = 0;
    HRESULT hrProgress = S_OK;
    do {
        MSG Message;

        while (PeekMessage(&Message, NULL, 0, 0, TRUE))
        {
            TranslateMessage(&Message);
            DispatchMessage(&Message);
        }

    hr = pEvent->WaitForCompletion(100, &lEvCode);

    // call their callback
    if (lpCallback) {
            REFERENCE_TIME rtCur, rtStop;
            hr = pSeek->GetCurrentPosition(&rtCur);
            // GetStopPosition isn't implemented
            HRESULT hr2 = pSeek->GetDuration(&rtStop);
            if (hr == S_OK && hr2 == S_OK && rtStop != 0) {
                int lTemp = (int)((double)rtCur / rtStop * 100.);
            hrProgress = lpCallback->Progress(lTemp);
            }
        }

        // Let the user hit escape to get out and let the callback get out
        if ((fAllowEscAbort && GetAsyncKeyState(VK_ESCAPE) & 0x0001) ||
                        hrProgress == S_FALSE) {
            hrProgress = S_FALSE;
            break;
        }

    } while (lEvCode == 0);
    
    hr = pGraphC->Stop();
    if (FAILED(hr)) {
        return hr;
    }
    
    return hrProgress;
}


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

	    // 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 = CopyCaptureFile(T2W(gcap.szCaptureFile),
                                       T2W(tachDstFile), TRUE, pIProg);
        if (pIProg)
            pIProg->Release();

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

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

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

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

    return result;
}


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

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

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

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

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

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

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

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

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



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

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

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

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

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

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

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

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

		    EndDialog(hDlg, TRUE) ;
		    return TRUE ;
		}

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

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

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

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

    return FALSE ;
}


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


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


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

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

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

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

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

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

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

    return dwlSize;
}


//
// AudioPropertiesProc: Choose audio properties
//
int FAR PASCAL AudioPropertiesProc(HWND hwnd, UINT msg, UINT wParam, LONG lParam) 
{
    USES_CONVERSION;
	UINT uRc =0;

    switch(msg) 
	{
        case WM_INITDIALOG:
			if(gcap.fWaveFormatEx == TRUE)
			{
				SetupAudioDialogBox(hwnd, TRUE);
                if (!gcap.isXP)
                {
		            HWND Hcc = GetDlgItem(hwnd, IDC_WAVEFORMATEXTENSIBLE);
		            EnableWindow(Hcc, FALSE);
                }
			
			
			}
			else
            {
                if (gcap.isXP)
    				SetupAudioDialogBox( hwnd, FALSE);
                else
                {
		            HWND Hcc = GetDlgItem(hwnd, IDC_WAVEFORMATEXTENSIBLE);
		            EnableWindow(Hcc, FALSE);
                    gcap.fWaveFormatEx =TRUE;
    				SetupAudioDialogBox(hwnd, TRUE);
                }
            }
            break;

        case WM_COMMAND:
            switch(LOWORD(wParam)) 
			{
            case IDCANCEL:
                EndDialog(hwnd, FALSE);
                break;
            case IDC_WAVEFORMATEX:
				SetupAudioDialogBox( hwnd, TRUE);
                if (!gcap.isXP)
                {
		            HWND Hcc = GetDlgItem(hwnd, IDC_WAVEFORMATEXTENSIBLE);
		            EnableWindow(Hcc, FALSE);
                }

                break;
            
			case IDC_WAVEFORMATEXTENSIBLE:
                if (gcap.isXP)
    				SetupAudioDialogBox( hwnd, FALSE);
                else
                {
		            HWND Hcc = GetDlgItem(hwnd, IDC_WAVEFORMATEXTENSIBLE);
		            EnableWindow(Hcc, FALSE);
                    gcap.fWaveFormatEx =TRUE;
    				SetupAudioDialogBox(hwnd, TRUE);
                }

                break;

			case IDC_BPSCOMBO:
				if (HIWORD(wParam) == CBN_SELCHANGE)
					SetupContainerBits(hwnd);

				break;

				
            case IDOK:
				GetAudioDialogBox(hwnd);
			   
                EndDialog(hwnd, TRUE);
                break;

			case IDAPPLY:
				GetAudioDialogBox(hwnd);
				ApplyAudioFormat(); 

                break;

            }
            break;

        default:
            return FALSE;
    }
    return TRUE;
}


/*
HRESULT SetupChannelDialogBox( HWND hDlg)
{
    HRESULT hr = S_OK;
// First Gray out the channels which are not present
    UINT uCheck =0;
    DWORD dwSpeaker = 0;
    
    dwSpeaker = FL;
    {
        HWND hwnd = GetDlgItem(hDlg, IDC_FL);
        EnableWindow(hwnd, TRUE);
    
        if ((gcap.m_dwReqChannelMask & dwSpeaker) == dwSpeaker)
            uCheck = BST_CHECKED;
        else
            uCheck = BST_UNCHECKED;
        CheckDlgButton(hDlg, IDC_FL, uCheck);

    }

    dwSpeaker = FR;
    {
        HWND hwnd = GetDlgItem(hDlg, IDC_FR);
        EnableWindow(hwnd, TRUE);

        if ((gcap.m_dwReqChannelMask & dwSpeaker) == dwSpeaker)
            uCheck = BST_CHECKED;
        else
            uCheck = BST_UNCHECKED;
        CheckDlgButton(hDlg, IDC_FR, uCheck);
    }

    dwSpeaker = FC;
    {
        HWND hwnd = GetDlgItem(hDlg, IDC_FC);
        EnableWindow(hwnd, TRUE);

        if ((gcap.m_dwReqChannelMask & dwSpeaker) == dwSpeaker)
            uCheck = BST_CHECKED;
        else
            uCheck = BST_UNCHECKED;
        CheckDlgButton(hDlg, IDC_FC, uCheck);

    }

    dwSpeaker = SUBW;
    {
        HWND hwnd = GetDlgItem(hDlg, IDC_SW);
        EnableWindow(hwnd, TRUE);

        if ((gcap.m_dwReqChannelMask & dwSpeaker) == dwSpeaker)
            uCheck = BST_CHECKED;
        else
            uCheck = BST_UNCHECKED;
        CheckDlgButton(hDlg, IDC_SW, uCheck);

    }

    dwSpeaker = BL;
    {
        HWND hwnd = GetDlgItem(hDlg, IDC_BL);
        EnableWindow(hwnd, TRUE);

        if ((gcap.m_dwReqChannelMask & dwSpeaker) == dwSpeaker)
            uCheck = BST_CHECKED;
        else
            uCheck = BST_UNCHECKED;
        CheckDlgButton(hDlg, IDC_BL, uCheck);
    }
    
    dwSpeaker = BR;
    {
        HWND hwnd = GetDlgItem(hDlg, IDC_BR);
        EnableWindow(hwnd, TRUE);
        if ((gcap.m_dwReqChannelMask & dwSpeaker) == dwSpeaker)
            uCheck = BST_CHECKED;
        else
            uCheck = BST_UNCHECKED;
        
        CheckDlgButton(hDlg, IDC_BR, uCheck);
    }

    dwSpeaker = FLC;
    {
        HWND hwnd = GetDlgItem(hDlg, IDC_FLC);
        EnableWindow(hwnd, TRUE);
        if ((gcap.m_dwReqChannelMask & dwSpeaker) == dwSpeaker)
            uCheck = BST_CHECKED;
        else
            uCheck = BST_UNCHECKED;
        CheckDlgButton(hDlg, IDC_FLC, uCheck);
    }

    dwSpeaker = FRC;
    {
        HWND hwnd = GetDlgItem(hDlg, IDC_FRC);
        EnableWindow(hwnd, TRUE);
        if ((gcap.m_dwReqChannelMask & dwSpeaker) == dwSpeaker)
            uCheck = BST_CHECKED;
        else
            uCheck = BST_UNCHECKED;
        CheckDlgButton(hDlg, IDC_FRC, uCheck);
    }

    return hr;
}
*/
/*HRESULT GetChannelDialogBox( HWND hDlg)
{
    HRESULT hr = S_OK;
    
    gcap.WaveExtIn.Format.nChannels =0;
    gcap.m_dwReqChannelMask =0;

    UINT Result =  IsDlgButtonChecked(hDlg, IDC_FL );
    if (Result == BST_CHECKED)
    {
        gcap.WaveExtIn.Format.nChannels+=1;
        gcap.m_dwReqChannelMask |= FL;
    }
    else
        gcap.m_dwReqChannelMask &=(~FL);           
    
    Result =  IsDlgButtonChecked(hDlg, IDC_FR );
    if (Result == BST_CHECKED)
    {
        gcap.WaveExtIn.Format.nChannels+=1;
        gcap.m_dwReqChannelMask |= FR;
    }
    else
        gcap.m_dwReqChannelMask &=(~FR);           

    Result =  IsDlgButtonChecked(hDlg, IDC_FC );
    if (Result == BST_CHECKED)
    {
        gcap.WaveExtIn.Format.nChannels+=1;
        gcap.m_dwReqChannelMask |= FC;
    }
    else
        gcap.m_dwReqChannelMask &=(~FC);           
    
    Result =  IsDlgButtonChecked(hDlg, IDC_SW );
    if (Result == BST_CHECKED)
    {
        gcap.WaveExtIn.Format.nChannels+=1;
        gcap.m_dwReqChannelMask |= SUBW;
    }
    else
        gcap.m_dwReqChannelMask &=(~SUBW);         

    Result =  IsDlgButtonChecked(hDlg, IDC_BL );
    if (Result == BST_CHECKED)
    {
        gcap.WaveExtIn.Format.nChannels+=1;
        gcap.m_dwReqChannelMask |= BL;
    }
    else
        gcap.m_dwReqChannelMask &=(~BL);           

    Result =  IsDlgButtonChecked(hDlg, IDC_BR );
    if (Result == BST_CHECKED)
    {
        gcap.WaveExtIn.Format.nChannels+=1;
        gcap.m_dwReqChannelMask |= BR;
    }
    else
        gcap.m_dwReqChannelMask &=(~BR);           


    Result =  IsDlgButtonChecked(hDlg, IDC_FLC );
    if (Result == BST_CHECKED)
    {
        gcap.WaveExtIn.Format.nChannels+=1;
        gcap.m_dwReqChannelMask |= FLC;
    }
    else
        gcap.m_dwReqChannelMask &=(~FLC);          

    Result =  IsDlgButtonChecked(hDlg, IDC_FRC );
    if (Result == BST_CHECKED)
    {
        gcap.WaveExtIn.Format.nChannels+=1;
        gcap.m_dwReqChannelMask |= FRC;
    }
    else
        gcap.m_dwReqChannelMask &=(~FRC); 
    
    gcap.WaveExtIn.dwChannelMask = gcap.m_dwReqChannelMask;
//    gcap.m_dwPreChannelMask = gcap.m_dwReqChannelMask;

    return hr;
}
*/

HRESULT SetupAudioDialogBox( HWND hwnd, BOOL bEx)
{
    HRESULT hr = S_OK;
    int i =0;
    HWND Hcc = NULL;
     TCHAR  tach[32];
   
	if (TRUE == bEx) // WaveformatEx
	{
		CheckRadioButton(
		hwnd,          // handle to dialog box
		IDC_WAVEFORMATEX, // identifier of first button in group
		IDC_WAVEFORMATEXTENSIBLE,  // identifier of last button in group
		IDC_WAVEFORMATEX  // identifier of button to select
		);
		// Sampling Rate Combobox
		Hcc = GetDlgItem(hwnd, IDC_SAMPLINGRATECOMBO);
		ComboBox_ResetContent(Hcc);
		wsprintf(tach, TEXT("8 KHz"));
		ComboBox_InsertString(Hcc, 0, tach);             
		wsprintf(tach, TEXT("11.025 KHz"));
		ComboBox_InsertString(Hcc, 1, tach);             
		wsprintf(tach, TEXT("12 KHz"));
		ComboBox_InsertString(Hcc, 2, tach);             
		wsprintf(tach, TEXT("16 KHz"));
		ComboBox_InsertString(Hcc, 3, tach);             
		wsprintf(tach, TEXT("22.05 KHz"));
		ComboBox_InsertString(Hcc, 4, tach);             
		wsprintf(tach, TEXT("24 KHz"));
		ComboBox_InsertString(Hcc, 5, tach);             
		wsprintf(tach, TEXT("32 KHz"));
		ComboBox_InsertString(Hcc, 6, tach);             
		wsprintf(tach, TEXT("44.1 KHz"));
		ComboBox_InsertString(Hcc, 7, tach);             
		wsprintf(tach, TEXT("48 KHz"));
		ComboBox_InsertString(Hcc, 8, tach);             
		i=0;
		switch(gcap.WaveExIn.nSamplesPerSec)
		{
		case 8000: i=0;break;
		case 11025: i=1;break;
		case 12000: i=2;break;
		case 16000: i=3;break;
		case 22050: i=4;break;
		case 24000: i=5;break;
		case 32000: i=6;break;
		case 44100: i=7;break;
		case 48000: i=8;break;
		default: i=7;
		}
		ComboBox_SetCurSel(Hcc, i);

		// Setup Channels
		Hcc = GetDlgItem(hwnd, IDC_CHANNELSCOMBO);
		ComboBox_ResetContent(Hcc);
		wsprintf(tach, TEXT("1(Mono)"));
		ComboBox_InsertString(Hcc, 0, tach);             
		wsprintf(tach, TEXT("2(Stereo)"));
		ComboBox_InsertString(Hcc, 1, tach);             
		switch(gcap.WaveExIn.nChannels)
		{
		case 1: i=0;break;
		case 2: i=1;break;
		default: i=1;
		}
		ComboBox_SetCurSel(Hcc, i);

		// Setup Bit Depth
		Hcc = GetDlgItem(hwnd, IDC_BPSCOMBO);
		ComboBox_ResetContent(Hcc);
		wsprintf(tach, TEXT("8"));
		ComboBox_InsertString(Hcc, 0, tach);             
		wsprintf(tach, TEXT("16"));
		ComboBox_InsertString(Hcc, 1, tach);             
		switch(gcap.WaveExIn.wBitsPerSample)
		{
		case 8: i=0;break;
		case 16: i=1;break;
		default: i=1;
		}
		ComboBox_SetCurSel(Hcc, i);

		// Setup Container Bits
		Hcc = GetDlgItem(hwnd, IDC_VBPSCOMBO);
		ComboBox_ResetContent(Hcc);
		ComboBox_Enable(Hcc, FALSE);

		Hcc = GetDlgItem(hwnd, IDC_VALIDBITSPERSAMPLEE);
		EnableWindow(Hcc, FALSE);
	}
	else
	{
		CheckRadioButton(
		hwnd,          // handle to dialog box
		IDC_WAVEFORMATEX, // identifier of first button in group
		IDC_WAVEFORMATEXTENSIBLE,  // identifier of last button in group
		IDC_WAVEFORMATEXTENSIBLE  // identifier of button to select
		);
		// Sampling Rate
		Hcc = GetDlgItem(hwnd, IDC_SAMPLINGRATECOMBO);
		ComboBox_ResetContent(Hcc);
		wsprintf(tach, TEXT("44.1 KHz"));
		ComboBox_InsertString(Hcc, 0, tach);             
		wsprintf(tach, TEXT("48 KHz"));
		ComboBox_InsertString(Hcc, 1, tach);             
		wsprintf(tach, TEXT("88.2 KHz"));
		ComboBox_InsertString(Hcc, 2, tach) ;            
		wsprintf(tach, TEXT("96 KHz"));
		ComboBox_InsertString(Hcc, 3, tach);             
		wsprintf(tach, TEXT("176.4 KHz"));
		ComboBox_InsertString(Hcc, 4, tach) ;            
		wsprintf(tach, TEXT("192 KHz"));
		ComboBox_InsertString(Hcc, 5, tach) ;            
		i=0;
		switch(gcap.WaveExtIn.Format.nSamplesPerSec)
		{
		case 44100: i=0;break;
		case 48000: i=1;break;
		case 88200: i=2;break;
		case 96000: i=3;break;
		case 176400: i=4;break;
		case 192000: i=5;break;
		default: i=1;
		}
		ComboBox_SetCurSel(Hcc, i);
		// Setup Channels
		Hcc = GetDlgItem(hwnd, IDC_CHANNELSCOMBO);
		ComboBox_ResetContent(Hcc);
		wsprintf(tach, TEXT("2(Stereo)"));
		ComboBox_InsertString(Hcc, 0, tach);             
		wsprintf(tach, TEXT("5.1"));
		ComboBox_InsertString(Hcc, 1, tach);             
		wsprintf(tach, TEXT("7.1"));
		ComboBox_InsertString(Hcc, 2, tach) ;            
		switch(gcap.WaveExtIn.Format.nChannels)
		{
		case 2: i=0;break;
		case 6: i=1;break;
		case 8: i=2;break;
		default: i=1;
		}
		ComboBox_SetCurSel(Hcc, i);

		// Setup Bit Depth

		Hcc = GetDlgItem(hwnd, IDC_BPSCOMBO);
		ComboBox_ResetContent(Hcc);
		wsprintf(tach, TEXT("16"));
		ComboBox_InsertString(Hcc, 0, tach);             
		wsprintf(tach, TEXT("20"));
		ComboBox_InsertString(Hcc, 1, tach);             
		wsprintf(tach, TEXT("24"));
		ComboBox_InsertString(Hcc, 2, tach) ;            
		switch(gcap.WaveExtIn.Samples.wValidBitsPerSample)
		{
		case 16: i=0;break;
		case 20: i=1;break;
		case 24: i=2;break;
		default: i=2;
		}
		ComboBox_SetCurSel(Hcc, i);
		

		// Setup Container Bits
		SetupContainerBits(hwnd);

/*		Hcc = GetDlgItem(hwnd, IDC_VALIDBITSPERSAMPLEE);
		EnableWindow(Hcc, TRUE);
		
		Hcc = GetDlgItem(hwnd, IDC_VBPSCOMBO);
		ComboBox_ResetContent(Hcc);
		ComboBox_Enable(Hcc, TRUE);
		wsprintf(tach, TEXT("16"));
		ComboBox_InsertString(Hcc, 0, tach);             
		wsprintf(tach, TEXT("24"));
		ComboBox_InsertString(Hcc, 1, tach);             
		wsprintf(tach, TEXT("32"));
		ComboBox_InsertString(Hcc, 2, tach) ;            
		switch(gcap.WaveExtIn.Format.wBitsPerSample)
		{
		case 16: i=0;break;
		case 24: i=1;break;
		case 32: i=2;break;
		default: i=2;
		}
		ComboBox_SetCurSel(Hcc, i);
		*/
	}
    
    return hr;
}
HRESULT SetupContainerBits( HWND hwnd)
{
    HRESULT hr = S_OK;
    int i =0;
    HWND Hcc = NULL;
    TCHAR  tach[32];
	UINT uRc =0;

	uRc = IsDlgButtonChecked(hwnd,  IDC_WAVEFORMATEX);
	if (BST_CHECKED == uRc)
		;
	else
	{
		Hcc = GetDlgItem(hwnd, IDC_BPSCOMBO);
		ComboBox_GetText(Hcc,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);
		int Bits = atoi(szANSI);
#else
		int Bits = atoi(tach);
#endif
		Hcc = GetDlgItem(hwnd, IDC_VALIDBITSPERSAMPLEE);
		EnableWindow(Hcc, TRUE);
		
		Hcc = GetDlgItem(hwnd, IDC_VBPSCOMBO);
		ComboBox_ResetContent(Hcc);
		ComboBox_Enable(Hcc, TRUE);
		switch(Bits)
		{
			case 16: 
				wsprintf(tach, TEXT("16"));
				ComboBox_InsertString(Hcc, 0, tach);             
				wsprintf(tach, TEXT("24"));
				ComboBox_InsertString(Hcc, 1, tach);             
				wsprintf(tach, TEXT("32"));
				ComboBox_InsertString(Hcc, 2, tach) ;            
				switch(gcap.WaveExtIn.Format.wBitsPerSample)
				{
				case 16: i=0;break;
				case 24: i=1;break;
				case 32: i=2;break;
				default: i=2;
				}
				break;
			case 20: 
			case 24: 
				wsprintf(tach, TEXT("24"));
				ComboBox_InsertString(Hcc, 0, tach);             
				wsprintf(tach, TEXT("32"));
				ComboBox_InsertString(Hcc, 1, tach) ;            
				switch(gcap.WaveExtIn.Format.wBitsPerSample)
				{
				case 24: i=0;break;
				case 32: i=1;break;
				default: i=1;
				}
				break;

		}
	}
	ComboBox_SetCurSel(Hcc, i);

    
    return hr;
}

HRESULT GetAudioDialogBox( HWND hwnd)
{
    HRESULT hr = S_OK;
    int i =0;
    HWND Hcc = NULL;
    TCHAR  tach[32];
	UINT uRc =0;
    
   // gcap.WaveExtIn.Format.nChannels =0;
   // gcap.m_dwReqChannelMask =0;

	uRc = IsDlgButtonChecked(hwnd,  IDC_WAVEFORMATEX);

	if (BST_CHECKED == uRc)
		gcap.fWaveFormatEx = TRUE;
	else
		gcap.fWaveFormatEx = FALSE;

	if (gcap.fWaveFormatEx == TRUE)
	{
		Hcc = GetDlgItem(hwnd, IDC_BPSCOMBO);
		ComboBox_GetText(Hcc,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);
		gcap.WaveExIn.wBitsPerSample = atoi(szANSI);
#else
		gcap.WaveExIn.wBitsPerSample = atoi(tach);
#endif

		Hcc = GetDlgItem(hwnd, IDC_SAMPLINGRATECOMBO);
		i = ComboBox_GetCurSel(Hcc);
		switch (i)
		{
		case 0:gcap.WaveExIn.nSamplesPerSec = 8000;break;
		case 1:gcap.WaveExIn.nSamplesPerSec = 11025;break;
		case 2:gcap.WaveExIn.nSamplesPerSec = 12000;break;
		case 3:gcap.WaveExIn.nSamplesPerSec = 16000;break;
		case 4:gcap.WaveExIn.nSamplesPerSec = 22050;break;
		case 5:gcap.WaveExIn.nSamplesPerSec = 24000;break;
		case 6:gcap.WaveExIn.nSamplesPerSec = 32000;break;
		case 7:gcap.WaveExIn.nSamplesPerSec = 44100;break;
		case 8:gcap.WaveExIn.nSamplesPerSec = 48000;break;
		default:
			ErrMsg("Bad Sampling Rate Selection");
		}


		Hcc = GetDlgItem(hwnd, IDC_CHANNELSCOMBO);
		i = ComboBox_GetCurSel(Hcc);
		switch (i)
		{
		case 0:gcap.WaveExIn.nChannels = 1;break;
		case 1:gcap.WaveExIn.nChannels = 2;break;
		default:
			ErrMsg("Bad Channel Selection");
		}
		gcap.WaveExIn.wFormatTag = WAVE_FORMAT_PCM;
		gcap.WaveExIn.cbSize = 0;
		gcap.WaveExIn.nBlockAlign  =gcap.WaveExIn.nChannels * gcap.WaveExIn.wBitsPerSample /8;
		gcap.WaveExIn.nAvgBytesPerSec  = gcap.WaveExIn.nBlockAlign * gcap.WaveExIn.nSamplesPerSec ;
	}
	else
	{
		Hcc = GetDlgItem(hwnd, IDC_BPSCOMBO);
		ComboBox_GetText(Hcc,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);
		gcap.WaveExtIn.Samples.wValidBitsPerSample = atoi(szANSI);
#else
		gcap.WaveExtIn.Samples.wValidBitsPerSample = atoi(tach);
#endif

		Hcc = GetDlgItem(hwnd, IDC_SAMPLINGRATECOMBO);
		i = ComboBox_GetCurSel(Hcc);
		switch (i)
		{
		case 0:gcap.WaveExtIn.Format.nSamplesPerSec = 44100;break;
		case 1:gcap.WaveExtIn.Format.nSamplesPerSec = 48000;break;
		case 2:gcap.WaveExtIn.Format.nSamplesPerSec = 88200;break;
		case 3:gcap.WaveExtIn.Format.nSamplesPerSec = 96000;break;
		case 4:gcap.WaveExtIn.Format.nSamplesPerSec = 176400;break;
		case 5:gcap.WaveExtIn.Format.nSamplesPerSec = 192000;break;
		default:
			ErrMsg("Bad Sampling Rate Selection");
		}



		Hcc = GetDlgItem(hwnd, IDC_CHANNELSCOMBO);
		i = ComboBox_GetCurSel(Hcc);
		switch (i)
		{
		case 0:gcap.WaveExtIn.Format.nChannels = 2;break;
		case 1:gcap.WaveExtIn.Format.nChannels = 6;break;
		case 2:gcap.WaveExtIn.Format.nChannels = 8;break;
		default:
			ErrMsg("Bad Channel Selection");
		}

		Hcc = GetDlgItem(hwnd, IDC_VBPSCOMBO);
		ComboBox_GetText(Hcc,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);
		gcap.WaveExtIn.Format.wBitsPerSample= atoi(szANSI);
#else
		gcap.WaveExtIn.Format.wBitsPerSample = atoi(tach);
#endif
		gcap.WaveExtIn.Format.cbSize = 22;
		gcap.WaveExtIn.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
		gcap.WaveExtIn.Format.nBlockAlign  =gcap.WaveExtIn.Format.nChannels * gcap.WaveExtIn.Format.wBitsPerSample /8;
		gcap.WaveExtIn.Format.nAvgBytesPerSec  = gcap.WaveExtIn.Format.nBlockAlign * gcap.WaveExtIn.Format.nSamplesPerSec ;
		gcap.WaveExtIn.SubFormat =  KSDATAFORMAT_SUBTYPE_PCM;
		switch(gcap.WaveExtIn.Format.nChannels)
		{
		case 2:
			gcap.WaveExtIn.dwChannelMask = FL|FR;break;
		case 6:
			gcap.WaveExtIn.dwChannelMask = FL|FR|FC|SUBW|BL|BR;break;
		case 8:
			gcap.WaveExtIn.dwChannelMask = FL|FR|FC|SUBW|BL|BR|FLC|FRC;break;
		default:
			gcap.WaveExtIn.dwChannelMask = FL|FR|FC|SUBW|BL|BR;break;
		}
	}
    return hr;
}
