direct show


写这篇笔记,就当是对这个期间学习 DirectShow 的一个总结,假如它可以给你带来一些收 获的话,那我会感到更高兴。还有我遇到的一些问题,希望和大家一起讨论。 一、基本概念 1.DirectShow 概述 DirectShow 的主要功能是流媒体的采集与回放。 它集成了 DirectDraw,DirectSound,Direct3D 的一些技术。 它是一个开放的架构,你自己可以写自己的 Filter 组件。 DirectShow 的架构: (这个图太经典了)

2.Filter Filter 其实就是一个 COM 组件,但它有自己的特性。它由输入,处理,输出三部分 组成, 输入,输出是叫 PIN,输入的叫输入 pin,输出的叫输出 PIN。PIN 也是一.种 COM 组 件,它实现了 IPin 接口。 在 Filter 的实现里,PIN 其实是 Filter 的一个数据成员。 (个人想法) (1) Filter 分为三类: Source Filter(源过滤器), Transform Filter(转换过滤器) Render Filter(渲染过滤器) (2)Source Filter 的主要负责音、视频数据的采集,读取。比如 File Source Filter(文件源过滤器),它是从磁盘里的音视文件读取数据的。 Transform Filter 负责音、视数据的压缩,解压,编码、解码、分析。 比如:Spliiter Filter(分离器) ,其功能是将音频流和视频流分离开来 Mux Filter(复合器),将音频流和视频流 全成单一数据流 还有一此 Encoder,Decoder,Compressor 等。 (3)Render Filter 负责音、视流的渲染,就是和输出,它将数据流送到输出设备, (显示器、磁盘、声卡) 。 Filter 有三种状态,运行,暂停,停止。

关于数据流: 3.Filter Graph(过滤器图) Filter Graph 是 DirectShow 一个基本的概念。我们开发一个 DirectShow 应用,其中最 重要的步骤就是构造一个 Filter Graph。下图就是一个 Filter Graph

