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
在可能的任何时候使用
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的内存池重新使用。