Microsoft DirectX 8.0

メディア サンプルの獲得

このトピックでは、サンプル グラバ フィルタを使ってメディア サンプルを取り出す方法を解説する。

Microsoft® DirectShow® アークテクチャでは、アプリケーションがメディア データを直接操作することはない。メディア データの変更が必要な場合は、そのための変換フィルタまたは Microsoft® DirectX® Media Object (DMO) を書く必要がある。一方、ビデオ ファイルからフレームを表示するなど、メディア データを変更せずに使用する必要がある場合は、サンプル グラバ フィルタを使用できる。

このトピックは、以下のセクションで構成される。

フィルタ グラフへのサンプル グラバの追加

サンプル グラバは、ISampleGrabber インターフェイスをサポートする変換フィルタである。このフィルタは、すべてのサンプルを変更しないでダウンストリームに送るので、データ ストリームを変更しなくてもフィルタ グラフに挿入できる。サンプル グラバを使用すると、サンプルがフィルタを通過するときにサンプルを獲得できる。

デフォルトでは、サンプル グラバには優先されるメディア タイプはない。サンプル グラバをフィルタ グラフに挿入する前に、ISampleGrabber::SetMediaType メソッドを呼び出して入力ピンのメディア タイプを設定する。メディア タイプを設定することで、フィルタ グラフ マネージャがグラフ内の正しい位置にサンプル グラバを挿入することが保証される。

SetMediaType メソッドは、メディア タイプを記述する AM_MEDIA_TYPE 構造体へのポインタを受け取る。状況に応じて、サブタイプまたはフォーマット タイプを GUID_NULL に設定できる。GUID_NULL は、"未設定" を示す。次の例では、サンプル グラバをフィルタ グラフに追加し、メディア タイプに 24 ビット非圧縮 RGB ビデオを設定している。

#include <dshow.h>
#include <qedit.h>

IBaseFilter     *pF = NULL;
ISampleGrabber  *pGrab = NULL;  // これらは後で必ず解放すること。
AM_MEDIA_TYPE   mt;

CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER, 
                    IID_IBaseFilter, (LPVOID *)&pF);
pF->QueryInterface(IID_ISampleGrabber, (void **)&pGrab);


pGraph->AddFilter(pF, L"Grabber");

ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE));
mt.majortype = MEDIATYPE_Video;
mt.subtype = MEDIASUBTYPE_RGB24;
mt.formattype = FORMAT_VideoInfo; 
hr = pGrab->SetMediaType(&mt);

これで、グラフの残りの部分を構築し、サンプル グラバ フィルタを接続できる。そのためには、さまざまな方法があるが、その例を次に示す。

たとえば、次のコードでは、Example.avi ファイルの再生グラフが構築される。

pGraph->RenderFile(L"C:\\Example.avi", NULL);

サンプル グラバのメディア タイプは非圧縮ビデオに設定されているので、フィルタ グラフ マネージャは、非圧縮ビデオ サンプルを受け取る場所である、ビデオ デコンプレッサとビデオ レンダラの間にフィルタを配置する。

レンダリングしないでサンプルを獲得したい場合は、サンプル グラバの出力ピンを Null レンダリング フィルタに接続する。このフィルタは、受け取ったサンプルを破棄する。

バッファとワンショット モード

サンプル グラバは、内部バッファに受け取るサンプルをコピーできる。バッファリングを有効にするには、値 TRUE を指定して ISampleGrabber::SetBufferSamples メソッドを呼び出す。サンプルのコピーを取得するには、ISampleGrabber::GetCurrentBuffer メソッドを呼び出す。

各サンプルは、バッファにある以前のサンプルにオーバーライドされる。ストリームの特定のポイントからサンプルを獲得するには、サンプル グラバを "ワンショット" モードに切り替える。ワンショット モードでは、サンプル グラバは 1 つのサンプルを受け取るとすぐにグラフを停止する。ワンショット モードを有効にするには、値 TRUE を指定して ISampleGrabber::SetOneShot メソッドを呼び出す。

目的の時間にシークし、グラフを実行し、グラフが停止するのを待つ。次のコードは、その方法を示している。

// これらのインターフェイス (示されない) についてフィルタ グラフ マネージャに問い合せる。
IMediaControl   *pMediaControl = NULL;
IMediaSeeking   *pSeek = NULL;
IMediaEvent     *pEvent = NULL;

// ワンショット モードを設定する。
pGrab->SetBufferSamples(TRUE);
pGrab->SetOneShot(TRUE);

// 3 秒シークする。
REFERENCE_TIME rtStart = 3 * 10000000;
REFERENCE_TIME rtStop = rtStart; 

hr = pSeek->SetPositions(&rtStart, AM_SEEKING_AbsolutePositioning, 
                         &rtStop, AM_SEEKING_AbsolutePositioning);

// グラフを実行し、終了を待つ。
long evCode;
hr = pMediaControl->Run();
hr = pEvent->WaitForCompletion(INFINITE, &evCode);

