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