Click here to Skip to main content
15,903,175 members
Articles / Programming Languages / C#
Article

DirectShow.NET

Rate me:
Please Sign up or sign in to vote.
4.94/5 (193 votes)
22 Jul 2002Public Domain3 min read 4.2M   34K   453   840
DirectShow for DVD and file playback, capture and sample grabber

Sample Image - directshownet.jpg

Disclaimer: Experimental code using DirectShow with the .NET Framework 1.0

Abstract

This experimental code shows how to use DirectShow with .NET and C#. This includes simple media playback, playing DVD discs, capturing video streams to disk and a sample picture grabber.

Note, this article doesn't save you from reading the detailed DirectShow SDK documentation! I will not explain DirectShow, only some of the used .NET Interop technologies!

DirectShow

DirectShow is a standardized Microsoft Win32 API to use any compliant movie or video device from your application. DirectShow is available with the current DirectX version 8.1(b) for Windows 98/ME/2000 and included in XP. Please install the latest version, this article doesn't support anything except 8.1 :

Again, I will not describe any DirectShow interfaces, you have to know them by installing the SDK for C++, reading the SDK documentation and understanding the SDK samples!

DirectShow is exposed as COM components and interfaces, at these two 'levels':

  • DirectShow custom interfaces - mainly for C++ programmers.
  • DirectShow VB components - designed for VB6, provides a type library.
You can use the DirectShow playback components for VB6 with .NET, as described in this CodeProject article: DirectShow MediaPlayer in C# (Daniel Strigl)

.NET Interop

While using the VB6 components with the provided type library is easy with .NET, there is no direct way to access the custom DirectShow interfaces. We have to use Interop with one of this approaches:

  • Use 'Managed Extensions for C++', as done e.g. by DirectX.NET
  • Rewrite all the interfaces from IDL to e.g. C# !
