Wednesday, September 07, 2005

 

Some notes on Direct Show - 1

1.
DirectShow.NET
http://directshownet.sourceforge.net/

An complex and ambitious approach

2.
DirectShow Type Libraries and .NET Interop DLL
http://www.kohsuke.org/dotnet/directshowTypelib/

An simpler one

3.
Dshow.dll
http://www.codeproject.com/cs/media/Motion_Detection.asp

another simpler directShow interop

4.
DirectShow流媒体信息获取及图象转换

By 沈浩 谭骏珊
From http://www.yesky.com/259/1854259_1.shtml

摘 要 本文描述的DirectShow技术的主要工作原理,介绍了DirectShow的主要接口,利用DirectShow技术来驱动摄像头和获取流媒体信息,以及将流媒体信息保存为计算机常用位图的方法。

关键词 DirectShow;COM接口, 流媒体,VC++

概述

  流媒体的处理,以其复杂性和技术性,一向广受工业界的关注。特别伴随着因特网的普及,流媒体在网络上已广泛应用﹐怎样使流媒体的处理变得简单而富有成效逐渐成为了焦点问题。选择一种合适的应用方案,将事半功倍。此时,微软的DirectShow给了我们一个不错的选择。

  DirectShow是微软公司在ActiveMovie和Video for Windows的基础上推出的新一代基于COM的流媒体处理的开发包,与DirectX开发包一起发布。目前,DirectX最新版本为9.0。DirectShow为多媒体流的捕捉和回放提供了强有力的支持。运用DirectShow,我们可以很方便地从支持WDM驱动模型的采集卡上捕获数据,并且进行相应的后期处理乃至存储到文件中。这样使在多媒体数据库管理系统(MDBMS)中多媒体数据的存取变得更加方便。

  DirectShow 原理及重要的接口

  1、DirectShow工作原理

  1) DirectShow的系统结构

  DirectShow的体系结构如图1所示。


图1 DirectShow系统
  
  DirectShow位于应用层中。它使用一种叫Filter Graph的模型来管理整个数据流的处理过程;参与数据处理的各个功能模块叫Filter;各个Filter 在Filter Graph中按一定的顺序连接成一条“流水线”协同工作。按照功能来分,Filter大致分为三类:Source Filters、Transform Filters和Rendering Filters。Source Filters主要负责取得数据,数据源可以是文件、因特网、或者计算机里的采集卡、数字摄像机等,然后将数据往下传输;Transform Fitlers主要负责数据的格式转换、传输;Rendering Filtes主要负责数据的最终去向,我们可以将数据送给声卡、显卡进行多媒体的演示,也可以输出到文件进行存储。

  在DirectShow系统之上,我们看到的,即是我们的应用程序(Application)。应用程序要按照一定的意图建立起相应的Filter Graph,然后通过Filter Graph Manager来控制整个的数据处理过程。DirectShow能在Filter Graph运行的时候接收到各种事件,并通过消息的方式发送到我们的应用程序。这样,就实现了应用程序与DirectShow系统之间的交互。

  2) Filter概述以及连接

  过滤器(Filter)是DirectShow中最基本的概念。DirectShow是通过Filter Graph来管理Filter的。Filter Graph是Filter的“容器”,而Filter是Filter Graph中的最小功能模块。Filter是一种COM组件,对于每个Filter,都有其自己的Pin,它是由Filter创建的COM对象。Filter通过Pin来进行他们之间的连接。Pin分为两种:输出Pin和输入Pin。输出的Pin把Filter处理后的数据传送到Filter的外部,而输入Pin则是把Filter外部的数据接收到Filter中,以便Filter对这些数据进行处理。对于三种类型的Filter(Source Filter,Transform Filter,Rendering Filter)的连接图如下:


图2 Filter的连接

  2、DirectShow对硬件的支持原理

  大家知道,为了提高系统的稳定性,Windows操作系统对硬件操作进行了隔离;应用程序一般不能直接访问硬件。DirectShow Filter工作在用户模式(User mode,操作系统特权级别为Ring 3),而硬件工作在内核模式(Kernel mode,操作系统特权级别为Ring 0),DirectShow解决的方法是,为这些硬件设计包装Filter;这种Filter能够工作在用户模式下,外观、控制方法跟普通Filter一样,而包装Filter内部完成与硬件驱动程序的交互。这样的设计,使得编写DirectShow应用程序的开发人员,从为支持硬件而需做出的特殊处理中解脱出来。DirectShow已经集成的包装Filter,包括Audio Capture Filte(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,)。下图就是各个包装Filter与硬件交互的结构图:

应用程序
DirectShow Filter Graph
KsTune.ax KsXbar.ax KsCap.ax 其他普通的Filter
Stream Class
Tuner minidriver Crossbar mindriver Capture minidriver Tuner,Crossbar,Capture minidriver

  3、DirectShow 的重要接口

  DirectShow采用了COM标准,所以很多重要的功能都是通过COM接口来完成。下面就列举一些重要的DirectShow的接口。

  (1) IGraphBuilder接口

  用于构造Filter Graph的接口,建立和管理一系列的Filter,过滤和处理源媒体流。

  (2) IMediaControl接口

  用于控制多媒体流在过滤器图表中的流动,如流的启动和停止。

  (3) IMediaEvent接口

  用于捕获播放过程中发生的事件,并通知应用程序,如EC_COMPLETE等。

  (4) IVideoWindow接口

  用于控制视频窗口的属性。

  (5) IMeadiaSeeking接口

  用于查找媒体的接口,定位流媒体,控制多媒体数据播放提供精确控制。

  (6) IBaseFilter接口

  从ImediaFilter接口继承,用来定义一个具体的过滤器指针,并对多媒体数据进行处理。

  (7) IPin接口

  用于管理两个过滤器之间的Pin,从而连接过滤器。

  (8) IsampleGrabberCB接口

  是Sample Grabber过滤器的一个接口,用于当流媒体数据通过过滤器时进行采样以获得帧图象。

