A while back I was tasked with creating an application that would allow a user to take pictures with a webcam. One would assume this would be a relatively easy task but as most things go in the developer world, it’s only easy once you figure out how to do it.

Lets get to figuring it out. If you fired up your browser of choice and went to google you probably stumbled across a lovely Stack Overflow post that recommended the DirectShow driver. You would then find a .Net wrapper library in NuGet called directshow.net. Now if you happened to get directshow.net from sourceforge you can run the sample applications it provided. All you need to do is fix the references. To fix the directshowlib-2005 reference, you will find the DLL located in the \DirectShowLibV2-1\lib folder. This will enable it to run with no problems.

I started by sketching out code in LINQpad (the .NET developer’s playground). I took the example code from the directshow.net sample and flattened it out into LINQpad. Before you can flatten it you will need to add references to the directshow.net library.

  1. Press F4 to bring up the “Add References” dialog.
  2. Click the ‘Add NuGet’ button and search for DirectShowLib. The description should start with “A port of DirectShowLib to .NET 4.5.”
  3. Click the ‘Add…’ button
  4. Search for System.dll and System.Runtime.InteropServices.dll. You need these for the InteropServivces that Marshal calls.

After doing the above you need to fix the hard coded path (location video will be written to) and source device (the webcam that is being used). For my testing I had 2 webcams available: one built-in and one external. I chose to use the external webcam so I used 1 and I have it recording to c:\Temp\test.wmv.

Note: The source number can and will change after every boot if you have different webcam’s connected. You can use the webcam name to look it up via code to make sure you always find the correct one.

Test the program by clicking the play button or pressing F5. After a second or two the program should be done with no errors and you should have a video where ever you told it to write the file. Congratulations! You have successfully interfaced to a webcam and captured something from it using C#.

If you use the CapWMV sample you should end up with code like this:

void Main()
{
 var cam = new Capture(1, "c:\\temp\\test.wmv");
 // Start capturing
 cam.Start();
 var run = true;
 for (var i = 0; i < 1000000; i++)
 {
  System.Console.Write(".");
 }
 // Pause the recording
 cam.Pause();
 // Close it down
 cam.Dispose();
}
/// <summary> Summary description for MainForm. </summary>
public class Capture : IDisposable
{
 #region Member variables
 /// <summary> graph builder interface. </summary>
 private IFilterGraph2 m_FilterGraph = null;
 IMediaControl m_mediaCtrl = null;
 /// <summary> Set by async routine when it captures an image </summary>
 private bool m_bRunning = false;
 #endregion
 /// <summary> release everything. </summary>
 public void Dispose()
 {
  GC.SuppressFinalize(this);
  CloseInterfaces();
 }
 ~Capture()
 {
  Dispose();
 }
 /// <summary>
 /// Create capture object
 /// </summary>
 /// <param name="iDeviceNum">Zero based index of capture device
 /// <param name="szFileName">Output ASF file name
 public Capture(int iDeviceNum, string szOutputFileName)
 {
  DsDevice[] capDevices;
  // Get the collection of video devices
  capDevices = DsDevice.GetDevicesOfCat(FilterCategory.VideoInputDevice);
  if (iDeviceNum + 1 > capDevices.Length)
  {
   throw new Exception("No video capture devices found at that index!");
  }
  try
  {
   // Set up the capture graph
   SetupGraph(capDevices[iDeviceNum], szOutputFileName);
   m_bRunning = false;
  }
  catch
  {
   Dispose();
   throw;
  }
 }
 // Start the capture graph
 public void Start()
 {
  if (!m_bRunning)
  {
   int hr = m_mediaCtrl.Run();
   Marshal.ThrowExceptionForHR(hr);
   m_bRunning = true;
  }
 }
 // Pause the capture graph.
 // Running the graph takes up a lot of resources.  Pause it when it
 // isn't needed.
 public void Pause()
 {
  if (m_bRunning)
  {
   IMediaControl mediaCtrl = m_FilterGraph as IMediaControl;
   int hr = mediaCtrl.Pause();
   Marshal.ThrowExceptionForHR(hr);
   m_bRunning = false;
  }
 }
 /// <summary> build the capture graph. </summary>
 private void SetupGraph(DsDevice dev, string szOutputFileName)
 {
  int hr;
  IBaseFilter capFilter = null;
  IBaseFilter asfWriter = null;
  ICaptureGraphBuilder2 capGraph = null;
  // Get the graphbuilder object
  m_FilterGraph = (IFilterGraph2)new FilterGraph();
  try
  {
   // Get the ICaptureGraphBuilder2
   capGraph = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();
   // Start building the graph
   hr = capGraph.SetFiltergraph(m_FilterGraph);
   Marshal.ThrowExceptionForHR(hr);
   // Add the capture device to the graph
   hr = m_FilterGraph.AddSourceFilterForMoniker(dev.Mon, null, dev.Name, out capFilter);
   Marshal.ThrowExceptionForHR(hr);
   asfWriter = ConfigAsf(capGraph, szOutputFileName);
   hr = capGraph.RenderStream(null, null, capFilter, null, asfWriter);
   Marshal.ThrowExceptionForHR(hr);
   m_mediaCtrl = m_FilterGraph as IMediaControl;
  }
  finally
  {
   if (capFilter != null)
   {
    Marshal.ReleaseComObject(capFilter);
    capFilter = null;
   }
   if (asfWriter != null)
   {
    Marshal.ReleaseComObject(asfWriter);
    asfWriter = null;
   }
   if (capGraph != null)
   {
    Marshal.ReleaseComObject(capGraph);
    capGraph = null;
   }
  }
 }
 private IBaseFilter ConfigAsf(ICaptureGraphBuilder2 capGraph, string szOutputFileName)
 {
  IFileSinkFilter pTmpSink = null;
  IBaseFilter asfWriter = null;
  int hr = capGraph.SetOutputFileName(MediaSubType.Asf, szOutputFileName, out asfWriter, out pTmpSink);
  Marshal.ThrowExceptionForHR(hr);
  try
  {
   IConfigAsfWriter lConfig = asfWriter as IConfigAsfWriter;
   // Windows Media Video 8 for Dial-up Modem (No audio, 56 Kbps)
   // READ THE README for info about using guids
   Guid cat = new Guid(0x6E2A6955, 0x81DF, 0x4943, 0xBA, 0x50, 0x68, 0xA9, 0x86, 0xA7, 0x08, 0xF6);
   hr = lConfig.ConfigureFilterUsingProfileGuid(cat);
   Marshal.ThrowExceptionForHR(hr);
  }
  finally
  {
   Marshal.ReleaseComObject(pTmpSink);
  }
  return asfWriter;
 }
 /// <summary> Shut down capture </summary>
 private void CloseInterfaces()
 {
  int hr;
  try
  {
   if (m_mediaCtrl != null)
   {
    // Stop the graph
    hr = m_mediaCtrl.Stop();
    m_bRunning = false;
   }
  }
  catch
  {
  }
  if (m_FilterGraph != null)
  {
   Marshal.ReleaseComObject(m_FilterGraph);
   m_FilterGraph = null;
  }
 }
}