这是一个实现本地文件(avi 文件)回放的 Filter Graph,File Source 负责读文件理的数据,之后将数据传输给 AVI Splitter,AVI Spliiter 将接收的数据进行分析,分离成视频流和音频流。视频流经过 AVI Ddcompressor 的 解码,送到 Video Renderer 输出到显示器。从 AVI Spliter 出来的音频流则送到 DirectSound Device, DirectSound Device 送到音频输出 由 设备。 4.Filter Graph Manager Filter Graph Manager 也是一个 COM 组件, 它是 DirectShow 的控制中心, 它控制 Filter 的运行。它有以下几个功能: (1)协调各个 Filter 的状态。Filter 有运行,暂停,停止三种状态,在 Filter Graph 中,各个 Filter 的状态必须协调一到,否则会引起冲突。Filter Graph Manager 负责协调各个 Filter 的状态。 (2) 提供一个参考时间,用以同步音频流和视频流。 (3) 提供一套用于建立 Filter Graph 的方法 (4)与应用程序进行交互,它会将 Filter Graph 内部发生的事件,通过消息的方式通 知应用程序。 5.Sample Filter Graph 运行的时候,数据从上游的 Filter 传到下游的 Filter,数据有一个单位,这个单 位叫 Sample,就是数据将分成一个一个的 Sample 进行传输。 6.Media Type 媒体类型是 DirectShow 的一个基本的概念,它描述了两个过滤器之间传输数据 (Sample)的格式。两个过滤器在连接之前,必须就所采用的媒体类型进行协商。 媒体类型,用 AM_MEDIA_TYPE 结构体来表示,它的结构可以看 MSDN 文档。 其中有几个域是比较重要的 typedef struct _MediaType{ GUID majortype //主类型 GUID subtype //辅助类型(进一步描述数据格式)

GUID formattype ULONG cbFormat BYTE* pbFormat //其它略 }

//更详细描述数据格式 //描述 formatype 数据块的大小 //描述 formattype 数据块的内存指针

比较常用的描述媒体类型的 GUID majortype 的有: MEDIATYPE_Video(数字视频) MEDIATYPE_Audio(数字音频) MEDIATYPE_AnologVideo(模拟视频) //似乎没用过 MEDIATYPE_AnologAudio(模拟音频) //同上 MEDIATYPE_Stream(字节流) //Filter Source Filter 从文件里读出来的数据 MEDIATYPE_Interleaved //DV 的数据流(包括音视频数据) Subtype //MEDIASUBTYPE_Y Formattype FORMAT_None FORMAT_DvInfo (描述 DV 的数据类型格式) FORMAT_VideoInfo (描述一般视频数据格式) FORMAT_WaveFormatE (描述一般音频数据格式) FORMAT_VideoInfo2 媒体类型协商:两个过滤器在传输数据之前,必须就所采用的媒体类型进行协商。 7.Filter 的连接过程 DirectShow 提 供 了 多 方 法 来 连 接 Filter , 如 IFilterGraph::ConectDirect,IGraphBuilder::Connect Filter 的连接过程实际是两个过滤器就媒体类型进行协商的过程。 Filter 连接的过程其 实它们的 PIN 连接的过程,就是上游的输出 PIN 与上游的输入 PIN 连接。我们知道,每一个 PIN 都有它自己支持的媒体类型,而当 两个 PIN 没有共同支持的媒体类型的时候,它们是不能连接的,所以连接之前要进行协商。 在 Conect 函数的实现里,可以看到大致过程是这样的: 连接是由输出 PIN 发出的 Connect Start IF PIN 已连接 THEN EXIT IF Filter 不是停止状态 THEN EXIT

//开始协商过程 CALL AgreeMediaType Connect End AgreeMediaType Start 在输出 PIN 或输入 PIN 媒体类型枚举器 mediatypeEnums

调用函数 TryMediaTypes,并把枚举器的指针传给它 AgreeMediaType End TryMediaTypes Start FOR EACH mediatype IN mediatypEnums AttempConnection(media) NEXT TryMediaTypes End AttemptConnection Start CheckMediaType //检查媒体类型是否被支持(输出 PIN 自己) IF 支持 THEN 在 PIN 上保存媒体类型 ELSE FAILED END IF CALL ReceiveConnect // 询问输入 PIN 是否接受当前的媒体类型。 IF SUCCEED THEN CALL CompleteConnect ELSE FAIL END IF AttemptConnect End CompleteConnect 的作用是完成连接后的一些后续工作,主要是决定数据传输的时候 使用哪一个内存分配器,由谁来创建内存分配器(IMemAllocator) 。 注: Filter 传输的数据单元叫 Sample, 它是一个 COM 组件, 封装了一个缓冲区。 Sample 由内存分配器来管理。内存分配器是实现了 IMenAllocator 接口的 COM 组件,负责管理 Sample。 智能连接:构造 Filter Graph 的时候通过一些方法的调用,DirectShow 会帮我们构造整 个 Filter Graph。用到的方法有 IGraphBuilder::RenderFile, IGraphBuilder:Render ICaptureGraphBuilder2::RenderStream 8.Filter 的数据传送 (1)数据传送有两种模式:拉模式,推模式。 推模式:上游 Filter 调用下游输入 PIN 上的 IMemInputPin::Receive,使用这种模式的 话,输入 PIN 必须实现 IMemInputPin 接口。比较典型的使用推模式的 Filter 是 Capture Source Filter。 拉模式:下游的 Filter 调用 上游输出 PIN 的 IAsycnRender 接口的方法,这是,输 出 PIN 必须实现 IAsyncReader 接口。比较典型的使用拉模式的是 Fillter Source.

(2)传送过程中的内存管理:在传送的过程中,必须分配一定的缓冲。这个过程是 通过内存分配器(IMemAllocator)来实现,它管理一系列的 Sample。Sample 是一个 COM 对 象(实现 IMediaSample 接口) ,它封装并管理一个缓冲区。 在连接的过程中,Filter 协商双方共同使用的内存管理器,并知道了内存管理器的位 置。 这样, 当上游的 Filter 要传输数据的时候, 它将调用 IMemAllocator::GetBuffer 来得到 一个闲的 Sample 然后将数据进去,传输给下游。 9.关于 Filter Graph 的状态转换: 10.质量控制:好像没什么用,没用到过 11.音、视同步: (1) 参考时钟: 主要就是为 Filter Graph 设置一个参考时钟,凡实现了 IReferenceClock 接口的都可以作 为参考时钟,FGM(Filter Graph Manager)有一套选定参考时钟的策略:一般有 Live Source(Capture Source Filter)就选它作时钟,因为,这种 Filter 包装的硬件上一般都有一个定时器。如果没 有 Live Source,那就选 Audio Renderer,因为声卡上一般也有定时器。如果还没有的话,那就看其它 Filter 有没有 实现 IReferenceClock。确实没有的话,就选系统时钟。 (2) 时间戳(Time Stample) 送到 Renderer 渲染的 Sample 必须打上时间戳, 就是一个渲染的 Start Time 和 End Time 好让 Renderer 为它选择渲染时间。IMediaSample::SetTime 12.DirectShow 对硬件的支持 DirectShow 中 Filter 是工作在用户模式(user mode),而硬件设备,比如:视频采集卡, 是工作是内核模式(kernel mode) ,那么,它们是怎样进行交互的呢?DirectShow 为我们提供了包装 Filter,包 装 Filter 也是工作用户模式,不过它可以跟硬件驱动程序进行交互,再通过硬件驱动去控制 硬件设备。 DirectShow 里面有一个叫 ksProxy 的包装 Filter,它是支持所有 WDM 设备,它提供了 一个框架,然后由硬件厂商进行实现。

二.常用的接口 比较有用的接口和方法 IBaseFilter 这是所有 Filter 至少要实现的接口 IPin PIN 至少要实现的接口 IFilterGraph 提供了一些建立 Filter Graph 的方法 IGraphBuilder (这个接口是由 IFilterGraph 继承来的,它提供了一些更方便的方法 来建立 Filter Graph,比如:RenderFile,Render,AddSourceFilter) IMediaControl(控制 Filter Graph 的状态) IVideoWindow(控制视频窗口) //接口方法:put_MessageDrain IMediaEventEx(事件处理有关的)

IMediaSeeking(用于媒体播放定位的) 上面这些接口,都是通过 Filter Graph Manager 暴露出来的,我们只创建一个 Filter Graph Manaer 组件,就可以它们 ICaptureGraphBuilder2(用来建立 Capture Filter Graph,这是一个非常有用的接口) 方法: (1)SetFiltergraph //设置对应的 IGraphBuilder*,也就是 FilterGraph (2)RenderStream //用得最多的方法,对 Capture Source Filter 指定的输出 PIN (Capture ,Preview,CC,VBI,)完成 Filter Graph 下游其它部分的构造。 它的原型如下:HRESULT RenderStream( const GUID *pCategory, const GUID *pType, IUnknown *pSource, IBaseFilter *pIntermediate, IBaseFilter *pSink);它比较常见的用法如下: 为了对视频进行预览,我们只要如下调用: RenderStream( & PIN_CATEGORY_PREVIEW,&MEDIATYPE_Video,pCap,NULL,NULL); 为了对视频进行保存的: RenderStream(&PIN_CATEGORY_CAPTURE,&MEDIATYPE_Video,pCap,NULL,pMux); pMux 是一个 Mux Splitter 对输入的音、视频数据合并并进行编码, (3) SetOutputFileName 这个方法是在对视频进行保存时调用的,可以设置保存文件路 径,保存类型(如 avi,asf) ,并会返回一个 Mux Filter 指针。 它的原型是: HRESULT SetOutputFileName( const GUID *pType, LPCOLESTR lpwstrFile, //文件保存位 置 IBaseFilter **ppf, //返回一个 Mux Filter 指针 IFileSinkFilter **pSink //这个指针用 来更改文件保存路径,如果不需要可以置为 NULL);pType 可能是一个 subtype,当它是 subtype 时,只能下面两种取值: MEDIASUBTYPE_Avi(保存为 Avi 格式) , MEDIASUBTYPE_Asf(保存为 Asf), 因为这两种格式是 windows 可以识别的,所以它自动返回一个 Mux Filter 指针 如果要保存为其它格式,那么要将 pType 这个参数指定为一个 Mux Filter 或一个 File Writer 的 GUID。 模拟电视卡可能用到的接口: ICrossbar //输入端子选择 IAMTuner IAMTVTuner //电视卡上 TV Tuner 的包装类,控制电视频道的接收与选择 常用方法:put_Channel //设置接收频道

三、无题 1. DirectShow 开发环境的配置(VC 6.0) 一般包含的头文件是 dshow.h 或 streams.h 有时还会用到其它头文件, 比如: qedit.h 用到的静态连接库文件:strmiids.lib strmbasd.lib(strmbase.lib Release 版本) quartz.lib winmm.lib uuid.lib 路径配置:VC tools\option\ 有配置的时候, 最后把所有的库文件都包含上去, 这样连接的时候就不会出现莫名其 妙的错误了。 2. DirectShow 应用开发的一般步骤 (1) 创建一个 Filter Graph Manager 组件 (2) 构造一个完整的 Filter Graph (3) 从 Filter Graph Manager 组件得到各种接口,比如:IMediaControl,对 程序进行控制 具体如下: (1) 创建 Filter Graph Manager 组件的代码如下: hr=CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_FilterGraph, (void**)&pGraph) 相关接口的查询: IMediaControl *pControl=NULL; Hr=pGraph->QueryInterface(IID_IMediaControl,(void**)&pControl): IVideoWindow* pVW=NULL; Hr=pGraph->QueryInterface(IID_IVideoWindow,(void**)&pVW); (2) 之后,创建 Filter Graph 这是整个程序最关键的部分 创建 Filter Graph 有下面一些方法 IFilterGraph::AddFilter IFilterGraph::ConnectDirect IGraphBuilder::AddSourceFilter IGraphBuilder::Connect IGraphBuilder::RenderFile IGraphBuilder::Render ICaptureGraphBuilder2::RenderStream 一般采用智能连接比较方便,当智能连接不行的时候再自己手动连接 (3) 视频窗口的设置:如果要对视频进行预览的话,那就要设置视频窗口。视频窗口 是由 Renderer Filter 自动创建出来,我们编程的时候,只需要对它时进行设置就行了 IVideoWindow 接口用来专门控制视频窗口,用以下常见的方法: //put_Owner 为视频窗口指定父窗口,通过这个方法,我们可以将视频窗口嵌 到我们自己的窗口,比如一个静态文本控件, SetWindowPosition 设置视频窗口的位置, //put_MessageDrain 这个是比较有用的。对视频窗口而言,我们是不能改写它的窗口处理过程,因为它是

