9
9
import java .util .Iterator ;
10
10
import java .util .List ;
11
11
import java .util .concurrent .CopyOnWriteArrayList ;
12
+ import java .util .concurrent .ExecutorService ;
13
+ import java .util .concurrent .Executors ;
14
+ import java .util .concurrent .ThreadFactory ;
12
15
import java .util .concurrent .TimeUnit ;
13
16
import java .util .concurrent .TimeoutException ;
14
17
import java .util .concurrent .atomic .AtomicBoolean ;
34
37
*/
35
38
public class Webcam {
36
39
40
+ /**
41
+ * Class used to asynchronously notify all webcam listeners about new image
42
+ * available.
43
+ *
44
+ * @author Bartosz Firyn (sarxos)
45
+ */
46
+ private static final class ImageNotification implements Runnable {
47
+
48
+ /**
49
+ * Camera.
50
+ */
51
+ private final Webcam webcam ;
52
+
53
+ /**
54
+ * Acquired image.
55
+ */
56
+ private final BufferedImage image ;
57
+
58
+ /**
59
+ * Create new notification.
60
+ *
61
+ * @param webcam the webcam from which image has been acquired
62
+ * @param image the acquired image
63
+ */
64
+ public ImageNotification (Webcam webcam , BufferedImage image ) {
65
+ this .webcam = webcam ;
66
+ this .image = image ;
67
+ }
68
+
69
+ @ Override
70
+ public void run () {
71
+ if (image != null ) {
72
+ WebcamEvent we = new WebcamEvent (WebcamEventType .NEW_IMAGE , webcam , image );
73
+ for (WebcamListener l : webcam .getWebcamListeners ()) {
74
+ try {
75
+ l .webcamImageObtained (we );
76
+ } catch (Exception e ) {
77
+ LOG .error (String .format ("Notify image acquired, exception when calling listener %s" , l .getClass ()), e );
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ private final class NotificationThreadFactory implements ThreadFactory {
85
+
86
+ @ Override
87
+ public Thread newThread (Runnable r ) {
88
+ Thread t = new Thread (r , String .format ("notificator-[%s]" , getName ()));
89
+ t .setUncaughtExceptionHandler (WebcamExceptionHandler .getInstance ());
90
+ t .setDaemon (true );
91
+ return t ;
92
+ }
93
+ }
94
+
37
95
/**
38
96
* Logger instance.
39
97
*/
@@ -117,15 +175,24 @@ public class Webcam {
117
175
/**
118
176
* Webcam image updater.
119
177
*/
120
- private WebcamUpdater updater = new WebcamUpdater ( this ) ;
178
+ private volatile WebcamUpdater updater = null ;
121
179
122
180
/**
123
- * IMage transformer.
181
+ * Image transformer.
124
182
*/
125
183
private volatile WebcamImageTransformer transformer = null ;
126
184
185
+ /**
186
+ * Lock which denies access to the given webcam when it's already in use by
187
+ * other webcam capture API process or thread.
188
+ */
127
189
private WebcamLock lock = null ;
128
190
191
+ /**
192
+ * Executor service for image notifications.
193
+ */
194
+ private ExecutorService notificator = null ;
195
+
129
196
/**
130
197
* Webcam class.
131
198
*
@@ -140,6 +207,21 @@ protected Webcam(WebcamDevice device) {
140
207
this .lock = new WebcamLock (this );
141
208
}
142
209
210
+ /**
211
+ * Asynchronously start new thread which will notify all webcam listeners
212
+ * about the new image available.
213
+ */
214
+ protected void notifyWebcamImageAcquired (BufferedImage image ) {
215
+
216
+ // notify webcam listeners of new image available, do that only if there
217
+ // are any webcam listeners available because there is no sense to start
218
+ // additional threads for no purpose
219
+
220
+ if (getWebcamListenersCount () > 0 ) {
221
+ notificator .execute (new ImageNotification (this , image ));
222
+ }
223
+ }
224
+
143
225
/**
144
226
* Open the webcam in blocking (synchronous) mode.
145
227
*
@@ -175,9 +257,10 @@ public boolean open(boolean async) {
175
257
176
258
if (open .compareAndSet (false , true )) {
177
259
178
- assert updater != null ;
179
260
assert lock != null ;
180
261
262
+ notificator = Executors .newSingleThreadExecutor (new NotificationThreadFactory ());
263
+
181
264
// lock webcam for other Java (only) processes
182
265
183
266
lock .lock ();
@@ -195,16 +278,18 @@ public boolean open(boolean async) {
195
278
} catch (WebcamException e ) {
196
279
lock .unlock ();
197
280
open .set (false );
281
+ LOG .debug ("Webcam exception when opening" , e );
198
282
throw e ;
199
283
}
200
284
201
285
LOG .debug ("Webcam is now open {}" , getName ());
202
286
203
287
// setup non-blocking configuration
204
288
205
- asynchronous = async ;
206
-
207
- if (async ) {
289
+ if (asynchronous = async ) {
290
+ if (updater == null ) {
291
+ updater = new WebcamUpdater (this );
292
+ }
208
293
updater .start ();
209
294
}
210
295
@@ -243,7 +328,6 @@ public boolean close() {
243
328
244
329
LOG .debug ("Closing webcam {}" , getName ());
245
330
246
- assert updater != null ;
247
331
assert lock != null ;
248
332
249
333
// close webcam
@@ -261,7 +345,9 @@ public boolean close() {
261
345
}
262
346
263
347
// stop updater
264
- updater .stop ();
348
+ if (asynchronous ) {
349
+ updater .stop ();
350
+ }
265
351
266
352
// remove shutdown hook (it's not more necessary)
267
353
removeShutdownHook ();
@@ -284,6 +370,15 @@ public boolean close() {
284
370
}
285
371
}
286
372
373
+ notificator .shutdown ();
374
+ while (!notificator .isTerminated ()) {
375
+ try {
376
+ notificator .awaitTermination (100 , TimeUnit .MILLISECONDS );
377
+ } catch (InterruptedException e ) {
378
+ return false ;
379
+ }
380
+ }
381
+
287
382
LOG .debug ("Webcam {} has been closed" , getName ());
288
383
289
384
} else {
@@ -552,7 +647,7 @@ public BufferedImage getImage() {
552
647
553
648
// notify webcam listeners about new image available
554
649
555
- updater . notifyWebcamImageObtained ( this , image );
650
+ notifyWebcamImageAcquired ( image );
556
651
557
652
return image ;
558
653
}
0 commit comments