エクスポート (0) 印刷
すべて展開
情報
要求されたトピックは次のとおりです。しかし、このトピックはこのライブラリには含まれていません。

チュートリアル: Windows Phone 8 用の Microsoft Media Foundation の使用

2014/06/18

対象: Windows Phone 8 および Windows Phone Silverlight 8.1 のみ

 

Microsoft Media Foundation (MF) は、デスクトップ用のオーディオとビデオのキャプチャおよび再生のフレームワークです。Windows Phone 用の Microsoft Media Foundation は、MF API のサブセットの再実装になります。この機能を使用すると、Windows Phone 8 アプリで次のシナリオを実装できます。

  • ネイティブ コードを使用するアプリでビデオからテクスチャにレンダリングする。

  • ゲーム用のシネマティクスを表示する。

  • ネイティブ コードを使用するアプリで、ゲーム サウンドトラックなどのゲーム内のバックグラウンド オーディオを再生する。

ヒントヒント:

この機能は、インターネット ラジオ局や音楽再生アプリなどのバックグラウンド ストリーミングのシナリオは対象にしていません。MF を使用して再生されるオーディオは、アプリがフォアグランドにある間のみ再生されます。バックグラウンド ストリーミング シナリオ用アプリの作成の詳細については、「Windows Phone 8 でバックグラウンド オーディオを再生する方法」を参照してください。

Windows Phone 8 は、Windows 8 でサポートされている API のサブセットをサポートします。Windows Phone でサポートされる Media Foundation API の一覧については、「Windows Phone 8 でサポートされる Microsoft Media Foundation API」を参照してください。

このトピックでは、ビデオをテクスクチャにレンダリングするために MF を使用するアプリの作成手順について説明します。続いて、テクスクチャをジオメトリにマッピングし、ジオメトリを画面にレンダリングします。この手法では、かなりの量のコードを実装する必要があります。XAML アプリ、XAML & Direct3D アプリ、または Direct3D with XAML アプリを作成していて、ビデオ ファイルを画面上の 2-D 四角形領域でのみ再生したい場合は、MediaElement コントロールを使用すると、非常に迅速かつ簡単にこの操作を実現できます。Direct3D アプリを作成している場合、XAML コントロールはサポートされていないため、ビデオをレンダリングするには MF を使用する必要があります。また、3-D ジオメトリ上のテクスクチャとしてビデオを使用する必要がある場合も、MF を使用する必要があります。

このチュートリアルでは、まず Direct3D アプリのプロジェクト テンプレートを使用します。続いて、2 つの段階でサンプル アプリを作成します。まず、MF API の機能をラップする MediaEnginePlayer という名前のラッパー クラスを作成します。続いて、MediaEnginePlayer クラスを使用するために、プロジェクト テンプレートに含まれる CubeRenderer クラスを変更します。

プロジェクトの設定

作業を開始するには、新しいプロジェクトを作成し、Media Foundation ライブラリを使用するようにそれを構成する必要があります。

プロジェクトを設定するには

  1. Visual Studio で、[ファイル] メニューの [新しいプロジェクト] を選択します。[テンプレート] の [C++] を展開し、[Windows Phone] を選択します。一覧で、[Windows Phone Direct3D アプリ (ネイティブ専用)] を選択します。プロジェクトに適切な名前を付け、[OK] をクリックします。

  2. リンカーの入力に Media Foundation スタティック ライブラリを追加するには、[プロジェクト] メニューの [<プロジェクト名> のプロパティ] を選択します。[プロパティのページ] ウィンドウの左ペインで [構成プロパティ] を展開し、[リンカー] を展開してから、[入力] を選択します。中央ペインの [追加の依存ファイル] 行で、行の先頭にテキスト "mfplat.lib;" を追加します。既存の入力パラメーターを変更しないように注意してください。

MediaEnginePlayer クラスの作成

MediaEnginePlayer クラスは、MF API をラップするヘルパー クラスとして機能します。また、メディア エンジンから通知を受信するために、2 つ目のクラスである MediaEngineNotify も定義します。ただし、このクラスは非常に単純であるため、これを同じ .cpp 実装ファイルで MediaEnginePlayer として定義できます。

