當前位置:編程學習大全網 - 源碼下載 - 如何從 Microsoft DirectShow 篩選器圖形獲取數據

如何從 Microsoft DirectShow 篩選器圖形獲取數據

編寫 Grabber 篩選器示例

從 DirectShow 篩選器圖形獲取數據的壹個簡單方式是編寫壹個自定義“grabber”篩選器示例。將該篩選器與您要監視的數據流進行連接,然後運行篩選器圖形。當數據通過篩選器進行傳遞時,應用程序會根據您的想法對數據進行操作。

這些 grabber 篩選器示例的可能用途包括:

將整個文件解碼到內存緩沖區。

從視頻文件獲取壹個貼幀。

從實時視頻流獲取靜態圖像。

將視頻文件解碼到 Microsoft DirectDraw? 緩沖區中。

DirectShow 8.0 SDK 包括了壹個 grabber 篩選器示例,但是沒有提供源代碼。DirectShow 8.1 SDK 則包括了這個 grabber 篩選器示例改進版本的源代碼,作為壹個 SDK 示例,其名稱為 GrabberSample Filter Sample。

從何處開始?

首先您要選擇是編寫轉換篩選器還是輸出程序。轉換篩選器可以連接另壹個下遊篩選器,使您能夠呈現數據、將其寫入文件,以及執行其他操作。但是因為轉換篩選器需要壹個附加的下遊連接,所以要使其正確實現可能更復雜壹些。輸出程序篩選器則只需要壹個輸入連接。

本文將說明如何編寫壹個轉換篩選器,但是很多理念同樣適用於輸出程序篩選器。

本文講述的這個篩選器是壹個“就地轉換”篩選器,這就表示它會直接在它接收的緩沖區中修改數據,而不會創建新緩沖區的副本。它使用 DirectShow 基類庫。

要編寫壹個就地轉換篩選器,請執行下列步驟:

1.

定義壹個從 CTransInPlaceFilter 類派生的新類。

2.

您可以使篩選器成為壹個能夠執行自註冊的真正的 COM 對象。為此,您需要壹個帶有 CLSID 定義的 IDL 文件或標頭文件,壹個導出 DLL 函數的 DEF 文件,還需要壹個靜態類方法,才能創建篩選器。有關詳細信息,請參閱 DirectShow SDK 文檔中有關如何創建 DLL 和如何註冊 DirectShow 篩選器的主題。

3.

重寫 CTransInPlaceFilter 中的兩個純虛擬方法:Transform 方法和 CheckInputType 方法。

CTransInPlaceFilter 類會自動處理大量其他任務,例如:協商針連接和緩沖器,在必要時重新連接針,將數據從輸入針移動到輸出針,以及支持多個線程。閱讀這些基類的 C++ 代碼是了解 DirectShow 篩選器更多詳細信息的壹種很好的方法。如果您想執行壹些更復雜的操作,可能需要重寫 CTransInPlaceFilter 的其他方法。

重寫 CheckInputType 方法

篩選器中的 CheckInputType 方法決定了要接受哪些媒體類型,要拒絕哪些媒體類型。在針連接過程中,上遊針會提出各種媒體類型。您的篩選器可以接受任何媒體類型,也可以拒絕任何媒體類型。DirectShow 在構建篩選器圖形時,會自動嘗試找出註冊表中列出的篩選器,以使得連接能夠運行。例如,如果您的篩選器只接受未壓縮的視頻,則當應用程序嘗試將其連接到壹個 AVI 文件源時,DirectShow 會插入相應的視頻解壓縮程序。

格式類型

如果您的篩選器只接受帶有子類型 MEDIASUBTYPE_RGB24 的 MEDIATYPE_Video,則無需與 FORMAT_VideoInfo 格式類型進行連接。還存在幾個其他的視頻格式類型,其中包括 Format_VideoInfo2 和 FORMAT_DvInfo。您必須決定篩選器要處理哪些格式,以及相應接受或拒絕哪些不同的格式類型。

格式塊和反轉的 DIB

對於未壓縮的視頻類型,上遊篩選器可能要傳遞反轉的設備無關位圖 (DIB)。在連接時,它會在格式塊的 BITMAPINFOHEADER 結構的 biHeight 成員中指定上述內容。因此,如果您的篩選器需要壹種特定的 DIB 方向(反轉或不反轉),則請確保檢查 biHeight 成員,並拒絕篩選器不會處理的任何類型。

很多解壓縮器都可以使用兩個方向進行解碼,並且會同時建議兩種類型。如果您不檢查方向就接受媒體類型,這些針則會使用解壓縮器提出的第壹個方向進行連接。

在應用程序中設置媒體類型

在 grabber 篩選器示例中,讓應用程序來控制篩選器接受哪些媒體類型比較有意義。應用程序使用這種方式可以執行下列步驟:

1.

應用程序針對篩選器調用壹個自定義方法,來指定您想要的數據類型。這可以是壹種具體的格式,也可能是允許壹些可能格式的通用說明(例如,任何大小的 24 位 RGB 視頻)。

