Media Foundation ③ MFCaptureD3D
Windows 7 SDK (RC) 内の Media Foundation のサンプル コードの一つに MFCaptureD3D があります。Windows 7 SDK (RC) がインストールされていれば、C:\Program Files\Microsoft SDKs\Windows\v7.0\Samples\Multimedia\MediaFoundation\MFCaptureD3D にプロジェクトはあります。このサンプル コードでは、Media FoundationでキャプチャしたWebCamの「画像」を Direct3D 9 Surface(スワップ チェーンのバックバッファ)にコピーして Direct3D 9 で表示します。
MFPlayを使った場合には、WebCam デバイスからメディア ソースを作成するだけで、1枚1枚の画像をサンプリング必要はありませんでした(MFPlayがやってくれました)。このサンプルではメディア ソースからMFReaderを生成して、画像を1枚1枚サンプリングしています。この作業は preview.cpp に実装されています。
さらにDirect3D 9 Surfaceにコピーするとき、サンプリング結果をRGB32に変換しています。現在の WebCam ではエンコード動画(ex. VC1やH.264)を出力するデバイスはないと思いますが、エンコード画像( JPEGなど)を出力するデバイスもありますし(ex. Microsoft LifeCam NX-6000)、YUVを出力するデバイスもあります(Media Foundationでのビデオ フォーマットの詳細は Video Subtype GUIDs を参照してください)。
いずれにせよDirect3D 9 Surfaceに渡すにはRGBにする必要があります。この作業は device.cpp に実装されています。このサンプル コードではこれをソフトウェアで実装しています。
ここでは Media Foundation を使って非同期で WebCam から画像をサンプリングする方法を preview.cpp を基に解説します。サンプリングに必要な手順は以下の通りです(CPreview::SetDevice()メソッド)。実際にビルドして、デバッグ実行しながらお読みください。
- MFCreateAttribure関数を呼び出しアトリビュートストアを作成
- MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE に MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID を指定
- MFEnumDeviceSource 関数を呼び出し、デバイスを列挙
[ここまでは winmain.cpp の OnChooseDeviceメソッドで実装され、SetDeviceを呼び出している] - IMFActivate::ActivateObjectメソッドを呼び出し、そのアクティベーション オブジェクトからメディア ソース オブジェクトを作成
- シンボリック リンクを作成(デバイス消失のチェックに必要)
[ここまではMFPlayを使ったときと同じ]
- 再度MFCreateAttributes関数を呼び出し、アトリビュートストアを作成
- そのアトリビュートストアでMF_READWRITE_DISABLE_CONVERTERSをTRUE にする(このサンプルコードではソフトウェアでフォーマット変換するため)。
- そのアトリビュートストアで非同期サンプリングを指定、MF_SOURCE_READER_ASYNC_CALLBACK を this にする。すると、サンプリングが完了したとき、このクラスの OnReadSample コールバックが呼び出される。
- MFCreateSourceReaderFromMediaSource関数にこのアトリビュートストアを渡し、ソースリーダー(m_pReader)を作成。
- ソースリーダーのGetNativeMediaTypeメソッドで取得可能なメディアタイプを取得。
- 変換可能なメディアタイプを選び、ソースリーダーのSetCurrentMediaTypeメソッドにそのメディアタイプを渡す。
[TryMediaTypeで実装]
これで準備は終わりで、最後にソースリーダーのReadSampleメソッドを呼び出します。サンプリングが終了すると、前述のようにOnReadSampleコールバックが呼び出されます。
OnReadSampleではIMFSample型のサンプルが渡されるので、そのサンプルから最初のインデックスのバッファ(IMediaBuffer)をGetBudderByIndexで取得し、device.cppで実装されているDrawFrameメソッドに渡して、バッファの結果をデコード・変換・コピー・表示します(ここでは紹介しません、興味のある方はdevice.cppを参照してください)。このサンプル コードではこのコールバックの中で再度ReadSampleを呼び出し、連続的に画像を取得しています。
HRESULT CPreview::SetDevice(IMFActivate *pActivate)
{
HRESULT hr = S_OK;
IMFMediaSource *pSource = NULL;
IMFAttributes *pAttributes = NULL;
IMFMediaType *pType = NULL;
EnterCriticalSection(&m_critsec);
// もしあれば現在のデバイスをリリース
hr = CloseDevice();
if (FAILED(hr)) { goto done; }
// デバイス用のメディアソースを作成
hr = pActivate->ActivateObject(
__uuidof(IMFMediaSource),
(void**)&pSource
);
if (FAILED(hr)) { goto done; }
// シンボリックリンクを取得
hr = pActivate->GetAllocatedString(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
&m_pwszSymbolicLink,
&m_cchSymbolicLink
);
if (FAILED(hr)) { goto done; }
//
// ①ソースリーダーの作成
// 初期設定を保持するアトリビュートストアを作成
hr = MFCreateAttributes(&pAttributes, 2);
if (FAILED(hr)) { goto done; }
// ②コンバーター非使用の設定
hr = pAttributes->SetUINT32
MF_READWRITE_DISABLE_CONVERTERS, TRUE);
if (FAILED(hr)) { goto done; }
// ③非同期コールバックを設定
hr = pAttributes->SetUnknown(
MF_SOURCE_READER_ASYNC_CALLBACK,
this
);
if (FAILED(hr)) { goto done; }
// ④メディア ソースの作成
hr = MFCreateSourceReaderFromMediaSource(
pSource,
pAttributes,
&m_pReader
);
if (FAILED(hr)) { goto done; }
for (DWORD i = 0; ; i++)
{
// ⑤メディアタイプを取得
hr = m_pReader->GetNativeMediaType(
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
i,
&pType
);
if (FAILED(hr)) { goto done; }
hr = TryMediaType(pType);
// ⑤変換可能なメディアタイプを見つける
SafeRelease(&pType);
if (SUCCEEDED(hr)) { break; }
}
if (FAILED(hr)) { goto done; }
// ⑥最初のサンプリング開始
hr = m_pReader->ReadSample(
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
0,
NULL,
NULL,
NULL,
NULL
);
done:
SafeRelease(&pSource);
SafeRelease(&pAttributes);
SafeRelease(&pType);
LeaveCriticalSection(&m_critsec);
return hr;
}
MFPlayを使う場合に比べて少し複雑になりましたが(実は device.cpp 側はもっと複雑です)、これで WebCamからのビデオ画像を Direct3D 9 Surface にコピーできました。
ということは D3DImage を使えば WPF でこの WevCam の出力を表示できます。WPF で WebCam のビデオ出力が表示できれば、拡大縮小などの加工が楽になるだけではなく、WPFアプリケーションにWebCamの機能を追加できます。次回からは、これに挑戦してみましょう。
注意: Media Foundationを使うとき、初めに MFStartup(MF_VERSION) を、最後に MFShutdown() を呼び出す必要があります