【Media Foundation】简单实例 - 使用Media Session来播放文件

参考MSDN官方的页面:http://msdn.microsoft.com/en-us/library/ms703190(v=vs.85)

本文详细演示了如何使用Media Foundation中的Media Session对象来播放媒体文件。也就是不自己编写/自定义任何的Media Foundation组件,一切都是用现成的,以及让Media Foundation“自动完成”的(如Topology的解析)。Media Foundation的API会根据文件的路径或URL智能创建合适的media source组件,并会智能地在media source和音视频渲染器(renderer)之间添加合适的解码器等等。Topology中的数据流等任务由Media Session来处理。

这是最简单的开发任务。然而,如果要实现使用自定义的meida source或media transform组件这样的任务,可能不能使用Media Session。

 

 

预备知识

 

在阅读本主题之前,你需要熟悉以下MF概念:

  • Media Session
  • Source Resolver
  • Topologies
  • Media Event Generators
  • Presentation Descriptors

注意:此主题不描述如何播放被DRM保护的文件。关于MMF中DRM的相关信息,见 How to Play Protected Media Files

其实不太了解以上概念也没关系,通过这个小例子的动手实践,我们会对一些基本概念有个基本了解。

 

概述

以下对象用来和Media Session播放多媒体文件:

  • media source对象用来解析多媒体文件或其他媒体数据源。media source为文件中的每个音频或视频流创建一个steam对象。 Decoders把编码后的多媒体数据转换为非压缩视频和音频
  • Source Resolver从URL创建一个media source
  • EVR将视频渲染到屏幕上
  • SAR将音频渲染至扬声器或其他音频输出设备
  • Topology定义从media source至EVR和SAR的数据流
  • Media Session控制数据流,并发送状态数据到应用程序。下图展示了这个过程

 

 

step by step实例

大概了解一下概念,我们可以来进行实践了。我们主要将完成以下任务:

  • Media Foundation平台的初始化与关闭
  • 创建media session
  • 根据文件路径,(智能)创建(合适的)media source
  • 创建topology,添加media source、EVR/SAR(renderer)节点,并将其连接,此时的topology是一个partial topology
  • 将刚创建的topology关联到media session,内部的topology loader会给partial topology“智能地”加入所需的解码器等节点,使其成为一个complete topology
  • 获取和处理来自media session的事件
  • 用media session来控制播放,但不要直接操作media source
  • 程序结束,释放资源

1. 创建程序

我使用visual studio 2012创建了一个基于对话框的MFC项目。含有可缩放的边框、 最小化框。

再创建一个菜单,把对话框的菜单属性设为此菜单。

添加全局的播放核心类对象

Core*    g_pCore = NULL; 

Core*   _pCore = NULL;

在stdafx.h中添加要用到的头文件和类模板。头文件后面的注释说明了为什么需要它

  1. #include <mfapi.h>    // MFStartup, mfplat.lib 
  2. #include <mfidl.h>    // MFCreateMediaSession, mf.lib 
  3. #include <evr.h>  // IMFVideoDisplayControl, strmiids.lib 
  4. #include <shlwapi.h>  // QITABENT, shlwapi.lib 
  5. #include <mferror.h>  // MF_E_ALREADY_INITIALIZED 
  6.  
  7. template <class T> void SafeRelease(T **ppT) 
  8.     if (*ppT) 
  9.     { 
  10.         (*ppT)->Release(); 
  11.         *ppT = NULL; 
  12.     } 
  13.  
  14. #include "Core.h" // Core类头文件 

配置项目属性,此项目需要链接以下Lib:

mfplat.lib; mf.lib; mfuuid.lib; strmiids.lib; shlwapi.lib 

mfplat.lib; mf.lib; mfuuid.lib; strmiids.lib; shlwapi.lib

 

2. 创建播放核心类Core

