现在的位置: 首页 > 综合 > 正文

DirectShow系统综述

2013年02月05日 ⁄ 综合 ⁄ 共 15844字 ⁄ 字号 评论关闭

DirectShow系统综述

翻译自MSDN DirectShow文档, 译者 王斐

多媒体的挑战

使用多媒体会出现以下主要的挑战

1 多媒体包含了大量的需要快速处理的数据。
2 为了开启和停止时间相同,并且以相同的比率播放,音频和视频必须同步。
3 数据可以有各种格式,比如音频-视频交叉存取(AVI),高级流格式(ASF), 电影专家组(MPEG),以及数字视频(DV)。
4 程序员事先并不知道最终用户系统会出现什么样的硬件设备。

DirectShow提供的解决办法

    DirectShow被设计来解决以上的挑战。主要的设计目的是通过将应用程序与数据传输,硬件区别以及同步等复杂过程分隔开来从而简化在微软平台创建数字媒体应用程序的任务。
    为了达到视频和音频流所需要的吞吐量, DirectShow尽可能的使用DirectDraw和DirectSound技术。这些技术可以有效的将数据提交给声卡和显卡。DirectShow通过时间戳将媒体数据样本封装的办法来达到同步播放。为了处理各种可能的信息源,格式以及硬件设备,DirectShow使用了模组结构,从而应用程序可以融合匹配不同的被称作滤波器(Filter)的软件组件。
    DirectShow提供支持基于微软驱动模型(WDM)的捕获及调谐设备的滤波器(Filter),从前遗留的基于微软视频(Video for Windows)的捕获卡的滤波器,以及为音频压缩管理器(ACM)和视频压缩管理器(VCM)接口编写的多媒体数字信号编解码器(CODECS) 。
以下图示显示了应用程序,DirectShow组件,一些DirectShow支持的硬件软件组件之间的联系。

    如图所示,DirectShow滤波器可以与很多硬件通讯并控制它们,包括当地文件系统,TV调谐器,视频捕获卡, VFM编解码器,视频显示器(通过DirectShow和图形设备接口(GDI)),以及声卡(通过DirectSound)。这样DirectShow就将应用程序与设备的复杂事物隔离开来。DirectShow对于一定格式的文件也提供自带的压缩,解压滤波器。

过滤器图形及其组件

    这篇文章描述了DirectShow的主要组件。可以作为应用程序及定制DirectShow滤波器的开发人员的介绍性文档。应用程序开发人员通常可以不理会DirectShow中许多底层的细节。然而,为了全面理解DirectShow的结构,阅读这一章也是一个不错的想法。

关于DirectShow滤波器

    DirectShow使用模组结构,处理的每一个阶段都通过一个叫滤波器的COM组件来完成。DirectShow为应用程序提供了一套标准的滤波器,开发人员定制自己的滤波器来扩展DirectShow。这里有几个步骤来播放AVI视频文件,每一步都是通过滤波器来完成。

1 从字节流文件中读取原始数据(文件源滤波器-File Source Filter)
2 检测AVI头,将字节流解析成单独的视频桢和音频样本(AVI 劈分滤波器-AVI Splitter Filter)
3 解码视频桢(根据压缩格式的不同,存在不同的解码滤波器)
4 绘制视频桢(视频实现滤波器-Video Render Filter)
5 将音频样本送到声卡(缺省的DirectSound设备滤波器)

    如图所示,每一个滤波器与一个或多个其它的滤波器相连。而连接点也是COM对象,叫做针(pin)。滤波器使用针从一个滤波器向另外一个移动数据。图形中的箭头显示了数据传输的方向。在DirectShow中,滤波器集合为称作滤波器图形。

    滤波器有三个可能的状态:运行,停止和暂停。当滤波器运行的时候,它可以处理数据。当停止时,它停止处理数据。暂停状态用来在运行前提示数据。Data Flow in the Filter Graph部分详细描述的这个概念。大多数情况下,整个过滤器图形(Filter Graph)中的状态变化是同等的。图形中的过滤器统一调整状态。这样,整个滤波器图形也可以被称作运行,停止或暂停。

    滤波器可以被分为以下几个比较大的类别:
