DirectShow中FilterGraph及其组成

原文:http://blog.csdn.net/l5201314131413141314/article/details/7107324


 DirectShow是微软公司提供的一套在Windows平台上进行流媒体处理的开发包,与DirectX开发包一起发布。那么,DirectShow能够做些什么呢?且看,DirectShow为多媒体流的捕捉和回放提供了强有力的支持。运用DirectShow,我们可以很方便地从支持WDM驱动模型的采集卡上捕获数据,并且进行相应的后期处理乃至存储到文件中。它广泛地支持各种媒体格式,包括Asf、Mpeg、Avi、Dv、Mp3、Wave等等,使得多媒体数据的回放变得轻而易举。另外,DirectShow还集成了DirectX其它部分(比如DirectDraw、DirectSound)的技术,直接支持DVD的播放,视频的非线性编辑,以及与数字摄像机的数据交换。更值得一提的是,DirectShow提供的是一种开放式的开发环境,我们可以根据自己的需要定制自己的组件。


      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不能够支持同一种的媒体类型,那么他们就没法连接起来。


  






    对于大多数的应用来说,也许你不用考虑媒体类型,但是,有些应用程序中,你会直接应用到媒体类型的。


     媒体类型是通过AM_MEDIA_TYPE结构定义的,看看原始定义吧


    typedef struct  _MediaType {


        GUID      majortype;


        GUID      subtype;


        BOOL      bFixedSizeSamples;


        BOOL      bTemporalCompression;


        ULONG     lSampleSize;


        GUID      formattype;


        IUnknown  *pUnk;


        ULONG     cbFormat;


        [size_is(cbFormat)] BYTE *pbFormat;


    } AM_MEDIA_TYPE;


    Major type:是一个GUID,用来定义数据的主类型,包括,音频,视频,unparsed字节流,MIDI数据,等等,具体可以参考msdn。


    Subtype:子类型,也是一个GUID,用来进一步的细化数据格式,例如,在视频主类型中,还包括RGB-24, RGB-32, UYVY等等一些子类型,在音频主类型中还包括PCM audio, MPEG-1 payload等类型,子类型提供了比主类型更详细的信息,但是并没有定义所有的格式,例如,视频的子类型并没有定义图像大小,桢率。这些由下面的字段定义。


    bFixedSizeSamples当这个值为TRUE时,表示sample大小固定。


    bTemporalCompression当这个值为TRUE时,表示sample采用了临时压缩格式,表明不是所有的桢都是关键桢,如果为FALSE,表明所有的都是关键桢。


    lSampleSize 表示sample的大小。对于压缩的数据,这个值可能为零。


    Formattype一个GUID值,用来表明内存块的格式。包括如下:FORMAT_None,FORMAT_DvInfo,FORMAT_MPEGVideo,FORMAT_MPEG2Video,FORMAT_VideoInfo,


    FORMAT_VideoInfo2,FORMAT_WaveFormatEx,GUID_NULL


    pUnk该参数没有用到


    cbFormat内存块的大小


    pbFormat指向内存块的指针,


    下面我们看一段代码,看看filter如何检测媒体类型的。


    HRESULT CheckMediaType(AM_MEDIA_TYPE *pmt)


    {


        if (pmt == NULL) return E_POINTER;


        // Check the major type. We're looking for video.


        if (pmt->majortype != MEDIATYPE_Video)


        {


            return VFW_E_INVALIDMEDIATYPE;


        }


        // Check the subtype. We're looking for 24-bit RGB.


        if (pmt->subtype != MEDIASUBTYPE_RGB24)


        {


            return VFW_E_INVALIDMEDIATYPE;


        }


        // Check the format type and the size of the format block.


        if ((pmt->formattype == FORMAT_VideoInfo) &&


             (pmt->cbFormat >= sizeof(VIDEOINFOHEADER) &&


             (pmt->pbFormat != NULL))


        {


           // Now it's safe to coerce the format block pointer to the


            // correct structure, as defined by the formattype GUID.


            VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)pmt->pbFormat;


           // Examine pVIH (not shown). If it looks OK, return S_OK.


            return S_OK;


        }


        return VFW_E_INVALIDMEDIATYPE;


    }


    下面简单介绍几个和 Media Type相关的函数


    AM_MEDIA_TYPE结构包含一个指向数据块的指针,因此,当你使用这个结构的时候,一定要小心内存分配,以防内存泄漏。


   






 分配函数


    1 AM_MEDIA_TYPE * WINAPI CreateMediaType(


        AM_MEDIA_TYPE const *pSrc );


    这个函数分配一个新的AM_MEDIA_TYPE结构,包含特定格式的数据块。释放由这个函数分配的内存,可以调用DeleteMediaType. 函数


    2 STDAPI CreateAudioMediaType(


        const WAVEFORMATEX *pwfx,


        AM_MEDIA_TYPE *pmt,


        BOOL bSetFormat);


    该函数利用一个给定的WAVEFORMATIEX结构来初始化媒体类型,如果bsetFormat参数为TRUE,该函数就分配一块新的内存,如果原来的pmt已经包含内存,就有可能发生内存泄漏。为了避免内存泄漏,在调用这个函数前要调用FreeMediaType(),在这个函数返回之后,再次调用FreeMediaType(),释放format block。


    3HRESULT WINAPI CopyMediaType(


        AM_MEDIA_TYPE *pmtTarget,


        const AM_MEDIA_TYPE *pmtSource);


    这个函数复制了一个结构到另一个结构中去。这个函数也要重新分配内存给目的结构,如果pmtTarget,已经包含一个内存块,就要内存泄漏,因此,在调用该函数前后都要调用FreeMediaType函数。


    释放函数


    4 void WINAPI DeleteMediaType( AM_MEDIA_TYPE *pmt);


    无论是采用CoTaskMemAlloc函数还是用CreateMediaType函数分配的内存都可以用这个函数来释放,如果你没有连接基类的动态库,你可以用下面的代码


    void MyDeleteMediaType(AM_MEDIA_TYPE *pmt)


    {


        if (pmt != NULL)


        {


            MyFreeMediaType(*pmt); // 见下面的 FreeMediaType  函数


            CoTaskMemFree(pmt);


        }


    }


    5 void WINAPI FreeMediaType( AM_MEDIA_TYPE& mt);


    这个函数用来释放数据块的内存,如果要删除AM_MEDIA_TYPE结构,可以使用DeleteMediaType函数。


    void MyFreeMediaType(AM_MEDIA_TYPE& mt)


    {


        if (mt.cbFormat != 0)


        {


            CoTaskMemFree((PVOID)mt.pbFormat);


            mt.cbFormat = 0;


            mt.pbFormat = NULL;


        }


        if (mt.pUnk != NULL)


        {


            // Unecessary because pUnk should not be used, but safest.


            mt.pUnk->Release();


            mt.pUnk = NULL;


        }


    }


    4 媒体Samples和Allocators


 


      Filters通过pin的连接来传递数据,数据流是从一个filter的输出pin流向相连的filter的输入pin。输出pin常用的传递数据的方式是调用输入pin上的IMemInputPin::Receive方法。


      对于filter来说,可以有好几种方式来分配媒体数据使用的内存块,可以在堆上分配,可以在DirectDraw的表面,也可以采用GDI共享内存,还有其他的一些方法,在Directshow中用来进行内存分配任务的是内存分配器(allocator),也是一个COM对象,暴露了一个IMemAllocator接口。


      当两个pin连接的时候,必须有一个pin提供一个allocator,Directshow定义了一系列函数调用用来确定由哪个pin提供allocator,以及buffer的数量和大小。


      在数据流开始之前,allocator会创建一个内存池(pool of buffer),在开始发送数据流以后,源filter就会将数据填充到内存池中一个空闲的buffer中,然后传递给下面的filter。但是,源filter并不是直接将内存buffer的指针直接传递给下游的filter,而是通过一个media samples的COM对象,这个sample是allocator创建的用来管理内存buffer。Media sample暴露了IMediaSample接口,一个sample包含了下面的内容:


     一个指向没有发送的内存的指针。


     一个时间戳


     一些标志


     媒体类型。


    时间戳表明了presentation time,Renderer filter就是根据这个时间来安排render顺序的。标志是用来标示数据是否中断等等,媒体类型提供了中途改变数据格式的一种方法,不过,一般sample没有媒体类型,表明它们的媒体类型一直没有改变。


      当一个filter正在使用buffer,它就会保持一个sample的引用计数,allocator通过sample的引用计数用来确定是否可以重新使用一个buffer。这样就防止了buffer的使用冲突,当所有的filter都释放了对sample的引用,sample才返回到allocator的内存池,供重新使用。


  






  5硬件设备在graph中的作用


     下面的这段话借用的是陆其明的一段文档,特此标记2005-1-26我觉得他对硬件的表述比较清楚。


    大家知道,为了提高系统的稳定性,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,)。


    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已经帮我们做好了。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值