정보
요청한 주제가 아래에 표시됩니다. 그러나 이 주제는 이 라이브러리에 포함되지 않습니다.

연습: Windows Phone 8용 Microsoft Media Foundation 사용

2014-06-18

적용 대상: Windows Phone 8 및 Windows Phone Silverlight 8.1만

 

Microsoft MF(Media Foundation)는 데스크톱에 대한 오디오/동영상 캡처 및 재생을 위한 프레임워크입니다. 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 앱 프로젝트 템플릿 사용에서 시작됩니다. 그런 다음 두 개 단계에서 예제 앱을 만듭니다. 먼저 MF API의 기능을 래핑하는 MediaEnginePlayer라는 래퍼 클래스를 만듭니다. 그런 다음 프로젝트 템플릿에 포함된 CubeRenderer 클래스를 수정하여 MediaEnginePlayer 클래스를 사용합니다.

프로젝트 설정

시작하려면 새 프로젝트를 만들고 Media Foundation 라이브러리를 사용하도록 구성해야 합니다.

프로젝트를 설정하려면

  1. Visual Studio의 파일 메뉴에서 새 프로젝트를 선택합니다. 템플릿에서 C++를 확장하고 Windows Phone을 선택합니다. 목록에서 Windows Phone Direct3D 앱(네이티브 전용)을 선택합니다. 프로젝트 이름을 원하는 이름으로 지정하고 확인을 클릭합니다.

  2. Media Foundation 정적 라이브러리를 링크 입력에 추가하려면 프로젝트 메뉴에서 [프로젝트 이름] 속성을 선택합니다. 속성 페이지 창의 왼쪽 창에서 구성 속성, 링커를 차례로 확장하고 입력을 선택합니다. 가운데 창의 추가 종속성 줄에서 텍스트 “mfplat.lib;”을 줄의 시작 부분에 추가합니다. 기존 입력 매개 변수를 변경하지 않도록 주의하세요.

MediaEnginePlayer 클래스 만들기

