Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customizable WebcamUpdater; issue #307 #311

Merged
merged 1 commit into from
Feb 14, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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