| Notes on writing a VMR-9 custom allocator/presenter in C# |
|
|
|
|
The latest release of the Video Mixing Renderer (VMR-9) offers very
interesting possibilities. It has a modular design that allows developpers
to replace its main components by custom made components. We can replace
the Allocator/Presenter and the Compositor component. In these notes,
I'll describe how to write a custom allocator/presenter in C#. The code
is not as well polished and robust as I would like it. But it should serve as
a good starting point for others to build upon. Once again, the code can be found
in a zip file.
The documentation for this task is contained in a single page of the DirectShow SDK documentation entitled "Supplying a Custom Allocator-Presenter for VMR-9". The VMR9Allocator sample (in the VMR-9 samples directory) was used as the base for our implementation. First, we have to deal with accessing the required interfaces in C#. The DirectShowLib contains these definition in their "untested" sections. I looked at these, and I decided to create my own custom library because the "untested" version of IVMRSurfaceAllocatorNotify9.AllocateSurfaceHelper had an object for last argument, and I really prefer just a plain old IntPtr. So I wrote a small idl file with the needed interfaces which contain the following (plus a few that we won't need, all this taken from customap.idl)
During the development, I had to modify the standard marshaler behaviour for IVMRSurfaceAllocatorNotify9.SetD3DDevice because it creates this method with ref uint arguments and the only way I found to provide an interface pointer to the device needed for the first argument of this method was through the property Device.UnmanagedComPointer (which require the /unsafe switch to the C# compiler). The modification is minor and was created as follows: I ran the build_customaptypelib command file to produce an interop assembly for the interfaces listed above. I disassembled the assembly just produced (with the command "ildasm /out="tmp.il" customaptypelib.dll" and replace, in the file "tmp.il", the two arguments to SetD3DDevice with native int. Then I reassembled the assembly with the command: ilasm /dll /output=customaptypelib.dll" tmp.il. These steps are well explained in the section entitled "Editing An Interop Assembly" of the .Net SDK documentation but it makes you wonder why Microsoft doesn't provide a type library (.tlb) file for DirectShow. Now, we are ready to actually implement our custom allocator/presenter.
The VMR-9 renderer will hand over video samples as Direct3D surfaces to our custom allocator/presenter, so we need access to Direct3D and we reference these assemblies (plus our own CustomAPTypeLib). Then, we declare variables for the interfaces that we haven't seen before:
The interface IVMRFilterConfig9 is needed to set the VMR-9 filter in "renderless" mode i.e. the filter will not render samples but will let our "presenter" component do the job. The IVMRSurfaceAllocatorNotify9 interface is implemented on the VMR-9 filter and allows the renderer and our component to communicate. The customAP variable is our custom allocator/presenter component that implement two interfaces; it is declared as follows: (taken from CustomAP.cs)
We'll come back to these two interfaces a little later. In the function VMR9Alloc which build the filter graph, we find:
We create the VMR-9 renderer and access its IVMRFilterConfig9 interface to call the method SetRenderingMode with a "renderless" argument. Then we create our custom A/P and tell the renderer about it using the interface IVMRSurfaceAllocatorNotify9 and the method AdviseSurfaceAllocator. While it's not clear from the documentation, accessing this interface on the renderer should be done after setting the renderer in "renderless" mode because some cards will not allow it otherwise (I learned it the hard way :-| We tell our component about the renderer with a call to AdviseNotify and let "Intelligent Connect" do its magic. We are now ready to look at the implementation of our allocator/presenter component. Its member variables are as follows:
The comments make these declarations fairly straightforward. Then we have the method CreateDevice
This is a direct translation of the C++ code in the VMR9Allocator sample. The only difference is the new handler for cancelling the DeviceResizing event. The vertex buffer for our rectangle, in the Scene class, will be declared with the Pool.Managed as argument and we'll be happy with the default DirectX behaviour (instead of the smarter processing done by Managed DirectX). The DirectShow documentation says on point 6 (for supplying a custom A/P): "Allocate surfaces in the IVMRSurfaceAllocator9::InitializeDevice callback method". So this what we are going to do next. We start with:
Because textures whose dimensions are not power of 2 suffer significant performance penalties on many cards, we fix the dimension to satisfy this requirement. We also arrange our texture coordinates to represent our new dimension. Following this, we make sure that we are requesting "texture" surfaces and call AllocateSurfaceHelper on the IVMRSurfaceAllocatorNotify9 of the renderer.
We are keeping pointers to surfaces in our surfaces array which is sufficient for our purpose; so we just read the values returned by AllocateSurfaceHelper and stuff them in our array. After that, we call Init on our Scene The implementation of GetSurface just returned one of the pointers returned previously:
Since lplpSurface points in native memory, we call WriteIntPtr to copy our surface pointer into this location. AdviseNotify stores away the pointer to our renderer and asks it to SetD3DDevice:
I haven't found a better way to do this in C#. PresentImage is called by the renderer when it wants our component to display a video frame (or sample). The code simply sets the rendering target, gets the texture from the surface field of the PresentationInfo structure, calls our Scene.DrawScene and finally dump it to the screen with Device.Present.
The Scene class is standard Managed DirectX and we'll skip the details. [Note (25/9/06), I have received the following message. When I'll update this document, I'll look at. Till then, it might be of some help. Hello, I'm sending you some informations related to playing .MPG and .WMV files using your useful C# implementation of custom VMR-9 allocator/presenter (tutorialDS7.zip). Playing .AVI files worked from start but when I tried to play .MPG or .WMV files, there were some problems. After some poking around I found out a way to play these type of files as well. For example, addition to VidTexture.cs: ... fc = (IVMRFilterConfig9)vmr; fc.SetNumberOfStreams(1); fc.SetRenderingMode( 0x00000004 ); //VMRMode_Renderless = 0x00000004 ... Consequence of this addition is that lpAllocInfo.Format as argument to IVMRSurfaceAllocator9.InitializeDevice() is always 0 and call to m_san.AllocateSurfaceHelper doesn't fail as in situation when fc.SetNumberOfStreams is not called. If you think these informations are useful to other users of tutorialDS7.zip, you can post it on your notes page (http://www.a2ii..com/tech/directx/tutorialDS7.htm). Thanks, Predrag ]
|
| < Prev | Next > |
|---|














