/* * Copyright 2013-2014 Andrew Merrill & Cel Skeggs * * This file is part of the CCRE, the Common Chicken Runtime Engine. * * The CCRE is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * The CCRE 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 Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with the CCRE. If not, see <http://www.gnu.org/licenses/>. */ package miscellaneous; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.geom.AffineTransform; import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.net.Socket; import java.util.Iterator; import java.util.NoSuchElementException; import javax.imageio.ImageIO; /** * This file is provided in the hope that it is useful, but is not currently * used anywhere in the CCRE. * * It was used in some of our programs in the past, though. * * @author skeggsc */ @SuppressWarnings("javadoc") public class Webcam extends Thread { private static GraphicsConfiguration graphicsConfiguration; private static final boolean retryConnections = true; static { GraphicsEnvironment graphEnv = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice graphDevice = graphEnv.getDefaultScreenDevice(); graphicsConfiguration = graphDevice.getDefaultConfiguration(); } private static Iterable<String> readLinesUntilEmptyLine(final InputStream socketInputStream) { return new Iterable<String>() { @Override public Iterator<String> iterator() { return new Iterator<String>() { public String next = null; @Override public boolean hasNext() { if (next == null) { try { next = readLine(socketInputStream); } catch (IOException ex) { throw new WrappedIOException(ex); } } return !next.isEmpty(); } @Override public String next() throws NoSuchElementException { if (!hasNext()) { throw new NoSuchElementException(); } String out = next; next = null; return out; } @Override public void remove() { throw new UnsupportedOperationException("Not supported yet."); } }; } }; } static String readLine(InputStream stream) throws IOException, EOFException { final int BUFSIZE = 1024; byte[] buffer = new byte[BUFSIZE]; int b; int i = 0; while (true) { b = stream.read(); if (b == -1) { throw new EOFException(); } buffer[i] = (byte) b; if (i > 0 && b == 10 && buffer[i - 1] == 13) { return new String(buffer, 0, i - 1, "UTF-8"); } else { i++; if (i >= BUFSIZE) { break; } } } return null; } private final String label; private final String address; private BufferedImage image; private boolean active; private boolean keepRunning; private int frameCount; private long startTime; private int rotate = 0; private volatile long lastReceived = 0; private Socket curclose = null; public Webcam(String label, String address, boolean active, int rotate) { this.label = label; this.address = address; this.active = active; this.rotate = rotate; this.image = null; this.keepRunning = true; this.start(); } public void reconnect() { System.out.println("DoInterrupt"); if (curclose != null) { try { curclose.close(); } catch (IOException ex) { System.out.println("Got error while closing socket!"); ex.printStackTrace(); } } this.interrupt(); } @Override public void run() { while (keepRunning) { System.out.println("Try connect at " + System.currentTimeMillis()); try { connectToWebcam(); Thread.sleep(1000); } catch (EOFException eofe) { System.out.println("reached end of file"); } catch (WrappedIOException wio) { System.out.println("IO Exception: " + wio.getCause()); } catch (IOException ioe) { System.out.println("IO Exception: " + ioe); } catch (InterruptedException inte) { System.out.println("Interrupted: " + inte); } if (!retryConnections) { break; } } System.out.println("Finished thread."); } public void connectToWebcam() throws IOException, EOFException { String requestString = "GET /mjpg/video.mjpg HTTP/1.1\n" + "User-Agent: HTTPStreamClient\n" + "Connection: Keep-Alive\n" + "Cache-Control: no-cache\n" + "Authorization: Basic RlJDOkZSQw==\n\n"; Socket socket; BufferedInputStream socketInputStream; BufferedOutputStream socketOutputStream; String boundary = null; socket = new Socket(address, 80); curclose = socket; try { socketInputStream = new BufferedInputStream(socket.getInputStream()); socketOutputStream = new BufferedOutputStream(socket.getOutputStream()); socketOutputStream.write(requestString.getBytes("UTF-8")); socketOutputStream.flush(); String header; assertLine(socketInputStream, "HTTP/1.0 200 OK"); for (String line : readLinesUntilEmptyLine(socketInputStream)) { if (line.startsWith("Content-Type:")) { int i = line.indexOf("boundary="); boundary = line.substring(i + 9); } } frameCount = 0; startTime = System.currentTimeMillis(); while (keepRunning && !Thread.interrupted()) { header = nextNonemptyLine(socketInputStream); if (!header.endsWith(boundary)) { throw new IOException("not a boundary: " + header); } int contentLength = -1; for (String line : readLinesUntilEmptyLine(socketInputStream)) { if (line.startsWith("Content-Length:")) { String contentLengthString = line.substring(line.indexOf(": ") + 2); try { contentLength = Integer.parseInt(contentLengthString); } catch (NumberFormatException nfe) { throw new IOException("invalid content length: |" + contentLengthString + "|"); } } } if (contentLength < 1 || contentLength > 200000) { throw new IOException("content length out of range: " + contentLength); } BufferedImage newImage = readImage(socketInputStream, contentLength); if (rotate != 0) { AffineTransform rotateTransform = AffineTransform.getQuadrantRotateInstance(rotate); AffineTransformOp rotateOp = new AffineTransformOp(rotateTransform, AffineTransformOp.TYPE_BILINEAR); BufferedImage rotatedImage = graphicsConfiguration.createCompatibleImage(newImage.getHeight(), newImage.getWidth()); Graphics2D pen2 = rotatedImage.createGraphics(); pen2.drawImage(newImage, rotateOp, newImage.getWidth() / 2, newImage.getHeight() / 2); newImage = rotatedImage; } synchronized (this) { this.image = newImage; } lastReceived = System.nanoTime(); if (active) { // Here is where we'd send the image to the display panel. } frameCount++; if (frameCount % 360 == 0) { long currentTime = System.currentTimeMillis(); double fps = frameCount / ((currentTime - startTime) / 1000.0); System.out.println("FPS (camera " + label + "): " + fps); frameCount = 0; startTime = currentTime; } } System.out.println("Done!"); } finally { curclose = null; } } private void assertLine(InputStream stream, String require) throws IOException { String line = readLine(stream); if (!line.equals(require)) { throw new IOException("Expected '" + require + "', got '" + line + "'"); } } private String nextNonemptyLine(BufferedInputStream socketInputStream) throws IOException { while (true) { String line = readLine(socketInputStream); if (!line.isEmpty()) { return line; } } } BufferedImage readImage(InputStream stream, int contentLength) throws IOException, EOFException { byte[] imageBytes = new byte[contentLength]; int bytesRemaining = contentLength; int offset = 0; while (bytesRemaining > 0) { int bytesRead = stream.read(imageBytes, offset, bytesRemaining); if (bytesRead == -1) { throw new EOFException(); } bytesRemaining -= bytesRead; offset += bytesRead; } BufferedImage newImage = ImageIO.read(new ByteArrayInputStream(imageBytes)); return newImage; } public synchronized BufferedImage getImage() { return image; } public boolean isUpToDate() { return (System.nanoTime() - lastReceived) < 1000 * 1000000L; // 1 second } @SuppressWarnings(value = "serial") private static class WrappedIOException extends RuntimeException { WrappedIOException(IOException io) { super(io); } } }