Webcam control with WPF or how to create high framerate player with DirectShow by using InteropBitmap in WPF application

Did you ever see, that MediaElement “eats” about 30% of CPU while playing movie in WPF? Did you thought, that you  can display live camera capture in WPF with 60 fps full screen (I have really high resolution 1920×1200) and 2% of  CPU? You did not? Let’s see how it can be done. Today we’ll create simple WebCam player control that can show you live video capturing with high frame rate. In order to do it, we’ll use DirectShow, WPF and make them work together.

  image

You, probably do not believe me. Let’s start. In order to build this application, we need to make DirectDraw working in C# managed code. We can use DirectShow.NET, but this time we’ll do it manually. Why? because I love to do things manually. So let’s understand what we need? Actually, not very much: one Sample Grabber (ISampleGrabber) and one Device input filter(IBaseFilter). Both we should connvert with Graph Builder (IGraphBuilder) and point to some grabber implementation (ISampleGrabberCB). Also, we do not want DirectShow to render video for use, thus we’ll send it’s default Video Window (IVideoWindow) to null with no AutoShow and then run the controller (IMediaControl). Do you tired enough to lost me? Let’s see the code. One Filter graph with one Device Filter and one Sample grabber.

graph = Activator.CreateInstance(Type.GetTypeFromCLSID(FilterGraph)) as IGraphBuilder;
sourceObject = FilterInfo.CreateFilter(deviceMoniker);

grabber = Activator.CreateInstance(Type.GetTypeFromCLSID(SampleGrabber)) as ISampleGrabber;
grabberObject = grabber as IBaseFilter;

graph.AddFilter(sourceObject, "source");
graph.AddFilter(grabberObject, "grabber");

Set media type for our grabber

