David Sackstein's Blog

"The more that you learn, the more places you'll go.”, Dr. Seuss

July 2009 - Posts

Hello World Silverlight on a Blog Post

This is a test page that I wrote to check that I can run Silverlight applications in a blog post. This way I will be able to demonstrate Silverlight and WPF (not all) stuff without you having to necessarily download and compile it.

It works! and the possibilities are endless : )

Get Microsoft Silverlight

Here is the XAML for this Hello World blog-hosted Silverlight application:

<UserControl x:Class="HelloWorld.MainPage"

   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   Width="400" Height="300"

   >

   <UserControl.Resources>

      <Storyboard x:Key="storyBoard">

         <DoubleAnimation

                 Storyboard.TargetName="button"

                 Storyboard.TargetProperty="FontSize"

                 To="30"

                 Duration="0:0:2"

                 AutoReverse="True"

           >

         </DoubleAnimation>

      </Storyboard>

   </UserControl.Resources>

   <Grid x:Name="LayoutRoot" Background="White">

      <Button Name="button" Height="100" Width="200" FontSize="20"

             Click="Button_Click">

         <TextBlock>Hello World</TextBlock>

      </Button >

   </Grid>

</UserControl>

In order to start the animation I used this code behind implementation for Button_Click:

    private void Button_Click(object sender, RoutedEventArgs e)

    {

        Storyboard storyBoard = this.Resources["storyBoard"] as Storyboard;

        storyBoard.Begin();

    }

I was able to load the Silverlight control into this page simply by editing the HTML inserting the following:

   <div id="silverlightControlHost">

      <object data="data:application/x-silverlight-2,"

             type="application/x-silverlight-2"

             width="100%" height="100%">

         <param name="source"

          value="http://blogs.microsoft.co.il/blogs/davids/../HelloSilverlightOnBlog.zip/>

         <param name="background" value="white" />

         <param name="minRuntimeVersion" value="3.0.40624.0" />

         <param name="autoUpgrade" value="true" />

         <a href=http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0

           style="text-decoration:none">

            <img src=http://go.microsoft.com/fwlink/?LinkId=108181

                alt="Get Microsoft Silverlight" style="border-style:none"/>

         </a>

      </object>

  </div>

WPF Simple ControlTemplate Sample

Here is a sample of using a ControlTemplate to customize a button.

The result is a circular button with a radial brush that, when clicked, animates the origin of the brush from the center by a small offset - and then back.

I put it in a page so you can copy this snippet to a text file and open with Internet Explorer.

<Page

  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

  Title="Page1" Height="300" Width="300">

   <Page.Resources>

      <Style TargetType="{x:Type Button}">

         <Setter Property="Template">

            <Setter.Value>

               <ControlTemplate>

                  <Ellipse Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">

                     <Ellipse.Fill>

                        <RadialGradientBrush x:Name="radialBrush" SpreadMethod="Pad">

                           <GradientStop Offset="1" Color="Red"></GradientStop>

                           <GradientStop Offset="0" Color="White"></GradientStop>

                        </RadialGradientBrush>

                     </Ellipse.Fill>

                  </Ellipse>

                  <ControlTemplate.Triggers>

                     <EventTrigger RoutedEvent="ButtonBase.Click">

                        <BeginStoryboard>

                           <Storyboard

                            TargetName="radialBrush"

                            TargetProperty="GradientOrigin">

                              <PointAnimation

                               From="0.5,0.5"

                               To="0.4,0.4"

                               Duration="0:0:1"

                               AutoReverse="true">

                              </PointAnimation>

                           </Storyboard>

                        </BeginStoryboard>

                     </EventTrigger>

                  </ControlTemplate.Triggers>

               </ControlTemplate>

            </Setter.Value>

         </Setter>

      </Style>

   </Page.Resources>

   <Button Height="300" Width="300"/>

</Page>

Here are the highlights:

  1. The page contains one button and a style (in Page.Resources).
  2. The style applies to Button controls without a key – and therefore applies by default to all buttons in the Page.
  3. The style contains only one Setter which is the one that sets the Template property of the Button.
  4. The value of the Setter is a ControlTemplate that contains two parts: content – an Ellipse, and one trigger – for the animation.
  5. The Ellipse uses the TemplateBinding markup extension to grab the actual Width and Height of the button.
  6. The Ellipse’s Fill property is a RadialGradientBrush called radialBrush that is centered at (0.5, 0.5). This is interpreted as the center of the Ellipse’s bounding rectangle.
  7. radialBrush goes from White at the center (Offset 0) to Red at the edge (Offset 1).
  8. SpreadMethod is set to Pad so that when the center of the brush is moved away from the center of the Ellipse, the area of the Ellipse beyond Offset 1 is colored Red.
  9. The Trigger of the ControlTemplate is an EventTrigger, triggered by a click on the Button (ButtonBase.Click)
  10. When the event is triggered, a PointAnimation is executed.
  11. The PointAnimation moves the GradientOrigin from (0.5, 0.5) (center) to (0.4, 0.4) over a 1 second interval – and then reverses the operation.
Rendering Video with VMR9 on a Windows Form

This week I taught a class on video and DirectShow and sat down to prepare some demos.

In this demo I show how to use the Video Mixing Render 9 (VMR9) in Windowless mode to render video on the Form of a Windows Forms application. As in my posts on MSXML I found that the smart pointers of comdef.h and comip.h really helped to clean up the code.

Why, programming the VMR9 in C++ can be as elegant as C# !

You can find the complete source code here.

High Level Design

For this demo I started with the “windowless” sample provided with DirectShow, which is written in C++ against the Win32 native API’s.

With all due respect for the usefullness of the sample, the programming style is difficult to follow. I set out to separate the DirectShow code from the battle field of Win32 native programming and to make the DirectShow code as elegant as C#.

The result is two projects, one a dll written in C++ to handle the DirectShow code and the other a simple Windows Forms application. The Windows Forms application calls the C++ dll with good old Platform Invoke.

I didnt port all the functionality of the “windowless” sample, but that shouldn’t be difficult to do.

Let’s take a closer look at the C++ Library (WindowlessVMR9) and the Windows Forms Application.

The C++ Library: WindowlessVMR9

The API

The API of the library is a set of C-style functions that will be called by the Windows Form application using Platform Invoke. Here they are:

bool VmrInitialize(HWND hwnd);

void VmrCleanUp(HWND hwnd);

bool VmrPlay (LPCWSTR lpcwstrFile);

bool VmrStop ();

void VmrOnPaint (HWND hwnd);

void VmrDisplayModeChanged ();

void VmrMoveVideoWindow(HWND hwnd);

VmrInitialize creates a filter graph manager and a VMR9 renderer filter. It  adds the VMR9 filter to the graph manager and sets its rendering mode to “windowless”. This means that renderer will not create its own window, rather it will use the one it is given by the application.

VmrInitialize then retrieved the IVMRWindowlessControl9 interface from the VMR9 filter and uses it to provide the application window handle to the filter. This function will typically be called when the window loads.

VmrCleanUp releases the resources allocated by VmrInitialize. This function will typically be called when the window closes.

VmrPlay uses the RenderFile method of the filter graph manager’s IGraphBuilder interface to build the graph, based on the VMR9 filter previously added. It then retrieves the IMediaControl interface of the filter graph manager and calls the Run method through it.

VmrStop retrieves the IMediaControl interface of the filter graph manager and calls the Stop method through it.

VmrOnPaint retrieves a device context from the window and passes it to the RepaintVideo method of the IVMRWindowlessControl9 interface of the filter graph manager. This function should be called whenever the window handles a WM_PAINT message.

VmrMoveVideoWindow uses the IVMRWindowlessControl9 interface to reposition the rendered video in the client area of the hosting window’s new coordinates following a WM_SIZE message.

VmrDisplayModeChanged uses the IVMRWindowlessControl9 interface to notify the VMR9 filter that the display mode (e.g. the display resolution or the DPI) has changed. This function will rarely be called.

Smart Pointers Revisited

Before we dive into the implementation of the WindowlessVMR9 library, lets introduce some smart pointers for our DirectShow interfaces.

I started with this:

#include <comdef.h>

#define COM_SMARTPTR_TYPEDEF(Interface) _COM_SMARTPTR_TYPEDEF(Interface, IID_ ## Interface)

My COM_SMARTPTR_TYPEDEF macro exploits the _COM_SMARTPTR_TYPEDEF macro of comdef.h and the COM naming conventions for interfaces and their GUIDS to allow us to define smart pointers based on the _com_ptr_t template like so:

COM_SMARTPTR_TYPEDEF(IGraphBuilder);

and like so

COM_SMARTPTR_TYPEDEF(IMediaControl);

These statements define smart pointers IGraphBuilderPtr and IMediaControlPtr for the IGraphBuilder and IMediaControl interfaces respectively.

Of course, for these to compile you need to have previously included the headers that declare the interfaces themselves, but once done, you can similarly define smart pointers for all the interfaces you need.

Here is the full text of the DirectShowPtr.h include file that I used for this sample:

#pragma once

 

#include <comdef.h>

#include <dshow.h>

#include <d3d9.h>

#include <vmr9.h>

 

#define COM_SMARTPTR_TYPEDEF(Interface) _COM_SMARTPTR_TYPEDEF(Interface, IID_ ## Interface)

 

COM_SMARTPTR_TYPEDEF(IGraphBuilder);

COM_SMARTPTR_TYPEDEF(IFilterGraph);

COM_SMARTPTR_TYPEDEF(IMediaControl);

COM_SMARTPTR_TYPEDEF(IMediaFilter);

COM_SMARTPTR_TYPEDEF(IMediaEvent);

COM_SMARTPTR_TYPEDEF(IEnumFilters);

COM_SMARTPTR_TYPEDEF(IVideoWindow);

COM_SMARTPTR_TYPEDEF(IVMRWindowlessControl9);

COM_SMARTPTR_TYPEDEF(IBaseFilter);

COM_SMARTPTR_TYPEDEF(IVMRFilterConfig9);

Now, to say that _com_ptr_t is a smart pointer is a bit of an understatement.

Apart from having the all the assignment operators, copy constructors and conversion operators you need to make it safe in almost any context, _com_ptr_t also has some very smart COM specific constructors:

Take this statement for example:

        IGraphBuilderPtr graphBuilder (CLSID_FilterGraph);

Here, the constructor of IGraphBuilderPtr will call CoCreateInstance for the co class with the specified CLSID to retrieve a IGraphBuilder interface and store it in graphBuilder. All that in one line!

And look at this one:

        IMediaControlPtr mediaControl(graphBuilder);

Here, the constructor of IMediaControlPtr will call QueryInterface on the graphBuilder for an IMediaControl interface and store it in mediaControl.

With those arrows in our quiver, it would even be safe to write this:

        IMediaControlPtr mediaControl (CLSID_FilterGraph);

        IGraphBuilderPtr graphBuilder (CLSID_FilterGraph);

        mediaControl = IMediaControlPtr(graphBuilder);

and once you get your confidence up, you will even feel comfortable writing this:

        HRESULT hr = IMediaControlPtr(m_GraphBuilder)->Run();

Wow!

_com_ptr_t is defined in comip.h which I included via comdef.h. These files are located in the VC/Include directory of Visual Studio 2008.

Now _com_ptr_t isnt the only smart pointer we could use. ATL also provides smart pointers for COM (ComPtr and ComQIPtr), but I preferred not to include the ATL headers just for these classes.

Also, the Samples\Multimedia\DirectShow\Common folder under the Windows SDK provides a smart pointer class in the SmartPtr.h file, but it isnt half as powerful as _com_ptr_t.

Implementation

Having separated the DirectShow code from the Win32 Window code, and having hidden the complexities of COM behind smart pointers, I think the implementation explains itself:

static IGraphBuilderPtr             m_GraphBuilder;

static IVMRWindowlessControl9Ptr    m_WindowlessControl;

 

bool VmrInitialize(HWND hwnd)

{

    try

    {

        m_GraphBuilder = IGraphBuilderPtr (CLSID_FilterGraph);

 

        IBaseFilterPtr vmrFilter (CLSID_VideoMixingRenderer9);

 

        HRESULT hr = m_GraphBuilder->AddFilter(vmrFilter, L"Video Mixing Renderer 9");

        if (! SUCCEEDED(hr))

            return false;

 

        IVMRFilterConfig9Ptr(vmrFilter)->SetRenderingMode(VMR9Mode_Windowless);

 

        m_WindowlessControl = IVMRWindowlessControl9Ptr(vmrFilter);

        if(! m_WindowlessControl)

            return false;

 

        m_WindowlessControl->SetVideoClippingWindow(hwnd);

    }

    catch (_com_error)

    {

        assert (false);

        return false;

    }

    return true;

}

 

void VmrCleanUp(HWND hwnd)

{

    try

    {

        m_WindowlessControl.Release();

        m_GraphBuilder.Release();

    }

    catch (_com_error)

    {

        assert (false);

    }

}

 

bool VmrPlay (LPCWSTR lpcwstrFile)

{

    if (! m_GraphBuilder)

        return false;

 

    try

    {

        HRESULT hr = m_GraphBuilder->RenderFile(lpcwstrFile, NULL);

        if (! SUCCEEDED(hr))

            return false;

 

        hr = IMediaControlPtr(m_GraphBuilder)->Run();

        return SUCCEEDED (hr);

    }

    catch (_com_error)

    {

        assert (false);

    }

 

    return false;

}

 

bool VmrStop ()

{

    if (! m_GraphBuilder)

        return false;

 

    try

    {

        HRESULT hr = IMediaControlPtr(m_GraphBuilder)->Stop();

        return SUCCEEDED (hr);

    }

    catch (_com_error)

    {

        assert (false);

    }

    return false;

}

 