DirectShow流媒体数据的采集及图片的捕获

  用DirectShow来使用摄像头,一般要求摄像头的驱动是WDM格式的,当然,一些比较老的驱动格式DirectShow也可支持。在DirectShow中,有一个Sample Grabber过滤器,它是一个可以被插入流的过滤器,它有自己的缓冲,存放采样。我们就可以用它来从一个视频文件中简单的扑获一桢。DirectShow通过图形过滤管理器(Filter Graph Manager)来与上层应用程序和下层的驱动进行联系。DirectShow通过捕获过滤器(Capture Filter)来支持对摄像头的捕获,一个捕获过滤器有多个插口(pin),其中的预览(preview)插口可用来进行显示祯图象。

  1、创建图形过滤管理器Filter Graph

  如上面原理所述,首先要创建Filter Graph:

CComPtr< IGraphBuilder > m_pGraph;
hr=m_pGraph.CoCreateInstance( CLSID_FilterGraph );

  2、连接设备

  还要创建系统枚举器组件对象:

CComPtr<ICreateDevEnum>pCreateDevEnum;pCreateDevEnum.CoCreateInstance( CLSID_SystemDeviceEnum );

  然后使用接口方法CreateClassEnumerator ()为指定的Filter注册类型目录创建一个枚举器,并获得IenumMoniker接口:

CComPtr< IEnumMoniker > pEm;
pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEm, 0 );

  接着在调用BindToObject()以后,可以将设备标识生成一个DirectShow Filter,将其加到Filter Graph中就可以参与工作了。

CComPtr< IMoniker > pM;
CComPtr< IPropertyBag > pBag;
hr=pM->BindToStorage(0,0,ID_IPropertyBag, (void**) &pBag );

  3、创建Sample Grabber过滤器

CComPtr< ISampleGrabber > m_pGrabber
hr=m_pGrabber.CoCreateInstance( CLSID_SampleGrabber );

  当创建好SampleGrabber以后,在Sample Grabber 过滤器连接到别的过滤器之前你必须配置它。然后查询IsampleGrabber接口,还要设置流媒体类型:

m_pGrabber->SetMediaType();

  可以仅仅指定主媒体类型;或者主类型加子类型;或者主类型,子类型和类型格式。然后就把它加载到FilterGraph中去:

m_pGraph->AddFilter(pGrabBase,"Grabber" );

  4、查找Filter Graph 的Pin并完成后续连接。

  接下来就可以通过调用IGraphBuilder 的FindPin()接口来查找过滤管理器中的Pin接口,并通过ICaptureGraphBuilder2 中的接口RenderStream()来完成后续的连接。

hr=pCGB2->FindPin(pCap,PINDIR_OUTPUT,&PIN_CATEGORY_VIDEOPORT, NULL,FALSE,0,&pVPPin);
hr=pCGB2->RenderStream(&PIN_CATEGORY_CAPTURE,&MEDIATYPE_Video, pCap,pGrabBase,pRenderer);

  5、获取流媒体类型并运行

  通过GetConnectedMediaType()获取连接流媒体的类型以后,我们可以通过IsampleGrabberCB类的接口BufferCB()来把视频的数据拷贝到自定义的缓冲区中,然后通过在缓冲区的拷贝进行视频到图象数据的拷贝。最后运行﹕

CComQIPtr<IMediaControl,&IID_IMediaControl > pControl = m_pGraph;
hr = pControl->Run( );

  结论

  本文讨论了DirectShow的基本原理,创建Filter Graph的基本方法,以及通过DirectShow来捕获视频数据,然后将其保存为自己想要的图象,对于多媒体数据库管理系统是一个非常有利的补充,如对考试报名的软件系统有很强的适应性,可以降低开发成本。提高用户的实用性。

  DirectShow技术是一个开发多媒体的行之有效的方法。在未来几年中,DirectShow技术的发展前景相当广阔,掌握DirectShow的技术将有重要的实用意义。

5.
DirectShow中常见的RGB/YUV格式

By 陆其明
From http://hqtech.nease.net

摘自《DirectShow实务精选》 作者:陆其明

计算机彩色显示器显示色彩的原理与彩色电视机一样,都是采用R(Red)、G(Green)、B(Blue)相加混色的原理:通过发射出三种不同强度的电子束,使屏幕内侧覆盖的红、绿、蓝磷光材料发光而产生色彩。这种色彩的表示方法称为RGB色彩空间表示(它也是多媒体计算机技术中用得最多的一种色彩空间表示方法)。
根据三基色原理,任意一种色光F都可以用不同分量的R、G、B三色相加混合而成。

F = r [ R ] + g [ G ] + b [ B ]

其中,r、g、b分别为三基色参与混合的系数。当三基色分量都为0(最弱)时混合为黑色光;而当三基色分量都为k(最强)时混合为白色光。调整r、g、b三个系数的值,可以混合出介于黑色光和白色光之间的各种各样的色光。
那么YUV又从何而来呢?在现代彩色电视系统中,通常采用三管彩色摄像机或彩色CCD摄像机进行摄像,然后把摄得的彩色图像信号经分色、分别放大校正后得到RGB,再经过矩阵变换电路得到亮度信号Y和两个色差信号R-Y(即U)、B-Y(即V),最后发送端将亮度和色差三个信号分别进行编码,用同一信道发送出去。这种色彩的表示方法就是所谓的YUV色彩空间表示。
采用YUV色彩空间的重要性是它的亮度信号Y和色度信号U、V是分离的。如果只有Y信号分量而没有U、V分量,那么这样表示的图像就是黑白灰度图像。彩色电视采用YUV空间正是为了用亮度信号Y解决彩色电视机与黑白电视机的兼容问题,使黑白电视机也能接收彩色电视信号。
YUV与RGB相互转换的公式如下(RGB取值范围均为0-255):

Y = 0.299R + 0.587G + 0.114B
U = -0.147R - 0.289G + 0.436B
V = 0.615R - 0.515G - 0.100B

R = Y + 1.14V
G = Y - 0.39U - 0.58V
B = Y + 2.03U

在DirectShow中,常见的RGB格式有RGB1、RGB4、RGB8、RGB565、RGB555、RGB24、RGB32、ARGB32等;常见的YUV格式有YUY2、YUYV、YVYU、UYVY、AYUV、Y41P、Y411、Y211、IF09、IYUV、YV12、YVU9、YUV411、YUV420等。作为视频媒体类型的辅助说明类型(Subtype),它们对应的GUID见表2.3。