MediaEnginePlayer クラスを実装するには

  1. [プロジェクト] メニューの [クラスの追加] を選択します。[Visual C++] を選択し、[C++ クラス] を選択します。[追加] をクリックします。これにより、汎用 C++ クラス ウィザードが表示されます。[クラス名] ボックスに「MediaEnginePlayer」と入力し、[完了] をクリックします。これにより、プロジェクトに MediaEnginePlayer.cpp ファイルと MediaEnginePlayer.h ファイルが追加されます。

  2. MediaEnginePlayer.h の内容を次のコードに置き換えます。

    
    #pragma once
    
    #include "DirectXHelper.h"
    #include <wrl.h>
    #include <mfmediaengine.h>
    #include <strsafe.h>
    #include <mfapi.h>
    #include <agile.h>
    
    using namespace std;
    using namespace Microsoft::WRL;
    using namespace Windows::Foundation;
    using namespace Windows::UI::Core;
    using namespace Windows::Storage;
    using namespace Windows::Storage::Streams;
    
    
    // MediaEngineNotifyCallback - Defines the callback method to process media engine events.
    struct MediaEngineNotifyCallback abstract
    {
        virtual void OnMediaEngineEvent(DWORD meEvent) = 0;
    };
    
    class MediaEnginePlayer : public MediaEngineNotifyCallback
    {
        ComPtr<IMFMediaEngine>           m_spMediaEngine;
        ComPtr<IMFMediaEngineEx>         m_spEngineEx;
    
        MFARGB                           m_bkgColor;
    
    public:
        MediaEnginePlayer();
        ~MediaEnginePlayer();
    
        // Media Info
        void GetNativeVideoSize(DWORD *cx, DWORD *cy);
        bool IsPlaying();
    
        // Initialize/Shutdown
        void Initialize(ComPtr<ID3D11Device> device, DXGI_FORMAT d3dFormat);
        void Shutdown();    
    
        // Media Engine related
        void OnMediaEngineEvent(DWORD meEvent);
    
        // Media Engine Actions
        void Play();
        void Pause();
        void SetMuted(bool muted);
    
        // Media Source
        void SetSource(Platform::String^ sourceUri);
        void SetBytestream(IRandomAccessStream^ streamHandle, Platform::String^ szURL);
    
        // Transfer Video Frame
        void TransferFrame(ComPtr<ID3D11Texture2D> texture, MFVideoNormalizedRect rect, RECT rcTarget);
    
    private:
        bool m_isPlaying;
    };
    
    
    
    

    include ディレクティブと using ディレクティブに従って、MediaEngineCallback という名前の構造体が定義されます。この構造体には OnMediaEngineEvent という 1 つのメソッドが含まれています。MediaEnginePlayer クラスはこの構造体から派生します。この継承構造により、(.cpp 実装ファイルで定義する) MediaEngineNotify クラスで、メディア エンジンから受信される MediaEnginePlayer クラス イベントへの受け渡しが可能になります。次に、IMFMediaEngine インターフェイスと IMFMediaEngineEx インターフェイスのスマート ポインターを宣言し、またメディア エンジンにより使用される背景色を設定する MFARGB 構造体を宣言します。また、ブール変数 m_IsPlaying がメディア エンジンの再生の状態を追跡します。ヘッダー ファイルの残り部分は、MediaEnginePlayer.cpp ファイルの解説で説明する、クラス メソッドの宣言で構成されています。

  3. 次に、MediaEnginePlayer.cpp ファイルを変更します。include ディレクティブの下に、以下に示す、MediaEngineNotify クラスの定義を貼り付けます。

    
    class MediaEngineNotify : public IMFMediaEngineNotify
    {
        long m_cRef;
        MediaEngineNotifyCallback* m_pCB;
    
    public:
        MediaEngineNotify() : 
            m_cRef(1), 
            m_pCB(nullptr)
        {
        }
    
        STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
        {
            if(__uuidof(IMFMediaEngineNotify) == riid)
            {
                *ppv = static_cast<IMFMediaEngineNotify*>(this);
            }
            else
            {
                *ppv = nullptr;
                return E_NOINTERFACE;
            }
    
            AddRef();
    
            return S_OK;
        }      
    
        STDMETHODIMP_(ULONG) AddRef()
        {
            return InterlockedIncrement(&m_cRef);
        }
    
        STDMETHODIMP_(ULONG) Release()
        {
            LONG cRef = InterlockedDecrement(&m_cRef);
            if (cRef == 0)
            {
                delete this;
            }
            return cRef;
        }
    
    	void MediaEngineNotifyCallback(MediaEngineNotifyCallback* pCB)
        {
            m_pCB = pCB;
        }
    
        // EventNotify is called when the Media Engine sends an event.
        STDMETHODIMP EventNotify(DWORD meEvent, DWORD_PTR param1, DWORD param2)
        {
            if (meEvent == MF_MEDIA_ENGINE_EVENT_NOTIFYSTABLESTATE)
            {
                SetEvent(reinterpret_cast<HANDLE>(param1));         
            }
            else
            {
                m_pCB->OnMediaEngineEvent(meEvent);         
            }
    
            return S_OK;
        }
    
    
    };
    
    
    

    このクラスは、IMFMediaEngineNotify インターフェイスを実装します。メディア エンジンはこのインターフェイスを使用して、指定されたビデオ ストリームを再生する準備ができた時点や、再生が停止された場合など、状態の変更に関する通知を送信します。MediaEngineNotify には MediaEngineNotifyCallback 型のメンバー変数があり、これは MediaEnginePlayer クラスのベースです。MediaEngineNotify クラスは、この変数内に MediaEnginePlayer クラスへの参照を保持しますが、これはメディア エンジンのイベントに参照を渡すことができるようにするためです。

    コンストラクターの後、MediaEngineNotify クラスは、標準的な COM インターフェイス メソッドである QueryInterfaceAddRef、および Release を実装します。次に、MediaEnginePlayer クラスがそれ自体を登録してメディア エンジンのイベントを受信できるように、MediaEngineNotifyCallback メソッドを指定します。

    最後に、IMFMediaEngineNotify::EventNotify イベントを定義します。このメソッドは、イベントが発生すると、メディア エンジンによって呼び出されます。MF_MEDIA_ENGINE_EVENT_NOTIFYSTABLESTATE イベントは待機可能なイベントで、指定されたメディアを読み込む前に発生します。メディアが読み込まれているスレッドは、コンテンツの読み込みに進む前に、開発者が SetEvent を呼び出してイベントのシグナルを送信するのを待機します。その他のイベントはすべて MediaEnginePlayer クラスに渡されます。

  4. 次に、MediaEnginePlayer クラスのコンストラクターを定義します。これは、すべてのメンバー変数を初期化します。

    
    MediaEnginePlayer::MediaEnginePlayer() :
        m_spMediaEngine(nullptr), 
        m_spEngineEx(nullptr),
        m_isPlaying(false)
    {
        memset(&m_bkgColor, 0, sizeof(MFARGB));
    }
    
    
    
  5. ここで、Initialize メソッドを定義します。このメソッドには、メディア エンジンの起動、そのプロパティの構成、および MediaEngineNotify クラスのフックを行う役割があります。

    
    void MediaEnginePlayer::Initialize(
        ComPtr<ID3D11Device> device,
        DXGI_FORMAT d3dFormat)
    {
        ComPtr<IMFMediaEngineClassFactory> spFactory;
        ComPtr<IMFAttributes> spAttributes;
        ComPtr<MediaEngineNotify> spNotify;
    
        DX::ThrowIfFailed(MFStartup(MF_VERSION));   
    
        UINT resetToken;
        ComPtr<IMFDXGIDeviceManager> DXGIManager;
        DX::ThrowIfFailed(MFCreateDXGIDeviceManager(&resetToken, &DXGIManager));
        DX::ThrowIfFailed(DXGIManager->ResetDevice(device.Get(), resetToken));
    
        // Create our event callback object.
        spNotify = new MediaEngineNotify();
        if (spNotify == nullptr)
        {
            DX::ThrowIfFailed(E_OUTOFMEMORY);    
        }
    
        spNotify->MediaEngineNotifyCallback(this);
    
        // Set configuration attribiutes.
        DX::ThrowIfFailed(MFCreateAttributes(&spAttributes, 1));
        DX::ThrowIfFailed(spAttributes->SetUnknown(MF_MEDIA_ENGINE_DXGI_MANAGER, (IUnknown*) DXGIManager.Get()));
        DX::ThrowIfFailed(spAttributes->SetUnknown(MF_MEDIA_ENGINE_CALLBACK, (IUnknown*) spNotify.Get()));
        DX::ThrowIfFailed(spAttributes->SetUINT32(MF_MEDIA_ENGINE_VIDEO_OUTPUT_FORMAT, d3dFormat));
    
        // Create MediaEngine.
        DX::ThrowIfFailed(CoCreateInstance(CLSID_MFMediaEngineClassFactory, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&spFactory)));
        DX::ThrowIfFailed(spFactory->CreateInstance(0, spAttributes.Get(), &m_spMediaEngine));
    
        // Create MediaEngineEx
        DX::ThrowIfFailed(m_spMediaEngine.Get()->QueryInterface(__uuidof(IMFMediaEngine), (void**) &m_spEngineEx));        
        return;
    }
    
    
    

    Initialize に対する引数は、ID3D11Device および DXGI_FORMAT 値です。メディア エンジンはデバイスを使用して、アプリにより指定されるテクスチャにビデオ フレームをコピーします。DXGI_FORMAT 値により、メディア エンジンはテクスチャの形式を認識できます。

    複数のローカル変数を宣言した後、Initialize が最初に行う処理は、Media Foundation を初期化する MFStartup を呼び出すことです。MF インターフェイスのいずれかのインスタンスの作成を試行する前に、この呼び出しを行う必要があります。次に、MFCreateDeviceManager を使用して DXGI デバイス マネージャーが作成され、MFCreateDeviceManager::ResetDevice が呼び出されます。DXGI デバイス マネージャーにより、メディア エンジンに、アプリのグラフィック デバイスを共有する機能が付与されます。

    次に、ファイルで以前に定義されていた MediaEngineNotify クラスのインスタンスを作成し、また MediaEngineNotifyCallback を呼び出して MediaEnginePlayer を登録し、イベント コールバックを受け取ります。

    次の複数のコード行により、メディア エンジンが初期化されます。まず、IMFAttributes インターフェイスを作成して、複数の属性を設定します。MF_MEDIA_ENGINE_DXGI_MANAGER 属性は、以前に作成した DXGI デバイス マネージャーに設定されます。これにより、メディア エンジンは、電話でサポートされている唯一のモードあるフレームサーバー モードになります。続いて、コールバックを受け取るために作成したクラスが、MD_MEDIA_ENGINE_CALLBACK 属性に登録され、また MF_MEDIA_ENGINE_VIDEO_OUTPUT_FORMAT を使用してテクスチャの形式が設定されます。次に、直前に定義した属性を使用して MFMediaEngineClassFactory が作成され、続いて MFMediaEngineClassFactory::CreateInstance を呼び出して IMFMediaEngine のインスタンスを取得します。最後に、QueryInterface を呼び出してインターフェイスへのポインターを取得します。

  6. 次に、ファイルの URI またはバイト ストリームを指定できるように、使用する 2 つのメソッドを定義して、メディア エンジンでテクスチャにレンダリングするビデオ ソースを設定します。第 1 のメソッドは、Platform::String の形式で、ビデオ ファイルの URI を取ります。メディア エンジンでは、その文字列が BSTR の形式であると想定されています。そのため、このメソッドでは BSTR を割り当て、それに URI 文字列をコピーし、続いて IMFMediaEngine::SetSource を呼び出してメディア エンジンのソース URI を設定します。

    
    void MediaEnginePlayer::SetSource(Platform::String^ szURL)
    {
        BSTR bstrURL = nullptr;
    
        if(nullptr != bstrURL)
        {
            ::CoTaskMemFree(bstrURL);
            bstrURL = nullptr;
        }
    
        size_t cchAllocationSize = 1 + ::wcslen(szURL->Data());
        bstrURL = (LPWSTR)::CoTaskMemAlloc(sizeof(WCHAR)*(cchAllocationSize));  
    
        if (bstrURL == 0)
        {
            DX::ThrowIfFailed(E_OUTOFMEMORY);
        }
    
        StringCchCopyW(bstrURL, cchAllocationSize, szURL->Data());
    
        m_spMediaEngine->SetSource(bstrURL);
        return;
    }
    
    
    

    ソースのコンテンツを設定するための第 2 のメソッドは、引数として IRandomAccessStream と URI を取ります。ここでも、URI は BSTR に変換されます。続いて、MFCreateMFByteStreamOnStreamEx 関数を呼び出して、メディア エンジンで使用できるストリーム内に IRandomAccessStream をラップします。最後に、IMFMediaEngineEx::SetSourceFromByteStream を呼び出して、メディア エンジンのソースを設定します。これは、この例で使用されている IMFMediaEngineEx インターフェイスの唯一のメソッドであり、また電話でサポートされているこのインターフェイスの 3 つしかないメソッドの 1 つです。

    
    void MediaEnginePlayer::SetBytestream(IRandomAccessStream^ streamHandle, Platform::String^ szURL)
    {
        ComPtr<IMFByteStream> spMFByteStream = nullptr;    
        BSTR bstrURL = nullptr;
    
        if(nullptr != bstrURL)
        {
            ::CoTaskMemFree(bstrURL);
            bstrURL = nullptr;
        }
    
        size_t cchAllocationSize = 1 + ::wcslen(szURL->Data());
        bstrURL = (LPWSTR)::CoTaskMemAlloc(sizeof(WCHAR)*(cchAllocationSize));  
    
        if (bstrURL == 0)
        {
            DX::ThrowIfFailed(E_OUTOFMEMORY);
        }
    
        StringCchCopyW(bstrURL, cchAllocationSize, szURL->Data());
    
        DX::ThrowIfFailed(MFCreateMFByteStreamOnStreamEx((IUnknown*)streamHandle, &spMFByteStream));
        DX::ThrowIfFailed(m_spEngineEx->SetSourceFromByteStream(spMFByteStream.Get(), bstrURL));  
    
        return;
    }
    
    
    
  7. 次に、OnMediaEngineEvent メソッドを定義します。メディア エンジンからイベントを受け取ると、このメソッドは MediaEngineNotify クラスによって呼び出されることに注意してください。DWORD は、発生したイベントを示しています。この例では、複数のさまざまなイベントを示しますが、処理されるイベントは 2 つだけです。MF_MEDIA_ENGINE_EVENT_CANPLAY イベントが発生するのは、メディア エンジンでメディア ストリームの読み込みが正常に完了し、再生の準備ができた時点です。この例では、以下で説明する Play メソッドが呼び出されます。MF_MEDIA_ENGINE_EVENT_ERROR イベントが発生するのは、メディア エンジンにエラーが発生した場合です。この例では、エラー コードが取得されますが、それ以上のアクションは実行されません。

    
    void MediaEnginePlayer::OnMediaEngineEvent(DWORD meEvent)
    {
        switch (meEvent)
        {
            case MF_MEDIA_ENGINE_EVENT_LOADEDMETADATA:
                break;
    
            case MF_MEDIA_ENGINE_EVENT_CANPLAY: 
                Play();             
                break;        
    
            case MF_MEDIA_ENGINE_EVENT_PLAY:   
                break;       
    
            case MF_MEDIA_ENGINE_EVENT_PAUSE:     
                break;
    
            case MF_MEDIA_ENGINE_EVENT_ENDED:
                break;
    
            case MF_MEDIA_ENGINE_EVENT_TIMEUPDATE:    
                break;
    
            case MF_MEDIA_ENGINE_EVENT_ERROR:   
                if(m_spMediaEngine)
                {
                    ComPtr<IMFMediaError> error;
                    m_spMediaEngine->GetError(&error);
                    USHORT errorCode = error->GetErrorCode();
                }
            break;    
        }
    
        return;
    }
    
    
    
  8. 次に、IMFMediaEngine によって公開されるメソッドの単なるラッパーである、複数のメソッドを定義します。PlayPauseSetMuted、および GetNativeVideoSize により、メイン アプリでこれらのメソッドを簡単に呼び出すことができます。Play および Pause では、現在の再生の状態を追跡するために、メンバー変数 m_isPlaying が更新されることに注意してください。また、メイン アプリで再生の状態を照会できるように、アクセサー関数も指定されます。

    
    void MediaEnginePlayer::Play()
    {    
        if (m_spMediaEngine)
        {
            DX::ThrowIfFailed(m_spMediaEngine->Play());
            m_isPlaying = true;            
        }
        return;
    }
    
    
    
    
    void MediaEnginePlayer::SetMuted(bool muted)
    {    
        if (m_spMediaEngine)
        {
            DX::ThrowIfFailed(m_spMediaEngine->SetMuted(muted));
        }
        return;
    }
    
    
    
    
    void MediaEnginePlayer::GetNativeVideoSize(DWORD *cx, DWORD *cy)
    {    
        if (m_spMediaEngine)
        {
            m_spMediaEngine->GetNativeVideoSize(cx, cy);
        }
        return;
    }
    
    
    
    
    bool MediaEnginePlayer::IsPlaying()
    {
        return m_isPlaying;
    }
    
    
    
  9. メイン アプリでは、フレームごとに TransferFrame メソッドを呼び出します。これは、メディア エンジンがビデオのフレームを指定のテクスチャに実際にコピーする時点です。このメソッドに対する引数は、ビデオ フレームのコピー先にする必要があるテクスチャ、コピー元にする必要があるビデオ フレームからのソースの四角形、およびビデオ フレームのコピー先にする必要があるテクスチャの宛先の四角形です。このメソッドで確認するのは、メディア エンジンのインスタンスが null ではないこと、およびビデオが再生されている必要があることを m_isPlaying 変数で示していることです。次に、IMFMediaEngine::OnVideoStreamTick を呼び出します。メディア エンジンに、レンダリングの準備ができているビデオの新しいフレームが存在する場合、このメソッドにより S_OK が返されます。このようなフレームが存在する場合、IMFMediaEngine::TransferVideoFrame が呼び出され、テクスチャへのビデオ フレームの実際のコピーを実行します。

    メモメモ:

    ビデオ ストリームのシーク中 (IMFMediaEngine::SetCurrentTime を使用) またはビデオ ストリームの変更中は、IMFMediaEngine::TransferVideoFrame により E_FAIL が返されることがあります。アプリでは、このエラーを単純に無視してかまいません。次のフレームが使用可能になった時点でメソッドを再度呼び出してください。この例では、呼び出し元でキャッチできる例外がスローされます。

    
    void MediaEnginePlayer::TransferFrame(ComPtr<ID3D11Texture2D> texture, MFVideoNormalizedRect rect ,RECT rcTarget)
    {
        if (m_spMediaEngine != nullptr && m_isPlaying)
        {
            LONGLONG pts;
            if (m_spMediaEngine->OnVideoStreamTick(&pts) == S_OK)
            {
                // new frame available at the media engine so get it 
                DX::ThrowIfFailed(
                    m_spMediaEngine->TransferVideoFrame(texture.Get(), &rect, &rcTarget, &m_bkgColor)
                    );
            }
        }
    
        return;
    }
    
    
    
  10. MediaEnginePlayer クラスの最後のメソッドは,デストラクターと Shutdown メソッドです。これらのメソッドは、まず IMFMediaEngine::Shutdown を呼び出してメディア エンジンのインスタンスをシャットダウンし、そのリソースを解放します。続いて MFShutdown を呼び出して Media Foundation をシャットダウンします。

    
    MediaEnginePlayer::~MediaEnginePlayer()
    {
        Shutdown();
        MFShutdown();
    }
    
    
    
    
    void MediaEnginePlayer::Shutdown()
    {
        if (m_spMediaEngine)
        {
            m_spMediaEngine->Shutdown();
        }
        return;
    }
    
    
    

Direct3D アプリのテンプレートを変更して MediaEnginePlayer クラスを使用する

以前に作成した MediaEnginePlayer クラスは、メディア エンジンの機能の汎用目的ラッパーとして使用します。このクラスをインスタンス化し、Initialize を呼び出し、ビデオ ストリームのソースを設定し、Play を呼び出します。続いて、テクスチャを渡し、TransferFrame を呼び出して、ビデオ フレームのレンダリング先にするテクスチャを渡します。プレーヤーの使用方法は、アプリのデザインによって異なります。ここでは、Direct3D アプリのテンプレートを変更して、レンダリングされたビデオを画面上の四角形に表示する手順を説明します。これは、テクスチャにレンダリングされたビデオを使用する方法の 1 つにすぎません。

このセクションでは、Direct3D アプリのテンプレートに含まれる CubeRenderer クラスのみを変更します。説明内容をできる限り簡潔するにするため、ここでは、変更する必要がある CubeRenderer ソース ファイルのメソッドのみを示します。ここに示されていないメソッドは、この例では変更せずそのままにしておく必要があります。そのままにしておくと、Direct3D アプリのテンプレートにより、頂点の色を使用した網かけが適用された、回転するキューブが表示されます。CubeRenderer を変更すると、この動作が変更され、テクスチャを使用した網かけが適用された正方形が表示されます。

