Using a webcam in WPF
A colleague asked how to capture video from a webcam in WPF. I find it’s a lot more enjoyable to learn about a technology by having something to aim for and this seemed an ideal project.
So what tools are at our disposal to write a WPF application that will show the live output from a web cam attached locally to the machine? My first thought was the Media Foundation API new in Vista. This is a replacement for the venerable DirectShow which is powerful but very closely tied to COM (if you develop in managed code and wanted to use DirectShow, you’re no doubt aware that there is no official managed wrapper). Media Foundation looked ideal until I found that it doesn’t support hardware devices as yet. It supports creating your own sources and there is a WAV source sample in the SDK, but creating a source filter for a hardware device will take you to the kernel and that’s a step too far for me.
OK, it needs to be DirectShow. This will allow code to enumerate the system for hardware and connect all the necessary components to show the camera output on the screen (termed a graph - if you’ve not used DirectShow, play around with the GraphEdit tool in the SDK bin folder, all will be revealed). As its DirectShow, it will also mean C++. Using interop is possible but its a dark tunnel to go down before you see light, so I chose managed C++ to bridge WPF to DirectShow.
DirectShow will render the images from the camera using a component (called a filter in DirectShow) called the Video Mixing Renderer. This is a DirectX based component that will push the pixels around (see last post!) and in its default form likes to work with window handles and other non WPF concepts. This pointed me to the HwndHost control which gives a window handle to host things in. We can subclass this control and override a few methods on the control to hook things up. Enough background, here’s how I put things together:
I’ve got two classes: one managed that will subclass HwndHost and be a managed class, and another that will be unmanaged to do all the DirectShow magic. The managed class will instantiate and call methods on the undamaged class. First the managed class: I override the BuildWindowCore to create my own window using CreateWindowEx(), instantiate the unmanaged class and ask it to use DirectShow and hook it to our newly created window. I override DestroyWindowCore to clean-up the unmanaged class and our created window. The ArrangeOverride method also needs to be overridden (terminology meltdown!) so that we know the final size of the control when it is rendered to the screen as its not know on the call to BuildWindowCore. That, plus methods to start and stop the preview is all that’s in the managed class.
The unmanaged class exposes a setup method that will (DirectShow terminology warning) enumerate for video input class devices, create an instance of a device filter, add it to a graph and render its output pin. It then queries the graph for its COM interface to configure the video output and it’s at this point we can return to hooking in with WPF. This method was passed the window handle of our created window and we can configure DirectShow to render within the window area (i.e. our new WPF control). This class also exposes a method to do the final sizing of the video output and this is called from the ArrangeOverride method mentioned above. Some further methods to start, stop and cleanup and this class is done.
But does it work? The end result is a control we can place in our WPF application that will show the webcam output, nice! Yes, it seems to work well. The order in which you configure window properties as part of the creation process has an effect on Aero Glass. The Desktop Window Manager service controlling Aero Glass will fall over (with a event log details to tell you that it did) if the order is incorrect.
That’s all well and good, I hear you all cry, but there’s no code! You could have made all this up! A fair point and my answer is that including code snippets would have made writing this up much more complicated and even longer. When I can find a spot to host the sample code I’ll make it available if people are interested and you can pick over it at your leisure.