表2.3 常见的RGB和YUV格式

GUID 格式描述
MEDIASUBTYPE_RGB1 2色,每个像素用1位表示,需要调色板
MEDIASUBTYPE_RGB4 16色,每个像素用4位表示,需要调色板
MEDIASUBTYPE_RGB8 256色,每个像素用8位表示,需要调色板
MEDIASUBTYPE_RGB565 每个像素用16位表示,RGB分量分别使用5位、6位、5位
MEDIASUBTYPE_RGB555 每个像素用16位表示,RGB分量都使用5位(剩下的1位不用)
MEDIASUBTYPE_RGB24 每个像素用24位表示,RGB分量各使用8位
MEDIASUBTYPE_RGB32 每个像素用32位表示,RGB分量各使用8位(剩下的8位不用)
MEDIASUBTYPE_ARGB32 每个像素用32位表示,RGB分量各使用8位(剩下的8位用于表示Alpha通道值)
MEDIASUBTYPE_YUY2 YUY2格式,以4:2:2方式打包
MEDIASUBTYPE_YUYV YUYV格式(实际格式与YUY2相同)
MEDIASUBTYPE_YVYU YVYU格式,以4:2:2方式打包
MEDIASUBTYPE_UYVY UYVY格式,以4:2:2方式打包
MEDIASUBTYPE_AYUV 带Alpha通道的4:4:4 YUV格式
MEDIASUBTYPE_Y41P Y41P格式,以4:1:1方式打包
MEDIASUBTYPE_Y411 Y411格式(实际格式与Y41P相同)
MEDIASUBTYPE_Y211 Y211格式
MEDIASUBTYPE_IF09 IF09格式
MEDIASUBTYPE_IYUV IYUV格式
MEDIASUBTYPE_YV12 YV12格式
MEDIASUBTYPE_YVU9 YVU9格式

下面分别介绍各种RGB格式。

¨ RGB1、RGB4、RGB8都是调色板类型的RGB格式,在描述这些媒体类型的格式细节时,通常会在BITMAPINFOHEADER数据结构后面跟着一个调色板(定义一系列颜色)。它们的图像数据并不是真正的颜色值,而是当前像素颜色值在调色板中的索引。以RGB1(2色位图)为例,比如它的调色板中定义的两种颜色值依次为0x000000(黑色)和0xFFFFFF(白色),那么图像数据001101010111…(每个像素用1位表示)表示对应各像素的颜色为:黑黑白白黑白黑白黑白白白…。

¨ RGB565使用16位表示一个像素,这16位中的5位用于R,6位用于G,5位用于B。程序中通常使用一个字(WORD,一个字等于两个字节)来操作一个像素。当读出一个像素后,这个字的各个位意义如下:
高字节 低字节
R R R R R G G G G G G B B B B B
可以组合使用屏蔽字和移位操作来得到RGB各分量的值:

#define RGB565_MASK_RED 0xF800
#define RGB565_MASK_GREEN 0x07E0
#define RGB565_MASK_BLUE 0x001F
R = (wPixel & RGB565_MASK_RED) >> 11; // 取值范围0-31
G = (wPixel & RGB565_MASK_GREEN) >> 5; // 取值范围0-63
B = wPixel & RGB565_MASK_BLUE; // 取值范围0-31

¨ RGB555是另一种16位的RGB格式,RGB分量都用5位表示(剩下的1位不用)。使用一个字读出一个像素后,这个字的各个位意义如下:
高字节 低字节
X R R R R G G G G G B B B B B (X表示不用,可以忽略)
可以组合使用屏蔽字和移位操作来得到RGB各分量的值:

#define RGB555_MASK_RED 0x7C00
#define RGB555_MASK_GREEN 0x03E0
#define RGB555_MASK_BLUE 0x001F
R = (wPixel & RGB555_MASK_RED) >> 10; // 取值范围0-31
G = (wPixel & RGB555_MASK_GREEN) >> 5; // 取值范围0-31
B = wPixel & RGB555_MASK_BLUE; // 取值范围0-31

¨ RGB24使用24位来表示一个像素,RGB分量都用8位表示,取值范围为0-255。注意在内存中RGB各分量的排列顺序为:BGR BGR BGR…。通常可以使用RGBTRIPLE数据结构来操作一个像素,它的定义为:

typedef struct tagRGBTRIPLE {
BYTE rgbtBlue; // 蓝色分量
BYTE rgbtGreen; // 绿色分量
BYTE rgbtRed; // 红色分量
} RGBTRIPLE;

¨ RGB32使用32位来表示一个像素,RGB分量各用去8位,剩下的8位用作Alpha通道或者不用。(ARGB32就是带Alpha通道的RGB32。)注意在内存中RGB各分量的排列顺序为:BGRA BGRA BGRA…。通常可以使用RGBQUAD数据结构来操作一个像素,它的定义为:

typedef struct tagRGBQUAD {
BYTE rgbBlue; // 蓝色分量
BYTE rgbGreen; // 绿色分量
BYTE rgbRed; // 红色分量
BYTE rgbReserved; // 保留字节(用作Alpha通道或忽略)
} RGBQUAD;

下面介绍各种YUV格式。YUV格式通常有两大类:打包(packed)格式和平面(planar)格式。前者将YUV分量存放在同一个数组中,通常是几个相邻的像素组成一个宏像素(macro-pixel);而后者使用三个数组分开存放YUV三个分量,就像是一个三维平面一样。表2.3中的YUY2到Y211都是打包格式,而IF09到YVU9都是平面格式。(注意:在介绍各种具体格式时,YUV各分量都会带有下标,如Y0、U0、V0表示第一个像素的YUV分量,Y1、U1、V1表示第二个像素的YUV分量,以此类推。)

¨ YUY2(和YUYV)格式为每个像素保留Y分量,而UV分量在水平方向上每两个像素采样一次。一个宏像素为4个字节,实际表示2个像素。(4:2:2的意思为一个宏像素中有4个Y分量、2个U分量和2个V分量。)图像数据中YUV分量排列顺序如下:
Y0 U0 Y1 V0 Y2 U2 Y3 V2 …

