diff --git a/webcam-capture-drivers/driver-ffmpeg-cli/src/main/java/com/github/sarxos/webcam/ds/ffmpegcli/FFmpegCliDevice.java b/webcam-capture-drivers/driver-ffmpeg-cli/src/main/java/com/github/sarxos/webcam/ds/ffmpegcli/FFmpegCliDevice.java
index 6c8e1887..c27c8b96 100644
--- a/webcam-capture-drivers/driver-ffmpeg-cli/src/main/java/com/github/sarxos/webcam/ds/ffmpegcli/FFmpegCliDevice.java
+++ b/webcam-capture-drivers/driver-ffmpeg-cli/src/main/java/com/github/sarxos/webcam/ds/ffmpegcli/FFmpegCliDevice.java
@@ -138,6 +138,14 @@ public synchronized ByteBuffer getImageBytes() {
return ByteBuffer.wrap(readBytes());
}
+ @Override
+ public void getImageBytes(ByteBuffer buffer) {
+ if (!open.get()) {
+ return;
+ }
+ buffer.put(readBytes());
+ }
+
@Override
public BufferedImage getImage() {
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 8533a2a2..f59b45a0 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
@@ -24,10 +24,10 @@
import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDriver;
import com.github.sarxos.webcam.ds.cgt.WebcamCloseTask;
import com.github.sarxos.webcam.ds.cgt.WebcamDisposeTask;
+import com.github.sarxos.webcam.ds.cgt.WebcamGetBufferTask;
+import com.github.sarxos.webcam.ds.cgt.WebcamGetImageTask;
import com.github.sarxos.webcam.ds.cgt.WebcamOpenTask;
import com.github.sarxos.webcam.ds.cgt.WebcamReadBufferTask;
-import com.github.sarxos.webcam.ds.cgt.WebcamReadImageTask;
-import com.github.sarxos.webcam.util.ImageUtils;
/**
@@ -627,7 +627,7 @@ public BufferedImage getImage() {
// get image
t1 = System.currentTimeMillis();
- BufferedImage image = transform(new WebcamReadImageTask(driver, device).getImage());
+ BufferedImage image = transform(new WebcamGetImageTask(driver, device).getImage());
t2 = System.currentTimeMillis();
if (image == null) {
@@ -694,14 +694,43 @@ public ByteBuffer getImageBytes() {
// buffers, just convert image to RGB byte array
if (device instanceof BufferAccess) {
- return new WebcamReadBufferTask(driver, device).getBuffer();
+ return new WebcamGetBufferTask(driver, device).getBuffer();
} else {
- BufferedImage image = getImage();
- if (image != null) {
- return ByteBuffer.wrap(ImageUtils.toRawByteArray(image));
- } else {
- return null;
- }
+ throw new IllegalStateException(String.format("Driver %s does not support buffer access", driver.getClass().getName()));
+ }
+ }
+
+ /**
+ * Get RAW image ByteBuffer. It will always return buffer with 3 x 1 bytes
+ * per each pixel, where RGB components are on (0, 1, 2) and color space is
+ * sRGB.
+ *
+ *
+ * IMPORTANT!
+ * Some drivers can return direct ByteBuffer, so there is no guarantee that
+ * underlying bytes will not be released in next read image operation.
+ * Therefore, to avoid potential bugs you should convert this ByteBuffer to
+ * bytes array before you fetch next image.
+ *
+ * @return Byte buffer
+ */
+ public void getImageBytes(ByteBuffer target) {
+
+ if (!isReady()) {
+ return;
+ }
+
+ assert driver != null;
+ assert device != null;
+
+ // some devices can support direct image buffers, and for those call
+ // processor task, and for those which does not support direct image
+ // buffers, just convert image to RGB byte array
+
+ if (device instanceof BufferAccess) {
+ new WebcamReadBufferTask(driver, device, target).readBuffer();
+ } else {
+ throw new IllegalStateException(String.format("Driver %s does not support buffer access", driver.getClass().getName()));
}
}
diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamDevice.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamDevice.java
index f4720b7b..2211032e 100644
--- a/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamDevice.java
+++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamDevice.java
@@ -22,13 +22,27 @@ public interface WebcamDevice {
public static interface BufferAccess {
/**
- * Get image in form of raw bytes. Do not use this buffer to set
- * bytes value, it should be used only for read purpose!
+ * Read the underlying image memory buffer. This method will return new
+ * reference to pre-allocated off-heap memory where image bytes are
+ * stored. The size of this buffer is image width * height * 3 bytes.
+ *
+ *
+ * NOTE! Do not use this buffer to set bytes value. It
+ * should be used only for read purpose!
*
* @return Bytes buffer
*/
ByteBuffer getImageBytes();
+ /**
+ * Copy the underlying image memory into the target buffer passed as the
+ * argument.The remaining capacity of the target buffer needs to be at
+ * least image width * height * 3 bytes.
+ *
+ * @param target the buffer to which image data should be copied
+ */
+ void getImageBytes(ByteBuffer target);
+
}
public static interface FPSSource {
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 093a619d..14085f49 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
@@ -13,7 +13,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.github.sarxos.webcam.ds.cgt.WebcamReadImageTask;
+import com.github.sarxos.webcam.ds.cgt.WebcamGetImageTask;
/**
@@ -99,7 +99,7 @@ public void start() {
if (running.compareAndSet(false, true)) {
- image.set(new WebcamReadImageTask(Webcam.getDriver(), webcam.getDevice()).getImage());
+ image.set(new WebcamGetImageTask(Webcam.getDriver(), webcam.getDevice()).getImage());
executor = Executors.newSingleThreadScheduledExecutor(THREAD_FACTORY);
executor.execute(this);
@@ -166,7 +166,7 @@ private void tick() {
BufferedImage img = null;
t1 = System.currentTimeMillis();
- img = webcam.transform(new WebcamReadImageTask(driver, device).getImage());
+ img = webcam.transform(new WebcamGetImageTask(driver, device).getImage());
t2 = System.currentTimeMillis();
image.set(img);
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 a96b1260..790e9685 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
@@ -14,7 +14,6 @@
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
import org.bridj.Pointer;
import org.slf4j.Logger;
@@ -75,6 +74,7 @@ protected void handle() {
grabber.setTimeout(timeout);
result.set(grabber.nextFrame());
+ fresh.set(true);
}
}
@@ -119,9 +119,9 @@ protected void handle() {
private final AtomicBoolean open = new AtomicBoolean(false);
/**
- * When last frame was requested.
+ * Is the last image fresh one.
*/
- private final AtomicLong timestamp = new AtomicLong(-1);
+ private final AtomicBoolean fresh = new AtomicBoolean(false);
private Thread refresher = null;
@@ -132,6 +132,9 @@ protected void handle() {
private long t1 = -1;
private long t2 = -1;
+ /**
+ * Current FPS.
+ */
private volatile double fps = 0;
protected WebcamDefaultDevice(Device device) {
@@ -180,17 +183,24 @@ public ByteBuffer getImageBytes() {
LOG.debug("Webcam is disposed, image will be null");
return null;
}
-
if (!open.get()) {
LOG.debug("Webcam is closed, image will be null");
return null;
}
- LOG.trace("Webcam device get image (next frame)");
+ // if image is not fresh, update it
+
+ if (fresh.compareAndSet(false, true)) {
+ updateFrameBuffer();
+ }
// get image buffer
+ LOG.trace("Webcam grabber get image pointer");
+
Pointer image = grabber.getImage();
+ fresh.set(false);
+
if (image == null) {
LOG.warn("Null array pointer found instead of image");
return null;
@@ -203,6 +213,50 @@ public ByteBuffer getImageBytes() {
return image.getByteBuffer(length);
}
+ @Override
+ public void getImageBytes(ByteBuffer target) {
+
+ if (disposed.get()) {
+ LOG.debug("Webcam is disposed, image will be null");
+ return;
+ }
+ if (!open.get()) {
+ LOG.debug("Webcam is closed, image will be null");
+ return;
+ }
+
+ int minSize = size.width * size.height * 3;
+ int curSize = target.remaining();
+
+ if (minSize < curSize) {
+ throw new IllegalArgumentException(String.format("Not enough remaining space in target buffer (%d necessary vs %d remaining)", minSize, curSize));
+ }
+
+ // if image is not fresh, update it
+
+ if (fresh.compareAndSet(false, true)) {
+ updateFrameBuffer();
+ }
+
+ // get image buffer
+
+ LOG.trace("Webcam grabber get image pointer");
+
+ Pointer image = grabber.getImage();
+ fresh.set(false);
+
+ if (image == null) {
+ LOG.warn("Null array pointer found instead of image");
+ return;
+ }
+
+ LOG.trace("Webcam device read buffer {} bytes", minSize);
+
+ image = image.validBytes(minSize);
+ image.getBytes(target);
+
+ }
+
@Override
public BufferedImage getImage() {
@@ -301,8 +355,6 @@ public void open() {
} while (++i < 3);
- timestamp.set(System.currentTimeMillis());
-
LOG.debug("Webcam device {} is now open", this);
open.set(true);
@@ -370,11 +422,35 @@ public void setTimeout(int timeout) {
this.timeout = timeout;
}
+ /**
+ * Update underlying memory buffer and fetch new frame.
+ */
+ private void updateFrameBuffer() {
+
+ LOG.trace("Next frame");
+
+ if (t1 == -1 || t2 == -1) {
+ t1 = System.currentTimeMillis();
+ t2 = System.currentTimeMillis();
+ }
+
+ int result = new NextFrameTask(this).nextFrame();
+
+ t1 = t2;
+ t2 = System.currentTimeMillis();
+
+ fps = (4 * fps + 1000 / (t2 - t1 + 1)) / 5;
+
+ if (result == -1) {
+ LOG.error("Timeout when requesting image!");
+ } else if (result < -1) {
+ LOG.error("Error requesting new frame!");
+ }
+ }
+
@Override
public void run() {
- int result = -1;
-
do {
if (Thread.interrupted()) {
@@ -387,27 +463,7 @@ public void run() {
return;
}
- LOG.trace("Next frame");
-
- if (t1 == -1 || t2 == -1) {
- t1 = System.currentTimeMillis();
- t2 = System.currentTimeMillis();
- }
-
- result = new NextFrameTask(this).nextFrame();
-
- t1 = t2;
- t2 = System.currentTimeMillis();
-
- fps = (4 * fps + 1000 / (t2 - t1 + 1)) / 5;
-
- if (result == -1) {
- LOG.error("Timeout when requesting image!");
- } else if (result < -1) {
- LOG.error("Error requesting new frame!");
- }
-
- timestamp.set(System.currentTimeMillis());
+ updateFrameBuffer();
} while (open.get());
}
diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/cgt/WebcamGetBufferTask.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/cgt/WebcamGetBufferTask.java
new file mode 100644
index 00000000..1498e4d6
--- /dev/null
+++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/cgt/WebcamGetBufferTask.java
@@ -0,0 +1,48 @@
+package com.github.sarxos.webcam.ds.cgt;
+
+import java.nio.ByteBuffer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.github.sarxos.webcam.WebcamDevice;
+import com.github.sarxos.webcam.WebcamDevice.BufferAccess;
+import com.github.sarxos.webcam.WebcamDriver;
+import com.github.sarxos.webcam.WebcamTask;
+
+
+public class WebcamGetBufferTask extends WebcamTask {
+
+ private static final Logger LOG = LoggerFactory.getLogger(WebcamGetBufferTask.class);
+
+ private volatile ByteBuffer buffer = null;
+
+ public WebcamGetBufferTask(WebcamDriver driver, WebcamDevice device) {
+ super(driver, device);
+ }
+
+ public ByteBuffer getBuffer() {
+ try {
+ process();
+ } catch (InterruptedException e) {
+ LOG.debug("Image buffer request interrupted", e);
+ return null;
+ }
+ return buffer;
+ }
+
+ @Override
+ protected void handle() {
+
+ WebcamDevice device = getDevice();
+ if (!device.isOpen()) {
+ return;
+ }
+
+ if (!(device instanceof BufferAccess)) {
+ return;
+ }
+
+ buffer = ((BufferAccess) device).getImageBytes();
+ }
+}
diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/cgt/WebcamReadImageTask.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/cgt/WebcamGetImageTask.java
similarity index 83%
rename from webcam-capture/src/main/java/com/github/sarxos/webcam/ds/cgt/WebcamReadImageTask.java
rename to webcam-capture/src/main/java/com/github/sarxos/webcam/ds/cgt/WebcamGetImageTask.java
index 903375fb..bf09ef7b 100644
--- a/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/cgt/WebcamReadImageTask.java
+++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/cgt/WebcamGetImageTask.java
@@ -10,13 +10,13 @@
import com.github.sarxos.webcam.WebcamTask;
-public class WebcamReadImageTask extends WebcamTask {
+public class WebcamGetImageTask extends WebcamTask {
- private static final Logger LOG = LoggerFactory.getLogger(WebcamReadImageTask.class);
+ private static final Logger LOG = LoggerFactory.getLogger(WebcamGetImageTask.class);
private volatile BufferedImage image = null;
- public WebcamReadImageTask(WebcamDriver driver, WebcamDevice device) {
+ public WebcamGetImageTask(WebcamDriver driver, WebcamDevice device) {
super(driver, device);
}
diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/cgt/WebcamReadBufferTask.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/cgt/WebcamReadBufferTask.java
index f7e643f8..343dc43b 100644
--- a/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/cgt/WebcamReadBufferTask.java
+++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/cgt/WebcamReadBufferTask.java
@@ -2,9 +2,6 @@
import java.nio.ByteBuffer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import com.github.sarxos.webcam.WebcamDevice;
import com.github.sarxos.webcam.WebcamDevice.BufferAccess;
import com.github.sarxos.webcam.WebcamDriver;
@@ -13,22 +10,20 @@
public class WebcamReadBufferTask extends WebcamTask {
- private static final Logger LOG = LoggerFactory.getLogger(WebcamReadBufferTask.class);
-
- private volatile ByteBuffer buffer = null;
+ private volatile ByteBuffer target = null;
- public WebcamReadBufferTask(WebcamDriver driver, WebcamDevice device) {
+ public WebcamReadBufferTask(WebcamDriver driver, WebcamDevice device, ByteBuffer target) {
super(driver, device);
+ this.target = target;
}
- public ByteBuffer getBuffer() {
+ public ByteBuffer readBuffer() {
try {
process();
} catch (InterruptedException e) {
- LOG.debug("Image buffer request interrupted", e);
return null;
}
- return buffer;
+ return target;
}
@Override
@@ -43,6 +38,6 @@ protected void handle() {
return;
}
- buffer = ((BufferAccess) device).getImageBytes();
+ ((BufferAccess) device).getImageBytes(target);
}
}
diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/util/ImageUtils.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/util/ImageUtils.java
index 8b70f4aa..7ee2980a 100644
--- a/webcam-capture/src/main/java/com/github/sarxos/webcam/util/ImageUtils.java
+++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/util/ImageUtils.java
@@ -1,8 +1,6 @@
package com.github.sarxos.webcam.util;
import java.awt.image.BufferedImage;
-import java.awt.image.DataBuffer;
-import java.awt.image.DataBufferByte;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -65,38 +63,4 @@ public static byte[] toByteArray(BufferedImage image, String format) {
return bytes;
}
-
- public static byte[] toRawByteArray(BufferedImage image) {
-
- DataBuffer dbuf = image.getRaster().getDataBuffer();
-
- if (dbuf instanceof DataBufferByte) {
-
- return ((DataBufferByte) dbuf).getData();
-
- } else {
-
- int w = image.getWidth();
- int h = image.getHeight();
- int n = w * h;
-
- byte[] bytes = new byte[n * 3];
-
- int i, x, y, rgb;
-
- for (i = 0; i < n; i++) {
-
- x = i % w;
- y = i / h;
-
- rgb = image.getRGB(x, y);
-
- bytes[i * 3 + 0] = (byte) ((rgb >> 16) & 0xff);
- bytes[i * 3 + 1] = (byte) ((rgb >> 8) & 0xff);
- bytes[i * 3 + 2] = (byte) (rgb & 0xff);
- }
-
- return bytes;
- }
- }
}