CamServer.java :  » Linux » v4l4j » au » edu » jcu » v4l4j » examples » server » Java Open Source

Java Open Source » Linux » v4l4j 
v4l4j » au » edu » jcu » v4l4j » examples » server » CamServer.java
/*
* Copyright (C) 2011 Gilles Gigan (gilles.gigan@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public  License as published by the
* Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE.  
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
*/
package au.edu.jcu.v4l4j.examples.server;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Vector;

import au.edu.jcu.v4l4j.ControlList;
import au.edu.jcu.v4l4j.JPEGFrameGrabber;
import au.edu.jcu.v4l4j.CaptureCallback;
import au.edu.jcu.v4l4j.VideoDevice;
import au.edu.jcu.v4l4j.VideoFrame;
import au.edu.jcu.v4l4j.exceptions.V4L4JException;

/**
 * This class creates a tcp server socket and waits for incoming connections frm a web browser
 * video streaming apps such as VLC and ffplay. It uses two threads: a  server threads to handle
 * incoming tcp connections and figure out what to sent, and a capture thread to retrieve a video
 * frame, and send it to all connected client in the form of an MJPEG stream.
 * When a connection is made, the server thread parses the first line it receives to decide what 
 * to send back:
 * If the line contains no recognisable keywords (such as when a browser requests "GET / HTTP/1.1")
 * a simple html page with two frames is sent back. One frame asks for the "control" page, the
 * other asks for the "webcam" page. 
 * If the line contains the keyword "control", a basic html page with a table containing a row
 * for each control. Each row is an HTML form displaying the current control value, and allowing
 * the user to change it.
 * If the line contains the keyword "webcam", a basic page with an img tag is sent. The img tag
 * has a url pointing to "stream.jpg".
 * If the line contains the keyword "stream", this client is added to a client list.
 * The capture thread continuously retrieves frame and sends it to all currently-connected clients.
 * <br>
 * To use this class, run it, and point your browser to "localhost:8080". You can also view the
 * video stream in VLC (Select "Media"->"Open network stream...", then select "HTTP" as the 
 * protocol and enter "localhost:8080/stream" as the address). You can also use ffplay to view
 * the stream (Run <code>ffplay -f mjpeg http://localhost:8080/stream</code>).
 * @author gilles
 *
 */
public class CamServer implements Runnable, CaptureCallback{
  private ServerSocket         serverSocket;
  private VideoDevice          videoDevice;
  private JPEGFrameGrabber      frameGrabber;
  private ControlList          controlList;
  private Thread            serverThread;
  private Vector<ClientConnection>   clients;
  private String             httpLineFromClient;
  
  private static final int      MAIN_PAGE = 0;
  private static final int      WEBCAM_PAGE = 1;
  private static final int      CONTROL_PAGE = 2;
  private static final int      VIDEO_STREAM = 3;
  private static final int      UPDATE_CONTROL_VALUE = 4;


  public static void main(String[] args) throws V4L4JException, IOException{
    String dev = (System.getProperty("test.device") != null) ? System.getProperty("test.device") : "/dev/video0"; 
    int w = (System.getProperty("test.width")!=null) ? Integer.parseInt(System.getProperty("test.width")) : 640;
    int h = (System.getProperty("test.height")!=null) ? Integer.parseInt(System.getProperty("test.height")) : 480;
    int port = (System.getProperty("test.port")!=null) ? Integer.parseInt(System.getProperty("test.port")) : 8080;
 
    CamServer server = new CamServer(dev, w, h, port);
    server.start();
    System.out.println("Press enter to exit.");
    System.in.read();
    server.stop();
  }

  /**
   * Builds a camera server object capturing frames from the given device
   * at the given resolution and sending them out to clients connected
   * to the given TCP port number.
   * @param dev the video device file
   * @param width the capture width
   * @param height the capture height
   * @param port the TCP port to listen on for incoming connections
   * @throws V4L4JException if a JPEG frame grabber cant be created 
   * @throws IOException if a server socket on the given port cant be created
   */
  public CamServer(String dev, int width, int height, int port) throws V4L4JException, IOException {
    videoDevice = new VideoDevice(dev);
    frameGrabber = videoDevice.getJPEGFrameGrabber(width, height, 0, 0, 80);
    frameGrabber.setCaptureCallback(this);
    controlList = videoDevice.getControlList();
    clients = new Vector<ClientConnection>();

    // initialise tcp port to listen on
    serverSocket = new ServerSocket(port);
    
    System.out.println("Server listening at "+
        serverSocket.getInetAddress().getHostAddress() + ":"+ serverSocket.getLocalPort());

    // create server thread
    serverThread = new Thread(this, "Server thread");
  }

  public void start() {
    // start the tcp server thread
    serverThread.start();
  }

