使用VMR9+D3D解码和播放视频

喜欢ヅ旅行 2022-07-15 02:37 357阅读 0赞

完整源码

VMR-9是DirectShow的渲染filter,可以修改它的allocator-presenter实现获取解码数据并自己渲染

使用DirectShow前需要安装解码器,否则只能解码avi、wmv等少数格式,推荐LVAFilters,这是基于FFMPEG的解码器

参考微软的文档:Supplying a Custom Allocator-Presenter for VMR-9和Direct3D 环境中的 DirectShow 电影

实现步骤:

  1. 自己写个类实现IVMRSurfaceAllocator9和IVMRImagePresenter9
  2. 调用IVMRFilterConfig9::SetRenderingMode设置VMR9Mode_Renderless
  3. 调用IVMRSurfaceAllocatorNotify9::AdviseSurfaceAllocator设置自己实现的allocator-presenter
  4. 调用IVMRSurfaceAllocatorNotify9::SetD3DDevice设置D3D设备
  5. 在IVMRSurfaceAllocator9::InitializeDevice里实现分配D3D表面,可以使用IVMRSurfaceAllocatorNotify9::AllocateSurfaceHelper
  6. 在IVMRImagePresenter9::PresentImage里实现渲染表面

Decoder.h:

  1. #pragma once
  2. #include <dshow.h>
  3. #include <d3d9.h>
  4. #include <vmr9.h>
  5. #include <vector>
  6. #include <functional>
  7. class CDecoder : private IVMRSurfaceAllocator9, private IVMRImagePresenter9
  8. {
  9. public:
  10. CDecoder(LPCWSTR fileName, HWND d3dHwnd, IDirect3DDevice9* device);
  11. virtual ~CDecoder() = default;
  12. virtual void Run();
  13. virtual void Pause();
  14. virtual void Stop();
  15. virtual void GetVideoSize(SIZE& size);
  16. // 设置需要呈现时的回调函数
  17. virtual void SetOnPresent(std::function<void(VMR9PresentationInfo*)>&& onPresent);
  18. protected:
  19. CComPtr<IGraphBuilder> m_graph;
  20. CComPtr<IMediaControl> m_control;
  21. CComPtr<IBaseFilter> m_source;
  22. CComPtr<IBaseFilter> m_vmr9;
  23. SIZE m_videoSize;
  24. // 需要呈现时被调用
  25. std::function<void(VMR9PresentationInfo*)> m_onPresent;
  26. // IUnknown
  27. private:
  28. virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR *__RPC_FAR *ppvObject);
  29. virtual ULONG STDMETHODCALLTYPE AddRef(void);
  30. virtual ULONG STDMETHODCALLTYPE Release(void);
  31. // IVMRSurfaceAllocator9
  32. private:
  33. HWND m_d3dHwnd;
  34. IDirect3DDevice9* m_device;
  35. CComPtr<IVMRSurfaceAllocatorNotify9> m_vmrNotify;
  36. std::vector<IDirect3DSurface9*> m_surfaces;
  37. virtual HRESULT STDMETHODCALLTYPE InitializeDevice(DWORD_PTR dwUserID, VMR9AllocationInfo *lpAllocInfo, DWORD *lpNumBuffers);
  38. virtual HRESULT STDMETHODCALLTYPE TerminateDevice(DWORD_PTR dwID);
  39. virtual HRESULT STDMETHODCALLTYPE GetSurface(DWORD_PTR dwUserID, DWORD SurfaceIndex, DWORD SurfaceFlags, IDirect3DSurface9 **lplpSurface);
  40. virtual HRESULT STDMETHODCALLTYPE AdviseNotify(IVMRSurfaceAllocatorNotify9 *lpIVMRSurfAllocNotify);
  41. // IVMRImagePresenter9
  42. private:
  43. virtual HRESULT STDMETHODCALLTYPE StartPresenting(DWORD_PTR dwUserID);
  44. virtual HRESULT STDMETHODCALLTYPE StopPresenting(DWORD_PTR dwUserID);
  45. virtual HRESULT STDMETHODCALLTYPE PresentImage(DWORD_PTR dwUserID, VMR9PresentationInfo *lpPresInfo);
  46. };