using (AMMediaType mediaType = new AMMediaType())
                {
                    mediaType.MajorType = MediaTypes.Video;
                    mediaType.SubType = MediaSubTypes.RGB32;
                    grabber.SetMediaType(mediaType);

And then connect device filter to out pin and grabber to in pin. Then get capabilities of video received (thiss stuff come from your web camera manufacturer)

if (graph.Connect(sourceObject.GetPin(PinDirection.Output, 0), grabberObject.GetPin(PinDirection.Input, 0)) >= 0)
                    {
                        if (grabber.GetConnectedMediaType(mediaType) == 0)
                        {
                            VideoInfoHeader header = (VideoInfoHeader)Marshal.PtrToStructure(mediaType.FormatPtr, typeof(VideoInfoHeader));
                            capGrabber.Width = header.BmiHeader.Width;
                            capGrabber.Height = header.BmiHeader.Height;
                        }
                    }

Out pin to grabber without buffering and callback to grabber object (this one will get all images from our source).

graph.Render(grabberObject.GetPin(PinDirection.Output, 0));
grabber.SetBufferSamples(false);
grabber.SetOneShot(false);
grabber.SetCallback(capGrabber, 1);

Dump output window

IVideoWindow wnd = (IVideoWindow)graph;
wnd.put_AutoShow(false);
wnd = null;

And run the controller

control = (IMediaControl)graph;
control.Run();

We done. Now our video is captured and can be accessed from BufferCB method of ISampleGrabberCB. Next step is to do WPF related stuff

First of all, we’ll use InteropBitmap. This one will provide us with real performance bust. So, one our DirectShow graph is ready and we know result image capabilities, we can create memory section and map it in order to provide ISampleGrabberCB with place to put images. This will be always the same pointer, so all we have to do is to .Invalidate interop image.

if (capGrabber.Width != default(int) && capGrabber.Height != default(int))
                {

                    uint pcount = (uint)(capGrabber.Width * capGrabber.Height * PixelFormats.Bgr32.BitsPerPixel / 8);
                    section = CreateFileMapping(new IntPtr(-1), IntPtr.Zero, 0×04, 0, pcount, null);
                    map = MapViewOfFile(section, 0xF001F, 0, 0, pcount);
                    BitmapSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromMemorySection(section, capGrabber.Width, capGrabber.Height, PixelFormats.Bgr32,
                        capGrabber.Width * PixelFormats.Bgr32.BitsPerPixel / 8, 0) as InteropBitmap;
                    capGrabber.Map = map;
                    if (OnNewBitmapReady != null)
                        OnNewBitmapReady(this, null);
                }

Now in capGrabber (ISampleGrabberCB) we’ll copy buffer, comes from our webcam to the mapped location for WPF usage

public int BufferCB(double sampleTime, IntPtr buffer, int bufferLen)
        {
            if (Map != IntPtr.Zero)
            {
                CopyMemory(Map, buffer, bufferLen);
                OnNewFrameArrived();
            }
            return 0;
        }

All we have to do is to call InteropBitmap.Invalidate() each frame to reread the image bytes from the mapped section.

if (BitmapSource != null)
                    {
                        BitmapSource.Invalidate();

How do display all this stuff? Simple – subclass from Image and set it’s Source property with the interop bitmap.

public class CapPlayer : Image,IDisposable
    {

void _device_OnNewBitmapReady(object sender, EventArgs e)
        {
            this.Source = _device.BitmapSource;
        }

Now, the usage from XAML is really simple

<l:CapPlayer x:Name="player"/>

We done :) As always, download full source code for this article

…and be good people and don’t tell anymore, that WPF performance in terms of imaging is sucks :)

P.S. small ‘r’ if you have more, then one WebCam connected. Inside CapDevice class there is member, public static FilterInfo[] DeviceMonikes, that provides you with all DirectShow devices installed. So, the only thing you should do in order to change the device is to set deviceMoniker = DeviceMonikes[0].MonikerString; with the moniker of your device. This sample works with first one.

  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • DotNetKicks
  • DZone
  • Live
  • Reddit
  • TwitThis
  • E-mail this story to a friend!
  • Slashdot
  • StumbleUpon

You may also be interested with:

  1. WPF DataGrid CTP is here. It’s also open source!
  2. Nifty time savers for WPF development
  3. The new version of WPF Performance Profiling Tool is available for download
  4. Quick Silverlight (and WPF) tip: How to write program without XAML
  5. Capturing and streaming sound by using DirectSound with C#

23 Responses to “Webcam control with WPF or how to create high framerate player with DirectShow by using InteropBitmap in WPF application”

  1. Rich Says:

    Very impressive.  +1 for changing the resolution.  I’ve been searching for a solution and could not find any that I understand.

  2. Geert van Horrik Says:

    I have extended your CapPlayer class a little bit by adding 2 properties:

    1) Rotation

    2) CurrentBitmap

    You can find a blog entry about it here:

    blog.catenalogic.com/…/Retrieving-snapshots-from-a-webcam.aspx

  3. Rodrigo Says:

    I’m starting in WPF. There is some method in the classes of this project that capture one frame of the CapPlayer control to put in one image control, for example?

  4. Matt Says:

    How would you specify the resolution you want the camera to produce?  Sorry about the newbie question.

  5. Ivan Says:

    What about webcam options? Is it possible to change them?

  6. Webcam Control with WPF « Roman’s Blog Says:

    Pingback from  Webcam Control with WPF  &laquo; Roman&#8217;s Blog

  7. C.S. Finch Says:

    Hi, I noticed someone posted  a question about the video being upside down, and I must admit I have the same problem, however, I fixed it with an easy fix.

    2 options.

    First option is you can go into Expression Blend 2.0, and just rotate the video player 180 degrees.

    if you don’t have Expression Blend 2.0 you can do the following

    replace the following <1:CapPlayer x:Name="player"/>

    with the following code

           <l:CapPlayer x:Name="player" RenderTransformOrigin="0.5,0.5">

            <l:CapPlayer.RenderTransform>

            <TransformGroup>

            <ScaleTransform ScaleX="1" ScaleY="1"/>

            <SkewTransform AngleX="0" AngleY="0"/>

            <RotateTransform Angle="180.294"/>

            <TranslateTransform X="0" Y="0"/>

            </TransformGroup>

            </l:CapPlayer.RenderTransform>

           </l:CapPlayer>

    Hope it helped.

  8. Brandon Says:

    How do you stop the feed of the images? I called the Stop() method located _device.Stop() and made a public method to access. When I call this it will stop but it takes a crazy long time and is very random, does it not dispose properly?

  9. Mike Says:

    Wow, excellent article/code, thanks!

    I’m trying to get this to work with an IP Camera (Linksys WVC54GC) but can’t figure out how to get the FilterInfo for the ip camera (device enum won’t work since it’s not directly connected to my pc).  I can get IFilterInfo for the ip cam by going through FilgraphManager instead, but connect fails when I try to use this filter.  Any ideas on how I can get this to work with an IP Camera?  Any help would be greatly appreciated.

    Thanks!

    Mike.

  10. Ted Says:

    Excellent article Tamir!

    I was playing around with it using a firewire-video as source, and it worked just fine (except for beeing flipped upside down, but that was easily corrected with a a scale transform), BUT I have a question though, is there any route I could take to add de-interlacing of the video as well? Do you know? Thanks!

    Cheers!

    -Ted

  11. Tamir Khason Says:

    cindex, I do not understand and cannot repro the problem. It looks like a problem with your webcam software or drivers

  12. cindex Says:

    Please, answer my problem.

    T.T

    Inspite of "player.Dispose();"

    webcam is not released.

    Because of that, new window do not load webcam.

    Help,me

  13. cindex Says:

    …hmm

    Lamp turned off when debugging stop, not when application is closed.

  14. cindex Says:

    Web cam video window is opened by "showdialog()"

  15. cindex Says:

    Thank you  for your answer,Tamir Khason.

    But, I do not understand yours.

    Inspite fo "player.Dispose();"

    My webcam`s lamp is not turned off.(my webcam`lame turned on when it run)

    Because of that, probably new window is non shown webcam video.

    It`s lamp is turned off when application is closed.

  16. Futile » links for 2009-01-06 Says:

    [...] Webcam control with WPF how to create high framerate player with DirectShow by using InteropBitmap in WPF application (tags: c# wpf webcam development) Share and Enjoy: [...]

  17. colby Says:

    Anybody got something like this in vb.net? I already have the whole app written in vb, i was just needing a webcam control and i would be golden…

  18. Pablo Says:

    This solution works for me but i have a big problem: this is “eating” more than 30 of CPU. Why could it be possible?
    Sorry for my english

  19. Anatoly Says:

    How to increase resolution of video? I ve got hd web cam and got only 160/120 resolution?

    With greetings, Anatoly

  20. Juan Jose Says:

    Good! It works fine! Thank you.

  21. Gatzo Says:

    Great job!
    How can I change the Webcam resolution?

    ————————————
    Rich Says:
    January 1st, 2009 at 12:45 am

    Very impressive. +1 for changing the resolution. I’ve been searching for a solution and could not find any that I understand.
    ————————————

  22. Skippy Says:

    Trying to use this to limit the video to just one of the channels (say… Red-only, don’t show the blue or green, so I have just the red part of the image…).

    I’m guessing that it’ll be a tweak in the capGrabber_PropertyChanged function of CapDevice.cs. Because that’s where the BitmapSource is built from memory.

    Or am I on completely the wrong track?

    -Scott

  23. maltub@gmail.com Says:

    why the picture is

Leave a Reply

Recommended

 

Sponsor



Partners

WPF Disciples
Dreamhost
Code Project
Switched to Better Place

Together