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 Filter
和
Analog Video Crossbar Filter. DirectShow
也提供了称为
KsProxy
的
Filter
用来描述任何类型的
WDM
流设备。硬件供应商可以提供聚集了
KsProxy
的
COM
对象
KsProxy Plug-in,
以此扩展
KsProxy
来支持自定义功能,
这些包装
Filters
暴露了描述设备功能的接口。应用程序使用这些接口与
Filter
交互数据。
Filter
把
COM
函数调用翻译为设备驱动调用,把信息传递到内核的驱动,然后再把结果转化给应用程序。
TV Tuner, TV Audio, Analog Video Crossbar
和
KsProxy
通过
IKsPropertySet
接口支持自定义驱动属性。
VFW Capture Filter
和
Audio Capture Filter
并没有这种扩展。
对应用程序开发人员,包装
Filter
允许应用程序控制设备像控制其他
DirectShow Filter
一样。我们不需要特殊的编程,这些与内核模式的通信细节都在
Filter
中封装起来。
2.5.2 Video for Windows Devices
VFW Capture Filter
支持早期的
VFW
捕捉卡。当目标系统上存在
VFW
卡时,它可以被发现,然后通过
DirectShow
的
System 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/2K
和
Windows 98/ME
移植。
WDM
视频捕捉卡可以支持
VFW
不能支持的特征,包括枚举捕捉格式,编程控制视频参数,比如色调和亮度,编程选择输入端和
TV Tuner
支持。
为了支持
WDM
流设备,
DirectShow
提供了
KsProxy Filter(ksproxy.ax)
。
KsProxy
也称为“
Swiss Army Knife Filter
”因为它的实现的功能很多。此
Filter
的
PINS
数量、暴露的
COM
接口数量,都取决于内在驱动的性能。
KsProxy
在
Filter Graph
中不以名称
KsProxy
出现。而总是采用设备的
Friendly Name,
可以通过注册表读取。查看系统的
WDM
设备,运行
GraphEdit
选择
WDM Streaming
种类。即使系统只有一个
WDM
卡,它也可能包括多个设备。每个设备是以不同的
Filter
表示。每个
Filter
实际上都是
KsProxy.
应用程序使用
System Device Enumerator
查找系统的
WDM
设备
Monikers
。
KsProxy
被函数
BindToObject
实例化。因为
KsProxy
可以表示所有的
WDM
设备。它必须查询驱动来决定驱动支持那种属性集。属性集是
WDM
驱动使用的数据结构集合,也被一些用户模式的
Filter
使用。
KsProxy
把
COM
调用转译为属性集并发送给驱动。硬件供应商可以提供
Plug-in
扩展
KsProxy
。
Plug-in
是硬件供应商指定暴露、实现特殊设备功能的接口。所有这些都从应用程序隐藏。应用程序通过
KsProxy
控制设备像控制其他
DirectShow Filter
一样。
2.5.5 Kernel Streaming
WDM
设备支持内核流,这当中所有的数据都是在内核模式流动。永远也不需要转换到用户模式。在内核模式和用户模式切换的计算代价很高。内核模式允许高位率的流而不加重
CPU
负担。基于
WDM
的
Filter
可使用内核流把多媒体数据从一个设备传递到另一个设备,可以是相同卡也可是不同的设备卡,不需要把数据拷贝到系统内存。
从应用程序角度看,数据好像是从一个用户模式
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 Mapper
和
System 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)
、如果
Filter
在
Graph
中,并且至少有一个未连接的
PIN
,
Filter Graph Manager
就尝试使用这个
Filter
。
(2)
、否则
Filter Graph Manager
在注册表查询能接收媒体类型连接的
Filter
。每个
Filter
在注册表有一个
Merit
值。它能粗略说明在完成
Graph
中
Filter
可能有多大的用处。
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::AddFilter
把
Filter
添加到
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, WAV
或
MP3,
使用
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
值高的一个。某些
Filter
的
Merit
设置得比较低,因为它们通常用于特殊用途,应该由应用程序把它们加入到
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 Mux
和
File Writer Filter
到
Graph
中。但是,让
Filter Graph Manager
来完成
Graph
也是可能的。比如我们可以调用
Render
函数提交
PIN
进行预览。
3.2.3 The Application builds the entire graph
在某些时候,我们的应用程序需要通过添加或者连接每个
Filter
来建立
Graph
。这时,我们应该知道哪个
Filter
应该被加入到
Graph
。使用这种方法,应用程序调用
AddFilter
来添加
Filter
。枚举
Filter
的
PIN
,调用
Connect
或
ConnectDirect
来连接它们。
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
函数都查找可以连接上一级
Filter
的
Filter
。数据流可以分支,如果连接
Filter
有多个输出
PIN
。直到每个流都有提交查找才停止。如果
Render
遇到困难,它可能返回用不同的
Filter
进行再次的尝试。
为了连接每个输出
PIN
,
Render
函数执行如下工作:
(1)
、如果
PIN
支持
ISteamBuilder
接口,
Filter Graph Manager
就委派给
IStreamBuilder
的
Render
函数。通过暴露此接口,
PIN
就被假定承担建立
Graph
剩余部分,一直到
Renderer.
但是很少有
PIN
支持这个接口。
(2)
、
Filter Graph Manager
尝试使用缓存在内存的
Filter
。如果有,贯穿整个智能连接过程,
Filter Graph Manager
可能缓存这个
Filter
开始处理的每一步的
Filter.(
参考
Dynamic Graph Building)
。
(3)
、如果
Filter Graph
中某个
Filter
有未连接输出
PIN
,
Filter Graph Manager
下一步就尝试它们。我们可以在调用
Render
之前,把某个特殊
Filter
添加到
Graph
,以此来强制
Render
函数尝试使用它。
(4)
、最后,
Filter Graph Manager
调用
IFilterMapper2::EnumMatchingFitlers
查找注册表。它尝试用输出
PIN
的首先媒体类型来匹配注册表中列举的媒体类型。
每个
Filter
都注册了一个
Merit
数值来区别与其他
Filter
的优先级。
EnumMatchingFitlers
函数返回以
Merit
排序的
Filters,
最小的
Merit
是
MERIT_DO_NOT_USE+1.
它会忽略比这个最小值还小的
Filter. Filter
还会以
GUID
值的种类排序。种类本身也有
Merit
。
EnumMatchingFitlers
也忽略
Merit
比最小值小的种类。即使种类中存在
Merit
比较高的
Filter
。
总结起来,
Render
函数按下面的顺序尝试
Filter:
·使用
IStramBuilder
·尝试缓存
Filter
·尝试
Graph
中的
Filter
·查询注册表的
Filter
AddSourceFilter
函数添加一个可以提交指定文件的
Source Filter
。首先查询注册表匹配协议(比如
http://
),文件扩展名,预定义的检测字节集(通常是匹配一定模式的在文件中特殊偏移位置的字节)。细节可参考
Registering a Custom File Type.
如果函数找到合适的
Source Filter,
就创建
Filter
的实例并添加到
Graph.
然后用文件名调用
Filter
的
IFileSourceFilter::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 Filter
向
Source Filter
请求数据后,
Source Filter
才递送数据以响应请求。推模式用
IMemInputPin
接口,拉模式用
IAsyncReader
接口。
推模式比拉模式要更常用。因为后续的文章都假定使用推模式。本节的最后一部分,
Pull Mode
说明了
IAsyncReader
接口与
IMemInputPin
接口的区别。
4.2 Samples and Allocators
当一个
Pin
向另一个
Pin
传递数据的时候,它并不是直接将内存块的指针传递下一个
Pin
,实际上传递的是一个管理内存的
COM
对象指针。这个
COM
对象称
Media Sample
。
暴露了
IMediaSample
接口。接收
Pin
通过调用
IMediaSample
的方法来对内存进行操作,比如
GetSize
、
GetActualDataLength
以及
GetPointer
方法。
Sample
总是从上到下递送的,从输出
Pin
到输入
Pin
,输出
Pin
通过调用输入
Pin
上
IMemInputPin
的
Receive
方法传递
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.
下面的图表显示了
Allocator
,
Sample
和
Filter
之间的关系。
4.2.1
Media Sample Reference Counts
Allocator
创建了一个有限的
Sample
池。在任何时候,某些
Sample
可能在使用,而其他的可以响应
GetBuffer
调用。
Allocator
用引用计数保持跟踪
Samples
。
GetBuffer
返回的
Sample
的引用计数是
1
。当
Sample
的引用计数为
0
时,
Sample
就返回
Allocator
池,成为空闲
Sample
,可以再次响应
GetBuffer
的调用。如果所有的
Sample
都处于繁忙状态,
GetBuffer
就会阻塞,直到有一个
Sample
空闲。
例如,假设一个输入
Pin
接到一个
Sample
,如果它在
Receive
方法里同步的处理这个
Sample
,没有增加该
Sample
的引用计数,在
Receive
返回后,输出
Pin
释放这个
Sample
,引用计数为
0
,
Sample
就返回到
Allocator
池。另一方面,如果输入
Pin
在工作线程中处理该
Sample
,引用计数增加
1
,成为
2
,输出
Pin
返回,释放
Sample
,计数成
1
。在工作线程结束处理后,调用
Release
释放
Sample.
现在
Sample
返回到池中。
当一个
Pin
接收到一个
Sample
时,它可以将数据复制到另一个
Sample
中,也可以将这个
Sample
传递到下一个
Filter
。一个
Sample
可以流遍整个
Filter graph
。每个
Filter
依次调用
AddRef
和
Release
(译注:相当于使引用计数要保持大于
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类会等到它的析构函数来释放内存。