此页面有用吗?
您对此内容的反馈非常重要。 请告诉我们您的想法。
更多反馈?
1500 个剩余字符
MSDN Library
信息
您所需的主题如下所示。但此主题未包含在此库中。

演练:对 Windows Phone 8 使用 Microsoft 媒体基础

2014/6/18

仅适用于:Windows Phone 8 和 Windows Phone Silverlight 8.1

Microsoft Media Foundation (MF) 是用于桌面音频和视频捕获与播放的框架。Windows Phone 的 Microsoft Media Foundation 是 MF API 的子集的重新实现。借助此功能,Windows Phone 8 应用能够实现以下方案。

  • 在使用本机代码的应用中将视频呈现为纹理。

  • 显示游戏电影艺术。

  • 在使用本机代码的应用中播放游戏中的后台音频,如游戏原声音乐。

提示提示:

该功能不适合后台流方案,如 Internet 收音机电台或音乐播放器应用。仅在应用位于前台时,使用 MF 播放音频。关于为后台流方案创建应用的信息,请参见如何播放 Windows Phone 8 的后台音频

Windows Phone 8 支持 Windows 8 上受支持的 API 的子集。有关 Windows Phone 上受支持的 Media Foundation API 的列表,请参见 Windows Phone 8 支持的 Microsoft 媒体基础 API

在本主题中,我们将向您演示创建使用 MF 将视频呈现为纹理的应用的过程。接着,我们会将纹理映射到几何图形,然后再将几何图形呈现到屏幕。此方法要求实现大量代码。如果您正在创建 XAML 应用、XAML 和 Direct3D 应用或具有 XAML 应用的 Direct3D 并且您只想要在屏幕上采用二维矩形区域播放视频文件,使用 MediaElement 控件可以更快更轻松地执行此操作。如果您创建的是 Direct3D 应用,XAML 控件不受支持,因此您必须使用 MF 来呈现视频。此外,如果您要将视频用作三维几何图形上的纹理,也必须使用 MF。

本演练从 Direct3D 应用项目模板开始。然后,我们分两个阶段创建示例应用。首先,我们将创建名为 MediaEnginePlayer 的包装类,它将打包 MF API 的功能。然后,我们将修改包含在项目模板中的 CubeRenderer 类,以使用 MediaEnginePlayer 类。

设置项目

要开始操作,我们需要创建新的项目并配置它以使用 Media Foundation 库。

设置项目

  1. 在 Visual Studio 中的“文件”菜单上,选择“新建项目”。在“模板”下,展开“C++”,然后选择“Windows Phone”。在列表中,选择 Windows Phone Direct3D 应用(仅本机)。以任意名称命名该项目,然后单击“确定”

  2. 要将 Media Foundation 静态库添加到链接器输入,在“项目”菜单上,选择“[项目名称]属性”。在“属性页”窗口的左窗格中,展开“配置属性”,展开“链接器”,然后选择“输入”。在中心窗格中,将文本“mfplat.lib;”添加到“附加依赖项”行的开头。注意不要更改现有的输入参数。

创建 MediaEnginePlayer 类

MediaEnginePlayer 类充当包装 MF API 的帮助器类。我们还将定义第二个类,MediaEngineNotify,以从媒体引擎接收通知。但是,此类极为简单,因此我们可以在相同的 .cpp 实现文件中将它定义为 MediaEnginePlayer

实现 MediaEnginePlayer 类

  1. “项目”菜单上选择“添加类”。选择“Visual C++”,然后选择“C++ class”。单击“添加”。这将显示一般 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 指令定义名为 MediaEngineCallback 的结构,它包含一种方法,即 OnMediaEngineEventMediaEnginePlayer 类派生自此结构。此继承结构允许将 MediaEngineNotify 类(我们将在 .cpp 实现文件中定义它)传递到它从媒体引擎接收的 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 拥有 MediaEngineNotifyCallback 类型的成员变量,它是 MediaEnginePlayer 类的基类。MediaEngineNotify 类在此变量中保留对 MediaEnginePlayer 类的引用,以便可以向其传递媒体引擎事件。

    在其构造函数后,MediaEngineNotify 类提供标准 COM 接口方法(QueryInterfaceAddRefRelease)的实现。接下来,将提供 MediaEngineNotifyCallback 方法以便 MediaEnginePlayer 类可以注册自身以接收媒体引擎事件。

    最后,将定义 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 首先完成的第一件事情就是调用 MFStartup 以初始化 Media Foundation。在尝试创建任何 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 或字节流。第一种方法将接收 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 公开的方法的包装。PlayPauseSetMutedGetNativeVideoSize 允许主应用轻松调用这些方法。请注意,在 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。应用只需忽略此错误,并在下一个帧可用时,再次调用该方法。在本示例中,调用方可以捕获引发的异常。

    
    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 应用模板以在屏幕上采用矩形显示呈现的视频的过程。这只是使用已呈现为纹理的视频的一种方法。

本节演练只修改包含在 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 中对它进行了定义。默认模板将定义一个立方体,但此代码将定义六个点,表示由两个三角形组成的矩形。选择这些点的位置以提供与视频源的纵横比相同的正方形。您可以使用任何想要的几何图形。它不一定要是矩形或保持任何特定的纵横比。

  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 方法将引发异常。如果发生这种情况,应用仅捕获该异常并调用 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 );
}

至此,我们完成了使用媒体引擎将视频渲染为纹理的演练。

Microsoft 正在进行一项网上调查,以了解您对 MSDN 网站的意见。 如果您选择参加,我们将会在您离开 MSDN 网站时向您显示该网上调查。

是否要参加?
显示:
© 2015 Microsoft