/
WebcamUpdater.java
271 lines (216 loc) · 6.51 KB
/
WebcamUpdater.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
package com.github.sarxos.webcam;
import java.awt.image.BufferedImage;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.sarxos.webcam.ds.cgt.WebcamReadImageTask;
/**
* The goal of webcam updater class is to update image in parallel, so all calls
* to fetch image invoked on webcam instance will be non-blocking (will return
* immediately).
*
* @author Bartosz Firyn (sarxos)
*/
public class WebcamUpdater implements Runnable, ThreadFactory, UncaughtExceptionHandler {
/**
* Class used to asynchronously notify all webcam listeners about new image
* available.
*
* @author Bartosz Firyn (sarxos)
*/
private static class ImageNotification implements Runnable {
/**
* Camera.
*/
private final Webcam webcam;
/**
* Acquired image.
*/
private final BufferedImage image;
/**
* Create new notification.
*
* @param webcam the webcam from which image has been acquired
* @param image the acquired image
*/
public ImageNotification(Webcam webcam, BufferedImage image) {
this.webcam = webcam;
this.image = image;
}
@Override
public void run() {
if (image != null) {
WebcamEvent we = new WebcamEvent(WebcamEventType.NEW_IMAGE, webcam, image);
for (WebcamListener l : webcam.getWebcamListeners()) {
try {
l.webcamImageObtained(we);
} catch (Exception e) {
LOG.error(String.format("Notify image acquired, exception when calling listener %s", l.getClass()), e);
}
}
}
}
}
/**
* Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(WebcamUpdater.class);
/**
* Used to count thread in the executor pool.
*/
private static final AtomicInteger number = new AtomicInteger(0);
/**
* Target FPS.
*/
private static final int TARGET_FPS = 50;
/**
* Executor service.
*/
private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(this);
/**
* Executor service for image notifications.
*/
private final ExecutorService notificator = Executors.newSingleThreadExecutor(this);
/**
* Cached image.
*/
private final AtomicReference<BufferedImage> image = new AtomicReference<BufferedImage>();
/**
* Webcam to which this updater is attached.
*/
private Webcam webcam = null;
/**
* Current FPS rate.
*/
private volatile double fps = 0;
/**
* Is updater running.
*/
private volatile boolean running = false;
private volatile boolean imageNew = false;
/**
* Construct new webcam updater.
*
* @param webcam the webcam to which updater shall be attached
*/
protected WebcamUpdater(Webcam webcam) {
this.webcam = webcam;
}
/**
* Start updater.
*/
public void start() {
running = true;
image.set(new WebcamReadImageTask(Webcam.getDriver(), webcam.getDevice()).getImage());
executor.execute(this);
LOG.debug("Webcam updater has been started");
}
/**
* Stop updater.
*/
public void stop() {
running = false;
LOG.debug("Webcam updater has been stopped");
}
@Override
public void run() {
if (!running) {
return;
}
long t1 = 0;
long t2 = 0;
// Calculate time required to fetch 1 picture.
WebcamDriver driver = Webcam.getDriver();
WebcamDevice device = webcam.getDevice();
assert driver != null;
assert device != null;
BufferedImage img = null;
t1 = System.currentTimeMillis();
img = webcam.transform(new WebcamReadImageTask(driver, device).getImage());
t2 = System.currentTimeMillis();
image.set(img);
imageNew = true;
// Calculate delay required to achieve target FPS. In some cases it can
// be less than 0 because camera is not able to serve images as fast as
// we would like to. In such case just run with no delay, so maximum FPS
// will be the one supported by camera device in the moment.
long delta = t2 - t1 + 1; // +1 to avoid division by zero
long delay = Math.max((1000 / TARGET_FPS) - delta, 0);
fps = (4 * fps + 1000 / delta) / 5;
// reschedule task
executor.schedule(this, delay, TimeUnit.MILLISECONDS);
// notify webcam listeners about the new image available
notifyWebcamImageObtained(webcam, image.get());
}
/**
* Asynchronously start new thread which will notify all webcam listeners
* about the new image available.
*/
protected void notifyWebcamImageObtained(Webcam webcam, BufferedImage image) {
// notify webcam listeners of new image available, do that only if there
// are any webcam listeners available because there is no sense to start
// additional threads for no purpose
if (webcam.getWebcamListenersCount() > 0) {
notificator.execute(new ImageNotification(webcam, image));
}
}
/**
* Return currently available image. This method will return immediately
* while it was been called after camera has been open. In case when there
* are parallel threads running and there is a possibility to call this
* method in the opening time, or before camera has been open at all, this
* method will block until webcam return first image. Maximum blocking time
* will be 10 seconds, after this time method will return null.
*
* @return Image stored in cache
*/
public BufferedImage getImage() {
int i = 0;
while (image.get() == null) {
// Just in case if another thread starts calling this method before
// updater has been properly started. This will loop while image is
// not available.
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// Return null if more than 10 seconds passed (timeout).
if (i++ > 100) {
return null;
}
}
imageNew = false;
return image.get();
}
protected boolean isImageNew() {
return imageNew;
}
/**
* Return current FPS number. It is calculated in real-time on the base of
* how often camera serve new image.
*
* @return FPS number
*/
public double getFPS() {
return fps;
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, String.format("webcam-updater-thread-%d", number.incrementAndGet()));
t.setDaemon(true);
t.setUncaughtExceptionHandler(this);
return t;
}
@Override
public void uncaughtException(Thread t, Throwable e) {
LOG.error(String.format("Exception in thread %s", t.getName()), e);
}
}