DirectShow編程(3.5) - 關於DirectShow - DirectShow中的事件通告

http://blog.csdn.net/believefym/article/details/1779742

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, &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);
  }

  要重置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_APP到 0xBFFF的任一個。一旦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)事件。應用程序可以通過這個事件來確定隊列的狀態。
    
    注意:此處的術語可能被混淆。手工重設事件是由windows的CreateEvent函數創建的一種事件類型,它與由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, &param1, &param2, 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會關閉事件句柄,因此在這之後你就不能再使用事件句柄了。

 
發佈了2 篇原創文章 · 獲贊 1 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章