We’re not done yet though. We still need to add it to a WPF application.

  1. Create a WPF Application for framework 4.5 or up. I’m assuming you know how to do that. If not well um… Click Here
  2. Add the NuGet reference to DirectShowLib (the same one we add in the LINQPad example). Right click on the Project References and select “Manage NuGet Packages”.
  3. Assuming you want to stream video to your WPF app, you need to add 2 more things to the references: WindowsFormsIntergration, and System.Windows.Forms.

Now lets open up the XAML and add the windows forms integration piece.

<WindowsFormsHost Name="WindowsFormsHost1" Width="640" Height="480" />

You need to create Windows Forms Control. I used the PictureBox control and then add it to the Windows Forms Host Control (see code below).

var pictureBox2 = new PictureBox();
this.windowsFormsHost1.Child = pictureBox2;

At this point if you have not already downloaded the samples for Directshow.net lib, go ahead and do so.

  1. Extract the examples and navigate to \Samples\Capture\DxSnap
  2. Copy the Capture.cs file to your project.

You will notice that the constructor has changed. You still pass in the device number you want but instead of passing in the output file you want to use you pass in the width, height, bpp and a Windows Forms Control. The Width and Height is the size the video will be captured in, not necessarily the size of the control. The BPP is the Bits Per Pixel the video will be captured in (this is specific to your webcam so you may need to play around and find the right number). The Control is the control we made. I put the construction of the Capture object in the WPF window_load function. My code for creating the Capture object is:

var capture = new Capture(1, 640, 480, 24, pictureBox2);

Now when you run your app you should see the video that your webcam is capturing ^5.

There are some down falls with doing it this way:

  1. The Windows Form Host is ALWAYS on top.
  2. The size of the Control is the size of Windows Form Host. You can’t set the width and height of the Windows Form Host and expect that the video will show in that size. You have to set the width and height of the Control.

With this blog and the examples, you should be able to set up your webcam in a snap.


    • Not sure since I don’t know what your code looks like but here is my xaml and c# for the little sample app I created.

      XAML

      <Window
      x:Class="WebCam.MainWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:local="clr-namespace:WebCam"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      Loaded="MainWindow_OnLoaded"
      Title="MainWindow"
      Width="525"
      Height="350"
      mc:Ignorable="d">
      <Grid>
      <WindowsFormsHost
      Name="WindowsFormsHost1"
      Width="640"
      Height="480" />
      </Grid>
      </Window>

      C#

      public partial class MainWindow : Window
      {
      public MainWindow()
      {
      InitializeComponent();

      this.WindowsFormsHost1.Child = new PictureBox(); ;
      }

      private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
      {
      var capture = new Capture(0, 640, 480, 24, this.WindowsFormsHost1.Child);
      }
      }

      Hope this helps!

Leave a Reply

Your email address will not be published. Required fields are marked *