摘要:这篇文章讨论了一个播放Filter需要处理的一些消息通知。只要正确的处理这些消息通知,才能够正确地设置Directshow播放视频的画面。
1 开发一个可选择的视频播放filter
Directshow提供了一个基于窗口的视频播放Filter,它也提供了一个全屏幕实时播放的filter。你可以利用Directshow的基类开发自己的可选择的视频播放filter。你可以利用CBaseRenderer and CBaseVideoRenderer类根据下面的一些经验指南就可以开发一个可选择的视频播放filter。
在Directshow中主要有三种消息通知。
1数据流的通知,
这是数据流在graph图中传递的过程中,从一个filter到另一个filter的过程中发生的事件通知。例如,begin-flushing, end-flushing or end-of-stream事件通知,这些事件通知都是通过上游fitlter调用的下游filter的输入pin来通知下游filter的。比如IPin::BeginFlush。
2 filter图表管理器发送的消息通知,
这些都是filter给图表管理器发送的事件通知,比如EC_COMPLETE,这种消息一般都是通过图表管理器的IMediaEventSink::Notify发送或接收的。
3 应用程序发送的消息通知
应用程序通过调用图表管理器上的IMediaEvent::GetEvent方法就可以获得这些事件消息。一般来说,图表管理经常讲得到的消息传递给应用程序处理。
2对End-of-stream and Flushing消息的处理
当源filter发现没有数据传送的时候,它就会向下游发送一个end-of-stream通知,这个通知会沿着Graph图表中的filter一个一个的往下传递,最后到达Render Filter。这就导致Graph图表产生一个EC_COMPLETE消息。
当Renderer的输入pin上的IPin::EndOfStream被上游的filter调用的时候,Render Filter就会接收到一个end-of-stream消息通知。Render Filter应该标记下这个消息通知,然后将已经接收到的数据处理完毕。当所有剩余的数据接收完毕,Render Filter就会给Graph图表管理器发送一个EC_COMPLETE消息。当Render Filter在处理完毕所有的数据时,你应该给graph图表管理器发送一次EC_COMPLETE消息。只有当Render Filter处于运行状态时才能给图表管理器发送EC_COMPLETE消息。如果一个Render 正处于paused状态时接收到源filter 发送的end-of-stream通知,只有当Filter Graph结束的时候Render 才能给图表管理器发送EC_COMPLETE消息。
end-of-stream通知发出以后,如果上游filter再次调用Render Filter上的输入pin上的IMemInputPin::Receive or IMemInputPin::ReceiveMultiple方法时,Render就要拒绝它,此时就返回一个E_UNEXPECTED错误消息。
当Filter 图表管理器停止的时候,Render应该将捕捉到的任何end-of-stream通知都应该被清除,当图表管理器再次启动的时候,Render不应该再给管理器发送任何通知。因为图表管理器在启动以前会Paused所有的Filter,这就会导致发生flushing。例如,如果Filter graph处于Pause状态收到一个end-of-stream通知,然后Filter Graph就停止了,当filter Graph再次运行的时候,Render 就不应该给管理器发送EC_COMPLETE消息了。如果没有发生seek,源filter会自动地在发生pause时给下游的filter发送一个end-of-stream通知,如果在filter Graph图表stop的时候发生seek,此时源filter也许正有数据要发送,所以它不会发送end-of-stream通知。
Render filter经常依靠end-of-stream通知来发送EC_COMPLETE通知。例如,如果一个数据流已经结束发送(也就是end-of-stream消息已经发送出来),另一个窗口已经覆盖到视频窗口上,也产生了一系列的WM_PAINT消息。但是,当end-of-stream消息发出以后,Render就处于等待状态,但是Render也明白它不会再接收任何数据了,视频播放窗口就会出现黑屏幕。
Flushing是Render应该处理的另外一种复杂的事件。Flushing消息是通过IPin上的两个方法BeginFlush and EndFlush.触发的。源filter在没有调用EndFlush,而仅仅调用了BeginFlush方法是不合法的,所以此时Flushing的状态是短暂和不连续的。但是在flushing状态下,Render要处理好数据以及接收的消息。
在BeginFlush方法被调用之后所有接收到的数据都要立即被rejected,并且返回一个S_FASLE。并且捕捉到的end-of-stream也要立即清除。当Render接收到seek消息后,就立即处于flush状态。Flush确保在重新发送数据之前从filter Graph中清除所有的旧的数据。
3如何处理状态的改变Handling State Changes and Pause Completion
当一个Renderer filter的状态改变的时候,它的行为和其他的filter是一样的,但是也有以下的区别,当filter的状态变为pause的时候,Render filter 的数据就排成队列,等待下次播放,当一个video 播放filter 停止的时候,它也会保留这些队列中的数据,这就是一个例外,因为dshow规定,当一个graph停止的时候,它不应该保留任何资源。
造成这种例外的原因是如果render filter保持资源,这样,当这个filter接收到一个WM_PAINT 消息时可以通过这个资源来重绘窗口。同样保持这个资源也可以满足一些方法的调用,比如CBaseControlVideo::GetStaticImage,,这个方法用来返回当前图像的一份拷贝。保持资源的另一个原因是,在资源保持的过程中,它所占用的内存块不会被回收,这样,再次开始数据传输时速度会比较快一点,因为不用分配内存了。
在graph运行的期间,sample中的内容会被随时地提交sample内存也随时地被释放,但是,在停止运行的状态中,sample只能被提交,不能被释放,例如,在窗口绘制一幅静态的图画。音频流在停止的状态没法被提交,但是他们可以进行其他的动作,比如准备wave设备。Sample被提交的时间由sample的stream time和IMediaControl::Run方法调用时传递的参考时间综合以后得到的。当开始时间小于等于结束时间时,sample就应该被丢弃。
当应用程序调用IMediaControl::Pause方法准备停止一个graph图时,只有当提交过滤器中有一个数据队列时才能够返回。为了确保此点,当一个render filter 没有等待提交的数据时,该方法就返回S_FALSE,如果有数据等待提交,返回S_OK。
未来确保Render filter 有一个等待提交的数据,Filter图表管理器在停止一个graph时会检查方法的返回值,如果一个或者几个filter 还没有准备好,filter 图表管理器就会调用GetState方法来polls filter。GetState方法带有一个超时的参数,当GetState函数的等待的时间到期返回之前,如果filter还在等待数据的到来时,那么该函数返回VFW_S_STATE_INTERMEDIATE,如果filter已经有等到数据的时候,GetState返回s_ok。
当一个filter在等待数据的时候,源filter会发送一个end of stream的通知,此时状态的转变完成。
当一个graph中的所有的filter都有了等待提交的数据,那么整个graph就成为了pause状态。
4如何处理终止态(Handling Termination)
视频提交过滤器必须能够正确处理来自用户的终止数据流的事件。这就意味着要正确地隐藏窗口,并且知道当窗口重新显示的时候该怎么做。同时,当窗口销毁的时候,提交过滤器也要能够通知Filter 图表管理器来正确的释放资源。
当用户关闭了视频窗口时,或者(用户按ALT+F4),一般的做法是将视频窗口隐藏同时给filter 图表管理器发送一个EC_USERABORT通知,这个通知最终会被发送到应用程序,然后应用程序就会停止播放视频。当发出EC_USERABORT通知后,所有发送给提交filter的数据都会被拒绝。
当一个视频正在在播放的时候,如果用户此时按下ATL +F4,视频窗口就会暂时的隐藏起来,然后所有送往窗口的数据都会被拒绝。当窗口重新显示的时候,不会产生EC_REPAINT通知。
当一个视频提交filter终止的时候,它要给filter图表管理器发送一个EC_WINDOW_DESTROYED消息通知。事实上,最好的处理这个消息的时机是在IBaseFilter::JoinFilterGraph方法调用时,而不是等到实际的窗口销毁时。Sending this notification enables the plug-in distributor in the Filter Graph Manager to pass on resources that depend on window focus to other filters (such as audio devices).
5如何处理数据格式的动态改变
视频提交filter一般只接受那些容易处理的数据格式,例如,一般只接受RGB格式的数据,因为这种数据格式和显示器格式相匹配。
通常的话,上游的filter都是调用下游filter上的输入pin上的IPin::QueryAccept方法来查询,下游的filter是否接受新的数据格式,从而来动态的改变数据格式。一个render filter应该支持动态的修改数据格式,至少它应该允许上游的filter能够改变调色板。当上游filter改变媒体类型,它会在采用新格式的第一个smpale上贴上新的媒体类型。如果一个render filter还有一些老格式的数据没有提交完,它会等到这些老格式的数据提交完毕才改变媒体类型。
解码器也会动态的改变数据格式,这样就要求render filter能够相应的跟着改变,例如,如果我们想要解码器提供一种和DirectDraw兼容的数据格式,当render paused的时候,它就开始通过QueryAccept向上游的filter 询问,解码器都支持什么数据格式,解码器一般不会将它支持的所有数据格式都列举出来,因此,render filter 就要提供一些解码器接口没有说明的数据格式。
如果解码器可以接收要求的数据格式,它就会通过QueryAccept方法返回一个s_ok,于是Render filter就将新的媒体类型 attach to 上游内存分配器分配的下一个sample上。因此,render filter 就要提供一个内存分配器,这个提供一个私有的方法来将媒体类型贴到新的下一个samples上,在这个私有的方法中,调用IMediaSample::SetMediaType来设置媒体类型。
Render filter 的输入pin应该在IMemInputPin::GetAllocator方法中返回render filter 的内存分配器,重载一下IMemInputPin::NotifyAllocator方法,如果上游的filter不使用render filter 提供的内存分配器,那么这个方法就会返回false。
在一些解码器中,如果将biHeight设置为一
阅读(1960) | 评论(0) | 转发(0) |