摄像头远程监控的Vb.net实现方法()

n two of my earlier articles for DevX, "Teach Your Old Web Cam New Tricks: Use Video Captures in Your .NET Applications" and "Building an Enhanced Security System with a Web Cam and a Servo," I showed how to incorporate your web cam into your .NET applications and use it for surveillance purposes. However, using those solutions, the videos can only be viewed locally on the computer running the web cam. An interesting enhancement would be to extend the applications so that the video can be viewed remotely over the Internet. 
解压密码为lob.cn
And so, in this article I will show you how to use socket communication to send a live video p_w_picpath from a server to remote clients. The sample applications built in this article consists of:

·A server that displays the video captured by a web cam

·A client that receives the live video p_w_picpath from the server

The server will allow multiple clients to connect to it at the same time. Among other things, it is a useful application for the home environment where you can use it in your office to monitor your home or kids.

Creating the Server
I'll first create the server. Using Visual Studio 2005, create a new Windows application and name it RemoteMonitoring. In the default Form1, add a PictureBox control (see Figure 1) and set its properties as follows:

·Size—449, 253

·SizeMode—StretchImage


Figure 1. Add a PictureBox control to Form1. 
Figure 2. You can now preview the video from the web cam in the application.

Switch to the code-behind of Form1 and import the following namespace: Imports System.Runtime.InteropServicesDeclare the constants and variable used for displaying the web cam p_w_picpath: Public Class Form1 '---constants for capturing the video from webcam--- 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 '---used as a window handle--- Dim hWnd As IntegerAfter declaring the constants, you need to declare the functions for video capture. 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 BooleanDefine the PreviewVideo() subroutine so that you can display the p_w_picpath captured by the web cam in a PictureBox control: '---preview the selected video source--- Private Sub PreviewVideo(ByVal pbCtrl As PictureBox) hWnd = capCreateCaptureWindowA(0, _ WS_VISIBLE Or WS_CHILD, 0, 0, 0, _ 0, pbCtrl.Handle.ToInt32, 0) If SendMessage( _ hWnd, WM_CAP_DRIVER_CONNECT, _ 0, 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 p_w_picpath--- 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 SubYou can now test if you are able to preview the video by calling the PreviewVideo() subroutine in the Form1_Load event: Private Sub Form1_Load( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load PreviewVideo(PictureBox1) End Sub

Press F5 to test the application (be sure to connect your web cam to your computer now). Figure 2 shows the webcam video displayed in the PictureBox control. 

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 p_w_picpaths captured by the web cam.

The first step is to recognize that the video captured by the web cam can be saved as individual p_w_picpaths. By displaying a series of continuous p_w_picpaths on the client, it is similar to watching a video stream. To capture an p_w_picpath, 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 p_w_picpath to the clipboard--- SendMessage(hWnd, WM_CAP_EDIT_COPY, 0, 0) '---retrieve the p_w_picpath 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 p_w_picpath 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 p_w_picpath 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 ModuleSaving the p_w_picpath as a byte array allows me to easily transmit the p_w_picpath over a socket connection.