¨ YVYU格式跟YUY2类似,只是图像数据中YUV分量的排列顺序有所不同:
Y0 V0 Y1 U0 Y2 V2 Y3 U2 …

¨ UYVY格式跟YUY2类似,只是图像数据中YUV分量的排列顺序有所不同:
U0 Y0 V0 Y1 U2 Y2 V2 Y3 …

¨ AYUV格式带有一个Alpha通道,并且为每个像素都提取YUV分量,图像数据格式如下:
A0 Y0 U0 V0 A1 Y1 U1 V1 …

¨ Y41P(和Y411)格式为每个像素保留Y分量,而UV分量在水平方向上每4个像素采样一次。一个宏像素为12个字节,实际表示8个像素。图像数据中YUV分量排列顺序如下:
U0 Y0 V0 Y1 U4 Y2 V4 Y3 Y4 Y5 Y6 Y8 …

¨ Y211格式在水平方向上Y分量每2个像素采样一次,而UV分量每4个像素采样一次。一个宏像素为4个字节,实际表示4个像素。图像数据中YUV分量排列顺序如下:
Y0 U0 Y2 V0 Y4 U4 Y6 V4 …

¨ YVU9格式为每个像素都提取Y分量,而在UV分量的提取时,首先将图像分成若干个4 x 4的宏块,然后每个宏块提取一个U分量和一个V分量。图像数据存储时,首先是整幅图像的Y分量数组,然后就跟着U分量数组,以及V分量数组。IF09格式与YVU9类似。

¨ IYUV格式为每个像素都提取Y分量,而在UV分量的提取时,首先将图像分成若干个2 x 2的宏块,然后每个宏块提取一个U分量和一个V分量。YV12格式与IYUV类似。

¨ YUV411、YUV420格式多见于DV数据中,前者用于NTSC制,后者用于PAL制。YUV411为每个像素都提取Y分量,而UV分量在水平方向上每4个像素采样一次。YUV420并非V分量采样为0,而是跟YUV411相比,在水平方向上提高一倍色差采样频率,在垂直方向上以U/V间隔的方式减小一半色差采样,如图2.12所示。

6.
深入浅出DirectShow Filter

By 陆其明
From http://hqtech.nease.net

1. Filter概述
Filter是一个COM组件,由一个或多个Pin组成。Pin也是一个COM组件。Filter文件的扩展名为.ax,但也可以是.dll。Filter根据其包含Input pin或Output pin的情况(或在Filter Graph的位置),大致可分为三类:Source Filter(仅有Output pin)、Transform Filter(同时具有Input pin和Output pin)和Renderer Filter(仅有Input pin)。

一般情况下,创建Filter使用一个普通的Win32 DLL项目。而且,一般Filter项目不使用MFC。这时,应用程序通过CoCreateInstance函数Filter实例;Filter与应用程序在二进制级别的协作。另外一种方法,也可以在MFC的应用程序项目中创建Filter。这种情况下,Filter不需注册为COM组件,Filter与应用程序之间的协作是源代码级别的;创建Filter实例,不再使用CoCreateInstance函数,而是直接new出一个Filter对象,如下:
m_pFilterObject = new CFilterClass();
// make the initial refcount 1 to match COM creation
m_pFilterObject ->AddRef();
因为Filter的基类实现了对象的引用计数,所以即使在第二种情况下,对创建后的Filter对象的操作也完全可以遵循COM标准。
Filter是一个独立功能模块,最好不要将Filter依赖于其他第三方的DLL。因为Filter具有COM的位置透明性特点,Filter文件可以放在硬盘的任何位置,只要位置移动后重新注册。但此时,如果Filter依赖其他DLL,则Filter对该DLL的定位就会出现问题。

Filter不能脱离Filter Graph单独使用。所以,如果你想绕过Filter Graph直接使用Filter实现的模块功能,请将你的Filter移植成DMO(DirectX Media Object)。对于DirectShow应用程序开发者来说,还有一点,请不要忘记使用OleInitialize进行初始化。

2. Filter的注册
Filter是COM组件,所以在使用前一定要注册。Filter的注册程序为regsvr32.exe。如果带上命令行参数/u,表示注销;如果带上是/s,表示不弹出任何注册/注销成功与否的提示对话框。如果你想在Build Filter项目的时候进行自动注册,请在VC的Project settings的Custom Build页如下设置:
Description: Register filter
Commands: regsvr32 /s /c $(TargetPath)
echo regsvr32 exe.time > $(TargetDir)\$(TargetName).trg
Outputs: $(TargetDir)\$(TargetName).trg

Filter的注册信息包括两部分:基本的COM信息和Filter信息。注册信息都存放在注册表中。前者的位置为:HKEY_CLASSES_ROOT\CLSID\Filter Clsid\,后者的位置为:HKEY_CLASSES_ROOT\CLSID\Category\Instance\ Filter Clsid\。COM信息标示了Filter是一个标准的可以通过CoCreateInstance函数创建的COM组件,Filter信息标示了我们通过Graphedit看到的描述这个Filter的信息。如果你不想让Graphedit看到(或者让Filter枚举器找到)你写的Filter,你完全可以不注册Filter信息。而且不用担心,你这么做也完全不会影响Filter的功能。
屏蔽注册Filter信息的方法也很简单。因为CBaseFilter实现了IAMovieSetup接口的两个函数:Register和Unregister。我们只需重载这两个函数,直接return S_OK就行了。

Filter的Merit值。这个值是微软的“智能连接”函数使用的。在Graphedit中,当我们加入一个Source Filter后,在它的pin上执行“Render”,会自动连上一些Filter。Merit的值参考如下:
MERIT_PREFERRED = 0x800000,
MERIT_NORMAL = 0x600000,
MERIT_UNLIKELY = 0x400000,
MERIT_DO_NOT_USE = 0x200000,
MERIT_SW_COMPRESSOR = 0x100000,
MERIT_HW_COMPRESSOR = 0x100050
Merit值只有大于MERIT_DO_NOT_USE的时候才有可能被“智能连接”使用;Merit的值越大,这个Filter的机会就越大。