由 DirectShow 自动创建的。我们可以通过这个方法将视频窗口收到的消息挂接到一个我们 自己的窗口,这样就实现了对视频窗口的消息处理了。 //put_Visible 将视频窗口显示出来 //SetNotifyWindow 当 Filter Graph 中发生一些事件时,会向应用程序发送一些消 息,这个方法是指定处理这此消息的的窗口 //put_WindowStyle 设置 Video Window 的样式,比如: long style=0; get_WindowStyle(&style); style&=~WS_CAPTION; put_WindowStyle(style) 这样就可以去掉 Video Window 的标题栏了 ///put_FullScreenMode 设置全屏模式

(4)启动 Filter Graph pGraph->Run(); 3. 常用的 Filter Graph 的构造 构造 Filter Graph,可以 GraphEdit 这个工具来测试 (1) 本地文件播放 最方便的方法:IGraphBuilder::RenderFile(filename); 只要选择播放的文件,IGraphBuilder 就智能地帮你构造出整个 Filter Graph (2) 视频输入设备采集视频的预览 ICaptureGraphBuilder2 * pGB=NULL; Hr=CoCreateInstance (CLSID_CaptureGraphBuilder2,NULL,CLSCTX_INPROC,IID_ICaptureGraphBuilder2,(void**) &pGB); //对于 DV 面言, 它可能有两种视频输出 PIN,一种是 Interleaved Pin, 一种是 Video Pin //前面一种包含音视频流,后面一种只有视频流,所以我们预览的时候,可以先试一 下 Interleaved Pin,如果失败的话,再试一下 Video Pin Hr=pGB->RenderStream(PIN_CATEGORY_PREVIEW,MEDIATYPE_Interleaved, //pCap,NULL,NULL); if(FAILED(hr)) { hr=pGB->RenderStream(PIN_GATEGORY_PREVIEW,MEDIATYPE_Video,pCap,NULL,NULL ); } if(FAILED(hr)) { return hr; }

