消防感动记录:[转]DirectShow实践经验杂谈

来源:百度文库 编辑:偶看新闻 时间:2024/06/11 18:58:57

[转]DirectShow实践经验杂谈

1.当向Filter Graph中加入同一个Filter的多个实例时,使用Intelligent connect,优先使用最晚加入Filter Graph中的那个Filter实例。


2.使用IGraphConfig接口可以将Filter加入Cache,以在Intelligentconnect时,提高该Filter的连接优先级。如果要加入Cache的Filter已在Graph中,确信它的所有Pin处于断开状态,而且调用IGraphConfig::AddFilterToCache之后,Graph中的Filter实例会自动Remove掉;当Intelligentconnect之后,使用了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 CMediaType *pmt)
{
// Dump the GUID types and a short description
DbgLog((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("Subtype: %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 types
DbgLog((LOG_TRACE,0,TEXT("Fixed size sample %d"),pmt->IsFixedSize()));
DbgLog((LOG_TRACE,0,TEXT("Temporal 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 work...
    DbgFunc("~CFltTracer");
}


5.如下的代码,可以将DirectShow的错误码以文本的方式显示出来:
void ShowError(HRESULT hr)
{
    if (FAILED(hr))
    {
        TCHAR szErr[MAX_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_ICONERROR);
    }
}


6.0进行DV camcorder编程时要注意,当FilterGraph在运行,而将Camcorder置于Paused状态,"Microsoft DV Camera andVCR"仍然会不停地将Paused时刻的那一帧不断地发送出来(因为DirectShow主要是为Playback设计的,所以这一点对于压缩、合成、写文件的应用非常头疼),送出的Sample时间戳线性递增;而将Camcorder置于Stopped状态,"Microsoft DVCamera and VCR"也就不再送出数据。而Filter Graph上的Pause、Stop操作并不会影响到Camcorder本身的状态。

6.1 Camcorder机器Paused状态持续大约三分钟(测试机为JVC)后,机器的Preview画面会变成蓝屏。此时,即使Filter Graph在运行状态,"Microsoft DV Camera and VCR"也停止输出数据。


7.DirectShow加入DeviceFilter一般都要靠枚举。Device的名字一般是在枚举的时候,通过IPropertyBag::Read(L"FriendlyName",&varName, 0)获得。对于DV camcorder,这种方式得到的名字为"Microsoft DV Camera andVCR";而另一种方法(必须在WindowsMe或XP下),通过读取"Description"属性,可以更详细地得到Camcorder的生产厂商名字。参考代码如下:
VARIANT varName;
VariantInit(&varName);
hr = pPropBag->Read(L"Description", &varName, 0);
if (FAILED(hr))
{
    hr = pPropBag->Read(L"FriendlyName", &varName, 0);
}   


8.Playback快进(慢进)的支持,可以通过IMediaSeeking::SetRate(或IMediaPosition::put_Rate)来实现。参数为1.0表示正常速度,2.0为双倍速度,0.5为半速;理论上参数为负数时能够实现倒放,但绝大部分Filter不支持倒放。对于Rate改变的响应,主要工作在负责打时间戳的Filter上,有可能是ParserFilter(如AVI Splitter),也有可能是Push模式下的SourceFilter;这些Filter需要根据新的Rate重新打好时间戳。一般DecoderFilter不用对Rate改变做出响应,它们收到NewSegment后只要往下传递就行了,但对于一些可能需要改变时间戳的Decoder,则也需要考虑这个Rate的改变。VideoRenderer一般也不需要关心Rate的改变,因为进来的Sample已经是按照新的Rate打好时间戳了;AudioRenderer需要对Rate进行更新,因为一般AudioDecoder不会因为Rate的改变而作相应的转换。还有一点,SetRate的调用之前,Filter GraphManager会先将Filter Graph停止;调用之后,新的Sample时间戳从0开始。PlaybackRate的改变会导致Audio很难听,所以此时最好将Audio静音处理。


9.Playback的单帧控制,可以通过Filter Graph Manager上获得的IVideoFrameStep接口来实现。FilterGraph Manager也主要是协同Video Renderer或者OverlayMixer来实现单帧播放的。IVideoFrameStep接口方法不仅支持单帧跳进,也支持多帧的跳进。在使用接口方法时,最好先调用IVideoFrameStep::CanStep判断是否支持你指定的跳帧操作。调用IVideoFrameStep::Step实现跳进,其中第二个参数为实际实现跳帧功能的Filter,为NULL时Filter Graph Manager默认使用VideoRenderer。当Step函数调用成功,Filter GraphManager会向应用程序发送一个EC_STEP_COMPLETE事件,Filter Graph也自动转入Paused状态。


10.Mpeg1文件的播放可以直接使用微软的MPEG-1 Stream Splitter、MPEG Video Decoder和MPEGAudio Decoder。VideoDecoder可以通过IMpegVideoDecoder接口(在mpgcodec.h中定义)进行参数修改,比如可以通过set_GreyScaleOutput设置输出图像是彩色的还是黑白的等;AudioDecoder可以通过IMpegAudioDecoder接口(在mpegtype.h中定义)进行参数设置,比如可以控制解码输出mono或是stereo,左右声道切换(注意:在解码输出mono的时候,不支持动态切换左右声道)等。


11.AVI Splitter, Mpeg1 Stream splitter, Mpeg2 splitter只能工作在Pull模式;Mpeg2Demultiplexer在非Window XP下工作在Push模式,而在WindowXP下也能工作在Pull模式。所以,网络客户端接收压缩数据要进行解码时,一般将接收器SourceFilter写成Pull模式(因为现成的ParserFilter大都只能工作在Pull模式)。如果要将接收器写成Push模式,则这个SourceFilter送出来的数据应该是已经做过Parse处理的(即Video和Audio已分离)。


12.ACM(Audio Compression Manager)和VCM(Video CompressionManager)在DirectShow中都是通过包装Filter应用的。ACM WrapperFilter,作为解压用时(主要进行Audio格式的转换,输出PCM数据),注册在"DirectShowFilters"目录下,Merit值为MERIT_NORMAL;作为压缩用时,各个Audio压缩器注册在"AudioCompressors"目录(CLSID_AudioCompressorCategory)下,Merit值为MERIT_DO_NOT_USE。注意:这里的AudioCompressor不能通过CoCreateInstance创建,而只能通过系统枚举。VCM包装Filter作为解压用时,即为AVIDecompressorFilter,一般实现Video从YUV到RGB格式的转换(注意:MPEG数据的压缩/解码都不是通过VCM包装Filter实现的);作为压缩用时,各压缩器注册在"VideoCompressors"目录(CLSID_VideoCompressorCategory)下,Merit值为MERIT_DO_NOT_USE。注意:这里的Video Compressor也不能直接通过CoCreateInstance创建。


13.微软提供了两个Tee Filter:Smart Tee和Infinite Pin Tee Filter。前者有两个Outputpin,且Preview pin输出的Sample已经去掉时间戳;后者,可以动态产生无数个Output pin,而且各个Outputpin输出的Sample是完全一样的。


14.在FiltrGraph运行状态下,可以动态加入新的Filter,但不能删掉Filter,也不能断开Filter之间的Pin连接。Filter从FilterGraph中删除必须在Stopped状态,删除前将这个Filter的Pin断开不是必要的!(连接着的Pin可以通过IFilterGraph::Disconnect断开,并且连接两头的Pin都要调用一次!)


15.AVIMux问题。四种工作模式(通过Filter上的IConfigInterleaving::put_Mode设置):INTERLEAVE_NONE,对输入的数据不缓存,各帧数据按照它们到达Mux的顺序直接写入文件中,速度最快;INTERLEAVE_FULL,对Video和Audio的交叉打包精确控制,每次合成都要阻塞等待等量的Video、Audio数据,适合文件源的视频编辑等应用;INTERLEAVE_CAPTURE,处于前两种模式之间,尽量不使用数据缓存,在一个Pin上数据到达后等待另一个Pin数据到达时,可能会丢帧,适合Live SourceCapture应用,注意此时Audio的Capturebuffer应该小于0.5秒;INTERLEAVE_NONE_BUFFERED,仅在WinXP下用,类似于INTERLEAVE_NONE,只是这种模式生成的文件较小。还有另外一个接口方法IConfigInterleaving::put_Interleaving,设置打包频率和Audio的预留(Preroll),推荐打包频率为1秒钟一次,Audio预留750ms。


16.AVI 1.0文件大小有上限1GB,AVI 2.0文件没有这个限制。AVIMux默认总是生成2.0的文件(1.0的文件主要是老的VFW生成的),也可以通过AVIMux上的IConfigAviMux::SetOutputCompatibilityIndex来设置生成1.0的文件,以保持向后兼容。另一个接口方法IConfigAviMux::SetMasterStream,用以同步多个输入流,特别是在不同源的video、AudioCapture时,两个源的Capture rate可能有细微的差别。设置了Masterstream之后,Mux会修改AVI文件的Playbackrates(AVIStreamHeader结构的dwScale和dwRate两个成员变量)。推荐将Audio stream设为Master。


17.在同一个Filter Graph中,既有Live Source(比如Camcorder,capture devices,mpeg2demux等,他们自带Reference Clock),又有Default DirectSoundDevice(声卡带有时钟),默认情况下,Filter Graph Manager选择Live Source的参考时钟。有时候会出现LiveSource数据流传输了几十个Sample后会不在传送Sample出来。可能的原因是,有多个参考时钟时回放速率匹配问题。解决的方法是,选择声卡的时钟,或者设置整个Filter Graph不使用时钟。


18.0目前(DirectX8.1),微软并没有提供.dv文件的“拉”Filter。如果要使用DV文件,一个变通的方法是,将DV数据保存到AVI文件中。播放这种文件,AVI Splitter会识别出DV数据,并送DV Splitter(微软提供,Push模式)和DV VideoDecoder(微软提供)进行解码。

18.1在回放含有DV的AVI文件时,我们发现,真正实现IMediaSeeking的是AVI Splitter,而不是DVSplitter(微软提供)。DV Splitter的Audio output pin或者Video Outputpin均将IMediaSeeking的请求通过其Input pin传递到Up stream去了。

18.2如果自己写一个"DV Parser Filter",需要注意的是,从FilterSource中拉数据,每次拉的数据大小一般是按512字节对齐的,所以不会是每次拉一个DV帧(PAL为144000字节,NTSC为120000字节)。而微软的DVSplitter输入的Sample要求是一帧一帧的数据(注意:如果每次送的数据太多了,会导致无法正常解码;如果送少了,导致后续Filter没有足够的数据进行解码,会发生Filter Graph的状态转换出错,即可能无法转入Paused状态),所以我们必须在DVParser上对拉出的数据进行必要的缓存。而且,在处理Seek时,新的Seeking位置也要考虑“字节对齐”的问题。
其实,为DV文件写一个Source Filter,直接将DV数据从文件中读出后push出去,问题就要简单一点!


19.Tee Filter问题。微软提供的Smart Tee(没有源码)与Infinite Pin TeeFilter(有源码)相比,后者的性能要好一点。两者的区别是,前者将Previewpin出来的Sample进行了“去时间戳”处理,而后者只是简单地将一个Sample分别在各个output pin上输出。


20.写多进一出,或者是一进多出的Filter,基类可以选择CTransformFilter或者CTransInPlaceFilter,虽然它们在BaseClasses中的实现都是一进一出的。一般的做法为,保持原有的Input pin和Outputpin为“主流”的链路,以这个标准Inputpin的数据“流入”,来驱动协调其他Pin数据处理后,再输入。需要注意的是,必须重载基类Filter的GetPinCount、GetPin实现,以增加新的Pin;重载FindPin支持新加pin;重载Filter的Pause或Stop实现,因为可能还要同步新增加的pin的数据流(如果新增加的pin在Filter要转入Paused状态时还处于死循环或阻塞,则可能导致Filter状态转换失败,出现Frozen现象);新增加Inputpin一般从CBaseInputPin上继承,Outputpin从CBaseOutputPin上继承,而且一般都要重载以下几个函数的实现:CheckMediaType、Receive、EndOfStream、BeginFlush、EndFlush。