1 源滤波器(Source Filter)将数据导入图形(Graph)。 数据可能来自文件,网络,相机或则其他地方。每一个源滤波器处理不同的数据来源。
2 变换滤波器(Transform Filter)获取输入流,处理数据,并且创建输出流。编码器和解码器都是变换滤波器的例子。
3 渲染滤波器(Render Filter)在整个链条的最后部分。它们接受数据并将它呈现给用户。比如,视频实现在显示器上绘制视频桢;音频实现将数据送往声卡;写文件滤波器将数据写入文件。
4 劈分滤波器(Splitter Filter)将一个输入劈分成两个或多个输出,通常是沿着路线解析输入流。例如,AVI劈分滤波器将字节流解析成独立的视频和音频流。
5 混合滤波器(MUX Filter)获取多个输入并将它们结合成单个的流。比如,AVI Mux执行AVI Splitter的反向操作。它获取音频和视频流并且产生一个AVI格式的字节流。

    这几个类别间的区别不是绝对的。比如,ASF读滤波器就可以同时扮演Source Filter和Splitter Filter的角色。
    所有的DirectShow滤波器都提供IBaseFilter接口, 所有的针(pin)都提供IPin接口。DirectShow也定义了一些其他接口来支持特殊的功能。

关于滤波器图形管理器

    滤波器图形管理器(Filter Graph Manager)是一个在滤波器图形中控制滤波器的组件,它执行了许多包括下面的函数:
1 协调滤波器间的状态变化。
2 设置参考时钟(Reference Clock)。
3 向应用程序传递事件
4 向应用程序提供构建滤波器图形的方法。

    这里简要地介绍一下这些函数。详细内容可以在本文档的其他部分获得。

    状态变化(State Changes)滤波器间状态变化必须以一定的次序发生。因此,应用程序并不直接发送状态变化命令给滤波器。而是给滤波器图形管理器发送一个命令,管理器在将这个命令发送个滤波器。查询工作也有类似的方式:应用程序发送查询命令给管理器,管理器再把它发送给滤波器。
    参考时钟(Reference Clock).在图形中的所有滤波器都是用同一个时钟, 叫做参考时钟。这个时钟假设所有的流都是同步工作的。视频和音频流实现的时间被称作表达(presentation)时间.表达时间可以相对于参考时钟被测量出来。滤波器图形管理器选择的参考时钟或者在声卡上,或者是系统时钟。
    图形事件(Graph Events).滤波器图形管理器使用事件队列来通知应用程序滤波器中发生的事件。这个机制和Windows信息循环(Windows Message Loop)类似。
    图形构建方法(Graph-building Methods).管理器为应用程序提供为向图形增加滤波器,滤波器间的连接与断开的方法。
滤波器图像管理器没有处理的一个函数是从一个滤波器向另外一个移送数据。这个工作用滤波器通过他们的针连接自己完成。这个过程通常发生在独立的线程中。

注意
    滤波器经常与管理器一起驻留在同一个进程中,并且通过进程内服务器被调用,因此在滤波器间,或者滤波器和管理器间,方法的调用并不是排列的。

关于媒体类型

    由于DirectShow是一个模组,因此它需要在滤波器图形的每一点通过一种方式来描述数据的格式。比如,考虑AVI的播放。数据以大块重复流(Stream of RIFF Chunks)的形式进入图形。它们被解析成视频和音频。视频流由可能被压缩的视频桢组成,解码后,视频流成为一系列解压得位图(Bitmaps)。音频也进行了同样过程的处理。

媒体类型:DirectShow如何表现格式

    媒体类型是用来描述数字媒体格式的一个普遍的可扩展的方法。当两个滤波器连起来的时候他们对于一种媒体类型达成一致。媒体类型用来识别上流(Upstream)滤波器将什么样的数据类型传递给了下流(Downstream)滤波器,以及数据的物理布局(layout)。如果两个滤波器不能在媒体类型上取得一致,他们就不能被连接。
    对于一些应用, 你不必关系媒体类型。比如,在文件的播放过程中,DirectShow将会处理所有的细节。其他的应用也许直接需要媒体类型进行工作。
媒体类型使用AM_MEDIA_TYPE结构定义。这个结构包含了一下的信息。

typedef struct _MediaType {
        GUID majortype; //全球唯一标识符指定媒体样本的主类。
        GUID subtype;     //全球唯一标识符指定媒体样本的子类。MEDIASUBSTYP_NONE表示格式不需要子类。
        BOOL bFixedSizeSamples; //如果是TRUE,样本尺寸固定。这项仅仅是信息性的, 对于音频,通常是TRUE;
                                // 对于视频,通常是未压缩的
为TRUE,压缩的为FALSE
        BOOL bTemporalCompression;//如果是TRUE,样本使用时序(桢间)压缩TRUE表明不是所有的桢都是键架(Key Frame)这项仅仅是信息性的。
        ULONG lSampleSize; //样本的字节数,对于压缩数据,这项为0
        GUID formattype;//GUID用来指向结构块。pbFormat成员指向对应模块结构
        IUnknown *pUnk; //不使用
        ULONG cbFormat; //格式模块尺寸,字节
        [size_is(cbFormat)] BYTE *pbFormat; // 指向格式模块指针。结构类型由formattype指定, 格式结构必须出现
                                            //除非formattype是 GUID_NILL或者 FORMAT_NONE
} AM_MEDIA_TYPE;

