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.

 

Comments (19)

  1. Adam says:

    Given all the media foundation stuff and the fact that MovieMaker ships in the product, will MovieMaker be rewritten to use media foundation in Longhorn?

  2. Sean Malloy says:

    Eric,

    just questioning that particular use of generics.

    What is the difference between your generic CreateComObject method, and say:

    private Object CreateComObject(Guid guid)

    {

    Type comType = Type.GetTypeFromCLSID(guid);

    object o = Activator.CreateInstance(comType);

    if (o == null)

    return null;

    return o;

    }

    IGraphBuilder graphBuilder = (IGraphBuilder)CreateComObject(CLSID_FilterGraph);

    It just seems like using generics because they’re there, rather than gaining some benefit out of them. Or am I missing the point?

  3. Sean Malloy says:

    or for the sake of brevity

    private Object CreateComObject(Guid guid)

    {

    return Activator.CreateInstance(Type.GetTypeFromCLSID(guid));

    }

  4. Chris Lundie says:

    Eric, are you aware of DirectShow.NET? (http://www.codeproject.com/cs/media/directshownet.asp) Or maybe you would just not be allowed to use it in your work.

    If there’s a real managed replacement for DirectShow coming, I can’t wait to hear about it!

  5. Matthew W. Jackson says:

    Personally, I would hope that there’s going to be a managed version of every API that still actively in use, _especially_ DirectShow. I’d really like to be able to write a good front-end for my VIVO/TV Capture stuff, as I’ve not found a program to my liking (most of my gripes are problems with multiple-monitor support).

    A goal for you guys: by the year 2010 (or earlier), I don’t want to ever have to write ANY P/Invoke or COM Interop code again.

    Back when I first started hearing about .NET, I kind of figured that at first it would be built on top of Windows at first, and over time, Windows would be built on top of it. It seems that this is at least partially true.

    But it could be taken farther, possibly ending up with something like Wine, but which ran on top of .NET. But why stop there? Throw an x86-to-IL dynamic recompiler/emulator, and you could theoretically run old unmanged apps in a managed sandbox environment. I actually wouldn’t mind having to run legacy unmanged applications inside another program, much like running a DOS box in Windows, (or more like running Win16 apps on OS/2).

    Anyway, I’m getting off topic. I guess what I’m really wondering is: what APIs are NOT part of the "WinFX initiative"? Are there any unmanged APIs (excluding very low-level Kernal/Driver stuff) that won’t ever make their way to the Managed world? Maybe there should be a chart with all of the Windows APIs, including a list of managed APIs that replace/wrap them, and a projected OS/Framework version for the managed version. It’d be nice to see some sort of big picture.

  6. Matthew W. Jackson:

    Part of your question is answered in this article: http://msdn.microsoft.com/library/?url=/library/en-us/dndotnet/html/win32map.asp.

    I hope that it will get updated for the changes in .NET 2.0.

  7. mihailik says:

    Eric, you have to do something more and get real C# definitions of interfaces.

    After you run IDL -> TLB -> Assembly conversion, you get some ‘dirty’ DLL. But with Lutz Roeder .NET Reflector you can get C# from this and just paste code to your project.

    That dirty DLL that produces IDL/TLB/Assembly conversion often have too many unneed types, some parameter types must be redefined or MarshalAs attributes replaced. If you convert all that to C# source code, you can clean all interfaces and get stable soft.

    To get interface definition in last version of Reflector open assembly, find the interface and press Space. I like this 🙂

  8. Matthew W. Jackson says:

    Andreas Haber:

    Thanks. That’s actually a lot more detailed than I was looking for, but there’s certainly no problem with that.

  9. Eric says:

    Here’s the file that I used to create the .TLB using MIDL. You should create your own UUID with UUIDGEN instead of using mine.

    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;

    };

  10. Sean McLeod says:

    If you’re playing around with DirectShow as part of your ramp up for Movie Maker development have you tried out the use of Direct3D pixel shaders for video effects?

    If you say wanted to convert video frames to grayscale or add a swirl effect you can either write a DirectShow filter which runs on the CPU or using the VMR-9 (Video Mixing Renderer) you can use the GPU with pixel shaders to get the same effect at the end of the pipeline.

  11. Zen says:

    I’ve taken a slightly different approach… instead of trying to bring all the directshow API’s into a managed langauge, I just create higher level wrappers in Managed C++, do all the actual directshow stuff inside in unmanaged code, and just call the interfaces on my wrapper.

    In most cases, you really don’t need access to every single directshow interface in the application.

    I just expose the functionality i need rather than trying to emulate the entire interface.

    You can look at my code here…

    http://svn.xiph.org/trunk/oggdsf/src/lib/player/libDSPlayDotNET/DSPlay.cpp

    and a very simple .NET player which uses it.

    http://svn.xiph.org/trunk/oggdsf/src/tools/DNPlay/frmDNPlay.cs

    Some of the stuff is specific to a particular media type i’m working on (annodex)

  12. Stephen Toub says:

    Several years ago, I wrote an article for MSDN on programming against the DVR-MS file format. I’m very

  13. Several years ago, I wrote an article for MSDN on programming against the DVR-MS file format. I&#39;m