Monday, June 27, 2011

Use Video Captures in Your .NET Applications


oday, a webcam is a common peripheral, used most often for video conferencing, that most people can easily afford. But what can you do with your webcam besides video conferencing? If you are a developer, the answer is plenty; you will be glad to know that integrating a webcam with an application is not as difficult as you might imagine.Using the AVICap window class available in the Windows operating system, you can easily incorporate video capture capabilities into your Windows application. The uses of this functionality are endless but in particular you can easily populate employee and other person databases with mug shots taken directly from the users' terminal.
The AVICap class (located in the avicap32.dll file) contains message-based interfaces to access video and waveform-audio acquisition hardware and provides the ability to capture streaming video to disk. The only downside to the AVICap class is that it is an API and is thus not exposed as a managed class to the .NET developer. Hence, as a .NET developer you'll need to use Platform Invoke (P/Invoke) to make use of the API.
In this article, I will show you how to incorporate video capabilities into your .NET Windows applications. In particular, you will learn how to:
  • preview video input (within your Windows application) from your Webcam
  • record streaming video
  • capture images using your webcam.
In addition, I will develop a Windows application that puts the new video capability to good use. You will see how you can use the techniques discussed to take photos of new users in a registration system.Using the AVICap Class
To show how to use the AVICap class for integrating video into your Windows application, I'll use Visual Studio 2005 to create a new Windows application project. I'll name the project C:\VideoCapture. The application that you will build will list all the video sources available on your computer. You can then select a video source (in this case, your webcam) and preview its content. You will also learn how to record a video stream to file.
First, populate the default Windows Form using the controls shown in Figure 1. You will need a ListBox control for displaying the video sources on your computer, a PictureBox control, with which to preview the video captures, and three Button controls to start and stop the video recording as well as to stop the selected camera.

Figure 1. Proper Form: The screen shot shows the placement of each control you'll need to put on the default Windows Form in order to create the functionality for the sample application.
Switch to the code-behind of the form and import the following namespace (required for P/Invoke):

Imports System.Runtime.InteropServices
Within the Form1 class, declare the constants used by the AVICap class:

    Const WM_CAP_START = &H400S
    Const WS_CHILD = &H40000000
    Const WS_VISIBLE = &H10000000

    Const WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10
    Const WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11
    Const WM_CAP_EDIT_COPY = WM_CAP_START + 30
    Const WM_CAP_SEQUENCE = WM_CAP_START + 62
    Const WM_CAP_FILE_SAVEAS = WM_CAP_START + 23

    Const WM_CAP_SET_SCALE = WM_CAP_START + 53
    Const WM_CAP_SET_PREVIEWRATE = WM_CAP_START + 52
    Const WM_CAP_SET_PREVIEW = WM_CAP_START + 50

    Const SWP_NOMOVE = &H2S
    Const SWP_NOSIZE = 1
    Const SWP_NOZORDER = &H4S
    Const HWND_BOTTOM = 1

After declaring the constants, you need to declare the functions. The first two functions are needed for video capturing purposes and can be found in the avicap32.dll library, while the next three functions (found in user32.dll) are used for manipulating the various windows.

'--The capGetDriverDescription function retrieves the version 
' description of the capture driver--
Declare Function capGetDriverDescriptionA Lib "avicap32.dll" _
   (ByVal wDriverIndex As Short, _
    ByVal lpszName As String, ByVal cbName As Integer, _
    ByVal lpszVer As String, _
    ByVal cbVer As Integer) As Boolean

'--The capCreateCaptureWindow function creates a capture window--
Declare Function capCreateCaptureWindowA Lib "avicap32.dll" _
   (ByVal lpszWindowName As String, ByVal dwStyle As Integer, _
    ByVal x As Integer, ByVal y As Integer, ByVal nWidth As Integer, _
    ByVal nHeight As Short, ByVal hWnd As Integer, _
    ByVal nID As Integer) As Integer

