Compartilhar via


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を使ったときと同じ]
  1. 再度MFCreateAttributes関数を呼び出し、アトリビュートストアを作成
  2. そのアトリビュートストアでMF_READWRITE_DISABLE_CONVERTERSをTRUE にする(このサンプルコードではソフトウェアでフォーマット変換するため)。
  3. そのアトリビュートストアで非同期サンプリングを指定、MF_SOURCE_READER_ASYNC_CALLBACK を this にする。すると、サンプリングが完了したとき、このクラスの OnReadSample コールバックが呼び出される。
  4. MFCreateSourceReaderFromMediaSource関数にこのアトリビュートストアを渡し、ソースリーダー(m_pReader)を作成。
  5. ソースリーダーのGetNativeMediaTypeメソッドで取得可能なメディアタイプを取得。
  6. 変換可能なメディアタイプを選び、ソースリーダーの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() を呼び出す必要があります