(3) 采集视频的录制(输出为本地文件) pGB->SetOutputFileName(&MEDIASUBTYPE_Asf, filename, &pMux,&pSink); MEDIASUBTYPE_Asf 指定为保存为 Asf 格式, MEDIASUBTYPE_Avi 保存为 AVI 格式 如是要保存为其它格式,要有相应格式的 Mux Spliiter 或 Filter Writer,然后传入它 的 GUID filename 是保存路径 hr=pGB->RenderStream(&PIN_CATEGORY_CAPTURE , &MEDIATYPE_Interleaved,pCap,NULL,pMux); if(FAILED(hr)) {

hr=pGB->RenderStream(&PIN_CATEGORY_CAPTURE,&MEDIATYPE_Video,pCap,NULL,p Mux); } (4) 模拟电视卡 VBI 信息的渲染 电视卡的 VBI 信息,用以下三种:CC(Closed Caption),WST(World Standard Teletext), 忘记了 CC( Closed Caption) 电视卡的 VBI 信号可能是从 VBI PIN 输出的,也可能是从 CC PIN 输出的,所以 我们要进行判断。 Hr=pGB->RenderStream(&PIN_CATEGORY_VBI,&MEDIATYPE_Video,pCap,NULL,NULL); If(FAILED(hr)) { pGB->RenderStream(&PIN_CATEGORY_CC,&MEDIATYPE_Video,pCap,NULL,NULL); } if(FAILED(hr)) { return hr; } RenderStream 会帮我们创建整个 Filter Graph

RenderStream 不支持 WST 信号的渲染,我们必须手动创建 Filter Graph 四.杂记 (1)电视卡 Crossbar, TV Tuner Crossbar 是用于输入端子的选择。使用的时候,我们只要用 ICrossbar::Route 导通 相应的端子就可以了。有的电视卡有两个 Crossbar。

TV Tuner 控制电视卡接收与频道选择,可以通过接口 IAMTVTuner 进行操作 (2)Stream Buffer Engine 这个可以实现实时流媒体的缓冲。它有两个 Filters,Stream Buffer Source,Stream Buffer Sink Stream Buffer Source 负责流媒体的采集,并写入磁盘,以供给 Stream Buffer Sink 读 取。 对于流媒体,Stream Buffer Source 只支持 DV 和 MPEG-2 两种格式,所以,在输入 Source 时,有时候要进行格式转换。Stream Buffer Source 优先采用的格式是 MPEG-2。 Stream Buffer Sink 负责读取 Source 写入的数据,然后进行回放。 读取的时候,它会先找到 Stream Buffer Source 写入的一个 Stub 文件,里面包含 了指向其它文件的指针。 使用 Stream Buffer Engine 时,我们要构造两个 Filter Graph。 (3) WM Asf Writer 将视频保存为 Asf 文件的时候,要用到 WM Asf Writer,它有一个音频输入端,一个视频输入端,它要求只两个端都要接上,否则构 造出来的 Filter Graph 无法启动。这个要求感觉有点勉强,不知有没有解决方法。 (4) ISampleGrabber 在 创 建 CLSID_SampleGrabber 组 件 的 时 候 , 我 们 不 能 直 接 就 得 到 里 面 的 ISampleGrabber ,而是要先得到 IBaseFilter 接口,然后再用 QueryInterface 得到 ISampleGrabber.感觉 很奇怪。 根据 IUnknown 原则,无论我们先得到那个接口都是可以的啊。

深入浅出 DirectShow Filter

1. Filter 概述 Filter 是一个 COM 组件,由一个或多个 Pin 组成。Pin 也是一个 COM 组件。Filter 文件 的扩展名为.ax,但也可以是.dll。Filter 根据其包含 Input pin 或 Output pin 的情 况(或在 Filter Graph 的位置) ,大致可分为三类:Source Filter(仅有 Output pin)、Transform Filter(同时具有 Input pin 和 Output pin)和 Renderer Filter(仅有 Input pin)。 一般情况下,创建 Filter 使用一个普通的 Win32 DLL 项目。而且,一般 Filter 项目 不使用 MFC。这时,应用程序通过 CoCreateInstance 函数 Filter 实例;Filter 与应 用程序在二进制级别的协作。另外一种方法,也可以在 MFC 的应用程序项目中创建 Filter。这种情况下,Filter 不需注册为 COM 组件,Filter 与应用程序之间的协作 是源代码级别的;创建 Filter 实例,不再使用 CoCreateInstance 函数,而是直接 new 出一个 Filter 对象,如下:

m_pFilterObject = new CFilterClass(); // make the initial refcount 1 to match COM creation m_pFilterObject ->AddRef(); 因为 Filter 的基类实现了对象的引用计数,所以即使在第二种情况下,对创建后的 Filter 对象的操作也完全可以遵循 COM 标准。 Filter 是一个独立功能模块,最好不要将 Filter 依赖于其他第三方的 DLL。因为 Filter 具有 COM 的位置透明性特点,Filter 文件可以放在硬盘的任何位置,只要位 置移动后重新注册。但此时,如果 Filter 依赖其他 DLL,则 Filter 对该 DLL 的定位就 会出现问题。 Filter 不能脱离 Filter Graph 单独使用。所以,如果你想绕过 Filter Graph 直接 使用 Filter 实现的模块功能,请将你的 Filter 移植成 DMO(DirectX Media Object) 。对于 DirectShow 应用程序开发者来说,还有一点,请不要忘记使用 OleInitialize 进行初始化。 2. Filter 的注册 Filter 是 COM 组件,所以在使用前一定要注册。Filter 的注册程序为 regsvr32.exe。如果带上命令行参数/u,表示注销;如果带上是/s,表示不弹出任 何注册/注销成功与否的提示对话框。如果你想在 Build Filter 项目的时候进行自 动注册,请在 VC 的 Project settings 的 Custom Build 页如下设置: Description: Register filter Commands: regsvr32 /s /c $(TargetPath) echo regsvr32 exe.time > $(TargetDir)\$(TargetName).trg Outputs: $(TargetDir)\$(TargetName).trg Filter 的注册信息包括两部分:基本的 COM 信息和 Filter 信息。注册信息都存放在 注册表中。前者的位置为:HKEY_CLASSES_ROOT\CLSID \Filter Clsid\,后者的位 置为:HKEY_CLASSES_ROOT\CLSID\Category\Instance\ Filter Clsid\。COM 信息 标示了 Filter 是一个标准的可以通过 CoCreateInstance 函数创建的 COM 组 件,Filter 信息标示了我们通过 Graphedit 看到的描述这个 Filter 的信息。如果你 不想让 Graphedit 看到(或者让 Filter 枚举器找到)你写的 Filter,你完全可以不 注册 Filter 信息。而且不用担心,你这么做也完全不会影响 Filter 的功能。 屏蔽注册 Filter 信息的方法也很简单。因为 CBaseFilter 实现了 IAMovieSetup 接口 的两个函数:Register 和 Unregister。我们只需重载这两个函数,直接 return S_OK 就行了。 Filter 的 Merit 值。这个值是微软的“智能连接”函数使用的。在 Graphedit 中,当我 们加入一个 Source Filter 后,在它的 pin 上执行“Render” ,会自动连上一些 Filter。Merit 的值参考如下: MERIT_PREFERRED = 0x800000, MERIT_NORMAL = 0x600000, MERIT_UNLIKELY = 0x400000, MERIT_DO_NOT_USE = 0x200000, MERIT_SW_COMPRESSOR = 0x100000,

MERIT_HW_COMPRESSOR = 0x100050 Merit 值只有大于 MERIT_DO_NOT_USE 的时候才有可能被“智能连接”使用;Merit 的值 越大,这个 Filter 的机会就越大。 3. Filter 之间 Pin 的连接过程 Filter 只有加入到 Filter Graph 中并且和其它 Filter 连接成完整的链路后,才会 发挥作用。Filter 之间的连接(也就是 Pin 之间的连接) ,实际上是连接双方的一 个 Media type 的协商过程。连接的方向总是从 Output pin 指向 Input pin。连接的 大致过程为:如果调用连接函数时已经指定了完整的 Media type,则用这个 Media type 进行连接,成功与否都结束连接过程;如果没有指定或不完全指定了 Media type,则进入下面的枚举过程。枚举欲连接的 Input pin 上所有的 Media type,逐 一用这些 Media type 与 Output pin 进行连接(如果连接函数提供了不完全 Media type,则要先将每个枚举出来的 Media type 与它进行匹配检查) ,如果 Output pin 也接受这种 Media type,则 Pin 之间的连接宣告成功;如果所有 Input pin 上枚举的 Media type,Output pin 都不支持,则枚举 Output pin 上的所有 Media type,并逐 一用这些 Media type 与 Input pin 进行连接。如果 Input pin 接受其中的一种 Media type,则 Pin 之间的连接到此也宣告成功;如果 Output pin 上的所有 Media type,Input pin 都不支持,则这两个 Pin 之间的连接过程宣告失败。 每个 Pin 都可以实现 GetMediaType 函数来提供该 Pin 上支持的所有 Preferred Media type(但一般只在 Output pin 上实现,Input pin 主要实现 CheckMediaType 看是否 支持当前提供的 Media type 就行了) 。连接过程中,Pin 上枚举得到的所有 Media type 就是这里提供的。 在 CBasePin 类中有一个 protected 的成员变量 m_bTryMyTypesFirst,默认值为 false。在我们定制 Filter 的 Output pin 中改变这个变量的值为 true,可以定制我 们自己的连接过程(先枚举 Output pin 上的 Media type) 。 当 Pin 之间的连接成功后,各自的 pin 上都会调用 CompleteConnect 函数。我们可以 在这里取得一些连接上的 Media type 的信息,以及进行一些计算等。在 Output pin 的 CompleteConnect 实现中,还有一个重要的任务,就是协商 Filter Graph 运行起 来后 Sample 传输使用的内存配置情况。这同样是一个交互过程:首先要询问一下 Input pin 上的配置要求,如果 Input pin 提供内存管理器(Allocator) ,则优先 使用 Input pin 上的内存管理器;否则,使用 Output pin 自己生成的内存管理器。 我们一般都要实现 DecideBufferSize 来决定存放 Sample 的内存大小。注意:这个过 程协商完成之后,实际的内存并没有分配,而要等到 Output pin 上的 Active 函数调用。 4. Filter Media type 概述 Media type 一般可以有两种表示:AM_MEDIA_TYPE 和 CMediaType。前者是一个 Struct,后者是从这个 Struct 继承过来的类。 每个 Media type 有三部分组成:Major type、Subtype 和 Format type。这三个部分 都使用 GUID 来唯一标示。Major type 主要定性描述一种 Media type,比如指定这是 一个 Video,或 Audio 或 Stream 等;Subtype 进一步细化 Media type,如果 Video 的话 可以进一步指定是 UYVY 或 YUY2 或 RGB24 或 RGB32 等;Format type 用一个 Struct 更进

一步细化 Media type。 如果 Media type 的三个部分都是指定了某个具体的 GUID 值,则称这个 Media type 是 完全指定的;如果 Media type 的三个部分中有任何一个值是 GUID_NULL,则称这个 Media type 是不完全指定的。GUID_NULL 具有通配符的作用。 常用的 Major type: MEDIATYPE_Video; MEDIATYPE_Audio; MEDIATYPE_AnalogVideo; // Analog capture MEDIATYPE_AnalogAudio; MEDIATYPE_Text; MEDIATYPE_Midi; MEDIATYPE_Stream; MEDIATYPE_Interleaved; // DV camcorder MEDIATYPE_MPEG1SystemStream; MEDIATYPE_MPEG2_PACK; MEDIATYPE_MPEG2_PES; MEDIATYPE_DVD_ENCRYPTED_PACK; MEDIATYPE_DVD_NAVIGATION; 常用的 Subtype: MEDIASUBTYPE_YUY2; MEDIASUBTYPE_YVYU; MEDIASUBTYPE_YUYV; MEDIASUBTYPE_UYVY; MEDIASUBTYPE_YVU9; MEDIASUBTYPE_Y411; MEDIASUBTYPE_RGB4; MEDIASUBTYPE_RGB8; MEDIASUBTYPE_RGB565; MEDIASUBTYPE_RGB555; MEDIASUBTYPE_RGB24; MEDIASUBTYPE_RGB32; MEDIASUBTYPE_ARGB32; // Contains alpha value MEDIASUBTYPE_Overlay; MEDIASUBTYPE_MPEG1Packet; MEDIASUBTYPE_MPEG1Payload; // Video payload MEDIASUBTYPE_MPEG1AudioPayload; // Audio payload MEDIASUBTYPE_MPEG1System; // A/V payload MEDIASUBTYPE_MPEG1VideoCD; MEDIASUBTYPE_MPEG1Video; MEDIASUBTYPE_MPEG1Audio; MEDIASUBTYPE_Avi; MEDIASUBTYPE_Asf;

MEDIASUBTYPE_QTMovie; MEDIASUBTYPE_PCM; MEDIASUBTYPE_WAVE; MEDIASUBTYPE_dvsd; // DV MEDIASUBTYPE_dvhd; MEDIASUBTYPE_dvsl; MEDIASUBTYPE_MPEG2_VIDEO; MEDIASUBTYPE_MPEG2_PROGRAM; MEDIASUBTYPE_MPEG2_TRANSPORT; MEDIASUBTYPE_MPEG2_AUDIO; MEDIASUBTYPE_DOLBY_AC3; MEDIASUBTYPE_DVD_SUBPICTURE; MEDIASUBTYPE_DVD_LPCM_AUDIO; MEDIASUBTYPE_DVD_NAVIGATION_PCI; MEDIASUBTYPE_DVD_NAVIGATION_DSI; MEDIASUBTYPE_DVD_NAVIGATION_PROVIDER; 常用的 Format type: FORMAT_None FORMAT_DvInfo FORMAT_MPEGVideo FORMAT_MPEG2Video FORMAT_VideoInfo FORMAT_VideoInfo2 FORMAT_WaveFormatEx

DVINFO MPEG1VIDEOINFO MPEG2VIDEOINFO VIDEOINFOHEADER VIDEOINFOHEADER2 WAVEFORMATEX

5. Filter 之间的数据传送 Filter 之间的数据是通过 Sample 来传送的。Sample 是一个 COM 组件,拥有自己的一 段数据缓冲。Sample 由 Allocator 统一管理。如下图所示:

Filter 之间数据传送的方式有两种:Push 模式和 Pull 模式。 所谓 Push 模式,即 Source filter 自己能够产生数据,并且一般在它的 Output pin 上有独立的子线程负责将数据发送出去,常见的情况如 WDM 模型的采集卡的 Live Source Filter;而所谓 Pull 模式,即 Source filter 不具有把自己的数据送出去的 能力,这种情况下,一般 Source filter 后紧跟着接一个 Parser Filter 或 Splitter Filter,这种 Filter 一般在 Input pin 上有个独立的子线程,负责不断地从 Source filter 索取数据,然后经过处理后将数据传送下去,常见的情况如 File source。 Push 模式下,Source filter 是主动的;Pull 模式下,Source filter 是被动的。而 事实上,如果将上图 Pull 模式中的 Source filter 和 Splitter Filter 看成另一个虚 拟的 Source filter,则后面的 Filter 之间的数据传送也与 Push 模式完全相同。 那么,数据到底是怎么通过连接着的 Pin 传送的呢?首先来看 Push 模式。在 Source filter 后面 Filter 的 Input pin 上,一定实现了一个 IMemInputPin 接口,数据正是

通过上一级 Filter 调用这个接口的 Receive 方法进行传送的。值得注意的是,数据 从 Output pin 通过 Receive 方法调用传送到 Input pin 上,并没有进行内存拷贝,它 只是一个相当于数据到达的“通知” 。再看一下 Pull 模式。Pull 模式下的 Source filter 的 Output pin 上,一定实现了一个 IAsyncReader 接口;其后面的 Splitter Filter,就是通过调用这个接口的 Request 方法或者 SyncRead 方法来获得数据。 Splitter Filter 然后像 Push 模式一样,调用下一级 Filter 的 Input pin 上的 IMemInputPin 接口 Receive 方法实现数据的往下传送。 一个 DirectShow 的应用程序,至少会有两条线程:主线程和 Filter 用于数据传送的 子线程。既然是多线程,就不可避免会出现线程同步问题。Filter 的状态改变都在 主线程中完成,Filter 的数据相关操作都在数据线程中调用。各线程一些主要函数 调用参考如下: Streaming thread(s): IMemInputPin::Receive, IMemInputPin::ReceiveMultiple, IPin::EndOfStream, IMemAllocator::GetBuffer. Application thread: IMediaFilter:ause, IMediaFilter::Run, IMediaFilter:top, IMediaSeeking:etPositions, IPin::BeginFlush, IPin::EndFlush. Either: IPin::NewSegment. 这些函数切忌混合调用,否则会引起线程的死锁。另外值得注意的是,BeginFlush 和 EndFlush 属于主线程调用,而不是数据线程调用。 6. Transform filter 和 Trans-in-place filter 的区别 首先,这两种 Filter 是有共同点的,因为 Trans-in-place filter 本身就是从 Transform filter 中继承过来的。其次,我们要明白的是,Trans-in-place filter“尽力”使自己的 Input pin 和 Output pin 使用相同的 Allocator,以免去一次 Sample 数据的 memcpy。我们说“尽力” ,就是说 Trans-in-place filter 也未必能够 实现它的初衷。 (如果 Trans-in-place filter 使用的 Allocator 是 ReadOnly 的,而 Trans-in-place filter 又要修改 Sample 的数据,则 Trans-in-place filter 的 Input pin 和 Output pin 将不得不使用不同的 Allocator。 ) Trans-in-place filter 有一个 protected 的成员变量 m_bModifiesData,默认值为 true。如果你确信定制 Trans-in-place filter 不需要修改 Sample 数据,则将 m_bModifiesData 赋值为 false,这样可以保证 Input pin 和 Output pin 使用相同的 Allocator。 Trans-in-place filter 的实现主要体现在以下三个函 数:CTransInPlaceFilter::CompleteConnect、 CTransInPlaceInputPin::GetAllocator 和 CTransInPlaceInputPin:: NotifyAllocator。CompleteConnect 中进行必要的重连(Reconnect) ,保证 Trans-in-place filter 的 Input pin 和 Output pin 使用相同的 Media type。 GetAllocator 能够取得 Trans-in-place filter 下一级 Filter 的 Input pin 上的 Allocator。NotifyAllocator“尽力”使 Trans-in-place filter 的 Input pin 和 Output pin 使用同一个 Allocator。 7. IMediaSeeking 的实现

IMediaSeeking 的实现在 Filter 上,但应用程序应该从 Filter Graph Manager 上得 到这个接口。在 Filter 级别,Filter Graph Manager 首先从 Renderer filter 开始 询问上一级 Filter 的 Output pin 是否支持 IMediaSeeking 接口。如果支持,则返回 这个接口;如果不支持,则继续往上一级 Filter 询问,直到 Source filter。一般 在 Source filter 的 Output pin 上实现 IMediaSeeking 接口。 (如果是 File source,一般在 Parser Filter 或 Splitter Filter 实现这个接口。 )对于 Filter 开 发者来说,如果我们写的是 Source filter,就要在 Filter 的 Output pin 上实现 IMediaSeeking 接口;如果写的是 Transform filter,只需要在 Output pin 上将用 户的接口请求往上传递给上一级 Filter 的 Output pin;如果写的是 Renderer Filter,需要在 Filter 上将用户的接口请求往上传递给上一级 Filter 的 Output pin。 注意:为了保证 Seek 操作后 Stream 的同步性,如果实际实现 IMediaSeeking 接口的 Filter 有多个 Output pin,一般仅有一个 pin 支持 Seek 操作。对于你定制的 Transform filter,如果有多个 Input pin,你需要自己决定当 Output pin 接收到 IMediaSeeking 接口请求时选择哪一条路径往上继续请求。 应用程序能够在任何时候(running, paused or stopped)对 Filter graph 执行 Seek 操作。但当 Filter graph 正在 running 的时候,Filter graph manager 会先 pause 住,执行完 Seek 操作后,再重新 run 起来。 IMediaSeeking 可以有如下几种 Seek 的时间格式: TIME_FORMAT_FRAME Video frames. TIME_FORMAT_SAMPLE Samples in the stream. TIME_FORMAT_FIELD Interlaced video fields. TIME_FORMAT_BYTE Byte offset within the stream. TIME_FORMAT_MEDIA_TIME Reference time (100-nanosecond units). 但实现这个接口的 Filter 未必支持所有的这些格式。一般 Filter 都会支持 TIME_FORMAT_MEDIA_ TIME,当使用其它的格式时,最好调用 IMediaSeeking::IsFormatSupported 进行一下确认。 对于 Filter,不赞成使用 IMediaPosition 接口。IMediaPosition 是用以支持 Automation 的(比如 VB 里面使用 DirectShow) ,IMediaSeeking 不支持 Automation。 8. Filter 的状态转换 Filter 有三种状态:stopped, paused, running。paused 是一种中间状 态,stopped 状态到 running 状态必定经过 paused 状态。paused 可以理解为数据就绪 状态,是为了快速切换到 running 状态而设计的。在 paused 状态下,数据线程是启 动的,但被 Renderer filter 阻塞了。 paused 与 running 两者间的状态转换,对于 Source filter 和 Transform filter 可 以忽略不计,而对于 Renderer filter(特别是 Video renderer / Audio renderer)情形稍有不同。Renderer 首先处理那个 paused 状态下 Hold 的 Sample,当 接收到新的 Sample 时,判断 Sample 上的时间戳。如果时间未到,Renderer 会 Hold 住这个 Sample 进行等待。