CubeRenderer クラスを変更するには

  1. まず、CubeRenderer.h を変更します。ファイルの先頭で include ステートメントを追加し、MediaEnginePlayer ヘッダー ファイルをインクルードします。

    
    #include "MediaEnginePlayer.h"
    
    
    
  2. SimpleVertex 構造体の定義を追加します。この構造体は、レンダー パイプラインに渡す頂点の形式を指定します。既定のアプリのテンプレートでは、位置と色が含まれる頂点を使用します。ジオメトリにビデオをテクスチャマッピングできるように、この例では、位置とテクスチャの座標が含まれる頂点を使用します。

  3. 次に、CubeRenderer クラス宣言の末尾で、次のメンバー変数を宣言します。

    
    	Microsoft::WRL::ComPtr<ID3D11Texture2D> m_texture;
        Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> m_textureView;      
        Microsoft::WRL::ComPtr<ID3D11SamplerState> m_sampler;  
    	
        MediaEnginePlayer* m_player;
    
    
    

    ID3D11Texture2D 変数は、ビデオのレンダリング先であるテクスチャを格納します。ID3D11ShaderResourceView は、テクスチャ リソースをシェーダーにバインドします。ID3D11SamplerState は、テクスチャをラップする、タイルにする、またはクリップするなど、テクスチャをシェーダーによりサンプリングする方法を定義します。 最後の新しいメンバー変数は、MediaEnginePlayer クラスへのポインターです。

  4. 次に、CubeRenderer.cpp 実装ファイルを変更します。最初に変更するメソッドは、CreateDeviceResources メソッドです。このメソッドでは、グラフィック デバイスのインスタンスを作成する必要があるリソースが初期化されます。このメソッドはかなり大きく、コードの一部はそのままにしますが、このチュートリアルでは既存のメソッドを削除して、その部分を以下のコードに置き換える必要があります。このメソッドには非常に多くのコード行があるため、以降の数回の手順でインクリメント方式で構築します。

    メソッドの最初の部分を、コンストラクターの後にある CubeRenderer クラス定義に貼り付けます。

    最初の行は、CreateDeviceResources の基本実装を呼び出します。これは、グラフィック デバイスが作成される場所です次に、頂点シェーダーとピクセル シェーダーで非同期に読み取るプロジェクト テンプレートにインクルードされている、DirectXHelper.h ファイルのヘルパー メソッドを使用します。これらのメソッドにより、以降のコードで実際に実行される task オブジェクトが返されます。

  5. 次に、createVSTask タスクのコードを貼り付けます。

    
    	auto createVSTask = loadVSTask.then([this](Platform::Array<byte>^ fileData) {
    		DX::ThrowIfFailed(
    			m_d3dDevice->CreateVertexShader(
    				fileData->Data,
    				fileData->Length,
    				nullptr,
    				&m_vertexShader
    				)
    			);
    
    		const D3D11_INPUT_ELEMENT_DESC vertexDesc[] = 
            {
                { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
                { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 }
            };
    
    
    
    		DX::ThrowIfFailed(
    			m_d3dDevice->CreateInputLayout(
    				vertexDesc,
    				ARRAYSIZE(vertexDesc),
    				fileData->Data,
    				fileData->Length,
    				&m_inputLayout
    				)
    			);
    	});
    
    
    

    ここでは、グラフィック デバイスを使用してシェーダー ファイル ストリームから頂点シェーダーを作成します。続いて、頂点シェーダーで使用する頂点の説明が定義されます。各頂点には位置とテクスチャの座標があることに注意してください。既定のテンプレートでは、位置と頂点の色が含まれる頂点を使用します。次に、定義済みの入力レイアウトを格納するために、ID3D11InputLayout オブジェクトが作成されます。

  6. 続いて、createPSTask タスクのコードを貼り付けます。

    
    	auto createPSTask = loadPSTask.then([this](Platform::Array<byte>^ fileData) {
    		DX::ThrowIfFailed(
    			m_d3dDevice->CreatePixelShader(
    				fileData->Data,
    				fileData->Length,
    				nullptr,
    				&m_pixelShader
    				)
    			);
    
    		CD3D11_BUFFER_DESC constantBufferDesc(sizeof(ModelViewProjectionConstantBuffer), D3D11_BIND_CONSTANT_BUFFER);
    		DX::ThrowIfFailed(
    			m_d3dDevice->CreateBuffer(
    				&constantBufferDesc,
    				nullptr,
    				&m_constantBuffer
    				)
    			);
    	});
    
    
    

    ここでは、グラフィック デバイスを使用してシェーダー ファイル ストリームからピクセル シェーダーを作成します。次に、モデル、ビュー、および射影行列を格納するために、定数バッファーが作成されます。レンダリングされた正方形を射影する目的ではこれらの行列を使用しませんが、これらの行列は既定のテンプレートの一部です。

  7. ここで、createCubeTask タスクの最初の部分のコードを貼り付けます。

    
    	auto createCubeTask = (createPSTask && createVSTask).then([this] () {
    		SimpleVertex cubeVertices[] = 
            {
                { XMFLOAT3( -1.0f, -0.45f, 0.4f ), XMFLOAT2( 0.0f, 1.0f ) },
                { XMFLOAT3( -1.0f, 0.45f, 0.4f ), XMFLOAT2( 0.0f, 0.0f ) },
                { XMFLOAT3( 1.0f, -0.45f, 0.4f ), XMFLOAT2( 1.0f, 1.0f ) },
    
                { XMFLOAT3( 1.0f, -0.45f, 0.4f ), XMFLOAT2( 1.0f, 1.0f ) },
                { XMFLOAT3( -1.0f, 0.45f, 0.4f ), XMFLOAT2( 0.0f, 0.0f ) },
                { XMFLOAT3( 1.0f, 0.45f, 0.4f ), XMFLOAT2( 1.0f, 0.0f ) },
            };
    
    		D3D11_SUBRESOURCE_DATA vertexBufferData = {0};
    		vertexBufferData.pSysMem = cubeVertices;
    		vertexBufferData.SysMemPitch = 0;
    		vertexBufferData.SysMemSlicePitch = 0;
    		CD3D11_BUFFER_DESC vertexBufferDesc(sizeof(cubeVertices), D3D11_BIND_VERTEX_BUFFER);
    		DX::ThrowIfFailed(
    			m_d3dDevice->CreateBuffer(
    				&vertexBufferDesc,
    				&vertexBufferData,
    				&m_vertexBuffer
    				)
    			);
    
    		unsigned short cubeIndices[] = 
            {
                0, 1, 2, 3, 4, 5
            };
    
    		m_indexCount = ARRAYSIZE(cubeIndices);
    
    		D3D11_SUBRESOURCE_DATA indexBufferData = {0};
    		indexBufferData.pSysMem = cubeIndices;
    		indexBufferData.SysMemPitch = 0;
    		indexBufferData.SysMemSlicePitch = 0;
    		CD3D11_BUFFER_DESC indexBufferDesc(sizeof(cubeIndices), D3D11_BIND_INDEX_BUFFER);
    		DX::ThrowIfFailed(
    			m_d3dDevice->CreateBuffer(
    				&indexBufferDesc,
    				&indexBufferData,
    				&m_indexBuffer
    				)
    			);
    
    
    

    コードのこのセクションでは、レンダリングされるジオメトリを構成する点を定義する、頂点バッファーを作成します。頂点は型 SimpleVertex で、これは CubeRenderer.h で定義済みです。既定のテンプレートではキューブを定義しますが、このコードでは 2 つの三角形から構成されている四角形を表す 6 つの点を定義します。これらの点の位置は、ビデオ ソースの同じ縦横比の正方形を指定するように選択されています。必要な任意のジオメトリを使用できます。ジオメトリは、四角形である必要はなく、また特定の縦横比を維持する必要はありません。

  8. 次に、インデックス バッファーを作成します。このバッファーで、頂点を描画する順序を定義します。これを行うには、createCubeTask タスクの次のセクションを貼り付けます。

    
    		DX::ThrowIfFailed(
                m_d3dDevice->CreateTexture2D(
                    &CD3D11_TEXTURE2D_DESC(
                        DXGI_FORMAT_B8G8R8A8_UNORM,
                        320,        // Width
                        240,        // Height
                        1,          // MipLevels
                        1,          // ArraySize
                        D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET
                        ),
                    nullptr,
                    &m_texture
                    )
                );
    
            DX::ThrowIfFailed(
                m_d3dDevice->CreateShaderResourceView(
                    m_texture.Get(),
                    &CD3D11_SHADER_RESOURCE_VIEW_DESC(
                        m_texture.Get(),
                        D3D11_SRV_DIMENSION_TEXTURE2D
                        ),
                    &m_textureView
                    )
                );
    
            D3D11_SAMPLER_DESC samplerDescription;
            ZeroMemory(&samplerDescription, sizeof(D3D11_SAMPLER_DESC));
            samplerDescription.Filter = D3D11_FILTER_ANISOTROPIC;
            samplerDescription.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
            samplerDescription.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
            samplerDescription.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
            samplerDescription.MipLODBias = 0.0f;
            samplerDescription.MaxAnisotropy = 4;
            samplerDescription.ComparisonFunc = D3D11_COMPARISON_NEVER;
            samplerDescription.BorderColor[0] = 0.0f;
            samplerDescription.BorderColor[1] = 0.0f;
            samplerDescription.BorderColor[2] = 0.0f;
            samplerDescription.BorderColor[3] = 0.0f;
            samplerDescription.MinLOD = 0;
            samplerDescription.MaxLOD = D3D11_FLOAT32_MAX;
    
            DX::ThrowIfFailed(
                m_d3dDevice->CreateSamplerState(
                    &samplerDescription,
                    &m_sampler)
                );
    	});
    
    
    

    このセクションにより、テクスチャを使用して正方形に網かけを適用するために必要な 3 つのメンバー変数が初期化されます。まず、ID3D11Texture2D が作成されます。テクスチャは DXGI_FORMAT_B8G8R8A8_UNORM ピクセル形式を使用して作成されていることに注意してください。ビデオが同じピクセル形式を使用してレンダリングされていることを確認するために、この形式は後でメディア エンジンに渡されます。続いて、ID3D11ShaderResourceView および ID3D11SamplerState が初期化されます。この短いコードで、createCubeTask タスクが完成します。

  9. 次のコードは CreateDeviceResources メソッドの最後のセクションです。

    このメソッドの最後の部分は、MediaEnginePlayer クラスの新しいインスタンスを作成し、Initialize を呼び出して、テクスチャの初期化に使用した同じピクセル形式を渡します。次に、ビデオ ファイルが開かれ、SetByteStream メソッドに渡されます。ビデオ ファイルの URI を SetSource メソッドに渡すためのコードも示されていますが、コメント アウトされています。

  10. 最後に変更する必要があるメソッドは、Render メソッドです。このメソッドも、いくつかの手順でインクリメント方式で構築します。Render メソッドの先頭は、既定のテンプレートと同じです。

    
    void CubeRenderer::Render()
    {
    
    	const float midnightBlue[] = { 0.098f, 0.098f, 0.439f, 1.000f };
    	m_d3dContext->ClearRenderTargetView(
    		m_renderTargetView.Get(),
    		midnightBlue
    		);
    
    	m_d3dContext->ClearDepthStencilView(
    		m_depthStencilView.Get(),
    		D3D11_CLEAR_DEPTH,
    		1.0f,
    		0
    		);
    
    
    	// Only draw the cube once it is loaded (loading is asynchronous).
    	if (!m_loadingComplete)
    	{
    		return;
    	}
    
    
    
    
  11. ここで、テクスチャにビデオをレンダリングするこのメソッドのセクションを貼り付けます。コードのこのセクションでは、ビデオをレンダリングするためのソースと対象の四角形を定義し、TransferFrame を呼び出して、四角形とターゲットのテクスチャを渡します。IMFMediaEngine::TransferVideoFrame からエラーが返されると、TransferFrame メソッドにより例外がスローされることに注意してください。この状態が発生すると、アプリでは単に例外をキャッチし、return を呼び出して現在のフレームをスキップします。

    
    	RECT r;
        r.top = 0.0f;
    	r.left = 0.0f;
        r.bottom = 240.0f;
        r.right = 320.0f;
    
        MFVideoNormalizedRect rect;
        rect.top = 0.0f;
    	rect.left = 0.0f;
        rect.right = 1.0f;
    	rect.bottom = 1.0f;
    
    	try
    	{
    		m_player->TransferFrame(m_texture, rect, r); 
    	}
    	catch (Platform::Exception ^ ex)
    	{
    		// Occasionally TransferFrame fails		
    		// Wait for next frame to draw
    		return ;
    	}
    
    
    
  12. メソッドの次のセクションでは、グラフィック デバイスのレンダー ターゲットを設定し、変換の行列が含まれるバッファーを取得します。このセクションは、既定のテンプレートから変更されていません。

    
    	m_d3dContext->OMSetRenderTargets(
    		1,
    		m_renderTargetView.GetAddressOf(),
    		m_depthStencilView.Get()
    		);
    
    	m_d3dContext->UpdateSubresource(
    		m_constantBuffer.Get(),
    		0,
    		NULL,
    		&m_constantBufferData,
    		0,
    		0
    		);
    
    
    
  13. 次に、グラフィック デバイスの現在のバッファーとして頂点バッファーを設定します。CubeRenderer.h で定義されている SimpleVertex 構造体が、頂点ごとのバイト数を指定していることに注意してください。

  14. Render メソッドの次のセクションは、既定のアプリのテンプレートと同じです。アクティブなインデックス バッファー、ピクセル シェーダー、および頂点シェーダーを設定します。

    
    
    	m_d3dContext->IASetIndexBuffer(
    		m_indexBuffer.Get(),
    		DXGI_FORMAT_R16_UINT,
    		0
    		);
    
    
    	m_d3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    
    	m_d3dContext->IASetInputLayout(m_inputLayout.Get());
    
    	m_d3dContext->VSSetShader(
    		m_vertexShader.Get(),
    		nullptr,
    		0
    		);
    
    
    
    	m_d3dContext->VSSetConstantBuffers(
    		0,
    		1,
    		m_constantBuffer.GetAddressOf()
    		);
    
    	m_d3dContext->PSSetShader(
    		m_pixelShader.Get(),
    		nullptr,
    		0
    		);
    
    
    
    
  15. 次の短いコードでは、ピクセル シェーダーにテクスチャをバインドし、テクスチャをサンプリングする方法を示すサンプラーを設定します。

  16. Render メソッドの最後の部分は、既定のテンプレートと同じです。単に ID3D11DeviceContext::DrawIndexed を呼び出して、画面に正方形をレンダリングします。

頂点シェーダーとピクセル シェーダーの変更

アプリを実行する前に、頂点シェーダーとピクセル シェーダーを変更する必要があります。頂点の色ではなくテクスチャ マッピングを使用して入力ジオメトリに網かけを適用するには、両方のシェーダーを変更する必要があります。また、既定のテンプレートに含まれるシェーダーでは、シェーダーに指定されている頂点には位置と色があることが前提になっています。位置とテクスチャの座標が含まれる頂点を使用するようにアプリを変更したため、このピクセル形式を使用するようにシェーダーを変更する必要があります。

SimpleVertexShader.hlsl の内容を次のコードに置き換えます。

Texture2D colorMap_ : register( t0 );
SamplerState colorSampler_ : register( s0 );

struct VS_Input
{
    float4 pos  : POSITION;
    float2 tex0 : TEXCOORD0;
};

struct PS_Input
{
    float4 pos  : SV_POSITION;
    float2 tex0 : TEXCOORD0;
};

PS_Input main(VS_Input vertex )
{
    PS_Input vsOut = ( PS_Input )0;
    vsOut.pos = vertex.pos;
    vsOut.tex0 = vertex.tex0;

    return vsOut;
}

SimplePixelShader の内容を次のコードに置き換えます。

Texture2D colorMap_ : register( t0 );
SamplerState colorSampler_ : register( s0 );

struct PS_Input
{
    float4 pos  : SV_POSITION;
    float2 tex0 : TEXCOORD0;
};

float4 main( PS_Input frag ) : SV_TARGET
{
    return colorMap_.Sample( colorSampler_, frag.tex0 );
}

これで、メディア エンジンを使用してテクスチャにビデオをレンダリングするアプリを作成するチュートリアルが完了しました。

表示:
© 2014 Microsoft