首先定义播放事件标识和播放状态的枚举

  1. constUINT WM_APP_PLAYER_EVENT = WM_APP + 1; 
  2.  
  3.  
  4. enum PlayerState 
  5.     Closed = 0,     // No session. 
  6.     Ready,          // Session was created, ready to open a file. 
  7.     OpenPending,    // Session is opening a file. 
  8.     Started,        // Session is playing a file. 
  9.     Paused,         // Session is paused. 
  10.     Stopped,        // Session is stopped (ready to play). 
  11.     Closing         // Application has closed the session, 
  12.                     // but is waiting for MESessionClosed. 
  13. }; 

 

此类被设计为单例模式(singleton),为了处理事件方便,它继承自IMFAsyncCallback接口,此接口又继承自IUnknown接口。因此我们要为Core类实现这些接口的所有必需方法。

类声明:

  1. class Core : public IMFAsyncCallback 
  2. protected
  3.     Core(HWND hVideo); 
  4.     virtual ~Core(void); 
  5.  
  6. public
  7.     staticHRESULT CreateInstance(HWND hVideo, Core** ppCore); 
  8.  
  9.     // IUnknown方法 
  10.     STDMETHODIMP QueryInterface(REFIID iid, void** ppv); 
  11.     STDMETHODIMP_(ULONG) AddRef(); 
  12.     STDMETHODIMP_(ULONG) Release(); 
  13.     // IMFAsyncCallback方法 
  14.     STDMETHODIMP GetParameters(DWORD*, DWORD*) 
  15.     { return E_NOTIMPL;} 
  16.     STDMETHODIMP Invoke(IMFAsyncResult* pAsyncResult); 
  17.  
  18.     HRESULT Initialize(); 
  19.     HRESULT OpenFile(PCWSTR sURL); 
  20.     HRESULT HandleEvent(UINT_PTR pEvent); 
  21.     PlayerState GetState() const { return m_state; } 
  22.     BOOL    HasVideo() const { return (m_pVideoControl != NULL); } 
  23.  
  24.     HRESULT StartPlay(); 
  25.     HRESULT Play(); 
  26.     HRESULT Pause(); 
  27.     HRESULT Stop(); 
  28.     HRESULT Shutdown(); 
  29.     HRESULT Repaint(); 
  30.     HRESULT ResizeVideo(WORD width, WORD height); 
  31.  
  32.     HRESULT OnTopologyStatus(IMFMediaEvent*); 
  33.     HRESULT OnPresentationEnded(IMFMediaEvent*); 
  34.  
  35. private
  36.     long                    m_nRefCount; 
  37.     PlayerState             m_state; 
  38.     IMFMediaSession*        m_pMediaSession; 
  39.     IMFMediaSource*         m_pMediaSource; 
  40.     IMFVideoDisplayControl* m_pVideoControl; 
  41.     HWND                    m_hwndVideo; 
  42.     HANDLE                  m_hCloseEvent; 
  43. };   virtual ~Core(void); public: static HRESULT CreateInstance(HWND hVideo, Core** ppCore); // IUnknown方法 STDMETHODIMP QueryInterface(REFIID iid, void** ppv); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); // IMFAsyncCallback方法 STDMETHODIMP GetParameters(DWORD*, DWORD*) { return E_NOTIMPL;} STDMETHODIMP Invoke(IMFAsyncResult* pAsyncResult); HRESULT Initialize(); HRESULT OpenFile(PCWSTR sURL); HRESULT HandleEvent(UINT_PTR pEvent); PlayerState GetState() const { return m_state; } BOOL HasVideo() const { return (m_pVideoControl != NULL); } HRESULT StartPlay(); HRESULT Play(); HRESULT Pause(); HRESULT Stop(); HRESULT Shutdown(); HRESULT Repaint(); HRESULT ResizeVideo(WORD width, WORD height); HRESULT OnTopologyStatus(IMFMediaEvent*); HRESULT OnPresentationEnded(IMFMediaEvent*); private: long m_nRefCount; PlayerState m_state; IMFMediaSession* m_pMediaSession; IMFMediaSource* m_pMediaSource; IMFVideoDisplayControl* m_pVideoControl; HWND m_hwndVideo; HANDLE m_hCloseEvent; };

构造函数、接口以及初始化等函数的实现, Event句柄m_hCloseEvent用来设置播放关闭时的标志,m_hwndVideo就是播放用的视频窗口,我设为主对话框的客户窗口,m_pVideoControl是一个IMFVideoDisplayControl接口指针,用来完成播放窗口相关的控制,如调整尺寸和重绘:

  1. Core::Core(HWND hVideo) : 
  2.     m_pMediaSession(NULL), 
  3.     m_pMediaSource(NULL), 
  4.     m_pVideoControl(NULL), 
  5.     m_hwndVideo(hVideo), 
  6.     m_hCloseEvent(NULL), 
  7.     m_state(Closed), 
  8.     m_nRefCount(1) 
  9.  
  10.  
  11. Core::~Core(void
  12.     if(m_pMediaSession) 
  13.     { 
  14.         Shutdown(); 
  15.     } 
  16.  
  17. HRESULT Core::QueryInterface(REFIID riid, void** ppv) 
  18.     staticconst QITAB qit[] =  
  19.     { 
  20.         QITABENT(Core, IMFAsyncCallback), 
  21.         { 0 } 
  22.     }; 
  23.     return QISearch(this, qit, riid, ppv); 
  24.  
  25. ULONG Core::AddRef() 
  26.     return InterlockedIncrement(&m_nRefCount); 
  27.  
  28. ULONG Core::Release() 
  29.     ULONG uCount = InterlockedDecrement(&m_nRefCount); 
  30.     if(uCount == 0) 
  31.         deletethis
  32.     return uCount; 
  33.   
  34.  
  35. HRESULT Core::CreateInstance(HWND hVideo, Core** ppCore) 
  36.     if(hVideo == NULL)  return E_UNEXPECTED; 
  37.     if(ppCore == NULL)  return E_POINTER; 
  38.  
  39.     Core* pCore = new Core(hVideo); 
  40.     if(pCore == NULL)   return E_OUTOFMEMORY; 
  41.  
  42.     HRESULT hr = pCore->Initialize(); 
  43.     if(SUCCEEDED(hr)) 
  44.     { 
  45.         *ppCore = pCore; 
  46.         (*ppCore)->AddRef(); 
  47.     } 
  48.  
  49.     SafeRelease(&pCore); 
  50.     return hr; 
  51.  
  52. HRESULT Core::Initialize() 
  53.     if(m_hCloseEvent) 
  54.         return MF_E_ALREADY_INITIALIZED; 
  55.  
  56.     HRESULT hr = MFStartup(MF_VERSION); 
  57.     if(FAILED(hr))  return hr; 
  58.  
  59.     m_hCloseEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 
  60.     if(m_hCloseEvent == NULL) 
  61.         hr = HRESULT_FROM_WIN32(GetLastError()); 
  62.      
  63.     return hr; 
  64.  

3. 打开文件时的处理

这是一个核心的部分,代码较多,为了简单,我把所有代码都写到一个函数里去了,主要是为了理解方便。 主要完成了以下工作:

  • 创建media session
  • 开始从media session取得事件,开启播放相关事件的流动
  • 根据文件路径创建合适的media source
  • 创建partial topology
  • 为media source的各个选定(selected)流创建source节点和renderer节点,将它们添加到partial topology并相互连接
  • 将partial topology与media session关联,将partial topology解析成完整可用的topology
  1. HRESULT Core::OpenFile(PCWSTR sURL) 
  2.     assert(m_state == Closed || m_state == Stopped); 
  3.  
  4.     // 创建Media Session 
  5.     HRESULT hr = MFCreateMediaSession(NULL, &m_pMediaSession); 
  6.     if(FAILED(hr))  return hr; 
  7.     m_state = Ready;     
  8.     // 开始从Media Session取得事件 
  9.     hr = m_pMediaSession->BeginGetEvent((IMFAsyncCallback*)this, NULL); 
  10.     if(FAILED(hr))  return hr; 
  11.  
  12.  
  13.     // 创建Media Source 
  14.     IMFSourceResolver* pSourceResolver = NULL;  
  15.     IUnknown* pUnknown = NULL; 
  16.     IMFTopology* pTopology = NULL; 
  17.     IMFPresentationDescriptor* pPD = NULL; 
  18.  
  19.     MF_OBJECT_TYPE objType = MF_OBJECT_INVALID; 
  20.     SafeRelease(&m_pMediaSource); 
  21.  
  22.     hr = MFCreateSourceResolver(&pSourceResolver); 
  23.     if(FAILED(hr))  goto over; 
  24.      
  25.     // 为简单,弄成同步方法 
  26.     hr = pSourceResolver->CreateObjectFromURL( 
  27.         sURL,  
  28.         MF_RESOLUTION_MEDIASOURCE, 
  29.         NULL, 
  30.         &objType, 
  31.         &pUnknown); 
  32.     if(FAILED(hr))  goto over; 
  33.  
  34.     hr = pUnknown->QueryInterface(IID_PPV_ARGS(&m_pMediaSource)); 
  35.     if(FAILED(hr))  goto over; 
  36.  
  37.  
  38.     // 创建Topology 
  39.     assert(m_pMediaSession != NULL); 
  40.     assert(m_pMediaSource != NULL); 
  41.      
  42.     DWORD cStreams = 0; 
  43.  
  44.     hr = MFCreateTopology(&pTopology); 
  45.     if(FAILED(hr))  goto over; 
  46.  
  47.     // 创建PresentationDescriptor 
  48.     hr = m_pMediaSource->CreatePresentationDescriptor(&pPD); 
  49.     if(FAILED(hr))  goto over; 
  50.  
  51.     // 获取source中的stream数目 
  52.     hr = pPD->GetStreamDescriptorCount(&cStreams); 
  53.     if(FAILED(hr))  goto over; 
  54.  
  55.     assert(pTopology != NULL); 
  56.     // 为每个stream创建topology节点,并将其加入到topology 
  57.     for(DWORD i = 0; i<cStreams; i++ ) 
  58.     { 
  59.             IMFStreamDescriptor* pSD = NULL; 
  60.         IMFTopologyNode* pSourceNode = NULL; 
  61.         IMFTopologyNode* pOutputNode = NULL; 
  62.         BOOL bSelected = FALSE; 
  63.  
  64.         hr= pPD->GetStreamDescriptorByIndex(i, &bSelected, &pSD); 
  65.         if(FAILED(hr)) goto over2; 
  66.         if(bSelected) 
  67.         { 
  68.             // source node 
  69.             hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &pSourceNode); 
  70.             if(FAILED(hr))  goto over2; 
  71.             hr = pSourceNode->SetUnknown(MF_TOPONODE_SOURCE, m_pMediaSource); 
  72.             if(FAILED(hr))  goto over2; 
  73.             hr = pSourceNode->SetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR, pPD); 
  74.             if(FAILED(hr))  goto over2; 
  75.             hr = pSourceNode->SetUnknown(MF_TOPONODE_STREAM_DESCRIPTOR, pSD); 
  76.             if(FAILED(hr))  goto over2; 
  77.  
  78.             // output node 
  79.             IMFMediaTypeHandler* pHandler = NULL; 
  80.             IMFActivate* pRendererActivate = NULL; 
  81.             GUID guidMajorType = GUID_NULL; 
  82.             DWORD id = 0; 
  83.              
  84.             pSD->GetStreamIdentifier(&id); // 忽略错误 
  85.             hr = pSD->GetMediaTypeHandler(&pHandler); 
  86.             if(FAILED(hr))  goto over3; 
  87.             hr = pHandler->GetMajorType(&guidMajorType); 
  88.             if(FAILED(hr))  goto over3; 
  89.             hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &pOutputNode); 
  90.             if(FAILED(hr))  goto over3; 
  91.             if(MFMediaType_Audio == guidMajorType) 
  92.                 hr = MFCreateAudioRendererActivate(&pRendererActivate); 
  93.             elseif(MFMediaType_Video == guidMajorType) 
  94.                 hr = MFCreateVideoRendererActivate(m_hwndVideo, &pRendererActivate); 
  95.             else 
  96.                 hr = E_FAIL; 
  97.             if(FAILED(hr))  goto over3; 
  98.             hr = pOutputNode->SetObject(pRendererActivate); 
  99.             if(FAILED(hr))  goto over3; 
  100.  
  101.             // 把source节点和输出节点添加到topology,并连接它们 
  102.             hr = pTopology->AddNode(pSourceNode); 
  103.             if(FAILED(hr))  goto over3; 
  104.             hr = pTopology->AddNode(pOutputNode); 
  105.             if(FAILED(hr))  goto over3; 
  106.             hr = pSourceNode->ConnectOutput(0, pOutputNode, 0); 
  107. over3: 
  108.             SafeRelease(&pRendererActivate); 
  109.             SafeRelease(&pHandler); 
  110.             goto over2; 
  111.         } 
  112.  
  113. over2: 
  114.         SafeRelease(&pSD); 
  115.         SafeRelease(&pSourceNode); 
  116.         SafeRelease(&pOutputNode); 
  117.     } 
  118.  
  119.     hr = m_pMediaSession->SetTopology(0, pTopology); 
  120.     if(FAILED(hr))  goto over; 
  121.     m_state = OpenPending; 
  122.  
  123. over: 
  124.     if(FAILED(hr))  m_state = Closed; 
  125.  
  126.     SafeRelease(&pPD); 
  127.     SafeRelease(&pTopology); 
  128.     SafeRelease(&pSourceResolver); 
  129.     SafeRelease(&pUnknown); 
  130.     return hr; 

 主对话框需要提供一个文件/打开菜单,用来打开文件,它的响应如下。由于IMFSourceResolver::CreateObjectFromURL方法只支持LPCWSTR类型的文件路径字符串参数,所以当我们以多字节配置build程序时,需要添加代码,把打开文件对话框取得的多字节文件路径字符串转换成宽字符串。

  1. void CBlackPlayerDlg::OnOpen() 
  2.     HRESULT hr = S_OK; 
  3.  
  4.     TCHAR path[MAX_PATH]; 
  5.     path[0] = _T('\0'); 
  6.  
  7.     OPENFILENAME ofn; 
  8.     ::ZeroMemory(&ofn, sizeof(ofn)); 
  9.     ofn.lStructSize = sizeof(ofn); 
  10.     ofn.hwndOwner = this->GetSafeHwnd(); 
  11.     ofn.lpstrFilter = _T("Media Files\0*.asf;*.avi;*.mp3;"
  12.                           _T("*.mp4;*.wav;*.wma;*.wmv\0All files\0*.*\0"); 
  13.     ofn.lpstrFile = path; 
  14.     ofn.nMaxFile = MAX_PATH; 
  15.     ofn.Flags = OFN_FILEMUSTEXIST; 
  16.     ofn.hInstance = AfxGetInstanceHandle(); 
  17.  
  18.     if(::GetOpenFileName(&ofn)) 
  19.     { 
  20. #ifdef UNICODE 
  21.         hr = g_pCore->OpenFile(ofn.lpstrFile); 
  22. #else 
  23.         size_t cLen = 0, cChars = 0; 
  24.         cLen = _tcslen(ofn.lpstrFile); 
  25.         WCHAR wstr[MAX_PATH*2] = {L'\0'}; 
  26.  
  27.         ::MultiByteToWideChar(CP_ACP, 0, ofn.lpstrFile, -1, (LPWSTR)wstr, MAX_PATH); 
  28.         hr = g_pCore->OpenFile(wstr); 
  29. #endif 
  30.         if(SUCCEEDED(hr)) 
  31.         { 
  32.             UpdateUI(g_pCore->GetState()); 
  33.         } 
  34.         else 
  35.         { 
  36.             AfxMessageBox(_T("无法打开文件!")); 
  37.         } 
  38.     } 

4. 处理media session事件

第3步代码的开头我们已经使用BeginGetEvent方法开始获取Media Session的事件,这是一个异步方法,当下一个事件发生时,media session会调用IMFAsyncCallback::Invoke方法。注意此方法是在worker线程调用的,不是主程序所在线程,所以这方法必须线程安全。

  1. HRESULT Core::Invoke(IMFAsyncResult* pResult) 
  2.     MediaEventType meType = MEUnknown; 
  3.     IMFMediaEvent* pEvent = NULL; 
  4.  
  5.     // 从事件队列中获取事件 
  6.     HRESULT hr = m_pMediaSession->EndGetEvent(pResult, &pEvent); 
  7.     if(FAILED(hr))  goto over; 
  8.     // 取得事件的类型 
  9.     hr = pEvent->GetType(&meType); 
  10.     if(FAILED(hr))  goto over; 
  11.      
  12.     if(meType == MESessionClosed) 
  13.     { 
  14.         ::SetEvent(m_hCloseEvent); 
  15.     } 
  16.     else 
  17.     { 
  18.         hr = m_pMediaSession->BeginGetEvent(this, NULL); 
  19.         if(FAILED(hr))  goto over; 
  20.     } 
  21.  
  22.     if(m_state != Closing) 
  23.     { 
  24.         pEvent->AddRef(); 
  25.         ::PostMessage(m_hwndVideo, WM_APP_PLAYER_EVENT, (WPARAM)pEvent, (LPARAM)0); 
  26.     } 
  27.  
  28. over: 
  29.     SafeRelease(&pEvent); 
  30.     return S_OK; 

Invoke方法中,我们先用EndGetEvent取得事件,接着用PostMessage将此事件发送给主程序窗口,其实主窗口还是把它送回给了Core的HandleEvent方法来进行实际处理。还要再次调用BeginGetEvent方法来异步获取下一事件。所以主对话框类要添加WM_APP_PLAYER_EVENT事件的处理程序:

  1. afx_msg LRESULT CBlackPlayerDlg::OnPlayerEvent(WPARAM wParam, LPARAM lParam) 
  2.     HRESULT hr = S_OK; 
  3.     hr = g_pCore->HandleEvent(wParam); 
  4.     if(FAILED(hr)) 
  5.         AfxMessageBox(_T("事件处理发生错误!")); 
  6.  
  7.     UpdateUI(g_pCore->GetState()); 
  8.     return 0; 
  9. }   

以下是Core类的HandleEvent,还可以根据需要添加更多的事件处理:

  1. HRESULT Core::HandleEvent(UINT_PTR pEventPtr) 
  2.     HRESULT hrStatus = S_OK; 
  3.     HRESULT hr = E_FAIL; 
  4.     IMFMediaEvent* pEvent = NULL; 
  5.  
  6.     IUnknown* pUnk = (IUnknown*)pEventPtr; 
  7.     if(pUnk == NULL)    return E_POINTER; 
  8.     hr = pUnk->QueryInterface(IID_PPV_ARGS(&pEvent)); 
  9.     if(FAILED(hr))  goto over; 
  10.      
  11.     MediaEventType meType = MEUnknown; 
  12.     hr = pEvent->GetType(&meType); 
  13.     if(FAILED(hr))  goto over; 
  14.  
  15.     hr = pEvent->GetStatus(&hrStatus); 
  16.     if(FAILED(hr))  goto over; 
  17.     if(FAILED(hrStatus)) 
  18.     { 
  19.         hr = hrStatus; 
  20.         goto over; 
  21.     } 
  22.  
  23.     switch(meType) 
  24.     { 
  25.     case MESessionTopologyStatus: 
  26.         hr = OnTopologyStatus(pEvent); 
  27.         break
  28.     case MEEndOfPresentation: 
  29.         hr = OnPresentationEnded(pEvent); 
  30.         break
  31.     default
  32.         break
  33.     } 
  34.  
  35. over: 
  36.     SafeRelease(&pUnk); 
  37.     SafeRelease(&pEvent); 
  38.     return hr; 
  39. }  

