This post builds upon prior work where we used C# with DirectShow.NET to enumerate video capture devices on a given machine. We use the device enumerator we built there to help us start composing a filter graph.

DirectShow uses the word filter to denote any software component that performs an operation on a multimedia stream. A filter has an input and an output, and filters can be strung together. Filters laced together like this are called filter graphs. The DirectShow documentation provides an example of a filter graph that reads an AVI file from disk for playback as below.

IC420231

So in this filter graph, we have five filters: one to read the file, one to split the signal, another to output the sound, and the remaining two to handle the video display. We won’t be building anything this complex; we will just be building a filter graph to capture the video from a web cam on our computer for display on our screen.

 

We Are Not Zealots

Our first order of business is to add two new projects to our cam enumeration solution named CamVideoLibrary and CamVideoLibraryTests. Now while I ascribe to TDD for most any code I write, coding against objects that have to think in a COM sort of way is one place where I back down from this. As such, we aren’t going to be using TDD for the following code; there is, however, a test that makes sure things compile and run (forgive me ahead of time for creating a “helper” class).

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DirectShowLib;
using NUnit.Framework;

namespace CamVideoLibraryTests
{
    [TestFixture]
    public class FilterGraphBuilderHelperTests
    {
        [Test]
        public void CanRenderStreamTest()
        {
            var x = new CamEnumeration.CaptureDeviceEnumerator();
            using (var y = new FilterGraphBuilderHelper(x.AvailableVideoInputDevices[0]))
            {
                int returnValue = y.RenderStream();

                Assert.That(returnValue, Is.GreaterThanOrEqualTo(0));
            }
        }
    }
}

 

As we can see, we are testing whether or not we can “render a stream” on our new filter graph builder helper class. Given the COM approach of things, our test is just checking the return value to ensure that it’s positive, which usually indicates something non-fatal occurred. We also see that we are using the device enumerator class that we built in prior work so we know which capture device we want to utilize. Now let’s flesh out the FilterGraphBuilderHelper object. We start with the constructor.

using System;
using DirectShowLib;

namespace CamVideoLibrary
{
    public class FilterGraphBuilderHelper : IDisposable
    {
        public DsDevice DeviceToCapture { get; private set; }

        public FilterGraphBuilderHelper(DsDevice d)
        {
            DeviceToCapture = d;
        }

        public void Dispose()
        {
            throw new NotImplementedException();
        }
    }
}

 

All we’re doing here is capturing the provided input device. Note also that we’re implementing IDisposable; this is key, since we will need to clean up after ourselves given that we’re using COM objects. With this in place, we can move on to the RenderStream method.

public ICaptureGraphBuilder2 CaptureGraphBuilder { get; private set; }
public IGraphBuilder GraphBuilder { get; set; }

public int RenderStream()
{
    GraphBuilder = new FilterGraph() as IGraphBuilder;
    CaptureGraphBuilder = new CaptureGraphBuilder2() as ICaptureGraphBuilder2;
    CaptureGraphBuilder.SetFiltergraph(GraphBuilder);

    IBaseFilter filter = GetBaseFilterForDevice();
    GraphBuilder.AddFilter(filter, "Video Capture");

    int renderStreamComResult = -1;
    try
    {
        renderStreamComResult = CaptureGraphBuilder.RenderStream(
            PinCategory.Preview,
            MediaType.Video,
            filter,
            null,
            null);
        DsError.ThrowExceptionForHR(renderStreamComResult);
    }
    finally
    {
        if (filter != null)
        {
            Marshal.ReleaseComObject(filter);
        }
    }

    return renderStreamComResult;
}

 

First we note that we’ve introduced two new properties on our helper object, an IGraphBuilder and an ICaptureGraphBuilder2 (yes, welcome to unmanaged code!). The IGraphBuilder allows us to build a filter graph, whereas the ICaptureGraphBuilder2 allows us to build a capture graph by pointing to a filter graph manager, otherwise known as an IGraphBuilder.

Next we see that we get the base filter for the device with which we initialized our helper object and add it to our graph.

After this, we attempt to render a stream from our capture graph builder. We are instructing our builder to use a preview pin, which essentially says that we want to display this stream on the screen and are ok dropping frames as needed in order to keep the display smooth (we could have specified a capture pin, which would indicate that we wanted to send the stream to a file). We are telling our builder that the stream is video, and we are passing our device’s base filter as well.

The ThrowExceptionForHR method is a helper method provided by the DirectShow.NET library which handles the nasty handling of COM errors for us.

Finally (get it), we make sure to clean up after ourselves by releasing our device’s base filter by using the hotness of Marshal.ReleaseComObject, returning the integer result that we retrieved when actually attempting to render the stream. Note that the Marshal object is in the System.Runtime.InteropServices namespace, so we have to make sure to import that in our class.

The last piece of magic here is the GetBaseFilterForDevice method, which appears below.

private IBaseFilter GetBaseFilterForDevice()
{
    object source;
    DeviceToCapture.Mon.BindToObject(
        null,
        null,
        typeof(IBaseFilter).GUID,
        out source);
    return source as IBaseFilter;
}

 

We can see that we’re getting a little closer to COM ugliness here. The Mon property on our DsDevice object is of type System.Runtime.InteropServices.ComTypes.IMoniker, which is COM’s way of both identifying an object and allowing a client to bind (use) said object.

All that’s left in our helper class is to clean up after ourselves. The fleshed out Dispose method appears below.

public void Dispose()
{
    if (GraphBuilder != null)
    {
        Marshal.ReleaseComObject(GraphBuilder);
    }
    if (CaptureGraphBuilder != null)
    {
        Marshal.ReleaseComObject(CaptureGraphBuilder);
    }
}

 

Again, since we’re butting up against the COM world, we have to make sure to clean up after ourselves. With this code in place, our simple test actually passes.

 

Next Steps

Now that we have build up our capture graph, we can actually connect this graph to a video display, which in our case will just be a form. That will be the next post. Enjoy!

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

   
© 2011 Musings of the Bare Bones Coder Suffusion theme by Sayontan Sinha