3. Filter之间Pin的连接过程
Filter只有加入到Filter Graph中并且和其它Filter连接成完整的链路后,才会发挥作用。Filter之间的连接(也就是Pin之间的连接),实际上是连接双方的一个Media type的协商过程。连接的方向总是从Output pin指向Input pin。连接的大致过程为:如果调用连接函数时已经指定了完整的Media type,则用这个Media type进行连接,成功与否都结束连接过程;如果没有指定或不完全指定了Media type,则进入下面的枚举过程。枚举欲连接的Input pin上所有的Media type,逐一用这些Media type与Output pin进行连接(如果连接函数提供了不完全Media type,则要先将每个枚举出来的Media type与它进行匹配检查),如果Output pin也接受这种Media type,则Pin之间的连接宣告成功;如果所有Input pin上枚举的Media type,Output pin都不支持,则枚举Output pin上的所有Media type,并逐一用这些Media type与Input pin进行连接。如果Input pin接受其中的一种Media type,则Pin之间的连接到此也宣告成功;如果Output pin上的所有Media type,Input pin都不支持,则这两个Pin之间的连接过程宣告失败。

每个Pin都可以实现GetMediaType函数来提供该Pin上支持的所有Preferred Media type(但一般只在Output pin上实现,Input pin主要实现CheckMediaType看是否支持当前提供的Media type就行了)。连接过程中,Pin上枚举得到的所有Media type就是这里提供的。

在CBasePin类中有一个protected的成员变量m_bTryMyTypesFirst,默认值为false。在我们定制Filter的Output pin中改变这个变量的值为true,可以定制我们自己的连接过程(先枚举Output pin上的Media type)。

当Pin之间的连接成功后,各自的pin上都会调用CompleteConnect函数。我们可以在这里取得一些连接上的Media type的信息,以及进行一些计算等。在Output pin的CompleteConnect实现中,还有一个重要的任务,就是协商Filter Graph运行起来后Sample传输使用的内存配置情况。这同样是一个交互过程:首先要询问一下Input pin上的配置要求,如果Input pin提供内存管理器(Allocator),则优先使用Input pin上的内存管理器;否则,使用Output pin自己生成的内存管理器。我们一般都要实现DecideBufferSize来决定存放Sample的内存大小。注意:这个过程协商完成之后,实际的内存并没有分配,而要等到Output pin上的Active函数调用。

4. Filter Media type概述
Media type一般可以有两种表示:AM_MEDIA_TYPE和CMediaType。前者是一个Struct,后者是从这个Struct继承过来的类。
每个Media type有三部分组成:Major type、Subtype和Format type。这三个部分都使用GUID来唯一标示。Major type主要定性描述一种Media type,比如指定这是一个Video,或Audio或Stream等;Subtype进一步细化Media type,如果Video的话可以进一步指定是UYVY或YUY2或RGB24或RGB32等;Format type用一个Struct更进一步细化Media type。
如果Media type的三个部分都是指定了某个具体的GUID值,则称这个Media type是完全指定的;如果Media type的三个部分中有任何一个值是GUID_NULL,则称这个Media type 是不完全指定的。GUID_NULL具有通配符的作用。

常用的Major type:
MEDIATYPE_Video;
MEDIATYPE_Audio;
MEDIATYPE_AnalogVideo; // Analog capture
MEDIATYPE_AnalogAudio;
MEDIATYPE_Text;
MEDIATYPE_Midi;
MEDIATYPE_Stream;
MEDIATYPE_Interleaved; // DV camcorder
MEDIATYPE_MPEG1SystemStream;
MEDIATYPE_MPEG2_PACK;
MEDIATYPE_MPEG2_PES;
MEDIATYPE_DVD_ENCRYPTED_PACK;
MEDIATYPE_DVD_NAVIGATION;

常用的Subtype:
MEDIASUBTYPE_YUY2;
MEDIASUBTYPE_YVYU;
MEDIASUBTYPE_YUYV;
MEDIASUBTYPE_UYVY;
MEDIASUBTYPE_YVU9;
MEDIASUBTYPE_Y411;
MEDIASUBTYPE_RGB4;
MEDIASUBTYPE_RGB8;
MEDIASUBTYPE_RGB565;
MEDIASUBTYPE_RGB555;
MEDIASUBTYPE_RGB24;
MEDIASUBTYPE_RGB32;
MEDIASUBTYPE_ARGB32; // Contains alpha value
MEDIASUBTYPE_Overlay;
MEDIASUBTYPE_MPEG1Packet;
MEDIASUBTYPE_MPEG1Payload; // Video payload
MEDIASUBTYPE_MPEG1AudioPayload; // Audio payload
MEDIASUBTYPE_MPEG1System; // A/V payload
MEDIASUBTYPE_MPEG1VideoCD;
MEDIASUBTYPE_MPEG1Video;
MEDIASUBTYPE_MPEG1Audio;
MEDIASUBTYPE_Avi;
MEDIASUBTYPE_Asf;
MEDIASUBTYPE_QTMovie;
MEDIASUBTYPE_PCM;
MEDIASUBTYPE_WAVE;
MEDIASUBTYPE_dvsd; // DV
MEDIASUBTYPE_dvhd;
MEDIASUBTYPE_dvsl;
MEDIASUBTYPE_MPEG2_VIDEO;
MEDIASUBTYPE_MPEG2_PROGRAM;
MEDIASUBTYPE_MPEG2_TRANSPORT;
MEDIASUBTYPE_MPEG2_AUDIO;
MEDIASUBTYPE_DOLBY_AC3;
MEDIASUBTYPE_DVD_SUBPICTURE;
MEDIASUBTYPE_DVD_LPCM_AUDIO;
MEDIASUBTYPE_DVD_NAVIGATION_PCI;
MEDIASUBTYPE_DVD_NAVIGATION_DSI;
MEDIASUBTYPE_DVD_NAVIGATION_PROVIDER;

常用的Format type:
FORMAT_None
FORMAT_DvInfo DVINFO
FORMAT_MPEGVideo MPEG1VIDEOINFO
FORMAT_MPEG2Video MPEG2VIDEOINFO
FORMAT_VideoInfo VIDEOINFOHEADER
FORMAT_VideoInfo2 VIDEOINFOHEADER2
FORMAT_WaveFormatEx WAVEFORMATEX

