샘플 그래버 사용

[이 페이지와 연결된 기능인 DirectShow는 레거시 기능입니다. MediaPlayer, IMFMediaEngineMedia Foundation의 오디오/비디오 캡처로 대체되었습니다. 이러한 기능은 Windows 10 및 Windows 11 최적화되었습니다. 가능한 경우 새 코드에서 DirectShow 대신 MediaPlayer, IMFMediaEngine오디오/비디오 캡처를 사용하는 것이 좋습니다. 가능한 경우 레거시 API를 사용하는 기존 코드를 다시 작성하여 새 API를 사용하도록 제안합니다.]

[이 API는 지원되지 않으며 나중에 변경되거나 사용할 수 없습니다.]

샘플 그래버 필터는 필터 그래프를 통과할 때 스트림에서 미디어 샘플을 캡처하는 데 사용할 수 있는 변환 필터입니다.

비디오 파일에서 비트맵을 잡으려면 Media Detector(MediaDet) 개체를 사용하는 것이 더 쉽습니다. 자세한 내용은 포스터 프레임 잡기 를 참조하세요. 그러나 샘플 그래버는 거의 모든 미디어 형식(몇 가지 예외에 대해서는 ISampleGrabber::SetMediaType 참조)에서 작동하며 애플리케이션에 더 많은 제어를 제공하므로 더 유연합니다.

필터 그래프 관리자 만들기

시작하려면 필터 그래프 관리자 를 만들고 IMediaControlIMediaEventEx 인터페이스를 쿼리합니다.

    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을 참조하세요. 버퍼링 모드를 활성화하려면 값이 TRUEISampleGrabber::SetBufferSamples 메서드를 호출합니다.

필요에 따라 값이 TRUEISampleGrabber::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&quot;Sample Grabber&quot;);
    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&quot;Source&quot;, &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&quot;Null Filter&quot;);
    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 = &#39;MB&#39;;
    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;
}

DirectShow 편집 서비스 사용