// build the capture graph // BOOL BuildCaptureGraph() { USES_CONVERSION; int cy, cyBorder; HRESULT hr; BOOL f; AM_MEDIA_TYPE *pmt=0; // we have one already if(gcap.fCaptureGraphBuilt) return TRUE; // No rebuilding while we're running if(gcap.fCapturing || gcap.fPreviewing) return FALSE; // We don't have the necessary capture filters if(gcap.pVCap == NULL) return FALSE; if(gcap.pACap == NULL && gcap.fCapAudio) return FALSE; // no capture file name yet... we need one first if(gcap.szCaptureFile[0] == 0) { f = SetCaptureFile(ghwndApp); if(!f) return f; } // we already have another graph built... tear down the old one if(gcap.fPreviewGraphBuilt) TearDownGraph(); // // We need a rendering section that will write the capture file out in AVI // file format // GUID guid; if( gcap.fMPEG2 ) { guid = MEDIASUBTYPE_Mpeg2; } else { guid = MEDIASUBTYPE_Avi; } hr = gcap.pBuilder->SetOutputFileName(&guid, T2W(gcap.szCaptureFile), &gcap.pRender, &gcap.pSink); if(hr != NOERROR) { ErrMsg(TEXT("Cannot set output file")); goto SetupCaptureFail; } // Now tell the AVIMUX to write out AVI files that old apps can read properly. // If we don't, most apps won't be able to tell where the keyframes are, // slowing down editing considerably // Doing this will cause one seek (over the area the index will go) when // you capture past 1 Gig, but that's no big deal. // NOTE: This is on by default, so it's not necessary to turn it on // Also, set the proper MASTER STREAM if( !gcap.fMPEG2 ) { hr = gcap.pRender->QueryInterface(IID_IConfigAviMux, (void **)&gcap.pConfigAviMux); if(hr == NOERROR && gcap.pConfigAviMux) { gcap.pConfigAviMux->SetOutputCompatibilityIndex(TRUE); if(gcap.fCapAudio) { hr = gcap.pConfigAviMux->SetMasterStream(gcap.iMasterStream); if(hr != NOERROR) ErrMsg(TEXT("SetMasterStream failed!")); } } } // // Render the video capture and preview pins - even if the capture filter only // has a capture pin (and no preview pin) this should work... because the // capture graph builder will use a smart tee filter to provide both capture // and preview. We don't have to worry. It will just work. // // NOTE that we try to render the interleaved pin before the video pin, because // if BOTH exist, it's a DV filter and the only way to get the audio is to use // the interleaved pin. Using the Video pin on a DV filter is only useful if // you don't want the audio. if( !gcap.fMPEG2 ) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, gcap.pVCap, NULL, gcap.pRender); if(hr != NOERROR) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, gcap.pVCap, NULL, gcap.pRender); if(hr != NOERROR) { ErrMsg(TEXT("Cannot render video capture stream")); goto SetupCaptureFail; } } if(gcap.fWantPreview) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Interleaved, gcap.pVCap, NULL, NULL); if(hr == VFW_S_NOPREVIEWPIN) { // preview was faked up for us using the (only) capture pin gcap.fPreviewFaked = TRUE; } else if(hr != S_OK) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, gcap.pVCap, NULL, NULL); if(hr == VFW_S_NOPREVIEWPIN) { // preview was faked up for us using the (only) capture pin gcap.fPreviewFaked = TRUE; } else if(hr != S_OK) { ErrMsg(TEXT("Cannot render video preview stream")); goto SetupCaptureFail; } } } } else { CComPtr< IBaseFilter > sink; if( &gcap.pSink ) { gcap.pSink->QueryInterface( IID_IBaseFilter, reinterpret_cast( &sink ) ); } hr = gcap.pBuilder->RenderStream(NULL, &MEDIATYPE_Stream, gcap.pVCap, NULL, sink); } // // Render the audio capture pin? // if(!gcap.fMPEG2 && gcap.fCapAudio) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio, gcap.pACap, NULL, gcap.pRender); if(hr != NOERROR) { ErrMsg(TEXT("Cannot render audio capture stream")); goto SetupCaptureFail; } } // // Render the closed captioning pin? It could be a CC or a VBI category pin, // depending on the capture driver // if(!gcap.fMPEG2 && gcap.fCapCC) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CC, NULL, gcap.pVCap, NULL, gcap.pRender); if(hr != NOERROR) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_VBI, NULL, gcap.pVCap, NULL, gcap.pRender); if(hr != NOERROR) { ErrMsg(TEXT("Cannot render closed captioning")); // so what? goto SetupCaptureFail; } } // To preview and capture VBI at the same time, we can call this twice if(gcap.fWantPreview) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_VBI, NULL, gcap.pVCap, NULL, NULL); } } // // Get the preview window to be a child of our app's window // // This will find the IVideoWindow interface on the renderer. It is // important to ask the filtergraph for this interface... do NOT use // ICaptureGraphBuilder2::FindInterface, because the filtergraph needs to // know we own the window so it can give us display changed messages, etc. if(!gcap.fMPEG2 && gcap.fWantPreview) { hr = gcap.pFg->QueryInterface(IID_IVideoWindow, (void **)&gcap.pVW); if(hr != NOERROR && gcap.fWantPreview) { ErrMsg(TEXT("This graph cannot preview")); } else if(hr == NOERROR) { RECT rc; gcap.pVW->put_Owner((OAHWND)ghwndApp); // We own the window now gcap.pVW->put_WindowStyle(WS_CHILD); // you are now a child // give the preview window all our space but where the status bar is GetClientRect(ghwndApp, &rc); cyBorder = GetSystemMetrics(SM_CYBORDER); cy = statusGetHeight() + cyBorder; rc.bottom -= cy; gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); // be this big gcap.pVW->put_Visible(OATRUE); } } // now tell it what frame rate to capture at. Just find the format it // is capturing with, and leave everything alone but change the frame rate if( !gcap.fMPEG2 ) { hr = gcap.fUseFrameRate ? E_FAIL : NOERROR; if(gcap.pVSC && gcap.fUseFrameRate) { hr = gcap.pVSC->GetFormat(&pmt); // DV capture does not use a VIDEOINFOHEADER if(hr == NOERROR) { if(pmt->formattype == FORMAT_VideoInfo) { VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)pmt->pbFormat; pvi->AvgTimePerFrame = (LONGLONG)(10000000 / gcap.FrameRate); hr = gcap.pVSC->SetFormat(pmt); } DeleteMediaType(pmt); } } if(hr != NOERROR) ErrMsg(TEXT("Cannot set frame rate for capture")); } // now ask the filtergraph to tell us when something is completed or aborted // (EC_COMPLETE, EC_USERABORT, EC_ERRORABORT). This is how we will find out // if the disk gets full while capturing hr = gcap.pFg->QueryInterface(IID_IMediaEventEx, (void **)&gcap.pME); if(hr == NOERROR) { gcap.pME->SetNotifyWindow((OAHWND)ghwndApp, WM_FGNOTIFY, 0); } // potential debug output - what the graph looks like // DumpGraph(gcap.pFg, 1); // Add our graph to the running object table, which will allow // the GraphEdit application to "spy" on our graph #ifdef REGISTER_FILTERGRAPH hr = AddGraphToRot(gcap.pFg, &g_dwGraphRegister); if(FAILED(hr)) { ErrMsg(TEXT("Failed to register filter graph with ROT! hr=0x%x"), hr); g_dwGraphRegister = 0; } #endif // All done. gcap.fCaptureGraphBuilt = TRUE; return TRUE; SetupCaptureFail: TearDownGraph(); return FALSE; } // build the preview graph! // // !!! PLEASE NOTE !!! Some new WDM devices have totally separate capture // and preview settings. An application that wishes to preview and then // capture may have to set the preview pin format using IAMStreamConfig on the // preview pin, and then again on the capture pin to capture with that format. // In this sample app, there is a separate page to set the settings on the // capture pin and one for the preview pin. To avoid the user // having to enter the same settings in 2 dialog boxes, an app can have its own // UI for choosing a format (the possible formats can be enumerated using // IAMStreamConfig) and then the app can programmatically call IAMStreamConfig // to set the format on both pins. // BOOL BuildPreviewGraph() { int cy, cyBorder; HRESULT hr; AM_MEDIA_TYPE *pmt; // we have one already if(gcap.fPreviewGraphBuilt) return TRUE; // No rebuilding while we're running if(gcap.fCapturing || gcap.fPreviewing) return FALSE; // We don't have the necessary capture filters if(gcap.pVCap == NULL) return FALSE; if(gcap.pACap == NULL && gcap.fCapAudio) return FALSE; // we already have another graph built... tear down the old one if(gcap.fCaptureGraphBuilt) TearDownGraph(); // // Render the preview pin - even if there is not preview pin, the capture // graph builder will use a smart tee filter and provide a preview. // // !!! what about latency/buffer issues? // NOTE that we try to render the interleaved pin before the video pin, because // if BOTH exist, it's a DV filter and the only way to get the audio is to use // the interleaved pin. Using the Video pin on a DV filter is only useful if // you don't want the audio. if( gcap.fMPEG2 ) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Stream, gcap.pVCap, NULL, NULL); if( FAILED( hr ) ) { ErrMsg(TEXT("Cannot build MPEG2 preview graph!")); } } else { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Interleaved, gcap.pVCap, NULL, NULL); if(hr == VFW_S_NOPREVIEWPIN) { // preview was faked up for us using the (only) capture pin gcap.fPreviewFaked = TRUE; } else if(hr != S_OK) { // maybe it's DV? hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, gcap.pVCap, NULL, NULL); if(hr == VFW_S_NOPREVIEWPIN) { // preview was faked up for us using the (only) capture pin gcap.fPreviewFaked = TRUE; } else if(hr != S_OK) { ErrMsg(TEXT("This graph cannot preview!")); gcap.fPreviewGraphBuilt = FALSE; return FALSE; } } // // Render the closed captioning pin? It could be a CC or a VBI category pin, // depending on the capture driver // if(gcap.fCapCC) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CC, NULL, gcap.pVCap, NULL, NULL); if(hr != NOERROR) { hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_VBI, NULL, gcap.pVCap, NULL, NULL); if(hr != NOERROR) { ErrMsg(TEXT("Cannot render closed captioning")); } } } } // // Get the preview window to be a child of our app's window // // This will find the IVideoWindow interface on the renderer. It is // important to ask the filtergraph for this interface... do NOT use // ICaptureGraphBuilder2::FindInterface, because the filtergraph needs to // know we own the window so it can give us display changed messages, etc. hr = gcap.pFg->QueryInterface(IID_IVideoWindow, (void **)&gcap.pVW); if(hr != NOERROR) { ErrMsg(TEXT("This graph cannot preview properly")); } else { //Find out if this is a DV stream AM_MEDIA_TYPE * pmtDV; if(gcap.pVSC && SUCCEEDED(gcap.pVSC->GetFormat(&pmtDV))) { if(pmtDV->formattype == FORMAT_DvInfo) { // in this case we want to set the size of the parent window to that of // current DV resolution. // We get that resolution from the IVideoWindow. CComQIPtr pBV(gcap.pVW); if(pBV != NULL) { HRESULT hr1, hr2; long lWidth, lHeight; hr1 = pBV->get_VideoHeight(&lHeight); hr2 = pBV->get_VideoWidth(&lWidth); if(SUCCEEDED(hr1) && SUCCEEDED(hr2)) { ResizeWindow(lWidth, abs(lHeight)); } } } } RECT rc; gcap.pVW->put_Owner((OAHWND)ghwndApp); // We own the window now gcap.pVW->put_WindowStyle(WS_CHILD); // you are now a child // give the preview window all our space but where the status bar is GetClientRect(ghwndApp, &rc); cyBorder = GetSystemMetrics(SM_CYBORDER); cy = statusGetHeight() + cyBorder; rc.bottom -= cy; gcap.pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); // be this big gcap.pVW->put_Visible(OATRUE); } // now tell it what frame rate to capture at. Just find the format it // is capturing with, and leave everything alone but change the frame rate // No big deal if it fails. It's just for preview // !!! Should we then talk to the preview pin? if(gcap.pVSC && gcap.fUseFrameRate) { hr = gcap.pVSC->GetFormat(&pmt); // DV capture does not use a VIDEOINFOHEADER if(hr == NOERROR) { if(pmt->formattype == FORMAT_VideoInfo) { VIDEOINFOHEADER *pvi = (VIDEOINFOHEADER *)pmt->pbFormat; pvi->AvgTimePerFrame = (LONGLONG)(10000000 / gcap.FrameRate); hr = gcap.pVSC->SetFormat(pmt); if(hr != NOERROR) ErrMsg(TEXT("%x: Cannot set frame rate for preview"), hr); } DeleteMediaType(pmt); } } // make sure we process events while we're previewing! hr = gcap.pFg->QueryInterface(IID_IMediaEventEx, (void **)&gcap.pME); if(hr == NOERROR) { gcap.pME->SetNotifyWindow((OAHWND)ghwndApp, WM_FGNOTIFY, 0); } // potential debug output - what the graph looks like // DumpGraph(gcap.pFg, 1); // Add our graph to the running object table, which will allow // the GraphEdit application to "spy" on our graph #ifdef REGISTER_FILTERGRAPH hr = AddGraphToRot(gcap.pFg, &g_dwGraphRegister); if(FAILED(hr)) { ErrMsg(TEXT("Failed to register filter graph with ROT! hr=0x%x"), hr); g_dwGraphRegister = 0; } #endif // All done. gcap.fPreviewGraphBuilt = TRUE; return TRUE; }