2.

應用程序將該 grabber 示例與圖形中的其他篩選器進行連接。在針協商過程中,CheckInput 方法嘗試將建議的媒體類型與應用程序在第壹步中指定的類型進行匹配。

3.

該應用程序調用另壹個自定義方法來檢索真正用於該連接的媒體類型。

例如,在第壹步中,應用程序可能指定了 24 位 RGB。在第二步中,這些針將要使用壹個特定的視頻大小進行連接,如 320 X 240 像素。在第三步中,應用程序檢索媒體類型,以確定視頻大小。如果沒有此信息,應用程序則無法解釋接收到的數據。

您必須在篩選器上定義壹個包含這兩個方法的自定義 COM 接口。DirectShow Grabber 篩選器示例使用的是 ISampleGrabber 接口;創建您自己的篩選器時,您可以將其用作指南。

重寫轉換方法

CTransInPlaceFilter 構造函數方法的其中壹個參數是指定篩選器是否修改它所接收的數據的標誌。如果您傳遞值 false,則不得以任何方式更改數據。否則,可以在Transform 方法中任意修改數據。

Transform 方法會接收壹個指向媒體示例 IMediaSample 接口的指針。這種方法稱為 CTransInPlaceFilter::Receive 方法。在 Transform 方法返回之後,Receive方法會針對輸出針調用 CBaseOutputPin::Deliver,以傳遞該示例。

如果 Transform 方法返回 S_FALSE,該基類則會發出壹個質量控制更改信號。但是,在本例中,Receive 方法返回了 S_OK(而不是 S_FALSE),因此上遊篩選器繼續傳遞。如果 Transform 方法返回錯誤代碼,該基類則會向篩選器圖形發出壹個流式處理錯誤信號,而且篩選器圖形停止。除非有真正的流式處理錯誤,否則不應返回錯誤代碼。如果您只是想停止該流,則應該重寫 Receive 方法,並從 Receive 返回 S_FALSE。

返回頁首

使用多線程處理

您的應用程序運行所基於的線程總是應該與向篩選器傳遞數據的線程不同。如果您想在應用程序中同步檢索數據,則必須考慮這種多線程處理。下面是用於處理壹些常見情況的建議:

解碼整個文件

如果您想要解碼整個壓縮文件,並按照順序獲取每個未壓縮的數據塊,則可能不需要擔心線程處理的問題。在應用程序中創建壹個全局緩沖區,編寫 Transform 方法,以便它寫入該緩沖區。另外,還可以讓 Transform 每當接收示例時都調用壹個回調方法。在該回調方法中寫入全局緩沖區。在您的應用程序中,設置該回調,運行圖形直到停止,這樣就完成了此過程。

解碼部分文件

這種情況與解碼整個文件相似,只是應用程序需要使用 IMediaSeeking::SetPositions 方法來設置開始和停止位置。還有壹種方法是,從 Receive 方法返回 S_FALSE,以便向源篩選器發送停止傳遞數據的信號。

解碼文件的隨機部分

如果您想要解碼文件的壹部分,然後搜索另壹個位置再進行解碼,這個過程就變得更復雜了。當您搜索篩選器圖形,或者從壹種圖形狀態更改為另壹種圖形狀態時,應用程序必須等待圖形狀態變為穩定狀態。

當您搜索圖形(使用 IMediaSeeking 或 IMediaPosition)時,首先會從輸出程序篩選器啟動調用,然後向上遊進行同步移動,直到到達源篩選器。源篩選器會異步 停止推數據,向下遊發送刷新,搜索新的位置,然後再次開始發送數據。

要獲取單個數據幀,請重寫 Receive 方法,以返回 S_FALSE。在應用程序中,暫停該圖形,並搜索到所需的時間。該源將通過搜索進行響應,然後它會向下遊發送壹個示例。

如果您希望應用程序同步處理示例,而不是進行異步處理,則請使用事件。在 Transform 方法中設置事件,並在應用程序中等待該事件。例如,您可以使用壹個如下所示的循環:

while (not done)

Seek the filter graph.

Wait for the event to be signaled.

此示例假設您完全在 Transform 方法內部或者回調方法內部處理數據。如果您希望在該應用程序循環中處理數據,則需要第二個事件。

while (not done)

Seek the filter graph.

Wait for event 1 to be signaled.

Process the data.

Signal event 2.

編寫篩選器的 Transform 方法,如下所示:

Transform:

Signal event 1.

Wait for event 2.

Return S_FALSE.

如果沒有第二個事件,Transform 方法則會立即返回,這是因為它是在另外壹個線程上運行的。然後,其他篩選器可能會在應用程序仍然處理舊數據的同時向該示例寫入新的數據。

註 還有壹個選擇就是,在 Transform 方法中針對該示例調用 AddRef,然後在應用程序中針對該示例調用 Release。通過在示例中保留壹個參考數,可以防止它返回到“可用”列表。但是,這樣無法阻止下遊示例修改該示例。有關參考計數和 IMediaSample 接口的詳細信息,請參閱 SDK 文檔中的“示例和分配器”主題。

