FIX:run() method now works on Sony Camera ADD:CaptureToFile demo application
[capturemjpeg.git] / src / it / lilik / capturemjpeg / AsyncProducer.java
blobb5adc17ed1592f9403170535ad2f97d124617a1b
1 /*
2 *
3 */
4 package it.lilik.capturemjpeg;
6 import it.lilik.capturemjpeg.utils.CircularBuffer;
8 import java.io.BufferedInputStream;
9 import java.io.ByteArrayInputStream;
10 import java.io.ByteArrayOutputStream;
11 import java.io.IOException;
12 import java.io.InputStream;
14 import org.apache.commons.httpclient.Header;
15 import org.apache.commons.httpclient.HttpClient;
16 import org.apache.commons.httpclient.HttpException;
17 import org.apache.commons.httpclient.HttpMethod;
18 import org.apache.commons.httpclient.UsernamePasswordCredentials;
19 import org.apache.commons.httpclient.auth.AuthScope;
21 /**
22 * This class produces JPEG images in <code>ByteArrayInputStream</code>
23 * getting them from a MJPEG stream.
25 * @author Alessio Caiazza
26 * @author Cosimo Cecchi
29 class AsyncProducer extends Thread {
31 /** timeout for HTTP request */
32 protected static final int HTTP_TIMEOUT = 5000;
33 private static final int BYTES_TO_READ = 128;
34 /** client */
35 protected HttpClient client;
36 /** method */
37 protected HttpMethod method;
38 /** used for stopping this thread */
39 protected boolean shouldStop;
40 /** <code>true</code> if there are pending changes in <code>method</code> */
41 protected boolean isChangePending;
42 /** circular buffer for images */
43 protected CircularBuffer buffer;
47 /**
48 * @return the shouldStop
50 public boolean getShouldStop() {
51 return shouldStop;
54 /**
55 * It stops this thread when the current image if finished
56 * @param shouldStop the shouldStop to set
58 public void setShouldStop(boolean shouldStop) {
59 this.shouldStop = shouldStop;
62 /**
63 * It changes the URI.<br>
64 * A new connection will be performed after a complete
65 * image reading.
66 * @param method the method to set
68 public void setMethod(HttpMethod method) {
69 synchronized (this.method) {
70 this.method = method;
71 this.isChangePending = true;
72 this.method.setFollowRedirects(true);
76 private void setCredential(String username, String password) {
77 UsernamePasswordCredentials creds =
78 new UsernamePasswordCredentials(username, password);
79 client.getState().setCredentials(AuthScope.ANY, creds);
82 /**
83 * @param method
85 public AsyncProducer(HttpMethod method) {
86 this(method, null, null);
89 /**
90 * This constructor provides support for HTTP AUTH
92 * @param method the MJPEG stream URI
93 * @param username HTTP AUTH username
94 * @param password HTTP AUTH password
96 public AsyncProducer(HttpMethod method, String username, String password) {
97 this.method = method;
98 this.shouldStop = false;
99 buffer = new CircularBuffer();
100 // create a singular HttpClient object
101 this.client = new HttpClient();
103 // establish a connection within 5 seconds
104 this.client.getHttpConnectionManager().getParams()
105 .setConnectionTimeout(HTTP_TIMEOUT);
107 if (username != null && password != null ) {
108 this.setCredential(username, password);
110 setMethod(this.method);
113 /* (non-Javadoc)
114 * @see java.lang.Runnable#run()
116 public void run() {
117 BufferedInputStream is = null;
118 InputStream responseBody = null;
119 String boundary = "";
121 while (!this.shouldStop) {
123 if (this.isChangePending) {
124 synchronized (this.method) {
125 // establish the connection
126 try {
127 this.client.executeMethod(this.method);
128 responseBody = this.method.getResponseBodyAsStream();
129 is = new BufferedInputStream (responseBody);
130 } catch (HttpException e) {
131 System.err.println("Http error connecting to '" + this.method.getPath() + "'");
132 System.err.println(e.getMessage());
133 continue;
134 } catch (IOException e) {
135 System.err.println("Unable to connect to '" + this.method.getPath() + "'");
138 // automagically guess the boundary
139 Header contentType = this.method.getResponseHeader("Content-Type");
140 String contentTypeS = contentType.toString();
141 int startIndex = contentTypeS.indexOf("boundary=");
142 int endIndex = contentTypeS.indexOf(';', startIndex);
143 if (endIndex == -1) //boundaty is the last option
144 endIndex = contentTypeS.length();
145 boundary = contentTypeS.substring(startIndex + 9, endIndex);
147 this.isChangePending = false;
148 } //end syncronyzed
149 } //end if(isChangePending)
151 /* Now flip flop to parse the images. We look for the JPEG magic MIME identifier,
152 * which is composed by the first two bites set to 0xff and 0xd8. We stop when we find
153 * another "--$boundary" string.
156 try {
157 byte tmp[] = new byte[BYTES_TO_READ];
158 ByteArrayOutputStream os = new ByteArrayOutputStream();
159 char delimiter[] = (boundary).toCharArray();
160 byte magicMIME[] = {(byte) 0xff,
161 (byte) 0xd8};
162 byte partialMatch[] = new byte[delimiter.length];
163 int bytesRead = 0;
164 int last = 0;
165 boolean foundStart = false;
167 /* This first cycle ensures we start parsing the stream on the first
168 * magic MIME identifier.
170 /*while (true) {
171 int data;
172 data = is.read();
174 if ((byte) data == magicMIME[0]) {
175 foundStart = true;
176 is.mark(2);
177 continue;
180 if (foundStart && ((byte) data) == magicMIME[1]) {
181 is.reset();
182 break;
185 if (foundStart)
186 foundStart = false;
187 } */
188 /* This cycle splits the stream into several ByteArrayInputStreams containing
189 * the real image, and pushs them to our internal buffer.
191 int startIdx = 0;
192 last = 0;
193 boolean lookinForMagicMIME = true;
194 boolean imageComplete;
195 imageComplete = false;
196 while (!imageComplete && (bytesRead = is.read(tmp)) != -1) {
197 for (int i = 0; i < bytesRead; i++) {
198 if (lookinForMagicMIME) {
199 if (tmp[i] == magicMIME[last]) {
200 partialMatch[last] = tmp[i];
201 last++;
203 if (last == 2) {
204 os.write(partialMatch, 0, 2);
205 last = 0;
206 lookinForMagicMIME = false;
208 } else {
209 last = 0;
211 }else { //lookinForBoundary
212 if (tmp[i] == delimiter[last]) {
213 partialMatch[last] = tmp[i];
214 last++;
216 if (last == delimiter.length) {
217 this.buffer.push(new ByteArrayInputStream(os.toByteArray()));
218 lookinForMagicMIME = true;
219 last = 0;
220 imageComplete = true;
221 break;
223 } else if (last > 0) {
224 partialMatch[last++] = tmp[i];
225 os.write(partialMatch, 0 , last);
226 last = 0;
227 } else {
228 os.write(tmp[i]);
235 }catch (IOException e) {
236 // TODO: handle exception
243 * Return the first image available as {@link java.io.ByteArrayInputStream}.
244 * @return the first image available
246 public ByteArrayInputStream pop() {
247 return this.buffer.pop();
251 * Return <code>true</code> if there is at least one image available into
252 * the internal buffer.
254 * @return the availability status
256 public boolean isImageAvailable() {
257 return !this.buffer.isEmpty();