1 主类(Major type)主类是一个GUID(Global unique identifier),它定义了全部的数据类别。主要类型包括视频,音频, 未解析字节流, MIDI数据等等。

2 子类(Subtype)子类是另外一个GUID,用来进一步定义格式。比如,在视频主类中,存在子类, RGB-24, RGB-32, UYVY等等。在音频中,有PCM音频,MPEG-1有效载荷(Playload)等等子类。子类比主类提供更多的信息,但是它没有对于格式的每个方面都进行了定义。比如,视频子类没有定义图像的尺寸或者桢频率(Frame Rate)。它们由下面描述的格式模块来定义。

3 格式模块(Format Block)格式模块是一个数据模块,用它来详细地描述格式。格式模块与AM_MEDIA_TYPE结构是分开的,结构中的pbFormat成员指向格式模块。

    pbFormat被声明为void*,这是因为格式模块布局的改变依赖媒体类型。比如,PCM音频使用WAVEFORMATEX结构。视频使用各种结构,包括VIDEOINFOHEADER和VIDEOINFOHEADER2。AM_MEDIA_TYPE中的formattype成员是一个指定在格式模块中上述哪一个结构被包含的GUID。每一个格式模块指定一个GUID。cbFormat成员指定了块的尺寸。通常在废弃pbFormat指针之前要检查这些值。

    如果这个块被填充,那么主类和子类就包含了多余的信息。然而,主类和子类可以在格式模块不完整的情况下提供一个便利的方法识别格式。比如,你可以指定一个普通的24位RGB格式(MEDIASUBTYPE_RGB24),而不需要知道通过VIDEOINFOHEADER所得到的全部信息,比如尺寸和桢频率。
比如,一个滤波器可以使用以下的代码来检查一个媒体类型:

HRESULT CheckMediaType(AM_MEDIA_TYPE *pmt)
{
        if (pmt == NULL) return E_POINTER;

        // 检查主类,我们需要视频
        if (pmt->majortype != MEDIATYPE_Video)
        {
                return VFW_E_INVALIDMEDIATYPE;
        }
        // 检查子类,我们需要 24-bit RGB.
        if (pmt->subtype != MEDIASUBTYPE_RGB24)
        {
                return VFW_E_INVALIDMEDIATYPE;
        }

        // 检查格式类型及格式块尺寸
        if ((pmt->formattype == FORMAT_VideoInfo) &&  (pmt->cbFormat >= sizeof(VIDEOINFOHEADER) && (pmt->pbFormat != NULL))
        {
        // 现在,可以安全地将指向格式块的指针强制转换成formattype GUID所定义的类型。
        VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)pmt->pbFormat;

        // 检查 pVIH (未显示).如果看起来没有错误,返回 S_OK.
        return S_OK;
        }
        return VFW_E_INVALIDMEDIATYPE;
}

    AM_MEDIA_TYPE结构也包含一些可选的领域。它们用来提供附加信息,但是滤波器并不需要使用它们。

1 ISampleSize 如果这项非零,它定义了每一个样本的尺寸。如果等于零,表示每个样本的尺寸可以改变。
2 bFixedSizeSamples 如果布尔标志为TRUE, 意味着在IsampleSize的值是有效的,否则,你应该忽略ISampleSize。
3 bTemporalCompression 如果布尔标志为FALSE,意味着所有的桢都是键架。

关于媒体样本和分配符

    滤波器通过针(pin)连接传送数据。数据从输出针流往另一个滤波器的输入针。对于输出针传送数据最一般的方法是在输入针上调用IMemInputPin::Receive方法。尽管也存在其它的机制。
    根据滤波器的不同,媒体数据内存的分配也有几种不同的方式:在堆上,在DirectDraw表面,共享GDI内存,或者使用一些其他的分配机制。负责分配内存的是一个叫做Allocator 的COM组件,提供IMemAllocator接口。
    当两个针连接的时候,一个针必须提供分配器。DirectShow定义了一系列方法用来确定哪一个针提供分配器。两个针对于分配器创建的缓冲数目及缓冲大小也取得一致。
    在流开始前,分配器创建缓冲池。在流传送过程中,上流(Upstream)滤波器将数据填入缓冲并且传递给下流(Downstream)滤波器。但是,上流滤波器并不是将缓冲原始指针传给下流滤波器,而是使用分配器产生的称为media samples的COM组件来管理缓冲。Media samples提供IMediaSample接口。

    Media Sample包括

