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设置为一个YUV类型的正数,那么就解码器就会产生上下颠倒的画面,这是不正确的,应该视为解码器的一个bug。
当render filter 发现graph中的数据格式发生改变的时候,它都会发送一个EC_DISPLAY_CHANGED消息通知的。大多数的render filter 在连接的时候都会选择一个GDI支持的数据格式,如果用户改变了当前的显示模式而没有重新启动机器,那么render filter 发现自己正使用一种很糟糕的数据格式进行连接,那么它就会发送上面的消息通知。第一个参数就是需要重新连接的pin,管理器就会让graph图停止运行,重新连接pin。在随后的重新连接过程中,render filter 就会选择接受合适的数据格式。
当render fitler发现调色板发生改变的时候,它要发送EC_PALETTE_CHANGED的消息通知给filter 图表管理器。
最后,视频render filter 发现视频的尺寸发生改变,它会给图表管理器发送一个EC_VIDEO_SIZE_CHANGED消息通知。
6如何处理永久性属性(Persistent Properties)
所有通过IBasicVideo and IVideoWindow接口设置的属性都意味着在整个连接过程中是永久不不变的。因此,断开连接,重新连接都对窗口的大小,位置,样式等属性没有影响。但是,如果视频的尺寸大小发生改变的时候,render filter应该重新设置源或者目的的矩形大小。
源或者目的的位置是通过IBasicVideo 设置的。
IBasicVideo and IVideoWindow 提供了足够的接口方法可以让应用程序来一种永久的格式来保存或者存储经过接口的数据。
7如何处理EC_REPAINT通知
当一个render filter 暂停或者停止的时候,它发送一个EC_REPAINT消息。这个消息告诉filter 图表管理器render filter 需要数据。如果一个filter 管理器准备停止的时候接收到这个消息,它会首先暂停 filter graph,等到所有的filter 都接收到了数据(通过调用GetState),然后它再重新停止graph。当处于停止状态,一个render filter 应该保存一副图画,这样可以在处理WM_PAINT消息的时候来显示这幅图画。
当一个render filter 在停止或者暂停的时候收到WM_PAINT 消息时,如果它没有任何的数据用来显示,它也会给 filter图表管理器发送一个EC_REPAINT消息。当处于暂停状态的filtr 图表管理器接收到一个EC_REPAINT消息时,图表管理器就会调用IMediaPosition::put_CurrentPosition方法,以当前的位置为参数,这个方法的调用就会导致源filter flush 图表管理器,然后通过图表管理器发送新的数据给render filter。
Render filter一般只发送一次这样的消息通知,也就是说,如果一个render filter发送了一个ec_repaint消息,在数据到来之前它不应该再发送ec_repaint消息了,因此,通常的做法设置一个标志用来标示已经发送了一个ec_repaint消息,当render 接收到数据或者输入pin 被flushed以后,这个标志应该被重置。当然,如果输入pin接收到end-of-stream通知的时候,这个标志不重置。
如果一个render filter 不控制它的EC_REPAINT消息的时候,那么整个filter图表管理器都会被EC_REPAINT消息淹没的。例如,如果一个render 没有图像显示的时候,如果一个窗口从render 窗口上拖动,那么render 窗口就会接收到大量的wm_paint消息的,只有第一个消息可以产生EC_REPAINT事件。
Render filter 应该将它的输入pin作为 EC_REPAINT 消息的第一个参数,by doing this,rendfilter 首先向附加的输出pin(不知道这么说是否正确,原文the attached output pin will be queried for IMediaEventSink) 请求IMediaEventSink,接口,如果输出pin支持这个接口,那么Ec_REPAINT消息就首先通过这里发送出去。这样就使输出pin在graph收到消息前能够处理 repain事件。如果graph没有处于停止的状态,输出pin不会处理这个消息,因为没有空闲的内存数据块。
如果输出pin不能够处理这个请求,或者graph正在运行,那么EC_REPAINT消息就会被丢弃。输出pin上的IMediaEventSink::Notify方法调用,返回S_OK表明输出pin可以正确的处理repaint消息。在graph的工作线程中会调用输出pin的,这样就避免了render直接调用输出pin,防止死锁。如果graph处于停止状态或者暂停状态,或者是输出pin不能处理这个请求,然后就会有缺省的处理过程来处理这个消息。
8如何处理全屏幕显示
IVideoWindow插件管理graph的全屏回放。它可以控制一个render filter 的窗口伸展成一个全屏来显示图像,或者直接用一个全屏的filter来直接回放。当一个filter从普通状态到全屏显示转化的过程都要发送EC_ACTIVATE消息,无论activated or deactivated.。也就是说,render filter 在接收到一个WM_ACTIVATEAPP一定要发送EC_ACTIVATE消息。
当一个filter 处于全屏模式的时候,这些消息用来管理是进入还是退出全屏模式。
当graph 接收到一个EC_ACTIVATE消息通知准备退出全屏模式,那么graph就发送一个EC_FULLSCREEN_LOST给应用程序,应用程序也许会利用这个消息来保存全屏按钮的状态。
9消息通知小结Notifications
1 EC_ACTIVATE
说明;render filter 在接收到一个WM_ACTIVATEAPP一定要发送EC_ACTIVATE消息。
2 EC_COMPLETE
当所有的数据都提交完毕的时候,发送此消息
3 EC_DISPLAY_CHANGED
当显示的格式发生变化的时候,发送此消息
4 EC_PALETTE_CHANGED
当调色板发生变化的时候,发送此消息
5 EC_REPAINT
重画的时候,只发送一次
6 EC_USERABORT
当用户关闭的时候
7 EC_VIDEO_SIZE_CHANGED
当视频的尺寸发生变化的时候
8 EC_WINDOW_DESTROYED
当render filter 销毁的时候,发送此消息
10Render中的源和目标矩形
在VIDEOINFO, VIDEOINFOHEADER, and VIDEOINFOHEADER2三种媒体结构中有三个尺寸。
这篇文档就是想解释一下这三个尺寸有何不同,以及他们是用来做什么的。
首先,在这些结构中有一个bmiHeader数据成员,这个成员是BITMAPINFOHEADER结构,这个结构的定义如下
typedef struct tagBITMAPINFOHEADER {
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount;
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;
这个结构有两个结构成员,biWidth和biHeight。
第二,在这些结构中有一个rcSource数据成员,同时也有一个rcTarget成员,假如你有两个filter,A和B,假如这两个filter以某一种媒体数据类型 相连,A在左边,上游的filter,B在右边,下游的filter。
在这两个filter间传递的buffer具有一定的尺寸,可以用bmiHeader.biWidth, bmiHeader.biHeight来标示。
Filter A的输入视频流由rcSource 的大小控制,Filter应该将输入视频的一部分扩展填充到buffer中rcTarget区域中,填充的一部分的大小,是根据rcSource和数据媒体类型的大小比较结果而定的。也就是rcSource和(biWidth, biHeight) 比较。如果rcSource为空,Filter A就会将全部的输入pin拷贝到rcTarget,如果rcTarget为空,那么Filter A就会将视频填充到整个的输出buffer中。举例如下:
假定Filter A接收的视频图像为160*120单位为象素,假定A和B连接的时候采用如下的数据类型
(biWidth,biHeight):320, 240
RcSource:(0,0,0,0)
RcTarget(0,0,0,0)
这就意味着Filter A就会将它接收到的视频数据x方向和y方向乘于2以后填充到320*240的输出bufer中。
又假如Filter A接收的视频图像为160*120单位为象素,假定A和B连接的时候采用如下的数据类型
(biWidth,biHeight):320, 240
RcSource:(0,0,160,240)
RcTarget(0,0,0,0)
两个filter连接的buffer是320*240,因为指定的rcSource指定了buffer的左半部分,Filter A就将输入视频的左半部分或者(0,0,80,120)部分,然后将视频扩展到320*240(x方向*4,y方向*2)然后填充到320*240的输出buffer中。
现在我们假定Filter A调用CBaseAllocator::GetBuffer,方法,这个方法的返回的sample会附着一个媒体类型,用来标示Filter B期望Filter A能够提供一个和前面的视频流具有不同size或者格式的数据。假定 新的媒体类型如下
(biWidth, biHeight): 640, 480
rcSource: (0, 0, 160, 120)
rcTarget: (0, 0, 80, 60)
这就意味着sample具有一个640*480的buffer,原来的rcSource适用于原来的(320, 240)的媒体类型不适用于新的媒体格式,因此,rcSource指定输入只使用左上角的四分之一,这一部分被放置到rcTarget的输出buffer的左上角(80,60),因为Filte A接收160*120的视频,输入视频的左上角正好是(80,60),和输出的位图一样大,不用扩展
Filter A不会在输出buffer的其它部分放置数据,The rcSource member is bounded by the biWidth and biHeight of the original connected media type between filters A and B, and rcTarget is bounded by the new biWidth and biHeight of the media sample.上面的例子中,rcSource就在
(0,0,320,240)范围内,rcTarget就在(0,0,640,480)范围内;
作者简介:李强,目前暂时供职于山大联润信息科技有限公司,从事网络视频会议软件的开发,目前的感兴趣的方向,移动设备上多媒体的开发。[email protected] 欢迎转载本文档 。
本文地址:http://com.8s8s.com/it/it21828.htm