本篇文档主要描述关于用Directshow进行视频开发的一些技术
Directshow中的视频捕捉(转)感觉这个非常全,但真的不知道出处了… | 姜糖水 http://t.cn/RU0vNki
主要包括下面内容
- 1关于视频捕捉(About Video Capture in Dshow)
- 2选择一个视频捕捉设备(Select capture device)
- 3预览视频(Previewing Video)
- 4如何捕捉视频流并保存到文件(Capture video to File)
- 5将设备从系统中移走时的事件通知(Device remove Notify)
- 6如何控制Capture Graph(Controlling Capture Graph)
- 7如何配置一个视频捕捉设备
- 8从静止图像pin中捕捉图片
1关于视频捕捉(About Video Capture in Dshow)
1视频捕捉Graph的构建
一个能够捕捉音频或者视频的graph图都称之为捕捉graph图。捕捉graph图比一般的文件回放graph图要复杂许多,dshow提供了一个 Capture Graph Builder COM组件使得捕捉graph图的生成更加简单。Capture Graph Builder提供了一个ICaptureGraphBuilder2接口,这个接口提供了一些方法用来构建和控制捕捉graph。
首先创建一个Capture Graph Builder对象和一个graph manger对象,然后用filter graph manager 作参数,调用ICaptureGraphBuilder2::SetFiltergraph来初始化Capture Graph Builder。
看下面的代码吧
HRESULT InitCaptureGraphBuilder(
IGraphBuilder **ppGraph, // Receives the pointer.
ICaptureGraphBuilder2 **ppBuild // Receives the pointer.
)
{
if (!ppGraph || !ppBuild)
{
return E_POINTER;
}
IGraphBuilder *pGraph = NULL;
ICaptureGraphBuilder2 *pBuild = NULL;
// Create the Capture Graph Builder.
HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL,
CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (void**)&pGraph);
if (SUCCEEDED(hr))
{
// Create the Filter Graph Manager.
hr = CoCreateInstance(CLSID_FilterGraph, 0, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void**)&pGraph);
if (SUCCEEDED(hr))
{
// Initialize the Capture Graph Builder.
pBuild->SetFiltergraph(pGraph);
// Return both interface pointers to the caller.
*ppBuild = pBuild;
*ppGraph = pGraph; // The caller must release both interfaces.
return S_OK;
}
else
{
pBuild->Release();
}
}
return hr; // Failed
}
2视频捕捉的设备
现在许多新的视频捕捉设备都采用的是WDM驱动方法,在WDM机制中,微软提供了一个独立于硬件设备的驱动,称为类驱动程序。驱动程序的供应商提供的驱动程序称为minidrivers。Minidrivers提供了直接和硬件打交道的函数,在这些函数中调用了类驱动。
在directshow的filter图表中,任何一个WDM捕捉设备都是做为一个WDM Video Capture过滤器(Filter)出现。
WDM Video Capture过滤器根据驱动程序的特征构建自己的filter
下面是陆其明的一篇有关于dshow和硬件的文章,可以拿来参考一下
//陆文章开始
大家知道,为了提高系统的稳定性,Windows操作系统对硬件操作进行了隔离;应用程序一般不能直接访问硬件。DirectShow Filter工作在用户模式(User mode,操作系统特权级别为Ring 3),而硬件工作在内核模式(Kernel mode,操作系统特权级别为Ring 0),那么它们之间怎么协同工作呢?
DirectShow解决的方法是,为这些硬件设计包装Filter;这种Filter能够工作在用户模式下,外观、控制方法跟普通Filter一样,而包装Filter内部完成与硬件驱动程序的交互。这样的设计,使得编写DirectShow应用程序的开发人员,从为支持硬件而需做出的特殊处理中解脱出来。DirectShow已经集成的包装Filter,包括Audio Capture Filter(qcap.dll)、VfW Capture Filter(qcap.dll,Filter的Class Id为CLSID_VfwCapture)、TV Tuner Filter(KSTVTune.ax,Filter的Class Id为CLSID_CTVTunerFilter)、Analog Video Crossbar Filter(ksxbar.ax)、TV Audio Filter(Filter的Class Id为CLSID_TVAudioFilter)等;另外,DirectShow为采用WDM驱动程序的硬件设计了KsProxy Filter(Ksproxy.ax,)。我们来看一下结构图:
图1...挂了
从上图中,我们可以看出,Ksproxy.ax、Kstune.ax、Ksxbar.ax这些包装Filter跟其它普通的DirectShow Filter处于同一个级别,可以协同工作;用户模式下的Filter通过Stream Class控制硬件的驱动程序minidriver(由硬件厂商提供的实现对硬件控制功能的DLL);Stream Class和minidriver一起向上层提供系统底层级别的服务。值得注意的是,这里的Stream Class是一种驱动模型,它负责调用硬件的minidriver;另外,Stream Class的功能还在于协调minidriver之间的工作,使得一些数据可以直接在Kernel mode下从一个硬件传输到另一个硬件(或同一个硬件上的不同功能模块),提高了系统的工作效率。(更多的关于底层驱动程序的细节,请读者参阅Windows DDK。)
下面,我们分别来看一下几种常见的硬件。
VfW视频采集卡。这类硬件在市场上已经处于一种淘汰的趋势;新生产的视频采集卡一般采用WDM驱动模型。但是,DirectShow为了保持向后兼容,还是专门提供了一个包装Filter支持这种硬件。和其他硬件的包装Filter一样,这种包装Filter的创建不是像普通Filter一样使用CoCreateInstance,而要通过系统枚举,然后BindToObject。
音频采集卡(声卡)。声卡的采集功能也是通过包装Filter来实现的;而且现在的声卡大部分都有混音的功能。这个Filter一般有几个Input pin,每个pin都代表一个输入,如Line In、Microphone、CD、MIDI等。值得注意的是,这些pin代表的是声卡上的物理输入端子,在Filter Graph中是永远不会连接到其他Filter上的。声卡的输出功能,可以有两个Filter供选择:DirectSound Renderer Filter和Audio Renderer (WaveOut) Filter。注意,这两个Filter不是上述意义上的包装Filter,它们能够同硬件交互,是因为它们使用了API函数:前者使用了DirectSound API,后者使用了waveOut API。这两个Filter的区别,还在于后者输出音频的同时不支持混音。(顺便说明一下,Video Renderer Filter能够访问显卡,也是因为使用了GDI、DirectDraw或Direct3D API。)如果你的机器上有声卡的话,你可以通过GraphEdit,在Audio Capture Sources目录下看到这个声卡的包装Filter。
WDM驱动的硬件(包括视频捕捉卡、硬件解压卡等)。这类硬件都使用Ksproxy.ax这个包装Filter。Ksproxy.ax实现了很多功能,所以有“瑞士军刀”的美誉;它还被称作为“变色龙Filter”,因为该Filter上定义了统一的接口,而接口的实现因具体的硬件驱动程序而异。在Filter Graph中,Ksproxy Filter显示的名字为硬件的Friendly name(一般在驱动程序的.inf文件中定义)。
我们可以通过GraphEdit,在WDM Streaming开头的目录中找到本机系统中安装的WDM硬件。因为KsProxy.ax能够代表各种WDM的音视频设备,所以这个包装Filter的工作流程有点复杂。这个Filter不会预先知道要代表哪种类型的设备,它必须首先访问驱动程序的属性集,然后动态配置Filter上应该实现的接口。
当Ksproxy Filter上的接口方法被应用程序或其他Filter调用时,它会将调用方法以及参数传递给驱动程序,由驱动程序最终完成指定功能。除此以外,WDM硬件还支持内核流(Kernel Streaming),即内核模式下的数据传输,而无需经过到用户模式的转换。因为内核模式与用户模式之间的相互转换,需要花费很大的计算量。如果使用内核流,不仅可以避免大量的计算,还避免了内核数据与主机内存之间的拷贝过程。在这种情况下,用户模式的Filter Graph中,即使pin之间是连接的,也不会有实际的数据流动。典型的情况,如带有Video Port Pin的视频捕捉卡,Preview时显示的图像就是在内核模式下直接传送到显卡的显存的。所以,你也休想在VP Pin后面截获数据流。
讲到这里,我想大家应该对DirectShow对硬件的支持问题有了一个总体的认识。对于应用程序开发人员来说,这方面的内容不用研究得太透,而只需作为背景知识了解一下就好了。其实,大量繁琐的工作DirectShow已经帮我们做好了。
//陆其明文章结束
Direcshow中视频捕捉的Filter
Pin的种类
捕捉Filter一般都有两个或多个输出pin,他们输出的媒体类型都一样,比如预览pin和捕捉pin,因此根据媒体类型就不能很好的区别这些pin。此时就要根据pin的功能来区别每个pin了,每个pin都有一个GUID,称为pin的种类。
如果想仔细的了解pin的种类,请看后面的相关内容Working with Pin Categories。对于大多数的应用来说,ICaptureGraphBuilder2提供了一些函数可以自动确定pin的种类。
预览pin和捕捉pin
视频捕捉Filter都提供了预览和捕捉的输出pin,预览pin用来将视频流在屏幕上显示,捕捉pin用来将视频流写入文件。
预览pin和输出pin有下面的区别:
1 为了保证捕捉pin对视频桢流量,预览pin必要的时候可以停止。
2 经过捕捉pin的视频桢都有时间戳,但是预览pin的视频流没有时间戳。
预览pin的视频流之所以没有时间戳的原因在于filter图表管理器在视频流里加一个很小的latency,如果捕捉时间被认为就是render时间的话,视频renderFilter就认为视频流有一个小小的延迟,如果此时render filter试图连续播放的时候,就会丢桢。去掉时间戳就保证了视频桢来了就可以播放,不用等待,也不丢桢。
预览pin的种类GUID为PIN_CATEGORY_PREVIEW
捕捉pin的种类GUID为PIN_CATEGORY_CAPTURE
Video Port pin
Video Port是一个介于视频设备(TV)和视频卡之间的硬件设备。同过Video Port,视频数据可以直接发送到图像卡上,通过硬件的覆盖,视频可以直接在屏幕显示出来。Video Port就是连接两个设备的。
使用Video Port的最大好处是,不用CPU的任何工作,视频流直接写入内存中。当然它也有下面的缺点drawbacks:
略
如果捕捉设备使用了Video Port,捕捉Filter就用一个video port pin代替预览pin。
video port pin的种类GUID为PIN_CATEGORY_VIDEOPORT
一个捕捉filter至少有一个Capture pin,另外,它可能有一个预览pin 和一个video port pin
,或者两者都没有,也许filter有很多的capture pin,和预览pin,每一个pin都代表一种媒体类型,因此一个filter可以有一个视频capture pin,视频预览pin,音频捕捉pin,音频预览pin。
Upstream WDM Filters
在捕捉Filter之上,WDM设备可能需要额外的filters,下面就是这些filter
TV Tuner Filter
TV Audio Filter.
Analog Video Crossbar Filter
尽管这些都是一些独立的filter,但是他们可能代表的是同一个硬件设备,每个filter都控制设备的不同函数,这些filter通过pin连接起来,但是在pin中没有数据流动。因此,这些pin 的连接和媒体类型无关。他们使用一个GUID值来定义一个给定设备的minidriver 例如:TV tuner Filter 和video capture filter都支持同一种medium。
在实际应用中,如果你使用ICaptureGraphBuilder2来创建你的capture graphs,这些filters就会自动被添加到你的graph中。更多的详细资料,可以参考WDM Class Driver Filters
2选择一个视频捕捉设备(Select capture device)
如何选择一个视频捕捉设备,可以采用系统设备枚举,详细资料参见Using the System Device Enumerator 。enumerator可以根据filter的种类返回一个设备的monikers。Moniker是一个com对象,可以参见IMoniker的SDK。
对于捕捉设备,下面两种类是相关的。
CLSID_AudioInputDeviceCategory 音频设备
CLSID_VideoInputDeviceCategory 视频设备
下面的代码演示了如何枚举一个视频捕捉设备
ICreateDevEnum *pDevEnum = NULL;
IEnumMoniker *pEnum = NULL;
// Create the System Device Enumerator.
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
CLSCTX_INPROC_SERVER, IID_ICreateDevEnum,
reinterpret_cast
if (SUCCEEDED(hr))
{
//创建一个枚举器,枚举视频设备
hr = pDevEnum->CreateClassEnumerator( CLSID_VideoInputDeviceCategory,
&pEnum, 0);
}
IEnumMoniker接口pEnum返回一个IMoniker接口的列表,代表一系列的moniker,你可以显示所有的设备,然后让用户选择一个。
采用IMoniker::BindToStorage方法,返回一个IPropertyBag接口指针。然后调用IPropertyBag::Read读取moniker的属性。下面看看都包含什么属性
1 FriendlyName 是设备的名字
2 Description 属性仅仅适用于DV和D-VHS/MPEG摄象机,如果这个属性可用,这个属性更详细的描述了设备的资料
3DevicePath 这个属性是不可读的,但是每个设备都有一个独一无二的。你可以用这个属性来区别同一个设备的不同实例
下面的代码演示了如何显示遍历设备的名称 ,接上面的代码
HWND hList; // Handle to the list box.
IMoniker *pMoniker = NULL;
while (pEnum->Next(1, &pMoniker, NULL) == S_OK)
{
IPropertyBag *pPropBag;
hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag,
(void**)(&pPropBag));
if (FAILED(hr))
{
pMoniker->Release();
continue; // Skip this one, maybe the next one will work.
}
// Find the description or friendly name.
VARIANT varName;
VariantInit(&varName);
hr = pPropBag->Read(L"Description", &varName, 0);
if (FAILED(hr))
{
hr = pPropBag->Read(L"FriendlyName", &varName, 0);
}
if (SUCCEEDED(hr))
{
// Add it to the application's list box.
USES_CONVERSION;
(long)SendMessage(hList, LB_ADDSTRING, 0,
(LPARAM)OLE2T(varName.bstrVal));
VariantClear(&varName);
}
pPropBag->Release();
pMoniker->Release();
}
如果用户选中了一个设备调用IMoniker::BindToObject为设备生成filter,然后将filter加入到graph中。
IBaseFilter *pCap = NULL;
hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pCap);
if (SUCCEEDED(hr))
{
hr = m_pGraph->AddFilter(pCap, L"Capture Filter");
}
3预览视频(Previewing Video)
为了创建可以预览视频的graph,可以调用下面的代码
ICaptureGraphBuilder2 *pBuild; // Capture Graph Builder
// Initialize pBuild (not shown).
IBaseFilter *pCap; // Video capture filter.
/* Initialize pCap and add it to the filter graph (not shown). */
hr = pBuild->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, pCap, NULL, NULL);
4如何捕捉视频流并保存到文件(Capture video to File)
1 将视频流保存到AVI文件
下面的图表显示了graph图
图2 .............Over
AVI Mux filter接收从capture pin过来的视频流,然后将其打包成AVI流。音频流也可以连接到AVI Mux Filter上,这样mux filter就将视频流和视频流合成AVI流。File writer将AVI流写入到文件中。
可以像下面这样构建graph图
IBaseFilter *pMux;
hr = pBuild->SetOutputFileName(
&MEDIASUBTYPE_Avi, // Specifies AVI for the target file.
L"C:\\Example.avi", // File name.
&pMux, // Receives a pointer to the mux.
NULL); // (Optional) Receives a pointer to the file sink.
第一个参数表明文件的类型,这里表明是AVI,第二个参数是制定文件的名称。对于AVI文件,SetOutputFileName函数会创建一个AVI mux Filter 和一个 File writer Filter ,并且将两个filter添加到graph图中,在这个函数中,通过File Writer Filter 请求IFileSinkFilter接口,然后调用IFileSinkFilter::SetFileName方法,设置文件的名称。
然后将两个filter连接起来。第三个参数返回一个指向 AVI Mux的指针,同时,它也通过第四个参数返回一个IFileSinkFilter参数,如果你不需要这个参数,你可以将这个参数设置成NULL。
然后,你应该调用下面的函数将capture filter 和AVI Mux连接起来。
hr = pBuild->RenderStream(
&PIN_CATEGORY_CAPTURE, // Pin category.
&MEDIATYPE_Video, // Media type.
pCap, // Capture filter.
NULL, // Intermediate filter (optional).
pMux); // Mux or file sink filter.
// Release the mux filter.
pMux->Release();
第5个参数就是使用的上面函数返回的pMux指针。
当捕捉音频的时候,媒体类型要设置为MEDIATYPE_Audio,如果你从两个不同的设备捕捉视频和音频,你最好将音频设置成主流,这样可以防止两个数据流间drift,因为avi mux filter为同步音频,会调整视频的播放速度的。为了设置master 流,调用IConfigAviMux::SetMasterStream方法,可以采用如下的代码:
IConfigAviMux *pConfigMux = NULL;
hr = pMux->QueryInterface(IID_IConfigAviMux, (void**)&pConfigMux);
if (SUCCEEDED(hr))
{
pConfigMux->SetMasterStream(1);
pConfigMux->Release();
}
SetMasterStream的参数指的是数据流的数目,这个是由调用RenderStream的次序决定的。例如,如果你调用RenderStream首先用于视频流,然后是音频,那么视频流就是0,音频流就是1。
添加编码filter
IBaseFilter *pEncoder;
/* Create the encoder filter (not shown). */
// Add it to the filter graph.
pGraph->AddFilter(pEncoder, L"Encoder);
/* Call SetOutputFileName as shown previously. */
// Render the stream.
hr = pBuild->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video,
pCap, pEncoder, pMux);
pEncoder->Release();
2将视频流保存成wmv格式的文件
为了将视频流保存成并编码成windows media video (WMV)格式的文件,将capture pin连到WM ASF Writer filter。
图3
构建graph图最简单的方法就是将在ICaptureGraphBuilder2::SetOutputFileName方法中指定MEDIASUBTYPE_Asf的filter。如下
IBaseFilter* pASFWriter = 0;
hr = pBuild->SetOutputFileName(
&MEDIASUBTYPE_Asf, // Create a Windows Media file.
L"C:\\VidCap.wmv", // File name.
&pASFWriter, // Receives a pointer to the filter.
NULL); // Receives an IFileSinkFilter interface pointer (optional).
参数MEDIASUBTYPE_Asf 告诉graph builder,要使用wm asf writer作为文件接收器,于是,pbuild 就创建这个filter,将其添加到graph图中,然后调用IFileSinkFilter::SetFileName来设置输出文件的名字。第三个参数用来返回一个ASF writer指针,第四个参数用来返回文件的指针。
在将任何pin连接到WM ASF Writer之前,一定要对WM ASF Writer进行一下设置,你可以同过WM ASF Writer的IConfigAsfWriter接口指针来进行设置。
IConfigAsfWriter *pConfig = 0;
hr = pASFWriter->QueryInterface(IID_IConfigAsfWriter, (void**)&pConfig);
if (SUCCEEDED(hr))
{
// Configure the ASF Writer filter.
pConfig->Release();
}
然后调用ICaptureGraphBuilder2::RenderStream将capture Filter 和 ASF writer连接起来。
hr = pBuild->RenderStream(
&PIN_CATEGORY_CAPTURE, // Capture pin.
&MEDIATYPE_Video, // Video. Use MEDIATYPE_Audio for audio.
pCap, // Pointer to the capture filter.
0,
pASFWriter); // Pointer to the sink filter (ASF Writer).
3保存成自定义的文件格式
如果你想将文件保存成自己的格式,你必须有自己的 file writer。看下面的代码
IBaseFilter *pMux = 0;
IFileSinkFilter *pSink = 0;
hr = pBuild->SetOutputFileName(
&CLSID_MyCustomMuxFilter, //自己开发的Filter
L"C:\\VidCap.avi", &pMux, &pSink);
4如何将视频流保存进多个文件
当你将视频流保存进一个文件后,如果你想开始保存第二个文件,这时,你应该首先将graph停止,然后通过IFileSinkFilter::SetFileName改变 File Writer 的文件名称。注意,IFileSinkFilter指针你可以在SetOutputFileName时通过第四个参数返回的。
看看保存多个文件的代码吧
IBaseFilter *pMux;
IFileSinkFilter *pSink
hr = pBuild->SetOutputFileName(&MEDIASUBTYPE_Avi, L"C:\\YourFileName.avi",
&pMux, &pSink);
if (SUCCEEDED(hr))
{
hr = pBuild->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video,
pCap, NULL, pMux);
if (SUCCEEDED(hr))
{
pControl->Run();
/* Wait awhile, then stop the graph. */
pControl->Stop();
// Change the file name and run the graph again.
pSink->SetFileName(L”YourFileName02.avi”, 0);
pControl->Run();
}
pMux->Release();
pSink->Release();
}
5组合视频的捕捉和预览
如果想组建一个既可以预览视频,又可以将视频保存成文件的graph,只需要两次调用ICaptureGraphBuilder2::RenderStream即可。代码如下:
// Render the preview stream to the video renderer.
hr = pBuild->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, pCap, NULL, NULL);
// Render the capture stream to the mux.
hr = pBuild->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pCap, NULL, pMux);
在上面的代码中,graph builder 其实隐藏了下面的细节。
1 如果capture Filter既有preview pin 也有captrue pin,那么RenderStream仅仅将两个pin和render filter接起来。如下图
图4
2如果caprture Filter只有一个capture pin,那么Capture Graph Builder就采用一个Smart Tee Filter将视频流分流,graph图如下
图5
5如何控制Capture Graph(Controlling Capture Graph)
Filter图表管理器可以通过IMediaControl接口控制整个graph的运行,停止和暂停。但是当一个graph有捕捉和预览两个数据流的时候,如果我们想单独的控制其中的一个数据流话,我们可以通过ICaptureGraphBuilder2::ControlStream 。
下面讲一下如何来单独控制捕捉和预览数据流。
1 控制捕捉视频流
下面的代码,让捕捉数据流在graph开始运行1秒后开始,允运行4秒后结束。
// Control the video capture stream.
REFERENCE_TIME rtStart = 1000 0000, rtStop = 5000 0000;
const WORD wStartCookie = 1, wStopCookie = 2; // Arbitrary values.
hr = pBuild->ControlStream(
&PIN_CATEGORY_CAPTURE, // Pin category.
&MEDIATYPE_Video, // Media type.
pCap, // Capture filter.
&rtStart, &rtStop, // Start and stop times.
wStartCookie, wStopCookie // Values for the start and stop events.
);
pControl->Run();
第一个参数表明需要控制的数据流,一般采用的是pin种类GUID,
第二个参数表明了媒体类型。
第三个参数指明了捕捉的filter。如果想要控制graph图中的所有捕捉filter,第二个和第三个参数都要设置成NULL。
第四和第五个参数表明了流开始和结束的时间,这是一个相对于graph开始的时间。
只有你调用IMediaControl::Run以后,这个函数才有作用。如果graph正在运行,这个设置立即生效。
最后的两个参数用来设置当数据流停止,开始能够得到的事件通知。对于任何一个运用此方法的数据流,graph当流开始的时候,会发送EC_STREAM_CONTROL_STARTED通知,在流结束的时候,要发送EC_STREAM_CONTROL_STOPPED通知。wStartCookie和wStopCookie是作为第二个参数的。
看看事件通知处理过程吧
while (hr = pEvent->GetEvent(&evCode, ¶m1, ¶m2, 0), SUCCEEDED(hr))
{
switch (evCode)
{
case EC_STREAM_CONTROL_STARTED:
// param2 == wStartCookie
break;
case EC_STREAM_CONTROL_STOPPED:
// param2 == wStopCookie
break;
}
pEvent->FreeEventParams(evCode, param1, param2);
}
ControlStream还定义了一些特定的值来表示开始和停止的时间。
MAXLONGLONG 从不开始,只有在graph停止的时候才停止
NULL, 立即开始和停止
例如,下面的代码立即停止捕捉流。
pBuild->ControlStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, pCap,
0, 0, // Start and stop times.
wStartCookie, wStopCookie);
2控制预览视频流
只要给ControlStream第一个参数设置成PIN_CATEGORY_PREVIEW就可以控制预览pin,整个函数的使用和控制捕捉流一样,但是唯一区别是在这里你没法设置开始和结束时间了,因为预览的视频流没有时间戳,因此你必须使用NULL或者MAXLONGLONG。例子
Use NULL to start the preview stream:
pBuild->ControlStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, pCap,
NULL, // Start now.
0, // (Don't care.)
wStartCookie, wStopCookie);
Use MAXLONGLONG to stop the preview stream:
pBuild->ControlStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, pCap,
0, // (Don't care.)
MAXLONGLONG, // Stop now.
wStartCookie, wStopCookie);
3关于数据流的控制
Pin的缺省的行为是传递sample,例如,如果你对PIN_CATEGORY_CAPTURE使用了ControlStream,但是对于PIN_CATEGORY_PREVIEW没有使用该函数,因此,当你run graph的时候,preview 流会立即运行起来,而capture 流则要等到你设置的时间运行。
6如何配置一个视频捕捉设备
1显示VFW驱动的视频设备对话框
如果视频捕捉设备采用的仍然是VFW方式的驱动程序,则必须支持下面三个对话框,用来设置视频设备。
1 Video Source
用来选择视频输入设备并且调整设备的设置,比如亮度和对比度。
2Video Format
用来设置桢的大小和位
3Video Display
用来设置视频的显示参数
为了显示上面的三个对话框,你可以do the following
1 停止graph。
2向捕捉filter请求IAMVfwCaptureDialogs接口,如果成功,表明设备支持VFW驱动。
3调用IAMVfwCaptureDialogs::HasDialog来检查驱动程序是否支持你请求的对话框,如果支持,返回S_OK,否则返回S_FALSE。注意不要用SUCCEDED宏。
4如果驱动支持该对话框,调用IAMVfwCaptureDialogs::ShowDialog显示该对话框。
5 重新运行graph
代码如下
pControl->Stop(); // Stop the graph.
// Query the capture filter for the IAMVfwCaptureDialogs interface.
IAMVfwCaptureDialogs *pVfw = 0;
hr = pCap->QueryInterface(IID_IAMVfwCaptureDialogs, (void**)&pVfw);
if (SUCCEEDED(hr))
{
// Check if the device supports this dialog box.
if (S_OK == pVfw->HasDialog(VfwCaptureDialog_Source))
{
// Show the dialog box.
hr = pVfw->ShowDialog(VfwCaptureDialog_Source, hwndParent);
}
}
pControl->Run();
2 调整视频的质量
WDM驱动的设备支持一些属性可以用来调整视频的质量,比如亮度,对比度,饱和度,等要向调整视频的质量,do the following
1 从捕捉filter上请求IAMVideoProcAmp接口
2 对于你想调整的任何一个属性,调用IAMVideoProcAmp::GetRange可以返回这个属性赋值的范围,缺省值,最小的增量值。IAMVideoProcAmp::Get返回当前正在使用的值。VideoProcAmpProperty枚举每个属性定义的标志。
3调用IAMVideoProcAmp::Set来设置这个属性值。设置属性的时候,不用停止graph。
看看下面的代码是如何调整视频的质量的
HWND hTrackbar; // Handle to the trackbar control.
// Initialize hTrackbar (not shown).
// Query the capture filter for the IAMVideoProcAmp interface.
IAMVideoProcAmp *pProcAmp = 0;
hr = pCap->QueryInterface(IID_IAMVideoProcAmp, (void**)&pProcAmp);
if (FAILED(hr))
{
// The device does not support IAMVideoProcAmp, so disable the control.
EnableWindow(hTrackbar, FALSE);
}
else
{
long Min, Max, Step, Default, Flags, Val;
// Get the range and default value.
hr = m_pProcAmp->GetRange(VideoProcAmp_Brightness, &Min, &Max, &Step,
&Default, &Flags);
if (SUCCEEDED(hr))
{
// Get the current value.
hr = m_pProcAmp->Get(VideoProcAmp_Brightness, &Val, &Flags);
}
if (SUCCEEDED(hr))
{
// Set the trackbar range and position.
SendMessage(hTrackbar, TBM_SETRANGE, TRUE, MAKELONG(Min, Max));
SendMessage(hTrackbar, TBM_SETPOS, TRUE, Val);
EnableWindow(hTrackbar, TRUE);
}
else
{
// This property is not supported, so disable the control.
EnableWindow(hTrackbar, FALSE);
}
}
3调整视频输出格式
我们知道视频流可以有多种输出格式,一个设备可以支持16-bit RGB, 32-bit RGB, and YUYV,在每一种格式下,设备还可以调整视频桢的大小。
在WDM驱动设备上,IAMStreamConfig 接口用来报告设备输出视频的格式的,VFW设备,可以采用对话框的方式来设置,参见前面的内容。
捕捉Filter的捕捉pin和预览pin都支持IAMStreamConfig 接口,可以通过ICaptureGraphBuilder2::FindInterface获得IAMStreamConfig接口。
IAMStreamConfig *pConfig = NULL;
hr = pBuild->FindInterface(
&PIN_CATEGORY_PREVIEW, // Preview pin.
0, // Any media type.
pCap, // Pointer to the capture filter.
IID_IAMStreamConfig, (void**)&pConfig);
设备还支持一系列的媒体类型,对于每一个媒体类型,设备都要支持一系列的属性,比如,桢的大小,图像如何缩放,桢率的范围等。
通过IAMStreamConfig::GetNumberOfCapabilities获得设备所支持的媒体类型的数量。这个方法返回两个值,一个是媒体类型的数量,二是属性所需结构的大小。
这个结构的大小很重要,因为这个方法是用于视频和音频的,视频采用的是VIDEO_STREAM_CONFIG_CAPS结构,音频用AUDIO_STREAM_CONFIG_CAPS结构。
通过函数IAMStreamConfig::GetStreamCaps来枚举媒体类型,要给这个函数传递一个序号作为参数,这个函数返回媒体类型和相应的属性结构体。看代码把
int iCount = 0, iSize = 0;
hr = pConfig->GetNumberOfCapabilities(&iCount, &iSize);
// Check the size to make sure we pass in the correct structure.
if (iSize == sizeof(VIDEO_STREAM_CONFIG_CAPS)
{
// Use the video capabilities structure.
for (int iFormat = 0; iFormat < iCount; iFormat++)
{
VIDEO_STREAM_CONFIG_CAPS scc;
AM_MEDIA_TYPE *pmtConfig;
hr = pConfig->GetStreamCaps(iFormat, &pmtConfig, (BYTE*)&scc);
if (SUCCEEDED(hr))
{
/* Examine the format, and possibly use it. */
// Delete the media type when you are done.
hr = pConfig->SetFormat(pmtConfig);//重新设置视频格式
DeleteMediaType(pmtConfig);
}
}
你可以调用IAMStreamConfig::SetFormat设置新的媒体类型
hr = pConfig->SetFormat(pmtConfig);
如果pin没有连接,当连接的时候就试图用新的格式,如果pin已经在连接了,它就会用的新的媒体格式重新连接。在任何一种情况下,下游的filter都有可能拒绝新的媒体格式。
在SetFormat前你可以修改VIDEO_STREAM_CONFIG_CAPS结构来重新设置媒体类型。
例如:
如果GetStreamCaps返回的是24-bit RGB format,桢的大小是320 x 240 像素,你可以通过检查媒体类型的major type,subtpye,和format等值
if ((pmtConfig.majortype == MEDIATYPE_Video) &&
(pmtConfig.subtype == MEDIASUBTYPE_RGB24) &&
(pmtConfig.formattype == FORMAT_VideoInfo) &&
(pmtConfig.cbFormat >= sizeof (VIDEOINFOHEADER)) &&
(pmtConfig.pbFormat != NULL))
{
VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)pmtConfig.pbFormat;
// pVih contains the detailed format information.
LONG lWidth = pVih->bmiHeader.biWidth;
LONG lHeight = pVih->bmiHeader.biHeight;
}
VIDEO_STREAM_CONFIG_CAPS结构里包含了该媒体类型的视频长度和宽度的最大值和最小值,还有递增的幅度值,就是每次调整视频size的幅度,例如,设备可能返回如下的值
? MinOutputSize: 160 x 120
? MaxOutputSize: 320 x 240
? OutputGranularityX: 8 pixels (horizontal step size)
? OutputGranularityY: 8 pixels (vertical step size)
这样你可以在(160, 168, 176, … 304, 312, 320) 范围内设置宽度,在 (120, 128, 136, … 104, 112, 120).设置高度值,
图6
如果想设置新的值,直接修改在GetStreamCaps函数中返回的值即可,
pVih->bmiHeader.biWidth = 160;
pVih->bmiHeader.biHeight = 120;
pVih->bmiHeader.biSizeImage = DIBSIZE(pVih->bmiHeader);
然后将媒体类型传递给SetFormat函数,就可修改视频格式了。
7将设备从系统中移走时的事件通知(Device remove Notify)
如果用户将一个graph正在使用的即插即用型的设备从系统中去掉,filter图表管理器就会发送一个EC_DEVICE_LOST事件通知,如果该设备又可以使用了,filter图表管理器就发送另外的一个EC_DEVICE_LOST通知,但是先前组建的捕捉filter graph图就没法用了,用户必须重新组建graph图。
当系统中有新的设备添加时,dshow是不会发送任何通知的,所以,应用程序如果想要知道系统中何时添加新的设备,应用程序可以监控WM_DEVICECHANGE消息。
8从静止图像pin中捕捉图片
有些照相机,摄像头除了可以捕获视频流以外还可以捕获单张的,静止的图片。通常,静止的图片的质量要比流的质量要高。摄像头一般都一个按钮来触发,或者是支持软件触发。支持输出静态图片的摄像头一般都要提供一个静态图片pin,这个pin的种类是PIN_CATEGORY_STILL。
从设备中获取静态图片,我们一般推荐使用windows Image Acquisition (WIA) APIs。当然,你也可以用dshow来获取图片。
在graph运行的时候利用IAMVideoControl::SetMode来触发静态的pin。代码如下
pControl->Run(); // Run the graph.
IAMVideoControl *pAMVidControl = NULL;
hr = pCap->QueryInterface(IID_IAMVideoControl, (void**)&pAMVidControl);
if (SUCCEEDED(hr))
{
// Find the still pin.
IPin *pPin = 0;
hr = pBuild->FindPin(pCap, PINDIR_OUTPUT, &PIN_CATEGORY_STILL, 0,
FALSE, 0, &pPin);
if (SUCCEEDED(hr))
{
hr = pAMVidControl->SetMode(pPin, VideoControlFlag_Trigger);
pPin->Release();
}
pAMVidControl->Release();
}
首先向capture Filter 请求IAMVideoContol,如果支持该接口,就调用ICaptureGraphBuilder2::FindPin请求指向静止pin 的指针,然后调用pin的put_Mode方法。
根据不同的摄像头,你可能静态pin连接前要render 该pin。
捕捉静态图片常用的filter是Sample Grabber filter,Sample Grabber使用了一个用户定义的回调汗水来处理图片。关于这个filter的详细用法,参见Using the Sample Grabber.。
下面的例子假设静态pin传递的是没有压缩的RGB图片。首先定义一个类,从ISampleGrabberCB继承。
// Class to hold the callback function for the Sample Grabber filter.
class SampleGrabberCallback : public ISampleGrabberCB
{
// Implementation is described later.
}
// Global instance of the class.
SampleGrabberCallback g_StillCapCB;
然后将捕捉filter的静态pin连接到Sample Grabber,将Sample Grabber连接到Null Renderer filter。Null Renderer仅仅是将她接收到的sample丢弃掉。实际的工作都是在回调函数里进行,连接Null Renderer 仅仅是为了给Sample Grabber’s 输出pin上连接点东西。具体见下面的代码
// Add the Sample Grabber filter to the graph.
IBaseFilter *pSG_Filter;
hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
IID_IBaseFilter, (void**)&pSG_Filter);
hr = pGraph->AddFilter(pSG_Filter, L"SampleGrab");
// Add the Null Renderer filter to the graph.
IBaseFilter *pNull;
hr = CoCreateInstance(CLSID_NullRendere, NULL, CLSCTX_INPROC_SERVER,
IID_IBaseFilter, (void**)&pNull);
hr = pGraph->AddFilter(pSG_Filter, L”NullRender”);
然后通过RenderStream将still pin ,sample grabber ,null Renderer连接起来
hr = pBuild->RenderStream(
&PIN_CATEGORY_STILL, // Connect this pin ...
&MEDIATYPE_Video, // with this media type ...
pCap, // on this filter ...
pSG_Filter, // to the Sample Grabber ...
pNull); // ... and finally to the Null Renderer.
然后调用ISampleGrabber指针,来通过这个指针可以分配内存。
// Configure the Sample Grabber.
ISampleGrabber *pSG;
hr = pSG_Filter->QueryInterface(IID_ISampleGrabber, (void**)&pSG);
pSG->SetOneShot(FALSE);
pSG->SetBufferSamples(TRUE);
设置你的回调对象
pSG->SetCallback(&g_StillCapCB, 0); // 0 = Use the SampleCB callback method
获取静态pin和sample grabber之间连接所用的媒体类型
// Store the media type for later use.
AM_MEDIA_TYPE g_StillMediaType;
hr = pSG->GetConnectedMediaType(&g_StillMediaType);
pSG->Release();
媒体类型包含一个BITMAPINFOHEADER结构来定义图片的格式,在程序退出前一定要释放媒体类型
// On exit, remember to release the media type.
FreeMediaType(g_StillMediaType);
看看下面的回调类吧。这个类从ISampleGrabber接口派生,但是它没有保持引用计数,因为应用程序在堆上创建这个对象,在整个graph的生存周期它都存在。
所有的工作都在BufferCB函数里完成,当有一个新的sample到来的时候,这个函数就会被sample Grabber调用到。在下面的例子里,bitmap被写入到一个文件中
class SampleGrabberCallback : public ISampleGrabberCB
{
public:
// Fake referance counting.
STDMETHODIMP_(ULONG) AddRef() { return 1; }
STDMETHODIMP_(ULONG) Release() { return 2; }
STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject)
{
if (NULL == ppvObject) return E_POINTER;
if (riid == __uuidof(IUnknown))
{
*ppvObject = static_cast(this);
return S_OK;
}
if (riid == __uuidof(ISampleGrabberCB))
{
*ppvObject = static_cast(this);
return S_OK;
}
return E_NOTIMPL;
}
STDMETHODIMP SampleCB(double Time, IMediaSample *pSample)
{
return E_NOTIMPL;
}
STDMETHODIMP BufferCB(double Time, BYTE *pBuffer, long BufferLen)
{
if ((g_StillMediaType.majortype != MEDIATYPE_Video) ||
(g_StillMediaType.formattype != FORMAT_VideoInfo) ||
(g_StillMediaType.cbFormat < sizeof(VIDEOINFOHEADER)) ||
(g_StillMediaType.pbFormat == NULL))
{
return VFW_E_INVALIDMEDIATYPE;
}
HANDLE hf = CreateFile(“C:\\Example.bmp”, GENERIC_WRITE,
FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
if (hf == INVALID_HANDLE_VALUE)
{
return E_FAIL;
}
long cbBitmapInfoSize = g_StillMediaType.cbFormat – SIZE_PREHEADER;
VIDEOINFOHEADER *pVideoHeader =
(VIDEOINFOHEADER*)g_StillMediaType.pbFormat;
BITMAPFILEHEADER bfh;
ZeroMemory(&bfh, sizeof(bfh));
bfh.bfType = ‘MB'; // Little-endian for “MB”.
bfh.bfSize = sizeof( bfh ) + BufferLen + cbBitmapInfoSize;
bfh.bfOffBits = sizeof( BITMAPFILEHEADER ) + cbBitmapInfoSize;
// Write the file header.
DWORD dwWritten = 0;
WriteFile( hf, &bfh, sizeof( bfh ), &dwWritten, NULL );
WriteFile(hf, HEADER(pVideoHeader), cbBitmapInfoSize, &dwWritten, NULL);
WriteFile( hf, pBuffer, BufferLen, &dwWritten, NULL );
CloseHandle( hf );
return S_OK;
}
};
最简单的基于DirectShow的示例:获取Filter信息
http://blog.csdn.net/leixiaohua1020/article/details/42649379
接口
该流程图中涉及到以下接口:
ICreateDevEnum *pSysDevEnum:设备列举接口。
IEnumMoniker *pEnumCat:Moniker(别名)枚举接口。
IMoniker *pMoniker:Moniker(别名)接口。
IPropertyBag *pPropBag:存储属性值的接口。
IBaseFilter *pFilter:Filter接口。
IEnumPins * pinEnum:Filter枚举接口。
IPin * pin: Pin接口。
PIN_INFO pinInfo:存储Pin的信息的结构体。
IEnumMediaTypes *mtEnum:MediaType枚举接口。
AM_MEDIA_TYPE *mt:描述媒体类型的结构体。
流程图
该流程图中涉及到以下函数:
【初始化】
【Filter的枚举】CoInitialize():初始化COM运行环境。
CoCreateInstance(…,pSysDevEnum):用指定的类标识符创建一个Com对象。在该示例中类标识符为“IID_ICreateDevEnum”,用于创建ICreateDevEnum。
pSysDevEnum->CreateClassEnumerator(…,pEnumCat):通过ICreateDevEnum查询IEnumMoniker枚举接口,枚举指定类型目录下的设备Moniker(别名)。【Pin的枚举】
pEnumCat->Next(…,pMoniker):通过IEnumMoniker查询下一个IMoniker接口。
pMoniker->BindToStorage(…,pPropBag):通过IMoniker查询IPropertyBag接口(用于获取Filter信息)。
pPropBag->Read("FriendlyName"):通过IPropertyBag获取“FriendlyName”属性的值。pMoniker->BindToObject(…,pFilter):通过IMoniker查询IBaseFilter接口(用于获取Filter,注意和BindToStorage()区别)。
pFilter->EnumPins(pinEnum):通过IBaseFilter查询IEnumPins枚举接口。【MediaType的枚举】
pinEnum->Next(…,pin):通过IEnumPins查询下一个IPin接口。pin->QueryPinInfo(PinInfo):通过IPin获取Pin的信息。
pin->EnumMediaTypes(&mtEnum):通过IPin查询IEnumMediaTypes枚举接口。
mtEnum->Next(…, &mt):通过IEnumMediaTypes查询下一个AM_MEDIA_TYPE。GuidToString(mt->majortype):把AM_MEDIA_TYPE的GUID转换成字符串(方便输出)。
【释放】
CoUninitialize():释放CoInitialize()初始化的COM运行环境。
ICreateDevEnum-->IEnumMoniker-->IMoniker-->IBaseFilter-->IEnumPins
-->IPin-->IEnumMediaTypes-->AM_MEDIA_TYPE
Directshow枚举设备
1、DirectShow的基本工作原理是:
将单元组件——Filter串联在一起,然后交由Filter Graph Manager统一控制,系统的一些输入设备、输出设备啊都可以看作是Filter。其实,DirectShow的基本工作单元就是Filter,如:采集设备也是Filter,DirectShow用特殊的包装Filter对它们进行了包装。
WDM Video Capture Filter是WDM驱动模型的采集设备Filter,实现文件是kswdmcap.ax。
VFW Capture Filter是VFW驱动模型的采集设备Filter,实现文件是qcap.dll。
2、采集设备Filter的不同注册目录。Video在Video Capture Sources目录,Audio在Audio Capture Sources目录下,另外WDM Streaming Capture Devices目录包括了以上两个目录的设备以及另外一些采集设备。
3、设备枚举的过程大致如下:
(1)创建一个系统枚举组件(CLSID_SystemDeviceEnum),并获得ICreateDevEnum接口。使用CoCreateInstance函数。
(2)使用接口方法ICreateDevEnum::CreateClassEnumerator为指定目录创建一个枚举器,即IEnumMoniker对象,同时获得IEnumMoniker接口。
(3)使用接口方法IEnumMoniker::Next枚举指定类型目录下所有的设备标识(DeviceMoniker)。每个设备标识对象上都实现了IMoniker接口。实际上可以认为每个设备就是一个IMoniker,它从IEnumMoniker::Next函数中获得实际值,或者说实例,然后可以取得设备的一些属性。
(4)调用IMoniker::BindToStorage之后就可以访问设备标识的属性集了。
(5)调用IMoniker::BindToObject将某个设备标识绑定到一个DirectShow Filter,然后调用IFilterGraph::AddFilter加入到Filter Graph中,这个设备就可以参与工作了。
directshow的中文资料之设备列举和捕捉接口
capboy的专栏 - 博客频道 - CSDN.NET http://t.cn/RU0vM9j
这篇解释和示例如何通过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 fPreviewing;
BOOL fCapAudio;
int iVideoDevice;
int iAudioDevice;
double FrameRate;
BOOL fWantPreview;
long lCapStartTime;
long lCapStopTime;
char achFriendlyName[120];
BOOL fUseTimeLimit;
DWORD dwTimeLimit;
} gcap;
例子用uIndex变量循环列举系统的硬件设备。
BOOL InitCapFilters()
{
HRESULT hr;
BOOL f;
UINT uIndex = 0;
MakeBuilder函数建立了一个filter graph builder(参考建立一个捕捉程序)。
f = MakeBuilder();
建立设备列表对象,得到ICreateDevEnum接口
ICreateDevEnum *pCreateDevEnum;
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum, (void**)&pCreateDevEnum);
建立一个特别类型的硬件设备的列表,类的ID是CLSID_VideoInputDeviceCategory。现在有了一个IEnumMoniker指针,可以访问捕捉设备的列表了。
IEnumMoniker *pEm;
hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEm, 0);
pCreateDevEnum->Release(); // We don't need the device enumerator anymore
pEm->Reset(); // Go to the start of the
enumerated list
现在需要实际的设备了,调用IEnumMoniker::Next ,然后用得到的指针pM调用IMoniker::BindToObject,绑定filter到设备。如果你不想建立联合的filter,使用IMoniker::BindToStorage 代替IMoniker::BindToObject。
ULONG cFetched;
IMoniker *pM; // This will access the actual devices
gcap.pVCap = NULL;
while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)
{
if ((int)uIndex == gcap.iVideoDevice) { // This is the one we want. Instantiate it.
hr = pM->BindToObject(0, 0, IID_IBaseFilter, (void**)&gcap.pVCap);
pM->Release(); // We don't need the moniker pointer anymore
break;
}
pM->Release();
uIndex++;
}
pEm->Release(); // We've got the device; don't need the
enumerator anymore
当有了设备后,通过接口指针去测量帧数,得到driver的名字,得到捕捉的尺寸(size)。在例子中,把每个指针都存储才gcap全局结构中了。
, and get the capture size. AMCap stores each pointer in the gcap global structure.
// We use this interface to get the number of captured and dropped frames
gcap.pBuilder->FindCaptureInterface(gcap.pVCap,
IID_IAMDroppedFrames, (void **)&gcap.pDF);
// We use this interface to get the name of the driver
gcap.pBuilder->FindCaptureInterface(gcap.pVCap,
IID_IAMVideoCompression, (void **)&gcap.pVC);
// We use this interface to set the frame rate and get the capture size
gcap.pBuilder->FindCaptureInterface(gcap.pVCap,
IID_IAMVideoStreamConfig, (void **)&gcap.pVSC);
然后得到媒体的类型和显示窗口的大小去匹配视频格式的尺寸。
AM_MEDIA_TYPE *pmt;
gcap.pVSC->GetFormat(&pmt); // Current capture format
ResizeWindow(HEADER(pmt->pbFormat)->biWidth,
HEADER(pmt->pbFormat)->biHeight);
DeleteMediaType(pmt);
现在,已经有了视频设备和他的相关信息,重复这个过程,得到音频设和他的信息并存储到全局机构中去。注意,这次是用参数CLSID_AudioInputDeviceCategory 调用ICreateDevEnum::CreateClassEnumerator 。
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum, (void**)&pCreateDevEnum);
uIndex = 0;
hr = pCreateDevEnum->CreateClassEnumerator(CLSID_AudioInputDeviceCategory,
&pEm, 0);
pCreateDevEnum->Release();
pEm->Reset();
gcap.pACap = NULL;
while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK)
{
if ((int)uIndex == gcap.iAudioDevice) { // this is the one we want
hr = pM->BindToObject(0, 0, IID_IBaseFilter, (void**)&gcap.pACap);
pM->Release();
break;
}
pM->Release();
uIndex++;
}
pEm->Release();
AMCap also repeats the process of retrieving the format interface, this time for the audio device.
hr = gcap.pBuilder->FindCaptureInterface(gcap.pACap,
IID_IAMAudioStreamConfig, (void **)&gcap.pASC);
}
如何保持DirectShow Filter (Properties) 道具
IPropertyBag 和 IPersistPropertyBag 接口存储和返回Properties的"bags"组。通过这些接口存储的Properties是可以持久保持的。同一个对象在不同的实例之间,他们保持一致。Filter可以存储他们的Properties(CLSID, FriendlyName, and DevicePath)。当一个filter存储完他的Properties之后,实例一个filter时,DirectShow会自动得到他们。添加功能到你的filter中,执行IPersistPropertyBag接口和他的方法。你可以用IPropertyBag::Read 方法装载filter Properties 到Win32 VARIANT 变量中,然后初始化输入输出pin。
下面的代码演示DirectShow的VfWCapture filter如何执行IPersistPropertyBag::Load方法的。记住:在执行期间,你的filter必须提供一个有效的IPropertyBag指针。
STDMETHODIMP CVfwCapture::Load(LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog)
{
HRESULT hr;
CAutoLock cObjectLock(m_pLock); // Locks the object; automatically unlocks it in the destructor.
if (m_pStream) // If the filter already exists for this stream
return E_UNEXPECTED;
VARIANT var; // VARIANT from Platform SDK
var.vt = VT_I4; // four-byte integer (long)
hr = pPropBag->Read(L"VFWIndex", &var, 0); // VFWIndex is the private name used by the Vidcap Class Manager to refer to the VFW Capture filter
if(SUCCEEDED(hr)) // If it read the properties successfully
{
hr = S_OK; // Defaults return value to S_OK
m_iVideoId = var.lVal; // Stores the specified hardware device number
CreatePins(&hr); // Inits the pins, replacing the return value if necessary
}
return hr; // Returns S_OK or an error value, if CreatePins failed
--------------------------------------------------------------------------------
COM笔记-CoCreateInstance - fangyukuan - 博客园 http://t.cn/zOKWxIo
CoCreateInstance 的声明
HRESULT _stdcall CoCreateInstance(REFCLSID rclsid,
LPUNKNOWN pUnkOuter,
DWORD dwClsContext,
REFIID riid,
LPVOID * ppv);
第一个参数:待创建组件的CLSID。
第二个参数:用于聚合组件。
第三个参数:dwClsContext的作用是限定所创建的组件的执行上下文。
第四个参数:iid为组件上待使用的接口的iid。
CoCreateInstance 将在最后一个参数中返回此接口的指针。通过将一个IID传给CoCreateInstance,客户将无需在创建组件之后去调用其QueryInterface函数。
CoCreateInstance的第三个参数dwClsContext可以控制所创建的组件是在与客户相同的进程中运行,还是在不同的进程中运行,或者是在另外一台机器上运行。
CLSCTX_INPROC_SERVER
客户希望创建在同一进程中运行的组件。为能够同客户在同一进程中运行,组件必须是在DLL中实现。
CLSCTX_INPROC_HANDLER
客户希望创建进程中处理器。一个进程中处理器实际上是一只实现了某个组件一部分的进程中组件。该组件的基体附录将由本地或远程服务器上的某个进程外组件实现。
SLSCTX_LOCAL_SERVER
客户希望创建一个在同一机器上的另外一个进程中运行的组件。本地服务器是由EXE实现的。
SLSCTX_REMOTE_SERVER
客户希望创建一个在远程机器上运行的组件。此标志需要分布式COM正常工作。
DirectShow之接口实战篇(一)
-----------------------------------------------
现今自己编程做一个多媒体播放工具是一件很令人开心愉悦的事情,但如果使用MediaPlay控制项开发则会受到很多限制,自己的很多好的创意想法都无法或者很难实现,如果利用微软的DirectX接口开发则可以充分的将作者的独特想法付诸於实现,何乐而不为呢!!不过关於DirectShow接口的开发说明文档实在是少之又少,仅有的一些不是英文的就是一些关於理论方面的,真正关於接口实战编程而且是用Delphi开发工具实现的更是凤毛麟角,使很多人都望而却步。在这里,我把我应用Directshow开发的心得以及我搜集到一些资料重新整理编辑出来公布,希望对所有由此兴趣的同仁有所帮助,就算达到了我的目的。废话少说,进入正文。
既然是接口实战篇,就先把一些常用的接口列出来,让大家有一些基本的认识,都是用来做什麽的,什麽时候我们会需要用到此接口。
IFilterGraph ----过滤通道接口
IFilterGraph2----增强的IFilterGraph
IGraphBuilder----最为重用的COM接口,用於手动或者自动构造过滤通道FilterGraph Manager
IMediaControl----用来控制流媒体,例如流的启动和停止暂停等,播放控制接口
IMediaEvent ----播放事件接口 ,该接口在Filter Graph发生一些事件时用来创建事件的标志资讯并传送给应用程序
IMediaEventEx----扩展播放事件接口
IMediaPosition----播放的位置和速度控制接口(控制播放位置只能为设置时间控制方式)
IMediaSeeking-----另一个播放的位置和播放速度控制接口,在位置选择方面功能较强.
设置播放格式,多种控制播放方式.常用的有:
(1)TIME_FORMAT_MEDIA_TIME单位100纳秒。
(2)TIME_FORMAT_FRAME按帧播放
IBasicAudio------声音控制接口
IBasicVideo------图像控制接口(串列传输速率,宽度,长度等资讯)
IVideoWindow------显示视窗控制接口 (有关播放视窗的一切控制,包括caption显示,视窗位置控制等)
ISampleGrabber-----捕获图像接口(可用於抓图控制)
IVideoFrameStep-----控制单帧播放的接口
好了,熟悉了应用DirectShow应用开发常用的接口後,我们就通过一个实例媒体播放器来熟悉掌握这些接口,实例的代码虽然简单,但五脏俱全,功能强大,同时也了解一下应用DirectShow开发一般常用的步骤。
--------------------------------------------------------------------------------
DirectShow之接口实战篇(二)
大体说来,一般使用DirectShow接口编程无非3个步骤,
初始化接口,
利用接口中的控制函数使用控制操作,
最後释放接口。
(当然这里假定你已经拥有了directshow.pas等必须单元,如果没有的话请在网上查找)
(注:以下变量没有定义,需自己定义使用)
(1) 首先初始化部分接口,需要定义需要使用的接口
GraphBuilder: IGraphBuilder;
MediaControl: IMediaControl;
MediaSeeking: IMediaSeeking;
MediaPosition: IMediaPosition;
MediaEventEx: IMediaEvent;
BasicAudio: IBasicAudio;
BasicVideo: IBasicVideo;
VideoWindow: IVideoWindow;
SampleGrabber: ISampleGrabber;
VideoFrameStep: IVideoFrameStep;
(2)然後需要使用CoCreateInstance函数创建一个Filter Graph Manager 实例,
CoCreateInstance(TGUID(CLSID_FilterGraph),nil, CLSCTX_INPROC_SERVER,TGUID(IID_IGraphBuilder),GraphBuilder)
因为需要抓图使用IsampleGrabber接口,需要创建SampleGrabber实例,
var Filter: IBaseFilter;
CoCreateInstance(CLSID_SampleGrabber, nil, CLSCTX_INPROC_SERVER,IID_IBaseFilter, Filter);
(3) 调用QueryInterface函数获取来获取指标,好以後操作控制
Filter.QueryInterface(IID_ISampleGrabber, SampleGrabber);
GraphBuilder.AddFilter(Filter, 'Grabber');
GraphBuilder.QueryInterface(IID_IMediaControl, MediaControl);
GraphBuilder.QueryInterface(IID_IMediaPosition, MediaPosition);
GraphBuilder.QueryInterface(IID_IMediaSeeking, MediaSeeking);
GraphBuilder.QueryInterface(IID_IMediaEventEx, MediaEventEx);
GraphBuilder.QueryInterface(IID_IVideoFrameStep, VideoFrameStep);
GraphBuilder.QueryInterface(IID_IBasicAudio, BasicAudio);
GraphBuilder.QueryInterface(IID_IBasicVideo, BasicVideo);
GraphBuilder.QueryInterface(IID_IVideoWindow, VideoWindow);
当然为了安全起见,可以对以上每个过程进行是否成功判断,给出资讯,否则很有可能出现问题找不到头绪。
好了,一切准备成功,就可以进入第4步,开始我们的控制操作了。
(4)通过接口提供的函数开始控制
哦,差点忘记一件重要的事情,在上面调用QueryInterface之前,还有两件重要的事情要做,
第一,要建立一个Unicode(wide character)字串,保存档案名。
var _wfile: array[0..MAX_PATH - 1] of wchar;
MultiByteToWideChar(CP_ACP, 0, pChar(播放档案名), -1, @_wfile, MAX_PATH);
然後需要成功RenderFile才可以控制操作
GraphBuilder.RenderFile(_wfile, nil);
当然在显示的时候要把显示表单和控制项关连起来,这里需要通过IvideoWindow接口方法,
VideoWindow. put_Owner(Edit1.Handle);
VideoWindow. put_WindowStyle(DSVIDEO_WINDOW_CHILD_STYLE);
VideoWindow.SetWindowPosition(0,0, Edit1.ClientWidth, Edit1.ClientHeight);
得到图像的一些必要资讯,使用IbasicVideo接口中的方法,一些变数自己定义,
BasicVideo.GetVideoSize(VideoWidth, VideoHeight);
BasicVideo.get_BitRate(VideoBitRate);
BasicVideo.get_AvgTimePerFrame(PerFrame);
得到当前档的总时间以及播放时间,需要使用ImediaSeeking接口方法,
MediaSeeking.GetDuration(Duration);//得到总时间
MediaSeeking.GetCurrentPosition(CurrentPos);//得到当前播放时间
也可以通过IMediaSeeking::SetPositions方法设置开始和结束时间。
哦,这里得到的单位好像是毫米级的,可以自己转化为秒级的.
还有,如果想以後能够单帧控制播放,在这里也需要设定播放格式为按帧播放。
MediaSeeking.SetTimeFormat(Time_Format_Frame);
播放,停止,暂停等控制,
这些需要使用ImediaControl接口的方法,控制起来很简单,分别为
MediaControl.Play;
MediaControl.Stop;
MediaControl.Pause;
--------------------------------------------------------------------------------
DirectShow之接口实战篇(三)
播放速度的设定
需要使用ImediaPosition的方法。
MediaPosition.put_Rate(1);//正常
MediaPosition.put_Rate(0.25);//慢速
MediaPosition.put_Rate(2);//快速
单帧播放控制
需要使用IvideoFrameStep的方法
VideoFrameStep.Step(1, nil);
音量控制
需要使用IbasicAudio的方法
增加音量:
BasicAudio.get_Volume (&volume);//得到音量
volume:= volume +volumestep;
BasicAudio.put_Volume (volume);//增加一定的音量的分贝
减小音量:
BasicAudio.get_Volume (&volume); //得到音量
volume:= volume -volumestep;
BasicAudio.putVolume (volume); //减小一定音量的分贝
显示放大缩小控制
只需改变Edit1的大小,然後使用IvideoWindow接口方法即可
VideoWindow.SetWindowPosition(0, 0, Edit1.Width, Edit1.Height);
单帧捕获,抓图
其实很多接口都提供了此功能,但是我更倾向於使用IsampleGrabber接口来实现,相对来说,效率高些。
这个控制起来做的工作稍微多些,首先,在打开文件的时候
var MediaType: TAM_MEDIA_TYPE;
ZeroMemory(@MediaType, SizeOf(TAM_MEDIA_TYPE));
MediaType.majortype := MEDIATYPE_Video;//视频流
MediaType.subtype := MEDIASUBTYPE_RGB24;//24位图像
MediaType.formattype := FORMAT_VideoInfo;
SampleGrabber.SetMediaType(MediaType);//关联接口
SampleGrabber.SetBufferSamples(True);
然後在抓图按钮事件中如下操作
var
MediaType: TAM_MEDIA_TYPE;
VideoInfoHeader: TVideoInfoHeader;
BitmapInfo: TBitmapInfo;
Bitmap: HBitmap;
Buffer: Pointer;
BufferSize: Integer;
begin
SampleGrabber.GetConnectedMediaType(MediaType);
ZeroMemory(@VideoInfoHeader, SizeOf(TVideoInfoHeader));
CopyMemory(@VideoInfoHeader, MediaType.pbFormat, SizeOf(VideoInfoHeader));
ZeroMemory(@BitmapInfo, SizeOf(TBitmapInfo));
CopyMemory(@BitmapInfo, @VideoInfoHeader.bmiHeader, SizeOf(VideoInfoHeader.bmiHeader));
Bitmap:=CreateDIBSection(0, BitmapInfo, DIB_RGB_COLORS, Buffer, 0, 0);
SampleGrabber.GetCurrentBuffer(BufferSize, Buffer);
Image1.Picture.Bitmap.Handle:=Bitmap
end;
即可。
在这里,先总结这麽多,希望对大家有所帮助,这些只是DirectX的一个皮毛,它可以实现的功能十分强大,我也只是把我在实际中的遇到的问题总结出来供大家参考,後面的工作还很多,我想我会逐步的更深入的总结这方面的经验发表出来与大家分享,好了,DirectShow接口施展篇到这里该完结了,如果大家有补充或者想法,请发表出来以便我总结整理,谢谢大家。
--------------------------------------------------
DirectShow之接口实战篇(四)
通过DirectShow驱动摄像头,首先要创建一个ICaptureGraphBuilder2的COM对象,它的作用是设备(摄像头)与输出画面打交道的一个接口.
//创建一个ICaptureGraphBuilder2对象
var
m_cBuilder2: ICaptureGraphBuilder2;
hr: HResult;
........................
begin
hr := CoCreateInstance(CLSID_CaptureGraphBuilder2, nil, CLSCTX_INPROC, ICaptureGraphBuilder2, m_cBuilder2);
if FAILED(hr) then Exit;//失败就返回
.........................
好有了接口就要准备输出的画面了,其实就是一个IGraphBuilder的COM对象!
在var下面添加
m_gBuilder: IGraphBuilder;
//创建一个画面
hr := CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC, IGraphBuilder, m_gBuilder);
if FAILED(hr) then Exit;
//OK!把它们关联起来
m_cBuilder2.SetFiltergraph(m_gBuilder);
现在有了输出画面但还没有设备,别慌!下面就讲怎么得到设备!
要想得到设备就要请ICreateDevEnum接口和IEnumMoniker接口帮忙了!
首先创建ICreateDevEnum接口对象,在var下面添加
de: ICreateDevEnum;
//创建ICreateDevEnum对象
hr := CoCreateInstance(CLSID_SystemDeviceEnum, nil, CLSCTX_INPROC, IID_ICreateDevEnum, de);
if FAILED(hr) then Exit;
有了ICreateDevEnum我们就可以枚举设备了,在var下面添加
em: IEnumMoniker;
//创建一个枚举
de.CreateClassEnumerator(CLSID_VideoInputDeviceCategory, em, 0);
if em = nil then Exit;
//枚举设备(摄像头)
在var下面添加
pM: IMoniker;//找到的接口
pBf: IBaseFilter;//设备的过滤器
dID: DWORD;
if em.Next(1, pM, @dID) = S_OK then
//得到设备的过滤器
hr := pM.BindToObject(nil, nil, IID_IBaseFilter, pBf);
if FAILED(hr) then Exit;
好了我们有了设备了!那就是pBf(过滤器)
现在我们要把它加入到输出画面去
//加入过滤器
hr := m_gBuilder.AddFilter(pBf, 'Video Capture');
if FAILED(hr) then Exit;
当有输出画面了有了过滤器还不行!要用ICaptureGraphBuilder2把他们关联起来
hr := m_cBuilder2.RenderStream(@PIN_CATEGORY_PREVIEW, @MEDIATYPE_Video, pBf, nil, nil);
if FAILED(hr) then Exit;
//现在基本完成了,下面就是要它们动起来!!
在var下面添加
m_mControl: IMediaControl;
hr := m_gBuilder.QueryInterface(IID_IMediaControl, m_mControl);
if FAILED(hr) then Exit;
这段代码是得到输出画面的控制接口,下面就要动了!!
//简单的一句
m_mControl.Run;
使用DirectShow采集图像 - OpenCV China :图像处理,计算机视觉库,Image Processing, Computer Vision http://t.cn/zR4slqh
Directshow是基于模块化,每个功能模块都采取COM组件方式,称为Filter。Directshow提供了一系列的标准的模块可用于应用开发,开发者也可以开发自己的功能Filter来扩展Directshow的应用。
采取Filter来播放一个AVI的视频文件:
1 首先从一个文件中读取AVI数据,形成字节流。(这个工作由源Filter完成)
2 检查AVI数据流的头格式,然后通过AVI分割Filter将视频流和音频流分开。
3解码视频流,根据压缩格式的不同,选取不同的decoder filters 。
4通过Renderer Filter重画视频图像。
5将音频流送到声卡进行播放,一般采用缺省的 DirectSound Device Filter。
每一个filter都一个其他的一个或者两个filter相连接。两个Filter相连接的连接点也是com对象,我们称为Pin。Filter通过pin将数据从一个filter传递到另一个filter中,从而可以使数据在由filter组成的链表中流动。图中的箭头表示filter链表中的数据流的方向。在Directshow中,像上面的这样一个filter 链表我们称为filter Graph。
Filter具有三个状态,运行,停止,暂停。
当一个filter运行时,它就处理媒体数据流,当停止时,filter就不在处理数据,暂停状态常用来给运行状态之前cure data。Data Flow in the Filter Graph一章详细描述了这些概念,可以参考。
除了一些特别的例外, Filter graph中所有的filter的状态的改变都是统一的,也就说,filte graph中的所有的filter 的状态改变是一致协调的。也就是说,我们也可以用filter graph也可以有运行,停止,暂停三种状态。
Filter 一般分为下面几种类型。
(1)源过滤器(source filter):源过滤器引入数据到过滤器图表中,数据来源可以是文件、网络、照相机等。不同的源过滤器处理不同类型的数据源。
(2)变换过滤器(transform filter):变换过滤器的工作是获取输入流,处理数据,并生成输出流。变换过滤器对数据的处理包括编解码、格式转换、压缩解压缩等。
(3)提交过滤器(renderer filter):提交过滤器在过滤器图表里处于最后一级,它们接收数据并把数据提交给外设。
(4)分割过滤器(splitter filter):分割过滤器把输入流分割成多个输出。例如,AVI分割过滤器把一个AVI格式的字节流分割成视频流和音频流。
(5)混合过滤器(mux filter):混合过滤器把多个输入组合成一个单独的数据流。例如,AVI混合过滤器把视频流和音频流合成一个AVI格式的字节流。
过滤器的这些分类并不是绝对的,例如一个ASF读过滤器(ASF Reader filter)既是一个源过滤器又是一个分割过滤器。
2 关于Filter Graph Manager
Filter Graph Manager也是一个com对象,用来控制Filter graph中的所有的filter,主要有以下的功能:
1 用来协调filter之间的状态改变,从而使graph 中的所有的filter的状态的改变应该一致。
2 建立一个参考时钟。
3 将filter 的消息通知返回给应用程序
4 提供用来建立 filter graph的方法。
这里只是简单的描述一下,详细地可以参考文档。
状态改变,Graph中的filter的状态改变应该一致,因此,应用程序并将状态改变的命令直接发给filter,而是将相应的状态改变的命令发送给Filter graph Manager,由manager将命令分发给graph中每一个filter。Seeking也是同样的方式工作,首先由应用程序将seek命令发送到filter graph 管理器,然后由其分发给每个filter。
参考时钟,graph中的filter都采用的同一个时钟,称为参考时钟(reference clock),参考时钟可以确保所有的数据流同步,视频桢或者音频桢应该被提交的时间称为presentation time.presentation time 是相对于参考时钟来确定的。Filter graph Manager应该选择一个参考时钟,可以选择声卡上的时钟,也可以选择系统时钟
Graph事件, Graph 管理器采用事件机制将graph中发生的事件通知给应用程序,这个机制类似于windows的消息循环机制。
Graph构建的方法,graph管理器给应用程序提供了将filter添加进graph的方法,连接filter的方法,断开filter连接的方法。
但是,graph 管理器没有提供如何将数据从一个filter发送到另一个filter的方法,这个工作是由filter在内部通过pin来独立完成的,
3媒体类型
因为Directshow是基于com组件的,就需要有一种方式来描述filter graph每一个点的数据格式,例如,我们还以播放AVI文件为例,数据以RIFF块的形式进入graph中,然后被分割成视频和音频流,视频流有一系列的压缩的视频桢组成,解压后,视频流由一系列的无压缩的位图组成,音频流也要走同样的步骤。
Media Types: How DirectShow Represents Formats
媒体类型是一种很普遍的,可以扩展的用来描述数字媒体格式的方法,当两个filter连接的时候,他们会就采用某一种媒体类型达成一致的协议。媒体类型定义了处于源头的filter将要给下游的filter发送什么样的数据,以及数据的physical layout。如果两个filter不能够支持同一种的媒体类型,那么他们就没法连接起来。 ............DirectShow中FilterGraph及其组成 - orchid的专栏 - 博客频道 - CSDN.NET http://t.cn/RU0hAti
操作filter之前,肯定要创建filter graph。
几乎所有的介绍directshow的书和例子都用了 IGraphBuilder, IcaptureGraphBuiler2
这两个接口,当然IFilterGraph也有,少用。
这三者有什么区别?
【1】IFilterGraph2,IcaptureGraphBuiler2这两个filter的CLSID是不一样的。IFilterGraph2,IGraphBuilder是一样 的。
IFilterGraph2,IGraphBuilder注册在CLSID_FilterGraph下,IcaptureGraphBuiler2注册在CLSID_CaptureGraphBuilder2下。
【2】对于IFilterGraph2来说,还有一个IFilterGraph,IFilterGraph3.
他们的继承关系为:
IFilterGraph——>IGraphBuilder——>IFilterGraph2——>IFilterGraph3
一般使用IGraphBuilder比较多
【3】如果我们仅仅是显示预览UVC,可以只使用IFilterGraph2,通过pFilterGraph2->AddSourceFilterForMoniker(),
用Moniker指针将filter添加到graph,则不必创建IcaptureGraphBuiler2这个接口。
【4】IcaptureGraphBuiler2是一个专门用来进行视频,音频捕捉的增强型接口;如果要特别的编解码,用它就很方便。
但为什么一般创建了IcaptureGraphBuiler2,还要创建IGraphBuilder呢?
这是因为我们在预览视频时处理播放,暂停,停止这些动作,需要用到IGraphBuilder,而这些功能IcaptureGraphBuiler2
是没有的,所以需要而这协调处理。
IcaptureGraphBuiler2创建后,要将IGraphBuilder的指针与他关联:
pCaptureGraphBuiler2->captureGraphBuiler2(pGraphBuilder).