南山狒狒

专注WinCE和GIS开发,提升ArcGIS和DirectShow,涉及过BizTalk,有Delphi经历
09年后的路,应该好好走了。

  博客园  :: 首页  ::  :: 联系 :: 订阅 订阅  :: 管理
About DirectShow
 
    本节描述DirectShow的整体结构。本节包含的内容比较丰富,我们可能不需要知道所有的这些知识。因此,我们首先应该选择浏览全部的内容,然后根据实际应用程序的需要查看Using DirectShow的内容。如果有关于DirectShow结构的特殊问题,可以再回过来参考本节的内容。
 
1. DirectShow System Overview
 
    1.1 The Challenge of Multimedia
    进行多媒体编程主要存在如下几个挑战:
    ·多媒体流包含大量的数据,这些数据又要求进行快速的处理
    ·音频、视频必须同步,以使它们在相同时间开始、停止,以相同的频率播放
    ·数据来源多,包括本地文件,计算机网络,电视广播和视频摄像机
    ·数据源的格式多,比如音频、视频交叉存取的(AVI),高级流格式(ASF),运动图像专家组(MPEG)和数字视频(DV)。
    ·程序员不能预先知道在用户终端系统存在的硬件设备
 
    1.2 The DirectShow Solution
    DirectShow就是被设计来解决这些挑战的。DirectShow通过把应用程序从复杂的数据传输、硬件区别和同步隔离起来。它的主要目标是简化在Windows平台创建数字媒体程序的任务。
    为了达到流递音频、视频所需的流量,DirectShow在可能的任何时候使用DirectDrawDirectSound。这些提交数据的数据有效的利用了用户的声卡和图形卡。DirectShow通过把媒体数据包装为带时间戳的Samples实现回放的同步。为了处理可能不同的数据源、格式和硬件设备,DirectShow使用模块化结构,通过这种结构应用程序可以混合、匹配不同的软件组件,这种组件称为Filters.
    DirectShow提供Filters支持捕捉和基于WDM的调频设备,Filters还支持旧的VFW捕捉卡,为音频压缩管理(ACM)编写的编解码器以及视频压缩管理器(VCM)接口。
    应用程序、DirectShow组件、以及DirectShow所支持的部分硬件、软件组件之间的关系如图。

    如同所示,DirectShow与各种不同的设备进行通信,并控制它们。包括本地文件系统,TV Tuner,视频捕捉卡,VFW编解码器,视频显示(通过DirectDraw或者GDI),以及声卡(通过DirectSound)。因此,DirectShow把应用程序与这些设备的复杂性隔离开来。DirectShow还为某些文件格式提供了本地的压缩、解压Filters.
 
