Skip to content

Commit

Permalink
Do not spawn unecessary threads, refs #228
Browse files Browse the repository at this point in the history
  • Loading branch information
sarxos committed Jun 21, 2014
1 parent 4371adb commit d9fcae8
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 81 deletions.
21 changes: 20 additions & 1 deletion webcam-capture/src/example/java/WebcamViewerExample.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import javax.swing.SwingUtilities;

import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamDiscoveryEvent;
import com.github.sarxos.webcam.WebcamDiscoveryListener;
import com.github.sarxos.webcam.WebcamEvent;
import com.github.sarxos.webcam.WebcamListener;
import com.github.sarxos.webcam.WebcamPanel;
Expand All @@ -21,7 +23,7 @@
*
* @author Bartosz Firyn (SarXos)
*/
public class WebcamViewerExample extends JFrame implements Runnable, WebcamListener, WindowListener, UncaughtExceptionHandler, ItemListener {
public class WebcamViewerExample extends JFrame implements Runnable, WebcamListener, WindowListener, UncaughtExceptionHandler, ItemListener, WebcamDiscoveryListener {

private static final long serialVersionUID = 1L;

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

Webcam.addDiscoveryListener(this);

setTitle("Java Webcam Capture POC");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
Expand Down Expand Up @@ -155,6 +159,7 @@ public void itemStateChanged(ItemEvent e) {
System.out.println("selected " + webcam.getName());

panel = new WebcamPanel(webcam, false);
panel.setFPSDisplayed(true);

add(panel, BorderLayout.CENTER);
pack();
Expand All @@ -173,4 +178,18 @@ public void run() {
}
}
}

@Override
public void webcamFound(WebcamDiscoveryEvent event) {
if (picker != null) {
picker.addItem(event.getWebcam());
}
}

@Override
public void webcamGone(WebcamDiscoveryEvent event) {
if (picker != null) {
picker.removeItem(event.getWebcam());
}
}
}
2 changes: 1 addition & 1 deletion webcam-capture/src/example/resources/logback.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
</layout>
</appender>
<root level="warn">
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
</configuration>
113 changes: 104 additions & 9 deletions webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
Expand All @@ -34,6 +37,61 @@
*/
public class Webcam {

/**
* Class used to asynchronously notify all webcam listeners about new image
* available.
*
* @author Bartosz Firyn (sarxos)
*/
private static final 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);
}
}
}
}
}

private final class NotificationThreadFactory implements ThreadFactory {

@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, String.format("notificator-[%s]", getName()));
t.setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance());
t.setDaemon(true);
return t;
}
}

/**
* Logger instance.
*/
Expand Down Expand Up @@ -117,15 +175,24 @@ public class Webcam {
/**
* Webcam image updater.
*/
private WebcamUpdater updater = new WebcamUpdater(this);
private volatile WebcamUpdater updater = null;

/**
* IMage transformer.
* Image transformer.
*/
private volatile WebcamImageTransformer transformer = null;

/**
* Lock which denies access to the given webcam when it's already in use by
* other webcam capture API process or thread.
*/
private WebcamLock lock = null;

/**
* Executor service for image notifications.
*/
private ExecutorService notificator = null;

/**
* Webcam class.
*
Expand All @@ -140,6 +207,21 @@ protected Webcam(WebcamDevice device) {
this.lock = new WebcamLock(this);
}

/**
* Asynchronously start new thread which will notify all webcam listeners
* about the new image available.
*/
protected void notifyWebcamImageAcquired(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 (getWebcamListenersCount() > 0) {
notificator.execute(new ImageNotification(this, image));
}
}

/**
* Open the webcam in blocking (synchronous) mode.
*
Expand Down Expand Up @@ -175,9 +257,10 @@ public boolean open(boolean async) {

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

assert updater != null;
assert lock != null;

notificator = Executors.newSingleThreadExecutor(new NotificationThreadFactory());

// lock webcam for other Java (only) processes

lock.lock();
Expand All @@ -195,16 +278,18 @@ public boolean open(boolean async) {
} catch (WebcamException e) {
lock.unlock();
open.set(false);
LOG.debug("Webcam exception when opening", e);
throw e;
}

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

// setup non-blocking configuration

asynchronous = async;

if (async) {
if (asynchronous = async) {
if (updater == null) {
updater = new WebcamUpdater(this);
}
updater.start();
}

Expand Down Expand Up @@ -243,7 +328,6 @@ public boolean close() {

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

assert updater != null;
assert lock != null;

// close webcam
Expand All @@ -261,7 +345,9 @@ public boolean close() {
}

// stop updater
updater.stop();
if (asynchronous) {
updater.stop();
}

// remove shutdown hook (it's not more necessary)
removeShutdownHook();
Expand All @@ -284,6 +370,15 @@ public boolean close() {
}
}

notificator.shutdown();
while (!notificator.isTerminated()) {
try {
notificator.awaitTermination(100, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
return false;
}
}

LOG.debug("Webcam {} has been closed", getName());

} else {
Expand Down Expand Up @@ -552,7 +647,7 @@ public BufferedImage getImage() {

// notify webcam listeners about new image available

updater.notifyWebcamImageObtained(this, image);
notifyWebcamImageAcquired(image);

return image;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ public void run() {
// completely started (it was in "starting" timeframe)

LOG.warn("Executor rejected paint update");
LOG.debug("Executor rejected paint update because of", e);
LOG.trace("Executor rejected paint update because of", e);

return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.github.sarxos.webcam;

import java.awt.image.BufferedImage;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
Expand Down Expand Up @@ -45,50 +44,6 @@ public Thread newThread(Runnable r) {

}

/**
* Class used to asynchronously notify all webcam listeners about new image
* available.
*
* @author Bartosz Firyn (sarxos)
*/
private static final 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.
*/
Expand All @@ -106,11 +61,6 @@ public void run() {
*/
private ScheduledExecutorService executor = null;

/**
* Executor service for image notifications.
*/
private final ExecutorService notificator = Executors.newSingleThreadExecutor(THREAD_FACTORY);

/**
* Cached image.
*/
Expand Down Expand Up @@ -146,6 +96,7 @@ protected WebcamUpdater(Webcam webcam) {
* Start updater.
*/
public void start() {

if (running.compareAndSet(false, true)) {

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

executor.shutdown();

while (!executor.isTerminated()) {
try {
executor.awaitTermination(100, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
LOG.trace(e.getMessage(), e);
return;
}
}
Expand Down Expand Up @@ -199,6 +148,10 @@ public void run() {

private void tick() {

if (!webcam.isOpen()) {
return;
}

long t1 = 0;
long t2 = 0;

Expand Down Expand Up @@ -245,22 +198,7 @@ private void tick() {

// 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));
}
webcam.notifyWebcamImageAcquired(image.get());
}

/**
Expand Down
Loading

0 comments on commit d9fcae8

Please sign in to comment.