DirectShow技术描述与应用(3)——续

类别:VC语言 点击:0 评论:0 推荐:
  

过滤器的状态(Filter States)

 

过滤器有三种状态:已停止,已暂停和正在运行。这个已暂停的状态可以立即对运行命令做出响应。在DirectShow中是由过滤器表管理器来控制所有状态的改变的。当应用程序调用IMediaControl::Run,IMediaControl::Pause和IMediaControl::Stop方法时,过滤器表管理器则用所有过滤器的IMediaFilter接口中的相应的方法,来改变所有的过滤器的状态。已停止状态和正在运行状态之间的转换,总会经过已暂停状态。所以,如果表处于已停止状态,当应用程序启动表时,过滤器表管理器首先会将表处于已暂停状态,然后再从已暂停状态转入正在运行状态。

大多数过滤器中,正在运行状态和已暂停状态是一样的。请考虑下面这么一个过滤器表:

Source > Transform > Renderer

假定现在的源过滤器不是一个实时捕获源。当源过滤器处于暂停状态,它建立一个线程来生成新的数据并将其快速地写入媒体样本。这个线程通过ImemInputPin::Receive方法将媒体样本push给其下游过滤器。转换过滤器接收源过滤器线程传来的媒体样本。它也可以使用工作线程来处理媒样本并传递给还原过滤器,但通常是不另外建立线程来进行处理数据的。当还原过滤器处于暂停状态,它就等待接受一个媒体样本。当它接受到一个媒体样本后,它就不一定是阻塞它还是保存它了。如果它是视频还原器,那么它就将数据还原为图像,因为只接受一个媒体样本,那么这个图象就是静止的,像是一张大海报。如果表处于暂停状态,那么媒体样本将在表的第一个媒体样本之后被搁置,每个过滤器都将在Receive或GetBuffer被阻塞。虽然不会有数据被丢失,一旦源线程被取消阻塞,它将从被阻塞的地方重新开始。

源过滤器和转换过滤器可以忽略暂停状态到运行状态的转换过程,它们会立即而且非常迅速地恢复处理数据的工作。但当还原器运作,开始还原媒体样本时。首先它还原它在暂停时所保存的媒体样本。每当还原器接收到一个新的媒体样本时,它会计算这个媒体样本的表达(presentation)时间。还原器保存在当前还原的媒体样本的表达时间以前的每一个媒体样本。当它等待所表达时间时,它要么在Receive方法中阻断,要么在工作线程中的队列里接受一个新的媒体样本。还原器上游的过滤器的不参与时序按排。

实时源,如捕获设备,是一个普通的结构体系的例外。对一个实时源来说,它不适合于预先提供任何数据。应用程序可以暂停过滤器表,而再一次启动它时则需要等待很长一段时间。表不应处理”旧的”媒体样本。因此,在暂停时实时源不产生样本,而只在运行时产生。为了向过滤器表管理器发送这个行为信息。源过滤器的IMediaFilter::GetState方法就会返VFW_S_CANT_CUE。这个常量指明尽管还原器没有接收到任何数据,但过滤器已经处于暂停状态,

当过滤器停止时,它将拒绝任何传递给它。随后,源过滤器关闭它的流处理线程,其它的过滤器关闭它所建立的工作线程。针们解除提交分配器。

 

状态的改变

过滤器表管理器以下游而上的顺序完成所以的状态改变,其开始于还原器,结束于源过滤器。这种顺序可以防止样本的丢失和表的死锁。在状态的改变中,暂停状态与停止状态之间的转换是至关重要的。

停止到暂停:在每个过滤器进入暂停后,它们都进入准备接受媒体样本的状态。源过滤器依然是最后一个进行暂停状态的。它建立一个流处理线程和开始传递媒体样本。因为所有的下游过滤器都处于暂停状态,所以没有过滤器拒绝样本。当每一个表里的还原器都接收到一个样本时,过滤器表管理器的状态改变便完成。(实时源过滤器是一个例外,先前已经讨论过。) 暂停到停止:当过滤器进入停止后,它释放(Release)所持有(holds)的媒体样本,这样可以解开上游过滤器在GetBuffer的等待。如果进入停止的当时,过滤器正在Receive方法中等待资源,那么它将停止等待并从Receive返回,因为其下游过滤器已经处于暂停状态,下游过滤器无条件地结束Receive方法。因此,当过滤器表管理器停止下一个上游过滤器时,那个过滤器已经从GetBuffer和Receive解锁出来,可以响应停止命令。上游过滤器也许会在它得到停止命令前递送一些媒体样本,但其下游过滤器会直接拒绝它,因为下游过滤器已经处于停止状态了。

 

 

Pull模式

在ImemInputPin接口中,是由上游过滤器来确定到底要发送什么数据,并由它将数据Push给下游过滤器。不过,在某些过滤器中,使用pull模式而为适合。在这种模式中,下游过滤器向上游过滤器请求数据。媒体样本仍然是从输出针到输入针到达下游过滤器,不同的是,是由下游过滤器激起数据的流动。这种连接应使用IasyncReader接口。

The typical use for the pull model is in file playback. For example, in an AVI playback graph, the Async File Source filter performs generic file reading operations and delivers the data as a byte stream, with no format information. The AVI Splitter filter reads the AVI headers and parses the stream into video and audio samples. The AVI Splitter can determine what data it needs better than the Async File Source filter, and therefore it uses IAsyncReader instead of IMemInputPin.

文件播放是一个比较典型的pull模式。例如,在一个AVI播放过滤器表中,Async File源过滤器完成文件的读操作,并产生一个没有任何格式信息的字节数据流。AVI分解过滤器读取AVI数据头并将流解析为音频流和视频流。AVI分解过滤器可以决定Async File源过滤器读取它所需的数据,因为它使用了IAsyncReader接口,代替了IMemeInputPin接口。

为了能够向输出针请求数据,输入针使用下面几种方法:

IAsyncReader::Request IAsyncReader::SyncRead IAsyncReader::SyncReadAligned.

第一种方法是异步,用来支持多重交迭读取。其它方法都是同步的。

理论上,有些过滤器可以支持IAsyncReader,但在实际中,它为那些与分解过滤器连接的源过滤器为设计。分解器的行为与在Push模式中的源过滤器非常相似。当它暂停时,它建立一个从IAsyncReader连接中pull数据的流处理线程,并向下流过滤器push数据。其输出针使用IMemInputPin接口,而且表的其它部分是标准的Push模式。

DirectShow中的事件通知

 

这一部分将描述在Microsoft® DirectShow®过滤器表中,事件是如何实现的;一个应用程序如何才能接受到事件通知并且响应它们。

 

事件通知概述

过滤器通过投递事件通知来向过滤器表管理器通报一个事件。事件可以是包含任何信息,如流的结束,也可以是一个错误,如还原流的失败。过滤器表管理器本身处理一些过滤器事件,其它事件则留给应用程序来进行处理。如果过滤器表管理器遇到一个不能处理的事件,它就将事件放入到一个队列中去。同样的,过滤器表管理器也会将它自己的事件通知放入队列中去,以期应用程序来进行处理。

应用程序可以从队列中接收到事件并对它们做出响应。因此DirectShow的事件通知与Microsoft® Windows®消息队列模式非常相似。应用程序可以取消过滤器表管理器本身可以处理事件的这种行为,过滤器表管理则直接地将这些事件放入队列中去,由应用程序来进行处理。这种机制允许:

过滤器表管理器与应用程序进行通信。 过滤器可以与应用程序和过滤器表管理进行通信。 由应用程序确定它自己都处理哪些事件。

 

接收事件

过滤器表管理器向外部暴露三个接口,来支持事件通知.

IMediaEventSink包含过滤器投递事件的方法 IMediaEvent包含应用程序接收事件的方法 IMediaEventEx inherits from and extends the IMediaEvent interface. IMediaEventEx继承于并扩展了IMdeiaEvent接口

过滤器通过调用过滤器表管理器的IMediaEventSink::Notify方法来投递事件通知。一个事件通知由一个事件代码和两个DWORD型参数组成。根据不同的事件代码,参数值可能会是指针、返回码、参考时间或者其它信息。

为能从队列中接收事件,应用程序应调用IMediaEvent::GetEvent方法来接收数据。这个方法会被阻断直到从一个队列中得到一个事件或者超过时限。当调用完GetEvent后,应用程序应该总是调用IMediaEvent::FreeEventParams方法来释放在事件参数中的资源。例如参数有可能是由过滤器表提供的BSTR字符串。

下面的代码提供一个如何从队列中接受事件的框架。

long evCode, param1, param2;

HRESULT hr;

while (hr = pEvent->GetEvent(&evCode, &param1, &param2, 0), SUCCEEDED(hr))

{

    switch(evCode)

    {

        // Call application-defined functions for each

        // type of event that you want to handle.

    }

    hr = pEvent->FreeEventParams(evCode, param1, param2);

}

为了停止某个事件的过滤器表管理器的默认处理,可以调用IMediaEvent::CancelDefaultHandling方法。你可以通过调用IMediaEvent::RestoreDefaultHandling方法来恢复事件的默认处理。对于那些没有过滤器表管理器默认处理的事件,调用这些方法将不会产生任何影响。

 

事件的什么时候发生

为了DirectShow事件,应用程序需要一种在队列中有事件在等待时,能够被得知的方法。过滤表管理器提供两种途径来实现:

窗口通知:过滤器表管理器在产生一个新事件时,向应用程序窗口发送一个自定义的消息。 事件信号:如果在队列有DirectShow事件时,则过滤器表管理器发送一个窗口事件。并在队列为空时重置事件。 An application can use either technique. Window notification is usually simpler. 应用程序可以使用其它手法来实现。但窗口消息通常会更为简单。

 

窗口通知

可以调用IMediaEventEx::SetNotifyWindow方法来指定一个私有消息,来建立起窗口通知。这个私有消息可以使用从WM_APP到WM_APP+0xBfff之间的数。只要当过滤器表管理向队列里放入新的事件,它就会向指定的应用程序窗口发送信息。应用程序对来自Windows消息循环的消息作出响应。

下面代码说明了如何设置通知窗口。

#define WM_GRAPHNOTIFY WM_APP + 1   // Private message.

pEvent->SetNotifyWindow((OAHWND)g_hwnd, WM_GRAPHNOTIFY, 0);

       下面代码说明了如何对窗口消息做出响应

LRESULT CALLBACK WindowProc( HWND hwnd, UINT msg, UINT wParam, LONG lParam)

{

    switch (msg)

    {

        case WM_GRAPHNOTIFY:

            HandleEvent();  //应用程序定义的函数.

            break;

        //也可以在这里编写消息处理。

    }

    return (DefWindowProc(hwnd, msg, wParam, lParam));

}

因为事件通知和消息循环都是异步的,所以在你的应用程序对消息作出响应时,队列中有可能包含多个事件消息。也可能有时候事件会变得无效,那么它就会被清除出队列。因此,在你的事件处理代码中,应不停地的调用GetEvent方法,直至返回失败码,即消息队列已为空。

注意,在你释放IMediaEventEx指针时,先要通过向SetNotifyWindow传入一个空指针来取消事件通知。在你的事件处理代码中,一个定要在调用GetEvent方法前检查IMeiaEventEx指针的有效性。这样可以预防在释放了IMediaEventEx指针后,应用程序再次执行事件处理代码所可能出现的错误。

 

事件的发出(signal)

过滤器表管理器维护一个手动重置(manual-reset)的事件,用来反应事件队列的状态。如果事件队列包含有未解决的事件通知,那么过滤器表管理会发出一个手动重置事件。如果事件为空,调用IMediaEvent::GetEvent方法会重置事件。应用程序可以使用此事件来确定队列的状态。

 

注意:这个语术可能会被误解。手动重置事件是一个通过Windows CreateEvent函数建立的事件类型,它在DirectShow中并不做任何事情。

 

调用IMediaEvent::GetEventHandle方法可以得到手动重置事件的句柄。可以通过WaitForMultipleObject函数来等待这个事件的发生。一旦此事件发生,调用IMediaEvent::GetEvent方法就可以得到DirectShow事件。

下面的代码示例了这样的功能。它取得事件的句柄,每100毫秒的间隔等待事件的发生。当事件发生后,通过调用GetEvent方法来取得事件,并将事件代码和事件参数显示出来。当EC_COMPLETE事件(其表示回放已经完成)出现则循环结束。

 

HANDLE  hEvent;

long    evCode, param1, param2;

BOOLEAN bDone = FALSE;

HRESULT hr = S_OK;

hr = pEvent->GetEventHandle((OAEVENT*)&hEv);

while(!bDone)

{

    if (WAIT_OBJECT_0 == WaitForSingleObject(hEvent, 100))

    {

        while (hr = pEvent->GetEvent(&evCode, &param1, &param2, 0), SUCCEEDED(hr))

        {

            printf("Event code: %#04x\n    Params: %d, %d\n", evCode, param1, param2);

            hr = pEvent->FreeEventParams(evCode, param1, param2);

            bDone = (EC_COMPLETE == evCode);

        }

    }

}

 

因为过滤器表会适当地自动设置或重置事件,你的应用程序可以免于对这些事情的处理。同样的,当你释放了过滤器表后,过滤器表就会释放掉事件句柄。因此在你释放过滤器表后,就不要再去使用事件句柄。

 (待续...)


本文地址:http://com.8s8s.com/it/it2140.htm