샘플 그래버 사용
[이 페이지와 연결된 기능인 DirectShow는 레거시 기능입니다. MediaPlayer, IMFMediaEngine 및 Media Foundation의 오디오/비디오 캡처로 대체되었습니다. 이러한 기능은 Windows 10 및 Windows 11 최적화되었습니다. 가능한 경우 새 코드에서 DirectShow 대신 MediaPlayer, IMFMediaEngine 및 오디오/비디오 캡처를 사용하는 것이 좋습니다. 가능한 경우 레거시 API를 사용하는 기존 코드를 다시 작성하여 새 API를 사용하도록 제안합니다.]
[이 API는 지원되지 않으며 나중에 변경되거나 사용할 수 없습니다.]
샘플 그래버 필터는 필터 그래프를 통과할 때 스트림에서 미디어 샘플을 캡처하는 데 사용할 수 있는 변환 필터입니다.
비디오 파일에서 비트맵을 잡으려면 Media Detector(MediaDet) 개체를 사용하는 것이 더 쉽습니다. 자세한 내용은 포스터 프레임 잡기 를 참조하세요. 그러나 샘플 그래버는 거의 모든 미디어 형식(몇 가지 예외에 대해서는 ISampleGrabber::SetMediaType 참조)에서 작동하며 애플리케이션에 더 많은 제어를 제공하므로 더 유연합니다.
필터 그래프 관리자 만들기
시작하려면 필터 그래프 관리자 를 만들고 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;
}
필터 그래프에 샘플 그래버 추가
샘플 그래버 필터의 instance 만들고 필터 그래프에 추가합니다. 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 메서드를 호출하여 샘플 그래버에 대한 미디어 형식을 설정해야 합니다.
샘플 그래버가 연결되면 이 미디어 형식을 다른 필터에서 제공하는 미디어 형식과 비교합니다. 검사하는 유일한 필드는 주 형식, 하위 형식 및 형식 형식입니다. 이러한 값에 대해 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 Manager는 파일 파서와 디코더를 자동으로 추가합니다. 다음 예제에서는 ConnectFilters 도우미 함수를 사용합니다. 이 함수는 두 필터 연결에 나열되어 있습니다.
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;
}
관련 항목