Introduction
This article shows the steps involved in creating and configuring DirectShow�s Video Mixing Renderer Filter 9 (VMR9). The two video streams, one on top of the other, are rendered on a single surface. This surface, in our case, is a PictureBox
control. Each stream's alpha value, position and height/width can be adjusted at runtime.
How VMR9 is different
The following diagrams show the difference between rendering two videos with VMR9 and without VMR9.
Without VMR9
We notice that simply rendering two videos will result in two separate Video Renderers, which means that the videos are being played on two separate surfaces.
With VMR9
In this case, the VMR9 filter directs both video streams into its own input pins. This means there is only one renderer, and thus a single rendering surface for both video streams.
The Working
To enhance reusability and readability factors, the functionality of the VMR9 filter has been encapsulated inside a class named myVMR9
.
The myVMR9 class
This class has the following private data members:
VMR9NormalizedRect *r;
IVMRWindowlessControl9 *pWC;
IVMRMixerControl9 *pMix;
IGraphBuilder *pGB;
IBaseFilter *pVmr;
IVMRFilterConfig9 *pConfig;
IMediaControl *pMC;
IMediaSeeking *pMS;
The constructor
The constructor receives a PictureBox
's coordinates of type System::Drawing::Rectangle
, along with its handler of type HWND
. These two attributes are used by VMR9 for rendering purposes.
public: myVMR9(System::Drawing::Rectangle rect, HWND hwnd)
{
r = new VMR9NormalizedRect;
r->left = 0;
r->top = 0;
r->right = 1;
r->bottom = 1;
pWC = NULL;
pMix = NULL;
pGB = NULL;
pVmr = NULL;
pConfig = NULL;
pMC = NULL;
pMS = NULL;
CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **)&pGB);
CoCreateInstance(CLSID_VideoMixingRenderer9, NULL, CLSCTX_INPROC,
IID_IBaseFilter, (void**)&pVmr);
pGB->AddFilter(pVmr, L"Video");
pVmr->QueryInterface(IID_IVMRFilterConfig9, (void**)&pConfig);
pConfig->SetRenderingMode(VMR9Mode_Windowless);
pVmr->QueryInterface(IID_IVMRWindowlessControl9, (void**)&pWC);
RECT rcDest = {0};
rcDest.bottom = rect.Bottom;
rcDest.left = rect.Left;
rcDest.right = rect.Right;
rcDest.top = rect.Top;
pWC->SetVideoPosition(NULL, &rcDest);
pWC->SetVideoClippingWindow(hwnd);
pVmr->QueryInterface(IID_IVMRMixerControl9, (void**)&pMix);
pGB->QueryInterface(IID_IMediaSeeking, (void **)&pMS);
pGB->QueryInterface(IID_IMediaControl, (void **)&pMC);
}
The methods
HRESULT play()
{
pMC->Run(); return
S_OK;
}
HRESULT pause()
{
pMC->Pause();
return S_OK;
}
HRESULT stop()
{
LONGLONG pos = 0;
pMC->Stop();
pMS->SetPositions(&pos, AM_SEEKING_AbsolutePositioning,
NULL,AM_SEEKING_NoPositioning);
pMC->Pause();
return S_OK;
}
HRESULT close()
{
SAFE_RELEASE(pWC);
SAFE_RELEASE(pMix);
SAFE_RELEASE(pGB);
SAFE_RELEASE(pVmr);
SAFE_RELEASE(pConfig);
SAFE_RELEASE(pMC);
SAFE_RELEASE(pMS);
return S_OK;
}
HRESULT setAlpha(DWORD stream, float alpha)
{
pMix->SetAlpha(stream, alpha);
return S_OK;
}
HRESULT setX(DWORD stream, float x)
{
r->right = x + (r->right - r->left);
r->left = x;
pMix->SetOutputRect(stream, r);
return S_OK;
}
HRESULT setY(DWORD stream, float y)
{
r->bottom = y + (r->bottom - r->top);
r->top = y;
pMix->SetOutputRect(stream, r);
return S_OK;
}
HRESULT setW(DWORD stream, float w)
{
r->right = r->left + w;
pMix->SetOutputRect(stream, r);
return S_OK;
}
HRESULT setH(DWORD stream, float h)
{
r->bottom = r->top + h;
pMix->SetOutputRect(stream, r);
return S_OK;
}
HRESULT renderFiles(String* file1, String* file2)
{
LPCTSTR lFile;
lFile =
static_cast<LPCTSTR>(const_cast<void*>(static_cast<const void*>
(System::Runtime::InteropServices::Marshal::StringToHGlobalAuto(file1))));
pGB->RenderFile((LPCWSTR)lFile, NULL);
lFile =
static_cast<LPCTSTR>(const_cast<void*>(static_cast<const void*>
(System::Runtime::InteropServices::Marshal::StringToHGlobalAuto(file2))));
pGB->RenderFile((LPCWSTR)lFile, NULL);
System::Runtime::InteropServices::Marshal::FreeHGlobal
(static_cast<IntPtr>(const_cast<void*>
(static_cast<const void*>(lFile))));
pMC->StopWhenReady();
return S_OK;
}
Now that the VMR9's functionality has been separated from the GUI, Button and TrackBar handlers can simply create a pointer to a myVMR9
object and call the required methods.
Additional Information
- The second video stream opened is on top of the first one, i.e., file-2 video is rendered on top of file-1 video. Therefore, if the first video's alpha value is a 100% and the second video's alpha value is 50%, then both videos will be equally (50%) visible.
- It should be noted that the values of Width and Height of trackbars can run into negative values. So when a video stream's width is -100%, it is laterally inverted. Similarly, when a video stream's height is -100%, the video is upside down.