| Experimental DirectShow Source Filter in C# |
|
|
|
|
This document describes the steps and code needed to implement a DirectShow source filter in C#. The code itself can be found in the following zip file. This zip file also contains a Readme.txt file which explains how to install the filter in your system. The strategy we use to implement a DirectShow filter in C# relies on having a C++ class that contains a pointer to a managed object which implements our filter. The C++ class just forward all the processing to the managed object. In order to achieve this, we can use a sample from the SDK and modify the code or use a Wizard (usually derived from the original Wizard designed by Ross Cutler) that generates a skeleton for the classes needed by our filter and the VS project settings. For the CsYuv filter, I've used Cutler's Wizard and in this filter we'll modify the project and code for the Ball filter from the DirectShow SDK. Make sure that you can build the Ball filter sample. If you just want to test the filter, then build the code in the zip file and follow the steps in the ReadMe file to install it. If you want to create your own source filter, now I'll describe the steps needed to modify the Ball sample project and after that, we'll look at the code. First, maybe you'll want to make a backup copy of the Ball sample filter directory. Then in VS, delete all the files of the project and add the header, cpp, and def files from the zip archive to the project. This is the code for the C++ wrapper. Then add a new C# Class Library project to the solution, and replace the files in the newly created project by the cs files in the zip archive. This is the code for our managed class that implements the filter functionality. Since you'll want to install the assembly for the filter in the GAC, you'll need to sign the assembly (e.g. type "sn -k key.snk" at the prompt of a VS shell). Note that the open-source DirectShowLib should also be in the GAC for the filter to work correctly. Some of the Ball sample project settings have to be modified in order to embed a pointer to a managed object. We'll describe these now. From the C++ project settings of the solution, go to the "C/C++/Precompiled Headers" page and choose "Not Using Precompiled Headers". Go to the "C/C++/General" page and choose "Assembly Support (/clr)" for the field "Compile As Managed". Also on the same page, enter the path to the managed assembly in the field "Resolve #using References". If the linker complains about a missing __CorDllMain function, toy with the /NODEFAULTLIB under Linker/Input/Ignore All Default Libraries". We are now ready to look at the code. Let's start with the header file for the C++ project. The top of the file shows the following:
Since we are embedding a pointer to a managed object in a native C++ class we need to tell the compiler about it. So we have the references to the "mscorlib.dll" and our assembly "csfilter.dll". Then we add an include file for the "gcroot" template that allows us to embed a pointer to a managed class. At the bottom of the header file, we have the following declaration:
The rest of the header file is almost identical to the Ball source header file. Let's now look at the cpp file, it's also very similar to any other source filter implementation. In the constructor for the source filter output pin, we define our managed object:
Notice that our managed object is member of a class derived from CSoureStream: the base class for the output pin on a source filter which is derived from the CAMThread. So, the native streaming thread manipulates a pointer to a managed object. Then the implementation of the all the member functions of the C++ class for the source filter output pin just calls a corresponding method on our managed object, for example:
We marshal all our arguments as IntPtr because we assume this is the option that produces the smallest overhead but we haven't done any actual profiling. We are now ready to look at the C# implementation of our source filter. The CsFilter.cs file starts with (after some standard using directives):
I've mentioned that the open-source DirectShowLib is needed. The IMemTypeLib was created using the techniques described in the "Cooking a custom library" tutorial but where the created assembly was disassembled using the "Reflector" utility (and the code was cut and pasted from "Reflector" panel to the file "IMemTypeLib.cs"). I won't write a DS source filter tutorial but I'll notice the differences between a typical DS source filter and the implementation in C# of this one. This experimental filter just support one media type, so we have implemented the GetMediaType method that requires a single argument. The method starts as follows:
First we marshal our media type structure and free its format field, then we'll fill a VideoInfoHeader structure and the fields of the media type. The interesting ones are:
We allocate memory from the native heap and make the formatPtr of media type points to it. Then we copy in the native heap the two structures. The DecideBufferSize method configures the memory allocator, again we'll concentrate on the differences when done in C#. The method starts with:
First, we get the interface "pointer" for the memory allocator which is created by the base classes before a call to the DecideBufferSize method is made. Similarly, we get the allocator properties required by the downstream filter through interop using the second argument to the method. After filling some properties, we release the memory allocator pointer:
The FillBuffer method is where we draw our frame, the method receives a pointer to a media sample.
We marshal the media sample, then prepare some timestamps and indicate that we have a keyframe since our samples are not compressed. After getting a pointer to the buffer data, we turn it into a bitmap object and use its "device context" to draw the backgroud red and display the frame count. When we're done we do some cleanup. The method OnThreadCreate just reset our frame counter to 0. As the title of this document stresses, this is an experimental filter. While MS doesn't recommend using managed code for filters, now at least, we have a basis for comparaison. I have delayed posting this code because I believe that the C++ code should be hidden in some kind of wrapper but I haven't found a good way to do this. |
| < Prev |
|---|