MediaEnginePlayer 클래스는 MF API를 래핑하는 도우미 클래스로 사용됩니다. 또한 두 번째 클래스 MediaEngineNotify를 정의하여 미디어 엔진에서 알림을 수신합니다. 하지만 이 클래스는 매우 단순하므로 MediaEnginePlayer와 동일한 .cpp 구현 파일에서 이 클래스를 정의할 수 있습니다.

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;
    };
    
    
    
    

    includeusing 지시문에 이어 하나의 메서드 OnMediaEngineEvent가 들어 있는 MediaEngineCallback 구조체가 정의됩니다. MediaEnginePlayer 클래스는 이 구조체에서 파생됩니다. 이 상속 구조체에서는 .cpp 구현 파일에서 정의할 MediaEngineNotify 클래스가 미디어 엔진에서 수신한 MediaEnginePlayer 클래스 이벤트로 전달될 수 있습니다. 다음으로 IMFMediaEngineIMFMediaEngineEx 인터페이스에 대한 스마트 포인터를 선언하고 미디어 엔진에서 사용되는 배경색을 설정하는 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에는 MediaEnginePlayer 클래스의 기본이 되는 MediaEngineNotifyCallback 형식의 구성원 변수가 있습니다. MediaEngineNotify 클래스는 MediaEnginePlayer 클래스에 대한 참조를 이 변수에 보관하므로 참조를 미디어 엔진 이벤트에 전달할 수 있습니다.

    생성자 후에 MediaEngineNotify 클래스는 표준 COM 인터페이스 메서드 QueryInterface, AddRefRelease의 구현을 제공합니다. 다음으로 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에 대한 인수는 ID3D11DeviceDXGI_FORMAT 값입니다. 미디어 엔진은 장치를 사용하여 동영상 프레임을 앱에서 제공된 질감으로 복사합니다. DXGI_FORMAT 값을 통해 미디어 엔진은 질감 형식을 인식할 수 있습니다.

    Initialize가 수행하는 첫 작업은 몇몇 로컬 변수를 선언한 후 Media Foundation을 초기화하는 MFStartup을 호출하는 것입니다. 이 호출은 MF 인터페이스의 인스턴스를 만들기 전에 이루어져야 합니다. 다음으로 DXGI 장치 관리자가 MFCreateDeviceManager를 통해 생성되고 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 또는 바이트 스트림을 지정할 수 있습니다. 첫 번째 메서드는 동영상 파일의 URI를 Platform::String 형식으로 사용합니다. 미디어 엔진은 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;
    }
    
    
    

    소스 콘텐츠 설정을 위한 두 번째 메서드는 IRandomAccessStream 및 URI를 인수로 사용합니다. 여기에서도 URI는 BSTR로 변환됩니다. 그런 다음 MFCreateMFByteStreamOnStreamEx 함수를 호출하여 IRandomAccessStream을 미디어 엔진이 사용할 수 있는 스트림으로 래핑합니다. 마지막으로 IMFMediaEngineEx::SetSourceFromByteStream을 호출하여 미디어 엔진의 소스를 설정합니다. 이 메서드는 이 예제에서 사용되는 IMFMediaEngineEx 인터페이스의 유일한 메서드이며 휴대폰에서 지원되는 이 인터페이스의 세 가지 메서드 중 하나입니다.

    
    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는 발생한 이벤트를 나타냅니다. 이 예제에서는 여러 가지 이벤트를 보여 주지만 두 가지 이벤트만 처리됩니다. 미디어 엔진이 미디어 스트림을 성공적으로 로드하고 미디어 스트림이 재생할 준비가 되면 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에 의해 노출되는 메서드의 래퍼인 여러 메서드를 정의합니다. Play, Pause, SetMutedGetNativeVideoSize를 통해 기본 앱이 이러한 메서드를 쉽게 호출할 수 있습니다. PlayPause에서 구성원 변수 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을 반환합니다. 앱은 이 오류를 무시하고 다음 프레임을 사용할 수 있을 때 다시 메서드를 호출해야 합니다. 이 예제에서는 호출자가 catch할 수 있는 예외가 발생합니다.

    
    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 클래스는 Media Engine 기능의 범용 래퍼로 사용됩니다. 클래스를 인스턴스화하고 Initialize를 호출하고 동영상 스트림 소스를 설정하고 Play를 호출합니다. 그런 다음 질감을 전달하고 TransferFrame을 호출하여 동영상 프레임을 렌더링할 대상 질감을 전달합니다. 플레이어 사용 방법은 앱 디자인에 따라 다릅니다. 이 섹션에서는 Direct3D 앱 템플릿을 수정하여 렌더링된 동영상을 화면의 사각형에 표시하는 프로세스를 살펴봅니다. 이는 질감에 렌더링된 동영상을 사용하는 한 가지 방법입니다.

연습의 이 섹션에서는 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
    				)
    			);
    
    
    

    코드의 이 섹션에서는 렌더링할 기하 도형을 구성하는 점을 정의하는 꼭짓점 버퍼를 만듭니다. 꼭짓점은 CubeRenderer.h에서 정의한 SimpleVertex 유형입니다. 기본 템플릿은 큐브를 정의하지만 이 코드는 삼각형 두 개로 구성된 사각형을 나타내는 점 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)
                );
    	});
    
    
    

    이 섹션은 사각형을 질감으로 음영 처리하는 데 필요한 구성원 변수 세 개를 초기화합니다. 먼저 ID3D11Texture2D가 생성됩니다. 질감은 DXGI_FORMAT_B8G8R8A8_UNORM 픽셀 형식으로 생성됩니다. 이 형식은 나중에 미디어 엔진으로 전달되어 동영상이 동일한 픽셀 형식으로 렌더링되는지 확인합니다. 그런 다음 ID3D11ShaderResourceViewID3D11SamplerState가 초기화됩니다. 코드의 이 부분은 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이 예외를 발생시킵니다. 이 예외가 발생하면 앱이 예외를 catch하고 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 );
}

미디어 엔진을 사용하여 동영상을 질감에 렌더링하는 앱을 만드는 연습을 완료했습니다.

표시: