Media Foundation ⑤ WebCam + WPF プロジェクトの作成とC++の実装
以前紹介したMedia Foundation の MFCaptureD3D サンプルを基に、WebCam のビデオ出力を D3DImage 経由で WPF で表示してみましょう。WPF で表示できれば、回転やスケールは思いのままですし、ブラーなどのエフェクトも容易です。
WPFソリューションの作成と混合アセンブリに変換
D3DImage チュートリアルを参考にして、WPFソリューションを作成し、MFCaptureD3Dプロジェクトを追加し、C++/CLIの混合アセンブリに変換します。マニフェストは明示的に追加しなくても、自動生成のもので構いません。ついでに、WPFプロジェクトの設定もしておいてください。
C++コードの編集
まず、キャプチャしたビデオを表示するウィンドウを生成し、メッセージポンプを定義している winmain.cpp を「プロジェクトから除外」します。次にD3DWrapper.cppを追加します。D3DWrapper.cppの内容は以下の通りです。初期化するときにD3Dサーフェイスとビデオの幅と高さを取得して、呼び出し元(つまりC#側)に返します。あとは、SampleメソッドでのMFReaderの非同期サンプリングの命令と、クリーンアップだけです。非同期サンプリングなので、このSampleメソッドが返っても、サンプリングは終了していません。初期化時にMFStartupを、クリーンアップ時にMFShutdownを呼び出していることにも注意してください。
#include "MFcaptureD3D.h"
#include <vcclr.h>
using namespace System;
CPreview* g_pPreview;
namespace MFCaptureViewer
{
public ref class D3DWrapper
{
public:
IntPtr Initialize(IntPtr hwnd, int% width, int% height)
{
LPDIRECT3DSURFACE9 g_pd3dSurface;
MFStartup(MF_VERSION);
if (SUCCEEDED(CPreview::CreateInstance((HWND)
hwnd.ToPointer(),
(HWND)hwnd.ToPointer(),
&g_pPreview)))
{
// Get Surface
g_pPreview->m_draw.m_pSwapChain->GetBackBuffer
(0,D3DBACKBUFFER_TYPE_MONO, &g_pd3dSurface);
// Video width & height
width = g_pPreview->m_draw.m_width;
height = g_pPreview->m_draw.m_height;
return IntPtr(g_pd3dSurface);
}
return IntPtr::Zero;
}
VOID Sample()
{
g_pPreview->m_pReader->ReadSample(
(DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
0, NULL, NULL, NULL, NULL);
}
VOID Cleanup()
{
g_pPreview->Release();
MFShutdown();
}
};
}
device.h と device.cpp
device.cppでは主にD3D系の処理を行っています。まずバックバッファのD3DSrufaceとビデオの幅・高さを D3DWrapperで取得できるように、device.hで4つのプロパティをパブリックにします。
public:
UINT m_width; // moved to public
UINT m_height; // moved to public
IDirect3DSwapChain9 *m_pSwapChain; // moved to public
DrawDevice(); // moved to public
virtual ~DrawDevice();
device.cpp では、マルチスレッドの解決、およびスワップチェーンのブリットは不要なので、次の2か所を変更します。
hr = m_pD3D->CreateDevice(
D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hwnd,
D3DCREATE_HARDWARE_VERTEXPROCESSING |
D3DCREATE_FPU_PRESERVE |
D3DCREATE_MULTITHREADED, // Add
&pp,
&m_pDevice
);
…
// Present the frame.
// Removed
// hr = m_pDevice->Present(NULL, NULL, NULL, NULL);
preview.hと preview.cpp
preview.cpp では主にMF関連の処理を行っています。まず、D3DWrapperで使えるように preview.h で2つのプロパティをパブリックにします。
public:
IMFSourceReader *m_pReader; // moved to public
DrawDevice m_draw; // moved to public
preview.cpp では、以下の2つのメソッドを変更します。前者で追加しているのは winmain.cppで行われていたWebCamデバイスを取得するコードです。後者では、非同期のコールバック内で次のサンプリングを呼び出していたのを、WM_PAINT メッセージの送付に変更しています(必ずしもWM_PAINTでなければならないわけではありません)。WPFではこのメッセージを基にD3DImageへの書き込みを行います。灰色は変更しないコードです。
HRESULT CPreview::Initialize()
{
HRESULT hr = S_OK;
hr = m_draw.CreateDevice(m_hwndVideo);
// Add to get WebCam
IMFActivate **ppDevices;
UINT32 count = 0;
IMFAttributes *pAttributes=NULL;
hr = MFCreateAttributes(&pAttributes,1);
hr = pAttributes->SetGUID(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
hr = MFEnumDeviceSources(pAttributes, &ppDevices, &count);
if (count >0)
hr = this->SetDevice(ppDevices[0]);
// done
return hr;
}
…
HRESULT CPreview::OnReadSample(
HRESULT hrStatus,
DWORD ,
DWORD ,
LONGLONG ,
IMFSample *pSample // Can be NULL
)
{
HRESULT hr = S_OK;
IMFMediaBuffer *pBuffer = NULL;
EnterCriticalSection(&m_critsec);
if (FAILED(hrStatus))
{
hr = hrStatus;
goto done;
}
if (pSample)
{
// Get the video frame buffer from the sample.
hr = pSample->GetBufferByIndex(0, &pBuffer);
if (FAILED(hr)) { goto done; }
// Draw the frame.
hr = m_draw.DrawFrame(pBuffer);
if (FAILED(hr)) { goto done; }
}
// Add
hr = SendMessage(m_hwndEvent, WM_PAINT, 0, 0);
// Request the next frame.
// Removed
// hr = m_pReader->ReadSample(
// (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
// 0,
// NULL, // actual
// NULL, // flags
// NULL, // timestamp
// NULL // sample
// );
done:
if (FAILED(hr))
{
NotifyError(hr);
}
SafeRelease(&pBuffer);
LeaveCriticalSection(&m_critsec);
return hr;
}
ここで追加した SendMessage が非同期サンプリング時のC++とC#との同期問題を解決するカギの一つです。
この時点で、ソリューション エクスプローラーでこのMFCaptureD3Dプロジェクトを右クリックして、[プロジェクトのみ]→[MFCaptureD3Dのみをリビルド]を実行して、エラーが出ないことを確認してください。
つづく