相关的具体事件的处理方法 :

  1. HRESULT Core::OnTopologyStatus(IMFMediaEvent* pEvent) 
  2.     MF_TOPOSTATUS status; 
  3.     HRESULT hr = pEvent->GetUINT32(MF_EVENT_TOPOLOGY_STATUS, (UINT32*)&status); 
  4.     if(SUCCEEDED(hr) && (status == MF_TOPOSTATUS_READY)) 
  5.     { 
  6.         SafeRelease(&m_pVideoControl); 
  7.         // 如果source没有视频stream,此方法将失败 
  8.         (void)MFGetService(m_pMediaSession, MR_VIDEO_RENDER_SERVICE, 
  9.             IID_PPV_ARGS(&m_pVideoControl)); 
  10.  
  11.         hr = StartPlay(); 
  12.     } 
  13.     return hr; 
  14.  
  15.  
  16.  
  17. HRESULT Core::OnPresentationEnded(IMFMediaEvent* pEvent) 
  18.     m_state = Stopped; 
  19.     return S_OK; 

5. 控制播放

构建好完整的topology、设置好事件处理之后,可以真正地开始播放了,以下是播放的一些相关代码:

  1. // 从当前位置开始播放 
  2. HRESULT Core::StartPlay() 
  3. {    
  4.     PROPVARIANT varStart; 
  5.     PropVariantInit(&varStart); 
  6.     varStart.vt = VT_EMPTY; 
  7.  
  8.     // start是异步方法 
  9.     HRESULT hr = m_pMediaSession->Start(&GUID_NULL, &varStart); 
  10.     if(SUCCEEDED(hr)) 
  11.         m_state = Started; 
  12.     PropVariantClear(&varStart); 
  13.  
  14.     return hr; 
  15.  
  16. HRESULT Core::Play() 
  17.     if(m_state != Paused && m_state != Stopped) 
  18.         return INET_E_INVALID_REQUEST; 
  19.     if(m_pMediaSession == NULL || m_pMediaSource == NULL) 
  20.         return E_UNEXPECTED; 
  21.     return StartPlay(); 
  22.  
  23. HRESULT Core::Pause() 
  24.     if(m_state != Started) 
  25.         return INET_E_INVALID_REQUEST; 
  26.     if(m_pMediaSession == NULL || m_pMediaSource == NULL) 
  27.         return E_UNEXPECTED; 
  28.  
  29.     HRESULT hr = m_pMediaSession->Pause(); 
  30.     if(SUCCEEDED(hr)) 
  31.         m_state = Paused; 
  32.     return hr; 
  33.  
  34. HRESULT Core::Stop() 
  35.     if(m_state != Started && m_state != Paused) 
  36.         return INET_E_INVALID_REQUEST; 
  37.     if(m_pMediaSession == NULL) 
  38.         return E_UNEXPECTED; 
  39.  
  40.     HRESULT hr = m_pMediaSession->Stop(); 
  41.     if(SUCCEEDED(hr)) 
  42.         m_state = Stopped; 
  43.     return hr; 

视频render(EVR)在我们提供的视频窗口绘制视频图象,这发生在一个工作线程,一般不需要管它。但如果播放暂停或停止时,当视频窗口接收到WM_PAINT消息时,我们必须通知EVR,方法是调用IMFVideoDisplayControl::RepaintVideo方法:

  1. HRESULT Core::Repaint() 
  2.     if(m_pVideoControl) 
  3.         return m_pVideoControl->RepaintVideo(); 
  4.     else 
  5.         return S_OK; 

同时,我们还需要修改主对话框的OnPaint事件处理函数:

  1. void CBlackPlayerDlg::OnPaint() 
  2.     if (IsIconic()) 
  3.     { 
  4.         // 略。。。。 
  5.     } 
  6.     else 
  7.     { 
  8.         if(g_pCore && g_pCore->HasVideo()) 
  9.             g_pCore->Repaint(); 
  10.  
  11.         CDialogEx::OnPaint(); 
  12.     } 

我们还需要让用户可以调整视频窗口大小,这通过IMFVideoDisplayControl::SetVideoPostition方法来完成:

  1. HRESULT Core::ResizeVideo(WORD width, WORD height) 
  2.     if(m_pVideoControl) 
  3.     { 
  4.         RECT rc = {0, 0, width, height}; 
  5.         return m_pVideoControl->SetVideoPosition(NULL, &rc); 
  6.     } 
  7.     else 
  8.         return S_OK; 

类似与重绘,我们也要相应修改主对话框类,为其添加WM_SIZE的消息处理程序:

  1. void CBlackPlayerDlg::OnSize(UINT nType, int cx, int cy) 
  2.     CDialogEx::OnSize(nType, cx, cy); 
  3.     if(g_pCore != NULL && g_pCore->HasVideo()) 
  4.         g_pCore->ResizeVideo(cx, cy); 

 6. 结束后的清理

 

播放完成后我们需要做一些清理工作,比如析构函数中的ShutDown方法:

  1. HRESULT Core::Shutdown() 
  2.     HRESULT hr = S_OK; 
  3.      
  4.     SafeRelease(&m_pVideoControl); 
  5.  
  6.     if(m_pMediaSession) 
  7.     { 
  8.         DWORD dwRet = 0; 
  9.         m_state = Closing; 
  10.         hr = m_pMediaSession->Close(); 
  11.         if(FAILED(hr))  goto over; 
  12.          
  13.         dwRet = WaitForSingleObject(m_hCloseEvent, 3000); 
  14.     } 
  15.  
  16.     // 以下shutdown都是同步方法,无事件 
  17.     if(m_pMediaSource) 
  18.         m_pMediaSource->Shutdown(); 
  19.     if(m_pMediaSession) 
  20.         m_pMediaSession->Shutdown(); 
  21.  
  22.     SafeRelease(&m_pMediaSource); 
  23.     SafeRelease(&m_pMediaSession); 
  24.     m_state = Closed; 
  25.  
  26.     MFShutdown(); 
  27.     if(m_hCloseEvent) 
  28.     { 
  29.         ::CloseHandle(m_hCloseEvent); 
  30.         m_hCloseEvent = NULL; 
  31.     } 
  32.  
  33. over: 
  34.     return hr; 
  35. }  