5. Filter之间的数据传送
Filter之间的数据是通过Sample来传送的。Sample是一个COM组件,拥有自己的一段数据缓冲。Sample由Allocator统一管理。如下图所示:

Filter之间数据传送的方式有两种:Push模式和Pull模式。

所谓Push模式,即Source filter自己能够产生数据,并且一般在它的Output pin上有独立的子线程负责将数据发送出去,常见的情况如WDM模型的采集卡的Live Source Filter;而所谓Pull模式,即Source filter不具有把自己的数据送出去的能力,这种情况下,一般Source filter后紧跟着接一个Parser Filter或Splitter Filter,这种Filter一般在Input pin上有个独立的子线程,负责不断地从Source filter索取数据,然后经过处理后将数据传送下去,常见的情况如File source。Push模式下,Source filter是主动的;Pull模式下,Source filter是被动的。而事实上,如果将上图Pull模式中的Source filter和Splitter Filter看成另一个虚拟的Source filter,则后面的Filter之间的数据传送也与Push模式完全相同。

那么,数据到底是怎么通过连接着的Pin传送的呢?首先来看Push模式。在Source filter后面Filter的 Input pin上,一定实现了一个IMemInputPin接口,数据正是通过上一级Filter调用这个接口的Receive方法进行传送的。值得注意的是,数据从Output pin通过Receive方法调用传送到Input pin上,并没有进行内存拷贝,它只是一个相当于数据到达的“通知”。再看一下Pull模式。Pull模式下的Source filter的 Output pin上,一定实现了一个IAsyncReader接口;其后面的Splitter Filter,就是通过调用这个接口的Request方法或者SyncRead方法来获得数据。Splitter Filter然后像Push模式一样,调用下一级Filter的Input pin上的IMemInputPin接口Receive方法实现数据的往下传送。

一个DirectShow的应用程序,至少会有两条线程:主线程和Filter用于数据传送的子线程。既然是多线程,就不可避免会出现线程同步问题。Filter的状态改变都在主线程中完成,Filter的数据相关操作都在数据线程中调用。各线程一些主要函数调用参考如下:
Streaming thread(s): IMemInputPin::Receive, IMemInputPin::ReceiveMultiple, IPin::EndOfStream, IMemAllocator::GetBuffer.
Application thread: IMediaFilter::Pause, IMediaFilter::Run, IMediaFilter::Stop, IMediaSeeking::SetPositions, IPin::BeginFlush, IPin::EndFlush.
Either: IPin::NewSegment.
这些函数切忌混合调用,否则会引起线程的死锁。另外值得注意的是,BeginFlush和EndFlush属于主线程调用,而不是数据线程调用。

6. Transform filter和Trans-in-place filter的区别
首先,这两种Filter是有共同点的,因为Trans-in-place filter本身就是从Transform filter中继承过来的。其次,我们要明白的是,Trans-in-place filter“尽力”使自己的Input pin和Output pin使用相同的Allocator,以免去一次Sample数据的memcpy。我们说“尽力”,就是说Trans-in-place filter也未必能够实现它的初衷。(如果Trans-in-place filter使用的Allocator是ReadOnly的,而Trans-in-place filter又要修改Sample的数据,则Trans-in-place filter的Input pin和Output pin将不得不使用不同的Allocator。)
Trans-in-place filter有一个protected的成员变量m_bModifiesData,默认值为true。如果你确信定制Trans-in-place filter不需要修改Sample数据,则将m_bModifiesData赋值为false,这样可以保证Input pin和Output pin使用相同的Allocator。

Trans-in-place filter的实现主要体现在以下三个函数:CTransInPlaceFilter::CompleteConnect、CTransInPlaceInputPin::GetAllocator和CTransInPlaceInputPin::NotifyAllocator。CompleteConnect中进行必要的重连(Reconnect),保证Trans-in-place filter的Input pin和Output pin使用相同的Media type。GetAllocator能够取得Trans-in-place filter下一级Filter的Input pin上的Allocator。NotifyAllocator“尽力”使Trans-in-place filter的Input pin和Output pin使用同一个Allocator。

7. IMediaSeeking的实现
IMediaSeeking的实现在Filter上,但应用程序应该从Filter Graph Manager上得到这个接口。在Filter级别,Filter Graph Manager首先从Renderer filter开始询问上一级Filter的Output pin是否支持IMediaSeeking接口。如果支持,则返回这个接口;如果不支持,则继续往上一级Filter询问,直到Source filter。一般在Source filter的Output pin上实现IMediaSeeking接口。(如果是File source,一般在Parser Filter或Splitter Filter实现这个接口。)对于Filter开发者来说,如果我们写的是Source filter,就要在Filter的Output pin上实现IMediaSeeking接口;如果写的是Transform filter,只需要在Output pin上将用户的接口请求往上传递给上一级Filter的Output pin;如果写的是Renderer Filter,需要在Filter上将用户的接口请求往上传递给上一级Filter的Output pin。

注意:为了保证Seek操作后Stream的同步性,如果实际实现IMediaSeeking接口的Filter有多个Output pin,一般仅有一个pin支持Seek操作。对于你定制的Transform filter,如果有多个Input pin,你需要自己决定当Output pin接收到IMediaSeeking接口请求时选择哪一条路径往上继续请求。

应用程序能够在任何时候(running, paused or stopped)对Filter graph执行Seek操作。但当Filter graph正在running的时候,Filter graph manager会先pause住,执行完Seek操作后,再重新run起来。

IMediaSeeking可以有如下几种Seek的时间格式:
TIME_FORMAT_FRAME Video frames.
TIME_FORMAT_SAMPLE Samples in the stream.
TIME_FORMAT_FIELD Interlaced video fields.
TIME_FORMAT_BYTE Byte offset within the stream.
TIME_FORMAT_MEDIA_TIME Reference time (100-nanosecond units).
但实现这个接口的Filter未必支持所有的这些格式。一般Filter都会支持TIME_FORMAT_MEDIA_ TIME,当使用其它的格式时,最好调用IMediaSeeking::IsFormatSupported进行一下确认。

