您的位置:首页 > 其它

编写DirectShow Filters—Filter的数据流动

2008-05-20 09:51 295 查看
seeker
本节详细描述数据如何通过filter graph流动。集中于使用IMemInputPin/IAsyncReader的本地内存传输使用。为编写自定义filter的开发者提供了扩展。
大量数据通过一个filter graph移动。粗略可为两类:媒体数据和控制数据。一般来说,媒体数据按downstream顺序传递而控制数据按upstream顺序传递。媒体包括视频帧、音频采样、MPEG包等等来形成一个流,但也包括了flush命令,end-of-stream通知,和用这个流传递的其它数据。控制数据不是媒体数据的一部分。例如质量控制请求和Seek命令。
1. 传递采样
本文描述filter如何传递采样。描述了两种模型:push 模型使用IMemInputPin方法,pull模型使用:IAsyncReader方法。
1) push模式:传递一个采样
输出PIN通过调用IMemInputPin::Receive 方法或者IMemInputPin::ReceiveMultiple 方法(传递一组采样)传递一个sample。在Receive 和ReceiveMultiple 方法里,输入pin 可以阻塞数据流。如果输入pin 阻塞,那么IMemInputPin::ReceiveCanBlock 应该返回S_OK。如果pin 保证不会阻塞,那么ReceiveCanBlock 方法要返回S_FALSE,返回S_OK 并不表明Receive 方法一直阻塞,只是表明可能阻塞。
虽然Receive 可以阻塞一直等待某种资源变为可用,但是它不阻塞来等待从upstream filter到来的更多数据。因为如果upstream filter 正在等待downstream的filter 释放sample,而这不会发生因为downstream filter正在等待upstream filter,此时就会造成死锁。如果一个filter 有多个输入pin,那么其中的一个pin 可以等待另外的一个pin 接收数据。例如AVI Mux filter 就是通过这种方法来分离音频和视频流的。
如果有以下原因,pin 可能拒绝sample。
a) pin 正在flushing
b) pin 没有连接
c) filter 停止
d) 发生了其他错误
在第一个情况中Receive应该返回S_FALSE,在其它情况下返回一个错误码。如果返回代码不是S_OK时upstream filter 就会停止发送sample。
你可能认为前面三种错误都是可以预见的错误,感觉filter在错误状态下接收sample。一个未知错误将引起pin拒绝sample即使这个正处于接收数据流的状态。如果这种类型的错误发生,pin 就给downstream发送一个end-of-stream通知,并且给Filter graph manager发送一个EC_ERRORABORT 事件通知。
在directshow 的基类中,CBaseInputPin::CheckStreaming 方法用来检查通常的数据流错误,比如flushing, stopped等等。派生类要检查具体filter的错误。万一发生错误,CBaseInputPin::Receive 方法发送一个end-of-stream通知和一个EC_ERRORABORT 事件。
2) pull模式:请求一个采样
在IAsyncReader 接口中,input pin 通过以下方法从output pin 中请求samples:
a) IAsyncReader::Request
b) IAsyncReader::SyncRead
c) IAsyncReader::SyncReadAligned
Reques 方法是异步的,input pin 调用IAsyncReader::WaitForNext 来等待请求完成。
另外两个方法是同步。
2. 传递数据时
1) 分析媒体数据
如果你的filter分析数据,忽略在内容中的头信息或其它信息。例如,不相信AVI RIFF chunk或MPEG包中出现的尺寸值。通常这种类型的错误例子包括:
a) 读数据的N字节,N值来自于内容,不检查N对应于自己的buffer的实际尺寸。
b) 跳到在buffer中的一个字节偏移,不验证这个偏移值是否在buffer中。
另一个通知错误类包括不验证在内容中的格式描述。如:
a) bitmapinfoheader结构可能依照一个颜色表。BITMAPINFO结构定义为一个BITMAPINFOHEADER结构依照于通过一个组成颜色表的RGBQUAD值的一个数组。数组的尺寸由biClrUsed值决定。从不拷贝一个颜色表到BITMAPINFO而不首先检查为BITMAPINFO结构分配的buffer的尺寸。
b) waveformatex结构可能有额外格式信息出现在结构中。cbSize成员指定这个额外信息的尺寸。
在PIN连接期间,filter应该验证所有格式结构是正确的并且包含相应的值。如果不是,拒绝这个连接,在代码中验证格式结构,尤其小心关于算法举出。例如,在BITMAPINFOHEADER中,宽和高是32位long值但图像尺寸只是个DWORD值。
如果从源中的格式数据大于你分配的buffer,不要截断源数据为了拷贝到你的buffer。应该建立一个结构,它的尺寸大于实际尺寸。例如,一个bitmap头可能指定并不存在的调色板表。代替的,重新分配buffer或简单的连接失败。
2) 在期间错误
当graph运行时,如果你的filter接收malform内容,它应该终止流,如下操作:
a) 从Receive返回一个错误代码
b) 在downstream filter调用Ipin::EndOfStream
c) 调用CBaseFilter::NotifyEvent发送一个EC_ERRORABORT事件
格式改变
3) 格式改变
存在于filter的几个机制在中途改变流格式。每个都包括多个步骤,这个步骤创造了潜在的错误接受。如果你的filter得到动态格式改变请求,然后它必须拒绝这个请求或允许新到达的新格式。简单地,从不转换格式除非其它filter允许。
3. End-of-stream通知
当Source filter完成发送数据,调用在downstream input pin上的Ipin::EndOFStream,downstream fitler传递这个调用到下一个filter,等等,当EndofStream到达renderer,renderer发送一个EC_COMPLETE事件到filter graph manager。如果 renderer有多个输入pin,在每个输入PIN接收这个结束流通知它传递EC_COMPLETE事件。
一个filter必须用其它流命令系列化Endofstream调用,如IMemInputPin::Receive(或者,downstream filter必须一直正确的顺序接收这个调用)
在一些情况下,downstream filter 可能在source filter先前检测到流结束。(例如,downstream可能分析这个流)。在这种情况下,downstream filter可能发送一个end-of-stream通知,这个情况应该从IMemInputPIn::Receive返回S_FALSE直到grpah停止或flush。S_FALSE返回值通知soruce filter来停止发送数据。
1. 默认处理EC_COMPLETE
默认的,filter graph manager并不将每个EC_COMPLETE 事件发送给应用程序,而是,当所有的数据流受信EC_COMPLETE 事件后,它才给应用程序发送一个EC_COMPLETE事件。所以,应用程序只有在所有的数据流都完成时才能接收到这个事件。

