Managed DirectShow |
The goal of the Managed DirectShow Project is to provide you with everything you need to develop media client and server software using .NET - for free. The intention of the project is not to supply a fully functional Media Server and Client system. Rather, it is to provide the basic building blocks that would make implementing such a system as easy as possible.
I had a dream. A dream about a media server with a set of basic features. I wanted it to be able to display TV, to pause and rewind live TV, to record TV, provide a programming guide, to schedule recordings based on the programming guide (and to be able to start the scheduled recording even if the server is suspended), and I wanted to watch live TV and recordings on devices that was connected wirelessly to the server. And I did not want to spend a fortune on it. I bought the hardware and downloaded trials of various media center software. The initial impression left by the trials was very positive; however, it quickly turns out that commercial products all seem to lack at least one of the basic features and almost all have rather nasty licensing. SnapStream BeyondTV has an excellent UI, but no support for networking, I couldn't get it to resume the server to start recording, the recompression functionality failed more often than not, and it had a bunch of other minor issues. Sage TV has good networking support, but has a poor UI and another bunch of features was missing, and it's Java-based! ;-( I never tried Microsoft Windows Media Center Edition (there was no trial version; in fact, at the time, it was only available to OEMs). None of these products are free (some are not expensive, but licensing requires you to e.g. shell out every time you wish to watch TV on a new device) and none offer programming guide (I live outside North America).[1]
Somehow, a path - which is now long lost in the sands of time - lead me to the DirectShow API. The brilliant graph editor application in the SDK soon let me realize that DirectShow is a virtual fountain of free (albeit as in beer and on Windows only) functionality, including support for any media hardware, pluggable encoders and decoders, pausing, rewinding, etc. live streams, and samples on how to broadcast streams on networks; essentially all the features I wanted. I quickly had some rough code up and running
However, it turns out that the DirectShow API is horribly messy and tricky. To do very basic things takes many hundreds of lines of code.
And, frankly, much (all) of the DirectShow sample code (both that supplied with the SDK and that embedded in the documentation) as well as the HRESULT hres = ...;
COM error "handling" model made me, well, uncomfortable. DirectShow it self is not much better.
Take for instance SetKey().
Eventhough it's all COM, there is very little object orientation in the API (seriously, there's 30 lines of code where one ought to be enough:
sink.Directory = @"C:\MyDirectory";).
If I were to do any least bit serious media application development work with this library, I needed to add a (couple of) layers on top of the raw DirectShow API providing easy-to-use intuitive object-oriented access to DirectShow. The result of the effort on these layers is this Managed DirectShow library.
I am aware that alternative open source projects already exist (such as DirectShow.NET and directshow.net library) that let you use DirectShow from managed code; however, aside from the fact that they don't work reliably, are not maintained, they provide only the thinnest possible wrapper around the DirectShow COM interfaces, exposing the raw COM DirectShow API, thus resulting in non-.NETish feel to the API, not relieving the cumbersome API. Some even returns the HRESULT as an int. Yuck! [1]
All right, enough ranting. Features provided by the library include
Everything on this site is Copyright © 2005-2007 Kristoffer Vinther, All rights reserved. All source code is released here under the GNU General Public License. Any binaries are distributed as-is. There is absolutely no warranty and no one can be held responsible for any damages caused by them, directly or indirectly. No binary released here can be used for any commercial purpose in any way without my (Kristoffer Vinther's) explicit consent.
Currently, the source code for the Managed DirectShow library is not available. That may well change in the future. The source of any specific usable media application is not available either. This will likely not change in the future.
What are available are the Managed DirectShow assemblies. Download them here and read the Manual section below to see how to use them.
Back to reality. There are surely many limitations, known and unknown.
Due to limited resources, the code has only been tested on a Hauppauge PVR250 TV Tuner card in a machine running Windows XP SP2 with the .NET Framework v. 2.0 and WinDVD installed. Its very likely they will only work with this hardware and software configuration, but with any luck it may also work on other configurations. I've been told it also works with WinTV USB2. Please let me know if you have any luck with another system. Also, feel free to contact me if you have improvement suggestions.
To install the assemblies, simply unzip them to a location where you can find them again and register the dsunicast.ax file using regsvr32.exe utility.
The programming reference is available here and aswers to a collection of frequently asked questions are available here. Start out in the KvTv.DirectShow.Components namespace. The programming reference was build using the latest CTP of Sandcastle. This makes the documentation limited in at least two ways: alpha assemblies and alpha document generator. However, it should be complete enough for you to start exploring the possibilities. The next section will provide a brief technical introduction, which I hope will prove useful as well.
As described in the Source section, no source is available. However, this section provides five short tutorials guiding you through creating simple essential media applications on your own. They all assume you have at least Visual C# 2005 Express installed.
In each of the tutorials, start by creating a new Windows Application project and
add a reference to the KvTv.DirectShow and KvTv.DirectShow.Components assemblies. If in doubt about which video window control to use, try the generic
VideoWindowSink control, which is both a control and a stream sink.
From the Toolbox, add the following to the form: FileSource, VideoWindowPullSink, and VideoMixingWindowless.
Set the Source property on the VideoWindowPullSink to the FileSource and the Window property to the VideoMixingWindowless.
Set the DockStyle property in the VideoMixingWindowless to Fill.
To select the media file to play, add the following method to the form:
/// <summary>Displays an <see cref="OpenFileDialog"/> and starts rendering the selected file.</summary>
/// <param name="e">The event arguments.</param>
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
OpenFileDialog dialog = new OpenFileDialog();
dialog.Title = "Please select file to play";
if( dialog.ShowDialog(this) != DialogResult.Cancel )
{
this.fileSource1.FileName = dialog.FileName;
try
{
this.videoWindowPullSink1.Render();
this.videoWindowPullSink1.Run();
}
catch( KvTv.DirectShow.Components.GraphRenderException )
{
MessageBox.Show(this, "The selected file could not be rendered. Please make sure you have all the necessary codecs installed and read access to the file.", "Error rendering file", MessageBoxButtons.OK, MessageBoxIcon.Error);
Close();
return;
}
}
else
{
Close();
}
}
To control media playback, set the KeyPreview property on the form to true and add the following code to the form class:
private bool playing = true;
/// <summary>Controls playing, pausing, rewinding and fast forwarding based on the key pressed.</summary>
/// <param name="e">The event arguments.</param>
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
switch( e.KeyCode )
{
case Keys.OemMinus:
this.fileSource1.Rewind();
break;
case Keys.Oemplus:
this.fileSource1.FastForward();
break;
case Keys.Space:
if( this.playing )
{
this.fileSource1.Pause();
}
else
{
this.fileSource1.Play();
}
this.playing = !this.playing;
break;
}
}
See also: MediaForm, PlayerStrip, Duration, and Position.
From the Toolbox, add the following to the form: TVTunerSource, VideoWindowPushSink (or a VideoWindowPullSink if your tuner card does not do hardware MPEG2 encoding!), and BasicVideoWindow.
Set the Source property on the VideoWindowPushSink to the TVTunerSource and the Window property to the BasicVideoWindow.
Set the DockStyle property in the BasicVideoWindow to Fill.
Add the following method to the form class:
/// <summary>Starts rendering and tunes to channel 67.</summary>
/// <param name="e">The event arguments.</param>
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
try
{
this.videoWindowPushSink1.Render();
this.videoWindowPushSink1.Run();
}
catch( KvTv.DirectShow.Components.GraphRenderException ex )
{
MessageBox.Show(this, "Could not render the TV stream: " + ex.Message, "Error rendering", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
// Tune to your favourite channel:
this.tvTunerSource1.Tuner.AutoTune(67);
}
The TVTunerSource needs Capture, Crossbar, TV Audio, and TV Video devices.
They can be specified using properties on the component. By default the properties are null references,
which directs the component to use first and best device it can find in each category. This meens that if you only have one device of each type in your system, the defaults will work fine.
However, if you have multiple devices, you may need to specify the ones to use manually. For example, I have a sound card with a capture device in my system, confusing the TVTunerSource when
selecting a capture device. To resolve this, I set the Capture property explicitly to the first capture device in the system in the OnLoad method, like this (also, I like channel 67):
/// <summary>Specifies the capture device to use, starts rendering and tunes to channel 67.</summary>
/// <param name="e">The event arguments.</param>
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
// Explictly select the first capture device:
foreach( KvTv.DirectShow.Moniker moniker in KvTv.DirectShow.Components.TVTunerSource.CaptureDevices )
{
this.tvTunerSource1.Capture = moniker;
break;
}
try
{
this.videoWindowPushSink1.Render();
this.videoWindowPushSink1.Run();
}
catch( KvTv.DirectShow.Components.GraphRenderException ex )
{
MessageBox.Show(this, "Could not render the TV stream: " + ex.Message, "Error rendering", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
// Tune to your favourite channel:
this.tvTunerSource1.Tuner.AutoTune(67);
}
See also: MediaForm, PlayerStrip, Duration, and Position.
From the Toolbox, add the following to the form: TVTunerSource, VideoWindowPullSink, StreamBuffer, and BasicVideoWindow.
Set the Source property on the VideoWindowPullSink to the StreamBuffer, the Source property on the StreamBuffer to the TVTunerSource, and the Window property to the BasicVideoWindow.
Set the DockStyle property in the BasicVideoWindow to Fill.
Add the same OnLoad() code as the Television tutorial to the form. To pause live TV, add the following code to the form class:
bool playing = true;
/// <summary>Controls playing, pausing, rewinding and fast forwarding based on the key pressed.</summary>
/// <param name="e">The event arguments.</param>
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
switch( e.KeyCode )
{
case Keys.Space:
if( this.playing )
{
this.streamBuffer1.Pause();
}
else
{
this.streamBuffer1.Play();
}
this.playing = !this.playing;
break;
}
}
Note that you can also rewind and fast forward the stream buffer.
See also: MediaForm, PlayerStrip, Duration, and Position.
From the Toolbox, add the following to the form: CameraSource, AsfRecorder, and VideoWindowSink.
Set the Source property on the VideoWindowSink to the AsfRecorder, the Source property on the AsfRecorder to the CameraSource.
Set the DockStyle property in the VideoWindowSink to Fill. Add the code to start the rendering:
/// <summary>Renders and runs the graph.</summary>
/// <param name="e">The event arguments.</param>
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.videoWindowSink1.Render();
this.videoWindowSink1.Run();
}
Add record keys, having set KeyPreview to true on the form:
private bool recording;
/// <summary>Controls recording based on the key pressed.</summary>
/// <param name="e">The event arguments.</param>
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
switch( e.KeyCode )
{
case Keys.Space:
if( this.recording )
{
SaveFileDialog fileDialog = new SaveFileDialog();
if( fileDialog.ShowDialog(this) == DialogResult.OK )
{
this.asfRecorder1.Record();
this.recording = true;
}
}
else
{
this.asfRecorder1.StopRecording();
this.recording = false;
}
break;
}
}
Before you do any media networking, please do read this excellent blog entry from the Windows Network Development team. All the points highlighted there are relevant for any media center software.
To create a server, you may use a Console Application rather than a Windows Application. If you are using the Visual Studio Standard edition (or better), I highly recommend starting out with a Windows Service Application; that way you can create the server using the designer as in the previous tutorials. I'm not, so let's do it manually. Start out by adding the references and appropriate using directives:
using System.Net; using KvTv.DirectShow.Components;
The program is going to run in three steps:
static void Main(string[] args)
{
try
{
Build();
Run();
Serve();
Console.Out.WriteLine("Client connected.");
}
catch( Exception ex )
{
Console.Error.WriteLine("Fatal server error:");
Console.Error.WriteLine(ex);
}
Console.Error.WriteLine("Press enter to exit...");
Console.ReadLine();
}
Now, let's build the graph. We're going to need a TVTunerSource and a UnicastSink:
private static TVTunerSource tv;
private static UnicastSink unicast;
private static void Build()
{
tv = new TVTunerSource();
unicast = new UnicastSink();
unicast.Source = tv;
}
Running it seems familiar (don't forget to select the devices for the tv if needed):
private static void Run()
{
unicast.Render();
unicast.Run();
}
And finally, serving the stream is just as simple:
private static void Serve()
{
unicast.EndPoint = new IPEndPoint(IPAddress.Any, 40100);
unicast.Accept();
}
See also: MultiwayUnicastSink.
For the client we're back in a Windows Application: from the Toolbox, add a UnicastSource and a VideoWindowSink.
Set the source of the VideoWindowSink to the UnicastSource and its DockStyle to Fill.
If the client is not running on the same host as the server, specify the server end point using the ProgramStream property on the UnicastSource.
As it happens, the default port on the UnicastSource is 40100, the port we specified in the server. If you want to use a different port, change the server and the ProgramStream property on the UnicastSource.
Now, run the graph when the form is loaded and you are done:
/// <summary>Renders and runs the graph.</summary>
/// <param name="e">The event arguments.</param>
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
this.videoWindowSink1.Render();
this.videoWindowSink1.Run();
}
TBD (the functionality is there, so feel free to go exploring)
TBD
That's all there is from the tutorial department. I hope they've been helpful. Here's a screenshot of what can be accomplished with little extra effort (based on a local TV guide):
Good luck on your way onward.
Last updated: May 17th, 2007
See here for latest revision.
[1] To be fair, these observations were made back in early 2005. Reality may well have changed since then. However, I still do not feel compelled to try any other (commercial or otherwise) solutions again.