I chose the second strategy for this reasons :
  • Uses only one (managed) language (C#)
  • Most DirectShow interfaces are not very complex
  • DirectShow methods for simple playback/capturing are not time-critical
  • We can directly use the (documented) interfaces without limitations, no 'wrapper classes'
Sure, this has some drawbacks:
  • Much of initial work for rewriting the interfaces
  • You have to understand Interop to use it correctly
  • Not very .NET/OO-like
One typical rewrite of an IDL interface in C# looks like this :
// ======== IDL of ICaptureGraphBuilder2 (AXExtend.idl) ======
[
    object,
    uuid(93E5A4E0-2D50-11d2-ABFA-00A0C9C6E38D),
    pointer_default(unique)
]
interface ICaptureGraphBuilder2 : IUnknown {

    // Use this filtergraph
    HRESULT SetFiltergraph( [in] IGraphBuilder *pfg );

    // what filtergraph are you using?
    // *ppfg->Release() when you're done with it
    HRESULT GetFiltergraph( [out] IGraphBuilder **ppfg);
    ....
... using Interop attributes with C# will be translated to :
C#
// ======== C# version of ICaptureGraphBuilder2 (DsExtend.cs) ======

   [ComVisible(true), ComImport,
    Guid("93E5A4E0-2D50-11d2-ABFA-00A0C9C6E38D"),
    InterfaceType( ComInterfaceType.InterfaceIsIUnknown )]
public interface ICaptureGraphBuilder2
{
        [PreserveSig]
   int SetFiltergraph( [In] IGraphBuilder pfg );

        [PreserveSig]
   int GetFiltergraph( [Out] out IGraphBuilder ppfg );
   ....

Once we have all this interface definitions in C#, we can start calling DirectShow just like we did in C++:

// ======== C++ code to create the COM instance of Filter Graph ========

    JIF(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, 
                         IID_IGraphBuilder, (void **)&pGB));

    // Have the graph builder construct its the <BR>    // appropriate graph automatically
    JIF(pGB->RenderFile(wFile, NULL));

    // QueryInterface for DirectShow interfaces
    JIF(pGB->QueryInterface(IID_IMediaControl, (void **)&pMC));
    ....
... we replace CoCreateInstance with Activator.CreateInstance, and QueryInterface just is a simple cast in C#:
C#
// ======== C# code to create the COM instance of Filter Graph ========

    Type comtype = null;
    object comobj = null;
    try {
        comtype = Type.GetTypeFromCLSID( Clsid.FilterGraph );
        if( comtype == null )
            throw new NotSupportedException( <BR>                "DirectX (8.1 or higher) not installed?" );
        comobj = Activator.CreateInstance( comtype );
        graphBuilder = (IGraphBuilder) comobj; comobj = null;
        
        int hr = graphBuilder.RenderFile( clipFile, null );
        if( hr < 0 )
            Marshal.ThrowExceptionForHR( hr );

        mediaCtrl    = (IMediaControl)  graphBuilder;
     ....

Projects Structure

The download contains all this C# source code:

\DirectShow\
     \DShowNET\              // the DirectShow interface definitions :
              \DsBugWO.cs      // workaround for a bug 
              \DsControl.cs    // ported from control.odl 
              \DsCore.cs       // ported from axcore.idl 
              \DsDevice.cs     // device enumerator, helper functions 
              \DsDVD.cs        // DVD interfaces from dvdif.idl 
              \DsExtend.cs     // ported from axextend.idl 
              \DsUtils.cs      // utility classes, SDK Common sources 
              \DsUuids.cs      // UUIDs and CLSIDs from uuids.h 
              \QEdit.cs        // grabber interfaces from qedit.idl 

     \CaptureNET\            // video stream capture sample 
     \DVDPlayerNET\          // DVD player sample 
     \PlayWndNET\            // simple media file playback 
     \SampleGrabberNET\      // picture grabber 

Playback

The first sample included in the download is PlayWndNET. It plays the known video and audio file formats of DirectShow like avi, mpg, wav, mid etc.

DirectShow playback

DVD Player

For the next sample, DVDPlayerNET you must have a third-party DVD codec installed, like WinDVD or PowerDVD. Then, the C# sample uses the DirectShow DVD interfaces to watch the movie. It also supports menu navigation.

DirectShow DVD

Grab Picture

The most complex sample provided is SampleGrabberNET. It shows a live video stream from a capture device like DV cam, web cam or TV card in a preview window. By pressing the 'Grab' toolbar-button, you can capture a still picture to a 24-Bit RGB bitmap file!

DirectShow picture grabber

The sample also supports the IAMTVTuner interface of a TV card, so you can switch the TV tuner channel.

Capturing

The last sample, CaptureNET can be used to capture a live video stream to disk. Note, the few settings can only be done once at startup, and writing to the AVI file starts immediately.

DirectShow Capturing

Limitations

  • EXPERIMENTAL! don't use it in production quality code.
  • The samples only provide partial and very basic functionality.
  • I did most tests on Windows XP and few on Windows ME.
  • Tested only on a very limited set of devices with only few media formats.
    I used a Logitech QuickCam, Sony DV camcorder, Hauppauge WinTV PCI and WinDVD.
  • Get the latest driver (WDM) from manufacturer.
  • Some devices fail if you select unsupported settings in the dialogs.
  • This code will NOT help to solve any DirectShow/WDM configuration problems.
  • Get >128MB RAM, >400MHz CPU, fast & huge harddisk.

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication


Written By
Web Developer
Switzerland Switzerland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: User contributions to DirectShow.NET Pin
jmiadowicz15-Jan-03 4:40
jmiadowicz15-Jan-03 4:40 
GeneralRe: User contributions to DirectShow.NET Pin
Brian Low15-Jan-03 6:34
Brian Low15-Jan-03 6:34 
GeneralRe: User contributions to DirectShow.NET Pin
23-Jan-03 14:27
suss23-Jan-03 14:27 
GeneralRe: User contributions to DirectShow.NET Pin
Mr.Naveed30-Mar-04 11:54
Mr.Naveed30-Mar-04 11:54 
GeneralChanging resolution of incoming video stream without using DsUtils.ShowCapPinDialog Pin
robBurke29-Nov-02 10:16
robBurke29-Nov-02 10:16 
GeneralRe: Changing resolution of incoming video stream without using DsUtils.ShowCapPinDialog Pin
Brian Low29-Nov-02 13:02
Brian Low29-Nov-02 13:02 
GeneralRe: Changing resolution of incoming video stream without using DsUtils.ShowCapPinDialog Pin
Brian Low13-Jan-03 20:44
Brian Low13-Jan-03 20:44 
GeneralRe: Changing resolution of incoming video stream without using DsUtils.ShowCapPinDialog Pin
Brian Low15-Jan-03 3:14
Brian Low15-Jan-03 3:14 
I've got some working code for this as well as setting Frame Rate, Audio Sampling Rate, Audio Sample Size, Number of Audio Channels.

After adding the video and audio devices to your filter graph find the IAMStreamConfig interface for your video and audio devices. The code below saves these two interfaces into videoStreamConfig and audioStreamConfig:
// Retrieve the stream control interface for the video device
// FindInterface will also add any required filters
// (WDM devices in particular may need additional
// upstream filters to function).

// Try looking for an interleaved media type
object o;
cat = PinCategory.Capture;
med = MediaType.Interleaved;
Guid iid = typeof(IAMStreamConfig).GUID;
hr = captureGraphBuilder.FindInterface(
    ref cat, ref med, videoDeviceFilter, ref iid, out o );

if ( hr != 0 )
{
    // If not found, try looking for a video media type
    med = MediaType.Video;
    hr = captureGraphBuilder.FindInterface(
        ref cat, ref med, videoDeviceFilter, ref iid, out o );

    if ( hr != 0 )
        o = null;
}
videoStreamConfig = (IAMStreamConfig) o;

// Retrieve the stream control interface for the audio device
o = null;
cat = PinCategory.Capture;
med = MediaType.Audio ;
iid = typeof(IAMStreamConfig).GUID;
hr = captureGraphBuilder.FindInterface(
    ref cat, ref med, audioDeviceFilter, ref iid, out o );
if ( hr != 0 )
    o = null;
audioStreamConfig = (IAMStreamConfig) o;


For my video card, you can only get and set the frame size when the device pins are not connected. Once I render the device (with SetOutputFilename) I can no longer access this property. I don't know if all devices are like that. To change the frame size or audio sampling rate:

/// <summary>
///  Gets and sets the frame size used to capture video.
///  Not all frame size are supported. Changing to an unsupported
///  size will throw and exception. Not all devices support 
///  getting/setting the frame size, in which case, accessing 
///  this property will throw an exception.
/// </summary>
public Size FrameSize
{
	get
	{
		BitmapInfoHeader bmiHeader;
		bmiHeader = (BitmapInfoHeader) getStreamConfigSetting( videoStreamConfig, "BmiHeader" );
		Size size = new Size( bmiHeader.Width, bmiHeader.Height );
		return( size );
	}

	set
	{
		BitmapInfoHeader bmiHeader;
		bmiHeader = (BitmapInfoHeader) getStreamConfigSetting( videoStreamConfig, "BmiHeader" );
		bmiHeader.Width = value.Width;
		bmiHeader.Height = value.Height;
		setStreamConfigSetting( videoStreamConfig, "BmiHeader", bmiHeader );
	}		
}

/// <summary>
///  Get or set the audio sampling rate for waveform-audio data. 
///  Common sampling rates are 8.0 kHz, 11.025 kHz, 22.05 kHz, and 
///  44.1 kHz. Not all devices support getting/setting the 
///  sampling rate and not all sampling rates are supported, 
///  in which case, accessing this property will throw an 
///  exception.
/// </summary>
public int AudioSamplingRate
{
	get
	{
		int samplingRate = (int) getStreamConfigSetting( audioStreamConfig, "nSamplesPerSec" );
		return( samplingRate );
	}
	set
	{
		setStreamConfigSetting( audioStreamConfig, "nSamplesPerSec", value );
	}
}


For other settings like Frame Rate check out the VIDEOINFOHEADER, BITMAPINFOHEADER and WAVEFORMATEX structures.

All the magic is in getStreamConfigSetting and setStreamConfigSetting. I'm not sure why this message board discards blank lines. It make the code sample a little hard to read.

/// <summary>
///  Retrieves the value of one member of the IAMStreamConfig format block.
///  Helper function for several properties that expose
///  video/audio settings from IAMStreamConfig.GetFormat().
///  IAMStreamConfig.GetFormat() returns a AMMediaType struct.
///  AMMediaType.formatPtr points to a format block structure.
///  This format block structure may be one of several
///  types, the type being determined by AMMediaType.formatType.
/// </summary>
protected object getStreamConfigSetting( IAMStreamConfig streamConfig, string fieldName)
{
    if ( streamConfig == null )
        throw new NotSupportedException();
    assertStateForProperty( CaptureState.Stopped );
    if ( isGraphRendered )
        derenderGraph();

    object returnValue = null;
    IntPtr pmt = IntPtr.Zero;
    AMMediaType mediaType = new AMMediaType();

    try
    {
        // Get the current format info
        int hr = streamConfig.GetFormat( out pmt );
        if ( hr != 0 )
            Marshal.ThrowExceptionForHR( hr );
        Marshal.PtrToStructure( pmt, mediaType );

        // The formatPtr member points to different structures
        // dependingon the formatType
        object formatStruct;
        if ( mediaType.formatType == FormatType.WaveEx )
            formatStruct = new WaveFormatEx();
        else if ( mediaType.formatType == FormatType.VideoInfo )
            formatStruct = new VideoInfoHeader();
        else
            throw new NotSupportedException( "This device does not support a recognized format block." );

        // Retrieve the nested structure
        Marshal.PtrToStructure( mediaType.formatPtr, formatStruct );

        // Find the required field
        Type structType = formatStruct.GetType();
        FieldInfo fieldInfo = structType.GetField( fieldName );
        if ( fieldInfo == null )
            throw new NotSupportedException( "Unable to find the member '" + fieldName + "' in the format block." );

        // Extract the field's current value
        returnValue = fieldInfo.GetValue( formatStruct );

    }
    finally
    {
        DsUtils.FreeAMMediaType( mediaType );
        Marshal.FreeCoTaskMem( pmt );
    }

    return( returnValue );
}

/// <summary>
///  Set the value of one member of the IAMStreamConfig format block.
///  Helper function for several properties that expose
///  video/audio settings from IAMStreamConfig.GetFormat().
///  IAMStreamConfig.GetFormat() returns a AMMediaType struct.
///  AMMediaType.formatPtr points to a format block structure.
///  This format block structure may be one of several
///  types, the type being determined by AMMediaType.formatType.
/// </summary>
protected object setStreamConfigSetting( IAMStreamConfig streamConfig, string fieldName, object newValue)
{
    if ( streamConfig == null )
        throw new NotSupportedException();
    assertStateForProperty( CaptureState.Stopped );
    if ( isGraphRendered )
        derenderGraph();

    object returnValue = null;
    IntPtr pmt = IntPtr.Zero;
    AMMediaType mediaType = new AMMediaType();

    try
    {
        // Get the current format info
        int hr = streamConfig.GetFormat( out pmt );
        if ( hr != 0 )
            Marshal.ThrowExceptionForHR( hr );
        Marshal.PtrToStructure( pmt, mediaType );

        // The formatPtr member points to different structures
        // dependingon the formatType
        object formatStruct;
        if ( mediaType.formatType == FormatType.WaveEx )
            formatStruct = new WaveFormatEx();
        else if ( mediaType.formatType == FormatType.VideoInfo )
            formatStruct = new VideoInfoHeader();
        else
            throw new NotSupportedException( "This device does not support a recognized format block." );

        // Retrieve the nested structure
        Marshal.PtrToStructure( mediaType.formatPtr, formatStruct );

        // Find the required field
        Type structType = formatStruct.GetType();
        FieldInfo fieldInfo = structType.GetField( fieldName );
        if ( fieldInfo == null )
            throw new NotSupportedException( "Unable to find the member '" + fieldName + "' in the format block." );

        // Update the value of the field
        fieldInfo.SetValue( formatStruct, newValue );

        // PtrToStructure copies the data so we need to copy it back
        Marshal.StructureToPtr( formatStruct, mediaType.formatPtr, false );

        // Save the changes
        hr = streamConfig.SetFormat( mediaType );
        if ( hr != 0 )
            Marshal.ThrowExceptionForHR( hr );

    }
    finally
    {
        DsUtils.FreeAMMediaType( mediaType );
        Marshal.FreeCoTaskMem( pmt );
    }

    return( returnValue );
}


And finally add this helper method to the DsUtils class:

/// <summary> 
///  Free the nested structures and release any 
///  COM objects within an AMMediaType struct.
/// </summary>
public static void FreeAMMediaType(AMMediaType mediaType)
{
	if ( mediaType.formatSize != 0 )
		Marshal.FreeCoTaskMem( mediaType.formatPtr );
	if ( mediaType.unkPtr != IntPtr.Zero ) 
		Marshal.Release( mediaType.unkPtr );
	mediaType.formatSize = 0;
	mediaType.formatPtr = IntPtr.Zero;
	mediaType.unkPtr = IntPtr.Zero;
}


I believe that is it. There may be minor changes needed to some of the DShow interfaces (e.g. changed type of argument from IntPtr to AMMediaType). Feel free to reply here or email with questions.

Brian
GeneralRe: Changing resolution of incoming video stream without using DsUtils.ShowCapPinDialog Pin
alexhongkong14-Mar-04 22:42
alexhongkong14-Mar-04 22:42 
GeneralExcellent Pin
Brian Low28-Nov-02 14:15
Brian Low28-Nov-02 14:15 
GeneralA pair of errors in SampleGrabber!!! Help please! Pin
Intrumov25-Nov-02 22:46
Intrumov25-Nov-02 22:46 
GeneralRe: A pair of errors in SampleGrabber!!! Help please! Pin
Steven Behnke6-Jan-03 7:44
Steven Behnke6-Jan-03 7:44 
GeneralHidden Mouse Pin
Member 7438819-Nov-02 20:20
Member 7438819-Nov-02 20:20 
GeneralHelp me please, Simple how to show bitmap or character overlay clip in fullscreen mode when play clip Pin
Nueng19-Nov-02 15:47
Nueng19-Nov-02 15:47 
GeneralRe: Help me please, Simple how to show bitmap or character overlay clip in fullscreen mode when play clip Pin
Brian Low29-Nov-02 13:06
Brian Low29-Nov-02 13:06 
GeneralHelp me please, How to stop clip when end clip, don't loop Pin
Nueng19-Nov-02 15:34
Nueng19-Nov-02 15:34 
GeneralIMediaSeeking / SetPositions Pin
Cory Smith18-Nov-02 5:57
Cory Smith18-Nov-02 5:57 
GeneralA bit offtopic: DVD Menu Button Positions Pin
FatBastardDelivery15-Nov-02 1:03
FatBastardDelivery15-Nov-02 1:03 
GeneralRe: A bit offtopic: DVD Menu Button Positions Pin
NETMaster15-Nov-02 4:56
NETMaster15-Nov-02 4:56 
GeneralRe: A bit offtopic: DVD Menu Button Positions Pin
FatBastardDelivery17-Nov-02 21:28
FatBastardDelivery17-Nov-02 21:28 
GeneralSaving a snapshot of a played back video Pin
Cory Smith12-Nov-02 9:08
Cory Smith12-Nov-02 9:08 
GeneralRe: Saving a snapshot of a played back video Pin
mgd76062913-Dec-03 6:44
mgd76062913-Dec-03 6:44 
GeneralRe: Saving a snapshot of a played back video Pin
furkinfedup10-Oct-04 23:35
furkinfedup10-Oct-04 23:35 
GeneralIMediaDet Interface and GetBitmapBits Pin
4-Nov-02 7:02
suss4-Nov-02 7:02 
GeneralClosed Captions Pin
Anonymous28-Oct-02 10:11
Anonymous28-Oct-02 10:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.