1 一个指向正在使用的缓冲的指针
2 一个时间戳
3 各种标记
4 可选的,一种媒体类型

    时间戳定义了表示时间,描画滤波器用它来规划描画。标记用来表示一些情况,比如从前一个样本开始,数据中是否有中断。媒体类型为滤波器提供了一种方法去改变中流格式。通常样本没有媒体类型,它意味着从上一个样本开始,格式没有改变。
    当一个滤波器使用一个缓冲的时候,它在样本上保持一个参考计数。分配器使用参考计数决定何时可以复用缓冲。这就可以防止当另外一个滤波器正在使用缓冲时滤波器复写(overwritting)。直到被每一个滤波器都释放,一个样本才会返回可用样本的分配器池。

硬件设备如何参与滤波器图形

    这篇文档描述了微软的DirectShow如何与音频和视频硬件交互作用。

包装滤波器(Wrapper Filter)
    所有的DirectShow滤波器都是用户模式(User mode)软件组件。为了使内核模式(Kernel mode)的硬件设备,如视频捕获卡,结合到DirectShow滤波器图形中,设备必须被描述成用户模式滤波器。这个功能通过DirectShow提供的专门包装滤波器完成。这些滤波器包括音频捕获滤波器,VFW捕获滤波器,TV调谐滤波器,TV音频滤波器以及模拟视频水平线滤波器(Analog Video Crossbar)。DirectShow也提供一个叫KsProxy的滤波器,它可以描述任何类型的基于WDM的流设备。硬件供应商可以通过KsProxy集合的组件KsProxy plug-in来扩展KsProxy以支持定制的功能。
包装滤波器提供COM接口来描述设备的性能。应用程序使用这些接口对滤波器传递和接受信息。滤波器将COM的方法调用解释成对设备驱动程序的调用,以核心模式向驱动程序传递信息,并且将解释的结果返回给应用程序。TV调谐器,TV音频, 模拟视频水平条以及KsProxy滤波器支持定制驱动特性通过IKsPropertySet接口。VFW视频捕获及音频捕获滤波器不能用这种方法扩充性能。

    对于应用程序开发人员,包装滤波器使得应用程序像对DirectShow滤波器那样控制设备。不需要特别的编写程序;与核心模式设备通信的细节被这个滤波器封装了起来。

Video For Windows设备
    VFW捕获滤波器支持早期的Video For Windows视频捕获卡。当一个VFW卡出现在目标系统中,使用DirectShow的System Device Enumerator发现并将它加入滤波器图形。

音频捕获和混频设备(声卡)
    较新的声卡有为麦克风和其他设备准备的插孔。通常来说,这些卡也有板载混频性能,可以对每个输入调节音量,三重音和低音。在DirectShow中,声卡的输入和混频被音捕获频滤波器包装。每一个声卡都可以使用System Device Enumerator被发现。为了在你的系统中浏览声卡,运行GraphEdit并且从音频捕获资源类种选择。在这个类中的每一个滤波器都是音频捕获滤波器的一个实例。

WDM流设备
    较新的硬件解码器和捕获卡符合微软驱动模型(WDM)标准。这些设备比VFW设备有更强的性能,并且在NT/2000和98/SE间移植性更强。WDM视频捕获卡可以支持VFW下无法实现的特性,包括捕获格式的列举,视频参数包括色调,亮度的可编程控制。可编程输入部分,以及TV调谐支持。

    为了支持WDM流设备,DirectShow提供Ksproxy滤波器(ksproxy.ax)。KsProxy一直被叫做瑞士军刀(Swiss Army Knife)滤波器,因为它可以有很多不同的用途。滤波器上针的数目,以及滤波器提供的COM接口数目完全依靠运行的驱动的性能。KsProxy并不以KsProxy在滤波器图形中出现,而是采用一个更友好的设备名,被写在注册表中。浏览你系统中的WDM设备,运行GraphEdit并且从WDM流设备中选择。尽管在你的系统中仅仅有一个WDM卡,这个卡可能包含多个设备。每一个设备被描述成一个单独的滤波器,并且每一个滤波器实际上是一个KsProxy。

    应用程序使用系统设备计数器(System Device Enumerator)在系统中查找WDM设备名称。KsProxy通过调用名称上的BindToObject初始化。由于KsProxy能够描述所有WDM设备,它必须查询驱动程序以决定驱动支持哪些特性集合。特性集合是WDM驱动和一些用户模式滤波器比如MPEG-2软件解码器,所使用的数据结构集。KsProxy配置自己并提供COM接口来对应那些特性集合。KsProxy将COM方法调用解释成特性集合并且将它们送给驱动程序。硬件供应商可以通过提供plug-ins 扩展KsProxy,通过提供应商指定界面提供特殊的设备性能。所有这些细节对于应用程序不可见。应用程序像对其他DirectShow一样通过KsProxy控制设备。