对于Filter,不赞成使用IMediaPosition接口。IMediaPosition是用以支持Automation的(比如VB里面使用DirectShow),IMediaSeeking不支持Automation。

8. Filter的状态转换
Filter有三种状态:stopped, paused, running。paused是一种中间状态,stopped状态到running状态必定经过paused状态。paused可以理解为数据就绪状态,是为了快速切换到running状态而设计的。在paused状态下,数据线程是启动的,但被Renderer filter阻塞了。

paused与running两者间的状态转换,对于Source filter和Transform filter可以忽略不计,而对于Renderer filter(特别是Video renderer / Audio renderer)情形稍有不同。Renderer首先处理那个paused状态下Hold的Sample,当接收到新的Sample时,判断Sample上的时间戳。如果时间未到,Renderer会Hold住这个Sample进行等待。

Filter graph manager以从下到上的顺序对Filter进行状态转换,即从Renderer filter一直回溯到Source filter。这个顺序能够有效地避免Sample的丢失以及Filter graph的死锁。
Stopped to paused:首先从Renderer开始进行paused状态的转换。这时,Filter调用自己所有Pin的Active函数进行初始化(一般Pin在Active中进行Sample内存的分配,如果是Source filter还将启动数据线程),使Filter处于一种就绪状态。Source filter是最后一个完成到就绪状态转换的Filter。然后,Source filter启动数据线程,往下发送Sample。当Renderer接收到第一个Sample后就阻塞住。当所有的Renderer实现了状态转换,Filter graph manager才认为状态转换完成。
Paused to stopped:当Filter进入stopped状态时,调用自己所有Pin的Inactive函数(一般Pin在Inactive中进行Sample内存的释放,如果是Source filter还将终止数据线程)。释放所有Hold的Sample,以使上一级Filter的GetBuffer脱离阻塞;终止所有在Receive中的等待,以使上一级Filter的Receive函数调用返回。Filter在stopped状态下拒绝接受任何Sample。这样从Renderer filter往上一级一级脱离阻塞,当到达Source filter的时候,可以确保数据线程终止。

9. EndOfStream问题
当Source filter的所有数据都已经发送出去,则会调用下一级Filter的Input pin上的IPin::EndOfStream,直到Renderer filter。当这个Renderer filter的所有Input pin都被调用了EndOfStream,则向Filter graph manager发送一个EC_COMPLETE事件。仅当Filter graph中的所有Stream都发送了EC_COMPLETE事件,Filter graph manager才会将这个事件发送给应用程序。

在我们定制的Filter中,如果接收到了上一级Filter传过来的EndOfStream,则说明上面的数据已经全部传送完毕,Receive方法不须再接收数据。如果我们对数据进行了缓冲,则应确认缓冲中的所有数据都被处理完并往下发送了,然后再往下调用EndOfStream。Pull模式下,一般是Splitter filter或Parser filter发送EndOfStream,而且方向是往下的,Source filter上不会收到这样的通知。

10. BeginFlush、EndFlush、NewSegment问题
典型的情况,当进行MediaSeeking之后,会调用BeginFlush、EndFlush。一般在Input pin上实现这两个函数。
对于Filter开发者来说,Filter在被调用BeginFlush时需要做以下工作:
· 调用下一级Filter的BeginFlush,使其不再接收新的Sample;
· 拒绝接收上一级Filter的数据,包括Receive调用和EndOfStream调用;
· 如果上一级Filter正在阻塞等待空的Sample,此时需要让它脱离阻塞(通过析构Allocator);
· 确保数据流线程脱离阻塞状态。
Filter在被调用EndFlush时需要做以下工作:
· 确保所有等待缓存的Sample被丢弃;
· 确保Filter上已经缓存的数据被丢弃;
· 清除没有发出去的EC_COMPLETE事件(如果这是一个Rendered input pin);
· 调用下一级Filter的EndFlush。
还有一点:如果你必须在定制的Filter中为每个Sample打Time stamp,那么记住在MediaSeeking之后出去的Sample的Time stamp应该从0开始重打。

Segment是一段时间内具有相同的Playback rate的一组Sample,以NewSegment函数调用来表示这个Segment的开始。NewSegment一般在开始新的Stream的时候,或者用户进行了MediaSeeking之后,由Source filter(Push模式下)或Parser/Splitter filter(Pull模式下)发起,并往下层层调用,一直到Renderer filter。
在我们定制的Filter中可以利用NewSegment传递下来的信息,特别是对于Decoder。对于Audio renderer也是一个典型例子,它根据Playback rate和Audio实际的采样频率来对声卡产生输出。

11. Quality Control问题
Filter之间的数据传送,有时候过快,有时候过慢。DirectShow使用Quality Control来解决这个问题,即IQualityControl接口的两个函数(SetSink和Notify)。一般,Renderer filter在Filter上实现这个接口,而其他Filter在Output pin上实现这个接口。

上图为一般的Quality Control的处理过程。而能够调整发送速度的IQualityControl接口一般在Source filter(pull模式下为parser/splitter filter)上实现,Transform filter只是将Quality Message往上一级Filter传递。
应用程序可以实现自己的Quality Control Manager,然后通过调用SetSink方法设置给Filter。上述的处理过程就改变了,Quality Message直接发送给自定义的Manager。但一般并不这么做。值得注意的是,具体的Quality Control实现取决于实际的Filter,可能是调整发送速度,也可能会丢失部分数据。所以,请慎重使用Quality Message。

12. 对运行过程中Media type改变的支持
我们可以从CBaseInputPin::Receive中可以看到,Input pin每次在接收Sample的之前,一般都会进行CheckStreaming(如果当前Filter已经Stop或正在Flush或发生了RuntimeError,则拒绝接收Sample),然后将当前的Sample属性保存到protected的m_SampleProps成员变量中。描述Sample属性的是一个AM_SAMPLE2_PROPERTIES的结构,它有一个标记来表明当前Sample的Media type是否已经改变。如果Media type改变了,则进行CheckMedaiType看我们的Filter是否仍然支持它。如果不支持,则发出一个RuntimeError,并发送EndOfStream。
一个健全的Filter应该能够对运行时Media type的改变做出处理。在我们的Receive方法实现中,我们可以通过if (pProps->dwSampleFlags & AM_SAMPLE_TYPECHANGED)来判断Meida type是否已经改变;如果改变,我们需要根据新的Media type进行必要的初始化。