为了确定流的数目,filter graph manager计算支持seeking (IMediaSeeking/IMeidaPosition)并且具有一个renderer input pin的filter的数目,这个pin定义为一个input pin而没有相应的output。filter graph manager决定是否一个PIN用两种中的一个方式render:
a) nPin 参数中pin 的IPin::QueryInternalConnections 方法返回0
b) filter暴露一个IAMFilterMiscFlags 接口,并且返回AM_FILTER_MISC_FLAGS_IS_RENDERER 标志。
2. 在拉模式下end-of-stream结束通知
在IAsyncReader 连接中,source filter 并不发送end-of-stream通知,代替的,由downstreamfilter来做这个,如典型的parser filter。parser发送EndOfStream调用downsteam。它不发送一个upstream到source filter。
4. new segment
一个segment就是共享相同开始时间、停止时间和回放速率的一组media samples。The IPin::NewSegment 方法用来通知一个new segments 的开始。source filter 通过这种方法来通
知downstream filter它的开始时间和播放速率已经改变。例如,如果source filter 在数据流中搜索一个新点,它用新的开始时间调用NewSegment.
一些downstream filter 在处理sample 时使用这个segment信息。例如,在使用桢内压缩的格式中,如果停止时间落在了一个delta帧,source filter需要在停止时间后发送额外数据。这使解码器来解码最后的delta帧。为了决定正确的最终帧,解码器引用segment停止时间。作为另一个例子,audio renderers使用segment速率连同audio sampling速率来产生正确的audio output。
在推模型中,source filter 初始化NewSegment调用。在拉模型中,这个工作是由parser filter来完成。两种情况下,filter 都调用downstream filter 的input pin 上的NewSegment,input pin传递这个调用到下一个filter,直到调用到达render。filters 必须用其它流调用序列化NewSegment。
在每个new segment后,流时间都被重置为零,当segment 从零开始的时候,传递samples上的时间戳。
5. Flushing
当filter graph运行时,任意数目的数据通过graph。他们中的一些可能在队列中,等待被传递。filter graph有时间来尽可能快的移除未决数据并用新数据来替换。在一个seek命令之后,例如,source filter从source中一个新位置来产生sample。为了最小化延迟,downstream filter应该丢弃在seek命令之前建立的任何sample。丢弃sample的过程叫做flushing。它使graph当事件改变普通数据流动时更快的响应。
在pull模型和push模型中flushing的处理有些许差别。本文通过描述push模型开始,然后描述与在pull模型中的不同。
flushing发生在两个阶段:
1) 首先,source filter调用downstream filter 的input pin 上的BeginFlush。downstream filter 开始拒绝从upstream 来的sample。它也丢弃正在保存的任何samples,并且发送BeginFlush 调用downstream到下一个filter。
2) 当source filter 准备发送新数据时,它调用inpput pin上的IPin::EndFlush。告知downstream filter可以接收新的samples。downstream filter发送EndFlush调用到下一个filter。
在BeginFlush 方法中,input pin 进行如下操作:
1) 调用在downstream input pins上的Calls BeginFlush。
2) 拒绝任何流数据调用,包括Receive 和endofstream。
3) 取消阻塞任何被阻塞等待从filter的allocator来的sample。一些filter为了这个目的反提交它们的allocator。
4) 退出任何阻塞流的等待。例如,Renderer filter暂停时阻塞。它们也阻塞当等待在正确流时间render的sample。filter 必须取消阻塞,所以sample队列upstream可能被传递和拒绝。这步确保所有upstream最终解除阻塞。
在EndFlush 方法中,input pin进行如下操作:
1) 等待所有在队列中的samples 被丢弃。
2) 释放任何被buffer的数据。这一步可能在BeginFlush 方法里不时执行。但是,beginflush 与streaming 线程是不同步的。Filter 必须在调用BeginFlush 和EndFlush 方法之间不处理或者缓存任何数据。
3) 清除所有未决的EC_COMPLETE 通知。
4) 调用downstream的EndFlush。
此时,filter 可以再次接收sample。所有samples保证比flush时的更新。
在pull模型中,parser Filter 初始化flushing,而不是由source filter,它不仅调用了downstream filter上的IPin::BeginFlush 和IPin::EndFlush,它也调用了source filter的output pin 上的IAsyncReader::BeginFlush 和IAsyncReader::EndFlush。如果此时source filter 有未决的read请求,它就抛弃。
6. Seeking
Filter 通过IMediaSeeking 接口支持seeking。应用程序从filter graph manager请求IMediaSeeking并且通过这个接口执行seek 命令。Filter graph manager分发每个seek命令到grpah中所有的renderer。每个renderer 传递seek 命令到upstream,通过upstream filter 的output pin 直到某一个可以执行filter这个seek 命令。典型的source filter或者parser filter,例如AVI Splitter,执行seek 操作。



如果一个parser filter 的output pin 多于一个,一般设计它们中的一个来接受seek 命令。其他的pin拒绝或者忽略接收到seek 命令。这样,parser 保持了它的所有流同步。
IMediaPosition接口
不赞成filter使用IMediaPosition。自动化客户端仍旧需要使用filter graph manager上的这个接口,因为IMediaSeeking不是自动化-兼容,但filter graph manager翻译 所有的IMediaPosition调用到IMediaSeeking调用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: