DirectShow技术介绍(长篇)-6

    技术2022-07-01  77

    3.5 DirectShow中的事件通告

         这一节主要描述在directshow filter graph中事件是怎样发生的,以及应用程序如何接收事件通告并响应它们。

    3.5.1 概述

         一个filter通过发送一个事件通来通知filter graph manager某个事件已经发生。这些事件可以是一些预知的事件比如流结束事件,也可以是一些异常如render流时失败。一部分事件由filter graph manager自己处理,另一部分则由应用程序来处理。如果filter graph manager不处理某个事件,那么这个事件会被放入到队列中去。filter graph也可以通过队列将自己的事件发送给应用程序。

         应用程序从队列中接收事件并根据其类型来响应它们。DirectShow中的事件通告类似于windows的消息队列机制。应用程序可以让filter graph manager取消对指定的事件类型的默认操作,而是将它们放入事件队列由应用程序来处理它们。

         由于这样的机制,使我们能做到:

          filter graph manager与应用程序的对话

          filter可以即和应用程序也和filter graph manager对话

          *由应用程序来决定处理事件的复杂度。

     

    3.5.2 从队列中取事件

         Filter Graph Manager暴露3个支持事件通知的接口:

          IMediaEventSink 包含filter发送事件的方法

          IMediaEvent 包含应用程序取事件的方法

          IMediaEventEx 继承扩展IMediaEvent接口

         filter通过在filter graph manager上调用IMediaEventSink::Notify方法来发送事件通告,一个事件通知由一个表示事件类型的事件号,和两个DWORD类型用以放置附加信息的参数组成。按事件号的不同,这两个参数可以是指针、返回值、参考时间或者其它信息。完整的事件号和参数列表,参见Event Notification codes(http://msdn.microsoft.com/library/en-us/directshow/htm/eventnotificationcodes.asp)

         要从事件队列中取事件,应用程序需要在filter graph manager上调用IMediaEvent::GetEvent事件。这个方法一直阻塞到取到事件或超时。一旦队列中有了事件,这个方法就返回事件号和两个事件参数。在调用GetEvent后,应用程序应该总是调用IMediaEvent::FreeEventParams方法来释放与事件参数相关的所有资源。比如,一个参数可能是由filter graph分配的BSTR值。

         下面的代码是一个如何从队列中取事件的框架:

     

     

       long evCode, param1, param2;

       HRESULT hr;

       while (hr = pEvent->GetEvent(&evCode, ¶m1, ¶m2, 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);

       }

     

     

     

       要重置filter graph manager默认的事件处理过程,调用IMediaEvent::CancelDefaultHandling方法,用事件号做参数。你可以通过调用IMediaEvent::RestoreDefaultHandling方法来恢复某个事件的处理过程。如果filter graph对某个事件号没有默认处理过程,则调用上面两个方法不产生任何影响。

     

    3.5.3 当事件发生时

         要处理DirectShow事件,应用程序需要一个方法来知道事件何时正等待在队列中。Filter Graph Manager提供两种方法:

         *窗口通告:一旦有事件发生,Filter Graph Manager就发送一个用户自定义窗口消息来通知应用程序窗口

         *事件信号:如果有DirectShow事件在队列中,filter graph manager就触发一个windows事件,如果队列为空,则reset这个事件。

         应用程序可以使用任何一种方法,但通常窗口通告方法相对比较简单。

       

        窗口通告:

         要设置窗口通告,调用IMediaEventEx::SetNotifyWindow方法并指定一个私有消息,私有消息可以是从WM_APP0xBFFF的任一个。一旦filter graph manager把一个新的事件通告放入队列中,它便发送这个消息给指定的窗口。应用程序从窗口的消息循环中来响应这个消息。

         下面是如何设置通知窗口的例子:

     

     

        #define WM_GRAPHNOTIFY WM_APP + 1    // Private message.

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

     

     

     

       消息是一个普通的windows消息,并且独立于DirectShow消息通告队列被发送。使用这种方法的好处是大部分应用程序拥有一个消息循环,因此,要知道DirectShow事件何时发生便无需做额外的工作了。

       下面是一段如何响应通告消息的框架代码:

     

     

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

       {

           switch (msg)

           {

               case WM_GRAPHNOTIFY:

                   HandleEvent();   // Application-defined function.

                   break;

               // Handle other Windows messages here too.

           }

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

       }

     

     

     

           因为事件通告与消息循环均为异步进行的,因此在应用程序响应事件时队列中可以会有多个事件。而当事件变为非法时,它们会从队列中被清除掉。所以在你的事件处理代码中,调用GetEvent直至返回一个表示队列已空的失败代号。

         在释放IMediaEventEx指针前,请以NULL作参数调用SetNotifyWindow方法来取消事件通告。并且在你的事件处理代码中,在调用GetEvent前检查IMediaEventEx指针是否合法。这些步骤可以防止在释放IMediaEventEx指针后应用程序继续接收事件通告的错误。

       

        事件信号:

         Filter Graph Manager建立一个反映事件队列状态的手工重设事件(manual-reset event)。如果队列中包含有未处理的事件通告,Filter Graph Manager就会发信号给手工重设事件。如果队列是空的,则调用IMediaEvent::GetEvent方法会重设(reset)事件。应用程序可以通过这个事件来确定队列的状态。

       

         注意:此处的术语可能被混淆。手工重设事件是由windowsCreateEvent函数创建的一种事件类型,它与由DirectShow定义的事件无关。

       

         调用IMediaEvent::GetEventHandle方法得到手工重设事件的句柄,调用一个函数如WaitForMultipleObjects来等待发送给手工重设事件的信号。一旦收到信号,就可以调用IMediaEvent::GetEvent来接收DirectShow事件了。

         下面的代码举例说明了这种方法。在取得事件句柄后,在100毫秒时间间隔内等待发送给手工重设事件的信号,如果有信号发来,它调用GetEvent然后在windows控制台上打印出事件号和事件参数,循环在EC_COMPLETE事件发生后结束,这标志着回放结束。

     

     

      HANDLE   hEvent;

       long     evCode, param1, param2;

       BOOLEAN bDone = FALSE;

       HRESULT hr = S_OK;

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

       if (FAILED(hr)

       {

           /* Insert failure-handling code here. */

       }

       while(!bDone)

       {

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

           {

               while (hr = pEvent->GetEvent(&evCode, ¶m1, ¶m2, 0), SUCCEEDED(hr))

               {

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

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

                   bDone = (EC_COMPLETE == evCode);

               }

           }

       }

     

     

       

         因为Filter Graph会在适当的时候自动重设事件,因此你的应用程序应当不去作重设工作。同时,当你释放filter graph时,filter graph会关闭事件句柄,因此在这之后你就不能再使用事件句柄了。


    最新回复(0)