void VmrOnPaint (HWND hwnd)

{

    if (! m_WindowlessControl)

        return;

 

    PAINTSTRUCT ps;

    HDC         hdc;

    hdc = BeginPaint(hwnd, &ps);

 

    // When using VMR Windowless mode, you must explicitly tell the

    // renderer when to repaint the video in response to WM_PAINT

    // messages.  This is most important when the video is stopped

    // or paused, since the VMR won't be automatically updating the

    // window as the video plays.

 

    try

    {

        HRESULT hr = m_WindowlessControl->RepaintVideo(hwnd, hdc);

    }

    catch (_com_error)

    {

        assert (false);

    }

 

    EndPaint(hwnd, &ps);

}

 

void VmrDisplayModeChanged ()

{

    if (! m_WindowlessControl)

        return;

 

    try

    {

        m_WindowlessControl->DisplayModeChanged();

    }

    catch (_com_error)

    {

        assert (false);

    }

}

 

void VmrMoveVideoWindow(HWND hwnd)

{

    if (! m_WindowlessControl)

        return;

 

    try

    {

        RECT g_rcDest;

 

        GetClientRect(hwnd, &g_rcDest);

        HRESULT hr = m_WindowlessControl->SetVideoPosition(NULL, &g_rcDest);

    }

    catch (_com_error)

    {

        assert (false);

    }

}

I think you will agree that this looks more like C# than native COM coding.

 

The Windows Forms Application

In the Windows Forms application I wrote this Platform Invoke class to enable me to call the WindowlessVMR9 unmanaged code:

    public class Vmr9

    {

        [DllImport("WindowlessVmr9", EntryPoint = "VmrInitialize")]

        public static extern bool Initialize(IntPtr hwnd);

 

        [DllImport("WindowlessVmr9", EntryPoint = "VmrCleanUp")]

        public static extern bool CleanUp();

 

        [DllImport("WindowlessVmr9", EntryPoint = "VmrPlay", CharSet = CharSet.Unicode)]

        public static extern bool Play(string fileName);

 

        [DllImport("WindowlessVmr9", EntryPoint = "VmrStop")]

        public static extern bool Stop();

 

        [DllImport("WindowlessVmr9", EntryPoint = "VmrOnPaint")]

        public static extern void OnPaint(IntPtr hwnd);

 

        [DllImport("WindowlessVmr9", EntryPoint = "VmrDisplayModeChanged")]

        public static extern void DisplayModeChanged();

 

        [DllImport("WindowlessVmr9", EntryPoint = "VmrMoveVideoWindow")]

        public static extern void MoveVideoWindow(IntPtr hwnd);

    }

WinForms allows us to retrieve the native HWND of a Form and Platform Invoke allows us to marshall it to the unmanaged code. The Form code itself has now been reduced to this:

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

        }

 

        private void Form1_Load(object sender, EventArgs e)

        {

            Vmr9.Initialize(panelVideo.Handle);

 

            buttonPlayStop.Text = "Play";

            buttonPlayStop.Enabled = false;

        }

 

        protected override void OnPaint(PaintEventArgs e)

        {

            base.OnPaint(e);

            Vmr9.OnPaint(panelVideo.Handle);

        }

        protected override void OnResize(EventArgs e)

        {

            base.OnResize(e);

            Vmr9.MoveVideoWindow(panelVideo.Handle);

        }

 

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)

        {

            Vmr9.CleanUp();

        }

 

        private void buttonOpen_Click(object sender, EventArgs e)

        {

            DialogResult result = openFileDialog1.ShowDialog();

            if (result == DialogResult.OK)

            {

                textBox1.Text = openFileDialog1.FileName;

                buttonPlayStop.Enabled = true;

            }

        }

 

        private void buttonPlayStop_Click(object sender, EventArgs e)

        {

            if (buttonPlayStop.Text == "Play")

            {

                buttonPlayStop.Text = "Stop";

                buttonOpen.Enabled = false;

                Vmr9.Play(openFileDialog1.FileName);

                Vmr9.MoveVideoWindow(panelVideo.Handle);

            }

            else

            {

                buttonPlayStop.Text = "Play";

                buttonOpen.Enabled = true;

                Vmr9.Stop();

            }

        }

    }

Summary

In this post I demonstrated how to use the Video Mixing Renderer 9 in a Windows Forms Application in windowless mode.

The code is based on the “windowless” sample of the Windows SDK but has been simplified by separating the VMR9 code into an unmanaged dll, replacing the Win32 GUI with Windows Forms and relying on smart pointers to hide the complexities of COM programming.