dcsimg
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Monitor Your Web Cam from a Remote Computer : Page 2

We've offered a few solutions for working with web cams within .NET to create fun and intriguing monitoring applications. In this article, we extend those ideas so that web cam images can be shared with multiple clients over the Web.


advertisement
Saving the Video as Images
You will now modify the server so that it can act as a video server, accepting connections from clients and sending them images captured by the web cam.

The first step is to recognize that the video captured by the web cam can be saved as individual images. By displaying a series of continuous images on the client, it is similar to watching a video stream. To capture an image, I have defined the following subroutine:


    '---save the video data into the Image global variable---
    Public Sub CaptureImage()
        Dim data As IDataObject
        Dim bmap As Image
        Dim ms As New IO.MemoryStream()

        '---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 Is Nothing Then Exit Sub
        If data.GetDataPresent(GetType(System.Drawing.Bitmap)) Then
            '---convert the data into a Bitmap---
            bmap = CType(data.GetData(GetType( _
               System.Drawing.Bitmap)), Image)
            '---save the Bitmap into a memory stream---
            bmap.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp)
            '---write the Bitmap from stream into a byte array---
            Image = ms.GetBuffer
        End If
    End Sub
Here, I first copy the image displayed in the PictureBox control to the clipboard. I then convert it to an Image object and save it to a memory stream. Finally, I use the memory stream to write out the image as an array of bytes. The array of bytes is saved into a global variable, Image, which is defined in Module1.vb (right-click on project name in Solution Explorer and select Add | New Item…. Then select Module):

Module Module1
    Public Image As Byte()
End Module
Saving the image as a byte array allows me to easily transmit the image over a socket connection.

To ensure that the Image variable is always containing the latest image, add a Timer control to Form1 and set its properties as follows:

  • Enabled—True
  • Interval—100

Double-click on the Timer control (located underneath Form1) to reveal its Tick event handler. Code the Tick event handler as follows:


    '---save the video image at regular intervals---
    Private Sub Timer1_Tick( _
       ByVal sender As System.Object, _
       ByVal e As System.EventArgs) Handles Timer1.Tick
        CaptureImage()
    End Sub
Essentially, you are invoking the CaptureImage() subroutine every 100 milliseconds (10 times per second) so that the Image variable is always containing the latest video image.

Communication between the Clients and Server
The final step is to write the code for communicating with clients over a socket connection. Before I show you the code to do so, you should understand how the client will communicate with the server.

Figure 3. Communication between a client and the server involves a Send message that receives a video capture as a reply.
As shown in Figure 3, upon connecting to the server, the client first sends a "Send" message to the server. When the server receives a "Send" message, it sends back to the client an image captured by the web cam (specifically, the data contained within the Image global variable). The transfer takes place synchronously and the client will only send back another "Send" message when it is ready to accept another image from the server. This technique prevents the server from overwhelming the client, especially if the client is connected to the server over a slow connection.

Now, to enable this communication, add a new class to the project and name it WebCamClient.vb. Import the following namespace:


Imports System.Net.Sockets
Declare the following constant and variables:

'---class to contain information of each client---
Public Class WebCamClient

    '--constant for LineFeed character---
    Private Const LF As Integer = 10

    '---contains a list of all the clients---
    Public Shared AllClients As New Hashtable

    '---information about the client---
    Private _client As TcpClient
    Private _clientIP As String

    '---used for sending/receiving data---
    Private data() As Byte

    '---used to store partially received data---
    Private partialStr As String
Define the constructor for the WebCamClient class as follows:

    '---when a client is connected---
    Public Sub New(ByVal client As TcpClient)
        _client = client

        '---get the client IP address---
        _clientIP = client.Client.RemoteEndPoint.ToString

        '---add the current client to the hash table---
        AllClients.Add(_clientIP, Me)

        '---start reading data from the client in a separate thread---
        ReDim data(_client.ReceiveBufferSize - 1)
        _client.GetStream.BeginRead(data, 0, _
           CInt(_client.ReceiveBufferSize), _
           AddressOf ReceiveMessage, Nothing)
    End Sub
