Skip to content

Commit

Permalink
Merge pull request #311 from krok32/master
Browse files Browse the repository at this point in the history
Customizable WebcamUpdater; issue #307
  • Loading branch information
sarxos committed Feb 14, 2015
2 parents 306a548 + d4645fe commit 0b96dd2
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 26 deletions.
61 changes: 45 additions & 16 deletions webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -225,34 +227,61 @@ 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() {
return open(false);
}

/**
* Open the webcam in either blocking (synchronous) or non-blocking (asynchronous) mode.The
* difference between those two modes lies in the image acquisition mechanism.<br>
* 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.<br>
* <br>
* 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.<br>
* 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.<br>
* <br>
* 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. <br>
* 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)) {

Expand Down Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 &gt; 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.
*
Expand Down Expand Up @@ -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;
}
}

/**
Expand Down Expand Up @@ -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;
}
Expand Down

0 comments on commit 0b96dd2

Please sign in to comment.