在看DXSDK\Samples\C++\DirectShow\Capture\AMCap工程时,发现了这个,于是把它转载过来了
DirectShow 提供了用应用程序从适当的硬件中捕捉和预览音/视频的能力。数据源包括:VCR,camera,TV tuner,microphone,或其他的数据源。一个应用程序可以立刻显示捕捉的数据(预览),或是保存到一个文件中。
在这个例子中,ICaptureGraphBuilder 接口是处理捕捉工作的主要接口。你可以在你自己的捕捉程序中使用同样的方法和接口。在这里主要讨论ICaptureGraphBuilder 如何执行音/视频的捕捉。我们假设你已经熟悉了DirectShow的filter graph的体系和一般的capture filter graph的结构(可以参考DirectShow基础指南)。
ICaptureGraphBuilder 接口提供了一个filter graph builder对象,让你的应用程序在建立capture filter graph时,省去处理很多单调乏味的工作,集中精力于捕捉中。他提供的方法满足了基本的捕捉和预览功能的要求。
方法FindInterface -- 在filter graph中查找一个于捕捉有关的详细的接口。使的你可以访
问一个详细接口的功能,而不需要你去列举在filter graph中的pins
和 filters。
方法RenderStream -- 连接源过滤器和渲染过滤器,选择添加一些中间的过滤器。
方法ControlStream -- 独立的精确的控制graph的开始和结束帧。
既然是硬件捕捉,当然要和硬件打交道,接着介绍设备列举和捕捉接口。
通过ICreateDevEnum::CreateClassEnumerator方法列举捕捉系统中的设备。之后,实例化一个DirectShow的filter去使用这个设备。接着用ICaptureGraphBuilder::FindInterface去获得于捕捉相关的接口指针IAMDroppedFrames, IAMVideoCompression, IAMStreamConfig, and IAMVfwCaptureDialogs 。因为设备列举和捕捉接口比较长,放在这会打乱结构,所有专门写了一篇(参考设备列举和捕捉接口)。
NOTE:
1.这个示例是DirectShow自带的例子。你可以在DirectShow SDK的目录Sample\DS\Caputre看这个例子代码(AMCap.cpp)。这里只是他的一些片断代码。可以说是他的中文模块的说明。
2.AMCap例子中,把所有的接口指针和一些成员变量保存在一个全局结构gcap中了。
定义如下:
struct _capstuff {
char szCaptureFile[_MAX_PATH];
WORD wCapFileSize; // size in Meg
ICaptureGraphBuilder *pBuilder;
IVideoWindow *pVW;
IMediaEventEx *pME;
IAMDroppedFrames *pDF;
IAMVideoCompression *pVC;
IAMVfwCaptureDialogs *pDlg;
IAMStreamConfig *pASC; // for audio cap
IAMStreamConfig *pVSC; // for video cap
IBaseFilter *pRender;
IBaseFilter *pVCap, *pACap;
IGraphBuilder *pFg;
IFileSinkFilter *pSink;
IConfigAviMux *pConfigAviMux;
int iMasterStream;
BOOL fCaptureGraphBuilt;
BOOL fPreviewGraphBuilt;
BOOL fCapturing;
BOOL fPreviewing;
BOOL fCapAudio;
int iVideoDevice;
int iAudioDevice;
double FrameRate;
BOOL fWantPreview;
long lCapStartTime;
long lCapStopTime;
char achFriendlyName[120];
BOOL fUseTimeLimit;
DWORD dwTimeLimit;
} gcap;
当不在需要保存在gcap中的接口指针是,一定要释放这些接口指针,一般是在程序的析构函数中,或是在别的同等功能函数中。如下:
if (gcap.pBuilder)
gcap.pBuilder->Release();
gcap.pBuilder = NULL;
if (gcap.pSink)
gcap.pSink->Release();
gcap.pSink = NULL;
if (gcap.pConfigAviMux)
gcap.pConfigAviMux->Release();
gcap.pConfigAviMux = NULL;
if (gcap.pRender)
gcap.pRender->Release();
gcap.pRender = NULL;
if (gcap.pVW)
gcap.pVW->Release();
gcap.pVW = NULL;
if (gcap.pME)
gcap.pME->Release();
gcap.pME = NULL;
if (gcap.pFg)
gcap.pFg->Release();
gcap.pFg = NULL;
设置文件名
使用普通的OpenFile dialog获得捕捉文件的信息。通过调用AllocCaptureFile 函数为捕捉文件分配空间。这一点是重要的,因为这是个巨大的空间。这样可以提高捕捉操作的速度。ICaptureGraphBuilder::AllocCapFile 执行实际的文件分配,IFileSinkFilter::SetFileName 指示file writer filter使用用户选择的文件名保存数据。ICaptureGraphBuilder::SetOutputFileName 把file writer filter加入filter graph(后面会介绍,他是ICaptureGraphBuilderd自带的)。
SetCaptureFile 和 AllocCaptureFile 函数如下:
/*
* Put up a dialog to allow the user to select a capture file.
*/
BOOL SetCaptureFile(HWND hWnd)
{
if (OpenFileDialog(hWnd, gcap.szCaptureFile, _MAX_PATH))
{
OFSTRUCT os;
// We have a capture file name
/*
* if this is a new file, then invite the user to
* allocate some space
*/
if (OpenFile(gcap.szCaptureFile, &os, OF_EXIST) == HFILE_ERROR)
{
// Bring up dialog, and set new file size
BOOL f = AllocCaptureFile(hWnd);
if (!f)
return FALSE;
}
}
else
{
return FALSE;
}
SetAppCaption(); // need a new app caption
// Tell the file writer to use the new file name
if (gcap.pSink)
{
WCHAR wach[_MAX_PATH];
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, gcap.szCaptureFile, -1, wach, _MAX_PATH);
gcap.pSink->SetFileName(wach, NULL);
}
return TRUE;
}
// Preallocate the capture file
//
BOOL AllocCaptureFile(HWND hWnd)
{
// We'll get into an infinite loop in the dlg proc setting a value
if (gcap.szCaptureFile[0] == 0)
return FALSE;
/*
* show the allocate file space dialog to encourage
* the user to pre-allocate space
*/
if (DoDialog(hWnd, IDD_AllocCapFileSpace, 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;
WCHAR wach[_MAX_PATH];
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, gcap.szCaptureFile, -1, wach, _MAX_PATH);
if (gcap.pBuilder->AllocCapFile(wach, gcap.wCapFileSize * 1024L * 1024L) != NOERROR)
{
MessageBoxA(ghwndApp, "Error", "Failed to pre-allocate capture file space", MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}
return TRUE;
}
else
{
return FALSE;
}
}
建立Graph Builder对象
AMCap的 MakeBuilder函数建立了一个capture graph builer对象,通过调用CoCreateInstance获得了ICaptureGraphBuilder 接口指针。AMCap把他存储到gcap结构的pBuilder中。
// Make a graph builder object we can use for capture graph building
BOOL MakeBuilder()
{
// We have one already
if (gcap.pBuilder)
return TRUE;
HRESULT hr = CoCreateInstance((REFCLSID)CLSID_CaptureGraphBuilder, NULL, CLSCTX_INPROC, (REFIID)IID_ICaptureGraphBuilder, (void **)&gcap.pBuilder);
return (hr == NOERROR) ? TRUE : FALSE;
}
建立Graph的渲染部分,并告诉他写文件(用先前决定的文件)
这包括一个multiplexer filter 和 file writer。DirectShow 提供了一个AVI MUX(multiplexer)filter。
在这里ICaptureGraphBuilder::SetOutputFileName 是一个关键的方法。他把multiplexer 和 file writer添加到filter graph中,连接他们,并设置文件的名字。第一个参数MEDIASUBTYPE_Avi,指出capture graph builder 对象将插入一个AVI multiplexer filter,因此,file writer将以AVI文件格式记录捕捉的数据。第二个参数(wach)是文件名。最后的两个参数指出multiplexer filter (gcap.pRender) 和file writer filter (gcap.pSink),这两个是通过SetOutputFileName 函数初始化的。AMCap存储这些指针到全局结构gcap中。capture graph builder 对象建立了一个filter graph对象(IGraphBuilder),把这两个filter加入到filter graph中去。他告诉file writer使用指定的文件保存数据。下面的例子演示了如何调用SetOutputFileName。
// We need a rendering section that will write the capture file out in AVI
// file format
WCHAR wach[_MAX_PATH];
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, gcap.szCaptureFile, -1, wach, _MAX_PATH);
GUID guid = MEDIASUBTYPE_Avi;
hr = gcap.pBuilder->SetOutputFileName(&guid, wach, &gcap.pRender, &gcap.pSink);
if (hr != NOERROR)
{
ErrMsg("Error %x: Cannot set output file", hr);
goto SetupCaptureFail;
}
获得当前的Filter Graph
因为在调用SetOutputFileName中,capture graph builder 对象建立了一个filter graph,所有你必须把需要的filter加入同一个filter graph 中。通过ICaptureGraphBuilder::GetFiltergraph获得新建立的filter graph。返回的指针是参数gcap.pFg。
// The graph builder created a filter graph to do that. Find out what it is,
// and put the video capture filter in the graph too.
hr = gcap.pBuilder->GetFiltergraph(&gcap.pFg);
if (hr != NOERROR)
{
ErrMsg("Error %x: Cannot get filtergraph", hr);
goto SetupCaptureFail;
}
添加音/视频过滤器到当前的Filter Graph
hr = gcap.pFg->AddFilter(gcap.pVCap, NULL);
if (hr != NOERROR)
{
ErrMsg("Error %x: Cannot add vidcap to filtergraph", hr);
goto SetupPreviewFail;
}
hr = gcap.pFg->AddFilter(gcap.pACap, NULL);
if (hr != NOERROR)
{
ErrMsg("Error %x: Cannot add audcap to filtergraph", hr);
goto SetupCaptureFail;
}
渲染视频捕捉过滤器的Capture Pin和音频捕捉的Capture Pin
ICaptureGraphBuilder::RenderStream 连接源过滤器的pin到渲染过滤器。pin的类别是可选的,capture pin (PIN_CATEGORY_CAPTURE) 或 preview pin (PIN_CATEGORY_PREVIEW)。下面的例子演示了连接video capture filter (gcap.pVCap) 的capture pin到渲染gcap.pRender中。
// Render the video capture and preview pins - we may not have preview, so
// don't worry if it doesn't work
hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, NULL, gcap.pVCap, NULL, gcap.pRender);
// Error checking
再次ICaptureGraphBuilder::RenderStream 连接audio capture filter (gcap.pACap) 到渲染audio renderer 中。
// Render the audio capture pin?
if (gcap.fCapAudio)
{
hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, NULL, gcap.pACap, NULL, gcap.pRender);
// Error checking
渲染Video Capture Filter的 Preview Pin
再次调用ICaptureGraphBuilder::RenderStream,从capture filter的preview pin到video renderer。代码如下:
hr = gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW, NULL, gcap.pVCap, NULL, NULL);
获得访问Video Preview Window的接口指针
缺省的,video preview window是一个独立的窗口。如果你想改变默认的行为,先调用 ICaptureGraphBuilder::FindInterface获得IVideoWindow 接口。第二个参数通过gcap.pVCap指定,描述video capture filter,第三个参数是想得到的接口(IVideoWindow),最后的是返回的接口。当你得到IVideoWindow