Skip to content

Commit 0b96dd2

Browse files
committed
Merge pull request #311 from krok32/master
Customizable WebcamUpdater; issue #307
2 parents 306a548 + d4645fe commit 0b96dd2

File tree

2 files changed

+116
-26
lines changed

2 files changed

+116
-26
lines changed

webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import org.slf4j.LoggerFactory;
2121

2222
import com.github.sarxos.webcam.WebcamDevice.BufferAccess;
23+
import com.github.sarxos.webcam.WebcamUpdater.DefaultDelayCalculator;
24+
import com.github.sarxos.webcam.WebcamUpdater.DelayCalculator;
2325
import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDevice;
2426
import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDriver;
2527
import com.github.sarxos.webcam.ds.cgt.WebcamCloseTask;
@@ -225,34 +227,61 @@ protected void notifyWebcamImageAcquired(BufferedImage image) {
225227
* Open the webcam in blocking (synchronous) mode.
226228
*
227229
* @return True if webcam has been open, false otherwise
228-
* @see #open(boolean)
230+
* @see #open(boolean, DelayCalculator)
229231
* @throws WebcamException when something went wrong
230232
*/
231233
public boolean open() {
232234
return open(false);
233235
}
234236

235237
/**
236-
* Open the webcam in either blocking (synchronous) or non-blocking (asynchronous) mode.The
237-
* difference between those two modes lies in the image acquisition mechanism.<br>
238+
* Open the webcam in either blocking (synchronous) or non-blocking
239+
* (asynchronous) mode. If the non-blocking mode is enabled the
240+
* DefaultDelayCalculator is used for calculating delay between two image
241+
* fetching.
242+
*
243+
* @param async true for non-blocking mode, false for blocking
244+
* @return True if webcam has been open, false otherwise
245+
* @see #open(boolean, DelayCalculator)
246+
* @throws WebcamException when something went wrong
247+
*/
248+
public boolean open(boolean async) {
249+
return open(async, new DefaultDelayCalculator());
250+
}
251+
252+
/**
253+
* Open the webcam in either blocking (synchronous) or non-blocking
254+
* (asynchronous) mode.The difference between those two modes lies in the
255+
* image acquisition mechanism.<br>
238256
* <br>
239-
* In blocking mode, when user calls {@link #getImage()} method, device is being queried for new
240-
* image buffer and user have to wait for it to be available.<br>
257+
* In blocking mode, when user calls {@link #getImage()} method, device is
258+
* being queried for new image buffer and user have to wait for it to be
259+
* available.<br>
241260
* <br>
242-
* In non-blocking mode, there is a special thread running in the background which constantly
243-
* fetch new images and cache them internally for further use. This cached instance is returned
244-
* every time when user request new image. Because of that it can be used when timeing is very
245-
* important, because all users calls for new image do not have to wait on device response. By
246-
* using this mode user should be aware of the fact that in some cases, when two consecutive
247-
* calls to get new image are executed more often than webcam device can serve them, the same
248-
* image instance will be returned. User should use {@link #isImageNew()} method to distinguish
249-
* if returned image is not the same as the previous one.
250-
*
261+
* In non-blocking mode, there is a special thread running in the background
262+
* which constantly fetch new images and cache them internally for further
263+
* use. This cached instance is returned every time when user request new
264+
* image. Because of that it can be used when timeing is very important,
265+
* because all users calls for new image do not have to wait on device
266+
* response. By using this mode user should be aware of the fact that in
267+
* some cases, when two consecutive calls to get new image are executed more
268+
* often than webcam device can serve them, the same image instance will be
269+
* returned. User should use {@link #isImageNew()} method to distinguish if
270+
* returned image is not the same as the previous one. <br>
271+
* The background thread uses implementation of DelayCalculator interface to
272+
* calculate delay between two image fetching. Custom implementation may be
273+
* specified as parameter of this method. If the non-blocking mode is
274+
* enabled and no DelayCalculator is specified, DefaultDelayCalculator will
275+
* be used.
276+
*
251277
* @param async true for non-blocking mode, false for blocking
278+
* @param delayCalculator responsible for calculating delay between two
279+
* image fetching in non-blocking mode; It's ignored in blocking
280+
* mode.
252281
* @return True if webcam has been open
253282
* @throws WebcamException when something went wrong
254283
*/
255-
public boolean open(boolean async) {
284+
public boolean open(boolean async, DelayCalculator delayCalculator) {
256285

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

@@ -301,7 +330,7 @@ public boolean open(boolean async) {
301330

302331
if (asynchronous = async) {
303332
if (updater == null) {
304-
updater = new WebcamUpdater(this);
333+
updater = new WebcamUpdater(this, delayCalculator);
305334
}
306335
updater.start();
307336
}

webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamUpdater.java

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,46 @@
2525
*/
2626
public class WebcamUpdater implements Runnable {
2727

28+
/**
29+
* Implementation of this interface is responsible for calculating the delay
30+
* between 2 image fetching, when the non-blocking (asynchronous) access to the
31+
* webcam is enabled.
32+
*/
33+
public interface DelayCalculator {
34+
35+
/**
36+
* Calculates delay before the next image will be fetched from the
37+
* webcam.
38+
* Must return number greater or equal 0.
39+
*
40+
* @param snapshotDuration - duration of taking the last image
41+
* @param deviceFps - current FPS obtained from the device, or -1 if the
42+
* driver doesn't support it
43+
* @return interval (in millis)
44+
*/
45+
long calculateDelay(long snapshotDuration, double deviceFps);
46+
}
47+
48+
/**
49+
* Default impl of DelayCalculator, based on TARGET_FPS. Returns 0 delay for
50+
* snapshotDuration &gt; 20 millis.
51+
*/
52+
public static class DefaultDelayCalculator implements DelayCalculator {
53+
54+
@Override
55+
public long calculateDelay(long snapshotDuration, double deviceFps) {
56+
// Calculate delay required to achieve target FPS.
57+
// In some cases it can be less than 0
58+
// because camera is not able to serve images as fast as
59+
// we would like to. In such case just run with no delay,
60+
// so maximum FPS will be the one supported
61+
// by camera device in the moment.
62+
63+
long delay = Math.max((1000 / TARGET_FPS) - snapshotDuration, 0);
64+
return delay;
65+
}
66+
}
67+
2868
/**
2969
* Thread factory for executors used within updater class.
3070
*
@@ -84,12 +124,32 @@ public Thread newThread(Runnable r) {
84124
private volatile boolean imageNew = false;
85125

86126
/**
87-
* Construct new webcam updater.
127+
* DelayCalculator implementation.
128+
*/
129+
private final DelayCalculator delayCalculator;
130+
131+
/**
132+
* Construct new webcam updater using DefaultDelayCalculator.
88133
*
89134
* @param webcam the webcam to which updater shall be attached
90135
*/
91136
protected WebcamUpdater(Webcam webcam) {
137+
this(webcam, new DefaultDelayCalculator());
138+
}
139+
140+
/**
141+
* Construct new webcam updater.
142+
*
143+
* @param webcam the webcam to which updater shall be attached
144+
* @param delayCalculator implementation
145+
*/
146+
public WebcamUpdater(Webcam webcam, DelayCalculator delayCalculator) {
92147
this.webcam = webcam;
148+
if (delayCalculator == null) {
149+
this.delayCalculator = new DefaultDelayCalculator();
150+
} else {
151+
this.delayCalculator = delayCalculator;
152+
}
93153
}
94154

95155
/**
@@ -172,16 +232,17 @@ private void tick() {
172232
image.set(img);
173233
imageNew = true;
174234

175-
// Calculate delay required to achieve target FPS. In some cases it can
176-
// be less than 0 because camera is not able to serve images as fast as
177-
// we would like to. In such case just run with no delay, so maximum FPS
178-
// will be the one supported by camera device in the moment.
179-
180-
long delta = t2 - t1 + 1; // +1 to avoid division by zero
181-
long delay = Math.max((1000 / TARGET_FPS) - delta, 0);
182-
235+
double deviceFps = -1;
183236
if (device instanceof WebcamDevice.FPSSource) {
184-
fps = ((WebcamDevice.FPSSource) device).getFPS();
237+
deviceFps = ((WebcamDevice.FPSSource) device).getFPS();
238+
}
239+
240+
long duration = t2 - t1;
241+
long delay = delayCalculator.calculateDelay(duration, deviceFps);
242+
243+
long delta = duration + 1; // +1 to avoid division by zero
244+
if (deviceFps >= 0) {
245+
fps = deviceFps;
185246
} else {
186247
fps = (4 * fps + 1000 / delta) / 5;
187248
}

0 commit comments

Comments
 (0)