2. The Filter Graph and Its Components
    本条款描述DirectShow的主要组件。主要目的是为应用程序开发人员和编写自定义DirectShow Filter的开发人员做些介绍。应用程序开发人员通常可忽略DirectShow的一些低级细节。但是,阅读此节仍是好主义,可以对DirectShow的结构有一个大概理解。
 
    2.1 About DirectShow Filters
    DirectShow基于模块化结构,每个处理阶段都由称为FilterCOM对象完成。DirectShow提供了一系列标准Filter用于应用程序开发,开发者也可以开发自己的Filter来扩展DirectShow的功能。为了举例说明,这里是一个播放AVI视频文件所需要的步骤,连同完成每步的Filters:
    ·从原始文件读取数据为字节流(File Source Filter
    ·检测AVI头,把字节流分析为单独的视频帧和音频SamplesAVI Splitter Filter
    ·解码视频帧(各种解码Filters, 取决于压缩格式)
    ·画视频帧(Video Renderer Filter
    ·把音频Samples发送到声卡(默认的DirectSound设备Filter
    这些Filters及结构如图所示:
    如图所示,每个Filter都与一个或多个Filter相连接。连接点也是一个COM对象,称为PinsFilters使用PINS把数据从一个Filter移动到下一个。图中的箭头表示数据的流向。在DirectShow,一个Filters的集合称为Filter Graph
    Filter有三种可能的状态,运行,停止,暂停。当一个Filter运行时,它就处理媒体数据流,当停止时,Filter就不处理数据,暂停状态用来在运行前Cue Data, Data Flow in the Filter Graph一节对这些概念有更详细的描述。除非特别的例外,状态改变都是协调贯穿在整个Filter GraphGraph的所有Filter的状态改变都是统一的。因此,Filter Graph也可说是运行,停止,暂停。
    Filter 一般分为下面几种类型。
    (1)、源FilterSource Filter):源Filter引入数据到Filter Graph,数据来源可以是文件、网络、照相机或者是任何地方。每个源Filter处理不同类型的数据源。
    (2)、变换FilterTransform Filter):变换Filter的工作是获取输入流,处理数据,并生成输出流。编码、解码读书变换Filter的例子。
    (3)、提交FilterRenderer Filter):提交FilterFilter图表里处于最后一级,它们接收数据并把数据提交给用户。比如,视频Renderer把视频帧画在显示器,音频Renderer把音频数据发送到声卡,File-Writer Filter把数据写入文件。
    (4)、分割FilterSplitter Filter):分割Filter把输入流分割成多个输出。例如,AVI分割Filter把一个AVI格式的字节流分割成视频流和音频流。
    (5)、混合FilterMux Filter):混合Filter把多个输入组合成一个单独的数据流。例如,AVI混合Filter把视频流和音频流合成一个AVI格式的字节流。它进行AVI分割Filter相反的操作。
    这些Filters种类的区别并非绝对。比如ASF Reader Filter既是Source Filter又是Splitter Filter.
    所有的Filters都暴露了IBaseFilter接口,所有的Pin都暴露了IPin接口。DirectShow也定义了一些其他的接口实现更特殊的功能。
 
    2.2 About the Filter Graph Manager
    Filter Graph Manager也是一个COM对象,用来控制Filter Graph中的所有的Filter,主要有以下的功能:
    ·协调Filters之间的状态改变
    ·建立参考时钟
    ·传递事件到应用程序
    ·提供应用程序建立Filter Graph的方法
    下面就这些功能做一个简单的说明。可以本文档的其他地方找到详细说明。
    状态改变:Filters的状态改变必须以一种特殊顺序发生。因此,应用程序并不将状态改变的命令直接发给Filter,而是发送给Filter Graph Manager一个简单命令,由Manager将命令分发给Graph中每一个FiltersSeeking也是按同样的方式工作,先由应用程序将Seek命令发送到Filter Graph Manager,然后由其分发给每个Filters
    参考时钟:Graph中的Filter都采用的同一个时钟,称为参考时钟(Reference Clock),参考时钟可以确保所有的数据流同步,视频桢或者音频Sample应该被提交的时间称为Presentation Time。它是相对于参考时钟来确定的。Filter Graph Manager应该选择一个参考时钟,可以选择声卡上的时钟,也可以选择系统时钟。
    Graph事件:Filter Graph Manager采用事件队列机制将Graph中发生的事件通知给应用程序,这个机制类似于Windows的消息循环。
    Graph构建的方法:Filter Graph Manager给应用程序提供了将Filter添加进Graph的方法,连接Filter的方法,断开Filter连接的方法。
    Filter Graph Manager没有的一个功能就是把数据从一个Filter移动到另一个Filter。这是由Filters自己通过它们的PIN连接完成的。处理过程总是在不同的线程进行。
    注意:Filters总是与Filter Graph Manager在同一进程,被进程类服务器加载。在Filters之间,FiltersFilter Graph Manager之间的函数调用都不会存在列举(Marshall)。
 
    2.3 About the Media Type
    因为DirectShow是基于模块化的,就需要有一种方式来描述Filter Graph每一个点的数据格式,例如,我们还以播放AVI文件为例,数据以RIFF块的形式进入Graph中,然后被分割成视频和音频流,视频流由一系列视频桢组成,而且还可能是压缩的。解码后,视频流由一系列的非压缩的位图组成,音频流也是同样的处理过程。
 
    2.3.1   Media Types: How DirectShow Represents Format
    媒体类型是一种很普遍的,可以扩展的用来描述数字媒体格式的方法,当两个Filter连接的时候,他们会在某种媒体类型达成一致。媒体类型决定了上一级Filter将要给下游的Filter发送什么类型的数据,以及数据的物理布局。如果两个Filters在媒体类型上没有达成一致,那么他们就没法连接起来。
    对于某些应用程序,我们不需要担心媒体类型。比如在文件回放中,DirectShow处理了所有的细节。其他类型的应用程序可能需要直接在媒体类型上操作。
    媒体类型是通过AM_MEDIA_TYPE结构定义的,此结构包含如下信息:
    ·主类型:它是一个GUID值,定义的全部的数据种类。主类型包括视频、音频、未解析的字节流、MIDI数据等等。
    ·子类型:也是GUID值,进一步定义媒体类型。比如,主类型是视频时,子类型可能是RGB-24RGB-32UYVY等等。对于音频,子类型可能是PCM音频、MPEG-1 Payload等等。主类型提供了比子类型更多的信息,但是它还没有定义格式的所有信息。比如,视频子类型没有定义图像大小和帧率。这些通过下面的格式子块说明。
    ·格式子块:它是描述格式细节的数据块。它是从AM_MEDIA_TYPE结构单独分配。AM_MEDIA_TYPEpbFormat指针指向格式子块。
    pbFormat是一个void*的指针,因为格式块会因为媒体类型的不同而有不同的布局。PCM音频使用WAVEFORMATEX结构。视频块结构包括VIDEOINFOHEADERVIDEOINFOHEADER2AM_MEDIA_TYPEformattype成员是指明在格式块所包含结构类型的GUID。每种结构都有一个GUIDcbForamt成员指定了格式块的大小。总是需要在废弃pbFormat指针前检测它的值。
    如果格式块被填充,主类型和子类型的信息可以忽略。但是,在没有完整的格式块时,主类型和子类型提供了一种方便的方法来识别格式。比如我们可指定一个通用的24RGB格式(MEDIASUBTYPE_RGB24,而不需要知道VIDEOINFOHEADER结构所需要的所有信息,比如图像大小和帧率。
    比如,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;
         return S_OK;
    }
    return VFW_E_INVALIDMEDIATYPE;
}
    AM_MEDIA_TYPE结构也包含了一些可选信息。它们可提供一些附加信息。但是Filters并不要求使用它们:
    ·lSampleSize: 如果非0就表示每个Sample的大小。如果是0,表示Sample大小可能随时改变
    ·bFixedSizeSamples: 如果是TRUE,表示lSampleSize值有效,否则应该忽略lSampleSize
    ·bTemporalCompression: 如果是FALSE,表示所有的帧都是关键帧
 
    2.4 About Media Samples and Allocators
    Filters通过Pin的连接来传递数据,数据流是从一个Filter的输出Pin流向相连的Filter的输入Pin。虽然有其他不少的传输机制存在,输出Pin最常用的传递数据的方式是调用输入Pin上的IMemInputPin::Receive方法。
    根据的Filter不同,有多种方式来分配媒体数据的内存块,可以在堆上分配,可以在DirectDraw的表面,也可以采用GDI共享内存,还有其他的一些分配机制。内存分配的响应对象被称为Allocator,也是一个COM对象,暴露了IMemAllocator接口。
    当两个Pin连接的时候,必须有一个Pin提供AllocatorDirectShow定义了一系列函数调用来确定由哪个Pin提供Allocator. PIN之间还会在Buffer的数量和大小上达成一致。
    在数据流开始之前,Allocator会创建一个Buffer池,在数据流动期间,上一级Filter就会将数据填充到Buffers然后传递给下一级Filter。但是,上一级Filter并不是直接将内存Buffer的指针直接传递给下一级的Filter,而是通过一个Media SampleCOM对象,这个COM对象是Allocator创建的用来管理内存BufferMedia Sample暴露了IMediaSample接口,一个Sample包含如下内容:
    ·指向内部Buffer的指针
    ·时间戳
    ·一些标志
    ·可选的媒体类型
    时间戳定义了Presentation TimeRenderer Filter就根据这个时间来安排Render顺序的。标志用来标示数据从前一个Sample后是否中断等等,媒体类型提供了一种中途改变数据格式的方法。通常,一般Sample没有媒体类型,表明它的格式从前一个Sample后没有改变。

    当一个Filter正在使用Buffer,它就会保持一个Sample的引用计数,Allocator通过引用计数用来确定是否可以重新使用一个Buffer。这样就防止了覆盖另一个Filter正在使用的Buffer。当所有的Filter都释放了对Sample的引用,Sample才返回到Allocator的内存池重新使用。 

 2.5    How Hardware Devices Participate in the Filter Graph

       本节描述DirectShow是如何与音频、视频设备进行交互。
 
       2.5.1 Wrapper Filters
       所有的DirectShow Filters都是用户组件模式的软件组件。为了把内核模式的硬件设备,比如视频捕捉卡等加入倒Filter Graph,设备必须被表示为用户模式Filter。这项功能是通过DirectShow提供的包装Filters来实现的。这些Filters包括Audio Capture Filters, VFW Capture Filters, TV Tuner Filter, TV Audio FilterAnalog Video Crossbar Filter. DirectShow也提供了称为KsProxyFilter用来描述任何类型的WDM流设备。硬件供应商可以提供聚集了KsProxyCOM对象KsProxy Plug-in, 以此扩展KsProxy来支持自定义功能,
       这些包装Filters暴露了描述设备功能的接口。应用程序使用这些接口与Filter交互数据。FilterCOM函数调用翻译为设备驱动调用,把信息传递到内核的驱动,然后再把结果转化给应用程序。TV Tuner, TV Audio, Analog Video CrossbarKsProxy通过IKsPropertySet接口支持自定义驱动属性。VFW Capture FilterAudio Capture Filter并没有这种扩展。
       对应用程序开发人员,包装Filter允许应用程序控制设备像控制其他DirectShow Filter一样。我们不需要特殊的编程,这些与内核模式的通信细节都在Filter中封装起来。
 
       2.5.2 Video for Windows Devices
       VFW Capture Filter支持早期的VFW捕捉卡。当目标系统上存在VFW卡时,它可以被发现,然后通过DirectShowSystem Device Enumerator添加到Filter Graph。细节可参考Enumerating Devices and Filters.
 
 
       2.5.3 Audio Capture and Mixing Devices (Sound Cards)
       新一代的声卡都有麦克风和其他类型设备的输入娱乐工具。通常这些声卡还有控制每个输入音量、立体音和重音的实时混合功能。在DirectShow中,声卡的输入和混合器都被Audio Capture Filter包装。每个声卡都能被System Device Enumerator发现。查看系统的声卡,运行GraphEdit并选择Audio Capture Source种类。属于此类的每个Filter都是Audio Capture Filter的一个单独实例。(参考Using GraphEdit).
 
       2.5.4 WDM Streaming Devices
     新一代硬件解码器和捕捉卡都与WDM规范一致。这些设备的功能比VFW设备强,可以在Windows NT/2KWindows 98/ME移植。WDM视频捕捉卡可以支持VFW不能支持的特征,包括枚举捕捉格式,编程控制视频参数,比如色调和亮度,编程选择输入端和TV Tuner支持。
     为了支持WDM流设备,DirectShow提供了KsProxy Filter(ksproxy.ax)KsProxy也称为“Swiss Army Knife Filter”因为它的实现的功能很多。此FilterPINS数量、暴露的COM接口数量,都取决于内在驱动的性能。KsProxyFilter Graph中不以名称KsProxy出现。而总是采用设备的Friendly Name, 可以通过注册表读取。查看系统的WDM设备,运行GraphEdit选择WDM Streaming种类。即使系统只有一个WDM卡,它也可能包括多个设备。每个设备是以不同的Filter表示。每个Filter实际上都是KsProxy.
       应用程序使用System Device Enumerator查找系统的WDM设备MonikersKsProxy被函数BindToObject实例化。因为KsProxy可以表示所有的WDM设备。它必须查询驱动来决定驱动支持那种属性集。属性集是WDM驱动使用的数据结构集合,也被一些用户模式的Filter使用。KsProxyCOM调用转译为属性集并发送给驱动。硬件供应商可以提供Plug-in扩展KsProxyPlug-in是硬件供应商指定暴露、实现特殊设备功能的接口。所有这些都从应用程序隐藏。应用程序通过KsProxy控制设备像控制其他DirectShow Filter一样。
 
       2.5.5 Kernel Streaming
       WDM设备支持内核流,这当中所有的数据都是在内核模式流动。永远也不需要转换到用户模式。在内核模式和用户模式切换的计算代价很高。内核模式允许高位率的流而不加重CPU负担。基于WDMFilter可使用内核流把多媒体数据从一个设备传递到另一个设备,可以是相同卡也可是不同的设备卡,不需要把数据拷贝到系统内存。
       从应用程序角度看,数据好像是从一个用户模式Filter移动到下一个。而实际上,数据可能根本没有进入用户模式,而是从一个内核设备直接流向另一个设备,直到被提交到图形卡。对于某些情况,比如捕捉到文件,某些时候要求数据从内核模式传递到用户模式。但是这样的交换并不必要求数据被拷贝到内存的新位置。应用程序开发人员一般不需要关心内核流的细节,除了作为背景信息。参考Microsoft DDK的关于WDM, 内核流和KsProxy的相关主题。
 