'--This function sends the specified message to a window or windows--
Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
   (ByVal hwnd As Integer, ByVal Msg As Integer, _
    ByVal wParam As Integer, _
   <MarshalAs(UnmanagedType.AsAny)> ByVal lParam As Object) As Integer

'--Sets the position of the window relative to the screen buffer--
Declare Function SetWindowPos Lib "user32" Alias "SetWindowPos" _
   (ByVal hwnd As Integer, _
    ByVal hWndInsertAfter As Integer, ByVal x As Integer, _
    ByVal y As Integer, _
    ByVal cx As Integer, ByVal cy As Integer, _
    ByVal wFlags As Integer) As Integer

'--This function destroys the specified window--
Declare Function DestroyWindow Lib "user32" _
   (ByVal hndw As Integer) As Boolean
Also declare the following member variables:

    '---used to identify the video source---
    Dim VideoSource As Integer
    '---used as a window handle---
    Dim hWnd As Integer
When the form is loaded for the first time, all the video sources connected to your computer will be listed in the ListBox control. This task is accomplished by the ListVideoSources() subroutine:

    Private Sub Form1_Load( _
       ByVal sender As System.Object, _
       ByVal e As System.EventArgs) Handles MyBase.Load
        btnStartRecording.Enabled = True
        btnStopRecording.Enabled = False
        '---list all the video sources---
        ListVideoSources()
    End Sub
The ListVideoSources() subroutine calls the capGetDriverDescriptionA() function and lists all the video sources in the ListBox control. Up to 10 video sources are supported:

    '---list all the various video sources---
    Private Sub ListVideoSources()
        Dim DriverName As String = Space(80)
        Dim DriverVersion As String = Space(80)
        For i As Integer = 0 To 9
            If capGetDriverDescriptionA(i, DriverName, 80, _
               DriverVersion, 80) Then
                lstVideoSources.Items.Add(DriverName.Trim)
            End If
        Next
    End Sub
Figure 2 shows the ListBox control showing a video source connected to my computer when the form is loaded.
Figure 2. Video Options: The ListBox control displays a list of the list of video sources from your computer.
Users can view a video source by selecting it in the ListBox control. To allow the user to do that, double-click on the ListBox control and code the following:

    '---list all the video sources---
    Private Sub lstVideoSources_SelectedIndexChanged( _
       ByVal sender As System.Object, ByVal e As System.EventArgs) _
       Handles lstVideoSources.SelectedIndexChanged
        '---check which video source is selected---
        VideoSource = lstVideoSources.SelectedIndex
        '---preview the selected video source
        PreviewVideo(PictureBox1)
    End Sub
The previous event basically identifies the selected video source and then calls the PreviewVideo() subroutine to preview the video, which is shown below:

    '---preview the selected video source---
    Private Sub PreviewVideo(ByVal pbCtrl As PictureBox)
        hWnd = capCreateCaptureWindowA(VideoSource, _
            WS_VISIBLE Or WS_CHILD, 0, 0, 0, _
            0, pbCtrl.Handle.ToInt32, 0)
        If SendMessage( _
           hWnd, WM_CAP_DRIVER_CONNECT, _
           VideoSource, 0) Then
            '---set the preview scale---
            SendMessage(hWnd, WM_CAP_SET_SCALE, True, 0)
            '---set the preview rate (ms)---
            SendMessage(hWnd, WM_CAP_SET_PREVIEWRATE, 30, 0)
            '---start previewing the image---
            SendMessage(hWnd, WM_CAP_SET_PREVIEW, True, 0)
            '---resize window to fit in PictureBox control---
            SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, _
               pbCtrl.Width, pbCtrl.Height, _
               SWP_NOMOVE Or SWP_NOZORDER)
        Else
            '--error connecting to video source---
            DestroyWindow(hWnd)
        End If
    End Sub
The Stop Camera button calls the StopPreviewWindow() subroutine to destroy the current window showing the video preview:

    '---stop the preview window---
    Private Sub btnStopCamera_Click( _
       ByVal sender As System.Object, _
       ByVal e As System.EventArgs) _
       Handles btnStopCamera.Click
        StopPreviewWindow()
    End Sub
The StopPreviewWindow() subroutine is as follows:

    '--disconnect from video source---
    Private Sub StopPreviewWindow()
        SendMessage(hWnd, WM_CAP_DRIVER_DISCONNECT, VideoSource, 0)
        DestroyWindow(hWnd)
    End Sub
The Start Recording button allows you to start capturing the selected video source:

    '---Start recording the video---
    Private Sub btnStartRecording_Click( _
       ByVal sender As System.Object, _
       ByVal e As System.EventArgs) _
       Handles btnStartRecording.Click
        btnStartRecording.Enabled = False
        btnStopRecording.Enabled = True
        '---start recording---
        SendMessage(hWnd, WM_CAP_SEQUENCE, 0, 0)
    End Sub
When the Stop Recording button is clicked, the video stream is saved as an .avi file:

    '---stop recording and save it on file---
    Private Sub btnStopRecording_Click( _
       ByVal sender As System.Object, _
       ByVal e As System.EventArgs) _
       Handles btnStopRecording.Click
        btnStartRecording.Enabled = True
        btnStopRecording.Enabled = False
        '---save the recording to file---
        SendMessage(hWnd, WM_CAP_FILE_SAVEAS, 0, _
           "C:\RecordedVideo.avi")
    End Sub
That’s it! You can now test the application by pressing F5. Make sure you have a video source (e.g. webcam) connected to your computer. You should see an item in the ListBox control (see Figure 3). Click the video source and you should see an image shown up in the PictureBox control. To start recording, click the Start Recording button and then click the Stop Recording button to save the video to file. The Stop Camera will disconnect the selected video source from the PictureBox control.

Figure 3. Testing the Application: Here's the form in action, with video being input from the webcam and the buttons at the bottom to control the camera shutter.
 
Figure 4. The Author at Work: Here you can see the author previewing the recorded video.

Note that the recording of video takes up large amount of disk space and can fill up your hard disk storage pretty quickly. Be sure to click the Stop Recording button to ensure that the file does not get too big. As an example, a 37-second clip took up 66MB of disk space (see Figure 4).



Using Video Capability in a Business Application
Now that I have managed to integrate video into my Windows application, I'll consider a practical use of it. In this section, I will use data-binding to display some records from the Employees table in the Northwind database (one of the sample databases that comes with SQL Server 2000).
If you are familiar with the Northwind database, you know that in the Employees table there is a field known as Photo. This field is used to store employees’ photographs. In a real-life application, it would be much easier to add in an employee’s photo if you can take a snapshot of him using a webcam and then directly add the record to the database. In this section, I will show you how to do that.
First, add a new data source to the project. You can do so using the Data->Add New Data Source… menu (see Figure 5).


Figure 5. Adding a Source: Use the simple menu command shown to launch the Data Source Configuration Wizard and add a new data source to the project.
 
Figure 6. Adding a Database Connection: You need to tell Visual Studio which new data source you wish to add, in this case, a connection to the Northwind database.

The Data Source Configuration Wizard will appear. Choose Database as the data source and click Next. In the next window, click the New Connection… button to establish a connection to the Northwind database.Enter the name of the SQL Server containing the Northwind database. For my example, it is located on the same machine running Visual Studio 2005 and hence I entered "(local)." Select the Northwind database and then click OK (see Figure 6). Click Next when you are back to the previous window.
In the next window, you will choose the tables to use. Check the fields as shown in Figure 7. Click Finish to complete the process. The fields selected are: EmployeeID, LastName, FirstName, Title, and Photo. The Photo field is of the image data type, which contains images of employee. You will bind the Photo field to a PictureBox control on the windows form.


Figure 7. Fields and Tables: Each field corresponds to a table in the Northwind database. Here I've chosen five fields that provide data about an Employee, inclulding the Photo field, which will beind to a PictureBox control.
 
Figure 8. Setting the Binding: The screen shot shows the steps to change the default bindings of the Employees table so that it binds to a photo, which displays in a PictureBox control.

