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

DirectX.Capture Class Library

Rate me:
Please Sign up or sign in to vote.
4.94/5 (227 votes)
29 Mar 2008Public Domain5 min read 4.1M   72.1K   598   1.5K
A .NET class library for capturing video and audio to AVI files.
Sample Image - DirectXCapture.jpg

Introduction

This article presents a class library for capturing audio and video to AVI files in .NET. Some of the features of this library:

  • List and select hardware devices
  • Access to common audio and video settings (e.g. frame rate, size)
  • Support audio and video compression codecs
  • Support video preview
  • Support TV tuners
  • Support crossbars and audio mixers
  • Retrieve device capabilities
  • Show property pages exposed by drivers
  • MSDN-style documentation included

Using the Code

The Capture class is the core of this library. Here is a simple example:

C#
// Remember to add a reference to DirectX.Capture.dll
using DirectX.Capture

// Capture using the first video
// and audio devices available
Capture capture = new Capture( Filters.VideoInputDevices[0],
                               Filters.AudioInputDevices[0] );

// Start capturing
capture.Start();

// Stop capturing
capture.Stop();

Remember to add a reference in your project to DirectX.Capture.dll. This DLL requires DShowNET.dll, so make sure they are both in the same directory. Once you add the reference, Visual Studio .NET should take care of the copying for you.

This example will capture video and audio using the first video and audio devices installed on the system. To capture video only, pass a null as the second parameter to the constructor.

The class is initialized to a valid temporary file in the Windows temp folder. To capture to a different file, set the Capture.Filename property before you begin capturing.

A Second Example

This next example shows how to change video and audio settings. Properties such as Capture.FrameRate and Capture.AudioSampleSize allow you to programmatically adjust the capture. Use Capture.VideoCaps and Capture.AudioCaps to determine valid values for these properties.

C#
Capture capture = new Capture( Filters.VideoInputDevices[0],
                               Filters.AudioInputDevices[1] );

capture.VideoCompressor = Filters.VideoCompressors[0];
capture.AudioCompressor = Filters.AudioCompressors[0];

capture.FrameRate = 29.997;                 // NTSC
capture.FrameSize = new Size( 640, 480 );   // 640x480
capture.AudioSamplingRate = 44100;          // 44.1 kHz
capture.AudioSampleSize = 16;               // 16-bit
capture.AudioChannels = 1;                  // Mono

capture.Filename = "C:\MyVideo.avi";

capture.Start();
...
capture.Stop();

The example above also shows the use of video and audio compressors. In most cases, you will want to use compressors. Uncompressed video can easily consume over 1GB of disk space per minute. Whenever possible, set the Capture.VideoCompressor and Capture.AudioCompressor properties as early as possible. Changing them requires the internal filter graph to be rebuilt which often causes most of the other properties to be reset to default values.

Behind the Scenes

This project uses 100% DirectShow to capture video. Once a capture is started, DirectShow spawns another thread and handles retrieving/moving all the video and audio data itself. That means you should be able to capture at the same speed and quality as an application written in C.

DirectShow is implemented as a set of COM components and we use .NET Interop to access them. The pioneering work on this was done by NETMaster with the DShowNET project. This Capture library uses DShowNET for the interop layer with only a few extensions. This is the DShowNET.dll mentioned earlier.

Sitting on top of all of this is the Capture class library. The center of any DirectShow app is the filter graph and the filter graph manager. For a good overview, see The Filter Graph and Its Components from the MSDN.

The Least Work Possible

The library tries at all times to do the least amount of work possible. The problem is: DirectShow is very flexible, but has few firm standards for driver developers and I have limited hardware to test with. As a result, the class tries to avoid doing any work that may not be necessary, hopefully avoiding potential incompatibilities in the process.

One example is video preview. You can start and stop preview with:

C#
// Start preview
capture.PreviewWindow = myPanelControl;

// Stop preview
capture.PreviewWindow = null;

Hopefully this is simple to use. Internally, DirectShow does a lot of work: add required upstream filters for WDM devices, search for preview pins, use the Overlay Manager for video ports (hardware overlays), insert SmartTee filters when a separate preview pin is not available and more. Instead of rendering the preview stream as soon as the class is created, the class waits until the PreviewWindow property is set.

For developers who don't need preview, none of this work will ever be done. That means your application is more likely to work on a wider range of hardware. For developers that do need preview, this makes it easier to locate the cause of the problem and fix it or handle it gracefully.

Performance Tips

Many of the properties on the Capture class are retrieved directly from the underlying DirectShow COM components. If you need to refer to the property repeatedly in a block of code, take a copy of the value and use your copy.

C#
// AudioSampleSize is retrieved from DirectShow each iteration
for ( int c = 0; c < 32; c++ )
{
    if ( c == capture.AudioSampleSize )
        MessageBox.Show( "Found!" );
}

// A faster solution
int x = capture.AudioSampleSize;
for ( int c = 0; c < 32; c++ )
{
    if ( c == x )
        MessageBox.Show( "Found!" );
}

Why doesn't the class simply cache the value internally? We don't know when the filter (device driver) will change this value, so we have to retrieve the value every time. This means you will always get the real value of the property.

Credits

The DirectShow interop layer was developed by NETMaster in the DShowNET project. The MDSN-style documentation was generated from the source code using nDoc.

Troubleshooting

I have tested this with an Asus v7700 (NVidia GeForce2, reference drivers) and my onboard sound card. I can't guarantee any other hardware will work. However, I expect most video capture cards and sound cards will work. You may have trouble with TV Tuner cards and DV devices (Firewire camcorders) though they should be solvable.

Try the AMCap sample from the DirectX SDK (DX9\Samples\C++\DirectShow\Bin\AMCap.exe) or Virtual VCR, a free DirectShow capture application.

This class library uses COM Interop to access the full capabilities of DirectShow, so if there is another application that can successfully use a hardware device then it should be possible to modify this class library to use the device. Please post your experiences, good or bad, in the forum below.

User Enhancements

The following enhancements have been posted to the discussion board:

Thanks to fdaupias and dauboro for their submissions. I have not had time to post a tested, updated version with these enhancements. If anyone wants to make an updated download zip, mail it to me and I will added it to this page. Keep the enhancements coming.

DirectX.Capture Wiki

A Wiki for this project is available here. This Wiki can be edited by anyone, no registration is required. I hope this Wiki will allow interested users to more easily collaborate on this project. New versions, enhancements, tips and tricks can be posted on the Wiki.

License

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


Written By
Web Developer
Canada Canada
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:Audio compressing not working Pin
almere1097-Mar-06 5:20
almere1097-Mar-06 5:20 
GeneralRe:Audio compressing not working Pin
almere1097-Mar-06 8:22
almere1097-Mar-06 8:22 
GeneralRe:Audio compressing not working Pin
sstil_lg8-Mar-06 0:49
sstil_lg8-Mar-06 0:49 
GeneralRe:Audio compressing not working Pin
almere1098-Mar-06 1:27
almere1098-Mar-06 1:27 
GeneralRe:Audio compressing not working Pin
almere1098-Mar-06 11:39
almere1098-Mar-06 11:39 
GeneralRe:Audio compressing not working Pin
almere1098-Mar-06 21:36
almere1098-Mar-06 21:36 
GeneralRe:Audio compressing not working Pin
sstil_lg9-Mar-06 0:55
sstil_lg9-Mar-06 0:55 
GeneralRe:Audio compressing not working Pin
almere1099-Mar-06 3:49
almere1099-Mar-06 3:49 
The full article on Audio file saving for DirectX.Capture can be found here:
http://www.codeproject.com/cs/media/audiosav.asp[^]

I deleted the text in this message to the information at one place. Also the original information in this message is more or less out of date.
xplain first the current implementation that implements the audio/video capturing to file. This functionality can be found in the function renderGraph in Capture.cs.

In the code the mediaSubtype is set, than SetOutputFileName() is called to add the Avi multiplexor and the file writer. Furthermore the filename is stored. The next step is to initialize the video rendering. A possible compressor filter is taken into account. After this the audio rendering is initialized. Upon a call of mediaControl.Run() in the function StartPreviewIfNeeded(), the capture graph starts.
<br />
//Original code fragment renderGraph Capture.cs<br />
<br />
Guid mediaSubType = MediaSubType.Avi;<br />
hr = captureGraphBuilder.SetOutputFileName( ref mediaSubType, Filename, out <br />
<br />
muxFilter, out fileWriterFilter );<br />
if( hr < 0 ) Marshal.ThrowExceptionForHR( hr );<br />
<br />
// Render video (video -> mux)<br />
if ( VideoDevice != null )<br />
{<br />
	// Try interleaved first, because if the device supports it,<br />
	// it's the only way to get audio as well as video<br />
	cat = PinCategory.Capture;<br />
	med = MediaType.Interleaved;<br />
	hr = captureGraphBuilder.RenderStream( ref cat, ref med, videoDeviceFilter, videoCompressorFilter, muxFilter ); <br />
	if( hr < 0 ) <br />
	{<br />
		med = MediaType.Video;<br />
		hr = captureGraphBuilder.RenderStream( ref cat, ref med, videoDeviceFilter, videoCompressorFilter, muxFilter ); <br />
		if ( hr == -2147220969 ) throw new DeviceInUseException( "Video device", hr );<br />
		if( hr < 0 ) Marshal.ThrowExceptionForHR( hr );<br />
	}<br />
}<br />
<br />
// Render audio (audio -> mux)<br />
if ( AudioDevice != null )<br />
{<br />
	cat = PinCategory.Capture;<br />
	med = MediaType.Audio;<br />
	hr = captureGraphBuilder.RenderStream( ref cat, ref med, audioDeviceFilter, audioCompressorFilter, muxFilter );<br />
	if( hr < 0 ) Marshal.ThrowExceptionForHR( hr );<br />
}<br />
<br />
isCaptureRendered = true;<br />
didSomething = true;<br />

Well now the enhancements:
The enumeration RecFileModeType is declared which specifies the possible audio/video recording file choices. The choices are Avi, Wmv and Wma. The Wma choice is default because this example is about Audio file saving. The Avi choice was added, so the original functionality is still there and could be used if needed. The Wmv is not explained in this article. It is a good learning goal to figure out how to use that.
The variable recFileMode contains the audio/video recording file mode, so this variable gets value RecFileModeType.Wma. The variable recFileMode should be accessed via RecFileMode. There is a check added that prevents changing the file mode during file capturing. This to prevent strange side effects. It also shows that other functionality could executed upon changing the value of recFileMode. A nice feature would be the change of the filename extension. This code should be put in Capture.cs, preferable in the beginning because there you can find more declarations.
<br />
/// <summary><br />
/// Recording file mode type enumerations<br />
/// </summary><br />
public enum RecFileModeType<br />
{<br />
	/// <summary> Avi video (+ audio) </summary><br />
	Avi,<br />
	/// <summary> Wmv video (+ audio) </summary><br />
	Wmv,<br />
	/// <summary> Wma audio </summary><br />
	Wma,<br />
}<br />
<br />
private RecFileModeType recFileMode = RecFileModeType.wma;<br />
<br />
/// <summary><br />
/// Recording file modes<br />
/// </summary><br />
public RecFileModeType RecFileMode<br />
{<br />
	get { return(recFileMode); }<br />
	set<br />
	{<br />
		if(this.graphState == GraphState.Capturing)<br />
		{<br />
			// Value may not be changed now<br />
			return;<br />
		}<br />
		recFileMode = value;<br />
	}<br />
}<br />

The most interesting part is the modifications of the capture specific code to renderGraph(), mentioned earlier. The major difference is that the file recording mode is taken into account. Still this example is kept simple, so for saving a specific audio and/or video file settings might be important.

Keep in mind that saving of Wma file is needed:
- No audio compressor
- Filename with filename extension .wma (or .asf)
- RecFileMode = RecFileModeType.Wma

Keep in mind that saving of Wmv file is needed:
- No video compressor
- No audio compressor
- Filename with filename extension .wmv (or .asf)
- Some video formats do not have an audio stream, video capturing will fail
in the current implementation
- RecFileMode = RecFileModeType.Wmv

Keep in mind that saving of Avi file is needed:
- Video compressor, eg DV Avi
- Filename with filename extension .avi
- RecFileMode = RecFileModeType.Avi

The code explains itselfs (I hope). There are checks added so depending on the file format specific actions can be performed. The first action is the initialization of mediaSubtype. The next action is to configure the Asf file writer. The configuration is a one-liner. The alternative coding with IWMProfile is much more complex and is surely no one-liner. Interesting is the type casting of the file writer pointer. This solution is specific for .Net, it gives an easy solution for changing the prefered audio/video profile. There is one question left, I think,
what does WMProfile_V80_64StereoAudio means? WMProfile_V80_64StereoAudio is the audio recording format I choosed as default. THere are more choices possible. For a different choice, a different value must be used. Also, if you want to save video, just select a Windows media video format.
The audio and video rendering sections looks the same, with one major difference.
Upon entering the video rendering section there is a check on the file format, for audio capturing, no video must be rendered so that section must be ignored!
<br />
// Record captured audio/video in Avi, Wmv or Wma format<br />
<br />
Guid mediaSubType; // Media sub type<br />
<br />
// Set media sub type<br />
if(RecFileMode == RecFileModeType.Avi)<br />
{<br />
	mediaSubType = MediaSubType.Avi;<br />
}<br />
else<br />
{<br />
	mediaSubType = MediaSubType.Asf;<br />
}<br />
<br />
// Intialize the Avi or Asf file writer<br />
hr = captureGraphBuilder.SetOutputFileName( ref mediaSubType, Filename, out muxFilter, out fileWriterFilter );<br />
if( hr < 0 )<br />
{<br />
	Marshal.ThrowExceptionForHR( hr );<br />
}<br />
<br />
// For Wma (and Wmv) a suitable profile must be selected. This can be done<br />
// via a property window, however the muxFilter is just created. if needed, the<br />
// property windows should show up right now!<br />
// Another solution is to configure the Asf file writer, however DShowNet does not<br />
// have the interface that is needed. Please ensure it is added. <br />
if(RecFileMode == RecFileModeType.Wma)<br />
{<br />
	IConfigAsfWriter lConfig = asfMuxFilter as IConfigAsfWriter;<br />
<br />
	// Obsolete interface?<br />
	// According to MSDN no, according to DirectShowLib yes.<br />
	// For simplicity, it will be used ...<br />
	hr = lConfig.ConfigureFilterUsingProfileGuid(WMProfile_V80_64StereoAudio);<br />
	if(hr < 0)<br />
	{<br />
		// Problems with selecting video write format<br />
		// Release resources ... (not done yet)<br />
		Marshal.ThrowExceptionForHR( hr );<br />
	}<br />
}<br />
<br />
// Render video (video -> mux) if needed or possible<br />
if((VideoDevice != null)&&(RecFileMode != RecFileModeType.Wma))<br />
{<br />
	// Try interleaved first, because if the device supports it,<br />
	// it's the only way to get audio as well as video<br />
	cat = PinCategory.Capture;<br />
	med = MediaType.Interleaved;<br />
	hr = captureGraphBuilder.RenderStream( ref cat, ref med, videoDeviceFilter, videoCompressorFilter, muxFilter ); <br />
	if( hr < 0 ) <br />
	{<br />
		med = MediaType.Video;<br />
		hr = captureGraphBuilder.RenderStream( ref cat, ref med, videoDeviceFilter, videoCompressorFilter, muxFilter ); <br />
		if ( hr == -2147220969 )<br />
		{<br />
			throw new DeviceInUseException( "Video device", hr );<br />
		}<br />
		if( hr < 0 )<br />
		{<br />
			Marshal.ThrowExceptionForHR( hr );<br />
		}<br />
	}<br />
}<br />
<br />
// Render audio (audio -> mux) if possible<br />
if ( AudioDevice != null )<br />
{<br />
	// Keep in mind that for certain Wmw formats do not have an audio stream,<br />
	// so when using this code, please ensure you use a format which supports<br />
	// audio!<br />
	cat = PinCategory.Capture;<br />
	med = MediaType.Audio;<br />
	hr = captureGraphBuilder.RenderStream( ref cat, ref med, audioDeviceFilter, audioCompressorFilter, muxFilter );<br />
	if( hr < 0 )<br />
	{<br />
		 Marshal.ThrowExceptionForHR( hr );<br />
	}<br />
}<br />
<br />
isCaptureRendered = true;<br />
didSomething = true;<br />

The function ConfigureFilterUsingProfileGuid() needs to be called for setting the proper audio format. Which formats exists? In this example there are 7 specific Windows media audio formats provided (hard coded) by means of a Guid. Each Guid stands for a specic format and profile. There are more Windows Media formats, however, those formats supports also video and there are also formats which do not support audio. The best way to get these formats is to use IWMProfile
functionality. Than you will be sure that you get formats that really exists. The formats mentioned next, should exist, but there is no real garantuee they exist really. In a future code sample, the IWMProfile method can be shown. For now, this will be though enough. Thanks to the IWMProfile interface I was able to retrieve all names that belong to a specific file format and profile. I could have typed it over. Besides of the work, the property window does not show the full line ...
The following code should be put somewhere in Capture.cs.
<br />
// Windows Media Audio 8 for Dial-up Modem (Mono, 28.8 Kbps)<br />
private static readonly Guid WMProfile_V80_288MonoAudio = new Guid("7EA3126D-E1BA-4716-89AF-F65CEE0C0C67");<br />
<br />
// Windows Media Audio 8 for Dial-up Modem (FM Radio Stereo, 28.8 Kbps)<br />
private static readonly Guid WMProfile_V80_288StereoAudio = new Guid("7E4CAB5C-35DC-45bb-A7C0-19B28070D0CC");<br />
<br />
// Windows Media Audio 8 for Dial-up Modem (32 Kbps)<br />
private static readonly Guid WMProfile_V80_32StereoAudio = new Guid("60907F9F-B352-47e5-B210-0EF1F47E9F9D");<br />
<br />
// Windows Media Audio 8 for Dial-up Modem (Near CD quality, 48 Kbps)<br />
private static readonly Guid WMProfile_V80_48StereoAudio = new Guid("5EE06BE5-492B-480a-8A8F-12F373ECF9D4");<br />
<br />
// Windows Media Audio 8 for Dial-up Modem (CD quality, 64 Kbps)<br />
private static readonly Guid WMProfile_V80_64StereoAudio = new Guid("09BB5BC4-3176-457f-8DD6-3CD919123E2D");<br />
<br />
// Windows Media Audio 8 for ISDN (Better than CD quality, 96 Kbps)<br />
private static readonly Guid WMProfile_V80_96StereoAudio = new Guid("1FC81930-61F2-436f-9D33-349F2A1C0F10");<br />
<br />
// Windows Media Audio 8 for ISDN (Better than CD quality, 128 Kbps)<br />
private static readonly Guid WMProfile_V80_128StereoAudio = new Guid("407B9450-8BDC-4ee5-88B8-6F527BD941F2");<br />

To get the code working still one interface needs to be addded because DShowNET does not support the interface of IConfigAsfWriter. DirectShowLib supports this interface, os if you use that library than no extra work is needed. There are more libaries which provides this interface, so if I forgot one, please forgive me. This interface can be added to Capture.cs or another suitable place. Keep in mind to change the naming accordingly.
<br />
[Guid("45086030-F7E4-486a-B504-826BB5792A3B"),<br />
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]<br />
public interface IConfigAsfWriter<br />
{<br />
        /// Obsolete?<br />
        [PreserveSig]<br />
        int ConfigureFilterUsingProfileId([In] int dwProfileId);<br />
<br />
        /// Obsolete?<br />
        [PreserveSig] <br />
        int GetCurrentProfileId([Out] out int pdwProfileId);<br />
<br />
        /// Obsolete?<br />
        [PreserveSig]<br />
        int ConfigureFilterUsingProfileGuid([In, MarshalAs(UnmanagedType.LPStruct)]<br />
Guid guidProfile);<br />
<br />
        [PreserveSig]<br />
        int GetCurrentProfileGuid([Out] out Guid pProfileGuid);<br />
<br />
        /// Obsolete?<br />
        [PreserveSig]<br />
        int ConfigureFilterUsingProfile([In] IntPtr pProfile);<br />
<br />
        /// Obsolete?<br />
        [PreserveSig]<br />
        int GetCurrentProfile([Out] out IntPtr ppProfile);<br />
<br />
        [PreserveSig]<br />
        int SetIndexMode([In, MarshalAs(UnmanagedType.Bool)] bool bIndexFile);<br />
<br />
        [PreserveSig]<br />
        int GetIndexMode([Out, MarshalAs(UnmanagedType.Bool)] out bool pbIndexFile);<br />
}<br />

I hope this code helps you in understanding the structure of DirectX.Capture, and I hope this I provided you an enhancement that might be useful to you. Feel free to post comments and questions.
Hans Vosman
GeneralRe:Audio compressing not working Pin
CPfx3000se10-Mar-06 3:20
CPfx3000se10-Mar-06 3:20 
GeneralRe:Audio compressing not working Pin
almere10910-Mar-06 4:10
almere10910-Mar-06 4:10 
GeneralRe:Audio compressing not working Pin
almere10911-Mar-06 2:49
almere10911-Mar-06 2:49 
GeneralRe:Audio compressing not working Pin
sstil_lg13-Mar-06 17:47
sstil_lg13-Mar-06 17:47 
GeneralRe:Audio compressing not working Pin
almere10913-Mar-06 20:25
almere10913-Mar-06 20:25 
GeneralRe:Audio compressing not working Pin
sstil_lg14-Mar-06 19:14
sstil_lg14-Mar-06 19:14 
GeneralRe:Audio compressing not working Pin
almere10915-Mar-06 0:21
almere10915-Mar-06 0:21 
GeneralRe:Audio compressing not working Pin
almere10915-Mar-06 4:50
almere10915-Mar-06 4:50 
Questiondirect3D Device?? Pin
jihoon,Park27-Feb-06 18:21
jihoon,Park27-Feb-06 18:21 
QuestionFirewire DV Cam Control Pin
cipher922-Feb-06 1:56
cipher922-Feb-06 1:56 
AnswerRe: Firewire DV Cam Control Pin
almere10923-Feb-06 1:48
almere10923-Feb-06 1:48 
General2 querys for lgs.lsg / Brian Lowe... Pin
basquille20-Feb-06 13:50
basquille20-Feb-06 13:50 
GeneralRe: 2 querys for lgs.lsg / Brian Lowe... Pin
almere10921-Feb-06 2:18
almere10921-Feb-06 2:18 
GeneralRe: 2 querys for lgs.lsg / Brian Lowe... Pin
basquille21-Feb-06 3:11
basquille21-Feb-06 3:11 
GeneralRe: 2 querys for lgs.lsg / Brian Lowe... Pin
almere10921-Feb-06 8:37
almere10921-Feb-06 8:37 
GeneralRe: 2 querys for lgs.lsg / Brian Lowe... Pin
basquille21-Feb-06 9:18
basquille21-Feb-06 9:18 
GeneralRe: 2 querys for lgs.lsg / Brian Lowe... Pin
almere10921-Feb-06 10:23
almere10921-Feb-06 10:23 

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.