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:
- EnabledTrue
- Interval100
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