Introduction

This article describes how to use DirectShow.NET. DirectShow.NET is a dll to use DirectShow as managed code. I found it hard to find examples for using DirectShow. I still didn't find out how that all works. That is why I translated a C# example of DirectShow.NET to VB.NET code. Here, I show you what the code finally looks like. You all could still help me (and others) to provide a better understanding of what the code means. In the help below, you will only find stuff that I thought was difficult for me to understand. The rest is explained in the code itself.

Background

DirectShow will be dumped for the Windows Presentation Foundation. In the meanwhile, I recommend the book "Programming Microsoft DirectShow for Digital Video and Television", ISBN: 0-7356-1821-6. I also recommend googling for DirectShow.NET and reading their website which is really good.

Step 2

Now we create our own namespace (maybe we want to import our VB in a different project or something later.

Inside we create our program in public class Form 1. Public means that outside this namespace , this class is still reachable. Inherits means something like in MSDN : causes the current class or interface to inherit the attributes, variables, properties, procedures, and events from another class or set of interfaces. So, go and let this class feel like system.windows.forms.form.

What I think happens here is that we want to let WM_GRAPHNOTIFY create a ordinary windows message holder in the format of a string. And that it has to start in place 0X8000, because that place is where the filtergraph events start. Filtergraph events are events that DirectShow gives you when you use inputs, filters and outputs.

Dim videoWindow As IVideoWindow = Nothing

What you do here is create an object with the format of a videowindow that starts out empty (with nothing in it). Generally, this is a video renderer that draws the video onto a window on the display.

Dim mediaControl As IMediaControl = Nothing

This creates an empty object/interface that function likes tape-deck buttons. The filter graph exposes the IMediaControl interface to allow applications to control the streaming of media through the filters in the graph. The interface provides methods for running, pausing, and stopping the streaming of data.

Dim mediaEventEx As IMediaEventEx = Nothing

This creates a empty object for event messages.

"This interface derives from IMediaEvent and adds a method that allows registration of a window to receive messages when events occur. This interface is used by applications to receive notification that an event has occurred. Applications can then avoid using a separate thread that waits until an event is set."

This means that you can receive events without the rest of your program stopping to wait for a new message..

Dim graphBuilder As IGraphBuilder = Nothing

This is our main object that will create filters. It will put the input filter (webcam), through a conversion filter, so that it can show it on a Ivideowindow.This interface provides methods that enable an application to build a filter graph. The Filter Graph Manager implements this interface.

Dim captureGraphBuilder As ICaptureGraphBuilder2 = Nothing

This is a object that we create to help us out with building stuff that has to do with capturing of video and/or audio. (That's called a helper object)

Dim rot As DsROTEntry = Nothing

This makes a helper object for the program "graphedit" . When you start up "graphedit" you can make a connection with your program!

What you can do to see this is the following:

Debug the program.

Start graphedit.exe and go to File->Connect to remote graph.

What would happen is that graphedit makes a graphical representation of how it creates your filters to show "the webcam capture device" to the "render video window".

Step 4B: Our main program loop

This is a short one:

<STAThread()> SharedSub <place w:st="on">Main</place>

What STAThread does is protect our program against using objects at the same time in our program. That's rather handy , because streaming can be rather more complicated if you can't think about this step by step. Think about what could happen between the window and the capture preview if they both live their own life. Then moving the main window could leave your capture preview behind because the capture preview is still busy with its own "message solving".

Sub main is the main/head sub-routine that will start when we write code..

Shared Sub Main means that if our class is used multiple times in a new program/object, we still only use one Sub Main that will be shared by all. There are no extra copies of the sub main in memory for each instance, there is only one.

First we create an object called resources that will have the feel of a resourceManager. This use of the resourceManager gives access to the resource management of the form.Next interesting line is the Debug.Writeline. It writes a line of text to your debug output. If you don't know where that is , go to the Visual Basic menu, go to debug, and select output. When you test run your program, you will see these messages appearing in this output window.

We will try to implement the code "you write" , but when there is an "exception error", we will show a "messageBox" which will translate the exception code (ex) to normal readable text.A exception error could be all kinds of errors we create. When an error occurs, either the system or the currently executing application reports it by throwing an exception containing information about the error. "hr" is a placeholder for (mainly error) messages. It will keep them as numbers. So you have to use a error number translator. I have shown how to do this later in the code.

Next we create an object that works like a "DirectShow filter object" . These objects have a input-pin and output-pin, and do filtering in between. DirectShow graphs are (in the most simplest way) a chain of filter-objects , each doing their own type of conversion or filtering. So all in all a "chain of filters" always has a source and a target. Now we use this filter object creation to create the source (our webcam). Filter objects do more, but I won't tell you now to keep it simple. So, what is in the "Big chunk of code" ? Let's check it out.

GetInterfaces()

This means we will start a subroutine to create the building blocks of our interface. Here is what "getinterfaces()" says : "hr" is for keeping the error code. "Me" is used to point out that we are talking about our own created objects , not stuff made by something else. CTYPE is a function to convert a object type to another object type.

Me.graphBuilder = CType(New FilterGraph, IGraphBuilder)

Lets fill the "graphBuilder"-object with a newFilterGraph object, (a type of "Igraphbuilder").The FilterGraph is our chain of filters that we will build.

This method designates a window as the recipient of messages generated by or sent to the current DirectShow object. Variable "hr" gets a number back for this.

DsError.ThrowExceptionForHR(hr)

ThrowExceptionForHR is a wrapper for Marshal.ThrowExceptionForHR, but additionally provides descriptions for any DirectShow specific error messages. If the " hr" value is not a fatal error, no exception will be thrown.

Debug.WriteLine("I started Sub Get interfaces , the result is : " &
DsError.GetErrorText(hr))

I already explained what this does before.

Now that getinterfaces() has been completed, let us go back to our try-code. Remember that when there is an error , the "dserror" code will throw an exception that directly starts up the catch code to tell us we had a error.

We use the "CaptureGraphBuilder" (helper) object to "set up" the "GraphBuilder" – Filtergraph. When there is an error we throw a exception.

sourceFilter = FindCaptureDevice()

What this code does is to use the system device enumerator and class enumerator to find a video capture/preview device, such as a desktop USB video camera. Let's look at the function "FindCaptureDevice()" code:

A sermonizer creates a "enumeration" object that makes it easy to use objects that have a name and id number.

Enumerations provide a convenient way to work with sets of related constants and to associate constant values with names. For example, you can declare an enumeration for a set of integer constants associated with the days of the week, and then use the names of the days rather than their integer values in your code. Enumerations most often count objects in the most abstract way possible. An enumerator is an object that iterates through its associated collection. It can be thought of as a movable pointer to any element in the collection.

Create an enumeration for the video capture devices .The CreateClassEnumerator method creates an enumerator for a specified device category meaning that we are going to fill classEnum with VideoInputDevices. The zero at the end means that we will check up every sort of filter (to find VideoInputDevices).

Marshal.ReleaseComObject(devEnum)

The device enumerator is no more needed so we dump it. Marshal provides a collection of methods for allocating unmanaged memory, copying unmanaged memory blocks, and converting managed to unmanaged types, as well as other miscellaneous methods used when interacting with unmanaged code.

If classEnum IsNothingThenThrowNew ApplicationException("No video capture device =
was detected.\r\n\r\n" &
"This sample requires a video capture device, such as a USB WebCam,\r\n" & _
"to be installed and working properly. The sample will now close.")
EndIf

I think it is clear to understand that when a enumeration goes well , it doesn't mean we have a device in the enumeration list. So, let's check if we have one, otherwise we don't have a webcam.

Let's go to the first line. An "if then else" function is easy to understand. Imonkier.next(a,b,c) retrieves a specified number of items in the enumeration sequence.

a = number of elements requested

b = the array of elements requested will be given back to b

c = if c is null, we want only one element back , if c is some other integer we want that total amount back in b.

So Classenum.next let moniker fill with only one element of classenum (that will be the first videoinputdevice). And if this classenum gives back a value 0 , then…By the way if it returns 0 (S_OK) it means that there is a moniker. If it returns 1 (S_False) it means that there is no moniker.

So if there is a videocapturedevice then :

Dim iid As Guid = GetType(IBaseFilter).GUID

Let "iid" be a global unique identifier , based on IbaseFilter.

moniker(0).BindToObject(Nothing, Nothing, iid, source)

Use the reference to the capturedevice as the source, and give it a global unique identifier.

If the form resizes, there will be different situations. For us, it is interesting what to do when the form is minimized or normal. Because the state of the directshow preview could better change in those situations. That's why we start out a function called ChangePreviewState(). The code of ResizeVideoWindow is already explained above.

The "protected overloads sub" means that we also want to have control over something. That something is WndProc, the message interface so that we can see messages that we need to know for our program.It is used byRef because we make a reference to that message.In case the message is a WM_Graphnotify one, we will handle this.

WM_Graphnotify was the holder of directshow messages. But if it is not the case, we want to restore the message and sent it back to where it belongs. That's what the rest means.

Otherwise we use a while loop. As long as the while loop condition is still true we do another loop. The .GetEvent method retrieves the next event notification from the event queue from MediaEventEx. It fills efCode, EvParam2 and evParam2 with info about that. The 0 means that we wait infinitely for that message.

As said before , if the result is 0 (meaning S_OK), there is a message. So as long there are messages in the queue, execute this while loop.

Like the routine above , we also want to have some control in ending our program, because we use references in memory that cannot be killed automatically by itself. You could keep crap in memory. Like for me, if I don't stop the program correctly (pressing stop using the debugger stop button), I cannot use the webcam the second time.

So if disposing is true, then start the subroutine closeinterfaces(), after this , do closing stuff that is normally done by the program itself.

PublicSub closeinterfaces()

Also closeinterfaces() is now is easy to understand.

So that's it. Hope you liked this stuff. If you see errors or have any questions/hints/corrections, please post them here.

Points of Interest

If I stop the program using the debugger instead of closing the form itself, the filtergraph doesn't close properly and the webcam does not become useable anymore.

Comments and Discussions

On my computer I've a webcam ad an USB capture device (the Syntek EasyCap). If Easycap is attached, video starts from this device (but doesn't operate well 'cause I cannot select acquisition source from EasyCap that have 3 inputs). If EasyCap isn't attached, video starts from webcam. How can I solve those problems?