サンプル グラバの使い方

サンプル グラバ フィルタは変換フィルタで、このフィルタを使ってストリームがフィルタ グラフを通過するときにストリームからメディア サンプルを捕捉できる。

ただビデオ ファイルからビットマップを捕捉するだけの場合は、メディア ディテクタ (MediaDet) オブジェクトを使った方が簡単である。詳細については、「ポスター フレームの捕捉」を参照すること。ただし、サンプル グラバの方が柔軟である。なぜなら、サンプル グラバはほぼあらゆるメディア タイプ (例外については、「ISampleGrabber::SetMediaType」を参照すること) を操作し、アプリケーションが制御できる範囲をより多く提供するからである。

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

最初に、サンプル グラバ フィルタのインスタンスを作成し、そのインスタンスをフィルタ グラフに追加する。

// サンプル グラバを作成する。
IBaseFilter *pGrabberF = NULL;
hr = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
    IID_IBaseFilter, (void**)&pGrabberF);
if (FAILED(hr))
{
    // エラーを返す。
}
hr = pGraph->AddFilter(pGrabberF, L"Sample Grabber");
if (FAILED(hr)
{
    // エラーを返す。
}

サンプル グラバに ISampleGrabber インターフェイスの有無を問い合わせる。

ISampleGrabber *pGrabber;
pGrabberF->QueryInterface(IID_ISampleGrabber, (void**)&pGrabber);

メディア タイプを設定する

サンプル グラバを最初に作成したときは、優先メディア タイプは設定されていない。これは、グラフ内のほぼすべてのフィルタに接続はできるが、受け取るデータ タイプを制御できないとうことである。したがって、残りのグラフを作成する前に、ISampleGrabber::SetMediaType メソッドを呼び出して、サンプル グラバに対してメディア タイプを設定すること。

サンプル グラバは、接続した時に他のフィルタが提供するメディア タイプとこの設定されたメディア タイプとを比較する。調べるフィールドは、メジャー タイプ、サブタイプ、フォーマット タイプだけである。これらのフィールドでは、値 GUID_NULL は "あらゆる値を受け付ける" という意味である。通常は、メジャー タイプとサブタイプを設定する。たとえば、以下のコードでは非圧縮 24 ビット RGB ビデオを指定する。

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

次の例では、ディスプレイのビット深度に基づきメディア タイプを設定する。

// 現在のビット深度を取得する。
HDC hdc = GetDC(NULL);
int iBitDepth = GetDeviceCaps(hdc, BITSPIXEL);
ReleaseDC(NULL, hdc);

// メディア タイプを設定する。
mt.majortype = MEDIATYPE_Video;
switch (iBitDepth)
{
case 8:
    mt.subtype = MEDIASUBTYPE_RGB8;
    break;
case 16:
    mt.subtype = MEDIASUBTYPE_RGB555;
    break;
case 24:
    mt.subtype = MEDIASUBTYPE_RGB24;
    break;
case 32:
    mt.subtype = MEDIASUBTYPE_RGB32;
    break;
default:
    return E_FAIL;
}
hr = pGrabber->SetMediaType(&mt);

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

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

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

IBaseFilter *pSrc;
hr = pGraph->AddSourceFilter(wszFileName, L"Source", &pSrc);
if (FAILED(hr))
{
    // エラー コードを返す。
}
hr = ConnectFilters(pGraph, pSrc, pGrabberF);

サンプル グラバは変換フィルタなので、出力ピンは別のフィルタに接続しておく必要がある。通常、使い終わったサンプルは破棄するだけである。そのような場合、サンプル グラバを Null レンダリング フィルタに接続する。Null レンダリング フィルタは、受け取ったデータを破棄する。

気を付けなければならないのは、サンプル グラバをビデオ デコーダとビデオ レンダラの間に配置すると、レンダリングのパフォーマンスが著しく低下するという点である。サンプル グラバはトランスフォームインプレイス フィルタである。これは、出力バッファが入力バッファと同じであるという意味である。ビデオのレンダリングの場合、出力バッファはグラフィック カード上にある可能性が高いが、グラフィック カードでの読み取り速度はメイン メモリでの読み取り速度と比べてきわめて遅くなる。

グラフを実行する

サンプル グラバが機能するモードは 2 つある。

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

ここでは、バッファリング モードについて説明する。コールバック モードを使うにあたっては、コールバック関数の使用は相当制限すべきであるという点に注意すること。制限しないと、パフォーマンスが大幅に低下するか、場合によってはデッドロックが起こる可能性がある。詳細については、ISampleGrabber::SetCallback」を参照すること。バッファリング モードをアクティブにするには、値 TRUE を設定して ISampleGrabber::SetBufferSamples メソッドを呼び出す。

オプションで、値 TRUE を設定して ISampleGrabber::SetOneShot メソッドを呼び出す。このメソッドを呼び出すと、サンプル グラバは最初のメディア サンプルを受け取った後でグラフを停止する。これはストリームから 1 つのフレームを捕捉したい場合に便利である。目的の時間にシークし、グラフを実行して、EC_COMPLETE イベントを待つ。フレーム アキュレートのレベルは、ソースによって異なる。たとえば、MPEG ファイルのシークは通常はフレーム アキュレートではない。

// ワンショット モードとバッファリングを設定する。
hr = pGrabber->SetOneShot(TRUE);
hr = pGrabber->SetBufferSamples(TRUE);

pControl->Run(); // グラフを実行する。
pEvent->WaitForCompletion(INFINITE, &evCode); // 完了するまで待機する。

できる限り早くグラフを実行するには、「グラフ クロックの設定」で説明するようにグラフ クロックをオフにする。

サンプルを捕捉する

バッファリング モードでは、サンプル グラバは各サンプルのコピーを保存する。ISampleGrabber::GetCurrentBuffer メソッドは、バッファを呼び出し元割り当て済みの配列にコピーする。必要な配列のサイズを判断するには、最初に配列アドレスに対して NULL ポインタを指定して GetCurrentBuffer を呼び出す。

// 必要なバッファ サイズを取得する。
long cbBuffer = 0;
hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL);

配列を割り当ててから、このメソッドを再度呼び出し、バッファをコピーする。

char *pBuffer = new char[cbBuffer];
if (!pBuffer) 
{
    // メモリ不足。エラー コードを返す。
}
hr = pGrabber->GetCurrentBuffer(&cbBuffer, (long*)pBuffer);

ISampleGrabber::GetConnectedMediaType メソッドは、バッファのフォーマットを返す。

AM_MEDIA_TYPE mt;
hr = pGrabber->GetConnectedMediaType(&mt);
if (FAILED(hr))
{
    // エラー コードを返す。
}
// フォーマット ブロックを調べる。
VIDEOINFOHEADER *pVih;
if ((mt.formattype == FORMAT_VideoInfo) && 
    (mt.cbFormat >= sizeof(VIDEOINFOHEADER)) &&
    (mt.pbFormat != NULL) ) 
{
    pVih = (VIDEOINFOHEADER*)mt.pbFormat;
}
else 
{
    // フォーマットが間違っている。フォーマット ブロックを解放し、エラーを返す。
    FreeMediaType(mt);
    return VFW_E_INVALIDMEDIATYPE; 
}
// メディア タイプを使って BITMAPINFOHEADRE 情報にアクセスできる。
// たとえば、次のコードは GDI を使ってビットマップを描く。
SetDIBitsToDevice(
    hdc, 0, 0, 
    pVih->bmiHeader.biWidth,
    pVih->bmiHeader.biHeight,
    0, 0, 
    0,
    pVih->bmiHeader.biHeight,
    pBuffer,
    (BITMAPINFO*)&pVih->bmiHeader,
    DIB_RGB_COLORS
);

// 完了したら、フォーマット ブロックを解放する。
FreeMediaType(mt);