核心流
    WDM设备支持核心流,数据完全流向核心模式而没有转换到用户模式。核心模式与用户模式之间的转换需要高昂的计算代价。核心流允许高比特率而不增加CUP的负担。基于WDM的滤波器可以使用核心流将多媒体数据直接从一个硬件设备传递到另外一个。在同一个卡,或在不同的卡。而不需要将数据拷贝到系统主内存中。
    从应用的角度看,数据好像从一个用户模式滤波器传送到另一个。实际上,数据可能根本没有进入用户模式,而是从一个核心模式设备到了另外一个知道描绘到显卡上。一些情况下,比如捕获到文件,需要数据在一些点上从核心模式到用户模式,然而,这个转换并不需要将数据拷贝到新的内存地址。
    应用程序开发者通常不需要关心核心流的细节,除非作为背景信息。

构建滤波器图形

The Filter Graph and Its Components文档描述了DirectShow滤波器图形各个基本的模块。这部分介绍了各个模块是如何被创建和被连接从而完成处理数据功能的。

图形构建组件

     DirectShow提供了几个可以构建滤波器图形的组件。包括:
1 Filter Graph Manager.这个对象控制滤波器图形。它提供IGraphBuilder, IMediaControl,以及IMediaEventEx接口。所有DirectShow应用程序都或多或少用到这个对象,尽管在一些情况下,滤波器图形是由其他对象创建的。
2 Capture Graph Builder. 这个对象为建构滤波器图形提供了附加的方法。最初,它是为构建视频捕获图形被设计出来,但是对于其他类型的定制滤波器图形也很有用。它提ICaptureGraphBuilder2接口。
3 Filter Mapper和System Device Enumerator. 这些对象用来定位在用户系统中已注册的滤波器,或者描述硬件设备。
4 DVD Graph Builder. 这个对象用来构建DVD播放和导航滤波器图形。它提供IDvdGraphBuilder接口。基于Script的应用程序可以使用MSWebDVD的ActiveX控件来支持DVD播放。
5 视频控制。微软的WindowsXP有支持视频的ActiveX控件。它可以处理在DirectShow中的数字和模拟电视。

智能连接

    智能连接这个概念包含了滤波器图形管理器用来构建全部或部分滤波器图形所采用的一套算法。当滤波器图形管理器需要附加的滤波器来完成图形的时候,它大体上需要做以下一些工作:
1. 如果一个正在图形中的滤波器,至少有一个未连接的输入针时, 滤波器图形管理器试图使用这个滤波器。
2. 否则,滤波器图形管理器在注册表中搜索可以接受正确媒体类型的滤波器进行连接。每一个滤波器拥有一个注册表值叫做Merit,这个值粗略标示这个滤波器完成图形的可用程度。滤波器图形管理器根据Merit值尝试滤波器。对于每一个流类型(如音频,视频,MIDI),缺省的表示滤波器(Render)有一个高的Merit值。解码器也具有一个高的Merit值。专用滤波器具有低的值。
如果滤波器图形管理器阻塞,它会返回并且尝试不同的滤波器组合。

图形构建概述

    为了创建一个滤波器图形,首先要创建一个滤波器图形管理器的实例。

IGraphBuilder* pIGB;
HRESULT hr = CoCreateInstance(CLSID_FilterGraph,
NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder,
(void **)&pIGB);

   滤波器管理器提供了一下的图形构建方法:

1 IFilterGraph::ConnectDirect试图在两个针之间进行直接连接。如果不能连接,方法实效。
2 IGraphBuilder::Connect连接两个针。如果可能,进行直接连接。否则,使用中间滤波器完成连接。
3 IGraphBuilder::Render开始于输出针并构建图形的其他部分。如果需要,这个方法增加滤波器,在下流工作,直到达到表现滤波器。
4 IGraphBuilder::RenderFile构建一个完整的文件播放图形。
5 IFilterGraph::AddFilter向图形增加一个滤波器。它并不连接滤波器。调用这个方法之前,你必须创建滤波器,或者调用CoCreateInstance,或者使用Filter Mapper或者System Device Enumerator。

    这些方法提供了构建图形的3个基本方法

1 滤波器图形管理器构建整个图形。
2 滤波器图形管理器构建部分图形。
3 应用程序构建这个图形。

    滤波器图形管理器构建整个图形
    如果你只是播放一个识别格式的文件,比如AVI, MPEG, WAV,或者MP3,使用RenderFile方法。
    RenderFile方法首先在注册表中查询可以解析此文件的源滤波器。它通常使用协议(比如在文件名中的http://),文件扩展名,或者在文件中预先定义字节模式来决定源滤波器。

    为了构建图形的其他部分,滤波器图形管理器使用迭代模式—在输出针上取得滤波器支持的媒体类型,然后在注册表中搜索可将此媒体类型作为输入的滤波器,它采用以下几个标准来缩小搜索范围和为滤波器排优先级。
1 滤波器种类标示了一个滤波器的一般功能。
2 媒体类型描述了滤波器可以接受那种类型的数据作为输入,并将数据传递到输出。
3 merit值确定了尝试的滤波器的次序。如果两个滤波器属于同一个类型,并且支持同一个输入类型,滤波器图形管理器选择具有最高merit值的一个。一些滤波器故意被附一个比较低的merit值,这是应为它们为特殊目的而设计的,而且只能由应用程序添加到图形中。

    滤波器图形管理器使用Filter Mapper对象来搜索注册表当每个滤波器被添加的时候,滤波器图形管理器试图将它连接到前一个滤波器的输出针上。滤波器间协调决定可否连接,如果可以,可以使用哪一种媒体类型,如果新的滤波器不能连接。滤波器图形管理器将抛弃它,并且尝试另外的滤波器。继续这个过程指导每一个流被表现出来。

    滤波器图形管理器构建部分图形

    除了简单的播放一个文件,你的应用程序必须至少做一些图形构建的工作。比如一个视频捕获程序必须选择一个捕获源滤波器并且将它添加到图形中。如果你要将数据写入AVI文件,你必须将AVI Mux和File Writter滤波器添加到图形中。然而,也可能使用滤波器图形管理器完成图形。比如,你可以通过调用Render方法来表现一个preview针。

    应用程序构建整个图形

    在一些情况下,你的应用程序也许需要通过添加连接每一个滤波器来完成图形构建。在这种情况下。你也许应该明确的指导哪一个滤波器应该被添加到图形中。用这种方法,应用程序通过调用AddFilter,添加每一个滤波器,列举滤波器上的每一个针,并且通过调用Connect或者ConnectDirect来连接它们。

智能连接

    智能连接是滤波器图形管理器用来构建图形的一种机制。它由几种用来选择滤波器并且将他们添加到滤波器图形中的相关算法组成。对于应用程序编程,你几乎不需了解智能连接的细节。但是如果你在构建一个滤波器图形时遇到困难并想解决问题,或者你正在自己写滤波器并想使它用于自动图形构建,你需要阅读这一节。

    智能连接包括以下IGraphBuilder方法

IGraphBuilder::Render
IGraphBuilder::AddSourceFilter
IGraphBuilder::RenderFile
IGraphBuilder::Connect

    Render方法构建了图形的子部分。它开始于未连接的输出针并且向下工作,在必要的时候,增加滤波器。开始滤波器必须已经存在于图形中。在每一步,Render方法搜索一个可以与前一个滤波器相连接的滤波器。如果连接的滤波器有多个输出针,那么流可以分叉。如果每一个流都拥有了Renderer那么搜索停止。如果Render方法失效,它会返回并使用另外一套滤波器重试。
为了连接每一个输出针,Render方法需要如下步骤:

1. 如果针支持IStreamBuilder接口,滤波器图形管理器将整个过程交给IStreamBuilder::Render方法处理。通过提供这个接口,针将承担起构建图形剩余部分直到Renderer的责任。然而,很少有针支持这个接口。
2. 如果有驻留在内存中的滤波器,滤波器管理器试图使用它。在整个智能连接过程中,滤波器图形管理器会在早期的步骤中驻藏滤波器。
3. 如果滤波器图形包含任何未连接输入针的滤波器,管理器其次会尝试它。你可以在调用Render方法前将滤波器加入图形来强制使用Render方法测试一个滤波器。
4. 最后管理器使用IFilterMapper2::EnumMatchingFilters方法来搜索注册表。它尝试用注册表列出的媒体类型来匹配输出针首选的媒体类型。

    每一个滤波器都用一个merit值注册,一个数值表明了一个滤波器对于其他滤波器的优先级别。EnumMatchingFilters方法按merit顺序返回滤波器,最小的值是MERIT_DO_NOT_USE+1。它忽略merit值小于等于MERIT_DO_NOT_USE的滤波器。滤波器也被分为几个类别,由GUID定义。类别本身也有merit,EnumMatchingFilters方法忽略merit值小于等于MERIT_DO_NOT_USE的类别,尽管其中的滤波器可能有比较高的merit值。

    总而言之,Render方法按一下次序尝试滤波器。
1. 使用IStreamBuider。
2. 尝试驻留滤波器。
3. 尝试图形中的滤波器。
4. 查询注册表中的滤波器。
    AddSourceFilter方法增加一个可以提供特定文件的源滤波器。首先,它在注册表中搜索,并且匹配协议(比如http://),文件扩展名,预定义检验字节集(在文件特殊位置符合一定模式的字节)。如果方法能够定位一个源滤波器,它便会创建这个滤波器的一个实例,并将它加入图形,并用文件名调用滤波器的IFileSourceFilter::Load方法。
    RenderFile方法构建了一个缺省的通过文件名的播放图形。本质上,这个方法使用AddSourceFilter来查找正确的源滤波器,并使用Render来构建图形的其他部分。
    Connect方法将输出针与一个输入针连接。这方法在必要时可以使用上面提到的Render方法中的各种算来添加滤波器。
1. 在没有中间滤波器的情况下,尝试直接连接两个滤波器。
2. 尝试驻留滤波器。
3. 尝试图形中的滤波器。
4. 查询注册表中的滤波器。

滤波器图形中的数据流

    这一部分描述了媒体数据在滤波器图形中如何移动。通常,写DirectShow应用程序时,你不需要了解这些细节。尽管在一些情况下,你会发现这对你很有用。如果你正在写DirectShow滤波器,你需要了解这部分的材料。

传输(Transports)

    为了移动媒体数据通过图形,DirectShow滤波器必须支持一些可能的协议。这些协议叫做传输。当两个滤波器相连时,他们必须支持同一个传输。否则就不能交换媒体数据。通常,一个传输需要其中的一个针支持一个特殊的接口。当滤波器连接的时候,一个针向另外一个查询接口。

    大多数DirectShow滤波器在主内存中装纳媒体数据,并且通过针将它们传送到另外一个滤波器。这种传输叫做局部存储传输。尽管局部存储传输是DirectShow中最常用的一种传输方式,并不是所有的滤波器都使用它。比如,一些滤波器沿着硬件途径传送数据,仅仅使用针来传输控制信息。比如IOverlay接口。

    DirectShow定义了局部存储传输的两种机制,推模式和拉模式。推模式中源滤波器产生数据并将它传给下流的滤波器。那个滤波器被动的接受数据,处理它,并进一步将它传给下流。在拉模式中,源滤波器连接一个Parser滤波器。Parser滤波器向源滤波器请求数据。源滤波器通过传送数据来相应请求。推模式IMemInputPin接口。拉模式使用IAsyncReader接口。
    推模式比拉模式更普遍。因此,本文以下部分均假设使用推模式。

样本和分配器

    当一个针向另外一个针传递媒体数据时,它并不是将指向存储缓冲器的指针传递给另一个针。而是传递给了管理内存的一个COM对象。这个对象叫做media sample,提供IMeidaSample接口。接收针通过调用IMediaSample方法,比如IMediaSample::GetPointer,IMediaSample::GetSize和IMediaSample::GetActualDataLength等来访问存储缓冲器。

    样本通常从输出针到输出针方向向下流动。在推模式下,输出针通过调用输入针上的IMemInputPin::Receive方法来传递样本。输入针或者同步处理数据(完全在Receive方法内部),或者在Worker Thread上异步处理。输入针容许在Receive方法中阻塞,如果它需要等待资源。

    另外有一个COM对象,叫做Allocator,用来创建和管理媒体样本。Allocator提供IMemAllocator接口,当一个滤波器需要一个空缓冲器的media sample时,它会调用IMemAllocator::GetBuffer方法,这个方法将返回指向样本的指针。每一对连接针共享一个Allocator。当两个针连接时,它们决定哪个针提供Allocator。针也可以设定Allocator的属性,比如缓冲器的数量,每一个缓冲器的大小。

媒体样本参考计数

    Allocator产生一个有限样本池。在任意时刻,一些样本可能在使用,而其他可以被GetBuffer调用。Allocator使用参考计数来追踪样本。GetBuffer方法返回一个样本和参考计数1。如果参考计数归零,样本返回Allocator池,这样它又可以被GetBuffer重新调用。只要参考计数大于零,这个样本就不能被GetBuffer调用。如果属于Allocator的每一个样本都被使用,那么GetBuffer方法会阻塞知道有了可用的样本。

    比如,假设一个输入针接收到了一个样本。如果它在Receive方法中同步处理样本,它并不增加计数值,在Receive返回式,输出针释放样本,参考计数归零,样本返回Allocator池。另一方面,如果输入针在Worker Thread上处理样本,它会在离开Receive方法前,增加计数值。参考计数为2。当输出针释放样本,计数值变为1,样本仍然没有返回池中。在Worker Thread处理完样本后,它调用Release来释放样本。这时样本才返回池中。

    当一个针接收到一个样本,它会将它拷贝到另外一个样本,或者它会修改原始样本并将它传给下一个滤波器。潜在地,一个样本可以环游整个图形,每一个滤波器依次调用AddRef和Release。因此,输出针决不能在调用Receive方法后再重用一个样本,因为下流滤波器也许正在使用这个样本。输出针通常调用GetBuffer获取一个新的样本。

    这种机制就减少了内存分配的数量,因为滤波器可以重用同一个缓冲器。它也防止了滤波器偶然地在未处理的数据上写的操作,因为allocator有一系列可用的样本。

    滤波器可以对输入输出使用独立的Allocator。如果它扩展了输入数据(比如,解压缩输入),那么它可能会这么做。如果输出没有输入长,滤波器可能占位(In Place)处理数据,而不必将它拷贝到新的样本中。在那种情况下,两个或者更多的针连接可以共享一个allocator。

Committing和Decommitting Allocators

    当滤波器第一次创建Allocator时,Allocator并没有存储缓冲器。这时,任何调用GetBuffer的方法都将失败。当流开始时,输出针调用IMemAllocator::Commit,来分配一块内存。这时针便可以调用GetBuffer了。
    当流停止时,针调用IMemAllocator::Decommit。所有的子过程调用GetBuffer都将实效直到再次commit。而且,如果等待样本而是任何调用GetBuffer阻塞,它们会立即返回一个失效代码。Decommit方法也许或者不会释放内存,这还要依靠应用。比如CMemAllocator类会等待知道Desturctor方法释放内存。

滤波器状态

     滤波器有三个可能状态,停止,暂停和运行。停止状态的目的是在图形中提示数据,而使运行命令立即响应。滤波器图形管理器控制所有的状态转变。当一个应用程序调用IMediaControl::Run,IMeidaControl::Pause或者IMediaControlStop时,管理器会在滤波器上调用相应的IMediaFilter方法。停止和运行间的状态转换通常都要经过暂停状态,所以如果应用程序在一个停止的图形上调用Run,管理器在运行前暂停图形。
对于大多数滤波器,运行和暂停状态是一样的。考虑以下的滤波器图形

Source > Transform > Renderer

    假设源滤波器不是一个实况捕获源。当源滤波器暂停,它产生一个线程用来产生新数据并将它尽快写入media sample。这个线程通过IMemInputPin::Receive方法将样本推往下流传递给Transform滤波器的输入针。Transform滤波器在源滤波器的线程上接收到样本。它可能通过Worker Thread将样本传给Renderer,不过通常它用同一个线程来传递数据。当Renderer暂停时,它等待接受样本。在接收一个样本后,它会阻塞并且无限期地保持样本。如果它是个视频Renderer,它会将样本显示为张贴图像,只要需要就可以重画。

    这时,流完全被提示并且准备来表现。如果图形仍然暂停,样本将会在图形中在第一个样本后堆积起来。直到每一个滤波器的Receive和GetBuffer都阻塞,然而,没有数据丢失。一旦源线程通畅,它会在阻塞点恢复。
源滤波器和Transform滤波器。

【上篇】
【下篇】

抱歉!评论已关闭.