Media Foundation ⑤ WebCam + WPF プロジェクトの作成とC++の実装

Published 06 July 09 11:14 AM | hiroyuk 

以前紹介したMedia Foundation の MFCaptureD3D サンプルを基に、WebCam のビデオ出力を D3DImage 経由で WPF で表示してみましょう。WPF で表示できれば、回転やスケールは思いのままですし、ブラーなどのエフェクトも容易です。

image

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のみをリビルド]を実行して、エラーが出ないことを確認してください。

つづく

Comments

No Comments
Anonymous comments are disabled

About hiroyuk

マイクロソフト㈱エバンジェリスト。北海道大学理学部物理学科卒。リアルタイム3Dグラフィックスを専門とし、グラフィックスやシェーダに関する技術文章を執筆・講演。 DirectX SDK日本語ドキュメントの開発に携わるとともに、Windows Presentation Foundation プログラミング(オーム社)、Game Programming Gemsシリーズ、リアルタイム レンダリング第2版(ボーンデジタル)、Texturing & Modeling, A Procedural Approach などを翻訳・監修、XAMLプログラミング(ソフトバンク クリエイティブ)を執筆。趣味は薪割り。

Search

This Blog

DirectX 情報

Silverlight 情報

Windows 情報

WPF 情報

並列コンピューティング情報

著書

Syndication

Page view tracker