一个典型的案例:当Camcorder输入时,Audio的Media type可能改变。比如,Filter连接时Media type使用了MEDIATYPE_PCM,而在运行时又换成了MEDIATYPE_WAVE;或者连接时Audio的采样频率时44.1K,而在运行时却变成了48K;或者Camcorder的带子上本身保存了混合的44.1K和48K的Audio。

7.
DirectShow视频捕捉WDM Vs VFW

By 陆其明
From http://hqtech.nease.net

说起视频捕捉问题,我们先要来看一下视频捕捉卡。根据使用的驱动程序的不同来分类,目前市场上大致有两种捕捉卡:VFW (Video for Windows)卡和WDM (Windows Driver Model)卡。前者是一种趋于废弃的驱动模型,而后者是前者的替代模型;WDM还支持更多新的特性,比如直接支持电视接收、视频会议、1394接口的设备、桌面摄像机、多条视频流(Line-21或Closed- Caption等)同时输出等等。采用VFW的一般都是些以前生产的卡;市面上新出现的,一般都是采用了WDM驱动程序。另外,视频捕捉卡的接口,可以是以PCI或AGP的方式插入PC机箱,也可以直接以USB接口的方式外挂;还有就是通过1394接口与PC机相连的数码摄像机等等。

使用DirectShow来处理一般的视频捕捉问题,是相对比较简单的。这当然得益于DirectShow这一整套先进的应用架构。捕捉卡通常也是以一个 (Capture) Filter的形式出现的。处理视频捕捉,我们同样是使用Filter Graph,同样是操作Filter;控制起来,就似于操作媒体文件的播放。当然,这主要是从应用程序控制层面上来说的;视频捕捉的应用场合比较多,视频捕捉本身的一些处理还是有它的特殊性的,而且牵涉面比较广。本文侧重于阐述一个建立视频捕捉程序的一般过程,以及WDM与VFW的兼容性问题。

当视频捕捉卡正确安装到系统中后,使用GraphEdit插入Filter,我们可以在“Video Capture Sources”目录下看到代表捕捉卡的那个Filter。一般一个Capture Filter至少有一个Capture Output Pin;典型的情况下,还有一个 Preview Pin或者Video Port Pin(一般Preview Pin和VP Pin不会共存)。有些视频捕捉卡,能够同时捕捉 Video和Audio,那么它的Filter自然还应该有Audio部分的输出Pin。视频捕捉卡都注册在 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(&pDevEnum));
if (SUCCEEDED(hr))
{
// Create an enumerator for the video capture category.
hr = pDevEnum->CreateClassEnumerator(
CLSID_VideoInputDeviceCategory, &pEnum, 0);
}
Capture Filter的创建也不是象其他Filter一样使用CoCreateInstance,而是在枚举的过程中BindToObject。在这一点上,对WDM卡和VFW卡的处理是一致的。

将Capture Filter加入Filter Graph之后,剩下的Filter怎么连接?DirectShow给我们提供了一个简单的解决方法:使用ICaptureGraphBuilder2接口。如下创建:
IGraphBuilder *pGraph = 0;
ICaptureGraphBuilder2 *pBuild = 0;
// Create the Capture Graph Builder.
HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, 0,
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);
}
}
接下来,就是使用ICaptureGraphBuilder2::RenderStream来继续各个Output Pin的连接。值得注意的是,这里有一个Pin Category的概念,作为RenderStream的第一个参数,比如Preview Pin的目录为 PIN_CATEGORY_PREVIEW,Capture Pin的目录为PIN_CATEGORY_CAPTURE等等。下面是 Preview Pin的连接示例:
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);
调用RenderStream实现Preview链路,不管Capture Filter是否有Preview Pin或者只有VP Pin, Capture Graph Builder都能自动正确地处理。(如果只有VP Pin,则自动连接VP Pin;如果Capture Filter只有一个Capture Output Pin,则自动插入一个Smart Tee Filter然后再连接。)

要实现视频捕捉到文件,最简单的方法也是使用ICaptureGraphBuilder2::RenderStream。如下(假设生成的是AVI文件):
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);
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();
下面是典型的两个经过RenderStream以后构建的Capture Filter Graph的示意图:





使用Capture Graph Builder构建Filter链路的好处,还在于它能自动加入Crossbar Filter(用于选择捕捉卡的输入端子,一般有三种:AV、S-Video、TV),如果是电视卡的话还有TV Tuner Filter等等;使用 ICaptureGraphBuilder2::FindInterface就可以找到相应的控制接口等等。

跟WDM卡相比,VFW卡实现的功能要简单得多。上述的Filter Graph创建过程,两种卡的处理是相似的;而对于视频捕捉的设置,则有较大的差异。WDM Capture Filter的执行文件为kswdmcap.ax,它实际上是kernel-mode下KsProxy的一个插件;而 DirectShow使用了一个标识为CLSID_VfwCapture的Filter来支持VFW卡。WDM卡,设置Capture输出的图像格式、图像的对比度、亮度、色度、饱和度等,都是通过IAMStreamConfig、IAMVideoProcAmp等接口来实现(当然,在GraphEdit 中可以通过Filter的Property Page来设置);而VFW卡,一般要将驱动程序内的设置对话框显示给用户。VFW驱动程序一般实现三个设置对话框:Video Source(设置图像源属性)、Video Format(设置图像输出格式)和Video Display(设置图像显示属性)。下面是显示Video Source对话框的示例:
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();

以上讲述了视频捕捉程序创建的一般过程。视频捕捉还有其他问题,比如AV同步、设备的热插拔、DV Camcorder的控制、Analog TV以及 Digital TV的支持,还有捕捉后的音视频压缩、音视频合成等等。要想编写出专业级的视频捕捉程序,这些问题是不可回避的!

8.
Tom Miller's Blog
http://blogs.msdn.com/tmiller/



<< Home

This page is powered by Blogger. Isn't yours?