次に、WaitForCompletion メソッドが返り、サンプル グラバのバッファにビデオ フレームのコピーが格納される。バッファには、サンプルのメディア データの部分だけが含まれ、フォーマット ヘッダーは含まれていない。フォーマット ヘッダーを獲得するには、ISampleGrabber::GetConnectedMediaType を呼び出す。このメソッドは、AM_MEDIA_TYPE 構造体を返す。この構造体の pbFormat メンバが、フォーマット ヘッダーを指している。使用後は、必ずバッファ メモリを解放すること。

例として、次のコードはフォーマット ヘッダーとサンプル バッファを使って、ビデオ ストリームからデバイスに依存しないビットマップ (DIB) を作成する。

AM_MEDIA_TYPE MediaType;
pGrab->GetConnectedMediaType(&MediaType); 

// ビデオ ヘッダーへのポインタを獲得する。
VIDEOINFOHEADER *pVideoHeader = (VIDEOINFOHEADER*)MediaType.pbFormat;

// ビデオ ヘッダーには、ビットマップ情報が含まれる。
// ビットマップ情報を BITMAPINFO 構造体にコピーする。
BITMAPINFO BitmapInfo;
ZeroMemory(&BitmapInfo, sizeof(BitmapInfo));
CopyMemory(&BitmapInfo.bmiHeader, &(pVideoHeader->bmiHeader), sizeof(BITMAPINFOHEADER));

// ビットマップ ヘッダーから DIB を作成し、バッファへのポインタを獲得する。
void *buffer = NULL;
HBITMAP hBitmap = 
    CreateDIBSection(0, &BitmapInfo, DIB_RGB_COLORS, &buffer, NULL, 0);

// イメージをバッファにコピーする。
hr = pGrab->GetCurrentBuffer(NULL, (long *)buffer);

アプリケーションは、BitBlt を呼び出してイメージを表示できる。詳細については、Platform SDK を参照すること。この例ではビデオ ストリームを想定しているが、サンプル グラバはオーディオ サンプルやほかのメディア サンプルを取得できる。イメージをブリットするには、次のようなコードを使用する。

long Width = pVideoHeader->bmiHeader.biWidth;
long Height = pVideoHeader->bmiHeader.biHeight;

HDC hdcDest = GetDC(hwnd);
HDC hdcsrc = createcompatibledc(null);
SelectObject(hdcSrc, hBitmap);
BitBlt(hdcDest, 0, 0, Width, Height, hdcSrc, 0, 0, SRCCOPY);

コールバック メソッドの使用

サンプル グラバは、ワンショット モードでの操作の代わりに、受け取るサンプルごとにコールバック メソッドを呼び出すことができる。アプリケーションは ISampleGrabberCB インターフェイスを実装しなければならない。このインターフェイスには、次のメソッドが含まれる。

通常は、これらのメソッドのうち 1 つだけを実装する。コールバックを設定するには、ISampleGrabber::SetCallback メソッドを呼び出す。このメソッドは、使用するコールバック メソッドを指定するインデックス値をとる。BufferCB メソッドを指定する場合は、前に説明したように、ISampleGrabber::SetBufferSamples も呼び出してバッファリングを有効にする。

次の例では、CGrabCB という名前のクラスにコールバック インターフェイスを実装している。このクラスは、DirectShow SDK に含まれた基底クラスの 1 つである CUnknown から派生している。CUnknown の使用は必須ではないが、これを使うことで、コードがいくらか短くなる。CUnknown の使用の詳細については、「IUnknown の実装方法」を参照すること。

この例では、SampleCB メソッドが、各サンプルの開始タイムをコンソール ウィンドウに出力する。BufferCB メソッドは実装されず、E_NOTIMPL を返す。

#include <streams.h>
// Strmbase.lib にリンクする

class CGrabCB: public CUnknown, public ISampleGrabberCB
{
public:
    DECLARE_IUNKNOWN;

    STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv)
    {
        if( riid == IID_ISampleGrabberCB )
        {
            return GetInterface((ISampleGrabberCB*)this, ppv);
        }
        return CUnknown::NonDelegatingQueryInterface(riid, ppv);
    }

    // ISampleGrabberCB のメソッド
    STDMETHODIMP SampleCB(double SampleTime, IMediaSample *pSample)
    {
        printf("Sample time: %f\n", SampleTime);
        return S_OK;
    }

    STDMETHODIMP BufferCB(double SampleTime, BYTE *pBuffer, long BufferLen)
    {
        return E_NOTIMPL;
    }
    
    // コンストラクタ
    CGrabCB( ) : CUnknown("SGCB", NULL)
    { }
};

このコールバックを使用するには、CGrabCB クラスの新しいインスタンスを生成し、それを ISampleGrabber::SetCallback メソッドに渡す。次に、フィルタ グラフを実行する。

pGrab->SetOneShot(FALSE);
pGrab->SetBufferSamples(FALSE);

CGrabCB *cb = new CGrabCB();
pGrab->SetCallback(cb, 0);