  public void stop() {
    // close the server socket first
    try {
      serverSocket.close();
    } catch (IOException e) {
      // error closing the server socket
    }
    
    // now interrupt the server thread        
    if (serverThread.isAlive()){
      serverThread.interrupt();
      try {
        serverThread.join();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }    
    
    // stop the capture
    try {
      frameGrabber.stopCapture();
    } catch (Exception e){}// the capture is already stopped

    // Stop all client connections
    // no need for the lock - the capture is already stopped which means
    // no more calls to nextFrame(), so we are the only one to access 
    // the client list
    for(ClientConnection client:clients)
      client.stop();

    // release v4l4j frame grabber, control list and video device
    videoDevice.releaseFrameGrabber();
    videoDevice.releaseControlList();
    videoDevice.release();
  }

  /**
   * the main server thread loop: wait for a client to connect to the server socket,
   * then read a single line and look for specific keywords to determine which action
   * to take: send the main html page, the webcam page, the control list page, or 
   * the webcam stream  
   * 
   * @throws IOException if there is an error accepting a connection on the server socket
   * @throws V4L4JException  if there is an error starting the capture when the first
   * client connects
   */
  private void serverMainLoop() throws IOException, V4L4JException{
    BufferedReader       inStream = null;
    DataOutputStream     outStream = null; 
    int           requestedAction = MAIN_PAGE;

    // Wait for new incoming connection
    Socket clientSocket = serverSocket.accept();
    System.out.println("Connection from "+
        clientSocket.getInetAddress().getHostAddress()+
        ":"+clientSocket.getPort());

    // Create input/output streams then check what page
    // was requested
    try {
      inStream = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
      outStream = new DataOutputStream(clientSocket.getOutputStream());
      requestedAction = parseLine(inStream);
    } catch (IOException e){
      // error setting up in and out streams with this client, abort
      inStream.close();
      outStream.close();
      clientSocket.close();
      return;
    }

    // if the video stream was requested, create a ClientConnection object
    // and add it to the list of clients. The capture thread is in charged
    // of sending mpjeg frames to all clients in the list
    if (requestedAction == VIDEO_STREAM) {

      // add new client to clients list 
      try {
        synchronized (clients){
          clients.add(new ClientConnection(clientSocket, inStream, outStream));

          // if this is the first client, start the capture
          if (clients.size() == 1)
            frameGrabber.startCapture();
        }
      } catch (IOException e) {
        // error connecting with client
        System.out.println("Disconnected from "+
            clientSocket.getInetAddress().getHostAddress()+
            ":"+clientSocket.getPort());
        
        inStream.close();
        outStream.close();
        clientSocket.close();
      }
      
      // exit at this stage 
      return;
    }
    
    // check what other page was requested
    try {
      switch(requestedAction){
      case WEBCAM_PAGE:
        // send webcam viewer page
        ClientConnection.sendWebcamPage(outStream);
        break;
      case UPDATE_CONTROL_VALUE:
        // parse http line and update the requested control's value
        ClientConnection.updateControlValue(controlList, frameGrabber, httpLineFromClient);
        //fallthrough so we re-send the control list
      case CONTROL_PAGE:
        // send the control list page
        ClientConnection.sendControlListPage(controlList, frameGrabber.getJPGQuality(), outStream);
        break;
      case MAIN_PAGE:
      default:
        // send the main page
        ClientConnection.sendMainPage(outStream);
        break;
      }
    } catch (Exception e){
      e.printStackTrace();
    } finally {
      // close the connection with the client
      try {
        System.out.println("Disconnected from "+
            clientSocket.getInetAddress().getHostAddress()+
            ":"+clientSocket.getPort());
        
        inStream.close();
        outStream.close();
        clientSocket.close();
      } catch (Exception e) {}
    }
  }

  private int parseLine(BufferedReader in) throws IOException{
    // read the first line to determine which page to send
    httpLineFromClient = in.readLine();
    
    if (httpLineFromClient == null)
      throw new IOException("Read null line");

    // if the line contains the word webcam, we want the video viewing page
    if (httpLineFromClient.indexOf("webcam") != -1)
      return WEBCAM_PAGE;

    // if the line contains the word control, we want the control list page
    if (httpLineFromClient.indexOf("control") != -1)
      return CONTROL_PAGE;
    
    // if the line contains the word stream, we want the control list page
    if (httpLineFromClient.indexOf("stream") != -1)
      return VIDEO_STREAM;
    
    // if the line contains the word update, we want to update a control's value
    if (httpLineFromClient.indexOf("update") != -1)
      return UPDATE_CONTROL_VALUE;
    
    return MAIN_PAGE;
  }

  /**
   * Implements the server thread.
   */
  @Override
  public void run() {
    try {
      // run the main loop only until we are interrupted 
      while (! Thread.interrupted())
        serverMainLoop();

    } catch (V4L4JException e){
      // error starting the capture when the first client connected
    } catch (Exception e){
      // error accepting new client connection over server socket
      // or closing connection with a client
    }
    System.out.println("Server thread exiting");
  }

  @Override
  public void nextFrame(VideoFrame frame) {
    Vector<ClientConnection> copyClients = null;

    // copy client vector
    synchronized(clients){
      copyClients = new Vector<ClientConnection>(clients);
    }

    // send the frame to each client
    for(ClientConnection client: copyClients){
      try {
        client.sendNextFrame(frame);
      } catch (IOException e) {
        // error sending frame to this client
        
        // close the connection
        client.stop();
        
        // remove client from list
        synchronized(clients){
          clients.remove(client);

          // stop capture if there are no more clients
          if (clients.size() == 0)
            frameGrabber.stopCapture();
        }
      }
    }

    // recycle frame
    frame.recycle();
  }

  @Override
  public void exceptionReceived(V4L4JException e) {
    // Error capturing frames, stop the frame grabber
    frameGrabber.stopCapture();
  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.