DirectShow and C#

I'm currently in the "ramping up" phase of my job, and last week I spent a fair amount of time playing around with DirectShow from C#.

DirectShow is an interesting technology that provides a sort of "building block" approach to multimedia. You create a DirectShow graph, populate it with different filters, hook them together, and start the whole process in motion. DirectShow handles the mechanics of getting the different parts talking to each other so data can flow through the system.

If, for example, you wanted to capture some audio and save it to the disk in WMA format. You'd start with an audio input filter, which has an output pin. You'd wire that output pin to the input pin of a WMA encoder, and then wire the output pin of the encoder to the input pin of the FileStream filter, and that would give you the basic graph. Select which input you want, tell the graph to play, and your app is up and savin' data.

Want to add a little reverb? Hook in the reverb filter.

Video is similar - you hook things up together, and DShow handles passing things around. There's a cool workbench app named GraphEdit (graphedt.exe) that lets you graphically play around, and is a great way to find out what works before you write code.

All of this is explained quite well in "Programming Microsoft DirectShow for Digital Video and Television"

There are some managed DirectX wrappers in the DX9 SDK, but there aren't any available for DirectShow. I think I know why, but I'll save that for later...

I set out to try to use C# from DirectShow, to emulate some of the C++ samples. The first problem is defining the COM interfaces. I've spent some time trying to define interfaces by hand in the past, but that hasn't been very successful. I searched for a typelib, but didn't find one.

I did, however, find something that is a little better - the .IDL files. IDL files provide the definition of COM interfaces, and in this case are used to generate the C++ header files. They can also be used as input to MIDL, which produces a type library, which can be consumed by tlbimp to produce managed wrappers, which allows us all to go home early.

They can if they've been set up to do that. But this IDL wasn't authored to produce a typelib, so it doesn't generate one by default. To do that, you need a library statement, which tells MIDL what to put in the typelib. Here's what I put in an IDL file:

import "devenum.idl";
import "axcore.idl";
import "axextend.idl";

[
uuid(22995cc9-e37e-4b96-9326-b418935ac4be),
helpstring("DirectShow interfaces")
]
library DirectShow
{
interface IFilterGraph;
interface ICreateDevEnum;
interface IGraphBuilder;
interface ICaptureGraphBuilder2;
interface IFileSinkFilter;
interface IFileSinkFilter2;
interface IAMAudioInputMixer;
};

You have to have a GUID there for it to work.

When run through MIDL, this gave me the typelib, which gave me a wrapper with some of the types I needed. You'll also need to add quartz.dll from windows\system32, which does have type information, so that you can get some of the other types.

Once you've got that set up, you can start writing code. The C++ COM code in the samples is like most C++ COM code - ugly and hard to understand. But it's not that bad in C#.

To create a COM object, you need to write:

Type t = Type.GetTypeFromCLSID(guid);

IGraphBuilder graphBuilder = (IGraphBuilder) Activator.CreateInstance(t);

The guid is a Guid instance, and you get the Guid to use by looking in the include files or copying it from GraphEdit. That's a bit ugly, so I wrote this little helper function:

private T CreateComObject<T>(Guid guid) where T: class|
{
Type comType = Type.GetTypeFromCLSID(guid);
object o = Activator.CreateInstance(comType);
if (o == null)
return null;

    else
return (T) o;
}

Which then allows me just to write:

IGraphBuilder graphBuilder =
CreateComObject<IGraphBuilder>(CLSID_FilterGraph);

That's a nice little use of a generic method.

Sometimes, you want an interface off an object rather than a new object. You can do that through a simple cast.

Up to this point, things are fairly easy, and it makes you wonder why there's no managed interface to this stuff. Some of the real power comes when you want to write a filter, which could do something convert a picture to grayscale. While one could write a filter in C#, one would have to be pretty daft to try it, given that there are lots of helper classes in C++ that you'd have to rewrite in C++ (ok, perhaps MC++ could help...). My guess is that that's why there are no managed wrappers.