返回頁首

應用程序代碼示例

下面的代碼是壹個使用 grabber 篩選器示例的控制臺應用程序:

#include "stdafx.h"

#include <atlbase.h>

#include <streams.h>

#include <qedit.h> // for Null Renderer

#include <filfuncs.h> // for GetOutPin, GetInPin

#include <filfuncs.cpp> // for GetOutPin, GetInPin

#include "SampleGrabber.h"

int test(int argc, char* argv[]);

int main(int argc, char* argv[])

{

CoInitialize( NULL );

int i = test( argc, argv );

CoUninitialize();

return i;

}

HANDLE gWaitEvent = NULL;

HRESULT Callback(IMediaSample* pSample, REFERENCE_TIME* StartTime,

REFERENCE_TIME* StopTime)

{

// Note: We cannot do anything with this sample until we call

// GetConnectedMediaType on the filter to find out the format.

DbgLog((LOG_TRACE, 0, "Callback with sample %lx for time %ld",

pSample, long(*StartTime / 10000)));

SetEvent(gWaitEvent);

return S_FALSE; // Tell the source to stop delivering samples.

}

int test( int argc, char * argv[] )

{

// Create an event, which is signaled when we get a sample.

gWaitEvent = CreateEvent( NULL, FALSE, FALSE, NULL );

// The sample grabber is not in the registry, so create it with 'new'.

HRESULT hr = S_OK;

CSampleGrabber *pGrab = new CSampleGrabber(NULL, &hr, FALSE);

pGrab->AddRef();

// Set the callback function of the filter.

pGrab->SetCallback(&Callback);

// Set up a partially specified media type.

CMediaType mt;

mt.SetType(&MEDIATYPE_Video);

mt.SetSubtype(&MEDIASUBTYPE_RGB24);

hr = pGrab->SetAcceptedMediaType(&mt);

// Create the filter graph manager.

CComPtr<IFilterGraph> pGraph;

hr = pGraph.CoCreateInstance( CLSID_FilterGraph );

// Query for other useful interfaces.

CComQIPtr<IGraphBuilder, &IID_IGraphBuilder> pBuilder(pGraph);

CComQIPtr<IMediaSeeking, &IID_IMediaSeeking> pSeeking(pGraph);

CComQIPtr<IMediaControl, &IID_IMediaControl> pControl(pGraph);

CComQIPtr<IMediaFilter, &IID_IMediaFilter> pMediaFilter(pGraph);

CComQIPtr<IMediaEvent, &IID_IMediaEvent> pEvent(pGraph);

// Add a source filter to the graph.

CComPtr<IBaseFilter> pSource;

hr = pBuilder->AddSourceFilter(L"C:\\test.avi", L"Source", &pSource);

// Add the sample grabber to the graph.

hr = pBuilder->AddFilter(pGrab, L"Grabber");

// Find the input and output pins, and connect them.

IPin *pSourceOut = GetOutPin(pSource, 0);

IPin *pGrabIn = GetInPin(pGrab, 0);

hr = pBuilder->Connect(pSourceOut, pGrabIn);

// Create the Null Renderer filter and add it to the graph.

CComPtr<IBaseFilter> pNull;

hr = pNull.CoCreateInstance(CLSID_NullRenderer);

hr = pBuilder->AddFilter(pNull, L"Renderer");

// Get the other input and output pins, and connect them.

IPin *pGrabOut = GetOutPin(pGrab, 0);

IPin *pNullIn = GetInPin(pNull, 0);

hr = pBuilder->Connect(pGrabOut, pNullIn);

// Show the graph in the debug output.

DumpGraph(pGraph, 0);

// Note: The graph is built, but we do not know the format yet.

// To find the format, call GetConnectedMediaType. For this example,

// we just write some information to the debug window.

REFERENCE_TIME Duration = 0;

hr = pSeeking->GetDuration(&Duration);

BOOL Paused = FALSE;

long t1 = timeGetTime();

for(int i = 0 ; i < 100 ; i++)

{

// Seek the graph.

REFERENCE_TIME Seek = Duration * i / 100;

hr = pSeeking->SetPositions(&Seek, AM_SEEKING_AbsolutePositioning,

NULL, AM_SEEKING_NoPositioning );

// Pause the graph, if it is not paused yet.

if( !Paused )

{

hr = pControl->Pause();

ASSERT(!FAILED(hr));

Paused = TRUE;

}

// Wait for the source to deliver a sample. The callback returns

// S_FALSE, so the source delivers one sample per seek.

WaitForSingleObject(gWaitEvent, INFINITE);

}

long t2 = timeGetTime();

DbgLog((LOG_TRACE, 0, "Frames per second = %ld", i * 1000/(t2 - t1)));

pGrab->Release();

return 0;

}

  • 上一篇:為什麽績效測量標準只有次優,沒有最優
  • 下一篇:求mysql用like檢索字段中帶有數字的語句
  • copyright 2024編程學習大全網