From d4645fef054d5cd1604a4a9e431c1d5d444fc22f Mon Sep 17 00:00:00 2001 From: krok32 Date: Fri, 13 Feb 2015 22:45:25 +0100 Subject: [PATCH] Customizable WebcamUpdater; issue #307 --- .../java/com/github/sarxos/webcam/Webcam.java | 61 ++++++++++---- .../github/sarxos/webcam/WebcamUpdater.java | 81 ++++++++++++++++--- 2 files changed, 116 insertions(+), 26 deletions(-) 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 2117c63c..6e0ec6dc 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 @@ -20,6 +20,8 @@ import org.slf4j.LoggerFactory; import com.github.sarxos.webcam.WebcamDevice.BufferAccess; +import com.github.sarxos.webcam.WebcamUpdater.DefaultDelayCalculator; +import com.github.sarxos.webcam.WebcamUpdater.DelayCalculator; import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDevice; import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDriver; import com.github.sarxos.webcam.ds.cgt.WebcamCloseTask; @@ -225,7 +227,7 @@ protected void notifyWebcamImageAcquired(BufferedImage image) { * Open the webcam in blocking (synchronous) mode. * * @return True if webcam has been open, false otherwise - * @see #open(boolean) + * @see #open(boolean, DelayCalculator) * @throws WebcamException when something went wrong */ public boolean open() { @@ -233,26 +235,53 @@ public boolean open() { } /** - * Open the webcam in either blocking (synchronous) or non-blocking (asynchronous) mode.The - * difference between those two modes lies in the image acquisition mechanism.
+ * Open the webcam in either blocking (synchronous) or non-blocking + * (asynchronous) mode. If the non-blocking mode is enabled the + * DefaultDelayCalculator is used for calculating delay between two image + * fetching. + * + * @param async true for non-blocking mode, false for blocking + * @return True if webcam has been open, false otherwise + * @see #open(boolean, DelayCalculator) + * @throws WebcamException when something went wrong + */ + public boolean open(boolean async) { + return open(async, new DefaultDelayCalculator()); + } + + /** + * Open the webcam in either blocking (synchronous) or non-blocking + * (asynchronous) mode.The difference between those two modes lies in the + * image acquisition mechanism.
*
- * In blocking mode, when user calls {@link #getImage()} method, device is being queried for new - * image buffer and user have to wait for it to be available.
+ * In blocking mode, when user calls {@link #getImage()} method, device is + * being queried for new image buffer and user have to wait for it to be + * available.
*
- * In non-blocking mode, there is a special thread running in the background which constantly - * fetch new images and cache them internally for further use. This cached instance is returned - * every time when user request new image. Because of that it can be used when timeing is very - * important, because all users calls for new image do not have to wait on device response. By - * using this mode user should be aware of the fact that in some cases, when two consecutive - * calls to get new image are executed more often than webcam device can serve them, the same - * image instance will be returned. User should use {@link #isImageNew()} method to distinguish - * if returned image is not the same as the previous one. - * + * In non-blocking mode, there is a special thread running in the background + * which constantly fetch new images and cache them internally for further + * use. This cached instance is returned every time when user request new + * image. Because of that it can be used when timeing is very important, + * because all users calls for new image do not have to wait on device + * response. By using this mode user should be aware of the fact that in + * some cases, when two consecutive calls to get new image are executed more + * often than webcam device can serve them, the same image instance will be + * returned. User should use {@link #isImageNew()} method to distinguish if + * returned image is not the same as the previous one.
+ * The background thread uses implementation of DelayCalculator interface to + * calculate delay between two image fetching. Custom implementation may be + * specified as parameter of this method. If the non-blocking mode is + * enabled and no DelayCalculator is specified, DefaultDelayCalculator will + * be used. + * * @param async true for non-blocking mode, false for blocking + * @param delayCalculator responsible for calculating delay between two + * image fetching in non-blocking mode; It's ignored in blocking + * mode. * @return True if webcam has been open * @throws WebcamException when something went wrong */ - public boolean open(boolean async) { + public boolean open(boolean async, DelayCalculator delayCalculator) { if (open.compareAndSet(false, true)) { @@ -301,7 +330,7 @@ public boolean open(boolean async) { if (asynchronous = async) { if (updater == null) { - updater = new WebcamUpdater(this); + updater = new WebcamUpdater(this, delayCalculator); } updater.start(); } 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 14085f49..a573cff0 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 @@ -25,6 +25,46 @@ */ public class WebcamUpdater implements Runnable { + /** + * Implementation of this interface is responsible for calculating the delay + * between 2 image fetching, when the non-blocking (asynchronous) access to the + * webcam is enabled. + */ + public interface DelayCalculator { + + /** + * Calculates delay before the next image will be fetched from the + * webcam. + * Must return number greater or equal 0. + * + * @param snapshotDuration - duration of taking the last image + * @param deviceFps - current FPS obtained from the device, or -1 if the + * driver doesn't support it + * @return interval (in millis) + */ + long calculateDelay(long snapshotDuration, double deviceFps); + } + + /** + * Default impl of DelayCalculator, based on TARGET_FPS. Returns 0 delay for + * snapshotDuration > 20 millis. + */ + public static class DefaultDelayCalculator implements DelayCalculator { + + @Override + public long calculateDelay(long snapshotDuration, double deviceFps) { + // 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 delay = Math.max((1000 / TARGET_FPS) - snapshotDuration, 0); + return delay; + } + } + /** * Thread factory for executors used within updater class. * @@ -84,12 +124,32 @@ public Thread newThread(Runnable r) { private volatile boolean imageNew = false; /** - * Construct new webcam updater. + * DelayCalculator implementation. + */ + private final DelayCalculator delayCalculator; + + /** + * Construct new webcam updater using DefaultDelayCalculator. * * @param webcam the webcam to which updater shall be attached */ protected WebcamUpdater(Webcam webcam) { + this(webcam, new DefaultDelayCalculator()); + } + + /** + * Construct new webcam updater. + * + * @param webcam the webcam to which updater shall be attached + * @param delayCalculator implementation + */ + public WebcamUpdater(Webcam webcam, DelayCalculator delayCalculator) { this.webcam = webcam; + if (delayCalculator == null) { + this.delayCalculator = new DefaultDelayCalculator(); + } else { + this.delayCalculator = delayCalculator; + } } /** @@ -172,16 +232,17 @@ private void tick() { 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); - + double deviceFps = -1; if (device instanceof WebcamDevice.FPSSource) { - fps = ((WebcamDevice.FPSSource) device).getFPS(); + deviceFps = ((WebcamDevice.FPSSource) device).getFPS(); + } + + long duration = t2 - t1; + long delay = delayCalculator.calculateDelay(duration, deviceFps); + + long delta = duration + 1; // +1 to avoid division by zero + if (deviceFps >= 0) { + fps = deviceFps; } else { fps = (4 * fps + 1000 / delta) / 5; }