This article demonstrates a simple wrapper class for playing video files with the new DirectShow Video Mixing Renderer 9.
In DirectX9, multimedia applications can use a new video renderer to display decoded frames, but this renderer is not the default renderer for compatibility issue. On Windows XP, the default renderer is the VMR7, but on older Windows version it's the Video Renderer. The main difference is performance and overlay mixing capabilities: as older renderer use different versions of DirectDraw API (even older API for the Video Renderer), the VMR9 is based on DirectX Graphics, so it uses the Direct3D capability of your 3D video card. The result is an improved performance on recent 3D cards, better support of overlay mixing, compatibility with all Windows versions that support DirectX9, and some new capability such as de-interlacing and ProcAmp support (contrast, saturation, etc.).
So the new VMR9 looks great but it's not the default renderer, regardless of Windows version... We've to build it manually, and this is why I wrote this class.
What you need...
To run the demo, DirectX9 runtime has to be installed on your system, which must have a Direct3D compatible display adapter. To build, DirectX9 SDK must be installed on your system. The source code was created under Visual C++ 6 SP5.
All the tests I've made is on my WinXP box, running an ATI Radeon M7 (kind of Radeon 7500). As I can't check real compatibility with many other Windows releases and video cards, please try with your system and put a word in the forum.
Using the code
A first look
All of the DirectShow graph management and VMR routines are included in one class :
int NumberOfStream = 4);
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);
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);
BOOL SetBitmap(const char* pszBitmapFileName,
int nAlpha, COLORREF cTransColor, RECT bitmapRect);
BOOL SetBitmapParams(int nAlpha,
COLORREF cTransColor, RECT bitmapRect);
For convenience, header and implementation files contains the DirectShow includes, Direct3D includes, and
pragma directives for
Step 1 : Building a simple player
Building a very simple video player is quite easy:
- Include VMR9Graph.h and VMR9Graph.cpp in your project,
- Add an instance of
CVMR9Graph in your application,
- Provide a window for video playback,
CVMR9Graph::SetMediaWindow(hMyVideoPlaybackHandle) to set the video playback window,
CVMR9Graph::SetMediaFile(0, pszPathToMyFile) to set the video file to render,
CVMR9Graph::RunGraph() to play video.
At this point video playback works but the video wasn't resized with your window...
Step 2 : Forwarding events
Your application have to tell to the graph when the video has to repaint or size:
- Create a handler for
WM_SIZE message and make a call to
- Create a handler for
WM_PAINT message and make a call to
You can notice that video playback preserves aspect ratio by default. You can change this by a call to
Ok, that looks much better... time to play with video mixing.
Step 3: Mixing video
Multiple file playback was handled by layers. Each layer plays a video and supports several properties such as ordering, alpha blending, size and position. The video produced by multiple layers is called a composition, and takes the size of the biggest media.
Each layer you insert is identified by it's layer index;
CVMR9Graph lets you play with 10 layers, with it's default value to 4 layers (VMR9 default).
The following example loads 2 video files and sets an alpha value of 50% to the first:
Alpha value can be set in real time, as show in the demo app.
Note 1: I have not been able to mix two DivX files, but only one DivX and other codecs such as MPEG... Don't know why... perhaps a hardware lack, since DirectShow samples seems to have the same troubles.
Note 2: The
CVMR9Graph adds only one sound renderer in the graph, so only the first video stream has sound. You can add another sound renderer with a call to
CVMR9Graph::AddFilter(_T"Another Sound Renderer", CLSID_DSoundRender).
Looks cool on a recent computers... Can we add more?
Step 4 : Setting an overlay bitmap
Overlay bitmap in
CVMR9Graph is loaded in a Direct3D surface. The bitmap can be in GIF, JPEG, PNG, BMP, DIB, TGA, or DDS format.
To set an overlay bitmap, call
CVMR9Graph::SetBitmap(), with the following parameters :
- a bitmap file path,
- an alpha value (overlay bitmap is always topmost in composition),
- a color key for bitmap transparency,
- and the bitmap size and position (keep in mind that video/bitmap size is relative to composition size).
Overlay bitmap is a cool feature that can be used to:
- display a small overlay indicator,
- create a mask for video display (perhaps someone will try it with a window region. This can be fun).
Last step: Go further away
To keep the class simple, playback control is minimal, but you can do more by getting some DirectShow COM interfaces:
IMediaEvent: gives the state and event of the graph.
CVMR9Graph automatically sends a
WM_MEDIA_NOTIF message to your video playback window; when this occurs, call
IMediaEvent::GetEvent() to get the event type.
IMediaControl: provides control over the playback, such as pause.
IMediaSeeking: provides control over playback rate and position.
IBasicAudio: provides control over the sound renderer, such as volume and balance.
Note: After the use of an interface, you have to release it by a call to
When the graph is running, a call to
CVMR9Graph::SetMediaFile() can be done, but it seems that in some cases composition can't run correctly after, particularly with bitmap media files...
A call to
CVMR9Graph::ResetGraph() cleans up the graph and constructs a new fresh instance.
When resizing video window in demo app, there is some flickering... That's because it's a MFC window with the standard
OnEraseBackgnd() implementation. Microsoft guidelines clearly indicate to bypass standard background paint.
// TODO : some improvements
IVMRMonitorConfig9 interface is retrieved but not used. Multi monitor can be great.
There's no support for de-interlacing or ProcAmp control (ProcAmp don't work on my current ATI display drivers... Grrr)
There's no support for dynamic overlay bitmap... As a Direct3D surface can be locked and modified by a device context handle or even directly, this can be quite simple...don't know why I din't code it... probably a small lack of sleep.