Skip to content

Commit

Permalink
Add possibility to read image to pre-created buffer, fixes #240
Browse files Browse the repository at this point in the history
  • Loading branch information
sarxos committed Jul 24, 2014
1 parent 3c6b66e commit 5742ff2
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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() {

Expand Down
49 changes: 39 additions & 10 deletions webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;


/**
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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.<br>
* <br>
*
* <b>IMPORTANT!</b><br>
* 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()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,27 @@ public interface WebcamDevice {
public static interface BufferAccess {

/**
* Get image in form of raw bytes. Do <b>not</b> 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.<br>
* <br>
*
* <b>NOTE!</b> Do <b>not</b> 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;


/**
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -75,6 +74,7 @@ protected void handle() {

grabber.setTimeout(timeout);
result.set(grabber.nextFrame());
fresh.set(true);
}
}

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

Expand All @@ -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) {
Expand Down Expand Up @@ -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<Byte> image = grabber.getImage();
fresh.set(false);

if (image == null) {
LOG.warn("Null array pointer found instead of image");
return null;
Expand All @@ -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<Byte> 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() {

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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()) {
Expand All @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
Loading

0 comments on commit 5742ff2

Please sign in to comment.