yooomedia - 2012-07-16

Introduction

In this post I describe how to make DirectShow filters in pure C#, I made the BaseClasses library also in pure C# and few samples to show you how easy it can be used. I think not advanced multimedia developers can use my library, but for extending it, I think, you should have knowledge of COM, marshaling and threading. At start I suggest you to check up my previous posts as I will not do repeating some major things described here.

Content overview

Source code consist 2 projects: BaseClasses and ExampleFilters. BaseClasses library consist of next classes:

  • BaseEnum - base class for enumerations.
  • EnumPins - implementation of IEnumPins interface.
  • EnumMediaTypes - implementation of IEnumMediaTypes interface.
  • BasePin - base class for pin.
  • BaseInputPin - base class for input pin.
  • BaseOutputPin - base class for output pin.
  • BaseFilter - base class for filter implementation.
  • TransformInputPin - transform filter input pin class.
  • TransformOutputPin - transform filter output pin class.
  • TransformFilter - transform filter base class.
  • TransInPlaceInputPin - trans-in-place filter input pin class.
  • TransInPlaceOutputPin - trans-in-place filter output pin class.
  • TransInPlaceFilter - trans-in-place filter base class.
  • RenderedInputPin - rendered input pin base class.
  • AMThread - Thread implementation base class.
  • ManagedThread - managed thread implementation of AMThread.
  • SourceStream - base output stream pin class.
  • BaseSourceFilter - base class for source filter implementation.

Another project in solution consist of sample filters:

  • NullInPlaceFilter - in-place transform filter which can be used as base.
  • NullTransformFilter - transform filter which can be used as base.
  • DumpFilter - filter for saving incoming data into file.
  • TextOverFilter - transform filter display overlay text on incoming video.
  • ImageSourceFilter - push source filter which display loaded image file as a video stream.
  • ScreenCaptureFilter - push source filter which capture display and provide it as a video stream.
  • VideoRotationFilter - transform filter rotate video on 90 degree.
  • WavDestFilter - filter for saving incoming PCM data into a WAV file.
  • AudioChannelFilter - transform filter which combine incoming PCM audio data and send it to specified channel.

BaseClasses

Base classes are also used some stuff of my class library and COM helper objects. They are partially described in my previous posts. For tracing debugging I suggest to use TRACE, TRACE_ENTER and ASSERT functions; and DO NOT use of Debug.Write and throwing exceptions. Why that described in previous article, plus I suggest to read my description regarding threading in previous post (just don't want to repeating). Here I describe only difference in mine implementation as my classes have similar methods as Microsoft native. So if you are follow all that aspects let's start the reviwing.

Filter registration

Filter registering as particular .NET COM object. Filter dll SHOULD BE signed. You can embed the typelibrary as unmanaged resource in dll but that's not mandatory. In samples registration perfomed automatically see install.bat and uninstall.bat files and post build events. You may specify the registration in project settings but that may not works. I made the filter registration as fairly simple. For that there is a class attribute with different constructors:

// Class declaration
[AttributeUsage(AttributeTargets.Class)]
public class AMovieSetup : Attribute
//.............
// Constructors
public AMovieSetup()
public AMovieSetup(bool _register)
public AMovieSetup(string _name)
public AMovieSetup(Merit _merit)
public AMovieSetup(Merit _merit, Guid _category)
public AMovieSetup(string _name, Merit _merit)
public AMovieSetup(string _name, Merit _merit, Guid _category)

That attribute should be specified to the class which should be registered as a DirectShow filter. Class should be inherited from any base filter class, such as BaseFilter, TransInPlaceFilter, TransformFilter, or BaseSourceFilter. In attribute you can specify filter name filter merit and filter category. If name not specified then will be used name which is specified in a filter class. Default Merit value is DoNotUse, default registering category is AmLegacyFiltersCategory. Another mandatory attribute of the filter should be the Guid that will be CLSID of the filter. How performed registering and un-registering you can see in following routines of the BaseFilter class

[ComRegisterFunctionAttribute]
public static void RegisterFunction(Type _type)

[ComUnregisterFunctionAttribute]
public static void UnregisterFunction(Type _type)

Example of filter declaration:

// Here name not specified and will be used name setted in constructor
// Category will be used by default AmLegacyFilters an merit - do not use
[ComVisible(true)]
[Guid("eeb3eef7-0592-491b-b7d4-8c65763c79c6")] // Filter CLSID
[AMovieSetup(true)] // We should register
public class NullInPlaceFilter : TransInPlaceFilter
{
public NullInPlaceFilter()
: base("CSharp Null InPlace Filter") {} // this name will be used
//..............
}

Another difference from native BaseClasses is pin access. For used Pins property and initialization is OnInitializePins routine which is abstract in BaseFilter class.

protected abstract int OnInitializePins();

Along with pins initialization you can dynamically manipulation of the pins by next helper routines:

public int AddPin(BasePin _pin)
public int RemovePin(BasePin _pin)

How registered filters looks in GraphEdit:

GraphEdit

To make your own filter just inherit it from any base filter class specify required attributes and add functionality.

Property Pages

Making filter property pages is also very simple. You should create Window Form and inherit it from BasePropertyPage class instead of Windows.Form. Form you created also require the Guid attribute as this is also COM object. BasePropertyPage class have same methods to override as base class from native BaseClasses. To assign your property page to a filter you should specify an attribute to your filter. This attribute looks:

[ComVisible(true)]
[AttributeUsage(AttributeTargets.Class)]
public class PropPageSetup : Attribute
//........

public PropPageSetup(Guid _guid)
public PropPageSetup(Guid _guid1,Guid _guid2)
public PropPageSetup(Guid _guid1,Guid _guid2,Guid _guid3)
public PropPageSetup(Type _type)
public PropPageSetup(Type _type1, Type _type2)
public PropPageSetup(Type _type1,Type _type2,Type _type3)

Attribute have couple of constructors. Example of specifying property page to a filter:

[ComVisible(true)]
[Guid("701F5A6E-CE48-4dd8-A619-3FEB42E4AC77")]
[AMovieSetup(true)]
[PropPageSetup(typeof(AudioChannelForm),typeof(AboutForm))]
public class AudioChannelFilter : TransformFilter, IAudioChannel

How property page looks for AudioChannelFilter:

AudioChannelFilter

DirectShowNET Library

BaseClasses does not uses the DirectShowNET library, it consist of couple same interfaces and structures but marshaled differently. That is necessary and very important, because in a lot of cases we need IntPtr instead of actual interface due threading issues which I described in previous article. Accessing to the actual interfaces done via my magic class (also from previous post) VTableInterface. I modify it a little and add more functionality. You also can use DirectShowNET library in your filters but keep in mind ability of ambiguous issue.

 

Last edit: yooomedia 2012-07-16