1. DirectShow System Overview
1.1 The Challenge of Multimedia
进行多媒体编程主要存在如下几个挑战:
·多媒体流包含大量的数据,这些数据又要求进行快速的处理
·音频、视频必须同步,以使它们在相同时间开始、停止,以相同的频率播放
·数据来源多,包括本地文件,计算机网络,电视广播和视频摄像机
·数据源的格式多,比如音频、视频交叉存取的(AVI),高级流格式(ASF),运动图像专家组(MPEG)和数字视频(DV)。
·程序员不能预先知道在用户终端系统存在的硬件设备
1.2 The DirectShow Solution
DirectShow就是被设计来解决这些挑战的。DirectShow通过把应用程序从复杂的数据传输、硬件区别和同步隔离起来。它的主要目标是简化在Windows平台创建数字媒体程序的任务。
为了达到流递音频、视频所需的流量,DirectShow在可能的任何时候使用DirectDraw和DirectSound。这些提交数据的数据有效的利用了用户的声卡和图形卡。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基于模块化结构,每个处理阶段都由称为Filter的COM对象完成。DirectShow提供了一系列标准Filter用于应用程序开发,开发者也可以开发自己的Filter来扩展DirectShow的功能。为了举例说明,这里是一个播放AVI视频文件所需要的步骤,连同完成每步的Filters:
·从原始文件读取数据为字节流(File Source Filter)
·检测AVI头,把字节流分析为单独的视频帧和音频Samples(AVI Splitter Filter)
·解码视频帧(各种解码Filters, 取决于压缩格式)
·画视频帧(Video Renderer Filter)
·把音频Samples发送到声卡(默认的DirectSound设备Filter)
这些Filters及结构如图所示:
如图所示,每个Filter都与一个或多个Filter相连接。连接点也是一个COM对象,称为Pins。Filters使用PINS把数据从一个Filter移动到下一个。图中的箭头表示数据的流向。在DirectShow,一个Filters的集合称为Filter Graph。
Filter有三种可能的状态,运行,停止,暂停。当一个Filter运行时,它就处理媒体数据流,当停止时,Filter就不处理数据,暂停状态用来在运行前Cue Data, 在Data Flow in the Filter Graph一节对这些概念有更详细的描述。除非特别的例外,状态改变都是协调贯穿在整个Filter Graph,Graph的所有Filter的状态改变都是统一的。因此,Filter Graph也可说是运行,停止,暂停。
Filter 一般分为下面几种类型。
(1)、源Filter(Source Filter):源Filter引入数据到Filter Graph,数据来源可以是文件、网络、照相机或者是任何地方。每个源Filter处理不同类型的数据源。
(2)、变换Filter(Transform Filter):变换Filter的工作是获取输入流,处理数据,并生成输出流。编码、解码读书变换Filter的例子。
(3)、提交Filter(Renderer Filter):提交Filter在Filter图表里处于最后一级,它们接收数据并把数据提交给用户。比如,视频Renderer把视频帧画在显示器,音频Renderer把音频数据发送到声卡,File-Writer Filter把数据写入文件。
(4)、分割Filter(Splitter Filter):分割Filter把输入流分割成多个输出。例如,AVI分割Filter把一个AVI格式的字节流分割成视频流和音频流。
(5)、混合Filter(Mux 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中每一个Filters。Seeking也是按同样的方式工作,先由应用程序将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之间,Filters与Filter 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-24、RGB-32和UYVY等等。对于音频,子类型可能是PCM音频、MPEG-1 Payload等等。主类型提供了比子类型更多的信息,但是它还没有定义格式的所有信息。比如,视频子类型没有定义图像大小和帧率。这些通过下面的格式子块说明。
·格式子块:它是描述格式细节的数据块。它是从AM_MEDIA_TYPE结构单独分配。AM_MEDIA_TYPE的pbFormat指针指向格式子块。
pbFormat是一个void*的指针,因为格式块会因为媒体类型的不同而有不同的布局。PCM音频使用WAVEFORMATEX结构。视频块结构包括VIDEOINFOHEADER和VIDEOINFOHEADER2。AM_MEDIA_TYPE的formattype成员是指明在格式块所包含结构类型的GUID。每种结构都有一个GUID。cbForamt成员指定了格式块的大小。总是需要在废弃pbFormat指针前检测它的值。
如果格式块被填充,主类型和子类型的信息可以忽略。但是,在没有完整的格式块时,主类型和子类型提供了一种方便的方法来识别格式。比如我们可指定一个通用的24位RGB格式(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提供Allocator,DirectShow定义了一系列函数调用来确定由哪个Pin提供Allocator. PIN之间还会在Buffer的数量和大小上达成一致。
在数据流开始之前,Allocator会创建一个Buffer池,在数据流动期间,上一级Filter就会将数据填充到Buffers然后传递给下一级Filter。但是,上一级Filter并不是直接将内存Buffer的指针直接传递给下一级的Filter,而是通过一个Media Sample的COM对象,这个COM对象是Allocator创建的用来管理内存Buffer。Media Sample暴露了IMediaSample接口,一个Sample包含如下内容:
·指向内部Buffer的指针
·时间戳
·一些标志
·可选的媒体类型
时间戳定义了Presentation Time,Renderer Filter就根据这个时间来安排Render顺序的。标志用来标示数据从前一个Sample后是否中断等等,媒体类型提供了一种中途改变数据格式的方法。通常,一般Sample没有媒体类型,表明它的格式从前一个Sample后没有改变。
当一个Filter正在使用Buffer,它就会保持一个Sample的引用计数,Allocator通过引用计数用来确定是否可以重新使用一个Buffer。这样就防止了覆盖另一个Filter正在使用的Buffer。当所有的Filter都释放了对Sample的引用,Sample才返回到Allocator的内存池重新使用。