The ReceiveMessage() subroutine reads the data sent from the client. All messages sent from the client will end with a LineFeed (LF) character. Because a single message may be broken up into a few blocks during transmission, it is thus important that you detect for a LF character to ensure that you have received the entire message. Once a message is received and it contains the word "Send," the web cam image is sent to the client using the SendData() subroutine (defined next):

    '---receiving a message from the client---
    Public Sub ReceiveMessage(ByVal ar As IAsyncResult)
        '---read from client---
        Dim bytesRead As Integer
        Try
            SyncLock _client.GetStream
                bytesRead = _client.GetStream.EndRead(ar)
            End SyncLock
            '---client has disconnected---
            If bytesRead < 1 Then
                AllClients.Remove(_clientIP)
                Exit Sub
            Else
                Dim messageReceived As String
                Dim i As Integer = 0
                Dim start As Integer = 0
                '---loop until no more chars---
                While data(i) <> 0
                    '---do not scan more than what is read---
                    If i + 1 > bytesRead Then Exit While

                    '---if LF is detected---
                    If data(i) = LF Then
                        messageReceived = partialStr & _
                           System.Text.Encoding.ASCII.GetString( _
                           data, start, i - start)
                        If messageReceived.StartsWith("Send") Then
                            SendData(Image)
                        End If
                        start = i + 1
                    End If
                    i += 1
                End While
                '---partial string---
                If start <> i Then
                    partialStr = _
                       System.Text.Encoding.ASCII.GetString( _
                       data, start, i - start)
                End If
            End If

            '---continue reading from client---
            SyncLock _client.GetStream
                _client.GetStream.BeginRead(data, 0, _
                CInt(_client.ReceiveBufferSize), _
                AddressOf ReceiveMessage, Nothing)
            End SyncLock
        Catch ex As Exception
            '---remove the client from the HashTable---
            AllClients.Remove(_clientIP)
            Console.WriteLine(ex.ToString)
        End Try
    End Sub
The SendData() subroutine sends the data contained in the Image global variable over to the client:

    '---send the data to the client---
    Public Sub SendData(ByVal data As Byte())
        Try
            Dim ns As System.Net.Sockets.NetworkStream
            SyncLock _client.GetStream
                ns = _client.GetStream
                ns.Write(data, 0, data.Length)
            End SyncLock
        Catch ex As Exception
            Console.WriteLine(ex.ToString)
        End Try
    End Sub
Back in Form1, you can now wire up the rest of the code to make the server functional. Add the following constants and variable:

Public Class Form1
    '---port no for listening and sending data---
    Const IP_Address As String = "127.0.0.1"
    Const portNo As Integer = 500

    '---use to spin off a thread to listen for incoming connections---
    Dim t As System.Threading.Thread	
Define the Listen() subroutine to listen for incoming socket connections:

    '---listen for incoming connections---
    Private Sub Listen()
        Dim localAdd As System.Net.IPAddress = _
           System.Net.IPAddress.Parse(IP_Address)
        Dim listener As New System.Net.Sockets.TcpListener( _
           localAdd, portNo)
        listener.Start()
        While True
            Dim user As New WebCamClient(listener.AcceptTcpClient)
        End While
    End Sub
In the Form1_Load event, preview the video by calling the PreviewVideo() subroutine and then spin off a separate thread to listen for incoming connections from clients:

    Private Sub Form1_Load( _
       ByVal sender As System.Object, _
       ByVal e As System.EventArgs) _
       Handles MyBase.Load

        '---preview the selected video source
        PreviewVideo(PictureBox1)

        '---listen for incoming connections from clients---
        t = New System.Threading.Thread(AddressOf Listen)
        t.Start()
    End Sub
Finally, if Form1 is closed, abort the thread (for listening for connections) and end the application:

    Private Sub Form1_FormClosing( _
       ByVal sender As Object, _
       ByVal e As System.Windows.Forms.FormClosingEventArgs) _
       Handles Me.FormClosing
        t.Abort()
        End
    End Sub


Thanks for your registration, follow us on our social networks to keep up-to-date