home *** CD-ROM | disk | FTP | other *** search
/ Windows Game Programming for Dummies (2nd Edition) / WinGamProgFD.iso / pc / DirectX SDK / DXSDK / samples / Multimedia / DirectShow / Capture / AMCap / amcap.cpp next >
Encoding:
C/C++ Source or Header  |  2001-10-08  |  140.6 KB  |  3,947 lines

  1. //------------------------------------------------------------------------------
  2. // File: AMCap.cpp
  3. //
  4. // Desc: Audio/Video Capture sample for DirectShow
  5. //
  6. //
  7. // Copyright (c) 1993-2001 Microsoft Corporation.  All rights reserved.
  8. //------------------------------------------------------------------------------
  9.  
  10. #include <windows.h>
  11. #include <dbt.h>
  12. #include <streams.h>
  13. #include <mmreg.h>
  14. #include <msacm.h>
  15. #include <fcntl.h>
  16. #include <io.h>
  17. #include <stdio.h>
  18. #include <commdlg.h>
  19. #include <atlbase.h>
  20. #include "stdafx.h"
  21. #include "amcap.h"
  22. #include "status.h"
  23. #include "crossbar.h"
  24.  
  25. // you can never have too many parentheses!
  26. #define ABS(x) (((x) > 0) ? (x) : -(x))
  27.  
  28. // An application can advertise the existence of its filter graph
  29. // by registering the graph with a global Running Object Table (ROT).
  30. // The GraphEdit application can detect and remotely view the running
  31. // filter graph, allowing you to 'spy' on the graph with GraphEdit.
  32. //
  33. // To enable registration in this sample, define REGISTER_FILTERGRAPH.
  34. //
  35. #ifdef  DEBUG
  36. #define REGISTER_FILTERGRAPH
  37. #endif
  38.  
  39.  
  40. //------------------------------------------------------------------------------
  41. // Global data
  42. //------------------------------------------------------------------------------
  43. HINSTANCE ghInstApp;
  44. HACCEL ghAccel;
  45. HFONT  ghfontApp;
  46. TEXTMETRIC gtm;
  47. TCHAR gszAppName[]=TEXT("AMCAP");
  48. HWND ghwndApp, ghwndStatus;
  49. HDEVNOTIFY ghDevNotify;
  50. PUnregisterDeviceNotification gpUnregisterDeviceNotification;
  51. PRegisterDeviceNotification gpRegisterDeviceNotification;
  52. DWORD g_dwGraphRegister=0;
  53.  
  54. struct _capstuff {
  55.     TCHAR szCaptureFile[_MAX_PATH];
  56.     WORD wCapFileSize;  // size in Meg
  57.     ICaptureGraphBuilder2 *pBuilder;
  58.     IVideoWindow *pVW;
  59.     IMediaEventEx *pME;
  60.     IAMDroppedFrames *pDF;
  61.     IAMVideoCompression *pVC;
  62.     IAMVfwCaptureDialogs *pDlg;
  63.     IAMStreamConfig *pASC;      // for audio cap
  64.     IAMStreamConfig *pVSC;      // for video cap
  65.     IBaseFilter *pRender;
  66.     IBaseFilter *pVCap, *pACap;
  67.     IGraphBuilder *pFg;
  68.     IFileSinkFilter *pSink;
  69.     IConfigAviMux *pConfigAviMux;
  70.     int  iMasterStream;
  71.     BOOL fCaptureGraphBuilt;
  72.     BOOL fPreviewGraphBuilt;
  73.     BOOL fCapturing;
  74.     BOOL fPreviewing;
  75.     BOOL fCapAudio;
  76.     BOOL fCapCC;
  77.     BOOL fCCAvail;
  78.     BOOL fCapAudioIsRelevant;
  79.     bool fDeviceMenuPopulated;
  80.     IMoniker *rgpmVideoMenu[10];
  81.     IMoniker *rgpmAudioMenu[10];
  82.     IMoniker *pmVideo;
  83.     IMoniker *pmAudio;
  84.     double FrameRate;
  85.     BOOL fWantPreview;
  86.     long lCapStartTime;
  87.     long lCapStopTime;
  88.     WCHAR wachFriendlyName[120];
  89.     BOOL fUseTimeLimit;
  90.     BOOL fUseFrameRate;
  91.     DWORD dwTimeLimit;
  92.     int iFormatDialogPos;
  93.     int iSourceDialogPos;
  94.     int iDisplayDialogPos;
  95.     int iVCapDialogPos;
  96.     int iVCrossbarDialogPos;
  97.     int iTVTunerDialogPos;
  98.     int iACapDialogPos;
  99.     int iACrossbarDialogPos;
  100.     int iTVAudioDialogPos;
  101.     int iVCapCapturePinDialogPos;
  102.     int iVCapPreviewPinDialogPos;
  103.     int iACapCapturePinDialogPos;
  104.     long lDroppedBase;
  105.     long lNotBase;
  106.     BOOL fPreviewFaked;
  107.     CCrossbar *pCrossbar;
  108.     int iVideoInputMenuPos;
  109.     LONG NumberOfVideoInputs;
  110.     HMENU hMenuPopup;
  111.     int iNumVCapDevices;
  112. } gcap;
  113.  
  114.  
  115. // implements IAMCopyCaptureFileProgress
  116. //
  117. class CProgress : public CUnknown, public IAMCopyCaptureFileProgress {
  118.     public:
  119.  
  120.     CProgress(TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr) :
  121.     CUnknown(pName, pUnk, phr) {
  122.     };
  123.     ~CProgress() {
  124.     };
  125.  
  126.     STDMETHODIMP_(ULONG) AddRef() {
  127.         return 1;
  128.     };
  129.     STDMETHODIMP_(ULONG) Release() {
  130.         return 0;
  131.     };
  132.  
  133.     STDMETHODIMP QueryInterface(REFIID iid, void **p) {
  134.         CheckPointer(p, E_POINTER);
  135.         if(iid == IID_IAMCopyCaptureFileProgress) {
  136.             return GetInterface((IAMCopyCaptureFileProgress *)this, p);
  137.         }
  138.         else {
  139.             return E_NOINTERFACE;
  140.         }
  141.     };
  142.     STDMETHODIMP Progress(int i) {
  143.         TCHAR tach[80];
  144.         wsprintf(tach, TEXT("Save File Progress: %d%%"), i);
  145.         statusUpdateStatus(ghwndStatus, tach);
  146.         return S_OK;
  147.     };
  148. };
  149.  
  150. CComModule _Module;
  151.  
  152. //------------------------------------------------------------------------------
  153. // Funciton Prototypes
  154. //------------------------------------------------------------------------------
  155. typedef LONG(PASCAL *LPWNDPROC)(HWND, UINT, WPARAM, LPARAM); // pointer to a window procedure
  156. LONG WINAPI AppWndProc(HWND hwnd, UINT uiMessage, WPARAM wParam, LPARAM lParam);
  157. LONG PASCAL AppCommand(HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam);
  158. BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
  159. int ErrMsg(LPTSTR sz,...);
  160. BOOL SetCaptureFile(HWND hWnd);
  161. BOOL SaveCaptureFile(HWND hWnd);
  162. BOOL AllocCaptureFile(HWND hWnd);
  163. int DoDialog(HWND hwndParent, int DialogID, DLGPROC fnDialog, long lParam);
  164. int FAR PASCAL AllocCapFileProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam);
  165. int FAR PASCAL FrameRateProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam);
  166. int FAR PASCAL TimeLimitProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam);
  167. int FAR PASCAL PressAKeyProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam);
  168. void TearDownGraph(void);
  169. BOOL BuildCaptureGraph();
  170. BOOL BuildPreviewGraph();
  171. void UpdateStatus(BOOL fAllStats);
  172. void AddDevicesToMenu();
  173. void ChooseDevices(TCHAR *szVideo, TCHAR *szAudio);
  174. void ChooseDevices(IMoniker *pmVideo, IMoniker *pmAudio);
  175. void ChooseFrameRate();
  176. BOOL InitCapFilters();
  177. void FreeCapFilters();
  178. BOOL StopPreview();
  179. BOOL StartPreview();
  180. BOOL StopCapture();
  181. DWORDLONG GetSize(LPCTSTR tach);
  182. void MakeMenuOptions();
  183. void OnClose();
  184.  
  185. // Adds/removes a DirectShow filter graph from the Running Object Table,
  186. // allowing GraphEdit to "spy" on a remote filter graph if enabled.
  187. HRESULT AddGraphToRot(IUnknown *pUnkGraph, DWORD *pdwRegister);
  188. void RemoveGraphFromRot(DWORD pdwRegister);
  189.  
  190.  
  191. //------------------------------------------------------------------------------
  192. // Name: SetAppCaption()
  193. // Desc: Set the caption to be the application name followed by the capture file
  194. //------------------------------------------------------------------------------
  195. void SetAppCaption() 
  196. {
  197.     TCHAR tach[_MAX_PATH + 80];
  198.     lstrcpy(tach, gszAppName);
  199.     if(gcap.szCaptureFile[0] != 0) {
  200.         lstrcat(tach, TEXT(" - "));
  201.         lstrcat(tach, gcap.szCaptureFile);
  202.     }
  203.     SetWindowText(ghwndApp, tach);
  204. }
  205.  
  206.  
  207. /*----------------------------------------------------------------------------*\
  208. |   AppInit( hInst, hPrev)                                                     |
  209. |                                                                              |
  210. |   Description:                                                               |
  211. |       This is called when the application is first loaded into               |
  212. |       memory.  It performs all initialization that doesn't need to be done   |
  213. |       once per instance.                                                     |
  214. |                                                                              |
  215. |   Arguments:                                                                 |
  216. |       hInstance       instance handle of current instance                    |
  217. |       hPrev           instance handle of previous instance                   |
  218. |                                                                              |
  219. |   Returns:                                                                   |
  220. |       TRUE if successful, FALSE if not                                       |
  221. |                                                                              |
  222. \*----------------------------------------------------------------------------*/
  223. BOOL AppInit(HINSTANCE hInst, HINSTANCE hPrev, int sw) 
  224. {
  225.     WNDCLASS    cls;
  226.     HDC         hdc;
  227.  
  228.     const DWORD  dwExStyle = 0;
  229.  
  230.     CoInitialize(NULL);
  231.     DbgInitialise(hInst);
  232.  
  233.     /* Save instance handle for DialogBoxs */
  234.     ghInstApp = hInst;
  235.  
  236.     ghAccel = LoadAccelerators(hInst, MAKEINTATOM(ID_APP));
  237.  
  238.     if(!hPrev) {
  239.         /*
  240.         *  Register a class for the main application window
  241.         */
  242.         cls.hCursor        = LoadCursor(NULL,IDC_ARROW);
  243.         cls.hIcon          = LoadIcon(hInst, TEXT("AMCapIcon"));
  244.         cls.lpszMenuName   = MAKEINTATOM(ID_APP);
  245.         cls.lpszClassName  = MAKEINTATOM(ID_APP);
  246.         cls.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
  247.         cls.hInstance      = hInst;
  248.         cls.style          = CS_BYTEALIGNCLIENT | CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
  249.         cls.lpfnWndProc    = (WNDPROC) AppWndProc;
  250.         cls.cbWndExtra     = 0;
  251.         cls.cbClsExtra     = 0;
  252.  
  253.         if(!RegisterClass(&cls))
  254.             return FALSE;
  255.     }
  256.  
  257.     // Is this necessary?
  258.     ghfontApp = (HFONT)GetStockObject(ANSI_VAR_FONT);
  259.     hdc = GetDC(NULL);
  260.     SelectObject(hdc, ghfontApp);
  261.     GetTextMetrics(hdc, >m);
  262.     ReleaseDC(NULL, hdc);
  263.  
  264.     ghwndApp=CreateWindowEx(dwExStyle,
  265.         MAKEINTATOM(ID_APP),    // Class name
  266.         gszAppName,             // Caption
  267.         // Style bits
  268.         WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
  269.         CW_USEDEFAULT, 0,       // Position
  270.         320,300,                // Size
  271.         (HWND)NULL,             // Parent window (no parent)
  272.         (HMENU)NULL,            // use class menu
  273.         hInst,                  // handle to window instance
  274.         (LPSTR)NULL             // no params to pass on
  275.         );
  276.  
  277.     // create the status bar
  278.     statusInit(hInst, hPrev);
  279.     ghwndStatus = CreateWindowEx(0,
  280.         szStatusClass,
  281.         NULL,
  282.         WS_CHILD|WS_BORDER|WS_VISIBLE|WS_CLIPSIBLINGS,
  283.         0, 0,
  284.         0, 0,
  285.         ghwndApp,
  286.         NULL,
  287.         hInst,
  288.         NULL);
  289.     if(ghwndStatus == NULL) {
  290.         return(FALSE);
  291.     }
  292.     ShowWindow(ghwndApp,sw);
  293.  
  294.     // get the capture file name from win.ini
  295.     GetProfileString(TEXT("annie"), TEXT("CaptureFile"), TEXT(""),
  296.         gcap.szCaptureFile,
  297.         sizeof(gcap.szCaptureFile));
  298.  
  299.     // get which devices to use from win.ini
  300.     ZeroMemory(gcap.rgpmAudioMenu, sizeof(gcap.rgpmAudioMenu));
  301.     ZeroMemory(gcap.rgpmVideoMenu, sizeof(gcap.rgpmVideoMenu));
  302.     gcap.pmVideo = 0;
  303.     gcap.pmAudio = 0;
  304.  
  305.     TCHAR szVideoDisplayName[1024], szAudioDisplayName[1024];
  306.     *szAudioDisplayName = *szVideoDisplayName = 0; // null terminate
  307.  
  308.     GetProfileString(TEXT("annie"), TEXT("VideoDevice2"), TEXT(""),
  309.         szVideoDisplayName,
  310.         sizeof(szVideoDisplayName)/sizeof(szVideoDisplayName[0]));
  311.     GetProfileString(TEXT("annie"), TEXT("AudioDevice2"), TEXT(""),
  312.         szAudioDisplayName,
  313.         sizeof(szAudioDisplayName)/sizeof(szAudioDisplayName[0]));
  314.  
  315.     gcap.fDeviceMenuPopulated = false;
  316.     AddDevicesToMenu();
  317.  
  318.     // do we want audio?
  319.     gcap.fCapAudio = GetProfileInt(TEXT("annie"), TEXT("CaptureAudio"), TRUE);
  320.     gcap.fCapCC = GetProfileInt(TEXT("annie"), TEXT("CaptureCC"), FALSE);
  321.  
  322.     // do we want preview?
  323.     gcap.fWantPreview = GetProfileInt(TEXT("annie"), TEXT("WantPreview"), FALSE);
  324.  
  325.     // which stream should be the master? NONE(-1) means nothing special happens
  326.     // AUDIO(1) means the video frame rate is changed before written out to keep
  327.     // the movie in sync when the audio and video capture crystals are not the
  328.     // same (and therefore drift out of sync after a few minutes).  VIDEO(0)
  329.     // means the audio sample rate is changed before written out
  330.     gcap.iMasterStream = GetProfileInt(TEXT("annie"), TEXT("MasterStream"), 1);
  331.  
  332.     // get the frame rate from win.ini before making the graph
  333.     gcap.fUseFrameRate = GetProfileInt(TEXT("annie"), TEXT("UseFrameRate"), 1);
  334.     int units_per_frame = GetProfileInt(TEXT("annie"), TEXT("FrameRate"), 666667);  // 15fps
  335.     gcap.FrameRate = 10000000. / units_per_frame;
  336.     gcap.FrameRate = (int)(gcap.FrameRate * 100) / 100.;
  337.  
  338.     // reasonable default
  339.     if(gcap.FrameRate <= 0.)
  340.         gcap.FrameRate = 15.0;
  341.  
  342.     gcap.fUseTimeLimit = GetProfileInt(TEXT("annie"), TEXT("UseTimeLimit"), 0);
  343.     gcap.dwTimeLimit = GetProfileInt(TEXT("annie"), TEXT("TimeLimit"), 0);
  344.  
  345.     // instantiate the capture filters we need to do the menu items
  346.     // this will start previewing, if wanted
  347.     // 
  348.     // make these the official devices we're using
  349.  
  350.     ChooseDevices(szVideoDisplayName, szAudioDisplayName);
  351.     // and builds a partial filtergraph.
  352.  
  353.     // Register for device add/remove notifications.
  354.     DEV_BROADCAST_DEVICEINTERFACE filterData;
  355.     ZeroMemory(&filterData, sizeof(DEV_BROADCAST_DEVICEINTERFACE));
  356.  
  357.     filterData.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
  358.     filterData.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
  359.     filterData.dbcc_classguid = AM_KSCATEGORY_CAPTURE;
  360.  
  361.     gpUnregisterDeviceNotification = NULL;
  362.     gpRegisterDeviceNotification = NULL;
  363.     // dynload device removal APIs
  364.     {
  365.         HMODULE hmodUser = GetModuleHandle(TEXT("user32.dll"));
  366.         ASSERT(hmodUser);       // we link to user32
  367.         gpUnregisterDeviceNotification = (PUnregisterDeviceNotification)
  368.         GetProcAddress(hmodUser, "UnregisterDeviceNotification");
  369.  
  370.         // m_pRegisterDeviceNotification is prototyped differently in unicode
  371.         gpRegisterDeviceNotification = (PRegisterDeviceNotification)
  372.             GetProcAddress(hmodUser,
  373. #ifdef UNICODE
  374.             "RegisterDeviceNotificationW"
  375. #else
  376.             "RegisterDeviceNotificationA"
  377. #endif
  378.             );
  379.         // failures expected on older platforms.
  380.         ASSERT(gpRegisterDeviceNotification && gpUnregisterDeviceNotification ||
  381.             !gpRegisterDeviceNotification && !gpUnregisterDeviceNotification);
  382.     }
  383.  
  384.     ghDevNotify = NULL;
  385.  
  386.     if(gpRegisterDeviceNotification) {
  387.         ghDevNotify = gpRegisterDeviceNotification(ghwndApp, &filterData, DEVICE_NOTIFY_WINDOW_HANDLE);
  388.         ASSERT(ghDevNotify != NULL);
  389.     }
  390.  
  391.     SetAppCaption();
  392.     return TRUE;
  393. }
  394.  
  395. void IMonRelease(IMoniker *&pm) 
  396. {
  397.     if(pm) {
  398.         pm->Release();
  399.         pm = 0;
  400.     }
  401. }
  402.  
  403. /*----------------------------------------------------------------------------*\
  404. |   WinMain( hInst, hPrev, lpszCmdLine, cmdShow )                              |
  405. |                                                                              |
  406. |   Description:                                                               |
  407. |       The main procedure for the App.  After initializing, it just goes      |
  408. |       into a message-processing loop until it gets a WM_QUIT message         |
  409. |       (meaning the app was closed).                                          |
  410. |                                                                              |
  411. |   Arguments:                                                                 |
  412. |       hInst           instance handle of this instance of the app            |
  413. |       hPrev           instance handle of previous instance, NULL if first    |
  414. |       szCmdLine       ->null-terminated command line                         |
  415. |       cmdShow         specifies how the window is initially displayed        |
  416. |                                                                              |
  417. |   Returns:                                                                   |
  418. |       The exit code as specified in the WM_QUIT message.                     |
  419. |                                                                              |
  420. \*----------------------------------------------------------------------------*/
  421. int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw) 
  422. {
  423.     MSG     msg;
  424.  
  425.     /* Call initialization procedure */
  426.     if(!AppInit(hInst,hPrev,sw))
  427.         return FALSE;
  428.  
  429.     /*
  430.     * Polling messages from event queue
  431.     */
  432.     for(;;) {
  433.         while(PeekMessage(&msg, NULL, 0, 0,PM_REMOVE)) {
  434.             if(msg.message == WM_QUIT)
  435.                 return ((int) msg.wParam);
  436.  
  437.             if(TranslateAccelerator(ghwndApp, ghAccel, &msg))
  438.                 continue;
  439.  
  440.             TranslateMessage(&msg);
  441.             DispatchMessage(&msg);
  442.         }
  443.  
  444.         WaitMessage();
  445.     }
  446.  
  447.     // not reached
  448.     return ((int) msg.wParam);
  449. }
  450.  
  451.  
  452. /*----------------------------------------------------------------------------*\
  453. |   AppWndProc( hwnd, uiMessage, wParam, lParam )                              |
  454. |                                                                              |
  455. |   Description:                                                               |
  456. |       The window proc for the app's main (tiled) window.  This processes all |
  457. |       of the parent window's messages.                                       |
  458. |                                                                              |
  459. |   Arguments:                                                                 |
  460. |       hwnd            window handle for the window                           |
  461. |       msg             message number                                         |
  462. |       wParam          message-dependent                                      |
  463. |       lParam          message-dependent                                      |
  464. |                                                                              |
  465. |   Returns:                                                                   |
  466. |       0 if processed, nonzero if ignored                                     |
  467. |                                                                              |
  468. \*----------------------------------------------------------------------------*/
  469. LONG WINAPI  AppWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 
  470. {
  471.     PAINTSTRUCT ps;
  472.     HDC hdc;
  473.     RECT rc;
  474.     int cxBorder, cyBorder, cy;
  475.  
  476.     switch(msg) {
  477.         case WM_CREATE:
  478.             break;
  479.  
  480.         case WM_COMMAND:
  481.             return AppCommand(hwnd,msg,wParam,lParam);
  482.  
  483.         case WM_INITMENU:
  484.             // we can start capture if not capturing already
  485.             EnableMenuItem((HMENU)wParam, MENU_START_CAP, 
  486.                 (!gcap.fCapturing) ? MF_ENABLED :
  487.             MF_GRAYED);
  488.             // we can stop capture if it's currently capturing
  489.             EnableMenuItem((HMENU)wParam, MENU_STOP_CAP, 
  490.                 (gcap.fCapturing) ? MF_ENABLED : MF_GRAYED);
  491.  
  492.             // We can bring up a dialog if the graph is stopped
  493.             EnableMenuItem((HMENU)wParam, MENU_DIALOG0, !gcap.fCapturing ?
  494.                 MF_ENABLED : MF_GRAYED);
  495.             EnableMenuItem((HMENU)wParam, MENU_DIALOG1, !gcap.fCapturing ?
  496.                 MF_ENABLED : MF_GRAYED);
  497.             EnableMenuItem((HMENU)wParam, MENU_DIALOG2, !gcap.fCapturing ?
  498.                 MF_ENABLED : MF_GRAYED);
  499.             EnableMenuItem((HMENU)wParam, MENU_DIALOG3, !gcap.fCapturing ?
  500.                 MF_ENABLED : MF_GRAYED);
  501.             EnableMenuItem((HMENU)wParam, MENU_DIALOG4, !gcap.fCapturing ?
  502.                 MF_ENABLED : MF_GRAYED);
  503.             EnableMenuItem((HMENU)wParam, MENU_DIALOG5, !gcap.fCapturing ?
  504.                 MF_ENABLED : MF_GRAYED);
  505.             EnableMenuItem((HMENU)wParam, MENU_DIALOG6, !gcap.fCapturing ?
  506.                 MF_ENABLED : MF_GRAYED);
  507.             EnableMenuItem((HMENU)wParam, MENU_DIALOG7, !gcap.fCapturing ?
  508.                 MF_ENABLED : MF_GRAYED);
  509.             EnableMenuItem((HMENU)wParam, MENU_DIALOG8, !gcap.fCapturing ?
  510.                 MF_ENABLED : MF_GRAYED);
  511.             EnableMenuItem((HMENU)wParam, MENU_DIALOG9, !gcap.fCapturing ?
  512.                 MF_ENABLED : MF_GRAYED);
  513.             EnableMenuItem((HMENU)wParam, MENU_DIALOGA, !gcap.fCapturing ?
  514.                 MF_ENABLED : MF_GRAYED);
  515.             EnableMenuItem((HMENU)wParam, MENU_DIALOGB, !gcap.fCapturing ?
  516.                 MF_ENABLED : MF_GRAYED);
  517.             EnableMenuItem((HMENU)wParam, MENU_DIALOGC, !gcap.fCapturing ?
  518.                 MF_ENABLED : MF_GRAYED);
  519.             EnableMenuItem((HMENU)wParam, MENU_DIALOGD, !gcap.fCapturing ?
  520.                 MF_ENABLED : MF_GRAYED);
  521.             EnableMenuItem((HMENU)wParam, MENU_DIALOGE, !gcap.fCapturing ?
  522.                 MF_ENABLED : MF_GRAYED);
  523.             EnableMenuItem((HMENU)wParam, MENU_DIALOGF, !gcap.fCapturing ?
  524.                 MF_ENABLED : MF_GRAYED);
  525.  
  526.             // toggles capturing audio or not - can't be capturing right now
  527.             // and we must have an audio capture device created
  528.             EnableMenuItem((HMENU)wParam, MENU_CAP_AUDIO, 
  529.                 (!gcap.fCapturing && gcap.pACap) ? MF_ENABLED : MF_GRAYED);
  530.             // are we capturing audio?
  531.             CheckMenuItem((HMENU)wParam, MENU_CAP_AUDIO, 
  532.                 (gcap.fCapAudio) ? MF_CHECKED : MF_UNCHECKED);
  533.             // are we doing closed captioning?
  534.             CheckMenuItem((HMENU)wParam, MENU_CAP_CC, 
  535.                 (gcap.fCapCC) ? MF_CHECKED : MF_UNCHECKED);
  536.             EnableMenuItem((HMENU)wParam, MENU_CAP_CC, 
  537.                 (gcap.fCCAvail) ? MF_ENABLED : MF_GRAYED);
  538.             // change audio formats when not capturing
  539.             EnableMenuItem((HMENU)wParam, MENU_AUDIOFORMAT, (gcap.fCapAudio &&
  540.                 !gcap.fCapturing) ? MF_ENABLED : MF_GRAYED);
  541.             // change frame rate when not capturing, and only if the video
  542.             // filter captures a VIDEOINFO type format
  543.             EnableMenuItem((HMENU)wParam, MENU_FRAMERATE,
  544.                 (!gcap.fCapturing && gcap.fCapAudioIsRelevant) ?
  545.                 MF_ENABLED : MF_GRAYED);
  546.             // change time limit when not capturing
  547.             EnableMenuItem((HMENU)wParam, MENU_TIMELIMIT,
  548.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  549.             // change capture file name when not capturing
  550.             EnableMenuItem((HMENU)wParam, MENU_SET_CAP_FILE,
  551.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  552.             // pre-allocate capture file when not capturing
  553.             EnableMenuItem((HMENU)wParam, MENU_ALLOC_CAP_FILE,
  554.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  555.             // can save capture file when not capturing
  556.             EnableMenuItem((HMENU)wParam, MENU_SAVE_CAP_FILE,
  557.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  558.             // do we want preview?
  559.             CheckMenuItem((HMENU)wParam, MENU_PREVIEW, 
  560.                 (gcap.fWantPreview) ? MF_CHECKED : MF_UNCHECKED);
  561.             // can toggle preview if not capturing
  562.             EnableMenuItem((HMENU)wParam, MENU_PREVIEW,
  563.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  564.  
  565.             // which is the master stream? Not applicable unless we're also
  566.             // capturing audio
  567.             EnableMenuItem((HMENU)wParam, MENU_NOMASTER,
  568.                 (!gcap.fCapturing && gcap.fCapAudio) ? MF_ENABLED : MF_GRAYED);
  569.             CheckMenuItem((HMENU)wParam, MENU_NOMASTER, 
  570.                 (gcap.iMasterStream == -1) ? MF_CHECKED : MF_UNCHECKED);
  571.             EnableMenuItem((HMENU)wParam, MENU_AUDIOMASTER,
  572.                 (!gcap.fCapturing && gcap.fCapAudio) ? MF_ENABLED : MF_GRAYED);
  573.             CheckMenuItem((HMENU)wParam, MENU_AUDIOMASTER, 
  574.                 (gcap.iMasterStream == 1) ? MF_CHECKED : MF_UNCHECKED);
  575.             EnableMenuItem((HMENU)wParam, MENU_VIDEOMASTER,
  576.                 (!gcap.fCapturing && gcap.fCapAudio) ? MF_ENABLED : MF_GRAYED);
  577.             CheckMenuItem((HMENU)wParam, MENU_VIDEOMASTER, 
  578.                 (gcap.iMasterStream == 0) ? MF_CHECKED : MF_UNCHECKED);
  579.  
  580.             // can't select a new capture device when capturing
  581.             EnableMenuItem((HMENU)wParam, MENU_VDEVICE0,
  582.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  583.             EnableMenuItem((HMENU)wParam, MENU_VDEVICE1,
  584.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  585.             EnableMenuItem((HMENU)wParam, MENU_VDEVICE2,
  586.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  587.             EnableMenuItem((HMENU)wParam, MENU_VDEVICE3,
  588.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  589.             EnableMenuItem((HMENU)wParam, MENU_VDEVICE4,
  590.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  591.             EnableMenuItem((HMENU)wParam, MENU_VDEVICE5,
  592.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  593.             EnableMenuItem((HMENU)wParam, MENU_VDEVICE6,
  594.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  595.             EnableMenuItem((HMENU)wParam, MENU_VDEVICE7,
  596.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  597.             EnableMenuItem((HMENU)wParam, MENU_VDEVICE8,
  598.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  599.             EnableMenuItem((HMENU)wParam, MENU_VDEVICE9,
  600.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  601.             EnableMenuItem((HMENU)wParam, MENU_ADEVICE0,
  602.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  603.             EnableMenuItem((HMENU)wParam, MENU_ADEVICE1,
  604.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  605.             EnableMenuItem((HMENU)wParam, MENU_ADEVICE2,
  606.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  607.             EnableMenuItem((HMENU)wParam, MENU_ADEVICE3,
  608.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  609.             EnableMenuItem((HMENU)wParam, MENU_ADEVICE4,
  610.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  611.             EnableMenuItem((HMENU)wParam, MENU_ADEVICE5,
  612.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  613.             EnableMenuItem((HMENU)wParam, MENU_ADEVICE6,
  614.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  615.             EnableMenuItem((HMENU)wParam, MENU_ADEVICE7,
  616.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  617.             EnableMenuItem((HMENU)wParam, MENU_ADEVICE8,
  618.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  619.             EnableMenuItem((HMENU)wParam, MENU_ADEVICE9,
  620.                 !gcap.fCapturing ? MF_ENABLED : MF_GRAYED);
  621.  
  622.             break;
  623.  
  624.  
  625.         case WM_INITMENUPOPUP:
  626.             if(GetSubMenu(GetMenu(ghwndApp), 1) == (HMENU)wParam) {
  627.                 AddDevicesToMenu();
  628.             }
  629.  
  630.             break;
  631.  
  632.         //
  633.         // We're out of here!
  634.         //
  635.         case WM_DESTROY:
  636.             DbgTerminate();
  637.  
  638.             IMonRelease(gcap.pmVideo);
  639.             IMonRelease(gcap.pmAudio); {
  640.                     for(int i = 0; i < NUMELMS(gcap.rgpmVideoMenu); i++) {
  641.                         IMonRelease(gcap.rgpmVideoMenu[i]);
  642.                     }
  643.                     for(i = 0; i < NUMELMS(gcap.rgpmAudioMenu); i++) {
  644.                         IMonRelease(gcap.rgpmAudioMenu[i]);
  645.                     }
  646.             }
  647.  
  648.             CoUninitialize();
  649.             PostQuitMessage(0);
  650.             break;
  651.  
  652.  
  653.         case WM_CLOSE:
  654.             OnClose();
  655.             break;
  656.  
  657.         case WM_ENDSESSION:
  658.             if(wParam || (lParam & ENDSESSION_LOGOFF)) {
  659.                 OnClose();
  660.             }
  661.             break;
  662.  
  663.         case WM_ERASEBKGND:
  664.             break;
  665.  
  666.         // ESC will stop capture
  667.         case WM_KEYDOWN:
  668.             if((GetAsyncKeyState(VK_ESCAPE) & 0x01) && gcap.fCapturing) {
  669.                 StopCapture();
  670.                 if(gcap.fWantPreview) {
  671.                     BuildPreviewGraph();
  672.                     StartPreview();
  673.                 }
  674.             }
  675.             break;
  676.  
  677.         case WM_PAINT:
  678.             hdc = BeginPaint(hwnd,&ps);
  679.  
  680.             // nothing to do
  681.             EndPaint(hwnd,&ps);
  682.             break;
  683.  
  684.         case WM_TIMER:
  685.             // update our status bar with #captured, #dropped
  686.             // if we've stopped capturing, don't do it anymore.  Some WM_TIMER
  687.             // messages may come late, after we've destroyed the graph and
  688.             // we'll get invalid numbers.
  689.             if(gcap.fCapturing)
  690.                 UpdateStatus(FALSE);
  691.  
  692.             // is our time limit up?
  693.             if(gcap.fUseTimeLimit) {
  694.                     if((timeGetTime() - gcap.lCapStartTime) / 1000 >=
  695.                         gcap.dwTimeLimit) {
  696.                         StopCapture();
  697.                         if(gcap.fWantPreview) {
  698.                             BuildPreviewGraph();
  699.                             StartPreview();
  700.                         }
  701.                     }
  702.             }
  703.             break;
  704.  
  705.         case WM_SIZE:
  706.             // make the preview window fit inside our window, taking up
  707.             // all of our client area except for the status window at the
  708.             // bottom
  709.             GetClientRect(ghwndApp, &rc);
  710.             cxBorder = GetSystemMetrics(SM_CXBORDER);
  711.             cyBorder = GetSystemMetrics(SM_CYBORDER);
  712.             cy = statusGetHeight() + cyBorder;
  713.             MoveWindow(ghwndStatus, -cxBorder, rc.bottom - cy,
  714.                 rc.right + (2 * cxBorder), cy + cyBorder, TRUE);
  715.             rc.bottom -= cy;
  716.             // this is the video renderer window showing the preview
  717.             if(gcap.pVW)
  718.                 gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom);
  719.             break;
  720.  
  721.         case WM_FGNOTIFY:
  722.             // uh-oh, something went wrong while capturing - the filtergraph
  723.             // will send us events like EC_COMPLETE, EC_USERABORT and the one
  724.             // we care about, EC_ERRORABORT.
  725.             if(gcap.pME) {
  726.                     LONG event, l1, l2;
  727.                     BOOL bAbort = FALSE;
  728.                     while(gcap.pME->GetEvent(&event, (LONG_PTR *) &l1, 
  729.                         (LONG_PTR *) &l2, 0) == S_OK) {
  730.                         gcap.pME->FreeEventParams(event, l1, l2);
  731.                         if(event == EC_ERRORABORT) {
  732.                             StopCapture();
  733.                             bAbort = TRUE;
  734.                             continue;
  735.                         }
  736.                         else if(event == EC_DEVICE_LOST) {
  737.                                 // Check if we have lost a capture filter being used.
  738.                                 // lParam2 of EC_DEVICE_LOST event == 1 indicates device added
  739.                                 //                                 == 0 indicates device removed
  740.                                 if(l2 == 0) {
  741.                                     IBaseFilter *pf;
  742.                                     IUnknown *punk = (IUnknown *) (LONG_PTR) l1;
  743.                                     if(S_OK == punk->QueryInterface(IID_IBaseFilter, (void **) &pf)) {
  744.                                         if(::IsEqualObject(gcap.pVCap, pf)) {
  745.                                             pf->Release();
  746.                                             bAbort = FALSE;
  747.                                             StopCapture();
  748.                                             TCHAR szError[100];
  749.                                             wsprintf(szError, 
  750.                                                 TEXT("Stopping Capture (Device Lost). Select New Capture Device"), l1);
  751.                                             ErrMsg(szError);
  752.                                             break;
  753.                                         }
  754.                                         pf->Release();
  755.                                     }
  756.                                 }
  757.                         }
  758.                     } // end while
  759.                     if(bAbort) {
  760.                             if(gcap.fWantPreview) {
  761.                                 BuildPreviewGraph();
  762.                                 StartPreview();
  763.                             }
  764.                             TCHAR szError[100];
  765.                             wsprintf(szError, TEXT("ERROR during capture, error code=%08x"), l1);
  766.                             ErrMsg(szError);
  767.                     }
  768.             }
  769.             break;
  770.  
  771.         case WM_DEVICECHANGE:
  772.             // We are interested in only device arrival & removal events
  773.             if(DBT_DEVICEARRIVAL != wParam && DBT_DEVICEREMOVECOMPLETE != wParam)
  774.                 break;
  775.             PDEV_BROADCAST_HDR pdbh = (PDEV_BROADCAST_HDR) lParam;
  776.             if(pdbh->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE) {
  777.                 break;
  778.             }
  779.             PDEV_BROADCAST_DEVICEINTERFACE pdbi = (PDEV_BROADCAST_DEVICEINTERFACE) lParam;
  780.             // Check for capture devices.
  781.             if(pdbi->dbcc_classguid != AM_KSCATEGORY_CAPTURE) {
  782.                 break;
  783.             }
  784.  
  785.             // Check for device arrival/removal.
  786.             if(DBT_DEVICEARRIVAL == wParam || DBT_DEVICEREMOVECOMPLETE == wParam) {
  787.                 gcap.fDeviceMenuPopulated = false;
  788.             }
  789.             break;
  790.  
  791.     }
  792.  
  793.     return (LONG) DefWindowProc(hwnd,msg,wParam,lParam);
  794. }
  795.  
  796.  
  797. // Make a graph builder object we can use for capture graph building
  798. //
  799. BOOL MakeBuilder() 
  800. {
  801.     // we have one already
  802.     if(gcap.pBuilder)
  803.         return TRUE;
  804.  
  805.     HRESULT hr = CoCreateInstance((REFCLSID)CLSID_CaptureGraphBuilder2,
  806.         NULL, CLSCTX_INPROC, (REFIID)IID_ICaptureGraphBuilder2,
  807.         (void **)&gcap.pBuilder);
  808.  
  809.     return (hr == NOERROR) ? TRUE : FALSE;
  810. }
  811.  
  812.  
  813. // Make a graph object we can use for capture graph building
  814. //
  815. BOOL MakeGraph() 
  816. {
  817.     // we have one already
  818.     if(gcap.pFg)
  819.         return TRUE;
  820.  
  821.     HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC,
  822.         IID_IGraphBuilder, (LPVOID *)&gcap.pFg);
  823.  
  824.     return (hr == NOERROR) ? TRUE : FALSE;
  825. }
  826.  
  827.  
  828. // make sure the preview window inside our window is as big as the
  829. // dimensions of captured video, or some capture cards won't show a preview.
  830. // (Also, it helps people tell what size video they're capturing)
  831. // We will resize our app's window big enough so that once the status bar
  832. // is positioned at the bottom there will be enough room for the preview
  833. // window to be w x h
  834. //
  835. int gnRecurse = 0;
  836.  
  837.  
  838. void ResizeWindow(int w, int h) 
  839. {
  840.     RECT rcW, rcC;
  841.     int xExtra, yExtra;
  842.     int cyBorder = GetSystemMetrics(SM_CYBORDER);
  843.  
  844.     gnRecurse++;
  845.  
  846.     GetWindowRect(ghwndApp, &rcW);
  847.     GetClientRect(ghwndApp, &rcC);
  848.     xExtra = rcW.right - rcW.left - rcC.right;
  849.     yExtra = rcW.bottom - rcW.top - rcC.bottom + cyBorder + statusGetHeight();
  850.  
  851.     rcC.right = w;
  852.     rcC.bottom = h;
  853.     SetWindowPos(ghwndApp, NULL, 0, 0, rcC.right + xExtra,
  854.         rcC.bottom + yExtra, SWP_NOZORDER | SWP_NOMOVE);
  855.  
  856.     // we may need to recurse once.  But more than that means the window cannot
  857.     // be made the size we want, trying will just stack fault.
  858.     //
  859.     if(gnRecurse == 1 && ((rcC.right + xExtra != rcW.right - rcW.left && w > GetSystemMetrics(SM_CXMIN)) ||
  860.         (rcC.bottom + yExtra != rcW.bottom - rcW.top)))
  861.         ResizeWindow(w,h);
  862.  
  863.     gnRecurse--;
  864. }
  865.  
  866.  
  867. // Tear down everything downstream of a given filter
  868. void NukeDownstream(IBaseFilter *pf) 
  869. {
  870.     //DbgLog((LOG_TRACE,1,TEXT("Nuking...")));
  871.  
  872.     IPin *pP, *pTo;
  873.     ULONG u;
  874.     IEnumPins *pins = NULL;
  875.     PIN_INFO pininfo;
  876.     HRESULT hr = pf->EnumPins(&pins);
  877.     pins->Reset();
  878.     while(hr == NOERROR) {
  879.         hr = pins->Next(1, &pP, &u);
  880.         if(hr == S_OK && pP) {
  881.             pP->ConnectedTo(&pTo);
  882.             if(pTo) {
  883.                 hr = pTo->QueryPinInfo(&pininfo);
  884.                 if(hr == NOERROR) {
  885.                     if(pininfo.dir == PINDIR_INPUT) {
  886.                         NukeDownstream(pininfo.pFilter);
  887.                         gcap.pFg->Disconnect(pTo);
  888.                         gcap.pFg->Disconnect(pP);
  889.                         gcap.pFg->RemoveFilter(pininfo.pFilter);
  890.                     }
  891.                     pininfo.pFilter->Release();
  892.                 }
  893.                 pTo->Release();
  894.             }
  895.             pP->Release();
  896.         }
  897.     }
  898.     if(pins)
  899.         pins->Release();
  900. }
  901.  
  902.  
  903. // Tear down everything downstream of the capture filters, so we can build
  904. // a different capture graph.  Notice that we never destroy the capture filters
  905. // and WDM filters upstream of them, because then all the capture settings
  906. // we've set would be lost.
  907. //
  908. void TearDownGraph() 
  909. {
  910.     if(gcap.pSink)
  911.         gcap.pSink->Release();
  912.     gcap.pSink = NULL;
  913.     if(gcap.pConfigAviMux)
  914.         gcap.pConfigAviMux->Release();
  915.     gcap.pConfigAviMux = NULL;
  916.     if(gcap.pRender)
  917.         gcap.pRender->Release();
  918.     gcap.pRender = NULL;
  919.     if(gcap.pVW) {
  920.         // stop drawing in our window, or we may get wierd repaint effects
  921.         gcap.pVW->put_Owner(NULL);
  922.         gcap.pVW->put_Visible(OAFALSE);
  923.         gcap.pVW->Release();
  924.     }
  925.     gcap.pVW = NULL;
  926.     if(gcap.pME)
  927.         gcap.pME->Release();
  928.     gcap.pME = NULL;
  929.     if(gcap.pDF)
  930.         gcap.pDF->Release();
  931.     gcap.pDF = NULL;
  932.  
  933.     // destroy the graph downstream of our capture filters
  934.     if(gcap.pVCap)
  935.         NukeDownstream(gcap.pVCap);
  936.     if(gcap.pACap)
  937.         NukeDownstream(gcap.pACap);
  938.  
  939.     // potential debug output - what the graph looks like
  940.     // if (gcap.pFg) DumpGraph(gcap.pFg, 1);
  941.  
  942. #ifdef REGISTER_FILTERGRAPH
  943.     // Remove filter graph from the running object table   
  944.     if (g_dwGraphRegister)
  945.     {
  946.         RemoveGraphFromRot(g_dwGraphRegister);
  947.         g_dwGraphRegister = 0;
  948.     }
  949. #endif
  950.  
  951.     gcap.fCaptureGraphBuilt = FALSE;
  952.     gcap.fPreviewGraphBuilt = FALSE;
  953.     gcap.fPreviewFaked = FALSE;
  954. }
  955.  
  956.  
  957. // create the capture filters of the graph.  We need to keep them loaded from
  958. // the beginning, so we can set parameters on them and have them remembered
  959. //
  960. BOOL InitCapFilters() 
  961. {
  962.     HRESULT hr=S_OK;
  963.     BOOL f;
  964.  
  965.     gcap.fCCAvail = FALSE;  // assume no closed captioning support
  966.  
  967.     f = MakeBuilder();
  968.     if(!f) {
  969.         ErrMsg(TEXT("Cannot instantiate graph builder"));
  970.         return FALSE;
  971.     }
  972.  
  973.     //
  974.     // First, we need a Video Capture filter, and some interfaces
  975.     //
  976.  
  977.     gcap.pVCap = NULL;
  978.     if(gcap.pmVideo != 0) {
  979.         IPropertyBag *pBag;
  980.         gcap.wachFriendlyName[0] = 0;
  981.         hr = gcap.pmVideo->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);
  982.         if(SUCCEEDED(hr)) {
  983.             VARIANT var;
  984.             var.vt = VT_BSTR;
  985.             hr = pBag->Read(L"FriendlyName", &var, NULL);
  986.             if(hr == NOERROR) {
  987.                 lstrcpyW(gcap.wachFriendlyName, var.bstrVal);
  988.                 SysFreeString(var.bstrVal);
  989.             }
  990.             pBag->Release();
  991.         }
  992.         hr = gcap.pmVideo->BindToObject(0, 0, IID_IBaseFilter, (void**)&gcap.pVCap);
  993.     }
  994.  
  995.     if(gcap.pVCap == NULL) {
  996.         ErrMsg(TEXT("Error %x: Cannot create video capture filter"), hr);
  997.         goto InitCapFiltersFail;
  998.     }
  999.  
  1000.     //
  1001.     // make a filtergraph, give it to the graph builder and put the video
  1002.     // capture filter in the graph
  1003.     //
  1004.  
  1005.     f = MakeGraph();
  1006.     if(!f) {
  1007.         ErrMsg(TEXT("Cannot instantiate filtergraph"));
  1008.         goto InitCapFiltersFail;
  1009.     }
  1010.     hr = gcap.pBuilder->SetFiltergraph(gcap.pFg);
  1011.     if(hr != NOERROR) {
  1012.         ErrMsg(TEXT("Cannot give graph to builder"));
  1013.         goto InitCapFiltersFail;
  1014.     }
  1015.  
  1016.     hr = gcap.pFg->AddFilter(gcap.pVCap, NULL);
  1017.     if(hr != NOERROR) {
  1018.         ErrMsg(TEXT("Error %x: Cannot add vidcap to filtergraph"), hr);
  1019.         goto InitCapFiltersFail;
  1020.     }
  1021.  
  1022.     // Calling FindInterface below will result in building the upstream
  1023.     // section of the capture graph (any WDM TVTuners or Crossbars we might
  1024.     // need).
  1025.  
  1026.     // we use this interface to get the name of the driver
  1027.     // Don't worry if it doesn't work:  This interface may not be available
  1028.     // until the pin is connected, or it may not be available at all.
  1029.     // (eg: interface may not be available for some DV capture)
  1030.     hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  1031.         &MEDIATYPE_Interleaved,
  1032.         gcap.pVCap, IID_IAMVideoCompression, (void **)&gcap.pVC);
  1033.     if(hr != S_OK) {
  1034.         hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  1035.             &MEDIATYPE_Video,
  1036.             gcap.pVCap, IID_IAMVideoCompression, (void **)&gcap.pVC);
  1037.     }
  1038.  
  1039.     // !!! What if this interface isn't supported?
  1040.     // we use this interface to set the frame rate and get the capture size
  1041.     hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  1042.         &MEDIATYPE_Interleaved,
  1043.         gcap.pVCap, IID_IAMStreamConfig, (void **)&gcap.pVSC);
  1044.     if(hr != NOERROR) {
  1045.         hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  1046.             &MEDIATYPE_Video, gcap.pVCap,
  1047.             IID_IAMStreamConfig, (void **)&gcap.pVSC);
  1048.         if(hr != NOERROR) {
  1049.             // this means we can't set frame rate (non-DV only)
  1050.             ErrMsg(TEXT("Error %x: Cannot find VCapture:IAMStreamConfig"), hr);
  1051.         }
  1052.     }
  1053.  
  1054.     gcap.fCapAudioIsRelevant = TRUE;
  1055.  
  1056.     AM_MEDIA_TYPE *pmt;
  1057.  
  1058.     // default capture format
  1059.     if(gcap.pVSC && gcap.pVSC->GetFormat(&pmt) == S_OK) {
  1060.         // DV capture does not use a VIDEOINFOHEADER
  1061.         if(pmt->formattype == FORMAT_VideoInfo) {
  1062.             // resize our window to the default capture size
  1063.             ResizeWindow(HEADER(pmt->pbFormat)->biWidth,
  1064.                 ABS(HEADER(pmt->pbFormat)->biHeight));
  1065.         }
  1066.         if(pmt->majortype != MEDIATYPE_Video) {
  1067.             // This capture filter captures something other that pure video.
  1068.             // Maybe it's DV or something?  Anyway, chances are we shouldn't
  1069.             // allow capturing audio separately, since our video capture    
  1070.             // filter may have audio combined in it already!
  1071.             gcap.fCapAudioIsRelevant = FALSE;
  1072.             gcap.fCapAudio = FALSE;
  1073.         }
  1074.         DeleteMediaType(pmt);
  1075.     }
  1076.  
  1077.     // we use this interface to bring up the 3 dialogs
  1078.     // NOTE:  Only the VfW capture filter supports this.  This app only brings
  1079.     // up dialogs for legacy VfW capture drivers, since only those have dialogs
  1080.     hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  1081.         &MEDIATYPE_Video, gcap.pVCap,
  1082.         IID_IAMVfwCaptureDialogs, (void **)&gcap.pDlg);
  1083.  
  1084.  
  1085.     // Use the crossbar class to help us sort out all the possible video inputs
  1086.     // The class needs to be given the capture filters ANALOGVIDEO input pin
  1087.     {
  1088.         IPin        *pP = 0;
  1089.         IEnumPins   *pins;
  1090.         ULONG        n;
  1091.         PIN_INFO     pinInfo;
  1092.         BOOL         Found = FALSE;
  1093.         IKsPropertySet *pKs;
  1094.         GUID guid;
  1095.         DWORD dw;
  1096.         BOOL fMatch = FALSE;
  1097.  
  1098.         gcap.pCrossbar = NULL;
  1099.  
  1100.         if(SUCCEEDED(gcap.pVCap->EnumPins(&pins))) {            
  1101.             while(!Found && (S_OK == pins->Next(1, &pP, &n))) {
  1102.                 if(S_OK == pP->QueryPinInfo(&pinInfo)) {
  1103.                     if(pinInfo.dir == PINDIR_INPUT) {
  1104.                         // is this pin an ANALOGVIDEOIN input pin?
  1105.                         if(pP->QueryInterface(IID_IKsPropertySet,
  1106.                             (void **)&pKs) == S_OK) {
  1107.                             if(pKs->Get(AMPROPSETID_Pin,
  1108.                                 AMPROPERTY_PIN_CATEGORY, NULL, 0,
  1109.                                 &guid, sizeof(GUID), &dw) == S_OK) {
  1110.                                 if(guid == PIN_CATEGORY_ANALOGVIDEOIN)
  1111.                                     fMatch = TRUE;
  1112.                             }
  1113.                             pKs->Release();
  1114.                         }
  1115.  
  1116.                         if(fMatch) {
  1117.                             gcap.pCrossbar = new CCrossbar(pP);
  1118.                             hr = gcap.pCrossbar->GetInputCount
  1119.                                 (&gcap.NumberOfVideoInputs);
  1120.                             Found = TRUE;
  1121.                         }
  1122.                     }
  1123.                     pinInfo.pFilter->Release();
  1124.                 }
  1125.                 pP->Release();
  1126.             }
  1127.             pins->Release();
  1128.         }
  1129.     }
  1130.  
  1131.     // there's no point making an audio capture filter
  1132.     if(gcap.fCapAudioIsRelevant == FALSE)
  1133.         goto SkipAudio;
  1134.  
  1135.     // create the audio capture filter, even if we are not capturing audio right
  1136.     // now, so we have all the filters around all the time.
  1137.  
  1138.     //
  1139.     // We want an audio capture filter and some interfaces
  1140.     //
  1141.  
  1142.     if(gcap.pmAudio == 0) {
  1143.         // there are no audio capture devices. We'll only allow video capture
  1144.         gcap.fCapAudio = FALSE;
  1145.         goto SkipAudio;
  1146.     }
  1147.     gcap.pACap = NULL;
  1148.  
  1149.  
  1150.     hr = gcap.pmAudio->BindToObject(0, 0, IID_IBaseFilter, (void**)&gcap.pACap);
  1151.  
  1152.     if(gcap.pACap == NULL) {
  1153.         // there are no audio capture devices. We'll only allow video capture
  1154.         gcap.fCapAudio = FALSE;
  1155.         ErrMsg(TEXT("Cannot create audio capture filter"));
  1156.         goto SkipAudio;
  1157.     }
  1158.  
  1159.     //
  1160.     // put the audio capture filter in the graph
  1161.     //
  1162.  
  1163.     // We'll need this in the graph to get audio property pages
  1164.     hr = gcap.pFg->AddFilter(gcap.pACap, NULL);
  1165.     if(hr != NOERROR) {
  1166.         ErrMsg(TEXT("Error %x: Cannot add audcap to filtergraph"), hr);
  1167.         goto InitCapFiltersFail;
  1168.     }
  1169.  
  1170.     // Calling FindInterface below will result in building the upstream
  1171.     // section of the capture graph (any WDM TVAudio's or Crossbars we might
  1172.     // need).
  1173.  
  1174.     // !!! What if this interface isn't supported?
  1175.     // we use this interface to set the captured wave format
  1176.     hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio,
  1177.         gcap.pACap, IID_IAMStreamConfig, (void **)&gcap.pASC);
  1178.     if(hr != NOERROR) {
  1179.         ErrMsg(TEXT("Cannot find ACapture:IAMStreamConfig"));
  1180.     }
  1181.  
  1182. SkipAudio:
  1183.  
  1184.     // Can this filter do closed captioning?
  1185.     IPin *pPin;
  1186.     hr = gcap.pBuilder->FindPin(gcap.pVCap, PINDIR_OUTPUT, &PIN_CATEGORY_VBI,
  1187.         NULL, FALSE, 0, &pPin);
  1188.     if(hr != S_OK)
  1189.         hr = gcap.pBuilder->FindPin(gcap.pVCap, PINDIR_OUTPUT, &PIN_CATEGORY_CC,
  1190.             NULL, FALSE, 0, &pPin);
  1191.     if(hr == S_OK) {
  1192.         pPin->Release();
  1193.         gcap.fCCAvail = TRUE;
  1194.     }
  1195.     else {
  1196.         gcap.fCapCC = FALSE;    // can't capture it, then
  1197.     }
  1198.  
  1199.     // potential debug output - what the graph looks like
  1200.     // DumpGraph(gcap.pFg, 1);
  1201.  
  1202.     return TRUE;
  1203.  
  1204. InitCapFiltersFail:
  1205.     FreeCapFilters();
  1206.     return FALSE;
  1207. }
  1208.  
  1209.  
  1210. // all done with the capture filters and the graph builder
  1211. //
  1212. void FreeCapFilters() 
  1213. {
  1214.     if(gcap.pFg)
  1215.         gcap.pFg->Release();
  1216.     gcap.pFg = NULL;
  1217.     if(gcap.pBuilder)
  1218.         gcap.pBuilder->Release();
  1219.     gcap.pBuilder = NULL;
  1220.     if(gcap.pVCap)
  1221.         gcap.pVCap->Release();
  1222.     gcap.pVCap = NULL;
  1223.     if(gcap.pACap)
  1224.         gcap.pACap->Release();
  1225.     gcap.pACap = NULL;
  1226.     if(gcap.pASC)
  1227.         gcap.pASC->Release();
  1228.     gcap.pASC = NULL;
  1229.     if(gcap.pVSC)
  1230.         gcap.pVSC->Release();
  1231.     gcap.pVSC = NULL;
  1232.     if(gcap.pVC)
  1233.         gcap.pVC->Release();
  1234.     gcap.pVC = NULL;
  1235.     if(gcap.pDlg)
  1236.         gcap.pDlg->Release();
  1237.     gcap.pDlg = NULL;
  1238.  
  1239.     if(gcap.pCrossbar) {
  1240.         delete gcap.pCrossbar;
  1241.         gcap.pCrossbar = NULL;
  1242.     }
  1243. }
  1244.  
  1245.  
  1246. // build the capture graph
  1247. //
  1248. BOOL BuildCaptureGraph() 
  1249. {
  1250.     USES_CONVERSION;
  1251.     int cy, cyBorder;
  1252.     HRESULT hr;
  1253.     BOOL f;
  1254.     AM_MEDIA_TYPE *pmt;
  1255.  
  1256.     // we have one already
  1257.     if(gcap.fCaptureGraphBuilt)
  1258.         return TRUE;
  1259.  
  1260.     // No rebuilding while we're running
  1261.     if(gcap.fCapturing || gcap.fPreviewing)
  1262.         return FALSE;
  1263.  
  1264.     // We don't have the necessary capture filters
  1265.     if(gcap.pVCap == NULL)
  1266.         return FALSE;
  1267.     if(gcap.pACap == NULL && gcap.fCapAudio)
  1268.         return FALSE;
  1269.  
  1270.     // no capture file name yet... we need one first
  1271.     if(gcap.szCaptureFile[0] == 0) {
  1272.         f = SetCaptureFile(ghwndApp);
  1273.         if(!f)
  1274.             return f;
  1275.     }
  1276.  
  1277.     // we already have another graph built... tear down the old one
  1278.     if(gcap.fPreviewGraphBuilt)
  1279.         TearDownGraph();
  1280.  
  1281.     //
  1282.     // We need a rendering section that will write the capture file out in AVI
  1283.     // file format
  1284.     //
  1285.  
  1286.     GUID guid = MEDIASUBTYPE_Avi;
  1287.     hr = gcap.pBuilder->SetOutputFileName(&guid, T2W(gcap.szCaptureFile),
  1288.         &gcap.pRender, &gcap.pSink);
  1289.     if(hr != NOERROR) {
  1290.         ErrMsg(TEXT("Cannot set output file"));
  1291.         goto SetupCaptureFail;
  1292.     }
  1293.  
  1294.     // Now tell the AVIMUX to write out AVI files that old apps can read properly.
  1295.     // If we don't, most apps won't be able to tell where the keyframes are,
  1296.     // slowing down editing considerably
  1297.     // Doing this will cause one seek (over the area the index will go) when
  1298.     // you capture past 1 Gig, but that's no big deal.
  1299.     // NOTE: This is on by default, so it's not necessary to turn it on
  1300.  
  1301.     // Also, set the proper MASTER STREAM
  1302.  
  1303.     hr = gcap.pRender->QueryInterface(IID_IConfigAviMux,
  1304.         (void **)&gcap.pConfigAviMux);
  1305.     if(hr == NOERROR && gcap.pConfigAviMux) {
  1306.         gcap.pConfigAviMux->SetOutputCompatibilityIndex(TRUE);
  1307.         if(gcap.fCapAudio) {
  1308.             hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream);
  1309.             if(hr != NOERROR)
  1310.                 ErrMsg(TEXT("SetMasterStream failed!"));
  1311.         }
  1312.     }
  1313.  
  1314.     //
  1315.     // Render the video capture and preview pins - even if the capture filter only
  1316.     // has a capture pin (and no preview pin) this should work... because the
  1317.     // capture graph builder will use a smart tee filter to provide both capture
  1318.     // and preview.  We don't have to worry.  It will just work.
  1319.     //
  1320.  
  1321.     // NOTE that we try to render the interleaved pin before the video pin, because
  1322.     // if BOTH exist, it's a DV filter and the only way to get the audio is to use
  1323.     // the interleaved pin.  Using the Video pin on a DV filter is only useful if
  1324.     // you don't want the audio.
  1325.  
  1326.     hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE,
  1327.         &MEDIATYPE_Interleaved,
  1328.         gcap.pVCap, NULL, gcap.pRender);
  1329.     if(hr != NOERROR) {
  1330.         hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE,
  1331.             &MEDIATYPE_Video,
  1332.             gcap.pVCap, NULL, gcap.pRender);
  1333.         if(hr != NOERROR) {
  1334.             ErrMsg(TEXT("Cannot render video capture stream"));
  1335.             goto SetupCaptureFail;
  1336.         }
  1337.     }
  1338.  
  1339.     if(gcap.fWantPreview) {
  1340.         hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW,
  1341.             &MEDIATYPE_Interleaved, gcap.pVCap, NULL, NULL);
  1342.         if(hr == VFW_S_NOPREVIEWPIN) {
  1343.             // preview was faked up for us using the (only) capture pin
  1344.             gcap.fPreviewFaked = TRUE;
  1345.         }
  1346.         else if(hr != S_OK) {
  1347.             hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW,
  1348.                 &MEDIATYPE_Video, gcap.pVCap, NULL, NULL);
  1349.             if(hr == VFW_S_NOPREVIEWPIN) {
  1350.                 // preview was faked up for us using the (only) capture pin
  1351.                 gcap.fPreviewFaked = TRUE;
  1352.             }
  1353.             else if(hr != S_OK) {
  1354.                 ErrMsg(TEXT("Cannot render video preview stream"));
  1355.                 goto SetupCaptureFail;
  1356.             }
  1357.         }
  1358.     }
  1359.  
  1360.     //
  1361.     // Render the audio capture pin?
  1362.     //
  1363.  
  1364.     if(gcap.fCapAudio) {
  1365.         hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE,
  1366.             &MEDIATYPE_Audio, gcap.pACap, NULL, gcap.pRender);
  1367.         if(hr != NOERROR) {
  1368.             ErrMsg(TEXT("Cannot render audio capture stream"));
  1369.             goto SetupCaptureFail;
  1370.         }
  1371.     }
  1372.  
  1373.     //
  1374.     // Render the closed captioning pin? It could be a CC or a VBI category pin,
  1375.     // depending on the capture driver
  1376.     //
  1377.  
  1378.     if(gcap.fCapCC) {
  1379.         hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CC, NULL,
  1380.             gcap.pVCap, NULL, gcap.pRender);
  1381.         if(hr != NOERROR) {
  1382.             hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_VBI, NULL,
  1383.                 gcap.pVCap, NULL, gcap.pRender);
  1384.             if(hr != NOERROR) {
  1385.                 ErrMsg(TEXT("Cannot render closed captioning"));
  1386.                 // so what? goto SetupCaptureFail;
  1387.             }
  1388.         }
  1389.         // To preview and capture VBI at the same time, we can call this twice
  1390.         if(gcap.fWantPreview) {
  1391.             hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_VBI, NULL,
  1392.                 gcap.pVCap, NULL, NULL);
  1393.         }
  1394.     }
  1395.  
  1396.     //
  1397.     // Get the preview window to be a child of our app's window
  1398.     //
  1399.  
  1400.     // This will find the IVideoWindow interface on the renderer.  It is 
  1401.     // important to ask the filtergraph for this interface... do NOT use
  1402.     // ICaptureGraphBuilder2::FindInterface, because the filtergraph needs to
  1403.     // know we own the window so it can give us display changed messages, etc.
  1404.  
  1405.     if(gcap.fWantPreview) {
  1406.         hr = gcap.pFg->QueryInterface(IID_IVideoWindow, (void **)&gcap.pVW);
  1407.         if(hr != NOERROR && gcap.fWantPreview) {
  1408.             ErrMsg(TEXT("This graph cannot preview"));
  1409.         }
  1410.         else if(hr == NOERROR) {
  1411.             RECT rc;
  1412.             gcap.pVW->put_Owner((OAHWND)ghwndApp);    // We own the window now
  1413.             gcap.pVW->put_WindowStyle(WS_CHILD);    // you are now a child
  1414.             // give the preview window all our space but where the status bar is
  1415.             GetClientRect(ghwndApp, &rc);
  1416.             cyBorder = GetSystemMetrics(SM_CYBORDER);
  1417.             cy = statusGetHeight() + cyBorder;
  1418.             rc.bottom -= cy;
  1419.             gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); // be this big
  1420.             gcap.pVW->put_Visible(OATRUE);
  1421.         }
  1422.     }
  1423.  
  1424.     // now tell it what frame rate to capture at.  Just find the format it
  1425.     // is capturing with, and leave everything alone but change the frame rate
  1426.     hr = gcap.fUseFrameRate ? E_FAIL : NOERROR;
  1427.     if(gcap.pVSC && gcap.fUseFrameRate) {
  1428.         hr = gcap.pVSC->GetFormat(&pmt);
  1429.         // DV capture does not use a VIDEOINFOHEADER
  1430.         if(hr == NOERROR) {
  1431.             if(pmt->formattype == FORMAT_VideoInfo) {
  1432.                 VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)pmt->pbFormat;
  1433.                 pvi->AvgTimePerFrame = (LONGLONG)(10000000 / gcap.FrameRate);
  1434.                 hr = gcap.pVSC->SetFormat(pmt);
  1435.             }
  1436.             DeleteMediaType(pmt);
  1437.         }
  1438.     }
  1439.     if(hr != NOERROR)
  1440.         ErrMsg(TEXT("Cannot set frame rate for capture"));
  1441.  
  1442.     // now ask the filtergraph to tell us when something is completed or aborted
  1443.     // (EC_COMPLETE, EC_USERABORT, EC_ERRORABORT).  This is how we will find out
  1444.     // if the disk gets full while capturing
  1445.     hr = gcap.pFg->QueryInterface(IID_IMediaEventEx, (void **)&gcap.pME);
  1446.     if(hr == NOERROR) {
  1447.         gcap.pME->SetNotifyWindow((OAHWND)ghwndApp, WM_FGNOTIFY, 0);
  1448.     }
  1449.  
  1450.     // potential debug output - what the graph looks like
  1451.     // DumpGraph(gcap.pFg, 1);
  1452.  
  1453.     // Add our graph to the running object table, which will allow
  1454.     // the GraphEdit application to "spy" on our graph
  1455. #ifdef REGISTER_FILTERGRAPH
  1456.     hr = AddGraphToRot(gcap.pFg, &g_dwGraphRegister);
  1457.     if (FAILED(hr))
  1458.     {
  1459.         ErrMsg(TEXT("Failed to register filter graph with ROT!  hr=0x%x"), hr);
  1460.         g_dwGraphRegister = 0;
  1461.     }
  1462. #endif
  1463.  
  1464.     // All done.
  1465.     gcap.fCaptureGraphBuilt = TRUE;
  1466.     return TRUE;
  1467.  
  1468. SetupCaptureFail:
  1469.     TearDownGraph();
  1470.     return FALSE;
  1471. }
  1472.  
  1473.  
  1474.  
  1475. // build the preview graph!
  1476. //
  1477. // !!! PLEASE NOTE !!!  Some new WDM devices have totally separate capture
  1478. // and preview settings.  An application that wishes to preview and then 
  1479. // capture may have to set the preview pin format using IAMStreamConfig on the
  1480. // preview pin, and then again on the capture pin to capture with that format.
  1481. // In this sample app, there is a separate page to set the settings on the 
  1482. // capture pin and one for the preview pin.  To avoid the user
  1483. // having to enter the same settings in 2 dialog boxes, an app can have its own
  1484. // UI for choosing a format (the possible formats can be enumerated using
  1485. // IAMStreamConfig) and then the app can programmatically call IAMStreamConfig
  1486. // to set the format on both pins.
  1487. //
  1488. BOOL BuildPreviewGraph() 
  1489. {
  1490.     int cy, cyBorder;
  1491.     HRESULT hr;
  1492.     AM_MEDIA_TYPE *pmt;
  1493.  
  1494.     // we have one already
  1495.     if(gcap.fPreviewGraphBuilt)
  1496.         return TRUE;
  1497.  
  1498.     // No rebuilding while we're running
  1499.     if(gcap.fCapturing || gcap.fPreviewing)
  1500.         return FALSE;
  1501.  
  1502.     // We don't have the necessary capture filters
  1503.     if(gcap.pVCap == NULL)
  1504.         return FALSE;
  1505.     if(gcap.pACap == NULL && gcap.fCapAudio)
  1506.         return FALSE;
  1507.  
  1508.     // we already have another graph built... tear down the old one
  1509.     if(gcap.fCaptureGraphBuilt)
  1510.         TearDownGraph();
  1511.  
  1512.     //
  1513.     // Render the preview pin - even if there is not preview pin, the capture
  1514.     // graph builder will use a smart tee filter and provide a preview.
  1515.     //
  1516.     // !!! what about latency/buffer issues?
  1517.  
  1518.     // NOTE that we try to render the interleaved pin before the video pin, because
  1519.     // if BOTH exist, it's a DV filter and the only way to get the audio is to use
  1520.     // the interleaved pin.  Using the Video pin on a DV filter is only useful if
  1521.     // you don't want the audio.
  1522.  
  1523.     hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW,
  1524.         &MEDIATYPE_Interleaved, gcap.pVCap, NULL, NULL);
  1525.     if(hr == VFW_S_NOPREVIEWPIN) {
  1526.         // preview was faked up for us using the (only) capture pin
  1527.         gcap.fPreviewFaked = TRUE;
  1528.     }
  1529.     else if(hr != S_OK) {
  1530.         // maybe it's DV?
  1531.         hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW,
  1532.             &MEDIATYPE_Video, gcap.pVCap, NULL, NULL);
  1533.         if(hr == VFW_S_NOPREVIEWPIN) {
  1534.             // preview was faked up for us using the (only) capture pin
  1535.             gcap.fPreviewFaked = TRUE;
  1536.         }
  1537.         else if(hr != S_OK) {
  1538.             ErrMsg(TEXT("This graph cannot preview!"));
  1539.         }
  1540.     }
  1541.  
  1542.     //
  1543.     // Render the closed captioning pin? It could be a CC or a VBI category pin,
  1544.     // depending on the capture driver
  1545.     //
  1546.  
  1547.     if(gcap.fCapCC) {
  1548.         hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CC, NULL,
  1549.             gcap.pVCap, NULL, NULL);
  1550.         if(hr != NOERROR) {
  1551.             hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_VBI, NULL,
  1552.                 gcap.pVCap, NULL, NULL);
  1553.             if(hr != NOERROR) {
  1554.                 ErrMsg(TEXT("Cannot render closed captioning"));
  1555.                 // so what? goto SetupCaptureFail;
  1556.             }
  1557.         }
  1558.     }
  1559.  
  1560.     //
  1561.     // Get the preview window to be a child of our app's window
  1562.     //
  1563.  
  1564.     // This will find the IVideoWindow interface on the renderer.  It is 
  1565.     // important to ask the filtergraph for this interface... do NOT use
  1566.     // ICaptureGraphBuilder2::FindInterface, because the filtergraph needs to
  1567.     // know we own the window so it can give us display changed messages, etc.
  1568.  
  1569.     hr = gcap.pFg->QueryInterface(IID_IVideoWindow, (void **)&gcap.pVW);
  1570.     if(hr != NOERROR) {
  1571.         ErrMsg(TEXT("This graph cannot preview properly"));
  1572.     }
  1573.     else {
  1574.  
  1575.         //Find out if this is a DV stream
  1576.         AM_MEDIA_TYPE * pmtDV;
  1577.  
  1578.  
  1579.         if(gcap.pVSC && SUCCEEDED(gcap.pVSC->GetFormat(&pmtDV))) {
  1580.             if(pmtDV->formattype == FORMAT_DvInfo) {
  1581.                 // in this case we want to set the size of the parent window to that of 
  1582.                 // current DV resolution.
  1583.                 // We get that resolution from the IVideoWindow.
  1584.                 CComQIPtr <IBasicVideo, &IID_IBasicVideo> pBV(gcap.pVW);
  1585.  
  1586.                 if(pBV != NULL) {   
  1587.                     HRESULT hr1, hr2;
  1588.                     long lWidth, lHeight;
  1589.                     hr1 = pBV->get_VideoHeight(&lHeight);
  1590.                     hr2 = pBV->get_VideoWidth(&lWidth);
  1591.                     if(SUCCEEDED(hr1) && SUCCEEDED(hr2)) {
  1592.                         ResizeWindow(lWidth, abs(lHeight));
  1593.                     }
  1594.                 }   
  1595.             }
  1596.         }
  1597.  
  1598.         RECT rc;
  1599.         gcap.pVW->put_Owner((OAHWND)ghwndApp);    // We own the window now
  1600.         gcap.pVW->put_WindowStyle(WS_CHILD);    // you are now a child
  1601.  
  1602.  
  1603.         // give the preview window all our space but where the status bar is
  1604.         GetClientRect(ghwndApp, &rc);
  1605.         cyBorder = GetSystemMetrics(SM_CYBORDER);
  1606.         cy = statusGetHeight() + cyBorder;
  1607.         rc.bottom -= cy;
  1608.         gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); // be this big
  1609.         gcap.pVW->put_Visible(OATRUE);
  1610.     }
  1611.  
  1612.     // now tell it what frame rate to capture at.  Just find the format it
  1613.     // is capturing with, and leave everything alone but change the frame rate
  1614.     // No big deal if it fails.  It's just for preview
  1615.     // !!! Should we then talk to the preview pin?
  1616.     if(gcap.pVSC && gcap.fUseFrameRate) {
  1617.         hr = gcap.pVSC->GetFormat(&pmt);
  1618.         // DV capture does not use a VIDEOINFOHEADER
  1619.         if(hr == NOERROR) {
  1620.             if(pmt->formattype == FORMAT_VideoInfo) {
  1621.                 VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)pmt->pbFormat;
  1622.                 pvi->AvgTimePerFrame = (LONGLONG)(10000000 / gcap.FrameRate);
  1623.                 hr = gcap.pVSC->SetFormat(pmt);
  1624.                 if(hr != NOERROR)
  1625.                     ErrMsg(TEXT("%x: Cannot set frame rate for preview"), hr);
  1626.             }
  1627.             DeleteMediaType(pmt);
  1628.         }
  1629.     }
  1630.  
  1631.     // make sure we process events while we're previewing!
  1632.     hr = gcap.pFg->QueryInterface(IID_IMediaEventEx, (void **)&gcap.pME);
  1633.     if(hr == NOERROR) {
  1634.         gcap.pME->SetNotifyWindow((OAHWND)ghwndApp, WM_FGNOTIFY, 0);
  1635.     }
  1636.  
  1637.     // potential debug output - what the graph looks like
  1638.     // DumpGraph(gcap.pFg, 1);
  1639.  
  1640.     // Add our graph to the running object table, which will allow
  1641.     // the GraphEdit application to "spy" on our graph
  1642. #ifdef REGISTER_FILTERGRAPH
  1643.     hr = AddGraphToRot(gcap.pFg, &g_dwGraphRegister);
  1644.     if (FAILED(hr))
  1645.     {
  1646.         ErrMsg(TEXT("Failed to register filter graph with ROT!  hr=0x%x"), hr);
  1647.         g_dwGraphRegister = 0;
  1648.     }
  1649. #endif
  1650.  
  1651.     // All done.
  1652.     gcap.fPreviewGraphBuilt = TRUE;
  1653.     return TRUE;
  1654. }
  1655.  
  1656.  
  1657. // Start previewing
  1658. //
  1659. BOOL StartPreview() 
  1660. {
  1661.     // way ahead of you
  1662.     if(gcap.fPreviewing)
  1663.         return TRUE;
  1664.  
  1665.     if(!gcap.fPreviewGraphBuilt)
  1666.         return FALSE;
  1667.  
  1668.     // run the graph
  1669.     IMediaControl *pMC = NULL;
  1670.     HRESULT hr = gcap.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC);
  1671.     if(SUCCEEDED(hr)) {
  1672.         hr = pMC->Run();
  1673.         if(FAILED(hr)) {
  1674.             // stop parts that ran
  1675.             pMC->Stop();
  1676.         }
  1677.         pMC->Release();
  1678.     }
  1679.     if(FAILED(hr)) {
  1680.         ErrMsg(TEXT("Error %x: Cannot run preview graph"), hr);
  1681.         return FALSE;
  1682.     }
  1683.  
  1684.     gcap.fPreviewing = TRUE;
  1685.     return TRUE;
  1686. }
  1687.  
  1688.  
  1689. // stop the preview graph
  1690. //
  1691. BOOL StopPreview() 
  1692. {
  1693.     // way ahead of you
  1694.     if(!gcap.fPreviewing) {
  1695.         return FALSE;
  1696.     }
  1697.  
  1698.     // stop the graph
  1699.     IMediaControl *pMC = NULL;
  1700.     HRESULT hr = gcap.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC);
  1701.     if(SUCCEEDED(hr)) {
  1702.         hr = pMC->Stop();
  1703.         pMC->Release();
  1704.     }
  1705.     if(FAILED(hr)) {
  1706.         ErrMsg(TEXT("Error %x: Cannot stop preview graph"), hr);
  1707.         return FALSE;
  1708.     }
  1709.  
  1710.     gcap.fPreviewing = FALSE;
  1711.  
  1712.     // !!! get rid of menu garbage
  1713.     InvalidateRect(ghwndApp, NULL, TRUE);
  1714.  
  1715.     return TRUE;
  1716. }
  1717.  
  1718.  
  1719. // start the capture graph
  1720. //
  1721. BOOL StartCapture() 
  1722. {
  1723.     BOOL f, fHasStreamControl;
  1724.     HRESULT hr;
  1725.  
  1726.     // way ahead of you
  1727.     if(gcap.fCapturing)
  1728.         return TRUE;
  1729.  
  1730.     // or we'll get confused
  1731.     if(gcap.fPreviewing)
  1732.         StopPreview();
  1733.  
  1734.     // or we'll crash
  1735.     if(!gcap.fCaptureGraphBuilt)
  1736.         return FALSE;
  1737.  
  1738.     // This amount will be subtracted from the number of dropped and not 
  1739.     // dropped frames reported by the filter.  Since we might be having the
  1740.     // filter running while the pin is turned off, we don't want any of the
  1741.     // frame statistics from the time the pin is off interfering with the
  1742.     // statistics we gather while the pin is on
  1743.     gcap.lDroppedBase = 0;
  1744.     gcap.lNotBase = 0;
  1745.  
  1746.     REFERENCE_TIME start = MAX_TIME, stop = MAX_TIME;
  1747.  
  1748.     // don't capture quite yet...
  1749.     hr = gcap.pBuilder->ControlStream(&PIN_CATEGORY_CAPTURE, NULL,
  1750.         NULL, &start, NULL, 0, 0);
  1751.     //DbgLog((LOG_TRACE,1,TEXT("Capture OFF returns %x"), hr));
  1752.  
  1753.     // Do we have the ability to control capture and preview separately?
  1754.     fHasStreamControl = SUCCEEDED(hr);
  1755.  
  1756.     // prepare to run the graph
  1757.     IMediaControl *pMC = NULL;
  1758.     hr = gcap.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC);
  1759.     if(FAILED(hr)) {
  1760.         ErrMsg(TEXT("Error %x: Cannot get IMediaControl"), hr);
  1761.         return FALSE;
  1762.     }
  1763.  
  1764.     // If we were able to keep capture off, then we can
  1765.     // run the graph now for frame accurate start later yet still showing a
  1766.     // preview.   Otherwise, we can't run the graph yet without capture
  1767.     // starting too, so we'll pause it so the latency between when they
  1768.     // press a key and when capture begins is still small (but they won't have
  1769.     // a preview while they wait to press a key)
  1770.  
  1771.     if(fHasStreamControl)
  1772.         hr = pMC->Run();
  1773.     else
  1774.         hr = pMC->Pause();
  1775.     if(FAILED(hr)) {
  1776.         // stop parts that started
  1777.         pMC->Stop();
  1778.         pMC->Release();
  1779.         ErrMsg(TEXT("Error %x: Cannot start graph"), hr);
  1780.         return FALSE;
  1781.     }
  1782.  
  1783.     // press a key to start capture
  1784.     f = DoDialog(ghwndApp, IDD_PressAKeyDialog, (DLGPROC)PressAKeyProc, 0);
  1785.     if(!f) {
  1786.         pMC->Stop();
  1787.         pMC->Release();
  1788.         if(gcap.fWantPreview) {
  1789.             BuildPreviewGraph();
  1790.             StartPreview();
  1791.         }
  1792.         return f;
  1793.     }
  1794.  
  1795.     // Start capture NOW!
  1796.     if(fHasStreamControl) {
  1797.         // we may not have this yet
  1798.         if(!gcap.pDF) {
  1799.             hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  1800.                 &MEDIATYPE_Interleaved, gcap.pVCap,
  1801.                 IID_IAMDroppedFrames, (void **)&gcap.pDF);
  1802.             if(hr != NOERROR)
  1803.                 hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  1804.                     &MEDIATYPE_Video, gcap.pVCap,
  1805.                     IID_IAMDroppedFrames, (void **)&gcap.pDF);
  1806.         }
  1807.  
  1808.         // turn the capture pin on now!
  1809.         hr = gcap.pBuilder->ControlStream(&PIN_CATEGORY_CAPTURE, NULL,
  1810.             NULL, NULL, &stop, 0, 0);
  1811.         //DbgLog((LOG_TRACE,0,TEXT("Capture ON returns %x"), hr));
  1812.         // make note of the current dropped frame counts
  1813.         if(gcap.pDF) {
  1814.             gcap.pDF->GetNumDropped(&gcap.lDroppedBase);
  1815.             gcap.pDF->GetNumNotDropped(&gcap.lNotBase);
  1816.             //DbgLog((LOG_TRACE,0,TEXT("Dropped counts are %ld and %ld"),
  1817.             //      gcap.lDroppedBase, gcap.lNotBase));
  1818.         } 
  1819.     }
  1820.     else {
  1821.         hr = pMC->Run();
  1822.         if(FAILED(hr)) {
  1823.             // stop parts that started
  1824.             pMC->Stop();
  1825.             pMC->Release();
  1826.             ErrMsg(TEXT("Error %x: Cannot run graph"), hr);
  1827.             return FALSE;
  1828.         }
  1829.     }
  1830.  
  1831.     pMC->Release();
  1832.  
  1833.     // when did we start capture?
  1834.     gcap.lCapStartTime = timeGetTime();
  1835.  
  1836.     // update status bar 30 times per second - #captured, #dropped
  1837.     SetTimer(ghwndApp, 1, 33, NULL);
  1838.  
  1839.     gcap.fCapturing = TRUE;
  1840.     return TRUE;
  1841. }
  1842.  
  1843.  
  1844. // stop the capture graph
  1845. //
  1846. BOOL StopCapture() 
  1847. {
  1848.     // way ahead of you
  1849.     if(!gcap.fCapturing) {
  1850.         return FALSE;
  1851.     }
  1852.  
  1853.     // stop the graph
  1854.     IMediaControl *pMC = NULL;
  1855.     HRESULT hr = gcap.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC);
  1856.     if(SUCCEEDED(hr)) {
  1857.         hr = pMC->Stop();
  1858.         pMC->Release();
  1859.     }
  1860.     if(FAILED(hr)) {
  1861.         ErrMsg(TEXT("Error %x: Cannot stop graph"), hr);
  1862.         return FALSE;
  1863.     }
  1864.  
  1865.     // when the graph was stopped
  1866.     gcap.lCapStopTime = timeGetTime();
  1867.  
  1868.     // no more status bar updates
  1869.     KillTimer(ghwndApp, 1);
  1870.  
  1871.     // one last time for the final count and all the stats
  1872.     UpdateStatus(TRUE);
  1873.  
  1874.     gcap.fCapturing = FALSE;
  1875.  
  1876.     // !!! get rid of menu garbage
  1877.     InvalidateRect(ghwndApp, NULL, TRUE);
  1878.  
  1879.     return TRUE;
  1880. }
  1881.  
  1882.  
  1883. // Let's talk about UI for a minute.  There are many programmatic interfaces
  1884. // you can use to program a capture filter or related filter to capture the
  1885. // way you want it to.... eg:  IAMStreamConfig, IAMVideoCompression,
  1886. // IAMCrossbar, IAMTVTuner, IAMTVAudio, IAMAnalogVideoDecoder, IAMCameraControl,
  1887. // IAMVideoProcAmp, etc.
  1888. //
  1889. // But you probably want some UI to let the user play with all these settings.
  1890. // For new WDM-style capture devices, we offer some default UI you can use.
  1891. // The code below shows how to bring up all of the dialog boxes supported 
  1892. // by any capture filters.
  1893. //
  1894. // The following code shows you how you can bring up all of the
  1895. // dialogs supported by a particular object at once on a big page with lots
  1896. // of thumb tabs.  You do this by starting with an interface on the object that
  1897. // you want, and using ISpecifyPropertyPages to get the whole list, and
  1898. // OleCreatePropertyFrame to bring them all up.  This way you will get custom
  1899. // property pages a filter has, too, that are not one of the standard pages that
  1900. // you know about.  There are at least 9 objects that may have property pages.
  1901. // Your app already has 2 of the object pointers, the video capture filter and
  1902. // the audio capture filter (let's call them pVCap and pACap)
  1903. // 1.  The video capture filter - pVCap
  1904. // 2.  The video capture filter's capture pin - get this by calling
  1905. //     FindInterface(&PIN_CATEGORY_CAPTURE, pVCap, IID_IPin, &pX);
  1906. // 3.  The video capture filter's preview pin - get this by calling
  1907. //     FindInterface(&PIN_CATEGORY_PREVIEW, pVCap, IID_IPin, &pX);
  1908. // 4.  The audio capture filter - pACap
  1909. // 5.  The audio capture filter's capture pin - get this by calling
  1910. //     FindInterface(&PIN_CATEGORY_CAPTURE, pACap, IID_IPin, &pX);
  1911. // 6.  The crossbar connected to the video capture filter - get this by calling
  1912. //     FindInterface(NULL, pVCap, IID_IAMCrossbar, &pX);
  1913. // 7.  There is a possible second crossbar to control audio - get this by 
  1914. //     looking upstream of the first crossbar like this:
  1915. //     FindInterface(&LOOK_UPSTREAM_ONLY, pX, IID_IAMCrossbar, &pX2);
  1916. // 8.  The TV Tuner connected to the video capture filter - get this by calling
  1917. //     FindInterface(NULL, pVCap, IID_IAMTVTuner, &pX);
  1918. // 9.  The TV Audio connected to the audio capture filter - get this by calling
  1919. //     FindInterface(NULL, pACap, IID_IAMTVAudio, &pX);
  1920. // 10. We have a helper class, CCrossbar, which makes the crossbar issue less
  1921. //     confusing.  In fact, although not supported here, there may be more than
  1922. //     two crossbars, arranged in many different ways.  An application may not
  1923. //     wish to have separate dialogs for each crossbar, but instead hide the
  1924. //     complexity and simply offer the user a list of inputs that can be chosen.
  1925. //     This list represents all the unique inputs from all the crossbars.
  1926. //     The crossbar helper class does this and offers that list as #10.  It is
  1927. //     expected that an application will either provide the crossbar dialogs
  1928. //     above (#6 and #7) OR provide the input list (this #10), but not both.
  1929. //     That would be confusing because if you select an input using dialog 6 or
  1930. //     7 the input list here in #10 won't know about your choice.
  1931. //
  1932. // Your last choice for UI is to make your own pages, and use the results of 
  1933. // your custom page to call the interfaces programmatically.
  1934.  
  1935.  
  1936. void MakeMenuOptions() 
  1937. {
  1938.     HRESULT hr;
  1939.     HMENU hMenuSub = GetSubMenu(GetMenu(ghwndApp), 2); // Options menu
  1940.  
  1941.     // remove any old choices from the last device
  1942.     RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
  1943.     RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
  1944.     RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
  1945.     RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
  1946.     RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
  1947.     RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
  1948.     RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
  1949.     RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
  1950.     RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
  1951.     RemoveMenu(hMenuSub, 4, MF_BYPOSITION);
  1952.  
  1953.     int zz = 0;
  1954.     gcap.iFormatDialogPos = -1;
  1955.     gcap.iSourceDialogPos = -1;
  1956.     gcap.iDisplayDialogPos = -1;
  1957.     gcap.iVCapDialogPos = -1;
  1958.     gcap.iVCrossbarDialogPos = -1;
  1959.     gcap.iTVTunerDialogPos = -1;
  1960.     gcap.iACapDialogPos = -1;
  1961.     gcap.iACrossbarDialogPos = -1;
  1962.     gcap.iTVAudioDialogPos = -1;
  1963.     gcap.iVCapCapturePinDialogPos = -1;
  1964.     gcap.iVCapPreviewPinDialogPos = -1;
  1965.     gcap.iACapCapturePinDialogPos = -1;
  1966.  
  1967.     // If this device supports the old legacy UI dialogs, offer them
  1968.  
  1969.     if(gcap.pDlg && !gcap.pDlg->HasDialog(VfwCaptureDialog_Format)) {
  1970.         AppendMenu(hMenuSub, MF_STRING, MENU_DIALOG0 + zz, TEXT("Video Format..."));
  1971.         gcap.iFormatDialogPos = zz++;
  1972.     }
  1973.     if(gcap.pDlg && !gcap.pDlg->HasDialog(VfwCaptureDialog_Source)) {
  1974.         AppendMenu(hMenuSub, MF_STRING, MENU_DIALOG0 + zz, TEXT("Video Source..."));
  1975.         gcap.iSourceDialogPos = zz++;
  1976.     }
  1977.     if(gcap.pDlg && !gcap.pDlg->HasDialog(VfwCaptureDialog_Display)) {
  1978.         AppendMenu(hMenuSub, MF_STRING, MENU_DIALOG0 + zz, TEXT("Video Display..."));
  1979.         gcap.iDisplayDialogPos = zz++;
  1980.     }
  1981.  
  1982.     // Also check the audio capture filter at this point, since even non wdm devices
  1983.     // may support an IAMAudioInputMixer property page (we'll also get any wdm filter
  1984.     // properties here as well). We'll get any audio capture pin property pages just
  1985.     // a bit later.
  1986.     if(gcap.pACap != NULL) {
  1987.         ISpecifyPropertyPages *pSpec;
  1988.         CAUUID cauuid;
  1989.  
  1990.         hr = gcap.pACap->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
  1991.         if(hr == S_OK) {
  1992.             hr = pSpec->GetPages(&cauuid);
  1993.             if(hr == S_OK && cauuid.cElems > 0) {
  1994.                 AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("Audio Capture Filter..."));
  1995.                 gcap.iACapDialogPos = zz++;
  1996.                 CoTaskMemFree(cauuid.pElems);
  1997.             }
  1998.             pSpec->Release();
  1999.         }
  2000.     }
  2001.  
  2002.     // don't bother looking for new property pages if the old ones are supported
  2003.     // or if we don't have a capture filter
  2004.     if(gcap.pVCap == NULL || gcap.iFormatDialogPos != -1)
  2005.         return;
  2006.  
  2007.     // New WDM devices support new UI and new interfaces.
  2008.     // Your app can use some default property
  2009.     // pages for UI if you'd like (like we do here) or if you don't like our
  2010.     // dialog boxes, feel free to make your own and programmatically set 
  2011.     // the capture options through interfaces like IAMCrossbar, IAMCameraControl
  2012.     // etc.
  2013.  
  2014.     // There are 9 objects that might support property pages.  Let's go through
  2015.     // them.
  2016.  
  2017.     ISpecifyPropertyPages *pSpec;
  2018.     CAUUID cauuid;
  2019.  
  2020.     // 1. the video capture filter itself
  2021.  
  2022.     hr = gcap.pVCap->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
  2023.     if(hr == S_OK) {
  2024.         hr = pSpec->GetPages(&cauuid);
  2025.         if(hr == S_OK && cauuid.cElems > 0) {
  2026.             AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("Video Capture Filter..."));
  2027.             gcap.iVCapDialogPos = zz++;
  2028.             CoTaskMemFree(cauuid.pElems);
  2029.         }
  2030.         pSpec->Release();
  2031.     }
  2032.  
  2033.     // 2.  The video capture capture pin
  2034.  
  2035.     IAMStreamConfig *pSC;
  2036.     hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  2037.         &MEDIATYPE_Interleaved,
  2038.         gcap.pVCap, IID_IAMStreamConfig, (void **)&pSC);
  2039.     if(hr != S_OK)
  2040.         hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  2041.             &MEDIATYPE_Video, gcap.pVCap,
  2042.             IID_IAMStreamConfig, (void **)&pSC);
  2043.  
  2044.     if(hr == S_OK) {
  2045.         hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
  2046.         if(hr == S_OK) {
  2047.             hr = pSpec->GetPages(&cauuid);
  2048.             if(hr == S_OK && cauuid.cElems > 0) {
  2049.                 AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("Video Capture Pin..."));
  2050.                 gcap.iVCapCapturePinDialogPos = zz++;
  2051.                 CoTaskMemFree(cauuid.pElems);
  2052.             }
  2053.             pSpec->Release();
  2054.         }
  2055.         pSC->Release();
  2056.     }
  2057.  
  2058.     // 3.  The video capture preview pin.
  2059.     // This basically sets the format being previewed.  Typically, you
  2060.     // want to capture and preview using the SAME format, instead of having to
  2061.     // enter the same value in 2 dialog boxes.  For a discussion on this, see
  2062.     // the comment above the MakePreviewGraph function.
  2063.  
  2064.     hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW,
  2065.         &MEDIATYPE_Interleaved, gcap.pVCap,
  2066.         IID_IAMStreamConfig, (void **)&pSC);
  2067.     if(hr != NOERROR)
  2068.         hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW,
  2069.             &MEDIATYPE_Video, gcap.pVCap,
  2070.             IID_IAMStreamConfig, (void **)&pSC);
  2071.     if(hr == S_OK) {
  2072.         hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
  2073.         if(hr == S_OK) {
  2074.             hr = pSpec->GetPages(&cauuid);
  2075.             if(hr == S_OK && cauuid.cElems > 0) {
  2076.                 AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz,TEXT("Video Preview Pin..."));
  2077.                 gcap.iVCapPreviewPinDialogPos = zz++;
  2078.                 CoTaskMemFree(cauuid.pElems);
  2079.             }
  2080.             pSpec->Release();
  2081.         }
  2082.         pSC->Release();
  2083.     }
  2084.  
  2085.     // 4 & 5.  The video crossbar, and a possible second crossbar
  2086.  
  2087.     IAMCrossbar *pX, *pX2;
  2088.     IBaseFilter *pXF;
  2089.     hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  2090.         &MEDIATYPE_Interleaved, gcap.pVCap,
  2091.         IID_IAMCrossbar, (void **)&pX);
  2092.     if(hr != S_OK)
  2093.         hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  2094.             &MEDIATYPE_Video, gcap.pVCap,
  2095.             IID_IAMCrossbar, (void **)&pX);
  2096.  
  2097.     if(hr == S_OK) {
  2098.         hr = pX->QueryInterface(IID_IBaseFilter, (void **)&pXF);
  2099.         if(hr == S_OK) {
  2100.             hr = pX->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
  2101.             if(hr == S_OK) {
  2102.                 hr = pSpec->GetPages(&cauuid);
  2103.                 if(hr == S_OK && cauuid.cElems > 0) {
  2104.                     AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz,
  2105.                         TEXT("Video Crossbar..."));
  2106.                     gcap.iVCrossbarDialogPos = zz++;
  2107.                     CoTaskMemFree(cauuid.pElems);
  2108.                 }
  2109.                 pSpec->Release();
  2110.             }
  2111.             hr = gcap.pBuilder->FindInterface(&LOOK_UPSTREAM_ONLY, NULL, pXF,
  2112.                 IID_IAMCrossbar, (void **)&pX2);
  2113.             if(hr == S_OK) {
  2114.                 hr = pX2->QueryInterface(IID_ISpecifyPropertyPages,
  2115.                     (void **)&pSpec);
  2116.                 if(hr == S_OK) {
  2117.                     hr = pSpec->GetPages(&cauuid);
  2118.                     if(hr == S_OK && cauuid.cElems > 0) {
  2119.                         AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz,
  2120.                             TEXT("Second Crossbar..."));
  2121.                         gcap.iACrossbarDialogPos = zz++;
  2122.                         CoTaskMemFree(cauuid.pElems);
  2123.                     }
  2124.                     pSpec->Release();
  2125.                 }
  2126.                 pX2->Release();
  2127.             }
  2128.             pXF->Release();
  2129.         }
  2130.         pX->Release();
  2131.     }
  2132.  
  2133.     // 6.  The TVTuner
  2134.  
  2135.     IAMTVTuner *pTV;
  2136.     hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  2137.         &MEDIATYPE_Interleaved, gcap.pVCap,
  2138.         IID_IAMTVTuner, (void **)&pTV);
  2139.     if(hr != S_OK)
  2140.         hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  2141.             &MEDIATYPE_Video, gcap.pVCap,
  2142.             IID_IAMTVTuner, (void **)&pTV);
  2143.     if(hr == S_OK) {
  2144.         hr = pTV->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
  2145.         if(hr == S_OK) {
  2146.             hr = pSpec->GetPages(&cauuid);
  2147.             if(hr == S_OK && cauuid.cElems > 0) {
  2148.                 AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("TV Tuner..."));
  2149.                 gcap.iTVTunerDialogPos = zz++;
  2150.                 CoTaskMemFree(cauuid.pElems);
  2151.             }
  2152.             pSpec->Release();
  2153.         }
  2154.         pTV->Release();
  2155.     }
  2156.  
  2157.     // no audio capture, we're done
  2158.     if(gcap.pACap == NULL)
  2159.         return;
  2160.  
  2161.     // 7.  The Audio capture filter itself... Thanks anyway, but we got these already
  2162.  
  2163.     // 8.  The Audio capture pin
  2164.  
  2165.     hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  2166.         &MEDIATYPE_Audio, gcap.pACap,
  2167.         IID_IAMStreamConfig, (void **)&pSC);
  2168.     if(hr == S_OK) {
  2169.         hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
  2170.         if(hr == S_OK) {
  2171.             hr = pSpec->GetPages(&cauuid);
  2172.             if(hr == S_OK && cauuid.cElems > 0) {
  2173.                 AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("Audio Capture Pin..."));
  2174.                 gcap.iACapCapturePinDialogPos = zz++;
  2175.                 CoTaskMemFree(cauuid.pElems);
  2176.             }
  2177.             pSpec->Release();
  2178.         }
  2179.         pSC->Release();
  2180.     }
  2181.  
  2182.     // 9.  The TV Audio filter
  2183.  
  2184.     IAMTVAudio *pTVA;
  2185.     hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, 
  2186.         &MEDIATYPE_Audio, gcap.pACap,
  2187.         IID_IAMTVAudio, (void **)&pTVA);
  2188.     if(hr == S_OK) {
  2189.         hr = pTVA->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec);
  2190.         if(hr == S_OK) {
  2191.             hr = pSpec->GetPages(&cauuid);
  2192.             if(hr == S_OK && cauuid.cElems > 0) {
  2193.                 AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("TV Audio..."));
  2194.                 gcap.iTVAudioDialogPos = zz++;
  2195.                 CoTaskMemFree(cauuid.pElems);
  2196.             }
  2197.             pSpec->Release();
  2198.         }
  2199.         pTVA->Release();
  2200.     }
  2201.  
  2202.     // 10.  Crossbar class helper menu item, to let you choose an input
  2203.  
  2204.     if(gcap.pCrossbar && gcap.NumberOfVideoInputs) {
  2205.         gcap.hMenuPopup = CreatePopupMenu();
  2206.         LONG j;
  2207.         LONG  PhysicalType;
  2208.         TCHAR buf[MAX_PATH];
  2209.         LONG InputToEnable = -1;
  2210.  
  2211.         gcap.iVideoInputMenuPos = zz++;
  2212.         AppendMenu(hMenuSub, MF_SEPARATOR, 0, NULL);
  2213.  
  2214.         for(j = 0; j < gcap.NumberOfVideoInputs; j++) {
  2215.             EXECUTE_ASSERT(S_OK == gcap.pCrossbar->GetInputType(j, &PhysicalType));
  2216.             EXECUTE_ASSERT(S_OK == gcap.pCrossbar->GetInputName(j, buf, sizeof (buf)));
  2217.             AppendMenu(gcap.hMenuPopup,MF_STRING,MENU_DIALOG0+zz, buf);
  2218.             zz++;
  2219.  
  2220.             // Route the first TVTuner by default
  2221.             if((PhysicalType == PhysConn_Video_Tuner) && InputToEnable == -1) {
  2222.                 InputToEnable = j;
  2223.             }
  2224.         }
  2225.  
  2226.         AppendMenu(hMenuSub, MF_STRING | MF_POPUP, (UINT_PTR)gcap.hMenuPopup, TEXT("Video Input"));
  2227.  
  2228.         if(InputToEnable == -1) {
  2229.             InputToEnable = 0;
  2230.         }
  2231.         CheckMenuItem(gcap.hMenuPopup, InputToEnable, MF_BYPOSITION | MF_CHECKED); 
  2232.  
  2233.         gcap.pCrossbar->SetInputIndex(InputToEnable);
  2234.     }
  2235.     // !!! anything needed to delete the popup when selecting a new input?
  2236. }
  2237.  
  2238.  
  2239. // how many captured/dropped so far
  2240. //
  2241. void UpdateStatus(BOOL fAllStats) 
  2242. {
  2243.     HRESULT hr;
  2244.     LONG lDropped, lNot=0, lAvgFrameSize;
  2245.     TCHAR tach[160];
  2246.  
  2247.     // we use this interface to get the number of captured and dropped frames
  2248.     // NOTE:  We cannot query for this interface earlier, as it may not be
  2249.     // available until the pin is connected
  2250.     if(!gcap.pDF) {
  2251.         hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  2252.             &MEDIATYPE_Interleaved, gcap.pVCap,
  2253.             IID_IAMDroppedFrames, (void **)&gcap.pDF);
  2254.         if(hr != S_OK)
  2255.             hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  2256.                 &MEDIATYPE_Video, gcap.pVCap,
  2257.                 IID_IAMDroppedFrames, (void **)&gcap.pDF);
  2258.     }
  2259.  
  2260.     // this filter can't tell us dropped frame info.
  2261.     if(!gcap.pDF) {
  2262.         statusUpdateStatus(ghwndStatus,
  2263.             TEXT("Filter cannot report capture information"));
  2264.         return;
  2265.     }
  2266.  
  2267.     hr = gcap.pDF->GetNumDropped(&lDropped);
  2268.     if(hr == S_OK)
  2269.         hr = gcap.pDF->GetNumNotDropped(&lNot);
  2270.     if(hr != S_OK)
  2271.         return;
  2272.  
  2273.     lDropped -= gcap.lDroppedBase;
  2274.     lNot -= gcap.lNotBase;
  2275.  
  2276.     if(!fAllStats) {
  2277.         LONG lTime = timeGetTime() - gcap.lCapStartTime;
  2278.         wsprintf(tach, TEXT("Captured %d frames (%d dropped) %d.%dsec"), lNot,
  2279.             lDropped, lTime / 1000, 
  2280.             lTime / 100 - lTime / 1000 * 10);
  2281.         statusUpdateStatus(ghwndStatus, tach);
  2282.         return;
  2283.     }
  2284.  
  2285.     // we want all possible stats, including capture time and actual acheived
  2286.     // frame rate and data rate (as opposed to what we tried to get).  These
  2287.     // numbers are an indication that though we dropped frames just now, if we
  2288.     // chose a data rate and frame rate equal to the numbers I'm about to
  2289.     // print, we probably wouldn't drop any frames.
  2290.  
  2291.     // average size of frame captured
  2292.     hr = gcap.pDF->GetAverageFrameSize(&lAvgFrameSize);
  2293.     if(hr != S_OK)
  2294.         return;
  2295.  
  2296.     // how long capture lasted
  2297.     LONG lDurMS = gcap.lCapStopTime - gcap.lCapStartTime;
  2298.     double flFrame;     // acheived frame rate
  2299.     LONG lData;         // acheived data rate
  2300.  
  2301.     if(lDurMS > 0) {
  2302.         flFrame = (double)(LONGLONG)lNot * 1000. /
  2303.             (double)(LONGLONG)lDurMS;
  2304.         lData = (LONG)(LONGLONG)(lNot / (double)(LONGLONG)lDurMS *
  2305.             1000. * (double)(LONGLONG)lAvgFrameSize);
  2306.     }
  2307.     else {
  2308.         flFrame = 0.;
  2309.         lData = 0;
  2310.     }
  2311.  
  2312.     wsprintf(tach, TEXT("Captured %d frames in %d.%d sec (%d dropped): %d.%d fps %d.%d Meg/sec"),
  2313.         lNot, lDurMS / 1000, lDurMS / 100 - lDurMS / 1000 * 10,
  2314.         lDropped, (int)flFrame,
  2315.         (int)(flFrame * 10.) - (int)flFrame * 10,
  2316.         lData / 1000000,
  2317.         lData / 1000 - (lData / 1000000 * 1000));
  2318.     statusUpdateStatus(ghwndStatus, tach);
  2319. }
  2320.  
  2321.  
  2322. // Check the devices we're currently using and make filters for them
  2323. //
  2324. void ChooseDevices(IMoniker *pmVideo, IMoniker *pmAudio) 
  2325. {
  2326.     USES_CONVERSION;
  2327. #define VERSIZE 40
  2328. #define DESCSIZE 80
  2329.     int versize = VERSIZE;
  2330.     int descsize = DESCSIZE;
  2331.     WCHAR wachVer[VERSIZE]={0}, wachDesc[DESCSIZE]={0};
  2332.     TCHAR tachStatus[VERSIZE + DESCSIZE + 5]={0};
  2333.  
  2334.  
  2335.     // they chose a new device. rebuild the graphs
  2336.     if(gcap.pmVideo != pmVideo || gcap.pmAudio != pmAudio) {
  2337.         if(pmVideo) {
  2338.             pmVideo->AddRef();
  2339.         }
  2340.         if(pmAudio) {
  2341.             pmAudio->AddRef();
  2342.         }
  2343.         IMonRelease(gcap.pmVideo);
  2344.         IMonRelease(gcap.pmAudio);
  2345.         gcap.pmVideo = pmVideo;
  2346.         gcap.pmAudio = pmAudio;
  2347.         if(gcap.fPreviewing)
  2348.             StopPreview();
  2349.         if(gcap.fCaptureGraphBuilt || gcap.fPreviewGraphBuilt)
  2350.             TearDownGraph();
  2351.         FreeCapFilters();
  2352.         InitCapFilters();
  2353.         if(gcap.fWantPreview) { // were we previewing?
  2354.             BuildPreviewGraph();
  2355.             StartPreview();
  2356.         }
  2357.         MakeMenuOptions();  // the UI choices change per device
  2358.     }
  2359.  
  2360.     // Set the check marks for the devices menu.
  2361.     int i;
  2362.     for(i = 0; i < NUMELMS(gcap.rgpmVideoMenu); i++) {
  2363.         if(gcap.rgpmVideoMenu[i] == NULL)
  2364.             break;
  2365.         CheckMenuItem(GetMenu(ghwndApp), 
  2366.             MENU_VDEVICE0 + i, 
  2367.             (S_OK == gcap.rgpmVideoMenu[i]->IsEqual(gcap.pmVideo)) ? MF_CHECKED : MF_UNCHECKED); 
  2368.     }
  2369.  
  2370.     for(i = 0; i < NUMELMS(gcap.rgpmAudioMenu); i++) {
  2371.         if(gcap.rgpmAudioMenu[i] == NULL)
  2372.             break;
  2373.         CheckMenuItem(GetMenu(ghwndApp), 
  2374.             MENU_ADEVICE0 + i, 
  2375.             (S_OK == gcap.rgpmAudioMenu[i]->IsEqual(gcap.pmAudio)) ? MF_CHECKED : MF_UNCHECKED); 
  2376.     }
  2377.  
  2378.     // Put the video driver name in the status bar - if the filter supports
  2379.     // IAMVideoCompression::GetInfo, that's the best way to get the name and
  2380.     // the version.  Otherwise use the name we got from device enumeration
  2381.     // as a fallback.
  2382.     if(gcap.pVC) {
  2383.         HRESULT hr = gcap.pVC->GetInfo(wachVer, &versize, wachDesc, &descsize,
  2384.             NULL, NULL, NULL, NULL);
  2385.         if(hr == S_OK) {
  2386.             // It's possible that the call succeeded without actually filling
  2387.             // in information for description and version.  If these strings
  2388.             // are empty, just display the device's friendly name.
  2389.             if(wcslen(wachDesc) && wcslen(wachVer)) {
  2390.                 wsprintf(tachStatus, TEXT("%s - %s"), W2T(wachDesc), W2T(wachVer));
  2391.                 statusUpdateStatus(ghwndStatus, tachStatus);
  2392.                 return;
  2393.             }
  2394.         }
  2395.     }
  2396.  
  2397.     // Since the GetInfo method failed (or the interface did not exist),
  2398.     // display the device's friendly name.
  2399.     statusUpdateStatus(ghwndStatus, W2T(gcap.wachFriendlyName));
  2400. }
  2401.  
  2402. void ChooseDevices(TCHAR *szVideo, TCHAR *szAudio) 
  2403. {
  2404.     WCHAR wszVideo[1024],  wszAudio[1024];
  2405.  
  2406. #ifndef UNICODE
  2407.     MultiByteToWideChar(CP_ACP, 0, szVideo, -1, wszVideo, NUMELMS(wszVideo));
  2408.     MultiByteToWideChar(CP_ACP, 0, szAudio, -1, wszAudio, NUMELMS(wszAudio));
  2409. #else
  2410.     wcscpy(wszVideo, T2W(szVideo));
  2411.     wcscpy(wszAudio, T2W(szAudio));
  2412. #endif
  2413.  
  2414.     IBindCtx *lpBC;
  2415.     HRESULT hr = CreateBindCtx(0, &lpBC);
  2416.     IMoniker *pmVideo = 0, *pmAudio = 0;
  2417.     if(SUCCEEDED(hr)) {
  2418.         DWORD dwEaten;
  2419.         hr = MkParseDisplayName(lpBC, wszVideo, &dwEaten,
  2420.             &pmVideo);
  2421.         hr = MkParseDisplayName(lpBC, wszAudio, &dwEaten,
  2422.             &pmAudio);
  2423.  
  2424.         lpBC->Release();
  2425.     }
  2426.  
  2427.     // Handle the case where the video capture device used for the previous session
  2428.     // is not available now.
  2429.     BOOL bFound = FALSE;
  2430.     if(pmVideo != NULL) {
  2431.         for(int i = 0; i < NUMELMS(gcap.rgpmVideoMenu); i++) {
  2432.             if(gcap.rgpmVideoMenu[i] != NULL &&
  2433.                 S_OK == gcap.rgpmVideoMenu[i]->IsEqual(pmVideo)) {
  2434.                 bFound = TRUE;
  2435.                 break;
  2436.             }
  2437.         }
  2438.     }
  2439.  
  2440.     if(!bFound) {
  2441.         if(gcap.iNumVCapDevices > 0) {
  2442.             IMonRelease(pmVideo);
  2443.             ASSERT(gcap.rgpmVideoMenu[0] != NULL);
  2444.             pmVideo = gcap.rgpmVideoMenu[0];
  2445.             pmVideo->AddRef();
  2446.         }
  2447.         else
  2448.             goto CleanUp;
  2449.     }
  2450.  
  2451.     ChooseDevices(pmVideo, pmAudio);
  2452.  
  2453. CleanUp:
  2454.     IMonRelease(pmVideo);
  2455.     IMonRelease(pmAudio);
  2456. }
  2457.  
  2458.  
  2459. // put all installed video and audio devices in the menus
  2460. //
  2461. void AddDevicesToMenu() 
  2462. {
  2463.     USES_CONVERSION;
  2464.  
  2465.     if(gcap.fDeviceMenuPopulated) {
  2466.         return;
  2467.     }
  2468.     gcap.fDeviceMenuPopulated = true;
  2469.     gcap.iNumVCapDevices = 0;
  2470.  
  2471.     UINT    uIndex = 0;
  2472.     HMENU   hMenuSub;
  2473.     HRESULT hr;
  2474.     BOOL bCheck = FALSE;
  2475.  
  2476.     hMenuSub = GetSubMenu(GetMenu(ghwndApp), 1);        // Devices menu
  2477.  
  2478.     // Clean the sub menu
  2479.     int iMenuItems = GetMenuItemCount(hMenuSub);
  2480.     if(iMenuItems == -1) {
  2481.         ErrMsg(TEXT("Error Cleaning Devices Menu"));
  2482.         return;
  2483.     }
  2484.     else if(iMenuItems > 0) {
  2485.         for(int i = 0; i < iMenuItems; i++) {
  2486.             RemoveMenu(hMenuSub, 0, MF_BYPOSITION);
  2487.         }
  2488.  
  2489.     } {
  2490.         for(int i = 0; i < NUMELMS(gcap.rgpmVideoMenu); i++) {
  2491.             IMonRelease(gcap.rgpmVideoMenu[i]);
  2492.         }
  2493.         for(i = 0; i < NUMELMS(gcap.rgpmAudioMenu); i++) {
  2494.             IMonRelease(gcap.rgpmAudioMenu[i]);
  2495.         }
  2496.     }
  2497.  
  2498.  
  2499.     // enumerate all video capture devices
  2500.     ICreateDevEnum *pCreateDevEnum;
  2501.     hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
  2502.         IID_ICreateDevEnum, (void**)&pCreateDevEnum);
  2503.     if(hr != NOERROR) {
  2504.         ErrMsg(TEXT("Error Creating Device Enumerator"));
  2505.         return;
  2506.     }
  2507.  
  2508.     IEnumMoniker *pEm;
  2509.     hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,
  2510.         &pEm, 0);
  2511.     if(hr != NOERROR) {
  2512.         ErrMsg(TEXT("Sorry, you have no video capture hardware"));
  2513.         goto EnumAudio;
  2514.     }
  2515.     pEm->Reset();
  2516.     ULONG cFetched;
  2517.     IMoniker *pM;
  2518.     while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK) {
  2519.         IPropertyBag *pBag;
  2520.         hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);
  2521.         if(SUCCEEDED(hr)) {
  2522.             VARIANT var;
  2523.             var.vt = VT_BSTR;
  2524.             hr = pBag->Read(L"FriendlyName", &var, NULL);
  2525.             if(hr == NOERROR) {
  2526.                 AppendMenu(hMenuSub, MF_STRING, MENU_VDEVICE0 + uIndex,
  2527.                     W2T(var.bstrVal));
  2528.  
  2529.                 if(gcap.pmVideo != 0 && (S_OK == gcap.pmVideo->IsEqual(pM)))
  2530.                     bCheck = TRUE;
  2531.                 CheckMenuItem(hMenuSub, 
  2532.                     MENU_VDEVICE0 + uIndex, 
  2533.                     (bCheck ? MF_CHECKED : MF_UNCHECKED));
  2534.                 bCheck = FALSE;
  2535.                 EnableMenuItem(hMenuSub,
  2536.                     MENU_VDEVICE0 + uIndex,
  2537.                     (gcap.fCapturing ? MF_DISABLED : MF_ENABLED));
  2538.  
  2539.  
  2540.                 SysFreeString(var.bstrVal);
  2541.  
  2542.                 ASSERT(gcap.rgpmVideoMenu[uIndex] == 0);
  2543.                 gcap.rgpmVideoMenu[uIndex] = pM;
  2544.                 pM->AddRef();
  2545.             }
  2546.             pBag->Release();
  2547.         }
  2548.         pM->Release();
  2549.         uIndex++;
  2550.     }
  2551.     pEm->Release();
  2552.  
  2553.     gcap.iNumVCapDevices = uIndex;
  2554.  
  2555.     // separate the video and audio devices
  2556.     AppendMenu(hMenuSub, MF_SEPARATOR, 0, NULL);
  2557.  
  2558. EnumAudio:
  2559.  
  2560.     // enumerate all audio capture devices
  2561.     uIndex = 0;
  2562.     bCheck = FALSE;
  2563.  
  2564.     ASSERT(pCreateDevEnum != NULL);
  2565.  
  2566.     hr = pCreateDevEnum->CreateClassEnumerator(CLSID_AudioInputDeviceCategory,
  2567.         &pEm, 0);
  2568.     pCreateDevEnum->Release();
  2569.     if(hr != NOERROR)
  2570.         return;
  2571.     pEm->Reset();
  2572.     while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK) {
  2573.         IPropertyBag *pBag;
  2574.         hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);
  2575.         if(SUCCEEDED(hr)) {
  2576.             VARIANT var;
  2577.             var.vt = VT_BSTR;
  2578.             hr = pBag->Read(L"FriendlyName", &var, NULL);
  2579.             if(hr == NOERROR) {
  2580.                 AppendMenu(hMenuSub, MF_STRING, MENU_ADEVICE0 + uIndex,
  2581.                     W2T(var.bstrVal));
  2582.  
  2583.                 if(gcap.pmAudio != 0 && (S_OK == gcap.pmAudio->IsEqual(pM)))
  2584.                     bCheck = TRUE;
  2585.                 CheckMenuItem(hMenuSub, 
  2586.                     MENU_ADEVICE0 + uIndex, 
  2587.                     (bCheck ? MF_CHECKED : MF_UNCHECKED));
  2588.                 bCheck = FALSE;
  2589.                 EnableMenuItem(hMenuSub,
  2590.                     MENU_ADEVICE0 + uIndex,
  2591.                     (gcap.fCapturing ? MF_DISABLED : MF_ENABLED));
  2592.  
  2593.                 SysFreeString(var.bstrVal);
  2594.  
  2595.                 ASSERT(gcap.rgpmAudioMenu[uIndex] == 0);
  2596.                 gcap.rgpmAudioMenu[uIndex] = pM;
  2597.                 pM->AddRef();                
  2598.             }
  2599.             pBag->Release();
  2600.         }
  2601.         pM->Release();
  2602.         uIndex++;
  2603.     }
  2604.     pEm->Release();
  2605. }
  2606.  
  2607.  
  2608.  
  2609. // let them pick a frame rate
  2610. //
  2611. void ChooseFrameRate() 
  2612. {
  2613.     double rate = gcap.FrameRate;
  2614.  
  2615.     DoDialog(ghwndApp, IDD_FrameRateDialog, (DLGPROC)FrameRateProc, 0);
  2616.  
  2617.     HRESULT hr = E_FAIL;
  2618.  
  2619.     // If somebody unchecks "use frame rate" it means we will no longer
  2620.     // tell the filter what frame rate to use... it will either continue
  2621.     // using the last one, or use some default, or if you bring up a dialog
  2622.     // box that has frame rate choices, it will obey them.
  2623.  
  2624.     // new frame rate?
  2625.     if(gcap.fUseFrameRate && gcap.FrameRate != rate) {
  2626.         if(gcap.fPreviewing)
  2627.             StopPreview();
  2628.         // now tell it what frame rate to capture at.  Just find the format it
  2629.         // is capturing with, and leave everything else alone
  2630.         if(gcap.pVSC) {
  2631.             AM_MEDIA_TYPE *pmt;
  2632.             hr = gcap.pVSC->GetFormat(&pmt);
  2633.             // DV capture does not use a VIDEOINFOHEADER
  2634.             if(hr == NOERROR) {
  2635.                 if(pmt->formattype == FORMAT_VideoInfo) {
  2636.                     VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)pmt->pbFormat;
  2637.                     pvi->AvgTimePerFrame =(LONGLONG)(10000000 / gcap.FrameRate);
  2638.                     hr = gcap.pVSC->SetFormat(pmt);
  2639.                     if(hr != S_OK)
  2640.                         ErrMsg(TEXT("%x: Cannot set new frame rate"), hr);
  2641.                 }
  2642.                 DeleteMediaType(pmt);
  2643.             }
  2644.         }
  2645.         if(hr != NOERROR)
  2646.             ErrMsg(TEXT("Cannot set frame rate for capture"));
  2647.         if(gcap.fWantPreview)  // we were previewing
  2648.             StartPreview();
  2649.     }
  2650. }
  2651.  
  2652.  
  2653. // let them set a capture time limit
  2654. //
  2655. void ChooseTimeLimit() 
  2656. {
  2657.     DoDialog(ghwndApp, IDD_TimeLimitDialog, (DLGPROC)TimeLimitProc, 0);
  2658. }
  2659.  
  2660.  
  2661. // choose an audio capture format using ACM
  2662. //
  2663. void ChooseAudioFormat() 
  2664. {
  2665.     ACMFORMATCHOOSE cfmt;
  2666.     DWORD dwSize;
  2667.     LPWAVEFORMATEX lpwfx;
  2668.     AM_MEDIA_TYPE *pmt;
  2669.  
  2670.     // there's no point if we can't set a new format
  2671.     if(gcap.pASC == NULL)
  2672.         return;
  2673.  
  2674.     // What's the largest format size around?
  2675.     acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &dwSize);
  2676.     HRESULT hr = gcap.pASC->GetFormat(&pmt);
  2677.     if(hr != NOERROR)
  2678.         return;
  2679.     lpwfx = (LPWAVEFORMATEX)pmt->pbFormat;
  2680.     dwSize = max(dwSize, lpwfx->cbSize + sizeof(WAVEFORMATEX));
  2681.  
  2682.     // !!! This doesn't really map to the supported formats of the filter.
  2683.     // We should be using a property page based on IAMStreamConfig
  2684.  
  2685.     // Put up a dialog box initialized with the current format
  2686.     lpwfx = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, dwSize);
  2687.     if(lpwfx) {
  2688.         CopyMemory(lpwfx, pmt->pbFormat, pmt->cbFormat);
  2689.         _fmemset(&cfmt, 0, sizeof(ACMFORMATCHOOSE));
  2690.         cfmt.cbStruct = sizeof(ACMFORMATCHOOSE);
  2691.         cfmt.fdwStyle = ACMFORMATCHOOSE_STYLEF_INITTOWFXSTRUCT;
  2692.         // show only formats we can capture
  2693.         cfmt.fdwEnum = ACM_FORMATENUMF_HARDWARE | ACM_FORMATENUMF_INPUT;
  2694.         cfmt.hwndOwner = ghwndApp;
  2695.         cfmt.pwfx = lpwfx;
  2696.         cfmt.cbwfx = dwSize;
  2697.  
  2698.         // we chose a new format... so give it to the capture filter
  2699.         if(!acmFormatChoose(&cfmt)) {
  2700.             if(gcap.fPreviewing)
  2701.                 StopPreview();  // can't call IAMStreamConfig::SetFormat
  2702.  
  2703.             // while streaming
  2704.             ((CMediaType *)pmt)->SetFormat((LPBYTE)lpwfx,
  2705.                 lpwfx->cbSize + sizeof(WAVEFORMATEX));
  2706.             gcap.pASC->SetFormat(pmt);  // filter will reconnect
  2707.             if(gcap.fWantPreview)
  2708.                 StartPreview();
  2709.         }
  2710.         GlobalFreePtr(lpwfx) ;
  2711.     }
  2712.     DeleteMediaType(pmt);
  2713. }
  2714.  
  2715.  
  2716. /*----------------------------------------------------------------------------*\
  2717. |    AppCommand()
  2718. |
  2719. |    Process all of our WM_COMMAND messages.
  2720. \*----------------------------------------------------------------------------*/
  2721. LONG PASCAL AppCommand(HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam) 
  2722. {
  2723.     HRESULT hr;
  2724.     int id = GET_WM_COMMAND_ID(wParam, lParam);
  2725.     switch(id) {
  2726.         //
  2727.         // Our about box
  2728.         //
  2729.         case MENU_ABOUT:
  2730.             DialogBox(ghInstApp, MAKEINTRESOURCE(IDD_ABOUT), hwnd, 
  2731.                 (DLGPROC)AboutDlgProc);
  2732.             break;
  2733.  
  2734.         //
  2735.         // We want out of here!
  2736.         //
  2737.         case MENU_EXIT:
  2738.             PostMessage(hwnd,WM_CLOSE,0,0L);
  2739.             break;
  2740.  
  2741.         // choose a capture file
  2742.         //
  2743.         case MENU_SET_CAP_FILE:
  2744.             SetCaptureFile(hwnd);
  2745.             break;
  2746.  
  2747.         // pre-allocate the capture file
  2748.         //
  2749.         case MENU_ALLOC_CAP_FILE:
  2750.             AllocCaptureFile(hwnd);
  2751.             break;
  2752.  
  2753.         // save the capture file
  2754.         //
  2755.         case MENU_SAVE_CAP_FILE:
  2756.             SaveCaptureFile(hwnd);
  2757.             break;
  2758.  
  2759.         // start capturing
  2760.         //
  2761.         case MENU_START_CAP:
  2762.             if(gcap.fPreviewing)
  2763.                 StopPreview();
  2764.             if(gcap.fPreviewGraphBuilt)
  2765.                 TearDownGraph();
  2766.             BuildCaptureGraph();
  2767.             StartCapture();
  2768.             break;
  2769.  
  2770.         // toggle preview
  2771.         // 
  2772.         case MENU_PREVIEW:
  2773.             gcap.fWantPreview = !gcap.fWantPreview;
  2774.             if(gcap.fWantPreview) {
  2775.                 BuildPreviewGraph();
  2776.                 StartPreview();
  2777.             }
  2778.             else
  2779.                 StopPreview();
  2780.             break;
  2781.  
  2782.         // stop capture
  2783.         //
  2784.         case MENU_STOP_CAP:
  2785.             StopCapture();
  2786.             if(gcap.fWantPreview) {
  2787.                     BuildPreviewGraph();
  2788.                     StartPreview();
  2789.             }
  2790.             break;
  2791.  
  2792.         // select the master stream
  2793.         //
  2794.         case MENU_NOMASTER:
  2795.             gcap.iMasterStream = -1;
  2796.             if(gcap.pConfigAviMux) {
  2797.                     hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream);
  2798.                     if(hr != NOERROR)
  2799.                         ErrMsg(TEXT("SetMasterStream failed!"));
  2800.             }
  2801.             break;
  2802.  
  2803.         case MENU_AUDIOMASTER:
  2804.             gcap.iMasterStream = 1;
  2805.             if(gcap.pConfigAviMux) {
  2806.                     hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream);
  2807.                     if(hr != NOERROR)
  2808.                         ErrMsg(TEXT("SetMasterStream failed!"));
  2809.             }
  2810.             break;
  2811.  
  2812.         case MENU_VIDEOMASTER:
  2813.             gcap.iMasterStream = 0;
  2814.             if(gcap.pConfigAviMux) {
  2815.                     hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream);
  2816.                     if(hr != NOERROR)
  2817.                         ErrMsg(TEXT("SetMasterStream failed!"));
  2818.             }
  2819.             break;
  2820.  
  2821.         // toggle capturing audio
  2822.         case MENU_CAP_AUDIO:
  2823.             if(gcap.fPreviewing)
  2824.                 StopPreview();
  2825.             gcap.fCapAudio = !gcap.fCapAudio;
  2826.             // when we capture we'll need a different graph now
  2827.             if(gcap.fCaptureGraphBuilt || gcap.fPreviewGraphBuilt)
  2828.                 TearDownGraph();
  2829.             if(gcap.fWantPreview) {
  2830.                     BuildPreviewGraph();
  2831.                     StartPreview();
  2832.             }
  2833.             break;
  2834.  
  2835.         // toggle closed captioning
  2836.         case MENU_CAP_CC:
  2837.             if(gcap.fPreviewing)
  2838.                 StopPreview();
  2839.             gcap.fCapCC = !gcap.fCapCC;
  2840.             // when we capture we'll need a different graph now
  2841.             if(gcap.fCaptureGraphBuilt || gcap.fPreviewGraphBuilt)
  2842.                 TearDownGraph();
  2843.             if(gcap.fWantPreview) {
  2844.                     BuildPreviewGraph();
  2845.                     StartPreview();
  2846.             }
  2847.             break;
  2848.  
  2849.         // choose the audio capture format
  2850.         //
  2851.         case MENU_AUDIOFORMAT:
  2852.             ChooseAudioFormat();
  2853.             break;
  2854.  
  2855.         // pick a frame rate
  2856.         //
  2857.         case MENU_FRAMERATE:
  2858.             ChooseFrameRate();
  2859.             break;
  2860.  
  2861.         // pick a time limit
  2862.         //
  2863.         case MENU_TIMELIMIT:
  2864.             ChooseTimeLimit();
  2865.             break;
  2866.  
  2867.         // pick which video capture device to use
  2868.         // pick which video capture device to use
  2869.         //
  2870.         case MENU_VDEVICE0:
  2871.         case MENU_VDEVICE1:
  2872.         case MENU_VDEVICE2:
  2873.         case MENU_VDEVICE3:
  2874.         case MENU_VDEVICE4:
  2875.         case MENU_VDEVICE5:
  2876.         case MENU_VDEVICE6:
  2877.         case MENU_VDEVICE7:
  2878.         case MENU_VDEVICE8:
  2879.         case MENU_VDEVICE9:
  2880.             ChooseDevices(gcap.rgpmVideoMenu[id - MENU_VDEVICE0], gcap.pmAudio);
  2881.             break;
  2882.  
  2883.         // pick which audio capture device to use
  2884.         //
  2885.         case MENU_ADEVICE0:
  2886.         case MENU_ADEVICE1:
  2887.         case MENU_ADEVICE2:
  2888.         case MENU_ADEVICE3:
  2889.         case MENU_ADEVICE4:
  2890.         case MENU_ADEVICE5:
  2891.         case MENU_ADEVICE6:
  2892.         case MENU_ADEVICE7:
  2893.         case MENU_ADEVICE8:
  2894.         case MENU_ADEVICE9:
  2895.             ChooseDevices(gcap.pmVideo, gcap.rgpmAudioMenu[id - MENU_ADEVICE0]);
  2896.             break;
  2897.  
  2898.         // video format dialog
  2899.         //
  2900.         case MENU_DIALOG0:
  2901.         case MENU_DIALOG1:
  2902.         case MENU_DIALOG2:
  2903.         case MENU_DIALOG3:
  2904.         case MENU_DIALOG4:
  2905.         case MENU_DIALOG5:
  2906.         case MENU_DIALOG6:
  2907.         case MENU_DIALOG7:
  2908.         case MENU_DIALOG8:
  2909.         case MENU_DIALOG9:
  2910.         case MENU_DIALOGA:
  2911.         case MENU_DIALOGB:
  2912.         case MENU_DIALOGC:
  2913.         case MENU_DIALOGD:
  2914.         case MENU_DIALOGE:
  2915.         case MENU_DIALOGF:
  2916.  
  2917.             // they want the VfW format dialog
  2918.             if(id - MENU_DIALOG0 == gcap.iFormatDialogPos) {
  2919.                     // this dialog will not work while previewing
  2920.                     if(gcap.fWantPreview)
  2921.                         StopPreview();
  2922.                     HRESULT hrD;
  2923.                     hrD = gcap.pDlg->ShowDialog(VfwCaptureDialog_Format, ghwndApp);
  2924.                     // Oh uh!  Sometimes bringing up the FORMAT dialog can result
  2925.                     // in changing to a capture format that the current graph 
  2926.                     // can't handle.  It looks like that has happened and we'll
  2927.                     // have to rebuild the graph.
  2928.                     if(hrD == VFW_E_CANNOT_CONNECT) {
  2929.                         DbgLog((LOG_TRACE,1,TEXT("DIALOG CORRUPTED GRAPH!")));
  2930.                         TearDownGraph();    // now we need to rebuild
  2931.                         // !!! This won't work if we've left a stranded h/w codec
  2932.                     }
  2933.  
  2934.                     // Resize our window to be the same size that we're capturing
  2935.                     if(gcap.pVSC) {
  2936.                             AM_MEDIA_TYPE *pmt;
  2937.                             // get format being used NOW
  2938.                             hr = gcap.pVSC->GetFormat(&pmt);
  2939.                             // DV capture does not use a VIDEOINFOHEADER
  2940.                             if(hr == NOERROR) {
  2941.                                 if(pmt->formattype == FORMAT_VideoInfo) {
  2942.                                     // resize our window to the new capture size
  2943.                                     ResizeWindow(HEADER(pmt->pbFormat)->biWidth,
  2944.                                         abs(HEADER(pmt->pbFormat)->biHeight));
  2945.                                 }
  2946.                                 DeleteMediaType(pmt);
  2947.                             }
  2948.                     }
  2949.  
  2950.                     if(gcap.fWantPreview) {
  2951.                             BuildPreviewGraph();
  2952.                             StartPreview();
  2953.                     }
  2954.             }
  2955.             else if(id - MENU_DIALOG0 == gcap.iSourceDialogPos) {
  2956.                     // this dialog will not work while previewing
  2957.                     if(gcap.fWantPreview)
  2958.                         StopPreview();
  2959.                     gcap.pDlg->ShowDialog(VfwCaptureDialog_Source, ghwndApp);
  2960.                     if(gcap.fWantPreview)
  2961.                         StartPreview();
  2962.             }
  2963.             else if(id - MENU_DIALOG0 == gcap.iDisplayDialogPos) {
  2964.                     // this dialog will not work while previewing
  2965.                     if(gcap.fWantPreview)
  2966.                         StopPreview();
  2967.                     gcap.pDlg->ShowDialog(VfwCaptureDialog_Display, ghwndApp);
  2968.                     if(gcap.fWantPreview)
  2969.                         StartPreview();
  2970.  
  2971.                     // now the code for the new dialogs
  2972.             }
  2973.             else if(id - MENU_DIALOG0 == gcap.iVCapDialogPos) {
  2974.                     ISpecifyPropertyPages *pSpec;
  2975.                     CAUUID cauuid;
  2976.                     hr = gcap.pVCap->QueryInterface(IID_ISpecifyPropertyPages,
  2977.                         (void **)&pSpec);
  2978.                     if(hr == S_OK) {
  2979.                         hr = pSpec->GetPages(&cauuid);
  2980.                         hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
  2981.                             (IUnknown **)&gcap.pVCap, cauuid.cElems,
  2982.                             (GUID *)cauuid.pElems, 0, 0, NULL);
  2983.                         CoTaskMemFree(cauuid.pElems);
  2984.                         pSpec->Release();
  2985.                     }
  2986.             }
  2987.             else if(id - MENU_DIALOG0 == gcap.iVCapCapturePinDialogPos) {
  2988.                     // You can change this pin's output format in these dialogs.
  2989.                     // If the capture pin is already connected to somebody who's 
  2990.                     // fussy about the connection type, that may prevent using 
  2991.                     // this dialog(!) because the filter it's connected to might not
  2992.                     // allow reconnecting to a new format. (EG: you switch from RGB
  2993.                     // to some compressed type, and need to pull in a decoder)
  2994.                     // I need to tear down the graph downstream of the
  2995.                     // capture filter before bringing up these dialogs.
  2996.                     // In any case, the graph must be STOPPED when calling them.
  2997.                     if(gcap.fWantPreview)
  2998.                         StopPreview();  // make sure graph is stopped
  2999.  
  3000.                     // The capture pin that we are trying to set the format on is connected if
  3001.                     // one of these variable is set to TRUE. The pin should be disconnected for
  3002.                     // the dialog to work properly.
  3003.                     if(gcap.fCaptureGraphBuilt || gcap.fPreviewGraphBuilt) {
  3004.                         DbgLog((LOG_TRACE,1,TEXT("Tear down graph for dialog")));
  3005.                         TearDownGraph();    // graph could prevent dialog working
  3006.                     }
  3007.                     IAMStreamConfig *pSC;
  3008.                     hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  3009.                         &MEDIATYPE_Interleaved, gcap.pVCap,
  3010.                         IID_IAMStreamConfig, (void **)&pSC);
  3011.                     if(hr != NOERROR)
  3012.                         hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  3013.                             &MEDIATYPE_Video, gcap.pVCap,
  3014.                             IID_IAMStreamConfig, (void **)&pSC);
  3015.                     ISpecifyPropertyPages *pSpec;
  3016.                     CAUUID cauuid;
  3017.                     hr = pSC->QueryInterface(IID_ISpecifyPropertyPages,
  3018.                         (void **)&pSpec);
  3019.                     if(hr == S_OK) {
  3020.                             hr = pSpec->GetPages(&cauuid);
  3021.                             hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
  3022.                                 (IUnknown **)&pSC, cauuid.cElems,
  3023.                                 (GUID *)cauuid.pElems, 0, 0, NULL);
  3024.  
  3025.                             // !!! What if changing output formats couldn't reconnect
  3026.                             // and the graph is broken?  Shouldn't be possible...
  3027.  
  3028.                             if(gcap.pVSC) {
  3029.                                 AM_MEDIA_TYPE *pmt;
  3030.                                 // get format being used NOW
  3031.                                 hr = gcap.pVSC->GetFormat(&pmt);
  3032.                                 // DV capture does not use a VIDEOINFOHEADER
  3033.                                 if(hr == NOERROR) {
  3034.                                     if(pmt->formattype == FORMAT_VideoInfo) {
  3035.                                         // resize our window to the new capture size
  3036.                                         ResizeWindow(HEADER(pmt->pbFormat)->biWidth,
  3037.                                             abs(HEADER(pmt->pbFormat)->biHeight));
  3038.                                     }
  3039.                                     DeleteMediaType(pmt);
  3040.                                 }
  3041.                             }
  3042.  
  3043.                             CoTaskMemFree(cauuid.pElems);
  3044.                             pSpec->Release();
  3045.                     }
  3046.                     pSC->Release();
  3047.                     if(gcap.fWantPreview) {
  3048.                             BuildPreviewGraph();
  3049.                             StartPreview();
  3050.                     }
  3051.             }
  3052.             else if(id - MENU_DIALOG0 == gcap.iVCapPreviewPinDialogPos) {
  3053.                     // this dialog may not work if the preview pin is connected
  3054.                     // already, because the downstream filter may reject a format
  3055.                     // change, so we better kill the graph. (EG: We switch from 
  3056.                     // capturing RGB to some compressed fmt, and need to pull in
  3057.                     // a decompressor)
  3058.                     if(gcap.fWantPreview) {
  3059.                         StopPreview();
  3060.                         TearDownGraph();
  3061.                     }
  3062.  
  3063.                     IAMStreamConfig *pSC;
  3064.  
  3065.                     // This dialog changes the preview format, so it might affect
  3066.                     // the format being drawn.  Our app's window size is taken
  3067.                     // from the size of the capture pin's video, not the preview
  3068.                     // pin, so changing that here won't have any effect. All in all,
  3069.                     // this probably won't be a terribly useful dialog in this app.
  3070.                     hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW,
  3071.                         &MEDIATYPE_Interleaved, gcap.pVCap,
  3072.                         IID_IAMStreamConfig, (void **)&pSC);
  3073.                     if(hr != NOERROR)
  3074.                         hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW,
  3075.                             &MEDIATYPE_Video, gcap.pVCap,
  3076.                             IID_IAMStreamConfig, (void **)&pSC);
  3077.  
  3078.                     ISpecifyPropertyPages *pSpec;
  3079.                     CAUUID cauuid;
  3080.                     hr = pSC->QueryInterface(IID_ISpecifyPropertyPages,
  3081.                         (void **)&pSpec);
  3082.                     if(hr == S_OK) {
  3083.                             hr = pSpec->GetPages(&cauuid);
  3084.                             hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
  3085.                                 (IUnknown **)&pSC, cauuid.cElems,
  3086.                                 (GUID *)cauuid.pElems, 0, 0, NULL);
  3087.                             CoTaskMemFree(cauuid.pElems);
  3088.                             pSpec->Release();
  3089.                     }
  3090.                     pSC->Release();
  3091.                     if(gcap.fWantPreview) {
  3092.                             BuildPreviewGraph();
  3093.                             StartPreview();
  3094.                     }
  3095.             }
  3096.             else if(id - MENU_DIALOG0 == gcap.iVCrossbarDialogPos) {
  3097.                     IAMCrossbar *pX;
  3098.                     hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  3099.                         &MEDIATYPE_Interleaved, gcap.pVCap,
  3100.                         IID_IAMCrossbar, (void **)&pX);
  3101.                     if(hr != NOERROR)
  3102.                         hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  3103.                             &MEDIATYPE_Video, gcap.pVCap,
  3104.                             IID_IAMCrossbar, (void **)&pX);
  3105.                     ISpecifyPropertyPages *pSpec;
  3106.                     CAUUID cauuid;
  3107.                     hr = pX->QueryInterface(IID_ISpecifyPropertyPages,
  3108.                         (void **)&pSpec);
  3109.                     if(hr == S_OK) {
  3110.                         hr = pSpec->GetPages(&cauuid);
  3111.                         hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
  3112.                             (IUnknown **)&pX, cauuid.cElems,
  3113.                             (GUID *)cauuid.pElems, 0, 0, NULL);
  3114.                         CoTaskMemFree(cauuid.pElems);
  3115.                         pSpec->Release();
  3116.                     }
  3117.                     pX->Release();
  3118.             }
  3119.             else if(id - MENU_DIALOG0 == gcap.iTVTunerDialogPos) {
  3120.                     IAMTVTuner *pTV;
  3121.                     hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  3122.                         &MEDIATYPE_Interleaved, gcap.pVCap,
  3123.                         IID_IAMTVTuner, (void **)&pTV);
  3124.                     if(hr != NOERROR)
  3125.                         hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  3126.                             &MEDIATYPE_Video, gcap.pVCap,
  3127.                             IID_IAMTVTuner, (void **)&pTV);
  3128.                     ISpecifyPropertyPages *pSpec;
  3129.                     CAUUID cauuid;
  3130.                     hr = pTV->QueryInterface(IID_ISpecifyPropertyPages,
  3131.                         (void **)&pSpec);
  3132.                     if(hr == S_OK) {
  3133.                         hr = pSpec->GetPages(&cauuid);
  3134.                         hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
  3135.                             (IUnknown **)&pTV, cauuid.cElems,
  3136.                             (GUID *)cauuid.pElems, 0, 0, NULL);
  3137.                         CoTaskMemFree(cauuid.pElems);
  3138.                         pSpec->Release();
  3139.                     }
  3140.                     pTV->Release();
  3141.             }
  3142.             else if(id - MENU_DIALOG0 == gcap.iACapDialogPos) {
  3143.                     ISpecifyPropertyPages *pSpec;
  3144.                     CAUUID cauuid;
  3145.                     hr = gcap.pACap->QueryInterface(IID_ISpecifyPropertyPages,
  3146.                         (void **)&pSpec);
  3147.                     if(hr == S_OK) {
  3148.                         hr = pSpec->GetPages(&cauuid);
  3149.                         hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
  3150.                             (IUnknown **)&gcap.pACap, cauuid.cElems,
  3151.                             (GUID *)cauuid.pElems, 0, 0, NULL);
  3152.                         CoTaskMemFree(cauuid.pElems);
  3153.                         pSpec->Release();
  3154.                     }
  3155.             }
  3156.             else if(id - MENU_DIALOG0 == gcap.iACapCapturePinDialogPos) {
  3157.                     // this dialog will not work while previewing - it might change
  3158.                     // the output format!
  3159.                     if(gcap.fWantPreview)
  3160.                         StopPreview();
  3161.                     IAMStreamConfig *pSC;
  3162.                     hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  3163.                         &MEDIATYPE_Audio, gcap.pACap,
  3164.                         IID_IAMStreamConfig, (void **)&pSC);
  3165.                     ISpecifyPropertyPages *pSpec;
  3166.                     CAUUID cauuid;
  3167.                     hr = pSC->QueryInterface(IID_ISpecifyPropertyPages,
  3168.                         (void **)&pSpec);
  3169.                     if(hr == S_OK) {
  3170.                         hr = pSpec->GetPages(&cauuid);
  3171.                         hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
  3172.                             (IUnknown **)&pSC, cauuid.cElems,
  3173.                             (GUID *)cauuid.pElems, 0, 0, NULL);
  3174.                         CoTaskMemFree(cauuid.pElems);
  3175.                         pSpec->Release();
  3176.                     }
  3177.                     pSC->Release();
  3178.                     if(gcap.fWantPreview)
  3179.                         StartPreview();
  3180.             }
  3181.             else if(id - MENU_DIALOG0 == gcap.iACrossbarDialogPos) {
  3182.                     IAMCrossbar *pX, *pX2;
  3183.                     IBaseFilter *pXF;
  3184.                     // we could use better error checking here... I'm assuming
  3185.                     // this won't fail
  3186.                     hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  3187.                         &MEDIATYPE_Interleaved, gcap.pVCap,
  3188.                         IID_IAMCrossbar, (void **)&pX);
  3189.                     if(hr != NOERROR)
  3190.                         hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  3191.                             &MEDIATYPE_Video, gcap.pVCap,
  3192.                             IID_IAMCrossbar, (void **)&pX);
  3193.                     hr = pX->QueryInterface(IID_IBaseFilter, (void **)&pXF);
  3194.                     hr = gcap.pBuilder->FindInterface(&LOOK_UPSTREAM_ONLY, NULL,
  3195.                         pXF, IID_IAMCrossbar, (void **)&pX2);
  3196.                     ISpecifyPropertyPages *pSpec;
  3197.                     CAUUID cauuid;
  3198.                     hr = pX2->QueryInterface(IID_ISpecifyPropertyPages,
  3199.                         (void **)&pSpec);
  3200.                     if(hr == S_OK) {
  3201.                         hr = pSpec->GetPages(&cauuid);
  3202.                         hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
  3203.                             (IUnknown **)&pX2, cauuid.cElems,
  3204.                             (GUID *)cauuid.pElems, 0, 0, NULL);
  3205.                         CoTaskMemFree(cauuid.pElems);
  3206.                         pSpec->Release();
  3207.                     }
  3208.                     pX2->Release();
  3209.                     pXF->Release();
  3210.                     pX->Release();
  3211.             }
  3212.             else if(id - MENU_DIALOG0 == gcap.iTVAudioDialogPos) {
  3213.                     IAMTVAudio *pTVA;
  3214.                     hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE,
  3215.                         &MEDIATYPE_Audio, gcap.pACap,
  3216.                         IID_IAMTVAudio, (void **)&pTVA);
  3217.                     ISpecifyPropertyPages *pSpec;
  3218.                     CAUUID cauuid;
  3219.                     hr = pTVA->QueryInterface(IID_ISpecifyPropertyPages,
  3220.                         (void **)&pSpec);
  3221.                     if(hr == S_OK) {
  3222.                         hr = pSpec->GetPages(&cauuid);
  3223.                         hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1,
  3224.                             (IUnknown **)&pTVA, cauuid.cElems,
  3225.                             (GUID *)cauuid.pElems, 0, 0, NULL);
  3226.                         CoTaskMemFree(cauuid.pElems);
  3227.                         pSpec->Release();
  3228.                     }
  3229.                     pTVA->Release();
  3230.             }
  3231.             else if(((id - MENU_DIALOG0) >  gcap.iVideoInputMenuPos) && 
  3232.                 (id - MENU_DIALOG0) <= gcap.iVideoInputMenuPos + gcap.NumberOfVideoInputs) {
  3233.                     // Remove existing checks
  3234.                     for(int j = 0; j < gcap.NumberOfVideoInputs; j++) {
  3235.                         CheckMenuItem(gcap.hMenuPopup, j, MF_BYPOSITION | 
  3236.                             ((j == (id - MENU_DIALOG0) - gcap.iVideoInputMenuPos - 1) ?
  3237.                             MF_CHECKED : MF_UNCHECKED )); 
  3238.                     }
  3239.  
  3240.                     if(gcap.pCrossbar) {
  3241.                             EXECUTE_ASSERT(S_OK == gcap.pCrossbar->SetInputIndex((id - MENU_DIALOG0) - gcap.iVideoInputMenuPos - 1));
  3242.                     }
  3243.             }
  3244.  
  3245.             break;
  3246.  
  3247.     }
  3248.     return 0L;
  3249. }
  3250.  
  3251.  
  3252. /*----------------------------------------------------------------------------*\
  3253. |   ErrMsg - Opens a Message box with a error message in it.  The user can     |
  3254. |            select the OK button to continue                                  |
  3255. \*----------------------------------------------------------------------------*/
  3256. int ErrMsg(LPTSTR sz,...) 
  3257. {
  3258.     static TCHAR tach[2000];
  3259.     va_list va;
  3260.  
  3261.     va_start(va, sz);
  3262.     wvsprintf(tach, sz, va);
  3263.     va_end(va);
  3264.     MessageBox(ghwndApp, tach, NULL, MB_OK|MB_ICONEXCLAMATION|MB_TASKMODAL);
  3265.     return FALSE;
  3266. }
  3267.  
  3268.  
  3269. /* AboutDlgProc()
  3270.  *
  3271.  * Dialog Procedure for the "about" dialog box.
  3272.  *
  3273.  */
  3274.  
  3275. BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 
  3276. {
  3277.     switch(msg) {
  3278.         case WM_COMMAND:
  3279.             EndDialog(hwnd, TRUE);
  3280.             return TRUE;
  3281.         case WM_INITDIALOG:
  3282.             return TRUE;
  3283.     }
  3284.     return FALSE;
  3285. }
  3286.  
  3287.  
  3288. // pre-allocate the capture file
  3289. //
  3290. BOOL AllocCaptureFile(HWND hWnd) 
  3291. {
  3292.     USES_CONVERSION;
  3293.  
  3294.     // we'll get into an infinite loop in the dlg proc setting a value
  3295.     if(gcap.szCaptureFile[0] == 0)
  3296.         return FALSE;
  3297.  
  3298.     /*
  3299.     * show the allocate file space dialog to encourage
  3300.     * the user to pre-allocate space
  3301.     */
  3302.     if(DoDialog(hWnd, IDD_AllocCapFileSpace, (DLGPROC)AllocCapFileProc, 0)) {
  3303.  
  3304.         // ensure repaint after dismissing dialog before
  3305.         // possibly lengthy operation
  3306.         UpdateWindow(ghwndApp);
  3307.  
  3308.         // User has hit OK. Alloc requested capture file space
  3309.         BOOL f = MakeBuilder();
  3310.         if(!f)
  3311.             return FALSE;
  3312.         if(gcap.pBuilder->AllocCapFile(T2W(gcap.szCaptureFile),
  3313.             (DWORDLONG)gcap.wCapFileSize * 1024L * 1024L) != NOERROR) {
  3314.             MessageBox(ghwndApp, TEXT("Error"),
  3315.                 TEXT("Failed to pre-allocate capture file space"),
  3316.                 MB_OK | MB_ICONEXCLAMATION);
  3317.             return FALSE;
  3318.         }
  3319.         return TRUE;
  3320.     }
  3321.     else {
  3322.         return FALSE;
  3323.     }
  3324. }
  3325.  
  3326.  
  3327. /*
  3328.  * Put up the open file dialog
  3329.  */
  3330. BOOL OpenFileDialog(HWND hWnd, LPTSTR pszName, int cb) 
  3331. {
  3332.     OPENFILENAME ofn;
  3333.     LPTSTR p;
  3334.     TCHAR        szFileName[_MAX_PATH];
  3335.     TCHAR        szBuffer[_MAX_PATH] ;
  3336.  
  3337.     if(pszName == NULL || cb <= 0)
  3338.         return FALSE;
  3339.  
  3340.     // start with capture file as current file name
  3341.     szFileName[0] = 0;
  3342.     lstrcpy(szFileName, gcap.szCaptureFile);
  3343.  
  3344.     // Get just the path info
  3345.     // Terminate the full path at the last backslash
  3346.     lstrcpy(szBuffer, szFileName);
  3347.     for(p = szBuffer + lstrlen(szBuffer); p > szBuffer; p--) {
  3348.         if(*p == '\\') {
  3349.             *(p+1) = '\0';
  3350.             break;
  3351.         }
  3352.     }
  3353.  
  3354.     _fmemset(&ofn, 0, sizeof(OPENFILENAME)) ;
  3355.     ofn.lStructSize = sizeof(OPENFILENAME) ;
  3356.     ofn.hwndOwner = hWnd ;
  3357.     ofn.lpstrFilter = TEXT("Microsoft AVI\0*.avi\0\0");
  3358.     ofn.nFilterIndex = 0 ;
  3359.     ofn.lpstrFile = szFileName;
  3360.     ofn.nMaxFile = sizeof(szFileName) ;
  3361.     ofn.lpstrFileTitle = NULL;
  3362.     ofn.lpstrTitle = TEXT("Set Capture File");
  3363.     ofn.nMaxFileTitle = 0 ;
  3364.     ofn.lpstrInitialDir = szBuffer;
  3365.     ofn.Flags = OFN_HIDEREADONLY | OFN_NOREADONLYRETURN | OFN_PATHMUSTEXIST ;
  3366.  
  3367.     if(GetOpenFileName(&ofn)) {
  3368.         // We have a capture file name
  3369.         lstrcpyn(pszName, szFileName, cb);
  3370.         return TRUE;
  3371.     }
  3372.     else {
  3373.         return FALSE;
  3374.     }
  3375. }
  3376.  
  3377.  
  3378. /*
  3379.  * Put up a dialog to allow the user to select a capture file.
  3380.  */
  3381. BOOL SetCaptureFile(HWND hWnd) 
  3382. {
  3383.     USES_CONVERSION;
  3384.  
  3385.     if(OpenFileDialog(hWnd, gcap.szCaptureFile, _MAX_PATH)) {
  3386.         OFSTRUCT os;
  3387.  
  3388.         // We have a capture file name
  3389.  
  3390.         /*
  3391.         * if this is a new file, then invite the user to
  3392.         * allocate some space
  3393.         */
  3394. #ifdef UNICODE
  3395.         // Convert Multibyte string to ANSI
  3396.         char szCaptureFile[STR_MAX_LENGTH];
  3397.         int rc = WideCharToMultiByte(CP_ACP, 0, gcap.szCaptureFile, -1, 
  3398.             szCaptureFile, STR_MAX_LENGTH, NULL, NULL);
  3399. #else
  3400.         TCHAR *szCaptureFile = gcap.szCaptureFile;
  3401. #endif
  3402.  
  3403.         if(OpenFile(szCaptureFile, &os, OF_EXIST) == HFILE_ERROR) {
  3404.  
  3405.             // bring up dialog, and set new file size
  3406.             BOOL f = AllocCaptureFile(hWnd);
  3407.             if(!f)
  3408.                 return FALSE;
  3409.         }
  3410.     }
  3411.     else {
  3412.         return FALSE;
  3413.     }
  3414.  
  3415.     SetAppCaption();    // new a new app caption
  3416.  
  3417.     // tell the file writer to use the new filename
  3418.     if(gcap.pSink) {
  3419.         gcap.pSink->SetFileName(T2W(gcap.szCaptureFile), NULL);
  3420.     }
  3421.  
  3422.     return TRUE;
  3423. }
  3424.  
  3425.  
  3426. /*
  3427.  * Put up a dialog to allow the user to save the contents of the capture file
  3428.  * elsewhere
  3429.  */
  3430. BOOL SaveCaptureFile(HWND hWnd) 
  3431. {
  3432.     USES_CONVERSION;
  3433.     HRESULT hr;
  3434.     TCHAR tachDstFile[_MAX_PATH];
  3435.  
  3436.     if(gcap.pBuilder == NULL)
  3437.         return FALSE;
  3438.  
  3439.     if(OpenFileDialog(hWnd, tachDstFile, _MAX_PATH)) {
  3440.  
  3441.         // We have a capture file name
  3442.         statusUpdateStatus(ghwndStatus, TEXT("Saving capture file - please wait..."));
  3443.  
  3444.         // we need our own graph builder because the main one might not exist
  3445.         ICaptureGraphBuilder2 *pBuilder;
  3446.         hr = CoCreateInstance((REFCLSID)CLSID_CaptureGraphBuilder2,
  3447.             NULL, CLSCTX_INPROC, (REFIID)IID_ICaptureGraphBuilder2,
  3448.             (void **)&pBuilder);
  3449.  
  3450.         if(hr == NOERROR) {
  3451.             // allow the user to press ESC to abort... ask for progress
  3452.             CProgress *pProg = new CProgress(TEXT(""), NULL, &hr);
  3453.             IAMCopyCaptureFileProgress *pIProg = NULL;
  3454.             if(pProg) {
  3455.                 hr = pProg->QueryInterface(IID_IAMCopyCaptureFileProgress,
  3456.                     (void **)&pIProg);
  3457.             }
  3458.             hr = pBuilder->CopyCaptureFile(T2W(gcap.szCaptureFile),
  3459.                 T2W(tachDstFile), TRUE, pIProg);
  3460.             if(pIProg)
  3461.                 pIProg->Release();
  3462.             pBuilder->Release();
  3463.         }
  3464.  
  3465.         if(hr == S_OK)
  3466.             statusUpdateStatus(ghwndStatus, TEXT("Capture file saved"));
  3467.         else if(hr == S_FALSE)
  3468.             statusUpdateStatus(ghwndStatus, TEXT("Capture file save aborted"));
  3469.         else
  3470.             statusUpdateStatus(ghwndStatus, TEXT("Capture file save ERROR"));
  3471.         return (hr == NOERROR ? TRUE : FALSE); 
  3472.  
  3473.     }
  3474.     else {
  3475.         return TRUE;    // they cancelled or something
  3476.     }
  3477. }
  3478.  
  3479. // brings up a dialog box
  3480. //
  3481. int DoDialog(HWND hwndParent, int DialogID, DLGPROC fnDialog, long lParam) 
  3482. {
  3483.     DLGPROC fn;
  3484.     int result;
  3485.  
  3486.     fn = (DLGPROC)MakeProcInstance(fnDialog, ghInstApp);
  3487.     result = (int) DialogBoxParam(ghInstApp,
  3488.         MAKEINTRESOURCE(DialogID),
  3489.         hwndParent,
  3490.         fn,
  3491.         lParam);
  3492.     FreeProcInstance(fn);
  3493.  
  3494.     return result;
  3495. }
  3496.  
  3497.  
  3498. //
  3499. // GetFreeDiskSpace: Function to Measure Available Disk Space
  3500. //
  3501. static long GetFreeDiskSpaceInKB(LPTSTR pFile) 
  3502. {
  3503.     DWORD dwFreeClusters, dwBytesPerSector, dwSectorsPerCluster, dwClusters;
  3504.     TCHAR RootName[MAX_PATH];
  3505.     LPTSTR ptmp;    //required arg
  3506.     ULARGE_INTEGER ulA, ulB, ulFreeBytes;
  3507.  
  3508.     // need to find path for root directory on drive containing
  3509.     // this file.
  3510.  
  3511.     GetFullPathName(pFile, sizeof(RootName)/sizeof(RootName[0]), RootName, &ptmp);
  3512.  
  3513.     // truncate this to the name of the root directory (how tedious)
  3514.     if(RootName[0] == '\\' && RootName[1] == '\\') {
  3515.  
  3516.         // path begins with  \\server\share\path so skip the first
  3517.         // three backslashes
  3518.         ptmp = &RootName[2];
  3519.         while(*ptmp && (*ptmp != '\\')) {
  3520.             ptmp++;
  3521.         }
  3522.         if(*ptmp) {
  3523.             // advance past the third backslash
  3524.             ptmp++;
  3525.         }
  3526.     }
  3527.     else {
  3528.         // path must be drv:\path
  3529.         ptmp = RootName;
  3530.     }
  3531.  
  3532.     // find next backslash and put a null after it
  3533.     while(*ptmp && (*ptmp != '\\')) {
  3534.         ptmp++;
  3535.     }
  3536.     // found a backslash ?
  3537.     if(*ptmp) {
  3538.         // skip it and insert null
  3539.         ptmp++;
  3540.         *ptmp = '\0';
  3541.     }
  3542.  
  3543.     // the only real way of finding out free disk space is calling
  3544.     // GetDiskFreeSpaceExA, but it doesn't exist on Win95
  3545.  
  3546.     HINSTANCE h = LoadLibrary(TEXT("kernel32.dll"));
  3547.     if(h) {
  3548.         typedef BOOL(WINAPI *MyFunc)(LPCTSTR RootName, PULARGE_INTEGER pulA, PULARGE_INTEGER pulB, PULARGE_INTEGER pulFreeBytes);
  3549.  
  3550. #ifdef UNICODE
  3551.         MyFunc pfnGetDiskFreeSpaceEx = (MyFunc)GetProcAddress(h, "GetDiskFreeSpaceExW");
  3552. #else
  3553.         MyFunc pfnGetDiskFreeSpaceEx = (MyFunc)GetProcAddress(h, "GetDiskFreeSpaceExA");
  3554. #endif
  3555.         FreeLibrary(h);
  3556.  
  3557.         if(pfnGetDiskFreeSpaceEx) {
  3558.             if(!pfnGetDiskFreeSpaceEx(RootName, &ulA, &ulB, &ulFreeBytes))
  3559.                 return -1;
  3560.             else
  3561.                 return (long)(ulFreeBytes.QuadPart / 1024);
  3562.         }
  3563.     }
  3564.  
  3565.     if(!GetDiskFreeSpace(RootName, &dwSectorsPerCluster, &dwBytesPerSector,
  3566.         &dwFreeClusters, &dwClusters))
  3567.         return (-1);
  3568.     else
  3569.         return(MulDiv(dwSectorsPerCluster * dwBytesPerSector,
  3570.             dwFreeClusters,
  3571.             1024));
  3572. }
  3573.  
  3574.  
  3575.  
  3576. // AllocCapFileProc: Capture file Space Allocation Dialog Box Procedure
  3577. //
  3578. int FAR PASCAL AllocCapFileProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam) 
  3579. {
  3580.     static int nFreeMBs = 0 ;
  3581.  
  3582.     switch(Message) {
  3583.         case WM_INITDIALOG: {
  3584.                 DWORDLONG        dwlFileSize = 0;
  3585.                 long             lFreeSpaceInKB;
  3586.  
  3587.                 // Get current capture file name and measure its size
  3588.                 dwlFileSize = GetSize(gcap.szCaptureFile);
  3589.  
  3590.                 // Get free disk space and add current capture file size to that.
  3591.                 // Convert the available space to MBs.
  3592.                 if((lFreeSpaceInKB = GetFreeDiskSpaceInKB(gcap.szCaptureFile)) != -1L) {
  3593.                     lFreeSpaceInKB += (long)(dwlFileSize / 1024);
  3594.                     nFreeMBs = lFreeSpaceInKB / 1024 ;
  3595.                     SetDlgItemInt(hDlg, IDD_SetCapFileFree, nFreeMBs, TRUE) ;
  3596.                 }
  3597.                 else {
  3598.                         EnableWindow(GetDlgItem(hDlg, IDD_SetCapFileFree), FALSE);
  3599.                 }
  3600.  
  3601.                 gcap.wCapFileSize = (WORD) (dwlFileSize / (1024L * 1024L));
  3602.  
  3603.                 SetDlgItemInt(hDlg, IDD_SetCapFileSize, gcap.wCapFileSize, TRUE) ;
  3604.                 return TRUE ;
  3605.             }
  3606.  
  3607.         case WM_COMMAND :
  3608.             switch(GET_WM_COMMAND_ID(wParam, lParam)) {
  3609.                 case IDOK : {
  3610.                         int         iCapFileSize ;
  3611.  
  3612.                         iCapFileSize = (int) GetDlgItemInt(hDlg, IDD_SetCapFileSize, NULL, TRUE) ;
  3613.                         if(iCapFileSize <= 0 || iCapFileSize > nFreeMBs) {
  3614.                             // You are asking for more than we have !! Sorry, ...
  3615.                             SetDlgItemInt(hDlg, IDD_SetCapFileSize, iCapFileSize, TRUE) ;
  3616.                             SetFocus(GetDlgItem(hDlg, IDD_SetCapFileSize)) ;
  3617.                             MessageBeep(MB_ICONEXCLAMATION) ;
  3618.                             return FALSE ;
  3619.                         }
  3620.                         gcap.wCapFileSize = (WORD)iCapFileSize ;
  3621.  
  3622.                         EndDialog(hDlg, TRUE) ;
  3623.                         return TRUE ;
  3624.                     }
  3625.  
  3626.                 case IDCANCEL :
  3627.                     EndDialog(hDlg, FALSE) ;
  3628.                     return TRUE ;
  3629.  
  3630.                 case IDD_SetCapFileSize: {
  3631.                             long l;
  3632.                             BOOL bchanged;
  3633.                             TCHAR tachBuffer[21];
  3634.  
  3635.                             // check that entered size is a valid number
  3636.                             GetDlgItemText(hDlg, IDD_SetCapFileSize, tachBuffer,
  3637.                                 sizeof(tachBuffer)/sizeof(tachBuffer[0]));
  3638.                             l = _ttol(tachBuffer);
  3639.                             bchanged = FALSE;
  3640.                             if(l < 1) {
  3641.                                 l = 1;
  3642.                                 bchanged = TRUE;
  3643.                                 // don't infinite loop if there's < 1 Meg free
  3644.                             }
  3645.                             else if(l > nFreeMBs && nFreeMBs > 0) {
  3646.                                     l = nFreeMBs;
  3647.                                     bchanged = TRUE;
  3648.                             }
  3649.                             else {
  3650.                                     // make sure there are no non-digit chars
  3651.                                     // atol() will ignore trailing non-digit characters
  3652.                                     int c = 0;
  3653.                                     while(tachBuffer[c]) {
  3654.                                         if(IsCharAlpha(tachBuffer[c]) ||
  3655.                                             !IsCharAlphaNumeric(tachBuffer[c])) {
  3656.  
  3657.                                             // string contains non-digit chars - reset
  3658.                                             l = 1;
  3659.                                             bchanged = TRUE;
  3660.                                             break;
  3661.                                         }
  3662.                                         c++;
  3663.                                     }
  3664.                             }
  3665.                             if(bchanged) {
  3666.                                     wsprintf(tachBuffer, TEXT("%ld"), l);
  3667.                                     SetDlgItemText(hDlg, IDD_SetCapFileSize, tachBuffer);
  3668.                             }
  3669.                             break;
  3670.                     }
  3671.             }
  3672.             break;
  3673.     }
  3674.  
  3675.     return FALSE ;
  3676. }
  3677.  
  3678.  
  3679. //
  3680. // FrameRateProc: Choose a frame rate
  3681. //
  3682. int FAR PASCAL FrameRateProc(HWND hwnd, UINT msg, UINT wParam, LONG lParam) 
  3683. {
  3684.     TCHAR  tach[32];
  3685.     USES_CONVERSION;
  3686.  
  3687.     switch(msg) {
  3688.         case WM_INITDIALOG:
  3689.             /* put the current frame rate in the box */
  3690.             wsprintf(tach, TEXT("%d"), (int) gcap.FrameRate);
  3691.             SetDlgItemText(hwnd, IDC_FRAMERATE, tach);
  3692.             CheckDlgButton(hwnd, IDC_USEFRAMERATE, gcap.fUseFrameRate);
  3693.             break;
  3694.  
  3695.         case WM_COMMAND:
  3696.             switch(wParam) {
  3697.             case IDCANCEL:
  3698.                 EndDialog(hwnd, FALSE);
  3699.                 break;
  3700.  
  3701.             case IDOK:
  3702.                 /* get the new frame rate */
  3703.                 GetDlgItemText(hwnd, IDC_FRAMERATE, tach, sizeof(tach)/sizeof(tach[0]));
  3704.  
  3705. #ifdef UNICODE
  3706.                 int rc;
  3707.  
  3708.                 // Convert Multibyte string to ANSI
  3709.                 char szANSI[STR_MAX_LENGTH];
  3710.                 rc = WideCharToMultiByte(CP_ACP, 0, tach, -1, szANSI, 
  3711.                     STR_MAX_LENGTH, NULL, NULL);
  3712.                 double frameRate = atof(szANSI);
  3713. #else
  3714.                 double frameRate = atof(tach);
  3715. #endif
  3716.  
  3717.                 if(frameRate <= 0.) {
  3718.                     ErrMsg(TEXT("Invalid frame rate."));
  3719.                     break;
  3720.                 }
  3721.                 else
  3722.                     gcap.FrameRate = frameRate;
  3723.  
  3724.                 gcap.fUseFrameRate = IsDlgButtonChecked(hwnd, IDC_USEFRAMERATE);
  3725.                 EndDialog(hwnd, TRUE);
  3726.                 break;
  3727.             }
  3728.             break;
  3729.  
  3730.         default:
  3731.             return FALSE;
  3732.     }
  3733.     return TRUE;
  3734. }
  3735.  
  3736.  
  3737. //
  3738. // TimeLimitProc: Choose a capture time limit
  3739. //
  3740. int FAR PASCAL TimeLimitProc(HWND hwnd, UINT msg, UINT wParam, LONG lParam) 
  3741. {
  3742.     TCHAR   tach[32];
  3743.     DWORD   dwTimeLimit;
  3744.  
  3745.     switch(msg) {
  3746.         case WM_INITDIALOG:
  3747.             /* put the current time limit info in the boxes */
  3748.             wsprintf(tach, TEXT("%d"), gcap.dwTimeLimit);
  3749.             SetDlgItemText(hwnd, IDC_TIMELIMIT, tach);
  3750.             CheckDlgButton(hwnd, IDC_USETIMELIMIT, gcap.fUseTimeLimit);
  3751.             break;
  3752.  
  3753.         case WM_COMMAND:
  3754.             switch(wParam) {
  3755.             case IDCANCEL:
  3756.                 EndDialog(hwnd, FALSE);
  3757.                 break;
  3758.  
  3759.             case IDOK:
  3760.                 /* get the new time limit */
  3761.                 dwTimeLimit = GetDlgItemInt(hwnd, IDC_TIMELIMIT, NULL, FALSE);
  3762.                 gcap.dwTimeLimit = dwTimeLimit;
  3763.                 gcap.fUseTimeLimit = IsDlgButtonChecked(hwnd, IDC_USETIMELIMIT);
  3764.                 EndDialog(hwnd, TRUE);
  3765.                 break;
  3766.             }
  3767.             break;
  3768.  
  3769.         default:
  3770.             return FALSE;
  3771.     }
  3772.     return TRUE;
  3773. }
  3774.  
  3775.  
  3776. //
  3777. // PressAKeyProc: Press OK to capture
  3778. //
  3779. int FAR PASCAL PressAKeyProc(HWND hwnd, UINT msg, UINT wParam, LONG lParam) 
  3780. {
  3781.     TCHAR tach[_MAX_PATH];
  3782.  
  3783.     switch(msg) {
  3784.         case WM_INITDIALOG:
  3785.             /* set the current file name in the box */
  3786.             wsprintf(tach, TEXT("%s"), gcap.szCaptureFile);
  3787.             SetDlgItemText(hwnd, IDC_CAPFILENAME, tach);
  3788.             break;
  3789.  
  3790.         case WM_COMMAND:
  3791.             switch(wParam) {
  3792.             case IDCANCEL:
  3793.                 EndDialog(hwnd, FALSE);
  3794.                 break;
  3795.  
  3796.             case IDOK:
  3797.                 EndDialog(hwnd, TRUE);
  3798.                 break;
  3799.             }
  3800.             break;
  3801.  
  3802.         default:
  3803.             return FALSE;
  3804.     }
  3805.     return TRUE;
  3806. }
  3807.  
  3808. DWORDLONG GetSize(LPCTSTR tach) 
  3809. {
  3810.     HANDLE hFile = CreateFile(tach, GENERIC_READ, FILE_SHARE_READ, 0,
  3811.         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  3812.  
  3813.     if(hFile == INVALID_HANDLE_VALUE) {
  3814.         return 0;
  3815.     }
  3816.  
  3817.     DWORD dwSizeHigh;
  3818.     DWORD dwSizeLow = GetFileSize(hFile, &dwSizeHigh);
  3819.  
  3820.     DWORDLONG dwlSize = dwSizeLow + ((DWORDLONG)dwSizeHigh << 32);
  3821.  
  3822.     if(!CloseHandle(hFile)) {
  3823.         dwlSize = 0;
  3824.     }
  3825.  
  3826.     return dwlSize;
  3827. }
  3828.  
  3829. void OnClose() 
  3830. {
  3831.     // Unregister device notifications
  3832.     if(ghDevNotify != NULL) {
  3833.         ASSERT(gpUnregisterDeviceNotification);
  3834.         gpUnregisterDeviceNotification(ghDevNotify);
  3835.         ghDevNotify = NULL;
  3836.     }
  3837.  
  3838.     StopPreview();
  3839.     StopCapture();
  3840.     TearDownGraph();
  3841.     FreeCapFilters();
  3842.  
  3843.     // store current settings in win.ini for next time
  3844.     WriteProfileString(TEXT("annie"), TEXT("CaptureFile"),
  3845.         gcap.szCaptureFile);
  3846.  
  3847.     TCHAR *tach = NULL;
  3848.     WCHAR *szDisplayName = NULL;
  3849.     szDisplayName = 0;
  3850.     if(gcap.pmVideo) {
  3851.         if(SUCCEEDED(gcap.pmVideo->GetDisplayName(0, 0, &szDisplayName))) {   
  3852.             if(szDisplayName) {
  3853.                 tach = new TCHAR [wcslen(szDisplayName)+1];
  3854.                 if(tach == NULL)
  3855.                     return;
  3856.  
  3857.                 wsprintf(tach, TEXT("%S"), szDisplayName);
  3858.                 CoTaskMemFree(szDisplayName);
  3859.             }
  3860.  
  3861.         }
  3862.     }
  3863.     WriteProfileString(TEXT("annie"), TEXT("VideoDevice2"), tach ? tach : TEXT("") );
  3864.     delete(tach);
  3865.     tach = NULL;
  3866.  
  3867.     szDisplayName = 0;
  3868.     if(gcap.pmAudio) {
  3869.         if(SUCCEEDED(gcap.pmAudio->GetDisplayName(0, 0, &szDisplayName))) {
  3870.             if(szDisplayName) {
  3871.                 tach = new TCHAR [wcslen(szDisplayName)+1];
  3872.                 if(tach == NULL)
  3873.                     return;
  3874.  
  3875.                 wsprintf(tach, TEXT("%S"), szDisplayName);
  3876.                 CoTaskMemFree(szDisplayName);
  3877.             }
  3878.  
  3879.         }
  3880.     }
  3881.  
  3882.     if(tach == NULL) {
  3883.         tach = new TCHAR [120];
  3884.         wsprintf(tach, TEXT("%S"), L"");
  3885.  
  3886.     }
  3887.  
  3888.     WriteProfileString(TEXT("annie"), TEXT("AudioDevice2"), tach);
  3889.  
  3890.     wsprintf(tach, TEXT("%d"), (int)(10000000 / gcap.FrameRate));
  3891.     WriteProfileString(TEXT("annie"), TEXT("FrameRate"), tach);
  3892.     wsprintf(tach, TEXT("%d"), gcap.fUseFrameRate);
  3893.     WriteProfileString(TEXT("annie"), TEXT("UseFrameRate"), tach);
  3894.     wsprintf(tach, TEXT("%d"), gcap.fCapAudio);
  3895.     WriteProfileString(TEXT("annie"), TEXT("CaptureAudio"), tach);
  3896.     wsprintf(tach, TEXT("%d"), gcap.fCapCC);
  3897.     WriteProfileString(TEXT("annie"), TEXT("CaptureCC"), tach);
  3898.     wsprintf(tach, TEXT("%d"), gcap.fWantPreview);
  3899.     WriteProfileString(TEXT("annie"), TEXT("WantPreview"), tach);
  3900.     wsprintf(tach, TEXT("%d"), gcap.iMasterStream);
  3901.     WriteProfileString(TEXT("annie"), TEXT("MasterStream"), tach);
  3902.     wsprintf(tach, TEXT("%d"), gcap.fUseTimeLimit);
  3903.     WriteProfileString(TEXT("annie"), TEXT("UseTimeLimit"), tach);
  3904.     wsprintf(tach, TEXT("%d"), gcap.dwTimeLimit);
  3905.     WriteProfileString(TEXT("annie"), TEXT("TimeLimit"), tach);
  3906.     delete(tach);
  3907. }
  3908.  
  3909.  
  3910.  
  3911. // Adds a DirectShow filter graph to the Running Object Table,
  3912. // allowing GraphEdit to "spy" on a remote filter graph.
  3913. HRESULT AddGraphToRot(IUnknown *pUnkGraph, DWORD *pdwRegister) 
  3914. {
  3915.     IMoniker * pMoniker;
  3916.     IRunningObjectTable *pROT;
  3917.     WCHAR wsz[128];
  3918.     HRESULT hr;
  3919.  
  3920.     if (FAILED(GetRunningObjectTable(0, &pROT)))
  3921.         return E_FAIL;
  3922.  
  3923.     wsprintfW(wsz, L"FilterGraph %08x pid %08x", (DWORD_PTR)pUnkGraph, 
  3924.               GetCurrentProcessId());
  3925.  
  3926.     hr = CreateItemMoniker(L"!", wsz, &pMoniker);
  3927.     if (SUCCEEDED(hr)) 
  3928.     {
  3929.         hr = pROT->Register(0, pUnkGraph, pMoniker, pdwRegister);
  3930.         pMoniker->Release();
  3931.     }
  3932.     pROT->Release();
  3933.     return hr;
  3934. }
  3935.  
  3936. // Removes a filter graph from the Running Object Table
  3937. void RemoveGraphFromRot(DWORD pdwRegister)
  3938. {
  3939.     IRunningObjectTable *pROT;
  3940.  
  3941.     if (SUCCEEDED(GetRunningObjectTable(0, &pROT))) 
  3942.     {
  3943.         pROT->Revoke(pdwRegister);
  3944.         pROT->Release();
  3945.     }
  3946. }
  3947.