サンプル グラバーの使用

[このページに関連付けられている機能 DirectShow は、従来の機能です。 MediaPlayer、IMFMediaEngine、Media Foundation のオーディオ/ビデオ キャプチャに置き換わりました。 これらの機能は、Windows 10とWindows 11用に最適化されています。 新しいコードでは、可能であれば、DirectShow ではなく Media Foundation で MediaPlayerIMFMediaEngineAudio/Video Capture を使用することを強くお勧めします。 Microsoft は、レガシ 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;
    }

フィルター グラフにサンプル グラバーを追加する

サンプル グラバー フィルターのインスタンスを作成し、フィルター グラフに追加します。 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;
    }

フィルター グラフを作成する

これで、フィルター グラフの残りの部分を作成できます。 サンプル グラバーは指定したメディアの種類のみを使用して接続するため、グラフを作成するときにフィルター グラフ マネージャーの インテリジェント接続 メカニズムを利用できます。

たとえば、圧縮されていないビデオを指定した場合、ソース フィルターをサンプル グラバーに接続すると、フィルター グラフ マネージャーによってファイル パーサーとデコーダーが自動的に追加されます。 次の例では、ConnectFilters ヘルパー関数を使用します。これは、「 2 つのフィルターを接続する」に記載されています。

    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;
    }

ビデオ デコーダーとビデオ レンダラーの間にサンプル グラバーを配置すると、レンダリング パフォーマンスが大幅に低下する可能性があることに注意してください。 サンプル グラバーはトランスインプレース フィルターです。これは、出力バッファーが入力バッファーと同じであることを意味します。 ビデオ レンダリングの場合、出力バッファーはグラフィックス カードに配置される可能性が高く、読み取り操作はメモリ内の読み取り操作と比較してはるかに遅くなりますメイン。

グラフを実行する

サンプル グラバーは、次の 2 つのモードのいずれかで動作します。

  • バッファリング モードでは、サンプルをダウンストリームに配信する前に、各サンプルのコピーが作成されます。
  • コールバック モードでは、各サンプルでアプリケーション定義のコールバック関数が呼び出されます。

この記事では、バッファリング モードについて説明します。 (コールバック モードを使用する前に、コールバック関数が非常に制限されている必要があることに注意してください。そうしないと、パフォーマンスが大幅に低下したり、デッドロックが発生したりする可能性があります。詳細については、「ISampleGrabber::SetCallback」を参照してください。バッファリング モードをアクティブにするには、値 TRUE を指定して ISampleGrabber::SetBufferSamples メソッドを呼び出します。

必要に応じて、値 TRUE を指定して ISampleGrabber::SetOneShot メソッドを呼び出します。 これにより、最初のメディア サンプルを受信した後にサンプル グラバーが停止します。これは、ストリームから 1 つのフレームを取得する場合に便利です。 目的の時刻をシークし、グラフを実行し、 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 を呼び出します。 次に、配列を割り当て、 メソッドを 2 回目に呼び出してバッファーをコピーします。 次の例は、これらの手順を示しています。

    // 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 Editing Services の使用