DirectShow SDK筆記【關於DirectShow(3)】
4.3 Filter States
Filter有三種狀態,停止,暫停,運行。暫停狀態是爲了在Graph中Cue Data, 使得運行命令可以立即響應。Filter Graph Manager控制着所有狀態的轉換。當應用程序調用IMediaControl的Run, Pause, Stop方法時, Filter Graph Manager就調用所有Filter的相應IMediaFilter方法。停止,運行狀態的切換總是要經過暫停,因此,當應用程序對停止的Graph 調用RUN命令時,Filter圖表管理器在Run之前首先要暫停。對於大多數的Filter來說,Running和Paused狀態是一樣的。考慮如下的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保持暫停,在第一幀Sample後Sample就堆積在Graph, 直到每個Filter都阻塞在Receive或GetBuffer. 但是不會有數據丟失。一旦Source線程非阻塞,它簡單從阻塞的點恢復。
Source Filter和Transform 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 Filter的IMediaFilter方法GetState返回VFW_S_CANT_CUE。此返回值表示Filter已經轉換到暫停狀態,即使Renderer沒有接收到任何數據。
當一個Filter停止時,它拒絕發送給它的任何Samples,Source Filter關閉他們的Stream線程,其他Filter也關閉他們創建的工作線程,Pin再Decommit他們的內存分配器。
4.3.1 State Transitions
Filter Graph Manager按照逆流的方向來切換Filter的狀態,從Renderer Filter到Source Filter,這種方式可以防止死鎖和Sample的丟失。最關鍵的狀態切換是暫停和停止之間。
·從停止到暫停,當Filter暫停時就準備好了從連接Filter接收Sample。Source Filter是最後一個暫停的。它創建Streaming線程發送Sample,因爲下游的Filter的狀態都已經切換到暫停了,所以,所有的Filter都可以接收Sample。只有當Graph所有的Renderer都接收到Sample,Filter Graph Manager纔算完成了狀態的切換(除了前面講的實時源例外)。
·從暫停到停止。當Filter停止時它要釋放它擁有的所有的Sample,以使得上一級Filter中的IMemAllocator::GetBuffer調用脫離阻塞。如果Filter在Receive函數中等待資源,它也會停止等待,並從Receive返回,以使調用Filter解鎖。因此,當Filter Graph Manager停止連接的上一級Filter時,Filter就不會在GetBuffer和Receive函數中阻塞,也就能響應停止命令。上級Filter在得到停止命令前可以會遞送一些額外Sample。但是下級Filter可能拒絕他們,因爲他們已經停止。
4.4 Pull Model
在IMemInputPin接口中,上級Filter決定發送什麼樣數據,然後將數據推給下級Filter。但對某些Filter,拉模式更適合。現在,下級Filter向上級Filter請求數據。數據依然是從上級到下級,從輸出Pin到輸入Pin,但是由下級Filter發動數據流動。這種類型連接採用的是IAsyncReader接口。
拉模式的典型應用是文件回放。例如在AVI文件的回放Graph中,Async File Source Filter就執行通用文件讀寫操作,然後將數據以字節流的方式(不帶格式信息)發送給下級Filter。AVI Splitter讀取AVI頭並把數據流分析爲視頻、音頻流。AVI Splitter能比Async File Source Filter更好的決定它需要什麼類型的數據,因此它用IMemInputPin代替IAsyncReader接口。
爲了從輸出PIN請求數據,輸入PIN調用如下一種方法:
·IAsyncReader::Request
·IAsyncReader::SyncRead
·IAsyncReader::SyncReadAligned
第一個方法是異步的,支持多個重疊讀寫。其他是同步的。
理論上,任何Filter可以支持IAsyncReader,但是實際上它被設計來連接Source Filter和Parser Filter。Parser的功能與推模式中的Source Filter非常相同。當它暫停時,創建一個數據流線程從IAsyncReader連接拉數據,並推向下一級。輸出PIN使用IMemInputPin,Graph的餘下部分使用標準的推模式。
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, ¶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方法。這樣就可以屏蔽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_APP到0XBFFF之間的消息值作爲私有消息。只要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, ¶m1, ¶m2, 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;
}
}
}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.