DirectShow SDK筆記【關於DirectShow(3)】

【續前一篇文章】

    4.3 Filter States
    Filter有三種狀態,停止,暫停,運行。暫停狀態是爲了在GraphCue Data, 使得運行命令可以立即響應。Filter Graph Manager控制着所有狀態的轉換。當應用程序調用IMediaControlRun, Pause, Stop方法時, Filter Graph Manager就調用所有Filter的相應IMediaFilter方法。停止,運行狀態的切換總是要經過暫停,因此,當應用程序對停止的Graph 調用RUN命令時,Filter圖表管理器在Run之前首先要暫停。對於大多數的Filter來說,RunningPaused狀態是一樣的。考慮如下的Filter Graph:
    Source -> Transform -> Renderer
    假定Source Filter不是實時源。當Source Filter暫停,它創建一個線程生成新的數據並儘可能快的寫入到Media Samples. 線程通過調用Transform Filter的輸入PIN上的IMemInputPin Receive方法把數據向下推。Transform Filter接收到在Source Filter的線程中的Sample.它可能用一個工作線程把Sample遞送給Renderer,但是通常是在同一個線程完成。此時Renderer暫停,它等待接收Sample. 在接收到一個Sample後,它就阻塞線程並不定的保存數據。如果是視頻Renderer, Sample作爲張貼圖像顯示,如果必要就重畫圖像。
    現在,數據流完全準備好進行提交。如果Graph保持暫停,在第一幀SampleSample就堆積在Graph, 直到每個Filter都阻塞在ReceiveGetBuffer. 但是不會有數據丟失。一旦Source線程非阻塞,它簡單從阻塞的點恢復。
    Source FilterTransform Filter忽略從暫停到運行的變換,它們簡單的儘可能快的繼續處理數據。當Render開始運行,它就開始提交Sample. 首先提交的就是當它阻塞時保存的Sample. 然後,每次都接收到新Sample. 計算Sample的提交時間。(細節可參考Time and Clocks in DirectShow),Render會保存每個Sample直到提交時間,到點後再提交Sample. 當它等待提交時間時,線程也阻塞在Receive方法,或者在工作線程用隊列接收Sample. Renderer向上的所有Filter都不會陷於時間安排。
    實時源(比如捕捉設備)與普通結構有些不同。在實時源中,不合適提前準備任何數據。應用程序可能暫停Graph,在運行前等待比較長的時間。Graph不應該提交過時Sample. 因此,暫停時實時源不會產生數據,只有運行時纔有。爲了把事件通知給Filter Graph Manager, Source FilterIMediaFilter方法GetState返回VFW_S_CANT_CUE。此返回值表示Filter已經轉換到暫停狀態,即使Renderer沒有接收到任何數據。
    當一個Filter停止時,它拒絕發送給它的任何SamplesSource Filter關閉他們的Stream線程,其他Filter也關閉他們創建的工作線程,PinDecommit他們的內存分配器。
 
    4.3.1   State Transitions
    Filter Graph Manager按照逆流的方向來切換Filter的狀態,從Renderer FilterSource Filter,這種方式可以防止死鎖和Sample的丟失。最關鍵的狀態切換是暫停和停止之間。
    ·從停止到暫停,當Filter暫停時就準備好了從連接Filter接收SampleSource Filter是最後一個暫停的。它創建Streaming線程發送Sample,因爲下游的Filter的狀態都已經切換到暫停了,所以,所有的Filter都可以接收Sample。只有當Graph所有的Renderer都接收到SampleFilter Graph Manager纔算完成了狀態的切換(除了前面講的實時源例外)
    ·從暫停到停止。當Filter停止時它要釋放它擁有的所有的Sample以使得上一級Filter中的IMemAllocator::GetBuffer調用脫離阻塞。如果FilterReceive函數中等待資源,它也會停止等待,並從Receive返回,以使調用Filter解鎖。因此,當Filter Graph Manager停止連接的上一級Filter時,Filter就不會在GetBufferReceive函數中阻塞,也就能響應停止命令。上級Filter在得到停止命令前可以會遞送一些額外Sample。但是下級Filter可能拒絕他們,因爲他們已經停止。
    4.4 Pull Model
IMemInputPin接口中,上級Filter決定發送什麼樣數據,然後將數據推給下Filter。但對某些Filter,拉模式更適合。現在,下Filter向上Filter請求數據。數據依然是從上級到下級,從輸出Pin到輸入Pin,但是由下Filter發動數據流動。這種類型連接採用的是IAsyncReader接口。
   拉模式的典型應用是文件回放。例如在AVI文件的回放Graph中,Async File Source Filter就執行通用文件讀寫操作,然後將數據以字節流的方式(不帶格式信息)發送給下FilterAVI Splitter讀取AVI頭並把數據流分析爲視頻、音頻流。AVI Splitter能比Async File Source Filter更好的決定它需要什麼類型的數據,因此它用IMemInputPin代替IAsyncReader接口。
    爲了從輸出PIN請求數據,輸入PIN調用如下一種方法:
    ·IAsyncReader::Request
    ·IAsyncReader::SyncRead
    ·IAsyncReader::SyncReadAligned
    第一個方法是異步的,支持多個重疊讀寫。其他是同步的。
    理論上,任何Filter可以支持IAsyncReader,但是實際上它被設計來連接Source FilterParser FilterParser的功能與推模式中的Source Filter非常相同。當它暫停時,創建一個數據流線程從IAsyncReader連接拉數據,並推向下一級。輸出PIN使用IMemInputPinGraph的餘下部分使用標準的推模式。
 
