用于使用 VMR9 播放视频的包装类
使用 DirectX9 Video Mixing Renderer 播放和混合视频文件的类。
引言
本文演示了一个简单的封装类,用于使用新的 DirectShow Video Mixing Renderer 9 播放视频文件。
在 DirectX9 中,多媒体应用程序可以使用新的视频渲染器来显示解码后的帧,但出于兼容性考虑,该渲染器并非默认渲染器。在 Windows XP 上,默认渲染器是 VMR7,而在较旧的 Windows 版本中,则是 Video Renderer。主要区别在于性能和覆盖混合功能:由于旧渲染器使用不同版本的 DirectDraw API(Video Renderer 使用的 API 更旧),而 VMR9 基于 DirectX Graphics,因此它利用了 3D 显卡的 Direct3D 功能。结果是,在较新的 3D 显卡上性能得到提升,覆盖混合支持更好,兼容所有支持 DirectX9 的 Windows 版本,并支持去隔行和 ProcAmp(对比度、饱和度等)等新功能。
因此,新的 VMR9 看起来很棒,但它不是默认渲染器,无论 Windows 版本如何……我们必须手动构建它,这就是我编写这个类的原因。
你需要什么...
要运行演示,你的系统必须安装 DirectX9 运行时,并且需要有 Direct3D 兼容的显示适配器。要进行编译,你的系统必须安装 DirectX9 SDK。源代码是在 Visual C++ 6 SP5 下创建的。
我进行的所有测试都在我的 WinXP 机器上完成,运行的是 ATI Radeon M7(类似于 Radeon 7500)。由于我无法检查与许多其他 Windows 版本和显卡之间的实际兼容性,请在你的系统上进行测试,并在论坛上发表评论。
使用代码
初窥
所有 DirectShow 图管理和 VMR 例程都包含在一个类中:CVMR9Graph
。
class CVMR9Graph { // Constructor / destructor public: CVMR9Graph(); CVMR9Graph(HWND MediaWindow, int NumberOfStream = 4); virtual ~CVMR9Graph(); // Methods public: // Graph configuration void SetNumberOfLayer(int nNumberOfLayer); BOOL SetMediaWindow(HWND MediaWindow); BOOL SetMediaFile(const char* pszFileName, int nLayer = 0); BOOL PreserveAspectRatio(BOOL bPreserve = TRUE); IBaseFilter* AddFilter(const char* pszName, const GUID& clsid); // Graph control BOOL PlayGraph(); BOOL StopGraph(); BOOL ResetGraph(); IMediaEvent* GetPtrMediaEvent(); IMediaControl* GetPtrMediaControl(); IMediaSeeking* GetPtrMediaSeeking(); IBasicAudio* GetPtrBasicAudio(); // Layer control BOOL GetVideoRect(LPRECT pRect); int GetAlphaLayer(int nLayer); BOOL SetAlphaLayer(int nLayer, int nAlpha); DWORD GetLayerZOrder(int nLayer); BOOL SetLayerZOrder(int nLayer, DWORD dwZOrder); BOOL SetLayerRect(int nLayer, RECT layerRect); // Bitmap control BOOL SetBitmap(const char* pszBitmapFileName, int nAlpha, COLORREF cTransColor, RECT bitmapRect); BOOL SetBitmapParams(int nAlpha, COLORREF cTransColor, RECT bitmapRect); // Reflected from window BOOL Repaint(); BOOL Resize(); // helper LPCTSTR GetLastError(); protected: // [...] };
为了方便起见,头文件和实现文件包含 DirectShow 包含文件、Direct3D 包含文件以及用于 lib
的 pragma
指令。
第一步:构建一个简单的播放器
构建一个非常简单的视频播放器非常容易
- 将 VMR9Graph.h 和 VMR9Graph.cpp 包含到你的项目中,
- 在你的应用程序中添加
CVMR9Graph
的一个实例, - 提供一个用于播放视频的窗口,
- 调用
CVMR9Graph::SetMediaWindow(hMyVideoPlaybackHandle)
来设置视频播放窗口, - 调用
CVMR9Graph::SetMediaFile(0, pszPathToMyFile)
来设置要渲染的视频文件, - 调用
CVMR9Graph::RunGraph()
来播放视频。
此时视频播放可以工作,但视频没有随你的窗口缩放...
第二步:转发事件
你的应用程序必须告诉图控件何时需要重绘或调整视频大小
- 创建一个
WM_SIZE
消息的处理器,并调用CVMR9Graph::Resize()
, - 创建一个
WM_PAINT
消息的处理器,并调用CVMR9Graph::Repaint()
你可以注意到视频播放默认保留了宽高比。你可以通过调用 CVMR9Graph::PreserveAspectRatio(FALSE)
来改变这一点。
好了,这样看起来好多了……该玩玩视频混合了。
第三步:混合视频
多个文件播放通过图层来处理。每个图层播放一个视频,并支持多种属性,如顺序、Alpha 混合、大小和位置。多个图层产生的视频称为合成,其大小与最大的媒体相同。
你插入的每个图层都由其图层索引标识;CVMR9Graph
允许你使用 10 个图层,默认值为 4 个图层(VMR9 默认)。
以下示例加载了 2 个视频文件,并将第一个文件的 Alpha 值设置为 50%
// load media files myGraph.SetMediaFile(0, "C:\\Video1.avi"); myGraph.SetMediaFile(1, "C:\\Video2.mpg"); // set alpha value to video1 myGraph.SetAlphaLayer(0, 50);
Alpha 值可以实时设置,正如演示应用程序所示。
注意 1: 我未能混合两个 DivX 文件,只能混合一个 DivX 和其他编解码器,如 MPEG……我不知道原因……可能是硬件不足,因为 DirectShow 示例似乎也有同样的麻烦。
注意 2: CVMR9Graph
只在图中添加了一个声音渲染器,因此只有第一个视频流有声音。你可以通过调用 CVMR9Graph::AddFilter(_T"Another Sound Renderer", CLSID_DSoundRender)
来添加另一个声音渲染器。
在较新的计算机上看起来很酷……我们还能添加更多吗?
第四步:设置覆盖位图
CVMR9Graph
中的覆盖位图加载到 Direct3D surface 中。位图可以是 GIF、JPEG、PNG、BMP、DIB、TGA 或 DDS 格式。
要设置覆盖位图,请调用 CVMR9Graph::SetBitmap()
,并附带以下参数:
- 一个位图文件路径,
- 一个 Alpha 值(覆盖位图始终位于合成的最顶层),
- 用于位图透明度的颜色键,
- 以及位图的大小和位置(请记住,视频/位图大小是相对于合成大小的)。
覆盖位图是一项很棒的功能,可用于
- 显示一个小的覆盖指示器,
- 创建视频显示的遮罩(也许有人会尝试用窗口区域来使用它。这可能很有趣)。
最后一步:更进一步
为了保持类的简单性,播放控制是最小化的,但你可以通过获取一些 DirectShow COM 接口来做更多事情
IMediaEvent
:提供图的状态和事件。CVMR9Graph
自动向你的视频播放窗口发送一个WM_MEDIA_NOTIF
消息;当这种情况发生时,调用IMediaEvent::GetEvent()
来获取事件类型。IMediaControl
:提供对播放的控制,例如暂停。IMediaSeeking
:提供对播放速率和位置的控制。IBasicAudio
:提供对声音渲染器的控制,例如音量和平衡。
注意: 使用接口后,你必须通过调用 TheInterface::Release()
来释放它。
已知问题
当图正在运行时,可以调用 CVMR9Graph::Stop()
或 CVMR9Graph::SetMediaFile()
,但在某些情况下,之后合成可能无法正确运行,特别是对于位图媒体文件...
调用 CVMR9Graph::ResetGraph()
会清理图并构建一个新的实例。
在演示应用程序中调整视频窗口大小时,有一些闪烁...这是因为这是一个 MFC 窗口,使用了标准的 OnEraseBackgnd()
实现。微软的指南明确指示要绕过标准的背景绘制。
// TODO:一些改进
IVMRMonitorConfig9
接口已检索但未使用。多显示器可以很棒。
不支持去隔行或 ProcAmp 控制(ProAmp 在我当前的 ATI 显示驱动程序上不起作用……哼!)
不支持动态覆盖位图……由于 Direct3D surface 可以通过设备上下文句柄或直接锁定和修改,这可能相当简单……我不知道为什么我没有写它……可能是因为缺少睡眠。