1、DirectShow 实践经验杂谈1.当向 Filter Graph 中加入同一个 Filter 的多个实例时,使用Intelligent connect,优先使用最晚加入 Filter Graph 中的那个Filter 实例。2.使用 IGraphConfig 接口可以将 Filter 加入 Cache,以在Intelligent connect 时,提高该 Filter 的连接优先级。如果要加入 Cache 的 Filter 已在 Graph 中,确信它的所有 Pin 处于断开状态,而且调用 IGraphConfig:AddFilterToCache 之后,Graph 中的Filter 实
2、例会自动 Remove 掉;当 Intelligent connect 之后,使用了 Cache 中的某个 Filter 实例,则这个 Filter 实例会被自动加入到 Graph,而 Cache 中这个 Filter 实例也会被 Remove 掉。3.在调试 Filter 时,每次进入 CheckMediaType 的 Media type,我们在 VC 中只能看到一串 UUID 数字,很麻烦!下面的代码能将Media type 的描述信息 Dump 到 VC 的 Output window:void DisplayMediaType(TCHAR *pDescription,const CM
3、ediaType *pmt)/ Dump the GUID types and a short descriptionDbgLog(LOG_TRACE,0,TEXT(“);DbgLog(LOG_TRACE,0,TEXT(“%s“),pDescription);DbgLog(LOG_TRACE,0,TEXT(“);DbgLog(LOG_TRACE,0,TEXT(“Media Type Description“);DbgLog(LOG_TRACE,0,TEXT(“Major type: %s“),GuidNames*pmt-Type();DbgLog(LOG_TRACE,0,TEXT(“Subty
4、pe: %s“),GuidNames*pmt-Subtype();DbgLog(LOG_TRACE,0,TEXT(“Subtype description: %s“),GetSubtypeName(pmt-Subtype();DbgLog(LOG_TRACE,0,TEXT(“Format size: %d“),pmt-cbFormat);/ Dump the generic media typesDbgLog(LOG_TRACE,0,TEXT(“Fixed size sample %d“),pmt-IsFixedSize();DbgLog(LOG_TRACE,0,TEXT(“Temporal
5、compression %d“),pmt-IsTemporalCompressed();DbgLog(LOG_TRACE,0,TEXT(“Sample size %d“),pmt-GetSampleSize();4.调试 Filter 时,想要知道程序是否进入某个函数,可以使用如下的宏定义:#define DbgFunc(a) DbgLog( LOG_TRACE , 0 , TEXT(“CFltTracer(Instance %d):%s“) , mThisInstance , TEXT(a) );使用方法为:CFltTracer:CFltTracer()/ Other cleaning wo
6、rk.DbgFunc(“CFltTracer“);5.如下的代码,可以将 DirectShow 的错误码以文本的方式显示出来:void ShowError(HRESULT hr)if (FAILED(hr)TCHAR szErrMAX_ERROR_TEXT_LEN;DWORD res = AMGetErrorText(hr, szErr, MAX_ERROR_TEXT_LEN);if (res = 0)wsprintf(szErr, “Unknown Error: 0x%2x“, hr);MessageBox(0, szErr, TEXT(“Error!“), MB_OK | MB_ICONE
7、RROR);6.0 进行 DV camcorder 编程时要注意,当 Filter Graph 在运行,而将 Camcorder 置于 Paused 状态,“Microsoft DV Camera and VCR“仍然会不停地将 Paused 时刻的那一帧不断地发送出来(因为DirectShow 主要是为 Playback 设计的,所以这一点对于压缩、合成、写文件的应用非常头疼) ,送出的 Sample 时间戳线性递增;而将 Camcorder 置于 Stopped 状态,“Microsoft DV Camera and VCR“也就不再送出数据。而 Filter Graph 上的 Pause
8、、Stop 操作并不会影响到 Camcorder 本身的状态。6.1 Camcorder 机器 Paused 状态持续大约三分钟(测试机为 JVC)后,机器的 Preview 画面会变成蓝屏。此时,即使 Filter Graph 在运行状态,“Microsoft DV Camera and VCR“也停止输出数据。7.DirectShow 加入 Device Filter 一般都要靠枚举。Device 的名字一般是在枚举的时候,通过 IPropertyBag:Read(L“FriendlyName“, 而另一种方法(必须在 Windows Me 或 XP 下) ,通过读取“Descriptio
9、n“属性,可以更详细地得到Camcorder 的生产厂商名字。参考代码如下:VARIANT varName;VariantInit(hr = pPropBag-Read(L“Description“, if (FAILED(hr)hr = pPropBag-Read(L“FriendlyName“, 8.Playback 快进(慢进)的支持,可以通过IMediaSeeking:SetRate(或 IMediaPosition:put_Rate)来实现。参数为 1.0 表示正常速度,2.0 为双倍速度,0.5 为半速;理论上参数为负数时能够实现倒放,但绝大部分 Filter 不支持倒放。对于Ra
10、te 改变的响应,主要工作在负责打时间戳的 Filter 上,有可能是 Parser Filter(如 AVI Splitter),也有可能是 Push 模式下的Source Filter;这些 Filter 需要根据新的 Rate 重新打好时间戳。一般 Decoder Filter 不用对 Rate 改变做出响应,它们收到NewSegment 后只要往下传递就行了,但对于一些可能需要改变时间戳的 Decoder,则也需要考虑这个 Rate 的改变。Video Renderer 一般也不需要关心 Rate 的改变,因为进来的 Sample 已经是按照新的Rate 打好时间戳了;Audio Re
11、nderer 需要对 Rate 进行更新,因为一般 Audio Decoder 不会因为 Rate 的改变而作相应的转换。还有一点,SetRate 的调用之前,Filter Graph Manager 会先将 Filter Graph 停止;调用之后,新的 Sample 时间戳从 0 开始。Playback Rate 的改变会导致 Audio 很难听,所以此时最好将 Audio 静音处理。9.Playback 的单帧控制,可以通过 Filter Graph Manager 上获得的 IVideoFrameStep 接口来实现。Filter Graph Manager 也主要是协同 Video
12、Renderer 或者 Overlay Mixer 来实现单帧播放的。IVideoFrameStep 接口方法不仅支持单帧跳进,也支持多帧的跳进。在使用接口方法时,最好先调用 IVideoFrameStep:CanStep 判断是否支持你指定的跳帧操作。调用 IVideoFrameStep:Step 实现跳进,其中第二个参数为实际实现跳帧功能的 Filter,为 NULL 时Filter Graph Manager 默认使用 Video Renderer。当 Step 函数调用成功,Filter Graph Manager 会向应用程序发送一个EC_STEP_COMPLETE 事件,Filte
13、r Graph 也自动转入 Paused 状态。10.Mpeg1 文件的播放可以直接使用微软的 MPEG-1 Stream Splitter、MPEG Video Decoder 和 MPEG Audio Decoder。Video Decoder 可以通过 IMpegVideoDecoder 接口(在 mpgcodec.h 中定义)进行参数修改,比如可以通过 set_GreyScaleOutput 设置输出图像是彩色的还是黑白的等;Audio Decoder 可以通过IMpegAudioDecoder 接口(在 mpegtype.h 中定义)进行参数设置,比如可以控制解码输出 mono 或是
14、 stereo,左右声道切换(注意:在解码输出 mono 的时候,不支持动态切换左右声道)等。11.AVI Splitter, Mpeg1 Stream splitter, Mpeg2 splitter 只能工作在 Pull 模式;Mpeg2 Demultiplexer 在非 Window XP 下工作在Push 模式,而在 Window XP 下也能工作在 Pull 模式。所以,网络客户端接收压缩数据要进行解码时,一般将接收器 Source Filter写成 Pull 模式(因为现成的 Parser Filter 大都只能工作在 Pull模式) 。如果要将接收器写成 Push 模式,则这个
15、Source Filter 送出来的数据应该是已经做过 Parse 处理的(即 Video 和 Audio 已分离) 。12.ACM(Audio Compression Manager)和 VCM(Video Compression Manager)在 DirectShow 中都是通过包装 Filter 应用的。ACM Wrapper Filter,作为解压用时(主要进行 Audio 格式的转换,输出 PCM 数据) ,注册在“DirectShow Filters“目录下,Merit 值为 MERIT_NORMAL;作为压缩用时,各个 Audio 压缩器注册在“Audio Compressor
16、s“目录(CLSID_AudioCompressorCategory)下,Merit 值为 MERIT_DO_NOT_USE。注意:这里的 Audio Compressor 不能通过 CoCreateInstance 创建,而只能通过系统枚举。VCM 包装 Filter 作为解压用时,即为 AVI Decompressor Filter,一般实现 Video 从 YUV 到 RGB 格式的转换(注意:MPEG 数据的压缩/解码都不是通过 VCM 包装 Filter 实现的) ;作为压缩用时,各压缩器注册在“Video Compressors“目录(CLSID_VideoCompressorCa
17、tegory)下,Merit 值为MERIT_DO_NOT_USE。注意:这里的 Video Compressor 也不能直接通过 CoCreateInstance 创建。13.微软提供了两个 Tee Filter:Smart Tee 和 Infinite Pin Tee Filter。前者有两个 Output pin,且 Preview pin 输出的 Sample 已经去掉时间戳;后者,可以动态产生无数个 Output pin,而且各个Output pin 输出的 Sample 是完全一样的。14.在 Filtr Graph 运行状态下,可以动态加入新的 Filter,但不能删掉 Filt
18、er,也不能断开 Filter 之间的 Pin 连接。Filter 从Filter Graph 中删除必须在 Stopped 状态,删除前将这个 Filter的 Pin 断开不是必要的!(连接着的 Pin 可以通过 IFilterGraph:Disconnect 断开,并且连接两头的 Pin 都要调用一次!)15.AVI Mux 问题。四种工作模式(通过 Filter 上的IConfigInterleaving:put_Mode 设置):INTERLEAVE_NONE,对输入的数据不缓存,各帧数据按照它们到达 Mux 的顺序直接写入文件中,速度最快;INTERLEAVE_FULL,对 Vide
19、o 和 Audio 的交叉打包精确控制,每次合成都要阻塞等待等量的 Video、Audio 数据,适合文件源的视频编辑等应用;INTERLEAVE_CAPTURE,处于前两种模式之间,尽量不使用数据缓存,在一个 Pin 上数据到达后等待另一个Pin 数据到达时,可能会丢帧,适合 Live Source Capture 应用,注意此时 Audio 的 Capture buffer 应该小于 0.5 秒;INTERLEAVE_NONE_BUFFERED,仅在 Win XP 下用,类似于INTERLEAVE_NONE,只是这种模式生成的文件较小。还有另外一个接口方法 IConfigInterleav
20、ing:put_Interleaving,设置打包频率和 Audio 的预留(Preroll) ,推荐打包频率为 1 秒钟一次,Audio预留 750ms。16.AVI 1.0 文件大小有上限 1GB,AVI 2.0 文件没有这个限制。AVI Mux 默认总是生成 2.0 的文件(1.0 的文件主要是老的 VFW 生成的),也可以通过 AVI Mux 上的IConfigAviMux:SetOutputCompatibilityIndex 来设置生成 1.0 的文件,以保持向后兼容。另一个接口方法IConfigAviMux:SetMasterStream,用以同步多个输入流,特别是在不同源的 v
21、ideo、Audio Capture 时,两个源的 Capture rate 可能有细微的差别。设置了 Master stream 之后,Mux 会修改 AVI 文件的 Playback rates(AVIStreamHeader 结构的 dwScale 和 dwRate两个成员变量) 。推荐将 Audio stream 设为 Master。17.在同一个 Filter Graph 中,既有 Live Source(比如Camcorder,capture devices,mpeg2 demux 等,他们自带 Reference Clock) ,又有 Default DirectSound De
22、vice(声卡带有时钟) ,默认情况下,Filter Graph Manager 选择 Live Source 的参考时钟。有时候会出现 Live Source 数据流传输了几十个 Sample 后会不在传送 Sample 出来。可能的原因是,有多个参考时钟时回放速率匹配问题。解决的方法是,选择声卡的时钟,或者设置整个 Filter Graph不使用时钟。18.0 目前(DirectX8.1),微软并没有提供.dv 文件的“拉”Filter。如果要使用 DV 文件,一个变通的方法是,将 DV 数据保存到 AVI 文件中。播放这种文件,AVI Splitter 会识别出 DV 数据,并送 DV
23、Splitter(微软提供,Push 模式)和 DV Video Decoder(微软提供)进行解码。18.1 在回放含有 DV 的 AVI 文件时,我们发现,真正实现IMediaSeeking 的是 AVI Splitter,而不是 DV Splitter(微软提供)。DV Splitter 的 Audio output pin 或者 Video Output pin 均将IMediaSeeking 的请求通过其 Input pin 传递到 Up stream 去了。18.2 如果自己写一个“DV Parser Filter“,需要注意的是,从Filter Source 中拉数据,每次拉的数
24、据大小一般是按 512 字节对齐的,所以不会是每次拉一个 DV 帧(PAL 为 144000 字节,NTSC 为120000 字节) 。而微软的 DV Splitter 输入的 Sample 要求是一帧一帧的数据(注意:如果每次送的数据太多了,会导致无法正常解码;如果送少了,导致后续 Filter 没有足够的数据进行解码,会发生Filter Graph 的状态转换出错,即可能无法转入 Paused 状态) ,所以我们必须在 DV Parser 上对拉出的数据进行必要的缓存。而且,在处理 Seek 时,新的 Seeking 位置也要考虑“字节对齐”的问题。其实,为 DV 文件写一个 Source
25、 Filter,直接将 DV 数据从文件中读出后 push 出去,问题就要简单一点!19.Tee Filter 问题。微软提供的 Smart Tee(没有源码)与Infinite Pin Tee Filter(有源码)相比,后者的性能要好一点。两者的区别是,前者将 Preview pin 出来的 Sample 进行了“去时间戳”处理,而后者只是简单地将一个 Sample 分别在各个 output pin 上输出。20.写多进一出,或者是一进多出的 Filter,基类可以选择CTransformFilter 或者 CTransInPlaceFilter,虽然它们在BaseClasses 中的实现
26、都是一进一出的。一般的做法为,保持原有的 Input pin 和 Output pin 为“主流”的链路,以这个标准 Input pin 的数据“流入” ,来驱动协调其他 Pin 数据处理后,再输入。需要注意的是,必须重载基类 Filter 的 GetPinCount、GetPin 实现,以增加新的 Pin;重载 FindPin 支持新加 pin;重载 Filter 的Pause 或 Stop 实现,因为可能还要同步新增加的 pin 的数据流(如果新增加的 pin 在 Filter 要转入 Paused 状态时还处于死循环或阻塞,则可能导致 Filter 状态转换失败,出现 Frozen 现象) ;新增加Input pin 一般从 CBaseInputPin 上继承,Output pin 从CBaseOutputPin 上继承,而且一般都要重载以下几个函数的实现:CheckMediaType、Receive、 EndOfStream、BeginFlush、EndFlush。