To ensure that the Image variable is always containing the latest p_w_picpath, 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 p_w_picpath at regular intervals--- Private Sub Timer1_Tick( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Timer1.Tick CaptureImage() End SubEssentially, you are invoking the CaptureImage() subroutine every 100 milliseconds (10 times per second) so that the Image variable is always containing the latest video p_w_picpath.

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 p_w_picpath 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 p_w_picpath 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.SocketsDeclare 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 StringDefine 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 SubThe 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 p_w_picpath 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 SubThe 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 SubBack 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 SubIn 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 SubFinally, 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 SubCreating the Client
Using Visual Studio 2005, create a new Windows application and name it RemoteMonitoringClient.In the default Form1, populate the controls as shown in Figure 4.Set the properties of PictureBox1 as follows:

·Size—449, 253

·SizeMode—StretchImage

PictureBox2 is set to display the p_w_picpath of an LCD monitor (you can do so via its Image property). Be sure to bring PictureBox1 to the front (right-click on PictureBox1 and select Bring to Front). Switching to the code-behind of Form1, import the following namespace: Imports System.Net.SocketsImports System.IODeclare the following constant and member variables: Public Class Form1 '---get own IP address--- Private ips As Net.IPHostEntry = _ Net.Dns.GetHostEntry(Net.Dns.GetHostName()) '---port nos and server IP address--- Const PORTNO As Integer = 500 Private server_IP As String = " 127.0.0.1" '---size of the video p_w_picpath--- Const SIZEOFIMAGE As Integer = 341504 '---use for connecting to the server--- Private client As TcpClient '--used for sending and receiving data--- Private data() As Byte '---used for receiving p_w_picpaths from the server--- Private t As System.Threading.ThreadCode the Click event handler of the Start button control as follows: Private Sub btnStartStop_Click( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnStartStop.Click If CType(sender, Button).Text = "Start" Then Try '---set the server IP address--- server_IP = txtServerIP.Text '---connect to the server--- client = New TcpClient client.Connect(server_IP, PORTNO) ReDim data(client.ReceiveBufferSize - 1) '---send message--- SendMessage("Send") '---begin reading data asynchronously from the ' server--- t = New System.Threading.Thread( _ AddressOf ReceiveImageLoop) t.Start() '---change the text on the Button--- CType(sender, Button).Text = "Stop" Catch ex As Exception Console.WriteLine(ex.ToString) End Try Else '---send message--- SendMessage("Stop") t.Abort() '---change the text on the Button--- CType(sender, Button).Text = "Start" End If End SubYou first connect to the server using its IP address and port number. You then send a "Send" message to the server to indicate that you are ready to receive the p_w_picpath. You spin off a thread so that you can receive p_w_picpaths (via the ReceiveImageLoop() subroutine) asynchronously. The ReceiveImageLoop() subroutine calls the ReceiveImage() function indefinitely, until an error occurs: Private Sub ReceiveImageLoop() '---keep on receiving p_w_picpath until an error occurs--- While ReceiveImage() End While '---display error message--- MsgBox("Server has stopped responding. Please try" & _ & " restarting the video.") End SubThe ReceiveImage() function reads the incoming p_w_picpath data (in blocks of 8192 bytes, as defined by the ReceiveBufferSize property of the TcpClient class) sent from the server. As each p_w_picpath sent is 341504 bytes (defined by the SIZEOFIMAGE constant; this value is dependent on the web cam used), you will therefore read the number of bytes as expected. Once the p_w_picpath is received, display it in the PictureBox control. To receive the next p_w_picpath from the server, send another "Send" message: '---receive video p_w_picpath from server--- Public Function ReceiveImage() As Boolean Dim s As New MemoryStream Try Dim nws As NetworkStream = client.GetStream Dim counter As Integer = 0 Dim totalBytes As Integer = 0 Do '---read the incoming data--- Dim bytesRead As Integer = _ nws.Read(data, 0, client.ReceiveBufferSize) totalBytes += bytesRead '---write the byte() array into the memory stream--- s.Write(data, 0, bytesRead) counter += 1 Loop Until totalBytes >= SIZEOFIMAGE '---display the p_w_picpath in the PictureBox control--- PictureBox1.Image = Image.FromStream(s) Catch ex As InvalidOperationException '---ignore this error--- Console.WriteLine(ex.ToString) Catch ex As Exception Console.WriteLine(ex.ToString) Return False End Try '---ask the server to send the next p_w_picpath--- SendMessage("Send") Return True End FunctionThe SendMessage() subroutine sends a message to the server: '---Sends a message to the server--- Private Sub SendMessage(ByVal message As String) '---adds a carriage return char--- message += vbLf Try '---send the text--- Dim ns As System.Net.Sockets.NetworkStream SyncLock client.GetStream ns = client.GetStream Dim bytesToSend As Byte() = _ System.Text.Encoding.ASCII.GetBytes(message) '---sends the text--- ns.Write(bytesToSend, 0, bytesToSend.Length) End SyncLock Catch ex As Exception Console.WriteLine(ex.ToString) End Try End SubWhen Form1 is closed, kill the thread that listens asynchronously for incoming p_w_picpath data: Private Sub Form1_FormClosing( _ ByVal sender As Object, _ ByVal e As System.Windows.Forms.FormClosingEventArgs) _ Handles Me.FormClosing t.Abort() End Sub Testing the Applications
You can test both the server and the client on one single machine. Simply set the IP_Address variable (on the server) to "127.0.0.1" and then press F5 in Visual Studio 2005 to test the application. For the client, type the server IP address (which is 127.0.0.1) and click the Start button. You should see the same p_w_picpath on both the client and the server (see Figure 5). You can also try the client and server on two separate machines. Just remember to set the server IP address accordingly. Best of all, you can connect multiple clients to the server! 
Some Points to Note
Please note the following points when testing the client and the server:

·The server application needs to be visible on screen. If it is minimized, the client will not be able to receive the p_w_picpath captured by the web cam. This is due to the fact that the application is capturing whatever p_w_picpaths are shown on the screen.

·For simplicity I have not added any security features into the server. In reality, you can add credentials information to the message sent to the server before the server sends the video p_w_picpath over to the client.

·

Figure 5. You can test the client and the server on the same machine