Now, with Dowitchers, we can write filters in managed code. And this tutorial starts where the previous technology left us. So we are now "In the beginning".
We'll start with a few "using" directives.
using System;
using System.IO;
using System.Collections.Generic;
// Dowitchers plumbing and classes for filter
using Dowitchers;
using Dowitchers.Baseclasses;
Now we are ready to start writing our filter. With Dowitchers,
a filter is derived from the class Dowitchers.Baseclasses.Filter,
and in our case, we have three fields for the input and output pin and
the mediatype.
public class DoEzRgbFilter : Filter
{
private InputPin _inputPin;
private IOutputPin _outputPin;
private IMediaType _videoType;
Then we can write our constructor. First, we call the parent constructor
with the name that we want to give to the filter. Then we create an
InputPin object and hook the CheckMediaTypeEvent that
decides which mediatype we accept for the filter. Finally, we declare an
object of type "DoEzRgbOutputPin" for our output pin since we have to
override the OutputMediaTypes property of the abstract class
OutputPin. So we'll have to write the code the class "DoEzRgbOutputPin"
later.
public DoEzRgbFilter()
: base("DoEzRgb")
{
_inputPin = new InputPin("Input", this);
_inputPin.CheckMediaTypeEvent += new CheckMediaTypeEventHandler(_checkMediaHandler);
_outputPin = new DoEzRgbOutputPin("Output", this);
}
Now, we are going to write the "_checkMediaHandler" method. In order to
keep the filter simple, we'll just accept the MediaType.RGB32 type
for input. So we can write "_checkMediaHandler" as follows:
bool _checkMediaHandler(IInputPin p, IMediaType mt)
{
IVideoType vt = mt as IVideoType;
if (vt == null || vt.Id != MediaType.RGB32)
return false;
else
{
// return true only for rbg32 and store the video type away
_videoType = mt;
return true;
}
}
We might as well write the code for the class "DoEzRgbOutputPin" now:
public class DoEzRgbOutputPin : OutputPin
{
public DoEzRgbOutputPin(string name, IFilter owner)
: base(name, owner)
{
}
public override IEnumerable OutputMediaTypes
{
get
{
DoEzRgbFilter f = Owner as DoEzRgbFilter;
// return the video type of the input pin if it's connected
if (f != null && f._inputPin.Connected != null)
yield return f._videoType;
else
yield return null;
}
}
}
First, we derive it from the class OutputPin and the constructor
just calls the parent constructor. Then we override the OutputMediaTypes
property of the parent class. The code uses the .Net 2.0 ability to turn an object
into an enumerator by using the yield keyword. If the input pin is
connected we return the "_videoType" object that was stored while doing checking
the media type on the input pin.The filter actual processing is done in the Receive method:
unsafe public override void Receive(ISampleStream sender, ISample sample)
{
base.Receive(sender, sample);
IMediaSample s = sample as IMediaSample;
if (s != null)
{
// rgb32 contains b g r a bytes
// just overwrite the b and g bytes
int length = s.Data.Length;
fixed (byte* bptr = s.Data)
{
for (int i = 0; i < length; i += 4)
// if you want to keep only the green component
// replace the 2nd index by i+2
// if you want to keep only the blue component
// replace the 1st index by i+2
bptr[i] = bptr[i+1] = (byte)0;
}
}
// pass the sample along
_outputPin.Receive(this, sample);
}
We use a pointer to the bytes of the frame, so we need to declare the method
unsafe. Then we call the parent method (which does nothing in this case).
After that, we cast the "sample" argument to an IMediaSample, if the cast
is not valid, we simply pass the sample along to the output pin (this is the case
when we seek to a new location and the first sample is a NewSegmentSample).
If the cast is valid, we grab the sample length and we overwrite the first two
bytes of every word (i.e. the blue and green components). We do this inside a
fixed statement to ensure that the GC thread will not try to move the
sample around while we are looping. And we use a pointer to byte to avoid any
managed overhead.
The ability to choose the color component to show should be left to a custom
interface. The custom interface and the code for adding the filter to a library
will be left for another tutorial.
To complete the filter, we need to implement the Pins property:
public override IEnumerable Pins
{
get
{
yield return _inputPin;
yield return _outputPin;
}
}
} // closing brace for the class
We just return the input and output pins as an enumerator.