Introduction
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 : CVMR9Graph
.
class CVMR9Graph
{
public:
CVMR9Graph();
CVMR9Graph(HWND MediaWindow,
int NumberOfStream = 4);
virtual ~CVMR9Graph();
public:
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 PlayGraph();
BOOL StopGraph();
BOOL ResetGraph();
IMediaEvent* GetPtrMediaEvent();
IMediaControl* GetPtrMediaControl();
IMediaSeeking* GetPtrMediaSeeking();
IBasicAudio* GetPtrBasicAudio();
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);
BOOL Repaint();
BOOL Resize();
LPCTSTR GetLastError();
protected:
};
For convenience, header and implementation files contains the DirectShow includes, Direct3D includes, and pragma
directives for lib
.
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,
- Call
CVMR9Graph::SetMediaWindow(hMyVideoPlaybackHandle)
to set the video playback window,
- Call
CVMR9Graph::SetMediaFile(0, pszPathToMyFile)
to set the video file to render,
- Call
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 CVMR9Graph::Resize()
,
- Create a handler for
WM_PAINT
message and make a call to CVMR9Graph::Repaint()
You can notice that video playback preserves aspect ratio by default. You can change this by a call to CVMR9Graph::PreserveAspectRatio(FALSE)
.
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:
myGraph.SetMediaFile(0, "C:\\Video1.avi");
myGraph.SetMediaFile(1, "C:\\Video2.mpg");
myGraph.SetAlphaLayer(0, 50);
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 TheInterface::Release()
.
Known issue
When the graph is running, a call to CVMR9Graph::Stop()
or 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
The 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.