//------------------------------------------------------------------------------ // File: AMCap.cpp // // Desc: Audio/Video Capture sample for DirectShow // // // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------------------------ #include "stdafx.h" #include #include #include #include #include #include #include #include #include "stdafx.h" #include "amcap.h" #include "status.h" #include "crossbar.h" #include "SampleCGB.h" //------------------------------------------------------------------------------ // Macros //------------------------------------------------------------------------------ #define ABS(x) (((x) > 0) ? (x) : -(x)) // An application can advertise the existence of its filter graph // by registering the graph with a global Running Object Table (ROT). // The GraphEdit application can detect and remotely view the running // filter graph, allowing you to 'spy' on the graph with GraphEdit. // // To enable registration in this sample, define REGISTER_FILTERGRAPH. // #ifdef DEBUG #define REGISTER_FILTERGRAPH #endif //------------------------------------------------------------------------------ // Global data //------------------------------------------------------------------------------ HINSTANCE ghInstApp=0; HACCEL ghAccel=0; HFONT ghfontApp=0; TEXTMETRIC gtm={0}; TCHAR gszAppName[]=TEXT("AMCAP"); HWND ghwndApp=0, ghwndStatus=0; HDEVNOTIFY ghDevNotify=0; PUnregisterDeviceNotification gpUnregisterDeviceNotification=0; PRegisterDeviceNotification gpRegisterDeviceNotification=0; DWORD g_dwGraphRegister=0; struct _capstuff { WCHAR wszCaptureFile[_MAX_PATH]; WORD wCapFileSize; // size in Meg ISampleCaptureGraphBuilder *pBuilder; IVideoWindow *pVW; IMediaEventEx *pME; IAMDroppedFrames *pDF; IAMVideoCompression *pVC; IAMVfwCaptureDialogs *pDlg; IAMStreamConfig *pASC; // for audio cap IAMStreamConfig *pVSC; // for video cap IBaseFilter *pRender; IBaseFilter *pVCap, *pACap; IGraphBuilder *pFg; IFileSinkFilter *pSink; IConfigAviMux *pConfigAviMux; int iMasterStream; BOOL fCaptureGraphBuilt; BOOL fPreviewGraphBuilt; BOOL fCapturing; BOOL fPreviewing; BOOL fMPEG2; BOOL fCapAudio; BOOL fCapCC; BOOL fCCAvail; BOOL fCapAudioIsRelevant; bool fDeviceMenuPopulated; IMoniker *rgpmVideoMenu[10]; IMoniker *rgpmAudioMenu[10]; IMoniker *pmVideo; IMoniker *pmAudio; double FrameRate; BOOL fWantPreview; long lCapStartTime; long lCapStopTime; WCHAR wachFriendlyName[120]; BOOL fUseTimeLimit; BOOL fUseFrameRate; DWORD dwTimeLimit; int iFormatDialogPos; int iSourceDialogPos; int iDisplayDialogPos; int iVCapDialogPos; int iVCrossbarDialogPos; int iTVTunerDialogPos; int iACapDialogPos; int iACrossbarDialogPos; int iTVAudioDialogPos; int iVCapCapturePinDialogPos; int iVCapPreviewPinDialogPos; int iACapCapturePinDialogPos; long lDroppedBase; long lNotBase; BOOL fPreviewFaked; CCrossbar *pCrossbar; int iVideoInputMenuPos; LONG NumberOfVideoInputs; HMENU hMenuPopup; int iNumVCapDevices; } gcap; // implements IAMCopyCaptureFileProgress // class CProgress : public IAMCopyCaptureFileProgress { public: CProgress() { }; ~CProgress() { }; STDMETHODIMP_(ULONG) AddRef() { return 1; }; STDMETHODIMP_(ULONG) Release() { return 0; }; STDMETHODIMP QueryInterface(REFIID iid, void **p) { if (p == NULL) { return E_POINTER; } if (iid == IID_IUnknown) { *p = reinterpret_cast(this); AddRef(); return S_OK; } else if (iid == IID_IAMCopyCaptureFileProgress) { *p = reinterpret_cast(this); AddRef(); return S_OK; } else { return E_NOINTERFACE; } }; STDMETHODIMP Progress(int i) { TCHAR tach[80]; HRESULT hr = StringCchPrintf(tach, 80, TEXT("Save File Progress: %d%%\0"), i); statusUpdateStatus(ghwndStatus, tach); return S_OK; }; }; //------------------------------------------------------------------------------ // Function Prototypes //------------------------------------------------------------------------------ typedef LONG(PASCAL *LPWNDPROC)(HWND, UINT, WPARAM, LPARAM); // pointer to a window procedure LONG WINAPI AppWndProc(HWND hwnd, UINT uiMessage, WPARAM wParam, LPARAM lParam); LONG PASCAL AppCommand(HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam); BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); void ErrMsg(LPTSTR sz,...); BOOL SetCaptureFile(HWND hWnd); BOOL SaveCaptureFile(HWND hWnd); BOOL AllocCaptureFile(HWND hWnd); int DoDialog(HWND hwndParent, int DialogID, DLGPROC fnDialog, long lParam); int FAR PASCAL AllocCapFileProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam); int FAR PASCAL FrameRateProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam); int FAR PASCAL TimeLimitProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam); int FAR PASCAL PressAKeyProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam); void TearDownGraph(void); BOOL BuildCaptureGraph(); BOOL BuildPreviewGraph(); void UpdateStatus(BOOL fAllStats); void AddDevicesToMenu(); void ChooseDevices(TCHAR *szVideo, TCHAR *szAudio); void ChooseDevices(IMoniker *pmVideo, IMoniker *pmAudio); void ChooseFrameRate(); BOOL InitCapFilters(); void FreeCapFilters(); BOOL StopPreview(); BOOL StartPreview(); BOOL StopCapture(); DWORDLONG GetSize(LPCTSTR tach); void MakeMenuOptions(); void OnClose(); //------------------------------------------------------------------------------ // Name: SetAppCaption() // Desc: Set the caption to be the application name followed by the capture file //------------------------------------------------------------------------------ void SetAppCaption() { TCHAR tach[_MAX_PATH + 80]; StringCchCopy(tach, NUMELMS(tach), gszAppName); if(gcap.wszCaptureFile[0] != 0) { HRESULT hr = StringCchCat(tach, _MAX_PATH + 80, TEXT(" - ")); hr = StringCchCat(tach, _MAX_PATH + 80, gcap.wszCaptureFile); } SetWindowText(ghwndApp, tach); } /*----------------------------------------------------------------------------*\ | AppInit( hInst, hPrev) | | | | Description: | | This is called when the application is first loaded into | | memory. It performs all initialization that doesn't need to be done | | once per instance. | | | | Arguments: | | hInstance instance handle of current instance | | hPrev instance handle of previous instance | | | | Returns: | | TRUE if successful, FALSE if not | | | \*----------------------------------------------------------------------------*/ BOOL AppInit(HINSTANCE hInst, HINSTANCE hPrev, int sw) { WNDCLASS cls; HDC hdc; const DWORD dwExStyle = 0; CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); /* Save instance handle for DialogBoxs */ ghInstApp = hInst; ghAccel = LoadAccelerators(hInst, MAKEINTATOM(ID_APP)); if(!hPrev) { /* * Register a class for the main application window */ cls.hCursor = LoadCursor(NULL,IDC_ARROW); cls.hIcon = NULL; cls.lpszMenuName = MAKEINTATOM(ID_APP); cls.lpszClassName = MAKEINTATOM(ID_APP); cls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); cls.hInstance = hInst; cls.style = CS_BYTEALIGNCLIENT | CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS; cls.lpfnWndProc = (WNDPROC) AppWndProc; cls.cbWndExtra = 0; cls.cbClsExtra = 0; if(!RegisterClass(&cls)) return FALSE; } // Is this necessary? ghfontApp = (HFONT)GetStockObject(ANSI_VAR_FONT); hdc = GetDC(NULL); SelectObject(hdc, ghfontApp); GetTextMetrics(hdc, >m); ReleaseDC(NULL, hdc); ghwndApp=CreateWindowEx(dwExStyle, MAKEINTATOM(ID_APP), // Class name gszAppName, // Caption // Style bits WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, CW_USEDEFAULT, 0, // Position 320,300, // Size (HWND)NULL, // Parent window (no parent) (HMENU)NULL, // use class menu hInst, // handle to window instance (LPSTR)NULL // no params to pass on ); // create the status bar statusInit(hInst, hPrev); ghwndStatus = CreateWindowEx(0, szStatusClass, NULL, WS_CHILD|WS_BORDER|WS_VISIBLE|WS_CLIPSIBLINGS, 0, 0, 0, 0, ghwndApp, NULL, hInst, NULL); if(ghwndStatus == NULL) return(FALSE); ShowWindow(ghwndApp,sw); // Read the capture file name from win.ini GetProfileString(TEXT("annie"), TEXT("CaptureFile"), TEXT(""), gcap.wszCaptureFile, NUMELMS(gcap.wszCaptureFile)); // Read the list of devices to use from win.ini ZeroMemory(gcap.rgpmAudioMenu, sizeof(gcap.rgpmAudioMenu)); ZeroMemory(gcap.rgpmVideoMenu, sizeof(gcap.rgpmVideoMenu)); gcap.pmVideo = 0; gcap.pmAudio = 0; gcap.fMPEG2 = FALSE; TCHAR szVideoDisplayName[1024], szAudioDisplayName[1024]; *szAudioDisplayName = *szVideoDisplayName = 0; // null terminate GetProfileString(TEXT("annie"), TEXT("VideoDevice2"), TEXT(""), szVideoDisplayName, NUMELMS(szVideoDisplayName)); GetProfileString(TEXT("annie"), TEXT("AudioDevice2"), TEXT(""), szAudioDisplayName, NUMELMS(szAudioDisplayName)); gcap.fDeviceMenuPopulated = false; AddDevicesToMenu(); // do we want audio? gcap.fCapAudio = GetProfileInt(TEXT("annie"), TEXT("CaptureAudio"), TRUE); gcap.fCapCC = GetProfileInt(TEXT("annie"), TEXT("CaptureCC"), FALSE); // do we want preview? gcap.fWantPreview = GetProfileInt(TEXT("annie"), TEXT("WantPreview"), FALSE); // which stream should be the master? NONE(-1) means nothing special happens // AUDIO(1) means the video frame rate is changed before written out to keep // the movie in sync when the audio and video capture crystals are not the // same (and therefore drift out of sync after a few minutes). VIDEO(0) // means the audio sample rate is changed before written out gcap.iMasterStream = GetProfileInt(TEXT("annie"), TEXT("MasterStream"), 1); // get the frame rate from win.ini before making the graph gcap.fUseFrameRate = GetProfileInt(TEXT("annie"), TEXT("UseFrameRate"), 1); int units_per_frame = GetProfileInt(TEXT("annie"), TEXT("FrameRate"), 666667); // 15fps gcap.FrameRate = 10000000. / units_per_frame; gcap.FrameRate = (int)(gcap.FrameRate * 100) / 100.; // reasonable default if(gcap.FrameRate <= 0.) gcap.FrameRate = 15.0; gcap.fUseTimeLimit = GetProfileInt(TEXT("annie"), TEXT("UseTimeLimit"), 0); gcap.dwTimeLimit = GetProfileInt(TEXT("annie"), TEXT("TimeLimit"), 0); // Instantiate the capture filters we need to do the menu items. // This will start previewing, if desired // // Make these the official devices we're using ChooseDevices(szVideoDisplayName, szAudioDisplayName); // Register for device add/remove notifications DEV_BROADCAST_DEVICEINTERFACE filterData; ZeroMemory(&filterData, sizeof(DEV_BROADCAST_DEVICEINTERFACE)); filterData.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE); filterData.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; filterData.dbcc_classguid = AM_KSCATEGORY_CAPTURE; gpUnregisterDeviceNotification = NULL; gpRegisterDeviceNotification = NULL; // dynload device removal APIs { HMODULE hmodUser = GetModuleHandle(TEXT("user32.dll")); ASSERT(hmodUser); // we link to user32 gpUnregisterDeviceNotification = (PUnregisterDeviceNotification) GetProcAddress(hmodUser, "UnregisterDeviceNotification"); // m_pRegisterDeviceNotification is prototyped differently in unicode gpRegisterDeviceNotification = (PRegisterDeviceNotification) GetProcAddress(hmodUser, "RegisterDeviceNotificationW" ); // failures expected on older platforms. ASSERT(gpRegisterDeviceNotification && gpUnregisterDeviceNotification || !gpRegisterDeviceNotification && !gpUnregisterDeviceNotification); } ghDevNotify = NULL; if(gpRegisterDeviceNotification) { ghDevNotify = gpRegisterDeviceNotification(ghwndApp, &filterData, DEVICE_NOTIFY_WINDOW_HANDLE); ASSERT(ghDevNotify != NULL); } SetAppCaption(); return TRUE; } void IMonRelease(IMoniker *&pm) { if(pm) { pm->Release(); pm = 0; } } /*----------------------------------------------------------------------------*\ | WinMain( hInst, hPrev, lpszCmdLine, cmdShow ) | | | | Description: | | The main procedure for the App. After initializing, it just goes | | into a message-processing loop until it gets a WM_QUIT message | | (meaning the app was closed). | | | | Arguments: | | hInst instance handle of this instance of the app | | hPrev instance handle of previous instance, NULL if first | | szCmdLine ->null-terminated command line | | cmdShow specifies how the window is initially displayed | | | | Returns: | | The exit code as specified in the WM_QUIT message. | | | \*----------------------------------------------------------------------------*/ int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw) { MSG msg; /* Call initialization procedure */ if(!AppInit(hInst,hPrev,sw)) return FALSE; /* * Polling messages from event queue */ for(;;) { while(PeekMessage(&msg, NULL, 0, 0,PM_REMOVE)) { if(msg.message == WM_QUIT) break; // Leave the PeekMessage while() loop if(TranslateAccelerator(ghwndApp, ghAccel, &msg)) continue; TranslateMessage(&msg); DispatchMessage(&msg); } if(msg.message == WM_QUIT) break; // Leave the for() loop WaitMessage(); } // Reached on WM_QUIT message CoUninitialize(); return ((int) msg.wParam); } /*----------------------------------------------------------------------------*\ | AppWndProc( hwnd, uiMessage, wParam, lParam ) | | | | Description: | | The window proc for the app's main (tiled) window. This processes all | | of the parent window's messages. | | | | Arguments: | | hwnd window handle for the window | | msg message number | | wParam message-dependent | | lParam message-dependent | | | | Returns: | | 0 if processed, nonzero if ignored | | | \*----------------------------------------------------------------------------*/ LONG WINAPI AppWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; HDC hdc; RECT rc; int cxBorder, cyBorder, cy; switch(msg) { case WM_CREATE: break; case WM_COMMAND: return AppCommand(hwnd,msg,wParam,lParam); case WM_INITMENU: // we can start capture if not capturing already EnableMenuItem((HMENU)wParam, MENU_START_CAP, (!gcap.fCapturing) ? MF_ENABLED : MF_GRAYED); // we can stop capture if it's currently capturing EnableMenuItem((HMENU)wParam, MENU_STOP_CAP, (gcap.fCapturing) ? MF_ENABLED : MF_GRAYED); // We can bring up a dialog if the graph is stopped EnableMenuItem((HMENU)wParam, MENU_DIALOG0, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_DIALOG1, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_DIALOG2, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_DIALOG3, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_DIALOG4, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_DIALOG5, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_DIALOG6, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_DIALOG7, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_DIALOG8, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_DIALOG9, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_DIALOGA, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_DIALOGB, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_DIALOGC, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_DIALOGD, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_DIALOGE, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_DIALOGF, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); // toggles capturing audio or not - can't be capturing right now // and we must have an audio capture device created EnableMenuItem((HMENU)wParam, MENU_CAP_AUDIO, (!gcap.fCapturing && gcap.pACap) ? MF_ENABLED : MF_GRAYED); // are we capturing audio? CheckMenuItem((HMENU)wParam, MENU_CAP_AUDIO, (gcap.fCapAudio) ? MF_CHECKED : MF_UNCHECKED); // are we doing closed captioning? CheckMenuItem((HMENU)wParam, MENU_CAP_CC, (gcap.fCapCC) ? MF_CHECKED : MF_UNCHECKED); EnableMenuItem((HMENU)wParam, MENU_CAP_CC, (gcap.fCCAvail) ? MF_ENABLED : MF_GRAYED); // change audio formats when not capturing EnableMenuItem((HMENU)wParam, MENU_AUDIOFORMAT, (gcap.fCapAudio && !gcap.fCapturing) ? MF_ENABLED : MF_GRAYED); // change frame rate when not capturing, and only if the video // filter captures a VIDEOINFO type format EnableMenuItem((HMENU)wParam, MENU_FRAMERATE, (!gcap.fCapturing && gcap.fCapAudioIsRelevant) ? MF_ENABLED : MF_GRAYED); // change time limit when not capturing EnableMenuItem((HMENU)wParam, MENU_TIMELIMIT, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); // change capture file name when not capturing EnableMenuItem((HMENU)wParam, MENU_SET_CAP_FILE, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); // pre-allocate capture file when not capturing EnableMenuItem((HMENU)wParam, MENU_ALLOC_CAP_FILE, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); // can save capture file when not capturing EnableMenuItem((HMENU)wParam, MENU_SAVE_CAP_FILE, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); // do we want preview? CheckMenuItem((HMENU)wParam, MENU_PREVIEW, (gcap.fWantPreview) ? MF_CHECKED : MF_UNCHECKED); // can toggle preview if not capturing EnableMenuItem((HMENU)wParam, MENU_PREVIEW, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); // MPEG2 enabled ? CheckMenuItem((HMENU)wParam, MENU_MPEG2, (gcap.fMPEG2) ? MF_CHECKED : MF_UNCHECKED); // can toggle MPEG2 if not capturing EnableMenuItem((HMENU)wParam, MENU_MPEG2, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); // which is the master stream? Not applicable unless we're also // capturing audio EnableMenuItem((HMENU)wParam, MENU_NOMASTER, (!gcap.fCapturing && gcap.fCapAudio) ? MF_ENABLED : MF_GRAYED); CheckMenuItem((HMENU)wParam, MENU_NOMASTER, (gcap.iMasterStream == -1) ? MF_CHECKED : MF_UNCHECKED); EnableMenuItem((HMENU)wParam, MENU_AUDIOMASTER, (!gcap.fCapturing && gcap.fCapAudio) ? MF_ENABLED : MF_GRAYED); CheckMenuItem((HMENU)wParam, MENU_AUDIOMASTER, (gcap.iMasterStream == 1) ? MF_CHECKED : MF_UNCHECKED); EnableMenuItem((HMENU)wParam, MENU_VIDEOMASTER, (!gcap.fCapturing && gcap.fCapAudio) ? MF_ENABLED : MF_GRAYED); CheckMenuItem((HMENU)wParam, MENU_VIDEOMASTER, (gcap.iMasterStream == 0) ? MF_CHECKED : MF_UNCHECKED); // can't select a new capture device when capturing EnableMenuItem((HMENU)wParam, MENU_VDEVICE0, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_VDEVICE1, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_VDEVICE2, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_VDEVICE3, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_VDEVICE4, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_VDEVICE5, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_VDEVICE6, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_VDEVICE7, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_VDEVICE8, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_VDEVICE9, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_ADEVICE0, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_ADEVICE1, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_ADEVICE2, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_ADEVICE3, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_ADEVICE4, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_ADEVICE5, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_ADEVICE6, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_ADEVICE7, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_ADEVICE8, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); EnableMenuItem((HMENU)wParam, MENU_ADEVICE9, !gcap.fCapturing ? MF_ENABLED : MF_GRAYED); break; case WM_INITMENUPOPUP: if(GetSubMenu(GetMenu(ghwndApp), 1) == (HMENU)wParam) { AddDevicesToMenu(); } break; // // We're out of here! // case WM_DESTROY: IMonRelease(gcap.pmVideo); IMonRelease(gcap.pmAudio); { for(int i = 0; i < NUMELMS(gcap.rgpmVideoMenu); i++) { IMonRelease(gcap.rgpmVideoMenu[i]); } for(int i = 0; i < NUMELMS(gcap.rgpmAudioMenu); i++) { IMonRelease(gcap.rgpmAudioMenu[i]); } } PostQuitMessage(0); break; case WM_CLOSE: OnClose(); break; case WM_ENDSESSION: if(wParam || (lParam & ENDSESSION_LOGOFF)) { OnClose(); } break; case WM_ERASEBKGND: break; // ESC will stop capture case WM_KEYDOWN: if((GetAsyncKeyState(VK_ESCAPE) & 0x01) && gcap.fCapturing) { StopCapture(); if(gcap.fWantPreview) { BuildPreviewGraph(); StartPreview(); } } break; case WM_PAINT: hdc = BeginPaint(hwnd,&ps); // nothing to do EndPaint(hwnd,&ps); break; case WM_TIMER: // update our status bar with #captured, #dropped // if we've stopped capturing, don't do it anymore. Some WM_TIMER // messages may come late, after we've destroyed the graph and // we'll get invalid numbers. if(gcap.fCapturing) UpdateStatus(FALSE); // is our time limit up? if(gcap.fUseTimeLimit) { if((timeGetTime() - gcap.lCapStartTime) / 1000 >= gcap.dwTimeLimit) { StopCapture(); if(gcap.fWantPreview) { BuildPreviewGraph(); StartPreview(); } } } break; case WM_SIZE: // make the preview window fit inside our window, taking up // all of our client area except for the status window at the // bottom GetClientRect(ghwndApp, &rc); cxBorder = GetSystemMetrics(SM_CXBORDER); cyBorder = GetSystemMetrics(SM_CYBORDER); cy = statusGetHeight() + cyBorder; MoveWindow(ghwndStatus, -cxBorder, rc.bottom - cy, rc.right + (2 * cxBorder), cy + cyBorder, TRUE); rc.bottom -= cy; // this is the video renderer window showing the preview if(gcap.pVW) gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); break; case WM_FGNOTIFY: // uh-oh, something went wrong while capturing - the filtergraph // will send us events like EC_COMPLETE, EC_USERABORT and the one // we care about, EC_ERRORABORT. if(gcap.pME) { LONG event; LONG_PTR l1, l2; HRESULT hrAbort = S_OK; BOOL bAbort = FALSE; while(gcap.pME->GetEvent(&event, &l1, &l2, 0) == S_OK) { gcap.pME->FreeEventParams(event, l1, l2); if(event == EC_ERRORABORT) { StopCapture(); bAbort = TRUE; hrAbort = static_cast(l1); continue; } else if(event == EC_DEVICE_LOST) { // Check if we have lost a capture filter being used. // lParam2 of EC_DEVICE_LOST event == 1 indicates device added // == 0 indicates device removed if(l2 == 0) { IBaseFilter *pf; IUnknown *punk = (IUnknown *) l1; if(S_OK == punk->QueryInterface(IID_IBaseFilter, (void **) &pf)) { if(AreComObjectsEqual(gcap.pVCap, pf)) { pf->Release(); bAbort = FALSE; StopCapture(); TCHAR szError[100]; HRESULT hr = StringCchCopy(szError, 100, TEXT("Stopping Capture (Device Lost). Select New Capture Device\0")); ErrMsg(szError); break; } pf->Release(); } } } } // end while if(bAbort) { if(gcap.fWantPreview) { BuildPreviewGraph(); StartPreview(); } TCHAR szError[100]; HRESULT hr = StringCchPrintf(szError, 100, TEXT("ERROR during capture, error code=%08x\0"), hrAbort); ErrMsg(szError); } } break; case WM_DEVICECHANGE: // We are interested in only device arrival & removal events if(DBT_DEVICEARRIVAL != wParam && DBT_DEVICEREMOVECOMPLETE != wParam) break; PDEV_BROADCAST_HDR pdbh = (PDEV_BROADCAST_HDR) lParam; if(pdbh->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE) { break; } PDEV_BROADCAST_DEVICEINTERFACE pdbi = (PDEV_BROADCAST_DEVICEINTERFACE) lParam; // Check for capture devices. if(pdbi->dbcc_classguid != AM_KSCATEGORY_CAPTURE) { break; } // Check for device arrival/removal. if(DBT_DEVICEARRIVAL == wParam || DBT_DEVICEREMOVECOMPLETE == wParam) { gcap.fDeviceMenuPopulated = false; } break; } return (LONG) DefWindowProc(hwnd,msg,wParam,lParam); } // Make a graph builder object we can use for capture graph building // BOOL MakeBuilder() { // we have one already if(gcap.pBuilder) return TRUE; gcap.pBuilder = new ISampleCaptureGraphBuilder( ); if( NULL == gcap.pBuilder ) { return FALSE; } return TRUE; } // Make a graph object we can use for capture graph building // BOOL MakeGraph() { // we have one already if(gcap.pFg) return TRUE; HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (LPVOID *)&gcap.pFg); return (hr == NOERROR) ? TRUE : FALSE; } // make sure the preview window inside our window is as big as the // dimensions of captured video, or some capture cards won't show a preview. // (Also, it helps people tell what size video they're capturing) // We will resize our app's window big enough so that once the status bar // is positioned at the bottom there will be enough room for the preview // window to be w x h // int gnRecurse = 0; void ResizeWindow(int w, int h) { RECT rcW, rcC; int xExtra, yExtra; int cyBorder = GetSystemMetrics(SM_CYBORDER); gnRecurse++; GetWindowRect(ghwndApp, &rcW); GetClientRect(ghwndApp, &rcC); xExtra = rcW.right - rcW.left - rcC.right; yExtra = rcW.bottom - rcW.top - rcC.bottom + cyBorder + statusGetHeight(); rcC.right = w; rcC.bottom = h; SetWindowPos(ghwndApp, NULL, 0, 0, rcC.right + xExtra, rcC.bottom + yExtra, SWP_NOZORDER | SWP_NOMOVE); // we may need to recurse once. But more than that means the window cannot // be made the size we want, trying will just stack fault. // if(gnRecurse == 1 && ((rcC.right + xExtra != rcW.right - rcW.left && w > GetSystemMetrics(SM_CXMIN)) || (rcC.bottom + yExtra != rcW.bottom - rcW.top))) ResizeWindow(w,h); gnRecurse--; } // Tear down everything downstream of a given filter void RemoveDownstream(IBaseFilter *pf) { IPin *pP=0, *pTo=0; ULONG u; IEnumPins *pins = NULL; PIN_INFO pininfo; if (!pf) return; HRESULT hr = pf->EnumPins(&pins); pins->Reset(); while(hr == NOERROR) { hr = pins->Next(1, &pP, &u); if(hr == S_OK && pP) { pP->ConnectedTo(&pTo); if(pTo) { hr = pTo->QueryPinInfo(&pininfo); if(hr == NOERROR) { if(pininfo.dir == PINDIR_INPUT) { RemoveDownstream(pininfo.pFilter); gcap.pFg->Disconnect(pTo); gcap.pFg->Disconnect(pP); gcap.pFg->RemoveFilter(pininfo.pFilter); } pininfo.pFilter->Release(); } pTo->Release(); } pP->Release(); } } if(pins) pins->Release(); } // Tear down everything downstream of the capture filters, so we can build // a different capture graph. Notice that we never destroy the capture filters // and WDM filters upstream of them, because then all the capture settings // we've set would be lost. // void TearDownGraph() { SAFE_RELEASE(gcap.pSink); SAFE_RELEASE(gcap.pConfigAviMux); SAFE_RELEASE(gcap.pRender); SAFE_RELEASE(gcap.pME); SAFE_RELEASE(gcap.pDF); if(gcap.pVW) { // stop drawing in our window, or we may get wierd repaint effects gcap.pVW->put_Owner(NULL); gcap.pVW->put_Visible(OAFALSE); gcap.pVW->Release(); gcap.pVW = NULL; } // destroy the graph downstream of our capture filters if(gcap.pVCap) RemoveDownstream(gcap.pVCap); if(gcap.pACap) RemoveDownstream(gcap.pACap); if(gcap.pVCap) gcap.pBuilder->ReleaseFilters(); // potential debug output - what the graph looks like // if (gcap.pFg) DumpGraph(gcap.pFg, 1); #ifdef REGISTER_FILTERGRAPH // Remove filter graph from the running object table if(g_dwGraphRegister) { RemoveGraphFromRot(g_dwGraphRegister); g_dwGraphRegister = 0; } #endif gcap.fCaptureGraphBuilt = FALSE; gcap.fPreviewGraphBuilt = FALSE; gcap.fPreviewFaked = FALSE; } // create the capture filters of the graph. We need to keep them loaded from // the beginning, so we can set parameters on them and have them remembered // BOOL InitCapFilters() { HRESULT hr=S_OK; BOOL f; gcap.fCCAvail = FALSE; // assume no closed captioning support f = MakeBuilder(); if(!f) { ErrMsg(TEXT("Cannot instantiate graph builder")); return FALSE; } // // First, we need a Video Capture filter, and some interfaces // gcap.pVCap = NULL; if(gcap.pmVideo != 0) { IPropertyBag *pBag; gcap.wachFriendlyName[0] = 0; hr = gcap.pmVideo->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag); if(SUCCEEDED(hr)) { VARIANT var; var.vt = VT_BSTR; hr = pBag->Read(L"FriendlyName", &var, NULL); if(hr == NOERROR) { hr = StringCchCopyW(gcap.wachFriendlyName, sizeof(gcap.wachFriendlyName) / sizeof(gcap.wachFriendlyName[0]), var.bstrVal); SysFreeString(var.bstrVal); } pBag->Release(); } hr = gcap.pmVideo->BindToObject(0, 0, IID_IBaseFilter, (void**)&gcap.pVCap); } if(gcap.pVCap == NULL) { ErrMsg(TEXT("Error %x: Cannot create video capture filter"), hr); goto InitCapFiltersFail; } // // make a filtergraph, give it to the graph builder and put the video // capture filter in the graph // f = MakeGraph(); if(!f) { ErrMsg(TEXT("Cannot instantiate filtergraph")); goto InitCapFiltersFail; } hr = gcap.pBuilder->SetFiltergraph(gcap.pFg); if(hr != NOERROR) { ErrMsg(TEXT("Cannot give graph to builder")); goto InitCapFiltersFail; } // Add the video capture filter to the graph with its friendly name hr = gcap.pFg->AddFilter(gcap.pVCap, gcap.wachFriendlyName); if(hr != NOERROR) { ErrMsg(TEXT("Error %x: Cannot add vidcap to filtergraph"), hr); goto InitCapFiltersFail; } // Calling FindInterface below will result in building the upstream // section of the capture graph (any WDM TVTuners or Crossbars we might // need). // we use this interface to get the name of the driver // Don't worry if it doesn't work: This interface may not be available // until the pin is connected, or it may not be available at all. // (eg: interface may not be available for some DV capture) hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, gcap.pVCap, IID_IAMVideoCompression, (void **)&gcap.pVC); if(hr != S_OK) { hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, gcap.pVCap, IID_IAMVideoCompression, (void **)&gcap.pVC); } // !!! What if this interface isn't supported? // we use this interface to set the frame rate and get the capture size hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, gcap.pVCap, IID_IAMStreamConfig, (void **)&gcap.pVSC); if(hr != NOERROR) { hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, gcap.pVCap, IID_IAMStreamConfig, (void **)&gcap.pVSC); if(hr != NOERROR) { // this means we can't set frame rate (non-DV only) ErrMsg(TEXT("Error %x: Cannot find VCapture:IAMStreamConfig"), hr); } } gcap.fCapAudioIsRelevant = TRUE; AM_MEDIA_TYPE *pmt; // default capture format if(gcap.pVSC && gcap.pVSC->GetFormat(&pmt) == S_OK) { // DV capture does not use a VIDEOINFOHEADER if(pmt->formattype == FORMAT_VideoInfo) { // resize our window to the default capture size ResizeWindow(HEADER(pmt->pbFormat)->biWidth, ABS(HEADER(pmt->pbFormat)->biHeight)); } if(pmt->majortype != MEDIATYPE_Video) { // This capture filter captures something other that pure video. // Maybe it's DV or something? Anyway, chances are we shouldn't // allow capturing audio separately, since our video capture // filter may have audio combined in it already! gcap.fCapAudioIsRelevant = FALSE; gcap.fCapAudio = FALSE; } DeleteMediaType(pmt); } // we use this interface to bring up the 3 dialogs // NOTE: Only the VfW capture filter supports this. This app only brings // up dialogs for legacy VfW capture drivers, since only those have dialogs hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, gcap.pVCap, IID_IAMVfwCaptureDialogs, (void **)&gcap.pDlg); // Use the crossbar class to help us sort out all the possible video inputs // The class needs to be given the capture filters ANALOGVIDEO input pin { IPin *pP = 0; IEnumPins *pins=0; ULONG n; PIN_INFO pinInfo; BOOL Found = FALSE; IKsPropertySet *pKs=0; GUID guid; DWORD dw; BOOL fMatch = FALSE; gcap.pCrossbar = NULL; if(SUCCEEDED(gcap.pVCap->EnumPins(&pins))) { while(!Found && (S_OK == pins->Next(1, &pP, &n))) { if(S_OK == pP->QueryPinInfo(&pinInfo)) { if(pinInfo.dir == PINDIR_INPUT) { // is this pin an ANALOGVIDEOIN input pin? if(pP->QueryInterface(IID_IKsPropertySet, (void **)&pKs) == S_OK) { if(pKs->Get(AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, NULL, 0, &guid, sizeof(GUID), &dw) == S_OK) { if(guid == PIN_CATEGORY_ANALOGVIDEOIN) fMatch = TRUE; } pKs->Release(); } if(fMatch) { HRESULT hrCreate=S_OK; gcap.pCrossbar = new CCrossbar(pP, &hrCreate); if (!gcap.pCrossbar || FAILED(hrCreate)) break; hr = gcap.pCrossbar->GetInputCount(&gcap.NumberOfVideoInputs); Found = TRUE; } } pinInfo.pFilter->Release(); } pP->Release(); } pins->Release(); } } // there's no point making an audio capture filter if(gcap.fCapAudioIsRelevant == FALSE) goto SkipAudio; // create the audio capture filter, even if we are not capturing audio right // now, so we have all the filters around all the time. // // We want an audio capture filter and some interfaces // if(gcap.pmAudio == 0) { // there are no audio capture devices. We'll only allow video capture gcap.fCapAudio = FALSE; goto SkipAudio; } gcap.pACap = NULL; hr = gcap.pmAudio->BindToObject(0, 0, IID_IBaseFilter, (void**)&gcap.pACap); if(gcap.pACap == NULL) { // there are no audio capture devices. We'll only allow video capture gcap.fCapAudio = FALSE; ErrMsg(TEXT("Cannot create audio capture filter")); goto SkipAudio; } // // put the audio capture filter in the graph // { WCHAR wachAudioFriendlyName[256]; IPropertyBag *pBag; wachAudioFriendlyName[0] = 0; // Read the friendly name of the filter to assist with remote graph // viewing through GraphEdit hr = gcap.pmAudio->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag); if(SUCCEEDED(hr)) { VARIANT var; var.vt = VT_BSTR; hr = pBag->Read(L"FriendlyName", &var, NULL); if(hr == NOERROR) { hr = StringCchCopyW(wachAudioFriendlyName, 256, var.bstrVal); SysFreeString(var.bstrVal); } pBag->Release(); } // We'll need this in the graph to get audio property pages hr = gcap.pFg->AddFilter(gcap.pACap, wachAudioFriendlyName); if(hr != NOERROR) { ErrMsg(TEXT("Error %x: Cannot add audio capture filter to filtergraph"), hr); goto InitCapFiltersFail; } } // Calling FindInterface below will result in building the upstream // section of the capture graph (any WDM TVAudio's or Crossbars we might // need). // !!! What if this interface isn't supported? // we use this interface to set the captured wave format hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio, gcap.pACap, IID_IAMStreamConfig, (void **)&gcap.pASC); if(hr != NOERROR) { ErrMsg(TEXT("Cannot find ACapture:IAMStreamConfig")); } SkipAudio: // Can this filter do closed captioning? IPin *pPin; hr = gcap.pBuilder->FindPin(gcap.pVCap, PINDIR_OUTPUT, &PIN_CATEGORY_VBI, NULL, FALSE, 0, &pPin); if(hr != S_OK) hr = gcap.pBuilder->FindPin(gcap.pVCap, PINDIR_OUTPUT, &PIN_CATEGORY_CC, NULL, FALSE, 0, &pPin); if(hr == S_OK) { pPin->Release(); gcap.fCCAvail = TRUE; } else { gcap.fCapCC = FALSE; // can't capture it, then } // potential debug output - what the graph looks like // DumpGraph(gcap.pFg, 1); return TRUE; InitCapFiltersFail: FreeCapFilters(); return FALSE; } // all done with the capture filters and the graph builder // void FreeCapFilters() { SAFE_RELEASE(gcap.pFg); if( gcap.pBuilder ) { delete gcap.pBuilder; gcap.pBuilder = NULL; } SAFE_RELEASE(gcap.pVCap); SAFE_RELEASE(gcap.pACap); SAFE_RELEASE(gcap.pASC); SAFE_RELEASE(gcap.pVSC); SAFE_RELEASE(gcap.pVC); SAFE_RELEASE(gcap.pDlg); if(gcap.pCrossbar) { delete gcap.pCrossbar; gcap.pCrossbar = NULL; } } // build the capture graph // BOOL BuildCaptureGraph() { int cy, cyBorder; HRESULT hr; BOOL f; AM_MEDIA_TYPE *pmt=0; // we have one already if(gcap.fCaptureGraphBuilt) return TRUE; // No rebuilding while we're running if(gcap.fCapturing || gcap.fPreviewing) return FALSE; // We don't have the necessary capture filters if(gcap.pVCap == NULL) return FALSE; if(gcap.pACap == NULL && gcap.fCapAudio) return FALSE; // no capture file name yet... we need one first if(gcap.wszCaptureFile[0] == 0) { f = SetCaptureFile(ghwndApp); if(!f) return f; } // we already have another graph built... tear down the old one if(gcap.fPreviewGraphBuilt) TearDownGraph(); // // We need a rendering section that will write the capture file out in AVI // file format // GUID guid; if( gcap.fMPEG2 ) { guid = MEDIASUBTYPE_Mpeg2; } else { guid = MEDIASUBTYPE_Avi; } hr = gcap.pBuilder->SetOutputFileName(&guid, gcap.wszCaptureFile, &gcap.pRender, &gcap.pSink); if(hr != NOERROR) { ErrMsg(TEXT("Cannot set output file")); goto SetupCaptureFail; } // Now tell the AVIMUX to write out AVI files that old apps can read properly. // If we don't, most apps won't be able to tell where the keyframes are, // slowing down editing considerably // Doing this will cause one seek (over the area the index will go) when // you capture past 1 Gig, but that's no big deal. // NOTE: This is on by default, so it's not necessary to turn it on // Also, set the proper MASTER STREAM if( !gcap.fMPEG2 ) { hr = gcap.pRender->QueryInterface(IID_IConfigAviMux, (void **)&gcap.pConfigAviMux); if(hr == NOERROR && gcap.pConfigAviMux) { gcap.pConfigAviMux->SetOutputCompatibilityIndex(TRUE); if(gcap.fCapAudio) { hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream); if(hr != NOERROR) ErrMsg(TEXT("SetMasterStream failed!")); } } } // // Render the video capture and preview pins - even if the capture filter only // has a capture pin (and no preview pin) this should work... because the // capture graph builder will use a smart tee filter to provide both capture // and preview. We don't have to worry. It will just work. // // NOTE that we try to render the interleaved pin before the video pin, because // if BOTH exist, it's a DV filter and the only way to get the audio is to use // the interleaved pin. Using the Video pin on a DV filter is only useful if // you don't want the audio. if( !gcap.fMPEG2 ) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, gcap.pVCap, NULL, gcap.pRender); if(hr != NOERROR) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, gcap.pVCap, NULL, gcap.pRender); if(hr != NOERROR) { ErrMsg(TEXT("Cannot render video capture stream")); goto SetupCaptureFail; } } if(gcap.fWantPreview) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Interleaved, gcap.pVCap, NULL, NULL); if(hr == VFW_S_NOPREVIEWPIN) { // preview was faked up for us using the (only) capture pin gcap.fPreviewFaked = TRUE; } else if(hr != S_OK) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, gcap.pVCap, NULL, NULL); if(hr == VFW_S_NOPREVIEWPIN) { // preview was faked up for us using the (only) capture pin gcap.fPreviewFaked = TRUE; } else if(hr != S_OK) { ErrMsg(TEXT("Cannot render video preview stream")); goto SetupCaptureFail; } } } } else { SmartPtr< IBaseFilter > sink; if( &gcap.pSink ) { gcap.pSink->QueryInterface( IID_IBaseFilter, reinterpret_cast( &sink ) ); } hr = gcap.pBuilder->RenderStream(NULL, &MEDIATYPE_Stream, gcap.pVCap, NULL, sink); } // // Render the audio capture pin? // if(!gcap.fMPEG2 && gcap.fCapAudio) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio, gcap.pACap, NULL, gcap.pRender); if(hr != NOERROR) { ErrMsg(TEXT("Cannot render audio capture stream")); goto SetupCaptureFail; } } // // Render the closed captioning pin? It could be a CC or a VBI category pin, // depending on the capture driver // if(!gcap.fMPEG2 && gcap.fCapCC) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CC, NULL, gcap.pVCap, NULL, gcap.pRender); if(hr != NOERROR) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_VBI, NULL, gcap.pVCap, NULL, gcap.pRender); if(hr != NOERROR) { ErrMsg(TEXT("Cannot render closed captioning")); // so what? goto SetupCaptureFail; } } // To preview and capture VBI at the same time, we can call this twice if(gcap.fWantPreview) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_VBI, NULL, gcap.pVCap, NULL, NULL); } } // // Get the preview window to be a child of our app's window // // This will find the IVideoWindow interface on the renderer. It is // important to ask the filtergraph for this interface... do NOT use // ICaptureGraphBuilder2::FindInterface, because the filtergraph needs to // know we own the window so it can give us display changed messages, etc. if(!gcap.fMPEG2 && gcap.fWantPreview) { hr = gcap.pFg->QueryInterface(IID_IVideoWindow, (void **)&gcap.pVW); if(hr != NOERROR && gcap.fWantPreview) { ErrMsg(TEXT("This graph cannot preview")); } else if(hr == NOERROR) { RECT rc; gcap.pVW->put_Owner((OAHWND)ghwndApp); // We own the window now gcap.pVW->put_WindowStyle(WS_CHILD); // you are now a child // give the preview window all our space but where the status bar is GetClientRect(ghwndApp, &rc); cyBorder = GetSystemMetrics(SM_CYBORDER); cy = statusGetHeight() + cyBorder; rc.bottom -= cy; gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); // be this big gcap.pVW->put_Visible(OATRUE); } } // now tell it what frame rate to capture at. Just find the format it // is capturing with, and leave everything alone but change the frame rate if( !gcap.fMPEG2 ) { hr = gcap.fUseFrameRate ? E_FAIL : NOERROR; if(gcap.pVSC && gcap.fUseFrameRate) { hr = gcap.pVSC->GetFormat(&pmt); // DV capture does not use a VIDEOINFOHEADER if(hr == NOERROR) { if(pmt->formattype == FORMAT_VideoInfo) { VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)pmt->pbFormat; pvi->AvgTimePerFrame = (LONGLONG)(10000000 / gcap.FrameRate); hr = gcap.pVSC->SetFormat(pmt); } DeleteMediaType(pmt); } } if(hr != NOERROR) ErrMsg(TEXT("Cannot set frame rate for capture")); } // now ask the filtergraph to tell us when something is completed or aborted // (EC_COMPLETE, EC_USERABORT, EC_ERRORABORT). This is how we will find out // if the disk gets full while capturing hr = gcap.pFg->QueryInterface(IID_IMediaEventEx, (void **)&gcap.pME); if(hr == NOERROR) { gcap.pME->SetNotifyWindow((OAHWND)ghwndApp, WM_FGNOTIFY, 0); } // potential debug output - what the graph looks like // DumpGraph(gcap.pFg, 1); // Add our graph to the running object table, which will allow // the GraphEdit application to "spy" on our graph #ifdef REGISTER_FILTERGRAPH hr = AddGraphToRot(gcap.pFg, &g_dwGraphRegister); if(FAILED(hr)) { ErrMsg(TEXT("Failed to register filter graph with ROT! hr=0x%x"), hr); g_dwGraphRegister = 0; } #endif // All done. gcap.fCaptureGraphBuilt = TRUE; return TRUE; SetupCaptureFail: TearDownGraph(); return FALSE; } // build the preview graph! // // !!! PLEASE NOTE !!! Some new WDM devices have totally separate capture // and preview settings. An application that wishes to preview and then // capture may have to set the preview pin format using IAMStreamConfig on the // preview pin, and then again on the capture pin to capture with that format. // In this sample app, there is a separate page to set the settings on the // capture pin and one for the preview pin. To avoid the user // having to enter the same settings in 2 dialog boxes, an app can have its own // UI for choosing a format (the possible formats can be enumerated using // IAMStreamConfig) and then the app can programmatically call IAMStreamConfig // to set the format on both pins. // BOOL BuildPreviewGraph() { int cy, cyBorder; HRESULT hr; AM_MEDIA_TYPE *pmt; // we have one already if(gcap.fPreviewGraphBuilt) return TRUE; // No rebuilding while we're running if(gcap.fCapturing || gcap.fPreviewing) return FALSE; // We don't have the necessary capture filters if(gcap.pVCap == NULL) return FALSE; if(gcap.pACap == NULL && gcap.fCapAudio) return FALSE; // we already have another graph built... tear down the old one if(gcap.fCaptureGraphBuilt) TearDownGraph(); // // Render the preview pin - even if there is not preview pin, the capture // graph builder will use a smart tee filter and provide a preview. // // !!! what about latency/buffer issues? // NOTE that we try to render the interleaved pin before the video pin, because // if BOTH exist, it's a DV filter and the only way to get the audio is to use // the interleaved pin. Using the Video pin on a DV filter is only useful if // you don't want the audio. if( gcap.fMPEG2 ) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Stream, gcap.pVCap, NULL, NULL); if( FAILED( hr ) ) { ErrMsg(TEXT("Cannot build MPEG2 preview graph!")); } } else { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Interleaved, gcap.pVCap, NULL, NULL); if(hr == VFW_S_NOPREVIEWPIN) { // preview was faked up for us using the (only) capture pin gcap.fPreviewFaked = TRUE; } else if(hr != S_OK) { // maybe it's DV? hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, gcap.pVCap, NULL, NULL); if(hr == VFW_S_NOPREVIEWPIN) { // preview was faked up for us using the (only) capture pin gcap.fPreviewFaked = TRUE; } else if(hr != S_OK) { ErrMsg(TEXT("This graph cannot preview!")); gcap.fPreviewGraphBuilt = FALSE; return FALSE; } } // // Render the closed captioning pin? It could be a CC or a VBI category pin, // depending on the capture driver // if(gcap.fCapCC) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CC, NULL, gcap.pVCap, NULL, NULL); if(hr != NOERROR) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_VBI, NULL, gcap.pVCap, NULL, NULL); if(hr != NOERROR) { ErrMsg(TEXT("Cannot render closed captioning")); } } } } // // Get the preview window to be a child of our app's window // // This will find the IVideoWindow interface on the renderer. It is // important to ask the filtergraph for this interface... do NOT use // ICaptureGraphBuilder2::FindInterface, because the filtergraph needs to // know we own the window so it can give us display changed messages, etc. hr = gcap.pFg->QueryInterface(IID_IVideoWindow, (void **)&gcap.pVW); if(hr != NOERROR) { ErrMsg(TEXT("This graph cannot preview properly")); } else { //Find out if this is a DV stream AM_MEDIA_TYPE * pmtDV; if(gcap.pVSC && SUCCEEDED(gcap.pVSC->GetFormat(&pmtDV))) { if(pmtDV->formattype == FORMAT_DvInfo) { // in this case we want to set the size of the parent window to that of // current DV resolution. // We get that resolution from the IVideoWindow. SmartPtr pBV; // If we got here, gcap.pVW is not NULL ASSERT(gcap.pVW != NULL); hr = gcap.pVW->QueryInterface(IID_IBasicVideo, (void**)&pBV); if(SUCCEEDED(hr)) { HRESULT hr1, hr2; long lWidth, lHeight; hr1 = pBV->get_VideoHeight(&lHeight); hr2 = pBV->get_VideoWidth(&lWidth); if(SUCCEEDED(hr1) && SUCCEEDED(hr2)) { ResizeWindow(lWidth, abs(lHeight)); } } } } RECT rc; gcap.pVW->put_Owner((OAHWND)ghwndApp); // We own the window now gcap.pVW->put_WindowStyle(WS_CHILD); // you are now a child // give the preview window all our space but where the status bar is GetClientRect(ghwndApp, &rc); cyBorder = GetSystemMetrics(SM_CYBORDER); cy = statusGetHeight() + cyBorder; rc.bottom -= cy; gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); // be this big gcap.pVW->put_Visible(OATRUE); } // now tell it what frame rate to capture at. Just find the format it // is capturing with, and leave everything alone but change the frame rate // No big deal if it fails. It's just for preview // !!! Should we then talk to the preview pin? if(gcap.pVSC && gcap.fUseFrameRate) { hr = gcap.pVSC->GetFormat(&pmt); // DV capture does not use a VIDEOINFOHEADER if(hr == NOERROR) { if(pmt->formattype == FORMAT_VideoInfo) { VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)pmt->pbFormat; pvi->AvgTimePerFrame = (LONGLONG)(10000000 / gcap.FrameRate); hr = gcap.pVSC->SetFormat(pmt); if(hr != NOERROR) ErrMsg(TEXT("%x: Cannot set frame rate for preview"), hr); } DeleteMediaType(pmt); } } // make sure we process events while we're previewing! hr = gcap.pFg->QueryInterface(IID_IMediaEventEx, (void **)&gcap.pME); if(hr == NOERROR) { gcap.pME->SetNotifyWindow((OAHWND)ghwndApp, WM_FGNOTIFY, 0); } // potential debug output - what the graph looks like // DumpGraph(gcap.pFg, 1); // Add our graph to the running object table, which will allow // the GraphEdit application to "spy" on our graph #ifdef REGISTER_FILTERGRAPH hr = AddGraphToRot(gcap.pFg, &g_dwGraphRegister); if(FAILED(hr)) { ErrMsg(TEXT("Failed to register filter graph with ROT! hr=0x%x"), hr); g_dwGraphRegister = 0; } #endif // All done. gcap.fPreviewGraphBuilt = TRUE; return TRUE; } // Start previewing // BOOL StartPreview() { // way ahead of you if(gcap.fPreviewing) return TRUE; if(!gcap.fPreviewGraphBuilt) return FALSE; // run the graph IMediaControl *pMC = NULL; HRESULT hr = gcap.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC); if(SUCCEEDED(hr)) { hr = pMC->Run(); if(FAILED(hr)) { // stop parts that ran pMC->Stop(); } pMC->Release(); } if(FAILED(hr)) { ErrMsg(TEXT("Error %x: Cannot run preview graph"), hr); return FALSE; } gcap.fPreviewing = TRUE; return TRUE; } // stop the preview graph // BOOL StopPreview() { // way ahead of you if(!gcap.fPreviewing) { return FALSE; } // stop the graph IMediaControl *pMC = NULL; HRESULT hr = gcap.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC); if(SUCCEEDED(hr)) { hr = pMC->Stop(); pMC->Release(); } if(FAILED(hr)) { ErrMsg(TEXT("Error %x: Cannot stop preview graph"), hr); return FALSE; } gcap.fPreviewing = FALSE; // get rid of menu garbage InvalidateRect(ghwndApp, NULL, TRUE); return TRUE; } // start the capture graph // BOOL StartCapture() { BOOL f, fHasStreamControl; HRESULT hr; // way ahead of you if(gcap.fCapturing) return TRUE; // or we'll get confused if(gcap.fPreviewing) StopPreview(); // or we'll crash if(!gcap.fCaptureGraphBuilt) return FALSE; // This amount will be subtracted from the number of dropped and not // dropped frames reported by the filter. Since we might be having the // filter running while the pin is turned off, we don't want any of the // frame statistics from the time the pin is off interfering with the // statistics we gather while the pin is on gcap.lDroppedBase = 0; gcap.lNotBase = 0; REFERENCE_TIME start = MAXLONGLONG, stop = MAXLONGLONG; // don't capture quite yet... hr = gcap.pBuilder->ControlStream(&PIN_CATEGORY_CAPTURE, NULL, NULL, &start, NULL, 0, 0); // Do we have the ability to control capture and preview separately? fHasStreamControl = SUCCEEDED(hr); // prepare to run the graph IMediaControl *pMC = NULL; hr = gcap.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC); if(FAILED(hr)) { ErrMsg(TEXT("Error %x: Cannot get IMediaControl"), hr); return FALSE; } // If we were able to keep capture off, then we can // run the graph now for frame accurate start later yet still showing a // preview. Otherwise, we can't run the graph yet without capture // starting too, so we'll pause it so the latency between when they // press a key and when capture begins is still small (but they won't have // a preview while they wait to press a key) if(fHasStreamControl) hr = pMC->Run(); else hr = pMC->Pause(); if(FAILED(hr)) { // stop parts that started pMC->Stop(); pMC->Release(); ErrMsg(TEXT("Error %x: Cannot start graph"), hr); return FALSE; } // press a key to start capture f = DoDialog(ghwndApp, IDD_PressAKeyDialog, (DLGPROC)PressAKeyProc, 0); if(!f) { pMC->Stop(); pMC->Release(); if(gcap.fWantPreview) { BuildPreviewGraph(); StartPreview(); } return f; } // Start capture NOW! if(fHasStreamControl) { // we may not have this yet if(!gcap.pDF) { hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, gcap.pVCap, IID_IAMDroppedFrames, (void **)&gcap.pDF); if(hr != NOERROR) hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, gcap.pVCap, IID_IAMDroppedFrames, (void **)&gcap.pDF); } // turn the capture pin on now! hr = gcap.pBuilder->ControlStream(&PIN_CATEGORY_CAPTURE, NULL, NULL, NULL, &stop, 0, 0); // make note of the current dropped frame counts if(gcap.pDF) { gcap.pDF->GetNumDropped(&gcap.lDroppedBase); gcap.pDF->GetNumNotDropped(&gcap.lNotBase); } } else { hr = pMC->Run(); if(FAILED(hr)) { // stop parts that started pMC->Stop(); pMC->Release(); ErrMsg(TEXT("Error %x: Cannot run graph"), hr); return FALSE; } } pMC->Release(); // when did we start capture? gcap.lCapStartTime = timeGetTime(); // update status bar 30 times per second - #captured, #dropped SetTimer(ghwndApp, 1, 33, NULL); gcap.fCapturing = TRUE; return TRUE; } // stop the capture graph // BOOL StopCapture() { // way ahead of you if(!gcap.fCapturing) { return FALSE; } // stop the graph IMediaControl *pMC = NULL; HRESULT hr = gcap.pFg->QueryInterface(IID_IMediaControl, (void **)&pMC); if(SUCCEEDED(hr)) { hr = pMC->Stop(); pMC->Release(); } if(FAILED(hr)) { ErrMsg(TEXT("Error %x: Cannot stop graph"), hr); return FALSE; } // when the graph was stopped gcap.lCapStopTime = timeGetTime(); // no more status bar updates KillTimer(ghwndApp, 1); // one last time for the final count and all the stats UpdateStatus(TRUE); gcap.fCapturing = FALSE; // get rid of menu garbage InvalidateRect(ghwndApp, NULL, TRUE); return TRUE; } // Let's talk about UI for a minute. There are many programmatic interfaces // you can use to program a capture filter or related filter to capture the // way you want it to.... eg: IAMStreamConfig, IAMVideoCompression, // IAMCrossbar, IAMTVTuner, IAMTVAudio, IAMAnalogVideoDecoder, IAMCameraControl, // IAMVideoProcAmp, etc. // // But you probably want some UI to let the user play with all these settings. // For new WDM-style capture devices, we offer some default UI you can use. // The code below shows how to bring up all of the dialog boxes supported // by any capture filters. // // The following code shows you how you can bring up all of the // dialogs supported by a particular object at once on a big page with lots // of thumb tabs. You do this by starting with an interface on the object that // you want, and using ISpecifyPropertyPages to get the whole list, and // OleCreatePropertyFrame to bring them all up. This way you will get custom // property pages a filter has, too, that are not one of the standard pages that // you know about. There are at least 9 objects that may have property pages. // Your app already has 2 of the object pointers, the video capture filter and // the audio capture filter (let's call them pVCap and pACap) // 1. The video capture filter - pVCap // 2. The video capture filter's capture pin - get this by calling // FindInterface(&PIN_CATEGORY_CAPTURE, pVCap, IID_IPin, &pX); // 3. The video capture filter's preview pin - get this by calling // FindInterface(&PIN_CATEGORY_PREVIEW, pVCap, IID_IPin, &pX); // 4. The audio capture filter - pACap // 5. The audio capture filter's capture pin - get this by calling // FindInterface(&PIN_CATEGORY_CAPTURE, pACap, IID_IPin, &pX); // 6. The crossbar connected to the video capture filter - get this by calling // FindInterface(NULL, pVCap, IID_IAMCrossbar, &pX); // 7. There is a possible second crossbar to control audio - get this by // looking upstream of the first crossbar like this: // FindInterface(&LOOK_UPSTREAM_ONLY, pX, IID_IAMCrossbar, &pX2); // 8. The TV Tuner connected to the video capture filter - get this by calling // FindInterface(NULL, pVCap, IID_IAMTVTuner, &pX); // 9. The TV Audio connected to the audio capture filter - get this by calling // FindInterface(NULL, pACap, IID_IAMTVAudio, &pX); // 10. We have a helper class, CCrossbar, which makes the crossbar issue less // confusing. In fact, although not supported here, there may be more than // two crossbars, arranged in many different ways. An application may not // wish to have separate dialogs for each crossbar, but instead hide the // complexity and simply offer the user a list of inputs that can be chosen. // This list represents all the unique inputs from all the crossbars. // The crossbar helper class does this and offers that list as #10. It is // expected that an application will either provide the crossbar dialogs // above (#6 and #7) OR provide the input list (this #10), but not both. // That would be confusing because if you select an input using dialog 6 or // 7 the input list here in #10 won't know about your choice. // // Your last choice for UI is to make your own pages, and use the results of // your custom page to call the interfaces programmatically. void MakeMenuOptions() { HRESULT hr; HMENU hMenuSub = GetSubMenu(GetMenu(ghwndApp), 2); // Options menu // remove any old choices from the last device RemoveMenu(hMenuSub, 4, MF_BYPOSITION); RemoveMenu(hMenuSub, 4, MF_BYPOSITION); RemoveMenu(hMenuSub, 4, MF_BYPOSITION); RemoveMenu(hMenuSub, 4, MF_BYPOSITION); RemoveMenu(hMenuSub, 4, MF_BYPOSITION); RemoveMenu(hMenuSub, 4, MF_BYPOSITION); RemoveMenu(hMenuSub, 4, MF_BYPOSITION); RemoveMenu(hMenuSub, 4, MF_BYPOSITION); RemoveMenu(hMenuSub, 4, MF_BYPOSITION); RemoveMenu(hMenuSub, 4, MF_BYPOSITION); int zz = 0; gcap.iFormatDialogPos = -1; gcap.iSourceDialogPos = -1; gcap.iDisplayDialogPos = -1; gcap.iVCapDialogPos = -1; gcap.iVCrossbarDialogPos = -1; gcap.iTVTunerDialogPos = -1; gcap.iACapDialogPos = -1; gcap.iACrossbarDialogPos = -1; gcap.iTVAudioDialogPos = -1; gcap.iVCapCapturePinDialogPos = -1; gcap.iVCapPreviewPinDialogPos = -1; gcap.iACapCapturePinDialogPos = -1; // If this device supports the old legacy UI dialogs, offer them if(gcap.pDlg) { hr = gcap.pDlg->HasDialog(VfwCaptureDialog_Format); if(SUCCEEDED(hr) && S_FALSE != hr) { AppendMenu(hMenuSub, MF_STRING, MENU_DIALOG0 + zz, TEXT("Video Format...")); gcap.iFormatDialogPos = zz++; } hr = gcap.pDlg->HasDialog(VfwCaptureDialog_Source); if(SUCCEEDED(hr) && S_FALSE != hr) { AppendMenu(hMenuSub, MF_STRING, MENU_DIALOG0 + zz, TEXT("Video Source...")); gcap.iSourceDialogPos = zz++; } hr = gcap.pDlg->HasDialog(VfwCaptureDialog_Display); if(SUCCEEDED(hr) && S_FALSE != hr) { AppendMenu(hMenuSub, MF_STRING, MENU_DIALOG0 + zz, TEXT("Video Display...")); gcap.iDisplayDialogPos = zz++; } } // Also check the audio capture filter at this point, since even non wdm devices // may support an IAMAudioInputMixer property page (we'll also get any wdm filter // properties here as well). We'll get any audio capture pin property pages just // a bit later. if(gcap.pACap != NULL) { ISpecifyPropertyPages *pSpec; CAUUID cauuid; hr = gcap.pACap->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(SUCCEEDED(hr)) { hr = pSpec->GetPages(&cauuid); if(SUCCEEDED(hr) && cauuid.cElems > 0) { AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("Audio Capture Filter...")); gcap.iACapDialogPos = zz++; CoTaskMemFree(cauuid.pElems); } pSpec->Release(); } } // don't bother looking for new property pages if the old ones are supported // or if we don't have a capture filter if(gcap.pVCap == NULL || gcap.iFormatDialogPos != -1) return; // New WDM devices support new UI and new interfaces. // Your app can use some default property // pages for UI if you'd like (like we do here) or if you don't like our // dialog boxes, feel free to make your own and programmatically set // the capture options through interfaces like IAMCrossbar, IAMCameraControl // etc. // There are 9 objects that might support property pages. // Let's go through them. ISpecifyPropertyPages *pSpec; CAUUID cauuid; // 1. the video capture filter itself hr = gcap.pVCap->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(SUCCEEDED(hr)) { hr = pSpec->GetPages(&cauuid); if(SUCCEEDED(hr) && cauuid.cElems > 0) { AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("Video Capture Filter...")); gcap.iVCapDialogPos = zz++; CoTaskMemFree(cauuid.pElems); } pSpec->Release(); } // 2. The video capture capture pin IAMStreamConfig *pSC; hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, gcap.pVCap, IID_IAMStreamConfig, (void **)&pSC); if(FAILED(hr)) hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, gcap.pVCap, IID_IAMStreamConfig, (void **)&pSC); if(SUCCEEDED(hr)) { hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(SUCCEEDED(hr)) { hr = pSpec->GetPages(&cauuid); if(SUCCEEDED(hr) && cauuid.cElems > 0) { AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("Video Capture Pin...")); gcap.iVCapCapturePinDialogPos = zz++; CoTaskMemFree(cauuid.pElems); } pSpec->Release(); } pSC->Release(); } // 3. The video capture preview pin. // This basically sets the format being previewed. Typically, you // want to capture and preview using the SAME format, instead of having to // enter the same value in 2 dialog boxes. For a discussion on this, see // the comment above the MakePreviewGraph function. hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Interleaved, gcap.pVCap, IID_IAMStreamConfig, (void **)&pSC); if(FAILED(hr)) hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, gcap.pVCap, IID_IAMStreamConfig, (void **)&pSC); if(SUCCEEDED(hr)) { hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(SUCCEEDED(hr)) { hr = pSpec->GetPages(&cauuid); if(SUCCEEDED(hr) && cauuid.cElems > 0) { AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz,TEXT("Video Preview Pin...")); gcap.iVCapPreviewPinDialogPos = zz++; CoTaskMemFree(cauuid.pElems); } pSpec->Release(); } pSC->Release(); } // 4 & 5. The video crossbar, and a possible second crossbar IAMCrossbar *pX, *pX2; IBaseFilter *pXF; hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, gcap.pVCap, IID_IAMCrossbar, (void **)&pX); if(FAILED(hr)) hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, gcap.pVCap, IID_IAMCrossbar, (void **)&pX); if(SUCCEEDED(hr)) { hr = pX->QueryInterface(IID_IBaseFilter, (void **)&pXF); if(SUCCEEDED(hr)) { hr = pX->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(SUCCEEDED(hr)) { hr = pSpec->GetPages(&cauuid); if(SUCCEEDED(hr) && cauuid.cElems > 0) { AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("Video Crossbar...")); gcap.iVCrossbarDialogPos = zz++; CoTaskMemFree(cauuid.pElems); } pSpec->Release(); } hr = gcap.pBuilder->FindInterface(&LOOK_UPSTREAM_ONLY, NULL, pXF, IID_IAMCrossbar, (void **)&pX2); if(SUCCEEDED(hr)) { hr = pX2->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(SUCCEEDED(hr)) { hr = pSpec->GetPages(&cauuid); if(SUCCEEDED(hr) && cauuid.cElems > 0) { AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("Second Crossbar...")); gcap.iACrossbarDialogPos = zz++; CoTaskMemFree(cauuid.pElems); } pSpec->Release(); } pX2->Release(); } pXF->Release(); } pX->Release(); } // 6. The TVTuner IAMTVTuner *pTV; hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, gcap.pVCap, IID_IAMTVTuner, (void **)&pTV); if(FAILED(hr)) hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, gcap.pVCap, IID_IAMTVTuner, (void **)&pTV); if(SUCCEEDED(hr)) { hr = pTV->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(SUCCEEDED(hr)) { hr = pSpec->GetPages(&cauuid); if(SUCCEEDED(hr) && cauuid.cElems > 0) { AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("TV Tuner...")); gcap.iTVTunerDialogPos = zz++; CoTaskMemFree(cauuid.pElems); } pSpec->Release(); } pTV->Release(); } // no audio capture, we're done if(gcap.pACap == NULL) return; // 7. The Audio capture filter itself... Thanks anyway, but we got these already // 8. The Audio capture pin hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio, gcap.pACap, IID_IAMStreamConfig, (void **)&pSC); if(SUCCEEDED(hr)) { hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(SUCCEEDED(hr)) { hr = pSpec->GetPages(&cauuid); if(SUCCEEDED(hr) && cauuid.cElems > 0) { AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("Audio Capture Pin...")); gcap.iACapCapturePinDialogPos = zz++; CoTaskMemFree(cauuid.pElems); } pSpec->Release(); } pSC->Release(); } // 9. The TV Audio filter IAMTVAudio *pTVA; hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio, gcap.pACap, IID_IAMTVAudio, (void **)&pTVA); if(SUCCEEDED(hr)) { hr = pTVA->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(SUCCEEDED(hr)) { hr = pSpec->GetPages(&cauuid); if(SUCCEEDED(hr) && cauuid.cElems > 0) { AppendMenu(hMenuSub,MF_STRING,MENU_DIALOG0+zz, TEXT("TV Audio...")); gcap.iTVAudioDialogPos = zz++; CoTaskMemFree(cauuid.pElems); } pSpec->Release(); } pTVA->Release(); } // 10. Crossbar class helper menu item, to let you choose an input if(gcap.pCrossbar && gcap.NumberOfVideoInputs) { gcap.hMenuPopup = CreatePopupMenu(); LONG j; LONG PhysicalType; TCHAR buf[MAX_PATH]; LONG InputToEnable = -1; gcap.iVideoInputMenuPos = zz++; AppendMenu(hMenuSub, MF_SEPARATOR, 0, NULL); for(j = 0; j < gcap.NumberOfVideoInputs; j++) { hr = gcap.pCrossbar->GetInputType(j, &PhysicalType); ASSERT(hr == S_OK); hr = gcap.pCrossbar->GetInputName(j, buf, sizeof (buf)); ASSERT(hr == S_OK); AppendMenu(gcap.hMenuPopup,MF_STRING,MENU_DIALOG0+zz, buf); zz++; // Route the first TVTuner by default if((PhysicalType == PhysConn_Video_Tuner) && InputToEnable == -1) { InputToEnable = j; } } AppendMenu(hMenuSub, MF_STRING | MF_POPUP, (UINT_PTR)gcap.hMenuPopup, TEXT("Video Input")); if(InputToEnable == -1) { InputToEnable = 0; } CheckMenuItem(gcap.hMenuPopup, InputToEnable, MF_BYPOSITION | MF_CHECKED); gcap.pCrossbar->SetInputIndex(InputToEnable); } // !!! anything needed to delete the popup when selecting a new input? } // how many captured/dropped so far // void UpdateStatus(BOOL fAllStats) { HRESULT hr; LONG lDropped, lNot=0, lAvgFrameSize; TCHAR tach[160]; // we use this interface to get the number of captured and dropped frames // NOTE: We cannot query for this interface earlier, as it may not be // available until the pin is connected if(!gcap.pDF) { hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, gcap.pVCap, IID_IAMDroppedFrames, (void **)&gcap.pDF); if(hr != S_OK) hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, gcap.pVCap, IID_IAMDroppedFrames, (void **)&gcap.pDF); } // this filter can't tell us dropped frame info. if(!gcap.pDF) { statusUpdateStatus(ghwndStatus, TEXT("Filter cannot report capture information")); return; } hr = gcap.pDF->GetNumDropped(&lDropped); if(hr == S_OK) hr = gcap.pDF->GetNumNotDropped(&lNot); if(hr != S_OK) return; lDropped -= gcap.lDroppedBase; lNot -= gcap.lNotBase; if(!fAllStats) { LONG lTime = timeGetTime() - gcap.lCapStartTime; hr = StringCchPrintf(tach, 160, TEXT("Captured %d frames (%d dropped) %d.%dsec\0"), lNot, lDropped, lTime / 1000, lTime / 100 - lTime / 1000 * 10); statusUpdateStatus(ghwndStatus, tach); return; } // we want all possible stats, including capture time and actual acheived // frame rate and data rate (as opposed to what we tried to get). These // numbers are an indication that though we dropped frames just now, if we // chose a data rate and frame rate equal to the numbers I'm about to // print, we probably wouldn't drop any frames. // average size of frame captured hr = gcap.pDF->GetAverageFrameSize(&lAvgFrameSize); if(hr != S_OK) return; // how long capture lasted LONG lDurMS = gcap.lCapStopTime - gcap.lCapStartTime; double flFrame; // acheived frame rate LONG lData; // acheived data rate if(lDurMS > 0) { flFrame = (double)(LONGLONG)lNot * 1000. / (double)(LONGLONG)lDurMS; lData = (LONG)(LONGLONG)(lNot / (double)(LONGLONG)lDurMS * 1000. * (double)(LONGLONG)lAvgFrameSize); } else { flFrame = 0.; lData = 0; } hr = StringCchPrintf(tach, 160, TEXT("Captured %d frames in %d.%d sec (%d dropped): %d.%d fps %d.%d Meg/sec\0"), lNot, lDurMS / 1000, lDurMS / 100 - lDurMS / 1000 * 10, lDropped, (int)flFrame, (int)(flFrame * 10.) - (int)flFrame * 10, lData / 1000000, lData / 1000 - (lData / 1000000 * 1000)); statusUpdateStatus(ghwndStatus, tach); } // Check the devices we're currently using and make filters for them // void ChooseDevices(IMoniker *pmVideo, IMoniker *pmAudio) { #define VERSIZE 40 #define DESCSIZE 80 int versize = VERSIZE; int descsize = DESCSIZE; WCHAR wachVer[VERSIZE]={0}, wachDesc[DESCSIZE]={0}; TCHAR tachStatus[VERSIZE + DESCSIZE + 5]={0}; // they chose a new device. rebuild the graphs if(gcap.pmVideo != pmVideo || gcap.pmAudio != pmAudio) { if(pmVideo) { pmVideo->AddRef(); } if(pmAudio) { pmAudio->AddRef(); } IMonRelease(gcap.pmVideo); IMonRelease(gcap.pmAudio); gcap.pmVideo = pmVideo; gcap.pmAudio = pmAudio; if(gcap.fPreviewing) StopPreview(); if(gcap.fCaptureGraphBuilt || gcap.fPreviewGraphBuilt) TearDownGraph(); FreeCapFilters(); InitCapFilters(); if(gcap.fWantPreview) // were we previewing? { BuildPreviewGraph(); StartPreview(); } MakeMenuOptions(); // the UI choices change per device } // Set the check marks for the devices menu. int i; for(i = 0; i < NUMELMS(gcap.rgpmVideoMenu); i++) { if(gcap.rgpmVideoMenu[i] == NULL) break; CheckMenuItem(GetMenu(ghwndApp), MENU_VDEVICE0 + i, (S_OK == gcap.rgpmVideoMenu[i]->IsEqual(gcap.pmVideo)) ? MF_CHECKED : MF_UNCHECKED); } for(i = 0; i < NUMELMS(gcap.rgpmAudioMenu); i++) { if(gcap.rgpmAudioMenu[i] == NULL) break; CheckMenuItem(GetMenu(ghwndApp), MENU_ADEVICE0 + i, (S_OK == gcap.rgpmAudioMenu[i]->IsEqual(gcap.pmAudio)) ? MF_CHECKED : MF_UNCHECKED); } // Put the video driver name in the status bar - if the filter supports // IAMVideoCompression::GetInfo, that's the best way to get the name and // the version. Otherwise use the name we got from device enumeration // as a fallback. if(gcap.pVC) { HRESULT hr = gcap.pVC->GetInfo(wachVer, &versize, wachDesc, &descsize, NULL, NULL, NULL, NULL); if(hr == S_OK) { // It's possible that the call succeeded without actually filling // in information for description and version. If these strings // are empty, just display the device's friendly name. if(wcslen(wachDesc) && wcslen(wachVer)) { hr = StringCchPrintf(tachStatus, VERSIZE + DESCSIZE + 5, TEXT("%s - %s\0"), wachDesc, wachVer); statusUpdateStatus(ghwndStatus, tachStatus); return; } } } // Since the GetInfo method failed (or the interface did not exist), // display the device's friendly name. statusUpdateStatus(ghwndStatus, gcap.wachFriendlyName); } void ChooseDevices(TCHAR *szVideo, TCHAR *szAudio) { WCHAR wszVideo[1024], wszAudio[1024]; StringCchCopyN(wszVideo, NUMELMS(wszVideo), szVideo, NUMELMS(wszVideo)-1); StringCchCopyN(wszAudio, NUMELMS(wszAudio), szAudio, NUMELMS(wszAudio)-1); wszVideo[1023] = wszAudio[1023] = 0; // Null-terminate IBindCtx *lpBC=0; IMoniker *pmVideo = 0, *pmAudio = 0; HRESULT hr = CreateBindCtx(0, &lpBC); if(SUCCEEDED(hr)) { DWORD dwEaten; hr = MkParseDisplayName(lpBC, wszVideo, &dwEaten, &pmVideo); hr = MkParseDisplayName(lpBC, wszAudio, &dwEaten, &pmAudio); lpBC->Release(); } // Handle the case where the video capture device used for the previous session // is not available now. BOOL bFound = FALSE; if(pmVideo != NULL) { for(int i = 0; i < NUMELMS(gcap.rgpmVideoMenu); i++) { if(gcap.rgpmVideoMenu[i] != NULL && S_OK == gcap.rgpmVideoMenu[i]->IsEqual(pmVideo)) { bFound = TRUE; break; } } } if(!bFound) { if(gcap.iNumVCapDevices > 0) { IMonRelease(pmVideo); ASSERT(gcap.rgpmVideoMenu[0] != NULL); pmVideo = gcap.rgpmVideoMenu[0]; pmVideo->AddRef(); } else goto CleanUp; } ChooseDevices(pmVideo, pmAudio); CleanUp: IMonRelease(pmVideo); IMonRelease(pmAudio); } // put all installed video and audio devices in the menus // void AddDevicesToMenu() { if(gcap.fDeviceMenuPopulated) { return; } gcap.fDeviceMenuPopulated = true; gcap.iNumVCapDevices = 0; UINT uIndex = 0; HMENU hMenuSub; HRESULT hr; BOOL bCheck = FALSE; hMenuSub = GetSubMenu(GetMenu(ghwndApp), 1); // Devices menu // Clean the sub menu int iMenuItems = GetMenuItemCount(hMenuSub); if(iMenuItems == -1) { ErrMsg(TEXT("Error Cleaning Devices Menu")); return; } else if(iMenuItems > 0) { for(int i = 0; i < iMenuItems; i++) { RemoveMenu(hMenuSub, 0, MF_BYPOSITION); } } for(int i = 0; i < NUMELMS(gcap.rgpmVideoMenu); i++) { IMonRelease(gcap.rgpmVideoMenu[i]); } for(int i = 0; i < NUMELMS(gcap.rgpmAudioMenu); i++) { IMonRelease(gcap.rgpmAudioMenu[i]); } // enumerate all video capture devices ICreateDevEnum *pCreateDevEnum=0; hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void**)&pCreateDevEnum); if(hr != NOERROR) { ErrMsg(TEXT("Error Creating Device Enumerator")); return; } IEnumMoniker *pEm=0; hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEm, 0); if(hr != NOERROR) { ErrMsg(TEXT("Sorry, you have no video capture hardware.\r\n\r\n") TEXT("Video capture will not function properly.")); goto EnumAudio; } pEm->Reset(); ULONG cFetched; IMoniker *pM; while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK) { IPropertyBag *pBag=0; hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag); if(SUCCEEDED(hr)) { VARIANT var; var.vt = VT_BSTR; hr = pBag->Read(L"FriendlyName", &var, NULL); if(hr == NOERROR) { AppendMenu(hMenuSub, MF_STRING, MENU_VDEVICE0 + uIndex, var.bstrVal); if(gcap.pmVideo != 0 && (S_OK == gcap.pmVideo->IsEqual(pM))) bCheck = TRUE; CheckMenuItem(hMenuSub, MENU_VDEVICE0 + uIndex, (bCheck ? MF_CHECKED : MF_UNCHECKED)); EnableMenuItem(hMenuSub, MENU_VDEVICE0 + uIndex, (gcap.fCapturing ? MF_DISABLED : MF_ENABLED)); bCheck = FALSE; SysFreeString(var.bstrVal); ASSERT(gcap.rgpmVideoMenu[uIndex] == 0); gcap.rgpmVideoMenu[uIndex] = pM; pM->AddRef(); } pBag->Release(); } pM->Release(); uIndex++; } pEm->Release(); gcap.iNumVCapDevices = uIndex; // separate the video and audio devices AppendMenu(hMenuSub, MF_SEPARATOR, 0, NULL); EnumAudio: // enumerate all audio capture devices uIndex = 0; bCheck = FALSE; ASSERT(pCreateDevEnum != NULL); hr = pCreateDevEnum->CreateClassEnumerator(CLSID_AudioInputDeviceCategory, &pEm, 0); pCreateDevEnum->Release(); if(hr != NOERROR) return; pEm->Reset(); while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK) { IPropertyBag *pBag; hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag); if(SUCCEEDED(hr)) { VARIANT var; var.vt = VT_BSTR; hr = pBag->Read(L"FriendlyName", &var, NULL); if(hr == NOERROR) { AppendMenu(hMenuSub, MF_STRING, MENU_ADEVICE0 + uIndex, var.bstrVal); if(gcap.pmAudio != 0 && (S_OK == gcap.pmAudio->IsEqual(pM))) bCheck = TRUE; CheckMenuItem(hMenuSub, MENU_ADEVICE0 + uIndex, (bCheck ? MF_CHECKED : MF_UNCHECKED)); EnableMenuItem(hMenuSub, MENU_ADEVICE0 + uIndex, (gcap.fCapturing ? MF_DISABLED : MF_ENABLED)); bCheck = FALSE; SysFreeString(var.bstrVal); ASSERT(gcap.rgpmAudioMenu[uIndex] == 0); gcap.rgpmAudioMenu[uIndex] = pM; pM->AddRef(); } pBag->Release(); } pM->Release(); uIndex++; } pEm->Release(); } // Allow the user to choose a frame rate // void ChooseFrameRate() { double rate = gcap.FrameRate; DoDialog(ghwndApp, IDD_FrameRateDialog, (DLGPROC)FrameRateProc, 0); HRESULT hr = E_FAIL; // If somebody unchecks "use frame rate" it means we will no longer // tell the filter what frame rate to use... it will either continue // using the last one, or use some default, or if you bring up a dialog // box that has frame rate choices, it will obey them. // new frame rate? if(gcap.fUseFrameRate && gcap.FrameRate != rate) { if(gcap.fPreviewing) StopPreview(); // now tell it what frame rate to capture at. Just find the format it // is capturing with, and leave everything else alone if(gcap.pVSC) { AM_MEDIA_TYPE *pmt; hr = gcap.pVSC->GetFormat(&pmt); // DV capture does not use a VIDEOINFOHEADER if(hr == NOERROR) { if(pmt->formattype == FORMAT_VideoInfo) { VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)pmt->pbFormat; pvi->AvgTimePerFrame =(LONGLONG)(10000000 / gcap.FrameRate); hr = gcap.pVSC->SetFormat(pmt); if(hr != S_OK) ErrMsg(TEXT("%x: Cannot set new frame rate"), hr); } DeleteMediaType(pmt); } } if(hr != NOERROR) ErrMsg(TEXT("Cannot set frame rate for capture")); if(gcap.fWantPreview) // we were previewing StartPreview(); } } // let them set a capture time limit // void ChooseTimeLimit() { DoDialog(ghwndApp, IDD_TimeLimitDialog, (DLGPROC)TimeLimitProc, 0); } // choose an audio capture format using ACM // void ChooseAudioFormat() { ACMFORMATCHOOSE cfmt; DWORD dwSize; LPWAVEFORMATEX lpwfx; AM_MEDIA_TYPE *pmt; // there's no point if we can't set a new format if(gcap.pASC == NULL) return; // What's the largest format size around? acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &dwSize); HRESULT hr = gcap.pASC->GetFormat(&pmt); if(hr != NOERROR) return; lpwfx = (LPWAVEFORMATEX)pmt->pbFormat; dwSize = (DWORD) (max(dwSize, lpwfx->cbSize + sizeof(WAVEFORMATEX))); // !!! This doesn't really map to the supported formats of the filter. // We should be using a property page based on IAMStreamConfig // Put up a dialog box initialized with the current format lpwfx = (LPWAVEFORMATEX)GlobalAllocPtr(GHND, dwSize); if(lpwfx) { CopyMemory(lpwfx, pmt->pbFormat, pmt->cbFormat); _fmemset(&cfmt, 0, sizeof(ACMFORMATCHOOSE)); cfmt.cbStruct = sizeof(ACMFORMATCHOOSE); cfmt.fdwStyle = ACMFORMATCHOOSE_STYLEF_INITTOWFXSTRUCT; // show only formats we can capture cfmt.fdwEnum = ACM_FORMATENUMF_HARDWARE | ACM_FORMATENUMF_INPUT; cfmt.hwndOwner = ghwndApp; cfmt.pwfx = lpwfx; cfmt.cbwfx = dwSize; // we chose a new format... so give it to the capture filter if(!acmFormatChoose(&cfmt)) { if(gcap.fPreviewing) StopPreview(); // can't call IAMStreamConfig::SetFormat // while streaming hr = SetMediaTypeFormatBlock(pmt, (LPBYTE)lpwfx, lpwfx->cbSize + sizeof(WAVEFORMATEX)); ASSERT(SUCCEEDED(hr)); gcap.pASC->SetFormat(pmt); // filter will reconnect if(gcap.fWantPreview) StartPreview(); } GlobalFreePtr(lpwfx) ; } DeleteMediaType(pmt); } /*----------------------------------------------------------------------------*\ | AppCommand() | | Process all of our WM_COMMAND messages. \*----------------------------------------------------------------------------*/ LONG PASCAL AppCommand(HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam) { HRESULT hr; int id = GET_WM_COMMAND_ID(wParam, lParam); switch(id) { // Our about box // case MENU_ABOUT: DialogBox(ghInstApp, MAKEINTRESOURCE(IDD_ABOUT), hwnd, (DLGPROC)AboutDlgProc); break; // Exit the application // case MENU_EXIT: PostMessage(hwnd,WM_CLOSE,0,0L); break; // choose a capture file // case MENU_SET_CAP_FILE: SetCaptureFile(hwnd); break; // pre-allocate the capture file // case MENU_ALLOC_CAP_FILE: AllocCaptureFile(hwnd); break; // save the capture file // case MENU_SAVE_CAP_FILE: SaveCaptureFile(hwnd); break; // start capturing // case MENU_START_CAP: if(gcap.fPreviewing) StopPreview(); if(gcap.fPreviewGraphBuilt) TearDownGraph(); BuildCaptureGraph(); StartCapture(); break; case MENU_MPEG2: if(gcap.fPreviewing) StopPreview(); gcap.fMPEG2 = !gcap.fMPEG2; // // when we capture we'll need a different graph now // if(gcap.fCaptureGraphBuilt || gcap.fPreviewGraphBuilt) { TearDownGraph(); } if(gcap.fWantPreview) { BuildPreviewGraph(); StartPreview(); } break; // toggle preview // case MENU_PREVIEW: gcap.fWantPreview = !gcap.fWantPreview; if(gcap.fWantPreview) { BuildPreviewGraph(); StartPreview(); } else StopPreview(); break; // stop capture // case MENU_STOP_CAP: StopCapture(); if(gcap.fWantPreview) { BuildPreviewGraph(); StartPreview(); } break; // select the master stream // case MENU_NOMASTER: gcap.iMasterStream = -1; if(gcap.pConfigAviMux) { hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream); if(hr != NOERROR) ErrMsg(TEXT("SetMasterStream failed!")); } break; case MENU_AUDIOMASTER: gcap.iMasterStream = 1; if(gcap.pConfigAviMux) { hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream); if(hr != NOERROR) ErrMsg(TEXT("SetMasterStream failed!")); } break; case MENU_VIDEOMASTER: gcap.iMasterStream = 0; if(gcap.pConfigAviMux) { hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream); if(hr != NOERROR) ErrMsg(TEXT("SetMasterStream failed!")); } break; // toggle capturing audio case MENU_CAP_AUDIO: if(gcap.fPreviewing) StopPreview(); gcap.fCapAudio = !gcap.fCapAudio; // when we capture we'll need a different graph now if(gcap.fCaptureGraphBuilt || gcap.fPreviewGraphBuilt) TearDownGraph(); if(gcap.fWantPreview) { BuildPreviewGraph(); StartPreview(); } break; // toggle closed captioning case MENU_CAP_CC: if(gcap.fPreviewing) StopPreview(); gcap.fCapCC = !gcap.fCapCC; // when we capture we'll need a different graph now if(gcap.fCaptureGraphBuilt || gcap.fPreviewGraphBuilt) TearDownGraph(); if(gcap.fWantPreview) { BuildPreviewGraph(); StartPreview(); } break; // choose the audio capture format // case MENU_AUDIOFORMAT: ChooseAudioFormat(); break; // pick a frame rate // case MENU_FRAMERATE: ChooseFrameRate(); break; // pick a time limit // case MENU_TIMELIMIT: ChooseTimeLimit(); break; // pick which video capture device to use // pick which video capture device to use // case MENU_VDEVICE0: case MENU_VDEVICE1: case MENU_VDEVICE2: case MENU_VDEVICE3: case MENU_VDEVICE4: case MENU_VDEVICE5: case MENU_VDEVICE6: case MENU_VDEVICE7: case MENU_VDEVICE8: case MENU_VDEVICE9: ChooseDevices(gcap.rgpmVideoMenu[id - MENU_VDEVICE0], gcap.pmAudio); break; // pick which audio capture device to use // case MENU_ADEVICE0: case MENU_ADEVICE1: case MENU_ADEVICE2: case MENU_ADEVICE3: case MENU_ADEVICE4: case MENU_ADEVICE5: case MENU_ADEVICE6: case MENU_ADEVICE7: case MENU_ADEVICE8: case MENU_ADEVICE9: ChooseDevices(gcap.pmVideo, gcap.rgpmAudioMenu[id - MENU_ADEVICE0]); break; // video format dialog // case MENU_DIALOG0: case MENU_DIALOG1: case MENU_DIALOG2: case MENU_DIALOG3: case MENU_DIALOG4: case MENU_DIALOG5: case MENU_DIALOG6: case MENU_DIALOG7: case MENU_DIALOG8: case MENU_DIALOG9: case MENU_DIALOGA: case MENU_DIALOGB: case MENU_DIALOGC: case MENU_DIALOGD: case MENU_DIALOGE: case MENU_DIALOGF: // they want the VfW format dialog if(id - MENU_DIALOG0 == gcap.iFormatDialogPos) { // this dialog will not work while previewing if(gcap.fWantPreview) StopPreview(); HRESULT hrD; hrD = gcap.pDlg->ShowDialog(VfwCaptureDialog_Format, ghwndApp); // Sometimes bringing up the FORMAT dialog can result // in changing to a capture format that the current graph // can't handle. It looks like that has happened and we'll // have to rebuild the graph. if(hrD == VFW_E_CANNOT_CONNECT) { TearDownGraph(); // now we need to rebuild // !!! This won't work if we've left a stranded h/w codec } // Resize our window to be the same size that we're capturing if(gcap.pVSC) { AM_MEDIA_TYPE *pmt; // get format being used NOW hr = gcap.pVSC->GetFormat(&pmt); // DV capture does not use a VIDEOINFOHEADER if(hr == NOERROR) { if(pmt->formattype == FORMAT_VideoInfo) { // resize our window to the new capture size ResizeWindow(HEADER(pmt->pbFormat)->biWidth, abs(HEADER(pmt->pbFormat)->biHeight)); } DeleteMediaType(pmt); } } if(gcap.fWantPreview) { BuildPreviewGraph(); StartPreview(); } } else if(id - MENU_DIALOG0 == gcap.iSourceDialogPos) { // this dialog will not work while previewing if(gcap.fWantPreview) StopPreview(); gcap.pDlg->ShowDialog(VfwCaptureDialog_Source, ghwndApp); if(gcap.fWantPreview) StartPreview(); } else if(id - MENU_DIALOG0 == gcap.iDisplayDialogPos) { // this dialog will not work while previewing if(gcap.fWantPreview) StopPreview(); gcap.pDlg->ShowDialog(VfwCaptureDialog_Display, ghwndApp); if(gcap.fWantPreview) StartPreview(); // now the code for the new dialogs } else if(id - MENU_DIALOG0 == gcap.iVCapDialogPos) { ISpecifyPropertyPages *pSpec; CAUUID cauuid; hr = gcap.pVCap->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(hr == S_OK) { hr = pSpec->GetPages(&cauuid); hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1, (IUnknown **)&gcap.pVCap, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); CoTaskMemFree(cauuid.pElems); pSpec->Release(); } } else if(id - MENU_DIALOG0 == gcap.iVCapCapturePinDialogPos) { // You can change this pin's output format in these dialogs. // If the capture pin is already connected to somebody who's // fussy about the connection type, that may prevent using // this dialog(!) because the filter it's connected to might not // allow reconnecting to a new format. (EG: you switch from RGB // to some compressed type, and need to pull in a decoder) // I need to tear down the graph downstream of the // capture filter before bringing up these dialogs. // In any case, the graph must be STOPPED when calling them. if(gcap.fWantPreview) StopPreview(); // make sure graph is stopped // The capture pin that we are trying to set the format on is connected if // one of these variable is set to TRUE. The pin should be disconnected for // the dialog to work properly. if(gcap.fCaptureGraphBuilt || gcap.fPreviewGraphBuilt) { TearDownGraph(); // graph could prevent dialog working } IAMStreamConfig *pSC; hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, gcap.pVCap, IID_IAMStreamConfig, (void **)&pSC); if(hr != NOERROR) hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, gcap.pVCap, IID_IAMStreamConfig, (void **)&pSC); ISpecifyPropertyPages *pSpec; CAUUID cauuid; hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(hr == S_OK) { hr = pSpec->GetPages(&cauuid); hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1, (IUnknown **)&pSC, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); // !!! What if changing output formats couldn't reconnect // and the graph is broken? Shouldn't be possible... if(gcap.pVSC) { AM_MEDIA_TYPE *pmt; // get format being used NOW hr = gcap.pVSC->GetFormat(&pmt); // DV capture does not use a VIDEOINFOHEADER if(hr == NOERROR) { if(pmt->formattype == FORMAT_VideoInfo) { // resize our window to the new capture size ResizeWindow(HEADER(pmt->pbFormat)->biWidth, abs(HEADER(pmt->pbFormat)->biHeight)); } DeleteMediaType(pmt); } } CoTaskMemFree(cauuid.pElems); pSpec->Release(); } pSC->Release(); if(gcap.fWantPreview) { BuildPreviewGraph(); StartPreview(); } } else if(id - MENU_DIALOG0 == gcap.iVCapPreviewPinDialogPos) { // this dialog may not work if the preview pin is connected // already, because the downstream filter may reject a format // change, so we better kill the graph. (EG: We switch from // capturing RGB to some compressed fmt, and need to pull in // a decompressor) if(gcap.fWantPreview) { StopPreview(); TearDownGraph(); } IAMStreamConfig *pSC; // This dialog changes the preview format, so it might affect // the format being drawn. Our app's window size is taken // from the size of the capture pin's video, not the preview // pin, so changing that here won't have any effect. All in all, // this probably won't be a terribly useful dialog in this app. hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Interleaved, gcap.pVCap, IID_IAMStreamConfig, (void **)&pSC); if (hr != NOERROR) { hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, gcap.pVCap, IID_IAMStreamConfig, (void **)&pSC); } ISpecifyPropertyPages *pSpec; CAUUID cauuid; hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(hr == S_OK) { hr = pSpec->GetPages(&cauuid); hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1, (IUnknown **)&pSC, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); CoTaskMemFree(cauuid.pElems); pSpec->Release(); } pSC->Release(); if(gcap.fWantPreview) { BuildPreviewGraph(); StartPreview(); } } else if(id - MENU_DIALOG0 == gcap.iVCrossbarDialogPos) { IAMCrossbar *pX; hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, gcap.pVCap, IID_IAMCrossbar, (void **)&pX); if(hr != NOERROR) hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, gcap.pVCap, IID_IAMCrossbar, (void **)&pX); ISpecifyPropertyPages *pSpec; CAUUID cauuid; hr = pX->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(hr == S_OK) { hr = pSpec->GetPages(&cauuid); hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1, (IUnknown **)&pX, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); CoTaskMemFree(cauuid.pElems); pSpec->Release(); } pX->Release(); } else if(id - MENU_DIALOG0 == gcap.iTVTunerDialogPos) { IAMTVTuner *pTV; hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, gcap.pVCap, IID_IAMTVTuner, (void **)&pTV); if(hr != NOERROR) hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, gcap.pVCap, IID_IAMTVTuner, (void **)&pTV); ISpecifyPropertyPages *pSpec; CAUUID cauuid; hr = pTV->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(hr == S_OK) { hr = pSpec->GetPages(&cauuid); hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1, (IUnknown **)&pTV, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); CoTaskMemFree(cauuid.pElems); pSpec->Release(); } pTV->Release(); } else if(id - MENU_DIALOG0 == gcap.iACapDialogPos) { ISpecifyPropertyPages *pSpec; CAUUID cauuid; hr = gcap.pACap->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(hr == S_OK) { hr = pSpec->GetPages(&cauuid); hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1, (IUnknown **)&gcap.pACap, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); CoTaskMemFree(cauuid.pElems); pSpec->Release(); } } else if(id - MENU_DIALOG0 == gcap.iACapCapturePinDialogPos) { // this dialog will not work while previewing - it might change // the output format! if(gcap.fWantPreview) StopPreview(); IAMStreamConfig *pSC; hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio, gcap.pACap, IID_IAMStreamConfig, (void **)&pSC); ISpecifyPropertyPages *pSpec; CAUUID cauuid; hr = pSC->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(hr == S_OK) { hr = pSpec->GetPages(&cauuid); hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1, (IUnknown **)&pSC, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); CoTaskMemFree(cauuid.pElems); pSpec->Release(); } pSC->Release(); if(gcap.fWantPreview) StartPreview(); } else if(id - MENU_DIALOG0 == gcap.iACrossbarDialogPos) { IAMCrossbar *pX, *pX2; IBaseFilter *pXF; // we could use better error checking here... I'm assuming // this won't fail hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, gcap.pVCap, IID_IAMCrossbar, (void **)&pX); if(hr != NOERROR) hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, gcap.pVCap, IID_IAMCrossbar, (void **)&pX); hr = pX->QueryInterface(IID_IBaseFilter, (void **)&pXF); hr = gcap.pBuilder->FindInterface(&LOOK_UPSTREAM_ONLY, NULL, pXF, IID_IAMCrossbar, (void **)&pX2); ISpecifyPropertyPages *pSpec; CAUUID cauuid; hr = pX2->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(hr == S_OK) { hr = pSpec->GetPages(&cauuid); hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1, (IUnknown **)&pX2, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); CoTaskMemFree(cauuid.pElems); pSpec->Release(); } pX2->Release(); pXF->Release(); pX->Release(); } else if(id - MENU_DIALOG0 == gcap.iTVAudioDialogPos) { IAMTVAudio *pTVA; hr = gcap.pBuilder->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio, gcap.pACap, IID_IAMTVAudio, (void **)&pTVA); ISpecifyPropertyPages *pSpec; CAUUID cauuid; hr = pTVA->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pSpec); if(hr == S_OK) { hr = pSpec->GetPages(&cauuid); hr = OleCreatePropertyFrame(ghwndApp, 30, 30, NULL, 1, (IUnknown **)&pTVA, cauuid.cElems, (GUID *)cauuid.pElems, 0, 0, NULL); CoTaskMemFree(cauuid.pElems); pSpec->Release(); } pTVA->Release(); } else if(((id - MENU_DIALOG0) > gcap.iVideoInputMenuPos) && (id - MENU_DIALOG0) <= gcap.iVideoInputMenuPos + gcap.NumberOfVideoInputs) { // Remove existing checks for(int j = 0; j < gcap.NumberOfVideoInputs; j++) { CheckMenuItem(gcap.hMenuPopup, j, MF_BYPOSITION | ((j == (id - MENU_DIALOG0) - gcap.iVideoInputMenuPos - 1) ? MF_CHECKED : MF_UNCHECKED )); } if(gcap.pCrossbar) { hr = gcap.pCrossbar->SetInputIndex((id - MENU_DIALOG0) - gcap.iVideoInputMenuPos - 1); ASSERT(hr == S_OK); } } break; } return 0L; } /*----------------------------------------------------------------------------*\ | ErrMsg - Opens a Message box with a error message in it. The user can | | select the OK button to continue | \*----------------------------------------------------------------------------*/ void ErrMsg(LPTSTR szFormat,...) { static TCHAR szBuffer[2048]={0}; const size_t NUMCHARS = sizeof(szBuffer) / sizeof(szBuffer[0]); const int LASTCHAR = NUMCHARS - 1; // Format the input string va_list pArgs; va_start(pArgs, szFormat); // Use a bounded buffer size to prevent buffer overruns. Limit count to // character size minus one to allow for a NULL terminating character. HRESULT hr = StringCchVPrintf(szBuffer, NUMCHARS - 1, szFormat, pArgs); va_end(pArgs); // Ensure that the formatted string is NULL-terminated szBuffer[LASTCHAR] = TEXT('\0'); MessageBox(ghwndApp, szBuffer, NULL, MB_OK | MB_ICONEXCLAMATION | MB_TASKMODAL); } /* AboutDlgProc() * * Dialog Procedure for the "about" dialog box. * */ BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_COMMAND: EndDialog(hwnd, TRUE); return TRUE; case WM_INITDIALOG: return TRUE; } return FALSE; } // pre-allocate the capture file // BOOL AllocCaptureFile(HWND hWnd) { // we'll get into an infinite loop in the dlg proc setting a value if(gcap.wszCaptureFile[0] == 0) return FALSE; /* * show the allocate file space dialog to encourage * the user to pre-allocate space */ if(DoDialog(hWnd, IDD_AllocCapFileSpace, (DLGPROC)AllocCapFileProc, 0)) { // ensure repaint after dismissing dialog before // possibly lengthy operation UpdateWindow(ghwndApp); // User has hit OK. Alloc requested capture file space BOOL f = MakeBuilder(); if(!f) return FALSE; if(gcap.pBuilder->AllocCapFile(gcap.wszCaptureFile, (DWORDLONG)gcap.wCapFileSize * 1024L * 1024L) != NOERROR) { MessageBox(ghwndApp, TEXT("Error"), TEXT("Failed to pre-allocate capture file space"), MB_OK | MB_ICONEXCLAMATION); return FALSE; } return TRUE; } else { return FALSE; } } /* * Put up the open file dialog */ BOOL OpenFileDialog(HWND hWnd, LPTSTR pszName, DWORD cchName) { OPENFILENAME ofn; LPTSTR p; TCHAR szFileName[_MAX_PATH]; TCHAR szBuffer[_MAX_PATH] ; if(pszName == NULL) { return FALSE; } // start with capture file as current file name szFileName[0] = 0; (void)StringCchCopy(szFileName, NUMELMS(szFileName), gcap.wszCaptureFile); // Get just the path info // Terminate the full path at the last backslash (void)StringCchCopy(szBuffer, NUMELMS(szBuffer), szFileName); for(p = szBuffer + lstrlen(szBuffer); p > szBuffer; p--) { if(*p == '\\') { *(p+1) = '\0'; break; } } szBuffer[_MAX_PATH-1] = 0; // Null-terminate ZeroMemory(&ofn, sizeof(OPENFILENAME)) ; ofn.lStructSize = sizeof(OPENFILENAME) ; ofn.hwndOwner = hWnd ; ofn.lpstrFilter = TEXT("Microsoft AVI\0*.avi\0\0"); ofn.nFilterIndex = 0 ; ofn.lpstrFile = szFileName; ofn.nMaxFile = sizeof(szFileName) ; ofn.lpstrFileTitle = NULL; ofn.lpstrTitle = TEXT("Set Capture File"); ofn.nMaxFileTitle = 0 ; ofn.lpstrInitialDir = szBuffer; ofn.Flags = OFN_HIDEREADONLY | OFN_NOREADONLYRETURN | OFN_PATHMUSTEXIST ; if(GetOpenFileName(&ofn)) { // We have a capture file name StringCchCopy(pszName, cchName, szFileName); return TRUE; } else { return FALSE; } } /* * Put up a dialog to allow the user to select a capture file. */ BOOL SetCaptureFile(HWND hWnd) { if(OpenFileDialog(hWnd, gcap.wszCaptureFile, _MAX_PATH)) { // We have a capture file name // If this is a new file, then invite the user to // allocate some space if (GetFileAttributes(gcap.wszCaptureFile) == INVALID_FILE_ATTRIBUTES) { // bring up dialog, and set new file size BOOL f = AllocCaptureFile(hWnd); if(!f) return FALSE; } } else { return FALSE; } SetAppCaption(); // tell the file writer to use the new filename if(gcap.pSink) { gcap.pSink->SetFileName(gcap.wszCaptureFile, NULL); } return TRUE; } /* * Put up a dialog to allow the user to save the contents of the capture file * elsewhere */ BOOL SaveCaptureFile(HWND hWnd) { HRESULT hr; TCHAR tachDstFile[_MAX_PATH]; if(gcap.pBuilder == NULL) return FALSE; if(OpenFileDialog(hWnd, tachDstFile, _MAX_PATH)) { // We have a capture file name statusUpdateStatus(ghwndStatus, TEXT("Saving capture file - please wait...")); // we need our own graph builder because the main one might not exist ICaptureGraphBuilder2 *pBuilder; hr = CoCreateInstance((REFCLSID)CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC, (REFIID)IID_ICaptureGraphBuilder2, (void **)&pBuilder); if(hr == NOERROR) { // allow the user to press ESC to abort... ask for progress CProgress *pProg = new CProgress(); IAMCopyCaptureFileProgress *pIProg = NULL; if(pProg) { hr = pProg->QueryInterface(IID_IAMCopyCaptureFileProgress, (void **)&pIProg); } hr = pBuilder->CopyCaptureFile(gcap.wszCaptureFile, tachDstFile, TRUE, pIProg); if(pIProg) pIProg->Release(); pBuilder->Release(); } if(hr == S_OK) statusUpdateStatus(ghwndStatus, TEXT("Capture file saved")); else if(hr == S_FALSE) statusUpdateStatus(ghwndStatus, TEXT("Capture file save aborted")); else statusUpdateStatus(ghwndStatus, TEXT("Capture file save ERROR")); return (hr == NOERROR ? TRUE : FALSE); } else { return TRUE; // they cancelled or something } } // Display a dialog box // int DoDialog(HWND hwndParent, int DialogID, DLGPROC fnDialog, long lParam) { DLGPROC fn; int result; fn = (DLGPROC)MakeProcInstance(fnDialog, ghInstApp); result = (int) DialogBoxParam(ghInstApp, MAKEINTRESOURCE(DialogID), hwndParent, fn, lParam); FreeProcInstance(fn); return result; } // // This function can be used when loading libraries in the Windows System // folder. It queries for the system folder and prepends the path to the // name of the target library. This helps to prevent spoofing of system DLLs. // // NOTE: Using this function may break apps who use Windows Fusion or who // explicitly replace Windows DLLs in the application's local folder. // If security is paramount, you can use this as a replacement for LoadLibrary(). // HMODULE UtilLoadLibrary(LPCTSTR lpFileName) { TCHAR szFullPath[MAX_PATH]; BOOL fSuccess = FALSE; size_t nLibLength = 0; if (lpFileName == NULL) { return NULL; } // Prevent buffer overflow by limiting size of library name if (FAILED(StringCchLength(lpFileName, MAX_PATH, &nLibLength))) { return NULL; } // nLibLength does not include the terminating NULL, so it must be strictly less than MAX_PATH if(nLibLength >= MAX_PATH) { return NULL; } int nSpaceAllowed = (int)(MAX_PATH - nLibLength - 4); // Allow for '\' and '\0' int nSpaceUsed = 0; // Initialize to a NULL string szFullPath[0] = TEXT('\0'); // Read the Windows System directory nSpaceUsed = GetSystemDirectory(szFullPath, nSpaceAllowed); // If the function fails, the return value will be zero. // If the buffer isn't large enough, the function will return the size // of the buffer required to hold the path. Check both failures. if((nSpaceUsed != 0) && (nSpaceUsed <= nSpaceAllowed)) { // Now we have the Windows/System path with enough space left // to add the libary name and a terminating NULL character HRESULT hr = StringCchCat(szFullPath, MAX_PATH, TEXT("\\\0")); hr = StringCchCat(szFullPath, MAX_PATH, lpFileName); hr = StringCchCat(szFullPath, MAX_PATH, TEXT("\0")); fSuccess = TRUE; } if(!fSuccess) { // An error occurred, so fallback to the default behavior by using // the NULL-terminated library name provided as a parameter. StringCchCopy(szFullPath, MAX_PATH, lpFileName); // Must succeed because lpFileName < MAX_PATH } return ((HMODULE) LoadLibrary(szFullPath)); } // // GetFreeDiskSpace: Function to Measure Available Disk Space // static long GetFreeDiskSpaceInKB(LPTSTR pFile) { DWORD dwFreeClusters, dwBytesPerSector, dwSectorsPerCluster, dwClusters; TCHAR RootName[MAX_PATH]; LPTSTR ptmp=0; //required arg ULARGE_INTEGER ulA, ulB, ulFreeBytes; // need to find path for root directory on drive containing this file. if (0 == GetFullPathName(pFile, NUMELMS(RootName), RootName, &ptmp)) { return -1; } // truncate this to the name of the root directory (how tedious) if(RootName[0] == '\\' && RootName[1] == '\\') { // path begins with \\server\share\path so skip the first // three backslashes ptmp = &RootName[2]; while(*ptmp && (*ptmp != '\\')) { ptmp++; } if(*ptmp) { // advance past the third backslash ptmp++; } } else { // path must be drv:\path ptmp = RootName; } // find next backslash and put a null after it while(*ptmp && (*ptmp != '\\')) { ptmp++; } // found a backslash ? if(*ptmp) { // skip it and insert null ptmp++; *ptmp = '\0'; } // the only real way of finding out free disk space is calling // GetDiskFreeSpaceExA, but it doesn't exist on Win95 HINSTANCE h = LoadLibrary(TEXT("kernel32.dll\0")); if(h) { typedef BOOL(WINAPI *MyFunc)(LPCTSTR RootName, PULARGE_INTEGER pulA, PULARGE_INTEGER pulB, PULARGE_INTEGER pulFreeBytes); MyFunc pfnGetDiskFreeSpaceEx = (MyFunc)GetProcAddress(h, "GetDiskFreeSpaceExW"); FreeLibrary(h); if(pfnGetDiskFreeSpaceEx) { if(!pfnGetDiskFreeSpaceEx(RootName, &ulA, &ulB, &ulFreeBytes)) return -1; else return (long)(ulFreeBytes.QuadPart / 1024); } } if(!GetDiskFreeSpace(RootName, &dwSectorsPerCluster, &dwBytesPerSector, &dwFreeClusters, &dwClusters)) return (-1); else return(MulDiv(dwSectorsPerCluster * dwBytesPerSector, dwFreeClusters, 1024)); } // AllocCapFileProc: Capture file Space Allocation Dialog Box Procedure // int FAR PASCAL AllocCapFileProc(HWND hDlg, UINT Message, UINT wParam, LONG lParam) { static int nFreeMBs = 0 ; switch(Message) { case WM_INITDIALOG: { DWORDLONG dwlFileSize = 0; long lFreeSpaceInKB; // Get current capture file name and measure its size dwlFileSize = GetSize(gcap.wszCaptureFile); // Get free disk space and add current capture file size to that. // Convert the available space to MBs. if((lFreeSpaceInKB = GetFreeDiskSpaceInKB(gcap.wszCaptureFile)) != -1L) { lFreeSpaceInKB += (long)(dwlFileSize / 1024); nFreeMBs = lFreeSpaceInKB / 1024 ; SetDlgItemInt(hDlg, IDD_SetCapFileFree, nFreeMBs, TRUE) ; } else { EnableWindow(GetDlgItem(hDlg, IDD_SetCapFileFree), FALSE); } gcap.wCapFileSize = (WORD) (dwlFileSize / (1024L * 1024L)); SetDlgItemInt(hDlg, IDD_SetCapFileSize, gcap.wCapFileSize, TRUE) ; return TRUE ; } case WM_COMMAND : switch(GET_WM_COMMAND_ID(wParam, lParam)) { case IDOK : { int iCapFileSize ; iCapFileSize = (int) GetDlgItemInt(hDlg, IDD_SetCapFileSize, NULL, TRUE) ; if(iCapFileSize <= 0 || iCapFileSize > nFreeMBs) { // You are asking for more than we have !! Sorry, ... SetDlgItemInt(hDlg, IDD_SetCapFileSize, iCapFileSize, TRUE) ; SetFocus(GetDlgItem(hDlg, IDD_SetCapFileSize)) ; MessageBeep(MB_ICONEXCLAMATION) ; return FALSE ; } gcap.wCapFileSize = (WORD)iCapFileSize ; EndDialog(hDlg, TRUE) ; return TRUE ; } case IDCANCEL : EndDialog(hDlg, FALSE) ; return TRUE ; case IDD_SetCapFileSize: { long l; BOOL bchanged; TCHAR tachBuffer[21]; // check that entered size is a valid number GetDlgItemText(hDlg, IDD_SetCapFileSize, tachBuffer, sizeof(tachBuffer)/sizeof(tachBuffer[0])); l = _wtol(tachBuffer); bchanged = FALSE; if(l < 1) { l = 1; bchanged = TRUE; // don't infinite loop if there's < 1 Meg free } else if(l > nFreeMBs && nFreeMBs > 0) { l = nFreeMBs; bchanged = TRUE; } else { // make sure there are no non-digit chars // atol() will ignore trailing non-digit characters int c = 0; while(tachBuffer[c]) { if(IsCharAlpha(tachBuffer[c]) || !IsCharAlphaNumeric(tachBuffer[c])) { // string contains non-digit chars - reset l = 1; bchanged = TRUE; break; } c++; } } if(bchanged) { HRESULT hr = StringCchPrintf(tachBuffer, 21, TEXT("%ld\0"), l); SetDlgItemText(hDlg, IDD_SetCapFileSize, tachBuffer); } break; } } break; } return FALSE ; } // // FrameRateProc: Choose a frame rate // int FAR PASCAL FrameRateProc(HWND hwnd, UINT msg, UINT wParam, LONG lParam) { TCHAR tach[32]; HRESULT hr; switch(msg) { case WM_INITDIALOG: /* put the current frame rate in the box */ hr = StringCchPrintf(tach, 32, TEXT("%d\0"), (int) gcap.FrameRate); SetDlgItemText(hwnd, IDC_FRAMERATE, tach); CheckDlgButton(hwnd, IDC_USEFRAMERATE, gcap.fUseFrameRate); break; case WM_COMMAND: switch(wParam) { case IDCANCEL: EndDialog(hwnd, FALSE); break; case IDOK: /* get the new frame rate */ GetDlgItemText(hwnd, IDC_FRAMERATE, tach, sizeof(tach)/sizeof(tach[0])); double frameRate = _wtof(tach); if(frameRate <= 0.) { ErrMsg(TEXT("Invalid frame rate.")); break; } else gcap.FrameRate = frameRate; gcap.fUseFrameRate = IsDlgButtonChecked(hwnd, IDC_USEFRAMERATE); EndDialog(hwnd, TRUE); break; } break; default: return FALSE; } return TRUE; } // // TimeLimitProc: Choose a capture time limit // int FAR PASCAL TimeLimitProc(HWND hwnd, UINT msg, UINT wParam, LONG lParam) { TCHAR tach[32]; DWORD dwTimeLimit; HRESULT hr; switch(msg) { case WM_INITDIALOG: /* put the current time limit info in the boxes */ hr = StringCchPrintf(tach, 32, TEXT("%d\0"), gcap.dwTimeLimit); SetDlgItemText(hwnd, IDC_TIMELIMIT, tach); CheckDlgButton(hwnd, IDC_USETIMELIMIT, gcap.fUseTimeLimit); break; case WM_COMMAND: switch(wParam) { case IDCANCEL: EndDialog(hwnd, FALSE); break; case IDOK: /* get the new time limit */ dwTimeLimit = GetDlgItemInt(hwnd, IDC_TIMELIMIT, NULL, FALSE); gcap.dwTimeLimit = dwTimeLimit; gcap.fUseTimeLimit = IsDlgButtonChecked(hwnd, IDC_USETIMELIMIT); EndDialog(hwnd, TRUE); break; } break; default: return FALSE; } return TRUE; } // // PressAKeyProc: Press OK to capture // int FAR PASCAL PressAKeyProc(HWND hwnd, UINT msg, UINT wParam, LONG lParam) { TCHAR tach[_MAX_PATH]; HRESULT hr; switch(msg) { case WM_INITDIALOG: /* set the current file name in the box */ hr = StringCchPrintf(tach, _MAX_PATH, TEXT("%s\0"), gcap.wszCaptureFile); SetDlgItemText(hwnd, IDC_CAPFILENAME, tach); break; case WM_COMMAND: switch(wParam) { case IDCANCEL: EndDialog(hwnd, FALSE); break; case IDOK: EndDialog(hwnd, TRUE); break; } break; default: return FALSE; } return TRUE; } DWORDLONG GetSize(LPCTSTR tach) { HANDLE hFile = CreateFile(tach, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if(hFile == INVALID_HANDLE_VALUE) { return 0; } DWORDLONG dwlSize = 0; DWORD dwSizeHigh = 0; DWORD dwSizeLow = GetFileSize(hFile, &dwSizeHigh); if (INVALID_FILE_SIZE != dwSizeLow) { dwlSize = dwSizeLow + ((DWORDLONG)dwSizeHigh << 32); } if(!CloseHandle(hFile)) { dwlSize = 0; } return dwlSize; } void OnClose() { TCHAR szBuf[512]; WCHAR *wszDisplayName = NULL; // Unregister device notifications if(ghDevNotify != NULL) { ASSERT(gpUnregisterDeviceNotification); gpUnregisterDeviceNotification(ghDevNotify); ghDevNotify = NULL; } // Destroy the filter graph and cleanup StopPreview(); StopCapture(); TearDownGraph(); FreeCapFilters(); // // Store current settings in win.ini for next time // // Save the name of the current capture file WriteProfileString(TEXT("annie"), TEXT("CaptureFile"), gcap.wszCaptureFile); wszDisplayName = 0; szBuf[0] = NULL; if(gcap.pmVideo) { if(SUCCEEDED(gcap.pmVideo->GetDisplayName(0, 0, &wszDisplayName))) { if(wszDisplayName) { StringCchCopyN(szBuf, NUMELMS(szBuf), wszDisplayName, NUMELMS(szBuf)-1); CoTaskMemFree(wszDisplayName); } } } // Save the current video device string WriteProfileString(TEXT("annie"), TEXT("VideoDevice2"), szBuf); wszDisplayName = 0; szBuf[0] = NULL; if(gcap.pmAudio) { if(SUCCEEDED(gcap.pmAudio->GetDisplayName(0, 0, &wszDisplayName))) { if(wszDisplayName) { (void)StringCchCopy(szBuf, NUMELMS(szBuf), wszDisplayName ); CoTaskMemFree(wszDisplayName); } } } // Save the current audio device string WriteProfileString(TEXT("annie"), TEXT("AudioDevice2"), szBuf); // Save the integer settings HRESULT hr = StringCchPrintf(szBuf, 512, TEXT("%d"), (int)(10000000 / gcap.FrameRate)); WriteProfileString(TEXT("annie"), TEXT("FrameRate"), szBuf); hr = StringCchPrintf(szBuf, 512, TEXT("%d"), gcap.fUseFrameRate); WriteProfileString(TEXT("annie"), TEXT("UseFrameRate"), szBuf); hr = StringCchPrintf(szBuf, 512, TEXT("%d"), gcap.fCapAudio); WriteProfileString(TEXT("annie"), TEXT("CaptureAudio"), szBuf); hr = StringCchPrintf(szBuf, 512, TEXT("%d"), gcap.fCapCC); WriteProfileString(TEXT("annie"), TEXT("CaptureCC"), szBuf); hr = StringCchPrintf(szBuf, 512, TEXT("%d"), gcap.fWantPreview); WriteProfileString(TEXT("annie"), TEXT("WantPreview"), szBuf); hr = StringCchPrintf(szBuf, 512, TEXT("%d"), gcap.iMasterStream); WriteProfileString(TEXT("annie"), TEXT("MasterStream"), szBuf); hr = StringCchPrintf(szBuf, 512, TEXT("%d"), gcap.fUseTimeLimit); WriteProfileString(TEXT("annie"), TEXT("UseTimeLimit"), szBuf); hr = StringCchPrintf(szBuf, 512, TEXT("%d"), gcap.dwTimeLimit); WriteProfileString(TEXT("annie"), TEXT("TimeLimit"), szBuf); }