3.     Building the Filter Graph
       The Filter Graph and Its Component一节描述了建立DirectShow Filter Graph的基本组件。本节检查这些组件是如何创建、连接在一起开始处理数据的。
 
       3.1    Graph-Building Components
       DirectShow提供了组件用来建立Filter Graph。包括:
       ·Filter Graph Manager. 它控制Filter Graph. 支持IGraphBuiler, IMediaControl, IMediaEventEx及其他接口。所有的DirectShow应用程序在某些时候都使用此对象,尽管在某些情况下Filter Graph Manager是由其他对象创建的。
       ·Capture Graph Builder. 提供建立Filter Graphs的额外方法。它最开始是设计来创建执行视频捕捉的Graph(从名称看也是),但是对一些其他类型的自定义Filter Graph也有用。它支持接口ICaptureGraphBuilder2.
       ·Filter MapperSystem Device Enumerator. 它们定位注册在用户系统上的Filters. 或者表示硬件设备。
       ·DVD Graph Builder. 建立DVD回放和导航的Filter Graph. 支持IDvdGraphBuilder接口。基于脚本的应用程序可使用MSWebDVD ActiveX控制进行DVD回放。
       ·视频控制。此ActiveX控制在Windows XP上可用。它在DirectShow中处理数字和模拟电视。更多信息可参考Using the Video Control.
 
       3.1.1 Intelligent Connect
       术语“Intelligent Connect”覆盖了Filter Graph Manager用来建立全部、部分Filter Graph的一个算法集。在Filter Graph Manager需要其他Filters来完成Graph的任何时候,粗略按照如下步骤进行:
       (1)、如果FilterGraph中,并且至少有一个未连接的PINFilter Graph Manager就尝试使用这个Filter
       (2)、否则Filter Graph Manager在注册表查询能接收媒体类型连接的Filter。每个Filter在注册表有一个Merit值。它能粗略说明在完成GraphFilter可能有多大的用处。Filter Graph Manager根据Merit值的顺序进行尝试。每种流类型(比如音频、视频或者MIDI),默认的Renderer都有一个较高的Merit值。解码器的Merit值也比较高。特殊目的Filter的值比较低。
     如果Filter Graph Manager遇到困难,就返回尝试不同Filters的组合。我们可从Intelligent Gonnect主题找到具体细节。
 
       3.2    Overview of Graph Building
       创建Filter Graph,先创建Filter Graph Manager实例:
IGraphBuilder* pIGB;
HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,             IID_IGraphBuilder, (void **)&pIGB);
       Filter Graph Manager支持如下的Graph建立方法:
       ·IFilterGraph::ConnectDirect直接在两个PINS上尝试连接。若PIN不能连接,函数失败。
       ·IFilterGraph::Connect连接两个PINS,如果可能先进行直接连接。否则使用中介Filter完成连接。
       ·IGraphBuilder::Render从某输出PIN开,建立Graph剩余部分。此函数在下一级添加必要的Filter直到到达Renderer Filter.
       ·IFilterGraph::AddFilterFilter添加到Graph. 并不连接Filter.必须在调用函数前创建Filter
       这些方法提供三种基本的方法建立Graph:
       (1)Filter Graph Manager建立整个Graph
       (2)Filter Graph Manager建立部分Graph
       (3)、应用程序建立整个Graph
 
       3.2.1 The Filter Graph Manager builds the entire graph
       如果我们想简单播放的已经格式创建的文件,比如AVI, MPEG, WAVMP3, 使用RenderFile函数。How To Play a File一节说明了具体如何操作。
       RendererFile开始在注册表搜索能解析文件的Source Filter。它使用协议(比如文件名称中的http://), 文件扩展名,或者文件中的预定义字节模式来决定使用哪个Source Filter。详细信息可参考Registering a Custom File Type.
       在建立Graph剩余部分时,Filter Graph Manager使用一种交互过程。交互时用Filter输出PIN支持的媒体类型,然后在注册表查找可以用此类型做输入的Filter。它使用几个标准来加快搜索和决定Filters的使用次序:
       ·Filter Category标示了Filter的基本功能
       ·媒体类型描述了Filter可接受的输入或者发送的输出格式
       ·Merit值决定了Filter被尝试使用的顺序。如果两个Filter属于相同的Category,支持相同的输入类型,Filter Graph Manager就选择Merit值高的一个。某些FilterMerit设置得比较低,因为它们通常用于特殊用途,应该由应用程序把它们加入到Graph
       Filter Graph Manager使用Filter Mapper来查找注册表。
       只要添加了每个Filter后,Filter Graph Manager开始把它与前一个Filter的输出PIN连接。Filter进行协商来决定它们是否连接。如果是,再决定连接采用的媒体类型。如果新Filter不能连接。Filter Graph Manager丢弃它并尝试其他Filter。这个过程会一直持续下去直到每个流都被提交。
 
       3.2.2 The Filter Graph Manager builds part of the graph
       当我们不止想简单播放文件时,应用程序必须至少执行一些Graph的建立工作。比如视频捕捉应用程序必须选择捕捉Source Filter并加入Graph。如果把数据写入到AVI文件,必须添加AVI MuxFile Writer FilterGraph中。但是,让Filter Graph Manager来完成Graph也是可能的。比如我们可以调用Render函数提交PIN进行预览。
 
       3.2.3 The Application builds the entire graph
       在某些时候,我们的应用程序需要通过添加或者连接每个Filter来建立Graph。这时,我们应该知道哪个Filter应该被加入到Graph。使用这种方法,应用程序调用AddFilter来添加Filter。枚举FilterPIN,调用ConnectConnectDirect来连接它们。
 
       3.3    Intelligent Connect
       智能连接是Filter Graph Manager建立Graph的机制。它包含几个选择Filter、把Filter添加到Filter Graph的算法。对于应用程序编程,我们很少需要知道智能连接的细节。如果你建立某些Filter有困难,并且想解决问题时,或者是编写自己的Filter并且允许支持自动Graph建立,可阅读此节。
       智能连接主要涉及到如下几个IGraphBuilder的函数:
       ·IGraphBuilder::Render
       ·IGraphBuilder::AddSourceFiler
       ·IGraphBuilder::RenderFile
       ·IGraphBuilder::Connect
       Render函数建立部分Graph.它从一个未连接的输出PIN开始,并向下工作,必要时添加Filter.开始的Filter必须已经在Graph中。每一步,Render函数都查找可以连接上一级FilterFilter。数据流可以分支,如果连接Filter有多个输出PIN。直到每个流都有提交查找才停止。如果Render遇到困难,它可能返回用不同的Filter进行再次的尝试。
       为了连接每个输出PINRender函数执行如下工作:
       (1)、如果PIN支持ISteamBuilder接口,Filter Graph Manager就委派给IStreamBuilderRender函数。通过暴露此接口,PIN就被假定承担建立Graph剩余部分,一直到Renderer. 但是很少有PIN支持这个接口。
       (2)Filter Graph Manager尝试使用缓存在内存的Filter。如果有,贯穿整个智能连接过程,Filter Graph Manager可能缓存这个Filter开始处理的每一步的Filter.(参考Dynamic Graph Building)
       (3)、如果Filter Graph中某个Filter有未连接输出PINFilter Graph Manager下一步就尝试它们。我们可以在调用Render之前,把某个特殊Filter添加到Graph,以此来强制Render函数尝试使用它。
       (4)、最后,Filter Graph Manager调用IFilterMapper2::EnumMatchingFitlers查找注册表。它尝试用输出PIN的首先媒体类型来匹配注册表中列举的媒体类型。
       每个Filter都注册了一个Merit数值来区别与其他Filter的优先级。EnumMatchingFitlers函数返回以Merit排序的Filters, 最小的MeritMERIT_DO_NOT_USE+1. 它会忽略比这个最小值还小的Filter. Filter还会以GUID值的种类排序。种类本身也有MeritEnumMatchingFitlers也忽略Merit比最小值小的种类。即使种类中存在Merit比较高的Filter
       总结起来,Render函数按下面的顺序尝试Filter:
       ·使用IStramBuilder
       ·尝试缓存Filter
       ·尝试Graph中的Filter
       ·查询注册表的Filter
     AddSourceFilter函数添加一个可以提交指定文件的Source Filter。首先查询注册表匹配协议(比如http://),文件扩展名,预定义的检测字节集(通常是匹配一定模式的在文件中特殊偏移位置的字节)。细节可参考Registering a Custom File Type. 如果函数找到合适的Source Filter, 就创建Filter的实例并添加到Graph. 然后用文件名调用FilterIFileSourceFilter::Load函数。
       RenderFile函数从文件名建立一个默认的回放Graph。内部它使用AddSourceFilter来查找正确的Source Filter,然后用Render建立Graph的余下部分。
       Connect函数连接输入、输出PIN。此函数会根据Render中描述的各种算法添加中介Filter,如果需要:
       (1)、尝试不用中介Filter直接连接两个Filter
       (2)、尝试缓存Filter
       (3)、尝试Graph中的Filter
       (4)。查询注册表的Filter.
 
4.     Data Flow in the Filter Graph
       本节描述媒体数据如何在Filter Graph中移动。通常,编写DirectShow应用程序不需要知道这些细节,尽管有时候会感觉到有帮助。如果是编写DirectShow Filter,就需要理解这些知识。
     4.1    Transports
       为了在Filter Graph中传送媒体数据,DirectShow Filter需要支持一些协议,称之为传输协议(transport)。相连的Filter必须支持同样的传输协议,否则不能交换媒体数据。通常,一个传输协议要求PIN支持一个特殊的接口。当Filter连接时,一个PIN就向另一个查询此接口。
大多数的DirectShow Filter把媒体数据保存在主存储器中,并通过Pin连接把数据递送给其它的Filter,这种传输称为本地内存传输。虽然本地内存传输在DirectShow中最常用,但并不是所有的Filter都使用它。例如,有些Filter通过硬件传送媒体数据,Pin只是用来提交控制信息,如IOverlay接口。
   DirectShow为本地内存器传输定义了两种机制:推模式和拉模式。在推模式中,Source Filter生成数据并递送给下一级Filter。下一级Filter被动接收数据,完成处理后再传送给再下一级Filter。在拉模式中,Source Filter与一个Parse Filter相连。Parse FilterSource Filter请求数据后,Source Filter才递送数据以响应请求。推模式用IMemInputPin接口,拉模式用IAsyncReader接口。
   推模式比拉模式要更常用。因为后续的文章都假定使用推模式。本节的最后一部分,Pull Mode说明了IAsyncReader接口与IMemInputPin接口的区别。
 
4.2 Samples and Allocators
       当一个Pin向另一个Pin传递数据的时候,它并不是直接将内存块的指针传递下一个Pin,实际上传递的是一个管理内存的COM对象指针。这个COM对象称Media Sample暴露了IMediaSample接口。接收Pin通过调用IMediaSample的方法来对内存进行操作,比如GetSizeGetActualDataLength以及GetPointer方法。
   Sample总是从上到下递送的,从输出Pin到输入Pin,输出Pin通过调用输入PinIMemInputPinReceive方法传递Sample,输入Pin可以在Receive函数同步处理数据,或者另外采用一个工作线程进行异步处理。输入PIN也允许阻塞在Receive方法中,如果需要等待资源。
       另外一个COM对象,叫做Allocator,用来创建和管理Sample的。暴露了IMemAllocator接口。当一个Filter需要一个空的Buffer的时候,就可以调用IMemAllocator::GetBuffer,该方法返回一个指向Sample的指针。每一个Pin连接都共享一个Allocator,当两个Pin连接的时候,他们会决定由哪个Filter来提供Allocator,通过Pin还可以设置Allocator的属性,例如,Buffer的数量和大小。具体细节可参考How Filters Connect, Negotiating Allocators.
       下面的图表显示了AllocatorSampleFilter之间的关系。

       4.2.1 Media Sample Reference Counts
       Allocator创建了一个有限的Sample池。在任何时候,某些Sample可能在使用,而其他的可以响应GetBuffer调用。Allocator用引用计数保持跟踪SamplesGetBuffer返回的Sample的引用计数是1。当Sample的引用计数为0时,Sample就返回Allocator池,成为空闲Sample,可以再次响应GetBuffer的调用。如果所有的Sample都处于繁忙状态,GetBuffer就会阻塞,直到有一个Sample空闲。
       例如,假设一个输入Pin接到一个Sample,如果它在Receive方法里同步的处理这个Sample,没有增加该Sample的引用计数,在Receive返回后,输出Pin释放这个Sample,引用计数为0Sample就返回到Allocator池。另一方面,如果输入Pin在工作线程中处理该Sample,引用计数增加1,成为2,输出Pin返回,释放Sample,计数成1。在工作线程结束处理后,调用Release释放Sample. 现在Sample返回到池中。
       当一个Pin接收到一个Sample时,它可以将数据复制到另一个Sample中,也可以将这个Sample传递到下一个Filter。一个Sample可以流遍整个Filter graph。每个Filter依次调用AddRefRelease(译注:相当于使引用计数要保持大于0)。因此,输出Pin调用Receive后就决不能再使用这个Sample。因为也许下游还有Filter正在使用该Sample。输出Pin必须调用GetBuffer获取新的Sample
       这种机制减少了内存分配的,因为Buffer可以重用。也防止了Filter在没有处理的Sample重新写入,因为Allocator维护了一个可用Sample列表。
       Filter可以给输入、输出使用分别的Allocator. Filter可以在扩展输入数据时这么做(比如解压数据)。如果输出并不比输入大,Filter可以就地处理数据,而不需拷贝到新Sample。那时,两个或者更多的PIN连接就可以共享Allocator.
 
       4.2.2 Committing and Decommitting Allocators
       Filter创建一个Allocator的时候,Allocator还没有保留任何的内存,如果这个时候有人GetBuffer,就会失败。只有当数据流开始的时候,输出Pin调用IMemAllocator::Commit,提交Allocator,现在才能分配内存。然后才可以调用GetBuffer.

       当数据流停止时,Pin就调用IMemAllocator::Decommit,来销毁Allocator。在Allocator再次Commit之前,所有的GetBuffer调用都会失败。同样,即使是GetBuffer正在阻塞等待Sample,也会立即返回一个错误码。Decommit能不能释放内存取决于它的实现。比如CMemAllocator类会等到它的析构函数来释放内存。

 4.3 Filter States

    Filter有三种状态,停止,暂停,运行。暂停状态是为了在GraphCue Data, 使得运行命令可以立即响应。Filter Graph Manager控制着所有状态的转换。当应用程序调用IMediaControlRun, Pause, Stop方法时, Filter Graph Manager就调用所有Filter的相应IMediaFilter方法。停止,运行状态的切换总是要经过暂停,因此,当应用程序对停止的Graph 调用RUN命令时,Filter图表管理器在Run之前首先要暂停。对于大多数的Filter来说,RunningPaused状态是一样的。考虑如下的Filter Graph:
    Source -> Transform -> Renderer
    假定Source Filter不是实时源。当Source Filter暂停,它创建一个线程生成新的数据并尽可能快的写入到Media Samples. 线程通过调用Transform Filter的输入PIN上的IMemInputPin Receive方法把数据向下推。Transform Filter接收到在Source Filter的线程中的Sample.它可能用一个工作线程把Sample递送给Renderer,但是通常是在同一个线程完成。此时Renderer暂停,它等待接收Sample. 在接收到一个Sample后,它就阻塞线程并不定的保存数据。如果是视频Renderer, Sample作为张贴图像显示,如果必要就重画图像。
    现在,数据流完全准备好进行提交。如果Graph保持暂停,在第一帧SampleSample就堆积在Graph, 直到每个Filter都阻塞在ReceiveGetBuffer. 但是不会有数据丢失。一旦Source线程非阻塞,它简单从阻塞的点恢复。
    Source FilterTransform Filter忽略从暂停到运行的变换,它们简单的尽可能快的继续处理数据。当Render开始运行,它就开始提交Sample. 首先提交的就是当它阻塞时保存的Sample. 然后,每次都接收到新Sample. 计算Sample的提交时间。(细节可参考Time and Clocks in DirectShow),Render会保存每个Sample直到提交时间,到点后再提交Sample. 当它等待提交时间时,线程也阻塞在Receive方法,或者在工作线程用队列接收Sample. Renderer向上的所有Filter都不会陷于时间安排。
    实时源(比如捕捉设备)与普通结构有些不同。在实时源中,不合适提前准备任何数据。应用程序可能暂停Graph,在运行前等待比较长的时间。Graph不应该提交过时Sample. 因此,暂停时实时源不会产生数据,只有运行时才有。为了把事件通知给Filter Graph Manager, Source FilterIMediaFilter方法GetState返回VFW_S_CANT_CUE。此返回值表示Filter已经转换到暂停状态,即使Renderer没有接收到任何数据。
    当一个Filter停止时,它拒绝发送给它的任何SamplesSource Filter关闭他们的Stream线程,其他Filter也关闭他们创建的工作线程,PinDecommit他们的内存分配器。
 
    4.3.1   State Transitions
    Filter Graph Manager按照逆流的方向来切换Filter的状态,从Renderer FilterSource Filter,这种方式可以防止死锁和Sample的丢失。最关键的状态切换是暂停和停止之间。
    ·从停止到暂停,当Filter暂停时就准备好了从连接Filter接收SampleSource Filter是最后一个暂停的。它创建Streaming线程发送Sample,因为下游的Filter的状态都已经切换到暂停了,所以,所有的Filter都可以接收Sample。只有当Graph所有的Renderer都接收到SampleFilter Graph Manager才算完成了状态的切换(除了前面讲的实时源例外)
    ·从暂停到停止。当Filter停止时它要释放它拥有的所有的Sample以使得上一级Filter中的IMemAllocator::GetBuffer调用脱离阻塞。如果FilterReceive函数中等待资源,它也会停止等待,并从Receive返回,以使调用Filter解锁。因此,当Filter Graph Manager停止连接的上一级Filter时,Filter就不会在GetBufferReceive函数中阻塞,也就能响应停止命令。上级Filter在得到停止命令前可以会递送一些额外Sample。但是下级Filter可能拒绝他们,因为他们已经停止。
    4.4 Pull Model
IMemInputPin接口中,上级Filter决定发送什么样数据,然后将数据推给下Filter。但对某些Filter,拉模式更适合。现在,下Filter向上Filter请求数据。数据依然是从上级到下级,从输出Pin到输入Pin,但是由下Filter发动数据流动。这种类型连接采用的是IAsyncReader接口。
   拉模式的典型应用是文件回放。例如在AVI文件的回放Graph中,Async File Source Filter就执行通用文件读写操作,然后将数据以字节流的方式(不带格式信息)发送给下FilterAVI Splitter读取AVI头并把数据流分析为视频、音频流。AVI Splitter能比Async File Source Filter更好的决定它需要什么类型的数据,因此它用IMemInputPin代替IAsyncReader接口。
    为了从输出PIN请求数据,输入PIN调用如下一种方法:
    ·IAsyncReader::Request
    ·IAsyncReader::SyncRead
    ·IAsyncReader::SyncReadAligned
    第一个方法是异步的,支持多个重叠读写。其他是同步的。
    理论上,任何Filter可以支持IAsyncReader,但是实际上它被设计来连接Source FilterParser FilterParser的功能与推模式中的Source Filter非常相同。当它暂停时,创建一个数据流线程从IAsyncReader连接拉数据,并推向下一级。输出PIN使用IMemInputPinGraph的余下部分使用标准的推模式。
 
5. Event Notification in DirectShow
 
    5.1 Overview of Event Notification
    某个事件发生时,比如数据流结束,产生一个错误,提交流失败等,Filter就给Filter Graph Manager发送一个事件通知。Filter Graph Manager自己处理部分事件,另一部分交给应用程序处理。如果Filter Graph Manager没有处理事件,它就把事件通知放入到一个队列中。图表管理器也可以将自己的事件通知放进队列中。
    应用程序以事件队列获取事件并根据事件类型进行处理。DirectShow中的事件通知和Windows的消息机制差不多。应用程序也可以取消Filter Graph Manager特定事件的默认行为。Filter Graph Manager就直接把事件放入队列,留给应用程序来处理。
 
    5.2 Retrieving Events
    Filter Graph Manager暴露了三个接口处理事件通知:
    ·IMediaEventSink  Filter用这个接口来Post事件。
    ·IMediaEvent       应用程序利用这个接口来从队列中查询消息。
    ·IMediaEventEx     IMediaEvent的扩展。
    Filter通过IMediaEventSink::Notify方法向Filter Graph Manager提交事件。事件通知包括一个事件Code,这个Code不仅仅代表了事件的类型,还包含两个DWORD类型的参数用来传递一些其他的信息。根据不同的事件Code,附加信息可能是指针、返回码、参考时间或其他信息。获取事件Code和参数的全部列表可参考Event Notification Codes.
    应用程序通过调用IMediaEvent::GetEvent从事件队列中获取事件。如果有事件发生,该函数就返回一个事件码和两个参数,如果没有事件,则一直阻塞直到有事件发生和超过某个时间。调用GetEvent函数后,应用程序必须调用IMediaEvent::FreeEventParams来释放事件码所关联的资源。例如,参数可能是Filter Graph分配的BSTR内存。下面的代码演示了如何从事件队列中提取事件:
long    evCode, param1, param2;
HRESULT hr;
while (hr = pEvent->GetEvent(&evCode, &param1, &param2, 0), SUCCEEDED(hr))
{
    switch(evCode)
    {
        // Call application-defined functions for each
        // type of event that you want to handle.
    }
    hr = pEvent->FreeEventParams(evCode, param1, param2);
}
    为了重载Filter Graph Manager对事件的缺省处理,可以用某个事件码做参数调用IMediaEventCancelDefaultHandling方法。这样就可以屏蔽Filter Graph Manager对某个事件码的处理了。如果要恢复图表管理器对该事件码的缺省处理,可以调用RestoreDefaultHandling方法。如果Filter Graph对某个事件没有缺省的处理,调用这两个函数是不起作用的。
 
    5.3 Learning When an Event Occurs
    为了处理DirectShow事件,应用程序需要一种机制来知道什么时候队列中有等待的事件。Filter Graph Manager提供了两种方法:
    ·窗口通知:Filter Graph Manager发送开发者自己定义的窗口消息
    ·事件信号:如果队列中有DirectShow事件,就用事件信号通知应用程序,如果队列为空就重新设置事件信号。
    应用程序可以使用这两种技术。窗口通知通常更简单。
 
    5.3.1   Window Notification
    建立窗口通知可调用IMediaEventEx::SetNotifyWindow方法,并指定一个私有消息。应用程序可使用从WM_APP0XBFFF之间的消息值作为私有消息。只要Filter Graph Manager把一个新事件放入到队列时,就向目标窗口发送消息。应用程序从消息队列响应消息。
下面的代码演示了如何利用消息通知:
#define WM_GRAPHNOTIFY WM_APP+1    // Private message.
pEvent->SetNotifyWindow((OAHWND)g_hwnd, WM_GRAPHNOTIFY, 0);
    消息是一个普通的窗口消息,是从DirectShow的事件通知队列单独提交的。这种方法的好处多数程序已经实现了消息循环。因此,我们可以合并DirectShow的事件通知而不需要过多额外工作。下面的代码说明了如何响应消息通知的框架。完整的例子可参考Responding to Events.
LRESULT CALLBACK WindowProc( HWND hwnd, UINT msg, UINT wParam, LONG lParam)
{
    switch (msg)
    {
        case WM_GRAPHNOTIFY:
            HandleEvent(); // Application-defined function.
            break;
        // Handle other Windows messages here too.
    }
    return (DefWindowProc(hwnd, msg, wParam, lParam));
}
    由于事件通知和窗口的消息循环都是异步的,因此,当你的应用程序处理消息的时候,队列中或许有N个事件等待处理。同样,如果事件失效,它也可能从事件队列清除掉。因此,在你调用GetEvent的时候,一定要循环调用,直到返回一个错误码,这表明队列是空的。
    当你释放IMediaEventEx指针时,你可以调用SetNotifyWindow来取消事件通知,记住此时要给这个函数传递一个NULL指针。在你的事件处理程序中,在调用GetEvent之前一定要检查IMediaEventEx指针是否为空,这样就可以避免错误。因为有时在释放IMediaEventEx指针后可能会得到通知。
 
    5.3.2   Event Signaling
    Filter Graph Manager里有一个手动设置的Event内核对象,用来反映事件队列的状态。如果队列中有等待处理的事件,Event就处于通知状态,如果队列是空的,IMediaEvent::GetEvent函数调用就会重置该Event对象。应用程序可利用此事件来判断队列的状态。
    应用程序可以调用IMediaEvent::GetEventHandle获得Event内核对象的句柄,然后就可以调用WaitForMultipleObjects来等待事件的发生,如果Event被通知了,就可以调用GetEvent来获得DirectShow的事件。下面的代码演示了事件信号。返回事件句柄,再等待1000毫秒。如果事件变为信号状态,调用GetEvent返回事件Code和参数。获取到EC_COMPLETE事件Code时表示回放结束中止循环:
HANDLE hEvent;
long    evCode, param1, param2;
BOOLEAN bDone = FALSE;
HRESULT hr = S_OK;
hr = pEvent->GetEventHandle((OAEVENT*)&hEvent);
if (FAILED(hr)
{
    /* Insert failure-handling code here. */
}
while(!bDone)
{
    if (WAIT_OBJECT_0 == WaitForSingleObject(hEvent, 100))
    {
        while (hr = pEvent->GetEvent(&evCode, &param1, &param2, 0), SUCCEEDED(hr))
        {
            printf("Event code: %#04x\n Params: %d, %d\n", evCode, param1, param2);
            pEvent->FreeEventParams(evCode, param1, param2);
              switch (evCode)
             {
              case EC_COMPLETE: // Fall through.
              case EC_USERABORT: // Fall through.
             case EC_ERRORABORT:
             CleanUp();
             PostQuitMessage(0);
             return;
             }
        }
    }
}
6      Time and Clocks in DirectShow
 
       6.1    Reference Clocks
       参考时钟是Filter Graph Manager用来同步所有Filter的。任何一个暴露了IReferenceClock 接口的对象都可以作为参考时钟。参考时钟可以是Filter提供,例如声卡就可以提供一个硬件的时钟。作为应变,Filter Graph Manager也可采用系统的时间。名义上,参考时钟的精确度在100纳秒,但实际上,没有那么精确。调用IReferenceClock::GetTime可以获取时钟的当前时间。时间的基准是开始计时的时间,根据实现的不同,GetTime的返回值不是绝对的。关键的是从时间开始的变化量。
       尽管时钟的精确性还有所变动,但是GetTime方法返回的保证时间是增加的。也就是说,时钟不会倒退回去,比如,对硬件时钟进行了调整,GetTime方法就返回上次的时间,直到硬件时钟赶上。更多信息可参考CBaseReferenceClock类。
 
       6.1.1 Default Reference Clock
       Graph运行的时候,Filter Graph Manager会自动选择一个参考时钟的,选择的法则如下
       1、如果应用程序选择了时钟,就采用应用程序选择的时钟
       2、如果Graph包含活动的源Filter,并且有IReferenceClock接口,那么就用这个时钟。
       3、如果Graph没有活动的源Filter,就用Graph中任何有IReferenceClock接口的Filter,选择的方法是从Renderers逆流向上,连接的Filter优先,没有连接的Filter次之。
       4、如果没有任何Filter符合条件,就采用系统参考时钟System Reference Clock
 
       6.1.2 Setting the Reference Clock
       应用程序可以调用Filter Graph Manager的接口IMediaFilterSetSyncSource方法来设置新的参考时钟。只有因为特殊原因需要设置其他时钟时才调用此函数。
       如果你给SetSyncSource传递的参数为NULLGraph不设置任何参考时钟。如果想恢复缺省的时钟,调用IFilterGraph::SetDefaultSyncSource。当Graph的参考时钟改变时, Filter Graph Manager调用IMediaFilter::SetSyncSource通知所有的每个Filter
 
       6.3    Clock Times
       DirectShow定义了两个相关的时间:参考时间和流时间。
       ·参考时间:是参考时钟返回的绝对时间
       ·流时间:和Graph最近异常开始运行的时间有关。
              ·当Graph正在运行,流时间就等于从开始时间减去开始时间
              ·当Graph暂停,流时间就等于它暂停开始的时间
              ·在进行Seek操作后,流时间重新设置为0
              ·当Graph停止时,流时间没有定义。
       当一个Sample具有时间戳T,就表示Sample应该在流时间T播放,因此流时间也叫播放时间。
       应用程序调用IMediaControl::Run运行Graph时,Filter Graph Manager会调用每个FilterIMediaFilter::Run方法。为了补偿调用每个Filter的时间延迟,Filter Graph Manager会指定一个稍微将来的时间进行补偿。
 
       6.4    Time Stamps
       时间戳定义了媒体Sample的开始和结束时间,用流时间表示。时间戳也叫播放时间。通过后面的知识你会了解到,并不是所有格式的数据流都采用同一种样式的时间戳。比如,并不是所有的MPEG Sample都有时间戳。在MPEG Filter Graph,时间戳并不应用在每帧上直到它们从解码器输出。
       Render Filter接收到Sample,根据Sample的时间戳进行提交。如果Sample达到晚了或者没有时间戳, Filter立即提交Sample。否则,在提交Sample前,Filter会一直等到Sample的开始时间。(通过调用IReferenceClock::AdviseTime方法等待开始时间。)
       Source FilterParse Filter有责任给处理的Sample设置正确的时间戳。使用如下规则:
       ·文件回放:第一个Sample的时间戳为0,随后的时间戳根据Sample的长度和播放的速率来确定。这些又由文件格式确定。解析Filter有责任计算正确的时间戳(例如AVI Splitter)。
       ·视频和音频的捕捉:每个Sample都打上开始时间,它等于捕捉的流时间。注意下面两点:
              ·预览PIN(与捕捉PIN相反)出来的视频帧没有时间戳。因为Graph延迟,打上捕捉时间的视频帧到达视频Renerer时总是有延迟。这样就会使Renderer丢帧以尝试质量控制。关于质量控制可参考Quality-Control Management
              ·音频捕捉:音频捕捉Filter使用自己的缓冲,与音频驱动所使用的不同。音频捕捉驱动以固定间隔时间填充捕捉Filter的缓冲。间隔时间取决于驱动,但通常不超过10毫秒。在音频Sample上的时间戳反应了驱动填充捕捉Filter缓冲的时间。这些时间有稍微不准确,特别是如果应用程序使用更小的缓冲。但是,媒体时间能准确反应缓冲中音频Sample的数量。
       ·Mux Filter: 根据输出数据流的格式,Mux Filter可能需要生成时间戳,也许不需要。比如AVI文件格式使用固定的帧率,没有时间戳,因此AVI Mux Filter假定Sample都是在大约正确的时间达到。如果到达时间比时间戳晚就会丢帧。而对于文件回放,新的时间戳是运行时生成的。
       可以通过调用IMediaSample::SetTime来给Sample设置时间。
       另一个可选功能是Filter可以给Sample指定Media Time。在视频流,Media Time表示帧数。在音频流,表示数据包中的Sample数量。比如,如果每个包有1秒的444.1KHZ的音频,第一个包的媒体开始时间是0媒体结束时间是44100。在支持Seek的流,总是与流的开始时间有关。比如,如果对一个15FPS的视频从开始定位到2秒。定位后的Media Sample的时间戳是0但是Media Time30
       RendererMux Filter可通过检测间隔用Media Time判断帧或者Sample是否被丢弃。但是,Filter并不要求设置Media Time。设置Media Time可调用IMediaSample::SetMediaTime.
 
       6.5    Live Source
       活动的Live Filter,就是推模式的源,实时的接收数据。视频捕捉和网络广播就是例子,活动源通常无法控制数据流得速率。
       Filter如果有下面的任意一个特征,通常被认为是Live Filter
       1IAMFilterMiscFlags::GetMiscFlags时返回AM_FILTER_MISC_FLAGS_IS_SOURCE,并且至少有一个输出Pin暴露了IAMPushSource接口
       2Filter暴露IKsPropertySet接口,并且有个捕捉PinPIN_CATEGORY_CAPTURE),更多信息可参考Pin Property Set.
       如果Live Source Filter提供时钟,那么Filter Graph Manager首先采用它作为参考时钟。
 
       6.5.1 Latency
       Filter的反映时间就是Filter处理Sample所花费的时间。对于Live Source,反应时间由Sample的内存大小决定。例如,假设一个Filter有一个33ms反应时间的视频源,一个500ms反应时间的音频源,每一个视频祯(video frame)比相应的音频Sample要早470ms,除非Graph进行补偿,否则视频和音频是不同步的。
       Live Source可以通过IAMPushSource接口来进行同步。Filter Graph Manager在应用程序调用IAMGraphStreams::SyncUsingStreamOffset方法后就不会对源进行同步。如果开启同步,Filter Graph Manager在每个Source Filter查询IAMPushSource接口,如果支持接口,Filter Graph Manager就用IAMLatency::GetLatency来得到Filter期望的反应时间。(IAMPushSourceIAMLatency继承)。通过组合这些反应值,Filter Graph Manager 决定Graph的最大反应时间。然后调用IAMPushSource::SetStreamOffset给每个源Filter设置一个数据流偏移时间,Filter给它产生的Sample打时间戳的时候会加上偏移时间的。
     这种方法主要用于实时预览。但是,实时捕捉设备的(比如摄像机)并不在Sample上设置时间戳。因此,在实时捕捉设备上用此方法,必须从捕捉PIN进行预览。可参考DirectShow Video Capture Filter.   现在,VFW Capture Filter Audio Capture Filter.都支持IAMPushSource接口。
 
       6.5.2 Rate Matching
       Render Filter利用参考时钟安排播放顺序的时候,如果源Filter采用另一种时钟,在重放的时候就会发生故障。播放的速度大于源产生的速度,就会产生间隙停顿,或者播放速度小于源的产生速度,就会形成数据的堆积,直到Graph丢帧。源一般来说是无法控制数据的产生速度的,因此,播放速度要随着源的速度改变而改变。
       现在,只有在音频播放Filter才能进行速率匹配,因为音频中的故障比视频中的更容易捕捉到。为了匹配播放速率,音频Renderer必须要选择一个匹配标准。使用如下规则:
       ·如果Graph没有使用参考时钟,没法进行速率匹配。(只要没有参考时钟Sample都是立即提交)
       ·如果Graph有参考时钟,Renderer的上一级必须要有一个活动的源。否则不进行匹配。
       ·如果上一级有Live Source, 并且支持IAMPushSource接口,调用GetPushSourceFlags
              ·返回AM_PUSHSOURCECAPS_INTERNAL_RM,表示Source有自己的匹配机制
              ·返回AM_PUSHSOURCECAPS_NOT_LIVE,表示不是Live Source,即使有IAMPushSource接口,因此Audio Renderer不匹配。
              ·返回AM_PUSHSOURCECAPS_PRIVATE_CLOCK,表示Source用私有时钟生成时间戳。此时,Audio Renderer用时间戳匹配。(如果没有时间戳,但是Renderer忽略Flag.
       ·如果GetPushSourceFlags返回0,播放Filter就根据Graph时钟和Sample的时间戳来自己决定播放速率。
              ·如果Audio Renderer不是Graph时钟并且Sample有时间戳,就与时间戳匹配。
              ·如果Sample没有时间戳,Audio Renderer尝试与进入Audio Data的频率匹配。
              ·如果Audio RendererGraph时钟,尝试与进入Audio Data匹配。

       最后一种情况的原因是:如果Audio Renderer是参考时钟,Source Filter用同样的时钟生成时间戳,然后Audio Renderer不能与时间戳匹配。如果这么做了,就可能尝试与自己匹配,这样会引起时钟混乱。因此,Audio Renderer就与进入Audio Data的频率匹配。 

posted on 2009-02-02 15:58  Jrong  阅读(1817)  评论(0编辑  收藏  举报