DirectShow视频采集专业技术方案.docx
- 文档编号:26389569
- 上传时间:2023-06-18
- 格式:DOCX
- 页数:43
- 大小:367.23KB
DirectShow视频采集专业技术方案.docx
《DirectShow视频采集专业技术方案.docx》由会员分享,可在线阅读,更多相关《DirectShow视频采集专业技术方案.docx(43页珍藏版)》请在冰豆网上搜索。
DirectShow视频采集专业技术方案
2.3 DirectShow视频采集方案
流媒体处理技术以其复杂性和技术性一直受到人们的关注。
随着网络技术的不断发展,流媒体在网络上得到了广泛地应用。
如何能够简单、有效地进行流媒体处理,已成为一个焦点问题。
为此,Microsoft推出了DirectShow,DirectShow是Microsoft推出的基于Windows平台的流媒体处理开发包,它与DirectX一起发布。
DirectShow对流媒体的捕捉、回放提供了强大的支持,使用它还可以在基于WDM驱动的采集卡上进行数据捕捉。
本节将介绍有关DirectShow的相关知识。
2.3.1 DirectShow系统结构分析
DirectShow主要由过滤器(FilterGraph)图表构成。
过滤图表中包含了各种Filter,这些Filter能够按一定顺序连接在一起,构成一条流水线。
从功能的角度划分,Filter大体可以分为3类,SourceFilters、TransformFilters和RenderingFilters。
SourceFilters主要负责获取数据,可以是一个文件、一个采集卡、声卡或数码相机等。
TransformFilters负责数据的转换、传输。
例如各种编码器、解码器等。
RenderingFilters负责数据的最终去向,例如将数据传送到声卡、显卡或存储为文件。
在开发DirectShow应用程序时,通常需要设计一个过滤图表(FilterGraph),向过滤图表中添加相应的过滤器,最后连接过滤器的引脚就完成了功能的设计。
例如,实现一个简单的视频预览功能,需要向过滤图表中添加一个视频捕捉源过滤器和一个VideoRenderer过滤 器,将视频捕捉源过滤器的输出引脚与VideoRenderer过滤器的输入引脚相连就可以了。
而在程序中只需要按照设计过滤图表的捕捉添加过滤器并连接过滤器引脚就可以了。
在连接过滤器引脚时需要注意:
只能是输出过滤器引脚与输入过滤器引脚相连,两个输出过滤器或两个输入过滤器引脚是不能相连的。
为了在程序中使用DirectShow,需要单独安装DirectX,当前DirectX的最新版本为9.0,即DirectX9.0,用户可以从Microsoft的官方网站上免费下载。
在安装DirectX之后,程序中需要引用“dshow.h”头文件,并导入“Strmiids.lib”库文件和“quartz.lib”库文件才可以使用DirectShow。
代码如下:
#pragmacomment(lib,"Strmiids")
#pragmacomment(lib,"quartz")
#include
2.3.2 Filter图表设计
为了方便用户设计过滤图表,DirectX提供了一个GraphEdit工具。
用户可以单击“开始”菜单下的“MicrosoftDirectX9SDK\DirectXUtilities\GraphEdit”菜单项打开GraphEdit工具,如图2.10所示。
图2.10 GraphEdit工具
下面笔者介绍如何使用GraphEdit工具设计过滤图表,过滤图表的功能是实现视频的预览功能。
具体步骤如下:
(1)在图2.4中单击“Graph/InsertFilters”菜单项打开“添加过滤器”窗口,如图2.11所示。
图2.11 添加过滤器窗口
(2)在“VideoCaptureSources”节点下选择一个视频捕捉源过滤器,单击“InsertFilter”按钮将其添加到过滤图表中,如图2.12所示
图2.12 添加视频捕捉源过滤器窗口
提示:
如果系统中没有安装摄像头及其驱动程序,该节点下将不会有视频捕捉源过滤器。
(3)在“DirectShowFilters”节点下选择“VideoRenderer”过滤器,将其添加到图表中,如图2.13所示。
图2.13 添加“VideoRenderer”过滤器窗口
(4)利用鼠标将视频捕捉源过滤器的“Capture”引脚与“VideoRenderer”过滤器的“VMRInput0”引脚相连,如图2.14所示。
图2.14 连接过滤器引脚窗口
(5)单击工具栏中的“
”按钮运行过滤图表,将显示一个视频预览窗口,如图2.15所示。
图2.15 视频预览窗口
2.3.3 枚举系统设备
使用GraphEdit工具,用户可以非常方便地获得与某一系统设备相关的过滤器。
但是,在程序中该如何获得这些过滤器呢?
用户可以采用枚举的方式列举系统中安装的设备。
以列举系统中的视频捕捉设备为例,首先定义一个设备列举接口ICreateDevEnum的一个指针,调用CoCreateInstance方法创建ICreateDevEnum实例,然后定义一个列举监视器IEnumMoniker的一个指针,调用ICreateDevEnum实例的CreateClassEnumerator方法创建IEnumMoniker实例,最后以循环的方式调用IEnumMoniker实例的Next方法遍历系统设备,调用IEnumMoniker实例的BindToObject方法将系统设备绑定到过滤器上。
在上面的描述中,ICreateDevEnum实例的CreateClassEnumerator方法的第一个参数确定枚举的系统设备。
例如,第一个参数为CLSID_VideoInputDeviceCategory,表示将要枚举系统中的视频捕捉卡,为CLSID_VideoCompressorCategory,表示枚举系统中的视频压缩器。
下面的代码演示了如何枚举系统中的视频捕捉卡。
//枚举视频设备
ICreateDevEnum*pDevEnum=NULL。
CoCreateInstance(CLSID_SystemDeviceEnum,NULL,CLSCTX_INPROC,
IID_ICreateDevEnum,(void**)&pDevEnum)。
IEnumMoniker*pClassEnum=NULL。
pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,&pClassEnum,0)。
ULONGcFetched。
while(pClassEnum->Next(1,&pMoniker,&cFetched)==S_OK)
{
pMoniker->BindToObject(0,0,IID_IBaseFilter,(void**)&pSrc)。
pMoniker->Release()。
break。
}
pClassEnum->Release()。
而下面的代码则用于判断系统中是否安装了指定的视频压缩器。
ICreateDevEnum*pDevEnum=NULL。
CoCreateInstance(CLSID_SystemDeviceEnum,NULL,CLSCTX_INPROC,
IID_ICreateDevEnum,(void**)&pDevEnum)。
IEnumMoniker*pClassEnum=NULL。
//列举视频压缩设备
pDevEnum->CreateClassEnumerator(CLSID_VideoCompressorCategory,&pClassEnum,0)。
while(pClassEnum->Next(1,&pMoniker,&cFetched)==S_OK)
{
IPropertyBag*pProp=NULL。
pMoniker->BindToStorage(0,0,IID_IPropertyBag,(void**)&pProp)。
VARIANTvarName。
varName.vt=VT_BSTR。
pProp->Read(L"FriendlyName",&varName,0)。
CStringstr=varName.bstrVal。
if(str.Find("MicrosoftVideo1",0)!
=-1)
{
pMoniker->BindToObject(0,0,IID_IBaseFilter,(void**)&pCompress)。
pMoniker->Release()。
break。
}
VariantClear(&varName)。
}
pClassEnum->Release()。
2.3.4 查找FilterPin
每一个过滤器(Filter)至少应有一个引脚(Pin),或者是输入引脚或者是输出引脚。
有些过滤器还拥有多个引脚,即又输入引脚又有输出引脚。
但是过滤器的输入、输出引脚并不是对应的,有些过滤器可以有多个输入引脚,而只有一个输出引脚或者没有输出引脚。
在程序中为了连接过滤器间的引脚,通常需要获得过滤器的各个引脚。
用户可以使用IEnumPins接口来枚举某一个过滤器的输入、输出引脚。
过滤器IBaseFilter提供了一个EnumPins方法用于生成一个IEnumPins接口实例,这样,通过调用IEnumPins的Next方法便可以访问各个引脚了。
下面的代码定义了一个FindPin函数,用于获得某个过滤器的输入或输出引脚。
//查找引脚
IPin*CKinescopeDlg:
:
FindPin(IBaseFilter*pFilter,PIN_DIRECTIONdir)
{
IEnumPins*pEnumPins。
IPin*pOutpin。
PIN_DIRECTIONpDir。
pFilter->EnumPins(&pEnumPins)。
while(pEnumPins->Next(1,&pOutpin,NULL)==S_OK)
{
pOutpin->QueryDirection(&pDir)。
if(pDir==dir)
{
returnpOutpin。
}
}
return0。
}
用户可以按下面的方式获得某个过滤器的输入、输出引脚。
IPin*pComOut,*pComIn。
pComIn =FindPin(pCompress,PINDIR_INPUT)。
pComOut=FindPin(pCompress,PINDIR_OUTPUT)。
2.3.5 连接FilterPin
使用GraphEdit工具,用户可以利用鼠标非常方便地连接两个过滤器间的引脚。
但是在程序中却没这么简单了。
首先需要按照2.3.4节介绍的方法获得两个过滤器的输入、输出引脚,然后将第一个过滤器的输出引脚连接到第二个过滤器的输入引脚,其中,连接两个引脚需要调用IGraphBuilder接口的ConnectDirect方法。
下面的代码演示了如何连接两个过滤器的引脚。
IPin*pComOut,*pComIn。
pComIn =FindPin(pCompress,PINDIR_INPUT)。
pComOut=FindPin(pCompress,PINDIR_OUTPUT)。
IPin*pOutpin=FindPin(pSrc,PINDIR_OUTPUT)。
//pSrc的输出引脚
HRESULTresult。
result=pGraph->ConnectDirect(pOutpin,pComIn,NULL)。
2.3.6 视频预览设计方案
在开发视频应用程序时,一个最基本的功能是视频预览。
本节将介绍如何应用DirectShow实现视频预览,效果如图2.16所示。
图2.16 视频预览设计方案
在使用DirectShow开发应用程序时,通常需要先设计过滤图表,然后根据图表来设计应用程序。
在设计视频预览过滤图表时,只需要两个步骤,第一个步骤是添加视频捕捉的源过滤器,第二个步骤是添加VideoRender过滤器,并连接两个过滤器引脚。
过滤图表具体设计步骤如下:
(1)启动GraphEdit工具,在GraphEdit工具中单击“Graph\InsertFilters”菜单项打开“添加过滤器窗口”,在“VideoCaptureSources”节点下选择一个视频捕捉源过滤器,单击“InsertFilter”按钮将其添加到过滤图表中,如图2.11所示。
(2)在“DirectShowFilters”节点下将“VideoRender”过滤器添加到图表中,如图2.17所示。
图2.17 过滤图表设计1
(3)连接过滤器引脚,如图2.18所示。
图2.18 过滤图表设计2
至此,完成视频预览过滤图表的设计。
单击“Play”按钮运行过滤图表,将弹出一个视频预览窗口,如图2.19所示。
图2.19 DerictShow预览窗口
程序具体步骤如下:
实例位置:
光盘\mr\2\2.3\2.3.6\01
(1)创建一个基于对话框的工程,在对话框类的头文件中引用“dshow.h”头文件。
#pragmacomment(lib,"Strmiids")
#pragmacomment(lib,"quartz")
#include
(2)在对话框类中定义如下成员变量。
IMediaControl *pMediaControl。
//媒体控制
IGraphBuilder *pGraph。
//过滤图表
IBaseFilter *pSrc,*pPreview。
//过滤器
IMoniker *pMoniker。
//监视器
(3)向对话框类中添加FindPin方法,查找过滤器的引脚。
//查找引脚
IPin*CPreviewDlg:
:
FindPin(IBaseFilter*pFilter,PIN_DIRECTIONdir)
{
IEnumPins*pEnumPins。
IPin*pOutpin。
PIN_DIRECTIONpDir。
pFilter->EnumPins(&pEnumPins)。
while(pEnumPins->Next(1,&pOutpin,NULL)==S_OK)
{
pOutpin->QueryDirection(&pDir)。
if(pDir==dir)
{
returnpOutpin。
}
}
return0。
}
(4)在应用程序类的InitInstance方法中初始化Com接口。
//初始化Com
CoInitialize(NULL)。
(5)在对话框初始化时枚举视频捕捉设备,设计过滤图表。
ICaptureGraphBuilder2*pBuilder=NULL。
pGraph=NULL。
pMediaControl=NULL。
//枚举视频设备
ICreateDevEnum*pDevEnum=NULL。
CoCreateInstance(CLSID_SystemDeviceEnum,NULL,CLSCTX_INPROC,
IID_ICreateDevEnum,(void**)&pDevEnum)。
IEnumMoniker*pClassEnum=NULL。
pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,&pClassEnum,0)。
ULONGcFetched。
if(pClassEnum->Next(1,&pMoniker,&cFetched)==S_OK)
{
pMoniker->BindToObject(0,0,IID_IBaseFilter,(void**)&pSrc)。
pMoniker->Release()。
}
pClassEnum->Release()。
CoCreateInstance(CLSID_CaptureGraphBuilder2,0,
CLSCTX_INPROC_SERVER,IID_ICaptureGraphBuilder2,(void**)&pBuilder)。
CoCreateInstance(CLSID_FilterGraph,NULL,CLSCTX_INPROC_SERVER,
IID_IGraphBuilder,(void**)&pGraph)。
pBuilder->SetFiltergraph(pGraph)。
pGraph->QueryInterface(IID_IMediaControl,(void**)&pMediaControl)。
pGraph->AddFilter(pSrc,L"avi")。
pPreview=NULL。
CoCreateInstance(CLSID_VideoRenderer,0,CLSCTX_ALL,
IID_IBaseFilter,(void**)&pPreview)。
if(pPreview!
=NULL)
{
pGraph->AddFilter(pPreview,L"preview")。
//连接引脚
IPin*pSourceOut。
pSourceOut=FindPin(pSrc,PINDIR_OUTPUT)。
IPin* pPreIn=FindPin(pPreview,PINDIR_INPUT)。
pGraph->ConnectDirect(pSourceOut,pPreIn,NULL)。
//获取预览窗口
IVideoWindow*pViewWnd=NULL。
pPreview->QueryInterface(IID_IVideoWindow,(void**)&pViewWnd)。
if(pViewWnd)
{
//设置预览窗口的拥有者
pViewWnd->put_Owner((long)m_hWnd)。
pViewWnd->put_Left
(1)。
pViewWnd->put_Top
(1)。
//获取预览窗口风格
longstyle。
pViewWnd->get_WindowStyle(&style)。
style=style&~WS_CAPTION。
style=style&~WS_DLGFRAME。
style=style&WS_CHILD。
pViewWnd->put_WindowStyle(style)。
//设置预览窗口宽度和高度
CRectrc。
GetClientRect(rc)。
pViewWnd->put_Height(rc.Height()-60)。
pViewWnd->put_Width(rc.Width()-2)。
}
pMediaControl->Run()。
}
2.3.7 事件通知设计方案
在使用DirectShow开发应用程序时,通常需要DirectShow与应用程序之间进行交互。
例如,在使用DirectShow播放影视文件时,在文件播放完毕后,应用程序需要得到通知,并通知用户文件播放完毕。
DirectShow采用了事件通知的机制与用户应用程序进行交互。
当过滤器的状态改变时,或者重画视频窗口时,都将产生相应的事件,由DirectShow处理并转发给应用程序,应用程序能够接收到该事件,并根据事件的不同类型作出相应的处理。
DirectShow提供了3个有关事件通知的接口,分别为“IMediaEventSink”、“IMediaEvent”和“IMediaEventEx”。
其中,IMediaEventEx接口是IMediaEvent接口的扩展,在应用程序中经常使用该接口类处理事件。
IMediaEventEx接口的主要方法如下:
1.GetNotifyFlags方法
该方法用于获取事件通知是否被激活。
语法如下:
HRESULTGetNotifyFlags(long*lplNoNotifyFlags)。
参数说明:
lplNoNotifyFlags:
确定事件通知是否被激活。
如果为零,表示事件通知被激活,为AM_MEDIAEVENT_NONOTIFY,表示事件通知不可用。
2.SetNotifyFlags方法
该方法用于设置事件通知是否被激活。
语法如下:
HRESULTSetNotifyFlags( longlNoNotifyFlags)。
lplNoNotifyFlags:
如果为零,表示激活事件通知,为AM_MEDIAEVENT_NONOTIFY,表示禁止事件通知。
3.SetNotifyWindow方法
该方法用于注册一个窗口来处理事件通知。
语法如下:
HRESULTSetNotifyWindow(OAHWNDhwnd,longlMsg,longlInstanceData)。
参数说明:
hwnd:
表示处理事件通知的窗口句柄。
lMsg:
表示与事件通知关联的窗口消息。
lInstanceData:
作为lMsg消息处理函数的lParam参数被传递。
4.GetEventHandle方法
该方法用于获取人工重置事件对象的句柄。
语法如下:
HRESULTGetEventHandle(OAEVENT*hEvent)。
参数说明:
hEvent:
表示返回的人工重置事件对象的句柄。
5.GetEvent方法
该方法用于从消息队列中返回下一个事件通知。
语法如下:
HRESULT(long*lEventCode,long*lParam1,long*lParam2,longmsTimeout)。
参数说明:
lEventCode:
表示接收的事件通知代码。
lParam1:
表示第一个事件参数。
lParam2:
表示第2个事件参数。
msTimeout:
表示超时时间,如果为NFINITE,将一直等待,直到有事件通知为止。
6.FreeEventParams方法
该函数用于释放事件参数。
语法如下:
HRESULTFreeEventParams(longlEventCode,longlParam1,longlParam2)。
参数说明:
lEventCode:
表示接收的事件通知代码。
lParam1:
表示第一个事件参数。
l
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- DirectShow 视频 采集 专业技术 方案