Freigeben über


Using Webcams in Silverlight 4

One of the new features in Silverlight 4 that I’m excited about is the support for webcams and microphones. I’ve spent the past few days playing around with this feature and thought it’d be worthwhile to write about how to get started using it. In future blogs posts and topics in the MSDN Library I'll go into more complex scenarios, but for today I'm going to show how to get up and running with webcams in Silverlight 4.

What I want to focus on is how to hook up video from a webcam, display the output, and then capture a single frame and display it. One scenario this can be applied to is taking profile pictures for a social networking application. 

 

Here's the source code for this sample if you want to jump ahead. 

 

The main API for accomplishing this, which are in the System.Windows.Media namespace, are CaptureDeviceConfiguration, CaptureSource, and VideoCaptureDevice.

 

First thing we need to do is define our CaptureSource and VideoCaptureDevice. The CaptureSource is the main object we use to interact with these devices. Through the CaptureSource you can start and stop the video and audio devices, check the state of the devices, and capture a single video frame. The VideoCaptureDevice is the webcam.

        CaptureSource captureSource;
        VideoCaptureDevice webcam;

To get hold of the audio and video devices installed on the system, the CaptureDeviceConfiguration class exposes several static methods. It’s possible to enumerate through all the available video capture devices by using the GetAvailableVideoCaptureDevices static method but a quick way to get access to the video device is through the GetDefaultVideoCaptureDevice static method.

     // get the webcam. null is returned if there is not a webcam installed
     webcam = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();

Next, we’ll set the VideoCaptureDevice property on the CaptureSource to the webcam VideoCaptureDevice object.

     // set the video capture source the WebCam     captureSource.VideoCaptureDevice = webcam;

 

The next thing we need to do is display the webcam. We'll use a VideoBrush to display the video and we'll set the Fill property of a Rectangle object to this brush.

We'll create our Rectangle in XAML as follows (and we'll jump ahead a little and create the Rectangle to display the captured frame):

     <!-- The Fill property will be set to the webCam VideoBrush -->
     <Rectangle x:Name="webcamDisplay" Grid.Row="1" Grid.Column="0" />

<!-- The Fill property will be set to the capturedImage ImageBrush -->

     <Rectangle x:Name="capturedDisplay" Grid.Column="1" Grid.Row="1" />

And in code we'll create a VideoBrush for the live video and an ImageBrush for the captured frame and then hook everything together. Before we set the VideoCaptureDevice property on the CaptureSource, we'll want to check and make sure the VideoCaptureDevice is not null.

  public VideoBrush webcamBrush;
public ImageBrush capturedImage;

     // the brush used to fill the display rectangle
     capturedImage = new ImageBrush();

     if (null != webcam)
{
         // set the video capture source the to WebCam
         captureSource.VideoCaptureDevice = webcam;

         // set the source on the VideoBrush used to display the video
         webcamBrush = new VideoBrush();
webcamBrush.SetSource(captureSource);

         // set the Fill property of the Rectangle (defined in XAML) to the VideoBrush
         webcamDisplay.Fill = webcamBrush;

         // set the Fill property of the Rectangle (defined in XAML) to the ImageBrush
         capturedDisplay.Fill = capturedImage;
}

That's it as far as the basic plumbing for hooking up the webcam. The final step and a gotcha I spent a few minutes spinning my wheels on is starting the video feed. In order to start the webcam you must request access using the RequestDeviceAccess static method on the CaptureDeviceConfiguration class. The gotcha is that this method must be called from within a user initiated event, such as a Button Click event handler. Calling this method in a non-user event, such as a loaded event handler, will result in this method returning false and subsequent calls to CaptureSource.Start will throw an InvalidOperationException

Here's the code for starting and stopping the webcam, which is called from within Button Click event handlers.

        private void StartButton_Click(object sender, RoutedEventArgs e)
{
            if(CaptureDeviceConfiguration.RequestDeviceAccess() &&
                 captureSource.VideoCaptureDevice != null
{
captureSource.Start();
}
}

        private void StopButton_Click(object sender, RoutedEventArgs e)

            if (captureSource.VideoCaptureDevice != null)
{
captureSource.Stop();
}
}

The last thing I want to talk about is how to capture an image from the video. You can use the CaptureImageAsync method on the CaptureSoure class to asynchronously capture a single frame of video and then listen to the CaptureImageCompleted and CaptureFailed events. The event argument class for the CaptureImageCompleted event is CaptureImageCompletedEventArgs. The Result property on the event args is the captured image, in the form of a WriteableBitmap. Simply set the ImageSource of the ImageBrush to the WriteableBitmap and you're all set. You can also listen to the CaptureFailed event to be notified if something went wrong.

Before we call CaptureImageAsync, we'll want to make sure the VideoCaptureDevice is not null and that the device is currently started. You can get the status of the device by checking the State property on the CaptureSource.

We'll set the ImageSource property on the ImageBrush to the WriteableBitmap. Earlier, we already set the Fill property of the Rectangle to the ImageBrush

Let's hook up the event handlers.

        // async capture failed event handler
        captureSource.CaptureFailed +=
new EventHandler<ExceptionRoutedEventArgs>(CaptureSource_CaptureFailed); 

        // async capture completed event handler
        captureSource.CaptureImageCompleted +=
new EventHandler<CaptureImageCompletedEventArgs>(CaptureSource_CaptureImageCompleted);  

 And here's the code for capturing the image:

        private void CaptureButton_Click(object sender, RoutedEventArgs e)
{
            // verify the VideoCaptureDevice is not null and the device is started
if (captureSource.VideoCaptureDevice != null &&
captureSource.State == CaptureState.Started)
{
captureSource.CaptureImageAsync();
}

        void CaptureSource_CaptureImageCompleted(object sender, CaptureImageCompletedEventArgs e)
{
            // Set the Image object to the WriteableBitmap passed in through the event arguments
            capturedImage.ImageSource = e.Result;

        void CaptureSource_CaptureFailed(object sender, ExceptionRoutedEventArgs e)
{
// For the sake of this example, simply show a messagebox
            MessageBox.Show(string.Format("Failed to capture image: {0}", e.ErrorException.Message));
}

Here's the XAML and C# source code for this example. 

And here's what the final application looks like after an image has been captured. The image on the left is the video feed and the image on the right is the captured image.

WebCamNew 

Enjoy -- Brian

Comments

  • Anonymous
    June 10, 2010
    The comment has been removed

  • Anonymous
    October 03, 2010
    The comment has been removed

  • Anonymous
    October 03, 2010
    Oops... Grant, You might want to have a look at this: mtaulty.com/.../silverlight-4-webcams-oncemorewithaudio.aspx

  • Anonymous
    November 29, 2010
    Hi Brian. Regarding this little trick: "method must be called from within a user initiated event" I found that it seems it needs to be called from separate event, not just user initiated event. For example it will not show access dialog and wont work if you create Videobrush and request device access in the same user event handler. Kostya

  • Anonymous
    November 29, 2010
    Oops. I made mistake. Above is not correct. I found that it fails to show dialog if some considerable delay occurs before call to CaptureDeviceConfiguration.RequestDeviceAccess(). In my case System.Threading.Thread.Sleep(1000) in front of request makes it fail, while delay of 500 is fine. Kostya