Filter graph manager 以从下到上的顺序对 Filter 进行状态转换,即从 Renderer filter 一直回溯到 Source filter。这个顺序能够有效地避免 Sample 的丢失以及 Filter graph 的死锁。 Stopped to paused:首先从 Renderer 开始进行 paused 状态的转换。这时,Filter 调 用自己所有 Pin 的 Active 函数进行初始化(一般 Pin 在 Active 中进行 Sample 内存的 分配,如果是 Source filter 还将启动数据线程) ,使 Filter 处于一种就绪状态。 Source filter 是最后一个完成到就绪状态转换的 Filter。然后,Source filter 启 动数据线程,往下发送 Sample。当 Renderer 接收到第一个 Sample 后就阻塞住。当所 有的 Renderer 实现了状态转换, Filter graph manager 才认为状态转换完成。 Paused to stopped:当 Filter 进入 stopped 状态时,调用自己所有 Pin 的 Inactive 函 数(一般 Pin 在 Inactive 中进行 Sample 内存的释放,如果是 Source filter 还将终 止数据线程) 。释放所有 Hold 的 Sample,以使上一级 Filter 的 GetBuffer 脱离阻 塞;终止所有在 Receive 中的等待,以使上一级 Filter 的 Receive 函数调用返回。 Filter 在 stopped 状态下拒绝接受任何 Sample。这样从 Renderer filter 往上一级一 级脱离阻塞,当到达 Source filter 的时候,可以确保数据线程终止。 9. EndOfStream 问题 当 Source filter 的所有数据都已经发送出去,则会调用下一级 Filter 的 Input pin 上的 IPin::EndOfStream,直到 Renderer filter。当这个 Renderer filter 的所有 Input pin 都被调用了 EndOfStream,则向 Filter graph manager 发送一个 EC_COMPLETE 事件。仅当 Filter graph 中的所有 Stream 都发送了 EC_COMPLETE 事 件,Filter graph manager 才会将这个事件发送给应用程序。 在我们定制的 Filter 中,如果接收到了上一级 Filter 传过来的 EndOfStream,则说 明上面的数据已经全部传送完毕,Receive 方法不须再接收数据。如果我们对数据 进行了缓冲,则应确认缓冲中的所有数据都被处理完并往下发送了,然后再往下调 用 EndOfStream。Pull 模式下,一般是 Splitter filter 或 Parser filter 发送 EndOfStream,而且方向是往下的,Source filter 上不会收到这样的通知。 10. BeginFlush、EndFlush、NewSegment 问题 典型的情况,当进行 MediaSeeking 之后,会调用 BeginFlush、EndFlush。一般在 Input pin 上实现这两个函数。 对于 Filter 开发者来说,Filter 在被调用 BeginFlush 时需要做以下工作: · 调用下一级 Filter 的 BeginFlush,使其不再接收新的 Sample; · 拒绝接收上一级 Filter 的数据,包括 Receive 调用和 EndOfStream 调用; · 如果上一级 Filter 正在阻塞等待空的 Sample,此时需要让它脱离阻塞(通过 析构 Allocator) ; · 确保数据流线程脱离阻塞状态。 Filter 在被调用 EndFlush 时需要做以下工作: · 确保所有等待缓存的 Sample 被丢弃; · 确保 Filter 上已经缓存的数据被丢弃; · 清除没有发出去的 EC_COMPLETE 事件(如果这是一个 Rendered input pin) ; · 调用下一级 Filter 的 EndFlush。

还有一点:如果你必须在定制的 Filter 中为每个 Sample 打 Time stamp,那么记住在 MediaSeeking 之后出去的 Sample 的 Time stamp 应该从 0 开始重打。 Segment 是一段时间内具有相同的 Playback rate 的一组 Sample,以 NewSegment 函 数调用来表示这个 Segment 的开始。NewSegment 一般在开始新的 Stream 的时候,或 者用户进行了 MediaSeeking 之后,由 Source filter(Push 模式下)或 Parser/Splitter filter(Pull 模式下)发起,并往下层层调用,一直到 Renderer filter。 在我们定制的 Filter 中可以利用 NewSegment 传递下来的信息,特别是对于 Decoder。对于 Audio renderer 也是一个典型例子,它根据 Playback rate 和 Audio 实际的采样频率来对声卡产生输出。 11. Quality Control 问题 Filter 之间的数据传送,有时候过快,有时候过慢。DirectShow 使用 Quality Control 来解决这个问题,即 IQualityControl 接口的两个函数(SetSink 和 Notify) 。一般,Renderer filter 在 Filter 上实现这个接口,而其他 Filter 在 Output pin 上实现这个接口。 上图为一般的 Quality Control 的处理过程。而能够调整发送速度的 IQualityControl 接口一般在 Source filter(pull 模式下为 parser/splitter filter)上实现,Transform filter 只是将 Quality Message 往上一级 Filter 传递。 应用程序可以实现自己的 Quality Control Manager,然后通过调用 SetSink 方法设 置给 Filter。上述的处理过程就改变了,Quality Message 直接发送给自定义的 Manager。但一般并不这么做。值得注意的是,具体的 Quality Control 实现取决于 实际的 Filter,可能是调整发送速度,也可能会丢失部分数据。所以,请慎重使用 Quality Message。 12. 对运行过程中 Media type 改变的支持 我们可以从 CBaseInputPin::Receive 中可以看到,Input pin 每次在接收 Sample 的 之前,一般都会进行 CheckStreaming(如果当前 Filter 已经 Stop 或正在 Flush 或发 生了 RuntimeError,则拒绝接收 Sample) ,然后将当前的 Sample 属性保存到 protected 的 m_SampleProps 成员变量中。描述 Sample 属性的是一个 AM_SAMPLE2_PROPERTIES 的结构,它有一个标记来表明当前 Sample 的 Media type 是 否已经改变。如果 Media type 改变了,则进行 CheckMedaiType 看我们的 Filter 是否 仍然支持它。如果不支持,则发出一个 RuntimeError,并发送 EndOfStream。 一个健全的 Filter 应该能够对运行时 Media type 的改变做出处理。在我们的 Receive 方法实现中,我们可以通过 if (pProps->dwSampleFlags & AM_SAMPLE_TYPECHANGED)来判断 Meida type 是否已经改变;如果改变,我们需要根 据新的 Media type 进行必要的初始化。 一个典型的案例:当 Camcorder 输入时,Audio 的 Media type 可能改变。比 如,Filter 连接时 Media type 使用了 MEDIATYPE_PCM,而在运行时又换成了 MEDIATYPE_WAVE;或者连接时 Audio 的采样频率时 44.1K,而在运行时却变成了 48K;或者 Camcorder 的带子上本身保存了混合的 44.1K 和 48K 的 Audio。

你可能感兴趣的:(DirectShow/D3D)