分享
 
 
 

directshow的中文资料之建立一个捕捉程序

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

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接口后,你可以调用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把捕捉的数据保存到指定的文件中。

/*

* Put up a dialog to allow the user to save the contents of the capture file

* elsewhere

*/

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 。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有