5. Event Notification in DirectShow
 
    5.1 Overview of Event Notification
    某個事件發生時,比如數據流結束,產生一個錯誤,提交流失敗等,Filter就給Filter Graph Manager發送一個事件通知。Filter Graph Manager自己處理部分事件,另一部分交給應用程序處理。如果Filter Graph Manager沒有處理事件,它就把事件通知放入到一個隊列中。圖表管理器也可以將自己的事件通知放進隊列中。
    應用程序以事件隊列獲取事件並根據事件類型進行處理。DirectShow中的事件通知和Windows的消息機制差不多。應用程序也可以取消Filter Graph Manager特定事件的默認行爲。Filter Graph Manager就直接把事件放入隊列,留給應用程序來處理。
 
    5.2 Retrieving Events
    Filter Graph Manager暴露了三個接口處理事件通知:
    ·IMediaEventSink  Filter用這個接口來Post事件。
    ·IMediaEvent       應用程序利用這個接口來從隊列中查詢消息。
    ·IMediaEventEx     IMediaEvent的擴展。
    Filter通過IMediaEventSink::Notify方法向Filter Graph Manager提交事件。事件通知包括一個事件Code,這個Code不僅僅代表了事件的類型,還包含兩個DWORD類型的參數用來傳遞一些其他的信息。根據不同的事件Code,附加信息可能是指針、返回碼、參考時間或其他信息。獲取事件Code和參數的全部列表可參考Event Notification Codes.
    應用程序通過調用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對事件的缺省處理,可以用某個事件碼做參數調用IMediaEventCancelDefaultHandling方法。這樣就可以屏蔽Filter Graph Manager對某個事件碼的處理了。如果要恢復圖表管理器對該事件碼的缺省處理,可以調用RestoreDefaultHandling方法。如果Filter Graph對某個事件沒有缺省的處理,調用這兩個函數是不起作用的。
 
    5.3 Learning When an Event Occurs
    爲了處理DirectShow事件,應用程序需要一種機制來知道什麼時候隊列中有等待的事件。Filter Graph Manager提供了兩種方法:
    ·窗口通知:Filter Graph Manager發送開發者自己定義的窗口消息
    ·事件信號:如果隊列中有DirectShow事件,就用事件信號通知應用程序,如果隊列爲空就重新設置事件信號。
    應用程序可以使用這兩種技術。窗口通知通常更簡單。
 
    5.3.1   Window Notification
    建立窗口通知可調用IMediaEventEx::SetNotifyWindow方法,並指定一個私有消息。應用程序可使用從WM_APP0XBFFF之間的消息值作爲私有消息。只要Filter Graph Manager把一個新事件放入到隊列時,就向目標窗口發送消息。應用程序從消息隊列響應消息。
下面的代碼演示瞭如何利用消息通知:
#define WM_GRAPHNOTIFY WM_APP+1    // Private message.
pEvent->SetNotifyWindow((OAHWND)g_hwnd, WM_GRAPHNOTIFY, 0);
    消息是一個普通的窗口消息,是從DirectShow的事件通知隊列單獨提交的。這種方法的好處多數程序已經實現了消息循環。因此,我們可以合併DirectShow的事件通知而不需要過多額外工作。下面的代碼說明了如何響應消息通知的框架。完整的例子可參考Responding to Events.
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));
}
    由於事件通知和窗口的消息循環都是異步的,因此,當你的應用程序處理消息的時候,隊列中或許有N個事件等待處理。同樣,如果事件失效,它也可能從事件隊列清除掉。因此,在你調用GetEvent的時候,一定要循環調用,直到返回一個錯誤碼,這表明隊列是空的。
    當你釋放IMediaEventEx指針時,你可以調用SetNotifyWindow來取消事件通知,記住此時要給這個函數傳遞一個NULL指針。在你的事件處理程序中,在調用GetEvent之前一定要檢查IMediaEventEx指針是否爲空,這樣就可以避免錯誤。因爲有時在釋放IMediaEventEx指針後可能會得到通知。
 
    5.3.2   Event Signaling
    Filter Graph Manager裏有一個手動設置的Event內核對象,用來反映事件隊列的狀態。如果隊列中有等待處理的事件,Event就處於通知狀態,如果隊列是空的,IMediaEvent::GetEvent函數調用就會重置該Event對象。應用程序可利用此事件來判斷隊列的狀態。
    應用程序可以調用IMediaEvent::GetEventHandle獲得Event內核對象的句柄,然後就可以調用WaitForMultipleObjects來等待事件的發生,如果Event被通知了,就可以調用GetEvent來獲得DirectShow的事件。下面的代碼演示了事件信號。返回事件句柄,再等待1000毫秒。如果事件變爲信號狀態,調用GetEvent返回事件Code和參數。獲取到EC_COMPLETE事件Code時表示回放結束中止循環:
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);
              switch (evCode)
             {
              case EC_COMPLETE: // Fall through.
              case EC_USERABORT: // Fall through.
             case EC_ERRORABORT:
             CleanUp();
             PostQuitMessage(0);
             return;
             }
        }
    }
}
發佈了77 篇原創文章 · 獲贊 0 · 訪問量 36萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章