The Employees item will now appear in the Data Sources window (Data->Show Data Sources). Change the bindings of the Employees table as shown in Figure 8. Essentially, you want to view the Employees records in the detail view and bind the Photo field to a PictureBox control.The Employees table should now look like Figure 9.


Figure 9. Bound to the Employees: The Employees table now shows up in the Data Sources window.
 
Figure 10. Data-binding in Action: Form 1 is automatically populated with data fields from the Employees table when the latter is dropped onto the form.

Drag the Employees table from the Data Sources window and drop it onto Form1 (see Figure 10). You should see that a few controls are added automatically to the form. Resize the PictureBox control and set its SizeMode property to AutoSize.If you test the application now (by pressing F5), you will be able to view the records in the Employees table (see Figure 11.). You can add a record by clicking the Add New button, entering the value for each field, and then clicking the Save Data button to save the changes.


Figure 11. Employee View: You can now build the application and use it to view the records from the Employees table.
 
Figure 12. Adding a Button Control: The button you add will allow end users to capture their own photo and save it to the employee database.

This is where things get interesting. When you add a new record, you would want to make use of the webcam to take a photo of the employee and then save the photo directly to the database. To do so, first add a Button control to the bottom of the PictureBox control and name it Capture Photo (see Figure 12).Double-click the Add New button so that when the user adds a new record, the video source is now bound to the PhotoPictureBox control:

    Private Sub BindingNavigatorAddNewItem_Click( _
       ByVal sender As System.Object, _
       ByVal e As System.EventArgs) _
       Handles BindingNavigatorAddNewItem.Click
        StopPreviewWindow()
        PreviewVideo(PhotoPictureBox)
    End Sub
When the user clicks on the Capture Photo button, you will call the CaptureImage subroutine to freeze the photo:

    Private Sub btnCapturePhoto_Click( _
       ByVal sender As System.Object, _
       ByVal e As System.EventArgs) _
       Handles btnCapturePhoto.Click
        CaptureImage()
    End Sub
The CaptureImage() subroutine copies the image from the video source onto the clipboard. You then display the image using the PhotoPictureBox control:

    '---save the image---
    Private Sub CaptureImage()
        Dim data As IDataObject
        Dim bmap As Image

        '---copy the image to the clipboard---
        SendMessage(hWnd, WM_CAP_EDIT_COPY, 0, 0)

        '---retrieve the image from clipboard and convert it 
        ' to the bitmap format
        data = Clipboard.GetDataObject()
        If data.GetDataPresent(GetType(System.Drawing.Bitmap)) Then
            bmap = _
               CType(data.GetData(GetType(System.Drawing.Bitmap)), _
               Image)
            PhotoPictureBox.Image = bmap
            StopPreviewWindow()
        End If
    End Sub
Figure 13. Adding the Author: In this screen shot I'm taking a photo of myself to create a new record in the employee database.
That's it! Press F5 to test the application. When the form loads, click on the Add New button to add a new record. Enter the details as shown in Figure 13 and then position the webcam to take a nice snapshot of yourself. Smile and then click the Capture Photo button.Finally, click the Save Data button to save the new record onto the database. You can verify that the photo is saved by restarting the application and then viewing the records again.
In this article, you have seen how to use the AVICap class to integrate video into your Windows application. While I have only showed you one particular usage, there are in fact many uses for video: you can build a video surveillance system using the techniques described in this article or you can build your own video conferencing application. The possibilities are endless and please let me know if you have an innovative application built using the techniques described in this article.


Wei-Meng Lee (Microsoft .NET MVP) is a technologist and founder of Developer Learning Solutions, a technology company specializing in hands-on training on the latest Microsoft technologies. He speaks regularly at international conferences and has authored and co-authored numerous books on .NET, XML, and wireless technologies, including 'Windows XP Unwired' and the '.NET Compact Framework Pocket Guide' (both from O'Reilly Media, Inc). He writes extensively for the O'Reilly Network on topics ranging from .NET to Mac OS X. Wei-Meng is currently the Microsoft Regional Director for Singapore.



Download the Code for this Article

No comments: