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