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 }