001 package com.github.sarxos.webcam; 002 003 import java.awt.image.BufferedImage; 004 import java.io.BufferedOutputStream; 005 import java.io.BufferedReader; 006 import java.io.ByteArrayOutputStream; 007 import java.io.IOException; 008 import java.io.InputStreamReader; 009 import java.net.ServerSocket; 010 import java.net.Socket; 011 import java.net.SocketException; 012 import java.util.concurrent.Executor; 013 import java.util.concurrent.Executors; 014 import java.util.concurrent.ThreadFactory; 015 import java.util.concurrent.atomic.AtomicBoolean; 016 017 import javax.imageio.ImageIO; 018 019 import org.slf4j.Logger; 020 import org.slf4j.LoggerFactory; 021 022 023 public class WebcamStreamer implements ThreadFactory, WebcamListener { 024 025 private static final Logger LOG = LoggerFactory.getLogger(WebcamStreamer.class); 026 027 private static final String BOUNDARY = "mjpegframe"; 028 029 private class Acceptor implements Runnable { 030 031 @Override 032 public void run() { 033 try { 034 ServerSocket server = new ServerSocket(port); 035 while (true) { 036 executor.execute(new Connection(server.accept())); 037 } 038 } catch (Exception e) { 039 LOG.error("Cannot accept socket connection", e); 040 } 041 } 042 } 043 044 private class Reader implements Runnable { 045 046 @Override 047 public void run() { 048 while (webcam.isOpen()) { 049 050 image = webcam.getImage(); 051 052 if (image != null) { 053 synchronized (lock) { 054 lock.notifyAll(); 055 } 056 } 057 058 try { 059 Thread.sleep(getDelay()); 060 } catch (InterruptedException e) { 061 LOG.error("Nasty interrupted exception", e); 062 } 063 } 064 } 065 } 066 067 private class Connection implements Runnable { 068 069 private static final String CRLF = "\r\n"; 070 071 private Socket socket = null; 072 073 public Connection(Socket socket) { 074 this.socket = socket; 075 } 076 077 @Override 078 public void run() { 079 080 BufferedReader br = null; 081 BufferedOutputStream bos = null; 082 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 083 084 try { 085 086 br = new BufferedReader(new InputStreamReader(socket.getInputStream())); 087 bos = new BufferedOutputStream(socket.getOutputStream()); 088 089 try { 090 091 while (webcam.isOpen()) { 092 093 if (socket.isInputShutdown()) { 094 break; 095 } 096 if (socket.isClosed()) { 097 break; 098 } 099 100 String line = br.readLine(); 101 if (line == null || line.isEmpty()) { 102 bos.write(("--" + BOUNDARY + "--" + CRLF).getBytes()); 103 LOG.info("Breaking"); 104 break; 105 } 106 107 if (line.startsWith("GET")) { 108 109 socket.setSoTimeout(0); 110 socket.setKeepAlive(true); 111 112 getImage(); 113 114 StringBuilder sb = new StringBuilder(); 115 sb.append("HTTP/1.0 200 OK").append(CRLF); 116 sb.append("Connection: keep-alive").append(CRLF); 117 sb.append("Cache-Control: no-cache").append(CRLF); 118 sb.append("Cache-Control: private").append(CRLF); 119 sb.append("Pragma: no-cache").append(CRLF); 120 sb.append("Content-type: multipart/x-mixed-replace; boundary=--").append(BOUNDARY).append(CRLF); 121 sb.append(CRLF); 122 123 bos.write(sb.toString().getBytes()); 124 125 do { 126 127 if (socket.isInputShutdown()) { 128 break; 129 } 130 if (socket.isClosed()) { 131 break; 132 } 133 134 baos.reset(); 135 136 ImageIO.write(getImage(), "JPG", baos); 137 138 sb.delete(0, sb.length()); 139 sb.append("--").append(BOUNDARY).append(CRLF); 140 sb.append("Content-type: image/jpeg").append(CRLF); 141 sb.append("Content-Length: ").append(baos.size()).append(CRLF); 142 sb.append(CRLF); 143 144 bos.write(sb.toString().getBytes()); 145 bos.write(baos.toByteArray()); 146 bos.write(CRLF.getBytes()); 147 148 try { 149 bos.flush(); 150 } catch (SocketException e) { 151 if (LOG.isTraceEnabled()) { 152 LOG.error("Socket exception", e); 153 } 154 } 155 156 Thread.sleep(getDelay()); 157 158 } while (webcam.isOpen()); 159 } 160 } 161 } catch (Exception e) { 162 163 String message = e.getMessage(); 164 if (message != null && message.startsWith("Software caused connection abort")) { 165 LOG.info("User closed stream"); 166 return; 167 } 168 169 LOG.error("Error", e); 170 171 bos.write("HTTP/1.0 501 Internal Server Error\r\n\r\n\r\n".getBytes()); 172 } 173 174 } catch (IOException e) { 175 LOG.error("I/O exception", e); 176 } finally { 177 if (br != null) { 178 try { 179 br.close(); 180 } catch (IOException e) { 181 LOG.error("Cannot close buffered reader", e); 182 } 183 } 184 if (bos != null) { 185 try { 186 bos.close(); 187 } catch (IOException e) { 188 LOG.error("Cannot close data output stream", e); 189 } 190 } 191 } 192 } 193 } 194 195 private Webcam webcam = null; 196 private double fps = 0; 197 private BufferedImage image = null; 198 private Object lock = new Object(); 199 private int number = 0; 200 private int port = 0; 201 private Executor executor = Executors.newCachedThreadPool(this); 202 private AtomicBoolean open = new AtomicBoolean(false); 203 private AtomicBoolean initialized = new AtomicBoolean(false); 204 205 public WebcamStreamer(int port, Webcam webcam, double fps, boolean start) { 206 207 if (webcam == null) { 208 throw new IllegalArgumentException("Webcam for streaming cannot be null"); 209 } 210 211 this.port = port; 212 this.webcam = webcam; 213 this.fps = fps; 214 215 if (start) { 216 start(); 217 } 218 } 219 220 private long getDelay() { 221 return (long) (1000 / fps); 222 } 223 224 private BufferedImage getImage() { 225 if (image == null) { 226 synchronized (lock) { 227 try { 228 lock.wait(); 229 } catch (InterruptedException e) { 230 LOG.error("Nasty interrupted exception", e); 231 } catch (Exception e) { 232 throw new RuntimeException("Problem waiting on lock", e); 233 } 234 } 235 } 236 return image; 237 } 238 239 public void start() { 240 if (open.compareAndSet(false, true)) { 241 webcam.addWebcamListener(this); 242 webcam.open(); 243 } 244 } 245 246 @Override 247 public Thread newThread(Runnable r) { 248 Thread thread = new Thread(r, String.format("streamer-thread-%s", number++)); 249 thread.setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance()); 250 thread.setDaemon(true); 251 return thread; 252 } 253 254 @Override 255 public void webcamOpen(WebcamEvent we) { 256 if (initialized.compareAndSet(false, true)) { 257 executor.execute(new Acceptor()); 258 executor.execute(new Reader()); 259 } 260 } 261 262 @Override 263 public void webcamClosed(WebcamEvent we) { 264 // TODO: shutdown executor? 265 } 266 267 @Override 268 public void webcamDisposed(WebcamEvent we) { 269 } 270 271 @Override 272 public void webcamImageObtained(WebcamEvent we) { 273 } 274 275 public static void main(String[] args) throws InterruptedException { 276 new WebcamStreamer(8081, Webcam.getDefault(), 0.5, true); 277 do { 278 Thread.sleep(1000); 279 } while (true); 280 } 281 }