在看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 函数如下:
BOOL SetCaptureFile(HWND hWnd) {
if (OpenFileDialog(hWnd, gcap.szCaptureFile,
_MAX_PATH))
{
OFSTRUCT os;
// We have a capture file name
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;
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接口后,你可以调用IVideoWindow的方法象put_Owner, put_WindowStyle, or
SetWindowPosition 去获得video preview window的handle,设置窗口属性,或把他放到想要的位置。
// This
will go through a possible decoder, find the video renderer
it's //
connected to, and get the IVideoWindow interface on
it hr =
gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW,
gcap.pVCap, IID_IVideoWindow, (void
**)&gcap.pVW); if
(hr != NOERROR)
{
ErrMsg("This graph cannot preview");
}
else
{
RECT rc;
gcap.pVW->put_Owner((long)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);
}
现在你已经建立完整的capture filter
graph了,你可以预览音频,视频,或捕捉数据。
控制 Capture Filter
Graph
因为通过ICaptureGraphBuilder接口构造的capture filter graph 只是一个简单的专门用途的filter graph,所有,控制他就象控制其他类型的filter graph一样。你可以使用IMediaControl
interface的 Run, Pause, and
Stop方法,你也可以使用CBaseFilter::Pause的方法。另外ICaptureGraphBuilder提供了ControlStream方法去控制capture filter graph的streams的开始和结束时间。ControlStream调用IAMStreamControl::StartAt 和 IAMStreamControl::StopAt控制filter graph的捕捉和预览的开始和结束的位置。 注意:不是所有的capture
filter都可以,因为不是每一个capture
filter都支持IAMStreamControl。
ICaptureGraphBuilder::ControlStream方法的第一个参数(pCategory)是一个输出pin类的GUID。这个变量通常是PIN_CATEGORY_CAPTURE
或 PIN_CATEGORY_PREVIEW。指定为NULL则控制所有的capture filter。第二个参数在(pFilter)指出那个filter控制。NULL说明为控制所有的filter graph。如果只是预览(防止捕捉)的话,可以调用ICaptureGraphBuilder::ControlStream,参数用capture pin类型,MAX_TIME作为开始时间(第三个参数,pstart)。再次调用该方法,参数用preview pin类型,NULL作为开始时间则立即开始预览。第四参数指出结束的时间(pstop),含义和第三个参数一样(NULL意味着立刻)。MAX_TIME在DirectShow中定义为最大的参考时间。在这里意味着忽略或取消指定的操作。最后的参数,wStartCookie和wStopCookie分别是开始和结束的cookies(不知道该怎么翻译,因为我也不理解这个参数的含义)。 下面的代码设置立刻开始预览,但是忽略捕捉。
// Let the preview
section run, but not the capture section //
(There might not be a capture section)
REFERENCE_TIME start = MAX_TIME, stop =
MAX_TIME;
//
show us a preview first? but don't capture quite
yet... hr =
gcap.pBuilder->ControlStream(&PIN_CATEGORY_PREVIEW,
NULL, gcap.fWantPreview ? NULL : &start,
gcap.fWantPreview ? &stop : NULL, 0,
0); if
(SUCCEEDED(hr))
hr =
gcap.pBuilder->ControlStream(&PIN_CATEGORY_CAPTURE,
NULL, &start, NULL, 0,
0);
同样的,如果你只想要捕捉而不要预览,设置捕捉的开始时间为NULL,设置捕捉的结束时间为MAX_TIME。设置预览的开始时间为MAX_TIME,NULL为结束时间。
下面的例子告诉filter graph开始预览(第三个参数:开始时间为NULL)。结束时间指定为MAX_TIME意味着忽视停止时间(永远放下去)。
gcap.pBuilder->ControlStream(&PIN_CATEGORY_PREVIEW,
NULL, NULL, MAX_TIME, 0, 0);
调用IMediaControl::Run
运行 graph
// 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("Error %x: Cannot run preview graph", hr);
return FALSE;
}
如果graph已经运行,通过调用ICaptureGraphBuilder::ControlStream立刻开始捕捉。例如下面的代码,控制整个的filter
graph(第二个参数为NULL),立刻开始(第三个参数是NULL),并且永不停止(第四个参数是MAX_TIME)。
//
NOW! gcap.pBuilder->ControlStream(&PIN_CATEGORY_CAPTURE,
NULL, MAX_TIME, &stop, 0, 0);
停止预览或捕捉操作,调用IMediaControl::Stop,就同你调用IMediaControl::Run一样。
// Stop the
graph
IMediaControl *pMC = NULL;
HRESULT hr =
gcap.pFg->QueryInterface(IID_IMediaControl, (void
**)&pMC); if
(SUCCEEDED(hr))
{
hr = pMC->Stop();
pMC->Release();
}
获得捕捉的信息
通过IAMDroppedFrames接口获得。测试丢失帧的数量(IAMDroppedFrames::GetNumDropped),捕捉的数量(IAMDroppedFrames::GetNumNotDropped)。IAMDroppedFrames::GetAverageFrameSize方法提供了捕捉帧的平均尺寸(单位:byte)。使用这些信息可以知道总的捕捉字节和每秒的帧数(速率)。
保存文件
最初分配的捕捉文件只是临时的保存数据,所有你可以尽可能快的捕捉。当你想把捕捉的数据保存到硬盘中时,调用ICaptureGraphBuilder::CopyCaptureFile。这个方法从先前得到的捕捉文件输出数据到你选择的另一个文件中。这个新的储存文件的大小是和实际捕捉的数据匹配的,而不是和先前的文件大小匹配。
ICaptureGraphBuilder::CopyCaptureFile方法的第一个参数是复制源,第二个是目标文件。第三个参数设为TRUE指出用户允许用ESC键中断复制操作。最后参数是可选的。允许你提供一个进程指示器。如果想要的化,通过执行
IAMCopyCaptureFileProgress 接口。下面示例了如何调用CopyCaptureFile。
hr =
pBuilder->CopyCaptureFile(wachSrcFile,
wachDstFile,TRUE,NULL);
通过普通的Open File dialog得到新的文件名。用MultiByteToWideChar
函数把文件名转成wide string,使用ICaptureGraphBuilder::CopyCaptureFile把捕捉的数据保存到指定的文件中。
BOOL SaveCaptureFile(HWND hWnd) {
HRESULT hr; char
achDstFile[_MAX_PATH];
WCHAR wachDstFile[_MAX_PATH];
WCHAR wachSrcFile[_MAX_PATH];
if
(gcap.pBuilder == NULL)
return FALSE;
if
(OpenFileDialog(hWnd, achDstFile, _MAX_PATH))
{
// We have a capture file name
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, gcap.szCaptureFile,
-1, wachSrcFile, _MAX_PATH);
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, achDstFile,
-1, wachDstFile, _MAX_PATH);
statusUpdateStatus(ghwndStatus, "Saving capture file - please
wait...");
// We need our own graph builder because the main one might not
exist
ICaptureGraphBuilder *pBuilder;
hr =
CoCreateInstance((REFCLSID)CLSID_CaptureGraphBuilder, NULL,
CLSCTX_INPROC,
(REFIID)IID_ICaptureGraphBuilder, (void
**)&pBuilder);
if (hr == NOERROR)
{
// Allow the user to press ESC to abort... don't ask for
progress
hr = pBuilder->CopyCaptureFile(wachSrcFile,
wachDstFile,TRUE,NULL);
pBuilder->Release();
}
if (hr == S_OK)
statusUpdateStatus(ghwndStatus, "Capture file
saved");
else if (hr == S_FALSE)
statusUpdateStatus(ghwndStatus, "Capture file save
aborted");
else
statusUpdateStatus(ghwndStatus, "Capture file save
ERROR");
return (hr == NOERROR ? TRUE : FALSE);
}
else
{
return TRUE;
// They canceled or something
} }
关于捕捉媒体文件和获得捕捉信息的详细内容,可以参考AMCap例子的Amcap.cpp 和 Status.cpp 。
DirectShow的中文资料之设备列举和捕捉接口
zz
|
|
|
这篇解释和示例如何通过DirectShow的接口去初始化和访问系统的硬件设备。代表性的,DirectShow应用程序使用下面类型的硬件。
音/视频捕捉卡 音频或视频回放卡 音频或视频压缩或解压卡(象MPEG解码器) 下面将以AV设备作参考。
如何列举设备
在DirectShow SDK中的接口,类,和例子提供了音/视频捕捉和回放的功能。因为文件源过滤器和filter graph manager处理了内在的工作,所有,添加捕捉功能到一个应用程序中,只需添加很少的代码。你可以通过列举系统硬件设备和得到设备列表完成特别的任务(例如:所有的视频捕捉卡的列表)。DirectShow自动为win32和Video for Windows 设备实例化过滤器。
要AV设备工作,首先,你必须检测当前系统存在的设备。ICreateDevEnum接口建立指定类型的列表。提供你需要的检测和设置硬件的功能。访问一个指定的设备有三步,详细的说明和代码如下:
建立系统硬件设备的列表
首先,申明一个列表指针,然后通过
CoCreateInstance 建立。CLSID_SystemDeviceEnum是我们想建立对象的类型,IID_ICreateDevEnum是接口的GUID。
ICreateDevEnum *pCreateDevEnum ;
CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,
(void**)&pCreateDevEnum);
其次,建立一个特别类型的硬件设备的列表(例如视频捕捉卡) 申明一个IEnumMoniker接口,并把他传给ICreateDevEnum::CreateClassEnumerator
方法。你就可以使用他访问新得到的列表了。
IEnumMoniker *pEnumMon ;
pCreateDevEnum->CreateClassEnumerator( [specify
device GUID here], &pEnumMon, 0);
最后,列举列表直到你得到你想要的设备为止。 如果先前的CreateClassEnumerator调用成功了,你可以用IEnumMoniker::Next得到设备。调用IMoniker::BindToObject建立一个和选择的device联合的filter,并且装载filter的属性(CLSID,FriendlyName, and
DevicePath)。不需要为if语句的(1 == cFetched) 困惑,在测试合法性之前,pEnumMon->Next(1,
&pMon,
&cFetched)方法会设置他为返回对象的数字(如果成功了为1)。
ULONG cFetched = 0;
IMoniker
*pMon ;
if
(S_OK == (pEnumMon->Next(1, &pMon,
&cFetched))
&& (1 ==
cFetched))
{
pMon->BindToObject(0, 0, IID_IBaseFilter, (void
**)&[desired interface here]) ;
好,现在你有了一个IMoniker指针,你可以添加设备的filter到filter graph。一旦你添加了filter,你就不需要IMoniker指针,设备列表,或系统设备列表。
pGraph->AddFilter([desired
interface here], L"[filter name here]") ;
pMon->Release()
; // Release moniker
}
pEnumMon->Release() ; // Release
the class enumerator
}
pCreateDevEnum->Release();
实例:AMCap中的设备列表代码
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 | | |