Korzystanie z narzędzia Sample Grabber
[Funkcja skojarzona z tą stroną, DirectShow, jest starszą funkcją. Został zastąpiony przez MediaPlayer, IMFMediaEnginei Audio/Video Capture w Media Foundation. Te funkcje zostały zoptymalizowane pod kątem systemów Windows 10 i Windows 11. Firma Microsoft zdecydowanie zaleca, aby nowy kod używał MediaPlayer, IMFMediaEngine i Audio/Video Capture w programie Media Foundation zamiast DirectShow, jeśli to możliwe. Firma Microsoft sugeruje, że istniejący kod, który używa starszych interfejsów API, należy przepisać go do korzystania z nowych interfejsów API, jeśli to możliwe.]
[Ten interfejs API nie jest obsługiwany i może zostać zmieniony lub niedostępny w przyszłości.]
Filtr Sample Grabber jest filtrem przekształcającym, który może być używany do pobierania próbek multimediów ze strumienia, gdy przechodzą przez wykres filtra.
Jeśli chcesz pobrać mapę bitową z pliku wideo, łatwiej jest użyć obiektu Media Detector (MediaDet). Aby uzyskać szczegółowe informacje, zobacz Chwytanie ramki plakatu. Sample Grabber jest jednak bardziej elastyczny, ponieważ działa z niemal dowolnym typem nośnika (zobacz ISampleGrabber::SetMediaType dla kilku wyjątków) i oferuje aplikacji większą kontrolę.
- Utwórz menedżera grafu filtrów
- Dodaj próbnik do grafu filtrów
- Ustaw typ nośnika
- Zbuduj wykres filtru
- Uruchamianie programu Graph
- Pobierz próbkę
- przykładowy kod
- Tematy pokrewne
Tworzenie menedżera filtrów programu Graph
Aby rozpocząć, utwórz menedżera Filter Graph Manager i wykonaj zapytanie o interfejsy IMediaControl oraz 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;
}
Dodaj narzędzie Sample Grabber do grafu filtrów
Utwórz wystąpienie filtru Sample Grabber i dodaj go do wykresu filtru. Wykonaj zapytanie dotyczące filtru przykładowego narzędzia Grabber dla interfejsu 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;
}
Ustawianie typu nośnika
Podczas pierwszego tworzenia Sample Grabbera nie ma preferowanego typu multimediów. Oznacza to, że możesz nawiązać połączenie z niemal dowolnym filtrem na grafie, ale nie będziesz mieć kontroli nad typem odebranych danych. Przed utworzeniem pozostałej części grafu należy ustawić typ nośnika dla elementu Sample Grabber, wywołując metodę ISampleGrabber::SetMediaType.
Gdy Sample Grabber się łączy, porówna ten typ nośnika z typem nośnika oferowanym przez drugi filtr. Jedyne sprawdzane pola to typ główny, podtyp i typ formatu. W przypadku któregokolwiek z nich wartość GUID_NULL oznacza "akceptowanie dowolnej wartości". W większości przypadków należy ustawić typ główny i podtyp. Na przykład poniższy kod określa nieskompresowany 24-bitowy film 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;
}
Kompilowanie wykresu filtru
Teraz możesz utworzyć pozostałą część wykresu filtru. Ponieważ przykładowy moduł Grabber będzie łączyć się tylko przy użyciu określonego typu nośnika, umożliwia to korzystanie z intelligent connect mechanizmów filtrowania menedżera grafów podczas kompilowania grafu.
Jeśli na przykład określono nieskompresowane wideo, możesz połączyć filtr źródłowy z chwytaczem próbek, a Menedżer grafu filtrów automatycznie doda analizator plików i dekoder. W poniższym przykładzie użyto funkcji pomocnika ConnectFilters, która jest wymieniona w 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;
}
Łapacz Próbki jest filtrem transformacji, więc wyjście musi być połączone z innym filtrem. Często możesz po prostu odrzucić próbki po zakończeniu pracy z nimi. W takim przypadku połącz Sample Grabber z filtrem renderowania typu Null , który odrzuca odbierane dane.
Poniższy przykład łączy Sample Grabber z filtrem Null Renderer.
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;
}
Należy pamiętać, że umieszczenie elementu Sample Grabber między dekoderem wideo a modułem renderujący wideo może znacząco zaszkodzić wydajności renderowania. Przykładowy grabber jest filtrem trans-in-place, co oznacza, że bufor wyjściowy jest taki sam jak bufor wejściowy. W przypadku renderowania wideo bufor wyjściowy może znajdować się na karcie graficznej, gdzie operacje odczytu są znacznie wolniejsze w porównaniu z operacjami odczytu w pamięci głównej.
Uruchamianie grafu
Sample Grabber działa w jednym z dwóch trybów operacyjnych:
- Tryb buforowania tworzy kopię każdej próbki przed dostarczeniem próbki podrzędnej.
- Tryb wywołania zwrotnego uruchamia funkcję zwrotną zdefiniowaną przez aplikację dla każdej próbki.
W tym artykule opisano tryb buforowania. (Przed użyciem trybu wywołania zwrotnego należy pamiętać, że funkcja wywołania zwrotnego musi być dość ograniczona. W przeciwnym razie może drastycznie zmniejszyć wydajność, a nawet spowodować zakleszczenia. Aby uzyskać więcej informacji, zobacz ISampleGrabber::SetCallback.) Aby aktywować tryb buforowania, wywołaj metodę ISampleGrabber::SetBufferSamples metodę z wartością TRUE.
Opcjonalnie wywołaj metodę ISampleGrabber::SetOneShot z wartością TRUE. Powoduje to zatrzymanie działania narzędzia Sample Grabber po otrzymaniu pierwszej próbki multimedialnej, co jest przydatne, jeśli chcesz pobrać pojedynczą klatkę ze strumienia. Wyszukaj żądany czas, uruchom graf i poczekaj na zdarzenie EC_COMPLETE. Należy pamiętać, że poziom dokładności ramek zależy od źródła. Na przykład wyszukiwanie pliku MPEG często nie jest precyzyjne na poziomie klatek.
Aby uruchomić graf tak szybko, jak to możliwe, wyłącz zegar grafu zgodnie z opisem w Ustawienie zegara grafu.
W poniższym przykładzie włączono tryb jednorazowy i tryb buforowania, uruchamia się graf filtru i czeka na ukończenie.
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);
Pobierz próbkę
W trybie buforowania Sample Grabber przechowuje kopię każdej próbki. Metoda ISampleGrabber::GetCurrentBuffer kopiuje bufor do tablicy przydzielonej przez obiekt wywołujący. Aby określić rozmiar wymaganej tablicy, najpierw wywołaj GetCurrentBuffer z wskaźnikiem NULL dla adresu tablicy. Następnie przydziel tablicę i wywołaj metodę po raz drugi, aby skopiować bufor. W poniższym przykładzie przedstawiono te kroki.
// 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;
}
Musisz znać dokładny format danych w buforze. Aby uzyskać te informacje, wywołaj metodę ISampleGrabber::GetConnectedMediaType. Metoda ta wypełnia strukturę AM_MEDIA_TYPE określonym formatem.
W przypadku nieskompresowanego strumienia wideo informacje o formacie znajdują się w strukturze VIDEOINFOHEADER. W poniższym przykładzie pokazano, jak uzyskać informacje o formacie dla nieskompresowanego strumienia wideo.
// 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;
}
Notatka
Narzędzie Sample Grabber nie obsługuje VIDEOINFOHEADER2.
Przykładowy kod
Oto kompletny kod dla poprzednich przykładów.
Notatka
W tym przykładzie użyto funkcji SafeRelease, aby zwolnić wskaźniki interfejsu.
#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;
}
Tematy pokrewne