我在对话框类的析构函数里添加:

  1. CBlackPlayerDlg::~CBlackPlayerDlg() 
  2.     if(g_pCore != NULL) 
  3.     { 
  4.         g_pCore->Shutdown(); 
  5.         g_pCore->Release(); 
  6.     } 
  7. }   

7. 杂项

如果要用键盘的空格键控制视频的暂停/重新播放,主对话框类需要重载PreTranslateMessage函数:

 

  1. // 必须直接重载此函数,直接处理WM_CHAR或WM_KEYUP消息不行 
  2. BOOL CBlackPlayerDlg::PreTranslateMessage(MSG* pMsg) 
  3.     // TODO: 在此添加专用代码和/或调用基类 
  4.     if(g_pCore != NULL && pMsg->message == WM_KEYUP) 
  5.     { 
  6.         if(pMsg->wParam == VK_SPACE) 
  7.         { 
  8.             if(g_pCore->GetState() == Started) 
  9.                 g_pCore->Pause(); 
  10.             elseif(g_pCore->GetState() == Paused) 
  11.                 g_pCore->Play(); 
  12.         } 
  13.         return TRUE; 
  14.     } 
  15.  
  16.     return CDialogEx::PreTranslateMessage(pMsg); 
  17.  

如果要添加个控制菜单,里面有暂停、停止,它们的响应如下:

  1. void CBlackPlayerDlg::OnPause() 
  2.     if(g_pCore != NULL) 
  3.     { 
  4.         if(g_pCore->GetState() == Started) 
  5.                 g_pCore->Pause(); 
  6.     } 
  7.  
  8.  
  9. void CBlackPlayerDlg::OnStop() 
  10.     if(g_pCore != NULL) 
  11.     { 
  12.         if(g_pCore->GetState() == Started || g_pCore->GetState() == Paused) 
  13.             g_pCore->Stop(); 
  14.     } 

 

posted on 2013-09-16 23:58  神一样的魔鬼  阅读(2254)  评论(0编辑  收藏  举报

导航