Decoder.cpp:

  1. #include "stdafx.h"
  2. #include "Decoder.h"
  3. CDecoder::CDecoder(LPCWSTR fileName, HWND d3dHwnd, IDirect3DDevice9* device) :
  4. m_d3dHwnd(d3dHwnd),
  5. m_device(device)
  6. {
  7. HRESULT hr;
  8. hr = m_vmr9.CoCreateInstance(CLSID_VideoMixingRenderer9, NULL, CLSCTX_INPROC_SERVER);
  9. hr = m_graph.CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER);
  10. hr = m_graph.QueryInterface(&m_control);
  11. hr = m_graph->AddFilter(m_vmr9, L"VMR9");
  12. hr = m_graph->AddSourceFilter(fileName, L"Source", &m_source);
  13. // 设置呈现器
  14. CComPtr<IVMRFilterConfig9> vmrConfig;
  15. hr = m_vmr9.QueryInterface(&vmrConfig);
  16. hr = vmrConfig->SetRenderingMode(VMR9Mode_Renderless);
  17. hr = m_vmr9.QueryInterface(&m_vmrNotify);
  18. hr = m_vmrNotify->AdviseSurfaceAllocator(0, this);
  19. hr = AdviseNotify(m_vmrNotify);
  20. // 连接源和渲染器
  21. CComPtr<IEnumPins> enumPins;
  22. CComPtr<IPin> sourcePin, renderPin;
  23. PIN_DIRECTION pinDir;
  24. m_source->EnumPins(&enumPins);
  25. while (enumPins->Next(1, &sourcePin, NULL) == S_OK)
  26. {
  27. sourcePin->QueryDirection(&pinDir);
  28. if (pinDir == PINDIR_OUTPUT)
  29. break;
  30. sourcePin.Release();
  31. }
  32. enumPins.Release();
  33. m_vmr9->EnumPins(&enumPins);
  34. while (enumPins->Next(1, &renderPin, NULL) == S_OK)
  35. {
  36. renderPin->QueryDirection(&pinDir);
  37. if (pinDir == PINDIR_INPUT)
  38. break;
  39. renderPin.Release();
  40. }
  41. enumPins.Release();
  42. hr = m_graph->Connect(sourcePin, renderPin);
  43. }
  44. void CDecoder::Run()
  45. {
  46. TRACE(_T("Run\n"));
  47. HRESULT hr = m_control->Run();
  48. }
  49. void CDecoder::Pause()
  50. {
  51. TRACE(_T("Pause\n"));
  52. HRESULT hr = m_control->Pause();
  53. }
  54. void CDecoder::Stop()
  55. {
  56. TRACE(_T("Stop\n"));
  57. HRESULT hr = m_control->Stop();
  58. }
  59. void CDecoder::GetVideoSize(SIZE& size)
  60. {
  61. size = m_videoSize;
  62. }
  63. void CDecoder::SetOnPresent(std::function<void(VMR9PresentationInfo*)>&& onPresent)
  64. {
  65. m_onPresent = std::move(onPresent);
  66. }
  67. // IUnknown
  68. HRESULT STDMETHODCALLTYPE CDecoder::QueryInterface(REFIID riid, _COM_Outptr_ void __RPC_FAR *__RPC_FAR *ppvObject)
  69. {
  70. if (riid == IID_IUnknown)
  71. {
  72. *ppvObject = (IUnknown*)(IVMRSurfaceAllocator9*)this;
  73. return S_OK;
  74. }
  75. if (riid == IID_IVMRSurfaceAllocator9)
  76. {
  77. *ppvObject = (IVMRSurfaceAllocator9*)this;
  78. return S_OK;
  79. }
  80. if (riid == IID_IVMRImagePresenter9)
  81. {
  82. *ppvObject = (IVMRImagePresenter9*)this;
  83. return S_OK;
  84. }
  85. return E_NOINTERFACE;
  86. }
  87. ULONG STDMETHODCALLTYPE CDecoder::AddRef(void)
  88. {
  89. return 0;
  90. }
  91. ULONG STDMETHODCALLTYPE CDecoder::Release(void)
  92. {
  93. return 0; // 没有严格实现COM组件,防止多次析构
  94. }
  95. // IVMRSurfaceAllocator9
  96. HRESULT STDMETHODCALLTYPE CDecoder::AdviseNotify(IVMRSurfaceAllocatorNotify9 *lpIVMRSurfAllocNotify)
  97. {
  98. TRACE(_T("AdviseNotify\n"));
  99. return lpIVMRSurfAllocNotify->SetD3DDevice(m_device, MonitorFromWindow(m_d3dHwnd, MONITOR_DEFAULTTOPRIMARY));
  100. }
  101. HRESULT STDMETHODCALLTYPE CDecoder::InitializeDevice(DWORD_PTR dwUserID, VMR9AllocationInfo *lpAllocInfo, DWORD *lpNumBuffers)
  102. {
  103. TRACE(_T("InitializeDevice\n"));
  104. m_videoSize = lpAllocInfo->szNativeSize;
  105. m_surfaces.resize(*lpNumBuffers);
  106. lpAllocInfo->dwFlags |= VMR9AllocFlag_OffscreenSurface; // 直接创建纹理表面很可能失败,所以创建离屏表面再复制到纹理表面
  107. HRESULT hr = m_vmrNotify->AllocateSurfaceHelper(lpAllocInfo, lpNumBuffers, &m_surfaces[0]);
  108. if (FAILED(hr))
  109. m_surfaces.clear();
  110. return hr;
  111. }
  112. HRESULT STDMETHODCALLTYPE CDecoder::TerminateDevice(DWORD_PTR dwID)
  113. {
  114. TRACE(_T("TerminateDevice\n"));
  115. if (!m_surfaces.empty())
  116. {
  117. for (auto& i : m_surfaces)
  118. i->Release();
  119. m_surfaces.clear();
  120. }
  121. return S_OK;
  122. }
  123. HRESULT STDMETHODCALLTYPE CDecoder::GetSurface(DWORD_PTR dwUserID, DWORD SurfaceIndex, DWORD SurfaceFlags, IDirect3DSurface9 **lplpSurface)
  124. {
  125. TRACE(_T("GetSurface(%u)\n"), SurfaceIndex);
  126. if (SurfaceIndex < 0 || SurfaceIndex >= m_surfaces.size())
  127. {
  128. *lplpSurface = NULL;
  129. return E_FAIL;
  130. }
  131. *lplpSurface = m_surfaces[SurfaceIndex];
  132. (*lplpSurface)->AddRef();
  133. return S_OK;
  134. }
  135. // IVMRImagePresenter9
  136. HRESULT STDMETHODCALLTYPE CDecoder::StartPresenting(DWORD_PTR dwUserID)
  137. {
  138. TRACE(_T("StartPresenting\n"));
  139. return S_OK;
  140. }
  141. HRESULT STDMETHODCALLTYPE CDecoder::StopPresenting(DWORD_PTR dwUserID)
  142. {
  143. TRACE(_T("StopPresenting\n"));
  144. return S_OK;
  145. }
  146. HRESULT STDMETHODCALLTYPE CDecoder::PresentImage(DWORD_PTR dwUserID, VMR9PresentationInfo *lpPresInfo)
  147. {
  148. //TRACE(_T("PresentImage(%I64d)\n"), lpPresInfo->rtStart);
  149. if (!m_onPresent._Empty())
  150. m_onPresent(lpPresInfo);
  151. return S_OK;
  152. }

使用方法:

  1. g_decoder = new CDecoder(L"E:\\pump it.avi", g_hWnd, g_device); // 指定播放的文件名
  2. g_decoder->SetOnPresent(std::function<void(VMR9PresentationInfo*)>(OnPresent));
  3. // ......
  4. g_decoder->Run();
  5. // ......
  6. void OnPresent(VMR9PresentationInfo* info)
  7. {
  8. // 复制到纹理表面,自动转换颜色格式
  9. g_device->StretchRect(info->lpSurf, NULL, g_surface, NULL, D3DTEXF_NONE);
  10. }

另外创建D3D设备时需要指定D3DCREATE_MULTITHREADED,因为OnPresent是在另一个线程调用的

发表评论

表情:
评论列表 (有 0 条评论,357人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Unity3D_视频

    Unity支持的播放视频格式有.mov、.mpg、.mpeg、.mp4、.avi和.asf。只需将对应的视频文件拖拽入Project视图即可,它会自动生成对应的MovieTex