Skip to content

Commit d9fcae8

Browse files
committed
Do not spawn unecessary threads, refs #228
1 parent 4371adb commit d9fcae8

File tree

6 files changed

+133
-81
lines changed

6 files changed

+133
-81
lines changed

webcam-capture/src/example/java/WebcamViewerExample.java

+20-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import javax.swing.SwingUtilities;
1010

1111
import com.github.sarxos.webcam.Webcam;
12+
import com.github.sarxos.webcam.WebcamDiscoveryEvent;
13+
import com.github.sarxos.webcam.WebcamDiscoveryListener;
1214
import com.github.sarxos.webcam.WebcamEvent;
1315
import com.github.sarxos.webcam.WebcamListener;
1416
import com.github.sarxos.webcam.WebcamPanel;
@@ -21,7 +23,7 @@
2123
*
2224
* @author Bartosz Firyn (SarXos)
2325
*/
24-
public class WebcamViewerExample extends JFrame implements Runnable, WebcamListener, WindowListener, UncaughtExceptionHandler, ItemListener {
26+
public class WebcamViewerExample extends JFrame implements Runnable, WebcamListener, WindowListener, UncaughtExceptionHandler, ItemListener, WebcamDiscoveryListener {
2527

2628
private static final long serialVersionUID = 1L;
2729

@@ -32,6 +34,8 @@ public class WebcamViewerExample extends JFrame implements Runnable, WebcamListe
3234
@Override
3335
public void run() {
3436

37+
Webcam.addDiscoveryListener(this);
38+
3539
setTitle("Java Webcam Capture POC");
3640
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
3741
setLayout(new BorderLayout());
@@ -155,6 +159,7 @@ public void itemStateChanged(ItemEvent e) {
155159
System.out.println("selected " + webcam.getName());
156160

157161
panel = new WebcamPanel(webcam, false);
162+
panel.setFPSDisplayed(true);
158163

159164
add(panel, BorderLayout.CENTER);
160165
pack();
@@ -173,4 +178,18 @@ public void run() {
173178
}
174179
}
175180
}
181+
182+
@Override
183+
public void webcamFound(WebcamDiscoveryEvent event) {
184+
if (picker != null) {
185+
picker.addItem(event.getWebcam());
186+
}
187+
}
188+
189+
@Override
190+
public void webcamGone(WebcamDiscoveryEvent event) {
191+
if (picker != null) {
192+
picker.removeItem(event.getWebcam());
193+
}
194+
}
176195
}

webcam-capture/src/example/resources/logback.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
55
</layout>
66
</appender>
7-
<root level="warn">
7+
<root level="debug">
88
<appender-ref ref="STDOUT" />
99
</root>
1010
</configuration>

webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java

+104-9
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
import java.util.Iterator;
1010
import java.util.List;
1111
import java.util.concurrent.CopyOnWriteArrayList;
12+
import java.util.concurrent.ExecutorService;
13+
import java.util.concurrent.Executors;
14+
import java.util.concurrent.ThreadFactory;
1215
import java.util.concurrent.TimeUnit;
1316
import java.util.concurrent.TimeoutException;
1417
import java.util.concurrent.atomic.AtomicBoolean;
@@ -34,6 +37,61 @@
3437
*/
3538
public class Webcam {
3639

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+
3795
/**
3896
* Logger instance.
3997
*/
@@ -117,15 +175,24 @@ public class Webcam {
117175
/**
118176
* Webcam image updater.
119177
*/
120-
private WebcamUpdater updater = new WebcamUpdater(this);
178+
private volatile WebcamUpdater updater = null;
121179

122180
/**
123-
* IMage transformer.
181+
* Image transformer.
124182
*/
125183
private volatile WebcamImageTransformer transformer = null;
126184

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+
*/
127189
private WebcamLock lock = null;
128190

191+
/**
192+
* Executor service for image notifications.
193+
*/
194+
private ExecutorService notificator = null;
195+
129196
/**
130197
* Webcam class.
131198
*
@@ -140,6 +207,21 @@ protected Webcam(WebcamDevice device) {
140207
this.lock = new WebcamLock(this);
141208
}
142209

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+
143225
/**
144226
* Open the webcam in blocking (synchronous) mode.
145227
*
@@ -175,9 +257,10 @@ public boolean open(boolean async) {
175257

176258
if (open.compareAndSet(false, true)) {
177259

178-
assert updater != null;
179260
assert lock != null;
180261

262+
notificator = Executors.newSingleThreadExecutor(new NotificationThreadFactory());
263+
181264
// lock webcam for other Java (only) processes
182265

183266
lock.lock();
@@ -195,16 +278,18 @@ public boolean open(boolean async) {
195278
} catch (WebcamException e) {
196279
lock.unlock();
197280
open.set(false);
281+
LOG.debug("Webcam exception when opening", e);
198282
throw e;
199283
}
200284

201285
LOG.debug("Webcam is now open {}", getName());
202286

203287
// setup non-blocking configuration
204288

205-
asynchronous = async;
206-
207-
if (async) {
289+
if (asynchronous = async) {
290+
if (updater == null) {
291+
updater = new WebcamUpdater(this);
292+
}
208293
updater.start();
209294
}
210295

@@ -243,7 +328,6 @@ public boolean close() {
243328

244329
LOG.debug("Closing webcam {}", getName());
245330

246-
assert updater != null;
247331
assert lock != null;
248332

249333
// close webcam
@@ -261,7 +345,9 @@ public boolean close() {
261345
}
262346

263347
// stop updater
264-
updater.stop();
348+
if (asynchronous) {
349+
updater.stop();
350+
}
265351

266352
// remove shutdown hook (it's not more necessary)
267353
removeShutdownHook();
@@ -284,6 +370,15 @@ public boolean close() {
284370
}
285371
}
286372

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+
287382
LOG.debug("Webcam {} has been closed", getName());
288383

289384
} else {
@@ -552,7 +647,7 @@ public BufferedImage getImage() {
552647

553648
// notify webcam listeners about new image available
554649

555-
updater.notifyWebcamImageObtained(this, image);
650+
notifyWebcamImageAcquired(image);
556651

557652
return image;
558653
}

webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamPanel.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ public void run() {
480480
// completely started (it was in "starting" timeframe)
481481

482482
LOG.warn("Executor rejected paint update");
483-
LOG.debug("Executor rejected paint update because of", e);
483+
LOG.trace("Executor rejected paint update because of", e);
484484

485485
return;
486486
}

webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamUpdater.java

+6-68
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.github.sarxos.webcam;
22

33
import java.awt.image.BufferedImage;
4-
import java.util.concurrent.ExecutorService;
54
import java.util.concurrent.Executors;
65
import java.util.concurrent.RejectedExecutionException;
76
import java.util.concurrent.ScheduledExecutorService;
@@ -45,50 +44,6 @@ public Thread newThread(Runnable r) {
4544

4645
}
4746

48-
/**
49-
* Class used to asynchronously notify all webcam listeners about new image
50-
* available.
51-
*
52-
* @author Bartosz Firyn (sarxos)
53-
*/
54-
private static final class ImageNotification implements Runnable {
55-
56-
/**
57-
* Camera.
58-
*/
59-
private final Webcam webcam;
60-
61-
/**
62-
* Acquired image.
63-
*/
64-
private final BufferedImage image;
65-
66-
/**
67-
* Create new notification.
68-
*
69-
* @param webcam the webcam from which image has been acquired
70-
* @param image the acquired image
71-
*/
72-
public ImageNotification(Webcam webcam, BufferedImage image) {
73-
this.webcam = webcam;
74-
this.image = image;
75-
}
76-
77-
@Override
78-
public void run() {
79-
if (image != null) {
80-
WebcamEvent we = new WebcamEvent(WebcamEventType.NEW_IMAGE, webcam, image);
81-
for (WebcamListener l : webcam.getWebcamListeners()) {
82-
try {
83-
l.webcamImageObtained(we);
84-
} catch (Exception e) {
85-
LOG.error(String.format("Notify image acquired, exception when calling listener %s", l.getClass()), e);
86-
}
87-
}
88-
}
89-
}
90-
}
91-
9247
/**
9348
* Logger.
9449
*/
@@ -106,11 +61,6 @@ public void run() {
10661
*/
10762
private ScheduledExecutorService executor = null;
10863

109-
/**
110-
* Executor service for image notifications.
111-
*/
112-
private final ExecutorService notificator = Executors.newSingleThreadExecutor(THREAD_FACTORY);
113-
11464
/**
11565
* Cached image.
11666
*/
@@ -146,6 +96,7 @@ protected WebcamUpdater(Webcam webcam) {
14696
* Start updater.
14797
*/
14898
public void start() {
99+
149100
if (running.compareAndSet(false, true)) {
150101

151102
image.set(new WebcamReadImageTask(Webcam.getDriver(), webcam.getDevice()).getImage());
@@ -166,12 +117,10 @@ public void stop() {
166117
if (running.compareAndSet(true, false)) {
167118

168119
executor.shutdown();
169-
170120
while (!executor.isTerminated()) {
171121
try {
172122
executor.awaitTermination(100, TimeUnit.MILLISECONDS);
173123
} catch (InterruptedException e) {
174-
LOG.trace(e.getMessage(), e);
175124
return;
176125
}
177126
}
@@ -199,6 +148,10 @@ public void run() {
199148

200149
private void tick() {
201150

151+
if (!webcam.isOpen()) {
152+
return;
153+
}
154+
202155
long t1 = 0;
203156
long t2 = 0;
204157

@@ -245,22 +198,7 @@ private void tick() {
245198

246199
// notify webcam listeners about the new image available
247200

248-
notifyWebcamImageObtained(webcam, image.get());
249-
}
250-
251-
/**
252-
* Asynchronously start new thread which will notify all webcam listeners
253-
* about the new image available.
254-
*/
255-
protected void notifyWebcamImageObtained(Webcam webcam, BufferedImage image) {
256-
257-
// notify webcam listeners of new image available, do that only if there
258-
// are any webcam listeners available because there is no sense to start
259-
// additional threads for no purpose
260-
261-
if (webcam.getWebcamListenersCount() > 0) {
262-
notificator.execute(new ImageNotification(webcam, image));
263-
}
201+
webcam.notifyWebcamImageAcquired(image.get());
264202
}
265203

266204
/**

0 commit comments

Comments
 (0)