使用範例擷取器
[與此頁面相關的功能 DirectShow是舊版功能。 它已被 MediaPlayer、 IMFMediaEngine和 Media Foundation 中的音訊/視訊擷取取代。 這些功能已針對Windows 10和Windows 11進行優化。 Microsoft 強烈建議新程式碼盡可能使用 MediaPlayer、 IMFMediaEngine 和 音訊/視訊擷取 ,而不是 DirectShow。 Microsoft 建議使用舊版 API 的現有程式碼盡可能重寫為使用新的 API。
[不支援此 API,未來可能會改變或無法使用。]
範例擷取器篩選是一種轉換篩選,可用來從資料流程擷取媒體樣本,因為它們通過篩選圖形。
如果您只是想要從視訊檔案抓取點陣圖,使用 Media Detector (MediaDet) 物件會比較容易。 如需詳細資訊 ,請參閱抓取海報框架 。 不過,範例擷取器更有彈性,因為它與幾乎任何媒體類型搭配使用, (請參閱 ISampleGrabber::SetMediaType ,以取得) 的幾個例外狀況,並提供更多控制權給應用程式。
建立篩選圖形管理員
若要開始,請建立 Filter Graph 管理員 ,並查詢 IMediaControl 和 IMediaEventEx 介面。
IGraphBuilder *pGraph = NULL;
IMediaControl *pControl = NULL;
IMediaEventEx *pEvent = NULL;
HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL,
CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&pGraph));
if (FAILED(hr))
{
goto done;
}
hr = pGraph->QueryInterface(IID_PPV_ARGS(&pControl));
if (FAILED(hr))
{
goto done;
}
hr = pGraph->QueryInterface(IID_PPV_ARGS(&pEvent));
if (FAILED(hr))
{
goto done;
}
將範例擷取器新增至篩選圖形
建立範例擷取器篩選準則的實例,並將其新增至篩選圖形。 查詢 ISampleGrabber 介面的範例擷取器篩選。
IBaseFilter *pGrabberF = NULL;
ISampleGrabber *pGrabber = NULL;
// Create the Sample Grabber filter.
hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pGrabberF));
if (FAILED(hr))
{
goto done;
}
hr = pGraph->AddFilter(pGrabberF, L"Sample Grabber");
if (FAILED(hr))
{
goto done;
}
hr = pGrabberF->QueryInterface(IID_PPV_ARGS(&pGrabber));
if (FAILED(hr))
{
goto done;
}
設定媒體類型
當您第一次建立範例擷取器時,它沒有慣用的媒體類型。 這表示您可以連線到圖形中幾乎任何篩選,但您無法控制它收到的資料類型。 因此,在建置圖形的其餘部分之前,您必須呼叫 ISampleGrabber::SetMediaType 方法,為 Sample Grabber 設定媒體類型。
當範例擷取器連線時,它會比較此媒體類型與其他篩選準則所提供的媒體類型。 唯一檢查的欄位是主要類型、子類型和格式類型。 對於其中任何一項,值GUID_Null表示「接受任何值」。大部分時候,您會想要設定主要類型和子類型。 例如,下列程式碼會指定未壓縮的 24 位 RGB 影片:
AM_MEDIA_TYPE mt;
ZeroMemory(&mt, sizeof(mt));
mt.majortype = MEDIATYPE_Video;
mt.subtype = MEDIASUBTYPE_RGB24;
hr = pGrabber->SetMediaType(&mt);
if (FAILED(hr))
{
goto done;
}
建置篩選圖形
現在您可以建置篩選圖表的其餘部分。 因為範例擷取器只會使用您指定的媒體類型進行連線,這可讓您在建置圖形時利用 Filter Graph Manager 的 Intelligent Connect 機制。
例如,如果您指定了未壓縮的視訊,您可以將來源篩選連線到範例抓取器,而 Filter Graph 管理員會自動新增檔案剖析器和解碼器。 下列範例使用 ConnectFilters 協助程式函式,此函式列于 Connect Two Filters中:
IBaseFilter *pSourceF = NULL;
IEnumPins *pEnum = NULL;
IPin *pPin = NULL;
hr = pGraph->AddSourceFilter(pszVideoFile, L"Source", &pSourceF);
if (FAILED(hr))
{
goto done;
}
hr = pSourceF->EnumPins(&pEnum);
if (FAILED(hr))
{
goto done;
}
while (S_OK == pEnum->Next(1, &pPin, NULL))
{
hr = ConnectFilters(pGraph, pPin, pGrabberF);
SafeRelease(&pPin);
if (SUCCEEDED(hr))
{
break;
}
}
if (FAILED(hr))
{
goto done;
}
範例擷取器是轉換篩選準則,因此輸出針腳必須連接到另一個篩選準則。 通常,您可能只想在完成這些範例之後捨棄這些範例。 在此情況下,將範例擷取器連接到 Null 轉譯器篩選,這會捨棄它所接收的資料。
下列範例會將範例擷取器連接到 Null 轉譯器篩選準則:
IBaseFilter *pNullF = NULL;
hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pNullF));
if (FAILED(hr))
{
goto done;
}
hr = pGraph->AddFilter(pNullF, L"Null Filter");
if (FAILED(hr))
{
goto done;
}
hr = ConnectFilters(pGraph, pGrabberF, pNullF);
if (FAILED(hr))
{
goto done;
}
請注意,將範例擷取器放在影片解碼器和視訊轉譯器之間,可能會大幅降低轉譯效能。 範例擷取器是就地轉換篩選,這表示輸出緩衝區與輸入緩衝區相同。 針對視訊轉譯,輸出緩衝區可能位於圖形卡上,相較于主要記憶體中的讀取作業,讀取作業的速度會比較慢。
執行圖形
範例擷取器會以兩種模式的其中一種運作:
- 緩衝模式會在傳遞範例下游之前,先製作每個樣本的複本。
- 回呼模式會在每個範例上叫用應用程式定義的回呼函式。
本文說明緩衝模式。 (使用回呼模式之前,請注意回呼函式必須相當有限。否則,它可以大幅降低效能,甚至造成死結。如需詳細資訊,請參閱ISampleGrabber::SetCallback.) 若要啟用緩衝模式,請使用TRUE值呼叫ISampleGrabber::SetBufferSamples方法。
或者,使用TRUE值呼叫ISampleGrabber::SetOneShot方法。 這會導致範例擷取器在收到第一個媒體範例之後停止,如果您想要從資料流程擷取單一畫面格,這會很有用。 搜尋所需的時間、執行圖表,並等候 EC_COMPLETE 事件。 請注意,畫面精確度的層級取決於來源。 例如,搜尋 MPEG 檔案通常不精確。
若要儘快執行圖表,請關閉圖形時鐘,如 設定圖形時鐘中所述。
下列範例會啟用單次模式和緩衝模式、執行篩選圖表,並等候完成。
hr = pGrabber->SetOneShot(TRUE);
if (FAILED(hr))
{
goto done;
}
hr = pGrabber->SetBufferSamples(TRUE);
if (FAILED(hr))
{
goto done;
}
hr = pControl->Run();
if (FAILED(hr))
{
goto done;
}
long evCode;
hr = pEvent->WaitForCompletion(INFINITE, &evCode);
擷取範例
在緩衝模式中,範例擷取器會儲存每個樣本的複本。 ISampleGrabber::GetCurrentBuffer方法會將緩衝區複製到呼叫端配置的陣列。 若要判斷所需的陣列大小,請先使用陣列位址的Null指標呼叫GetCurrentBuffer。 然後配置陣列,然後第二次呼叫 方法以複製緩衝區。 下列範例會示範這些步驟。
// Find the required buffer size.
long cbBuffer;
hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL);
if (FAILED(hr))
{
goto done;
}
pBuffer = (BYTE*)CoTaskMemAlloc(cbBuffer);
if (!pBuffer)
{
hr = E_OUTOFMEMORY;
goto done;
}
hr = pGrabber->GetCurrentBuffer(&cbBuffer, (long*)pBuffer);
if (FAILED(hr))
{
goto done;
}
您必須知道緩衝區中資料的確切格式。 若要取得這項資訊,請呼叫 ISampleGrabber::GetConnectedMediaType 方法。 這個方法會以 格式填入 AM_MEDIA_TYPE 結構。
若為未壓縮的視訊資料流程,格式資訊會包含在 VIDEOINFOHEADER 結構中。 下列範例示範如何取得未壓縮視訊資料流程的格式資訊。
// Examine the format block.
if ((mt.formattype == FORMAT_VideoInfo) &&
(mt.cbFormat >= sizeof(VIDEOINFOHEADER)) &&
(mt.pbFormat != NULL))
{
VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)mt.pbFormat;
hr = WriteBitmap(pszBitmapFile, &pVih->bmiHeader,
mt.cbFormat - SIZE_PREHEADER, pBuffer, cbBuffer);
}
else
{
// Invalid format.
hr = VFW_E_INVALIDMEDIATYPE;
}
注意
範例抓取器不支援 VIDEOINFOHEADER2。
範例程式碼
以下是先前範例的完整程式碼。
注意
此範例會使用 SafeRelease 函式來釋放介面指標。
#include <windows.h>
#include <dshow.h>
#include "qedit.h"
template <class T> void SafeRelease(T **ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
HRESULT WriteBitmap(PCWSTR, BITMAPINFOHEADER*, size_t, BYTE*, size_t);
HRESULT GrabVideoBitmap(PCWSTR pszVideoFile, PCWSTR pszBitmapFile)
{
IGraphBuilder *pGraph = NULL;
IMediaControl *pControl = NULL;
IMediaEventEx *pEvent = NULL;
IBaseFilter *pGrabberF = NULL;
ISampleGrabber *pGrabber = NULL;
IBaseFilter *pSourceF = NULL;
IEnumPins *pEnum = NULL;
IPin *pPin = NULL;
IBaseFilter *pNullF = NULL;
BYTE *pBuffer = NULL;
HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL,
CLSCTX_INPROC_SERVER,IID_PPV_ARGS(&pGraph));
if (FAILED(hr))
{
goto done;
}
hr = pGraph->QueryInterface(IID_PPV_ARGS(&pControl));
if (FAILED(hr))
{
goto done;
}
hr = pGraph->QueryInterface(IID_PPV_ARGS(&pEvent));
if (FAILED(hr))
{
goto done;
}
// Create the Sample Grabber filter.
hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pGrabberF));
if (FAILED(hr))
{
goto done;
}
hr = pGraph->AddFilter(pGrabberF, L"Sample Grabber");
if (FAILED(hr))
{
goto done;
}
hr = pGrabberF->QueryInterface(IID_PPV_ARGS(&pGrabber));
if (FAILED(hr))
{
goto done;
}
AM_MEDIA_TYPE mt;
ZeroMemory(&mt, sizeof(mt));
mt.majortype = MEDIATYPE_Video;
mt.subtype = MEDIASUBTYPE_RGB24;
hr = pGrabber->SetMediaType(&mt);
if (FAILED(hr))
{
goto done;
}
hr = pGraph->AddSourceFilter(pszVideoFile, L"Source", &pSourceF);
if (FAILED(hr))
{
goto done;
}
hr = pSourceF->EnumPins(&pEnum);
if (FAILED(hr))
{
goto done;
}
while (S_OK == pEnum->Next(1, &pPin, NULL))
{
hr = ConnectFilters(pGraph, pPin, pGrabberF);
SafeRelease(&pPin);
if (SUCCEEDED(hr))
{
break;
}
}
if (FAILED(hr))
{
goto done;
}
hr = CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&pNullF));
if (FAILED(hr))
{
goto done;
}
hr = pGraph->AddFilter(pNullF, L"Null Filter");
if (FAILED(hr))
{
goto done;
}
hr = ConnectFilters(pGraph, pGrabberF, pNullF);
if (FAILED(hr))
{
goto done;
}
hr = pGrabber->SetOneShot(TRUE);
if (FAILED(hr))
{
goto done;
}
hr = pGrabber->SetBufferSamples(TRUE);
if (FAILED(hr))
{
goto done;
}
hr = pControl->Run();
if (FAILED(hr))
{
goto done;
}
long evCode;
hr = pEvent->WaitForCompletion(INFINITE, &evCode);
// Find the required buffer size.
long cbBuffer;
hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL);
if (FAILED(hr))
{
goto done;
}
pBuffer = (BYTE*)CoTaskMemAlloc(cbBuffer);
if (!pBuffer)
{
hr = E_OUTOFMEMORY;
goto done;
}
hr = pGrabber->GetCurrentBuffer(&cbBuffer, (long*)pBuffer);
if (FAILED(hr))
{
goto done;
}
hr = pGrabber->GetConnectedMediaType(&mt);
if (FAILED(hr))
{
goto done;
}
// Examine the format block.
if ((mt.formattype == FORMAT_VideoInfo) &&
(mt.cbFormat >= sizeof(VIDEOINFOHEADER)) &&
(mt.pbFormat != NULL))
{
VIDEOINFOHEADER *pVih = (VIDEOINFOHEADER*)mt.pbFormat;
hr = WriteBitmap(pszBitmapFile, &pVih->bmiHeader,
mt.cbFormat - SIZE_PREHEADER, pBuffer, cbBuffer);
}
else
{
// Invalid format.
hr = VFW_E_INVALIDMEDIATYPE;
}
FreeMediaType(mt);
done:
CoTaskMemFree(pBuffer);
SafeRelease(&pPin);
SafeRelease(&pEnum);
SafeRelease(&pNullF);
SafeRelease(&pSourceF);
SafeRelease(&pGrabber);
SafeRelease(&pGrabberF);
SafeRelease(&pControl);
SafeRelease(&pEvent);
SafeRelease(&pGraph);
return hr;
};
// Writes a bitmap file
// pszFileName: Output file name.
// pBMI: Bitmap format information (including pallete).
// cbBMI: Size of the BITMAPINFOHEADER, including palette, if present.
// pData: Pointer to the bitmap bits.
// cbData Size of the bitmap, in bytes.
HRESULT WriteBitmap(PCWSTR pszFileName, BITMAPINFOHEADER *pBMI, size_t cbBMI,
BYTE *pData, size_t cbData)
{
HANDLE hFile = CreateFile(pszFileName, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, 0, NULL);
if (hFile == NULL)
{
return HRESULT_FROM_WIN32(GetLastError());
}
BITMAPFILEHEADER bmf = { };
bmf.bfType = 'MB';
bmf.bfSize = cbBMI+ cbData + sizeof(bmf);
bmf.bfOffBits = sizeof(bmf) + cbBMI;
DWORD cbWritten = 0;
BOOL result = WriteFile(hFile, &bmf, sizeof(bmf), &cbWritten, NULL);
if (result)
{
result = WriteFile(hFile, pBMI, cbBMI, &cbWritten, NULL);
}
if (result)
{
result = WriteFile(hFile, pData, cbData, &cbWritten, NULL);
}
HRESULT hr = result ? S_OK : HRESULT_FROM_WIN32(GetLastError());
CloseHandle(hFile);
return hr;
}
相關主題