diff --git a/webcam-capture/src/example/java/WebcamViewerExample.java b/webcam-capture/src/example/java/WebcamViewerExample.java index ae362ed7..b61d7922 100644 --- a/webcam-capture/src/example/java/WebcamViewerExample.java +++ b/webcam-capture/src/example/java/WebcamViewerExample.java @@ -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; @@ -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; @@ -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()); @@ -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(); @@ -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()); + } + } } diff --git a/webcam-capture/src/example/resources/logback.xml b/webcam-capture/src/example/resources/logback.xml index baeded4b..2bb01a64 100644 --- a/webcam-capture/src/example/resources/logback.xml +++ b/webcam-capture/src/example/resources/logback.xml @@ -4,7 +4,7 @@ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - + diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java index 31f6baa1..bc777598 100644 --- a/webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java +++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java @@ -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; @@ -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. */ @@ -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. * @@ -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. * @@ -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(); @@ -195,6 +278,7 @@ public boolean open(boolean async) { } catch (WebcamException e) { lock.unlock(); open.set(false); + LOG.debug("Webcam exception when opening", e); throw e; } @@ -202,9 +286,10 @@ public boolean open(boolean async) { // setup non-blocking configuration - asynchronous = async; - - if (async) { + if (asynchronous = async) { + if (updater == null) { + updater = new WebcamUpdater(this); + } updater.start(); } @@ -243,7 +328,6 @@ public boolean close() { LOG.debug("Closing webcam {}", getName()); - assert updater != null; assert lock != null; // close webcam @@ -261,7 +345,9 @@ public boolean close() { } // stop updater - updater.stop(); + if (asynchronous) { + updater.stop(); + } // remove shutdown hook (it's not more necessary) removeShutdownHook(); @@ -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 { @@ -552,7 +647,7 @@ public BufferedImage getImage() { // notify webcam listeners about new image available - updater.notifyWebcamImageObtained(this, image); + notifyWebcamImageAcquired(image); return image; } diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamPanel.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamPanel.java index 5f6ed058..0448b75e 100644 --- a/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamPanel.java +++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamPanel.java @@ -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; } diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamUpdater.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamUpdater.java index e7ee618d..093a619d 100644 --- a/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamUpdater.java +++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamUpdater.java @@ -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; @@ -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. */ @@ -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. */ @@ -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()); @@ -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; } } @@ -199,6 +148,10 @@ public void run() { private void tick() { + if (!webcam.isOpen()) { + return; + } + long t1 = 0; long t2 = 0; @@ -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()); } /** diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/WebcamDefaultDevice.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/WebcamDefaultDevice.java index e54a28b2..a96b1260 100644 --- a/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/WebcamDefaultDevice.java +++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/WebcamDefaultDevice.java @@ -307,7 +307,7 @@ public void open() { open.set(true); - refresher = new Thread(this, String.format("frames-refresher:%s", id)); + refresher = new Thread(this, String.format("frames-refresher-[%s]", id)); refresher.setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance()); refresher.setDaemon(true); refresher.start();