关于DirectShow在Video视频处理方面的的一些基本应用
DirectShow是微软提供的DirectX软件开发包中的一员,主要以处理流媒体数据为重点开发的一套windows下的编程接口,为了高效地处理音视频数据,它向应用提供了直接访问系统底层功能的接口,可以使应用直接控制从数据采集到数据演播的各个中间环节,如数据压缩和解压缩格式等。
DirectShow是以一种比较新的概念来操作媒体流运行的,他有点类似于计算机硬件的管脚Pin的概念,并且引进了滤波器Filter和流图Graph等原理。媒体流通过流图中的各个滤波器,最后到达输出界面,期间经过各个滤波器的转化和控制使最终数据达到一定的要求。通过DirectShow的基本接口,应用可以自行设计自己的各种滤波器完成数据在流动中的特殊处理需求,比如视频数据流的格式从MPEG到RGB的变换(解压缩)或象素格式从YUV到RGB的变换等。
DirectShow本身是一个比较复杂的系统,其中包含了许多的概念,对于一个初涉的编程人员,需要学习许多新的知识,特别是对于使用非VC进行应用开发的人员,想要实际使用DirectShow的功能其困难程度是可想而知的。为此,我这里提供一个折中的解决方案,使得非VC的开发人员也能方便的使用DirectShow提供的各种功能,同时也为所有希望进入视频编程领域的开发人员提供一个DirectShow的入门级引导。这篇文章和相关源码或许能对感兴趣的读者提供一些帮助。
一、 DirectShow的graph原理
Graph实际上是一个filters的容器,Dshow提供一个Graph控件(Com),应用可以用CoCreateInstance来建立一个graph对象。在Dshow中还有一个用于创建和操作Graph,这就是Builder对象,一般来讲,应用应该首先创建Builder对象,它是Dshow流控制的关键,Builder接口提供的方法包含了智能创建Graph中filters的能力,当未知媒体流格式时,使用Builder连接Filter时,将智能添加系统中的格式转换Filter到Graph的Filters链中产生目标格式的最佳转换链。
建立Builder和Graph后,将Graph加入到Builder中,便可以往Graph中添加指定的Filter了,一般系统中的Video设备是由设备枚举过程获得的,系统中的每一种视频驱动,都定义为一个视频设备(音频设备也是如此)。函数:
CoCreateInstance (CLSID_SystemDeviceEnum,
NULL,
CLSCTX_INPROC_SERVER,
IID_ICreateDevEnum,
(void**)&pCreateDevEnum);
建立系统设备枚举器对象,其中:
ICreateDevEnum * pCreateDevEnum; //设备枚举器对象指针
GUID IID_IcreateDevEnum //设备枚举器接口的GUID
Const CLSCTX_INPROC_SERVER//被建立对象的进程特征
GUID CLSID_SystemDeviceEnum //枚举器对象类的GUID
注意:GUID包含在Dshow头文件UUID.h中。
利用设备枚举对象列出系统中已经安装的设备,每一个设备类形成一个IenumMoniker对象然后枚举该类设备在系统中的安装个数:
//建立视频输入设备枚举器
IEnumMoniker *pEm;
pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEm, 0);
pEm是已经建立的视频设备每举器,通过该对象枚举系统的视频设备。
IMoniker * pM;//枚举视频设备
pEm->Next(1, &pM, &cFetched), hr==S_OK)//获得一个Imoniker接口
Dshow通过设备的Imoniker接口操作系统中的设备。同样步骤可以获得系统中的音频设备,它们统称为系统的输入设备。(关于函数的参数定义可以查看VC的帮助,非VC开发人员,只需了解基本原理就可以了)
Imoniker对象通过提供的BindToObject方法可以获得输入设备的Filter对象
pM –>BindToObject(0, 0, IID_IBaseFilter, (void**)&pVCap);
pVCap就是视频输入设备对象,利用QueryInterface函数可以获得特殊输入设备的接口对象(因为pVCap仅反映出一个基本的滤波器对象,因此不能反映一些特殊的方法,查询特殊接口需要知道接口的GUID,这可以通过设备购买时提供的资料获得,一般而言不需特殊接口功能,使用基本功能可以保证应用系统的兼容性和广泛可用性。
取得设备后将其加入到Graph中,然后使用智能连接的系统默认Render
pGraph->AddFilter(pVCap, NULL)
pBuilder->RenderStream( &PIN_CATEGORY_CAPTURE,
&MEDIATYPE_Video,
pACap,
NULL,
NULL);
就可以形成一条从输入开始到系统默认Render的视频播放filters链路了,启动这个链路,则可以在显示器上看到视频图象。当然还需要设置显示窗口的大小和位置。这个播放链路是建立在视频输入设备的Capture管脚上的,如果设备不存在Capture管脚,链路建立则不能成功,也可以将播放链路建立在Preview管脚上(PIN_CATEGORY_PREVIEW)。滤波器链路原理如图所示:
这是一个USB小摄象头作为视频输入设备的滤波器播放链路,在指定显示窗口大小的情况下,可以播放视频数据流。Graph基本概念框图如下:
其中:Filter1..是应用自己加入的特殊Filter来进行视频数据流的特殊处理,比如嵌入字模,滚动视频和实时取图等,也可以进行视频数据的特殊压缩和解压缩处理。视频数据流以pSample缓冲的方式逐级经过各个filter,最后到达render进行显示处理,pSample是filter数据交换缓冲区指针,应用通过自己的filter改变pSample的数据达到改变视频流显示的目的。
二、 建立和使用Filter
建立滤波器,就是建立一个滤波器对象类,并且输出一个接口方法组,根据滤波器的用途选择合适的父类,一般Dshow提供各种供选择的父类对象和接口,比如Graber用途的滤波器,可以直接抓取内存中的帧图象,比使用window API的cap类函数抓图要强很多。定义接口方法:
DECLARE_INTERFACE_(IGrabberIPP, IUnknown){
STDMETHOD(put_MediaType) (THIS_ CMediaType *pmt ) PURE; /* [in] */ // the media type selected
STDMETHOD(get_MediaType) (THIS_ CMediaType **pmt) PURE; /* [out] */ // the media type selected
STDMETHOD(get_IPin) (THIS_ IPin **pPin ) PURE; /* [out] */ //
the source pin
STDMETHOD(get_State) (THIS_ FILTER_STATE *state ) PURE; /* [out] */ //
the filter state
STDMETHOD(get_FilterIPin) (THIS_ int n,IPin **pPin ) PURE;/*[in][out] */ //
the in/out pin
STDMETHOD(get_ImageSize) (THIS_ long * outWidth,
long * outHeight,
long * outBitCount) PURE;
STDMETHOD(get_FrameSize) (THIS_ long * outFrameSize) PURE;
STDMETHOD(get_BitmapInfoHeader) (THIS_
BITMAPINFOHEADER * outBitmapInfo) PURE;
STDMETHOD(get_Is16BitsRGB) (THIS_ long * outIsRGB565) PURE;
STDMETHOD(put_IsFieldPicture) (THIS_ BOOL inIsField) PURE;
STDMETHOD(Snapshot) (THIS_ BYTE * outBuffer, BOOL inIsSyncMode) PURE;
STDMETHOD(IsAsyncSnapshotFinished) (THIS_ BOOL * outFinished) PURE;
STDMETHOD(CancelAllPending) (THIS) PURE;
};
//----------------------------------------------------------------------------
定义滤波器对象:
class CGrabberInPlace : public CtransInPlaceFilter,
public IGrabberIPP
{……};
//----------------------------------------------------------------------------
写一个滤波器对象和输出接口方法后,便可以用regsvr32注册滤波器到系统中,应用便可以使用该滤波器:
1、 用CoCreateInstance建立接口实例,使用基本滤波器对象类的GUID。
CoCreateInstance (
CLSID_GrabberInPlace,
NULL,
CLSCTX_INPROC_SERVER,
IID_IBaseFilter,
(void **)&pBf);
CLSID_GrabberInPlace //指定滤波器的类GUID,在你自己的滤波器中定义
IID_IbaseFilter //基本滤波器对象接口GUID,它是所有滤波器的父类
此时,你获得的是一个包含你自己的滤波器的基本滤波器指针(特殊的方法函数是不出现在该接口中的),当你需要使用特殊方法时,使用QueryInterface,查询得到特殊接口指针:
pBf->QueryInterface(IID_GrabberIPP,
(void **)&pGf);
IID_GrabberIPP //特殊滤波器的接口GUID
PGf //特殊滤波器接口指针
使用pGf,就可以操作特殊滤波器处理视频数据。注意:在你编写自己的特殊滤波器方法时,需要精确了解滤波器父类各层对象的操作关系和方法,这样才能灵活的处理各种情况的发生,滤波器是系统应用层以下的系统程序,它的任何异常都可能导致系统的崩溃,因此在调试阶段应该仔细处理各种情况。
三、VideoDll的输出函数
动态库输出函数:
启动Com和枚举系统设备
bool _stdcall fnVideoDll_Initial(void);
将系统中安装的音视频设备枚举出来,列成一张表,以供操作选择
销毁所有系统设备和已经建立的设备对象
bool _stdcall fnVideoDll_Finally(void);
销毁已经建立的设备对象,终止动态库操作
取得当前系统设备个数
int _stdcall fnVideoDll_GetVideoDeviceNumber(bool IsVideo);
IsVideo:true 表示取得视频设备数,否则取得音频设备数
取得系统中指定类型(音/视频)设备的个数
获得指定系统设备的参数
bool _stdcall fnVideoDll_GetVideoDeviceParms(long DevIndex,
TParms * pParms,
bool IsVideo);bool_stdcall
DevIndex 指定设备的索引号,多设备存在时为设备序号。
PParms 设备参数缓冲指针,接收设备参数
IsVideo:true 表示取得视频设备数,否则取得音频设备数
设置指定系统设备的参数
fnVideoDll_SetVideoDeviceParms(long DevIndex,TParms * pParms,bool IsVideo);
选择指定设备建立设备对象(根据指定参数)
bool _stdcall fnVideoDll_SelectVideoAudioDev(int DevIndex,
HWND AppHandle,
RECT rc,
int SelFlag);
DevIndex 指定设备的索引号,多设备存在时为设备序号。
AppHandle 操作设备的窗口handle
Rc 视频显示位置
SelFlag 设备选择标志,0=视频设备,1=音频设备,2=音视频设备
选择指定设备建立Graph链路,并开始播放。
关闭指定设备
bool _stdcall fnVideoDll_CloseVideoAudioDev(int DevIndex);
将指定设备及其建立的音视频Graph对象全部关闭,设备恢复到原始状态。
图象采集函数(将帧图象采集到缓冲区中)
设置显示矩形
bool _stdcall fnVideoDll_SetDisplayRect(int DevIndex,RECT rc);
更改指定设备的显示矩形位置和大小,用新指定的矩形代替原矩形。
取得抓图缓冲区的尺寸
bool _stdcall fnVideoDll_ImgGrabberGetSize(int DevIndex, int * ImgSize);
取得当前图象数据
bool _stdcall fnVideoDll_ImgGrabberGetImg(int DevIndex,void * ImgBuffer);
取得当前帧图象的格式信息
bool _stdcall fnVideoDll_ImgGrabberGetBmpHdr(int DevIndex,
BITMAPINFOHEADER * outBitmapInfo);
取得捕捉设备的视频流格式
bool _stdcall fnVideoDll_GetGraphFormat(int DevIndex,
BITMAPINFOHEADER *FormatInfo);
设置捕捉设备的视频流格式
bool _stdcall fnVideoDll_SetGraphFormat(int DevIndex,
BITMAPINFOHEADER *FormatInfo);
参数结构定义:
typedef struct _TParms_ {
long DevIndex;//系统采集设备枚举索引
WCHAR szDevDisplayName[1024];//显示名
WCHAR szFriendlyName[1024];//设备名
BOOL fWantPreview;//是否可以预览
BOOL fUseFrameRate;//是否指定每秒帧数
double FrameRate; //每秒帧数
DWORD dwWidth; //视频图面宽度(设备设置的)
DWORD dwHeight; //视频图面高度(设备设置的)
BOOL CanGrabber; //是否进行抓图操作
long PinSel; //连接pin的选择:0-->Capture,1-->Preview,2-->自适应
BOOL HasDecompress;//是否指定解压缩filters(以下参数未使用)
TDECOMP decomFlt[10]; //解压缩filters
long czGuid; //数组记数
} TParms, *pTParms;
四、 非VC开发人员使用方法(针对delphi进行说明)
在delphi中,使用动态库的方法是将将动态库函数进行声明(Pasical格式),因此只要声明了Dll的输出函数,就可以使用该动态库。Delphi中函数声明如下:
1、结构声明
type
PParms = ^TParms;
TParms = record
DevIndex :Integer;//系统采集设备枚举索引
szDevDisplayName :array[0..1023] of WCHAR;//显示名
szFriendlyName :array[0..1023] of WCHAR;//设备名
fWantPreview :Integer;//是否可以预览
fUseFrameRate :Integer;//是否指定每秒帧数
FrameRate :Double; //每秒帧数
dwWidth :DWORD; //视频图面宽度(设备设置的)
dwHeight :DWORD; //视频图面高度(设备设置的)
CanGrabber :Integer;//是否进行抓图操作
PinSel :Integer;//连接pin的选择:0-->Capture,1-->Preview,2-->自适应
HasDecompress :Integer;//是否指定解压缩filters
decomFlt :array[0..9]of TDECOMP;//解压缩filters
czGuid :Integer;//数组记数
end;
2、动态库输出函数
function fnVideoDll_Initial:boolean;stdcall;
function fnVideoDll_Finally:boolean;stdcall;
function fnVideoDll_GetVideoDeviceNumber(IsVideo:boolean):Integer;stdcall;
function fnVideoDll_GetVideoDeviceParms(DevIndex:Integer;
var pParms:TParms;IsVideo:boolean):boolean;stdcall;
function fnVideoDll_SetVideoDeviceParms(DevIndex:Integer;
var pParms:TParms;IsVideo:boolean):boolean;stdcall;
function fnVideoDll_SelectVideoAudioDev(DevIndex:Integer;
AppHandle:HWND;rc:TRECT;SelFlag:Integer):boolean;stdcall;
function fnVideoDll_CloseVideoAudioDev(DevIndex:Integer):boolean;stdcall;
function fnVideoDll_SetDisplayRect(DevIndex:Integer;rc:TRECT):boolean;stdcall;
function fnVideoDll_ImgGrabberGetSize(DevIndex:Integer;
var ImgSize:Integer):boolean;stdcall;
function fnVideoDll_ImgGrabberGetImg(DevIndex:Integer;
ImgBuffer:PChar):boolean;stdcall;
function fnVideoDll_ImgGrabberGetBmpHdr(DevIndex:Integer;
var BitmapInfo:TBITMAPINFOHEADER):boolean;stdcall;
function fnVideoDll_GetGraphFormat(DevIndex:Integer;
var FormatInfo:TBITMAPINFOHEADER):boolean;stdcall;
function fnVideoDll_SetGraphFormat(DevIndex:Integer;
var FormatInfo:TBITMAPINFOHEADER):boolean;stdcall;
实现部分
implementation
function fnVideoDll_Initial:boolean;stdcall;
external 'VideoDll.Dll' name 'fnVideoDll_Initial';
function fnVideoDll_Finally:boolean;stdcall;
external 'VideoDll.Dll' name 'fnVideoDll_Finally';
function fnVideoDll_GetVideoDeviceNumber(IsVideo:boolean):Integer;stdcall;
external 'VideoDll.Dll' name 'fnVideoDll_GetVideoDeviceNumber';
function fnVideoDll_GetVideoDeviceParms(DevIndex:Integer;
var pParms:TParms;IsVideo:boolean):boolean;stdcall;
external 'VideoDll.Dll' name 'fnVideoDll_GetVideoDeviceParms';
function fnVideoDll_SetVideoDeviceParms(DevIndex:Integer;
var pParms:TParms;IsVideo:boolean):boolean;stdcall;
external 'VideoDll.Dll' name 'fnVideoDll_SetVideoDeviceParms';
function fnVideoDll_SelectVideoAudioDev(DevIndex:Integer;AppHandle:HWND;
rc:TRECT;SelFlag:Integer):boolean;stdcall;
external 'VideoDll.Dll' name 'fnVideoDll_SelectVideoAudioDev';
function fnVideoDll_CloseVideoAudioDev(DevIndex:Integer):boolean;stdcall;
external 'VideoDll.Dll' name 'fnVideoDll_CloseVideoAudioDev';
function fnVideoDll_SetDisplayRect(DevIndex:Integer;rc:TRECT):boolean;stdcall;
external 'VideoDll.Dll' name 'fnVideoDll_SetDisplayRect';
function fnVideoDll_ImgGrabberGetSize(DevIndex:Integer;
var ImgSize:Integer):boolean;stdcall;
external 'VideoDll.Dll' name 'fnVideoDll_ImgGrabberGetSize';
function fnVideoDll_ImgGrabberGetImg(DevIndex:Integer;ImgBuffer:PChar):boolean;stdcall;
external 'VideoDll.Dll' name 'fnVideoDll_ImgGrabberGetImg';
function fnVideoDll_ImgGrabberGetBmpHdr(DevIndex:Integer;
var BitmapInfo:TBITMAPINFOHEADER):boolean;stdcall;
external 'VideoDll.Dll' name 'fnVideoDll_ImgGrabberGetBmpHdr';
function fnVideoDll_GetGraphFormat(DevIndex:Integer;
var FormatInfo:TBITMAPINFOHEADER):boolean;stdcall;
external 'VideoDll.Dll' name 'fnVideoDll_GetGraphFormat';
function fnVideoDll_SetGraphFormat(DevIndex:Integer;
var FormatInfo:TBITMAPINFOHEADER):boolean;stdcall;
external 'VideoDll.Dll' name 'fnVideoDll_SetGraphFormat';
五、 VC开发环境的设置
1、 安装directX开发环境
2、 指定include目录:
directX安装目录下的include
directX安装目录下的SAMPLES\MULTIMEDIA\COMMON\INCLUDE
directX安装目录下的SAMPLES\MULTIMEDIA\DIRECTSHOW\BASECLASSES
一般安装目录根据directX的版本可能会有差异,选择对应目录。
3、 库目录library
directX安装目录下的lib
directX安装目录下的
SAMPLES\MULTIMEDIA\DIRECTSHOW\BASECLASSES\DEBUG
4、 库Library的指定
在项目设置setting下的link中一定要指定下面的库,
uuid.lib
winmm.lib
msacm32.lib
strmiids.lib
strmbasd.lib
有时还需要删除这些库前面的路径,因为在路径中已经指定了库的路径。
5、 运行编译前首先要编译连接基本类,生成strmbasd.lib到指定位置。
结束语
本Dll及其程序是经过本人一段时间的视频开发获得的一个附属模块程序,对于非VC开发人员,特别是希望控制视频操作的delphi开发人员,是一个方便的抓图处理工具,可以很轻松的将视频功能结合进应用之中,比如在各种图象识别过程中,可以直接获得当前帧图象数据进行处理。使用其他语言开发应用的朋友也可以很方便的改写输出函数的声明,使用本动态库处理视频数据。
对于希望了解DirectShow开发过程的开发人员,使用本动态库开发框架,能够获得入门级知识,为进一步的开发打下良好的基础,在动态库框架下,很容易就可以扩展特殊的处理功能,比如播放指定的AVI文件,捕捉视频数据到文件等。本着付费使用的软件精神,也是为了更好地促进软件事业的发展,本人决定对源码进行收费,全套源码收费10元。其中包括:
一个抓图filter(VC),
一个动态库(VC),
一个Pas单元,
一个Pas实例程序。
以及一些说明文档。
通过实验本套程序,相信你很快就能进入DirectShow引领下的视频媒体流领域和图象处理领域。
付费方式:通过邮局汇款,在汇单中一定要注明你的电子邮箱地址,用于接收程序代码。
汇款地址:沈阳市,大东区,和睦北二路7号4-4-2室 郑女士
本文地址:http://com.8s8s.com/it/it24734.htm