All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.sarxos.webcam.ds.buildin.WebcamDefaultDevice Maven / Gradle / Ivy

Go to download

This library allows you to use your PC webcam, IP or network cameras directly from Java. It's compatible with most operating systems (Windows, Linux, MacOS).

There is a newer version: 0.3.12
Show newest version
package com.github.sarxos.webcam.ds.buildin;

import java.awt.Dimension;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import org.bridj.Pointer;
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.WebcamException;
import com.github.sarxos.webcam.WebcamExceptionHandler;
import com.github.sarxos.webcam.WebcamResolution;
import com.github.sarxos.webcam.WebcamTask;
import com.github.sarxos.webcam.ds.buildin.natives.Device;
import com.github.sarxos.webcam.ds.buildin.natives.DeviceList;
import com.github.sarxos.webcam.ds.buildin.natives.OpenIMAJGrabber;


public class WebcamDefaultDevice implements WebcamDevice, BufferAccess, Runnable, WebcamDevice.FPSSource {

	/**
	 * Logger.
	 */
	private static final Logger LOG = LoggerFactory.getLogger(WebcamDefaultDevice.class);

	/**
	 * The device memory buffer size.
	 */
	private static final int DEVICE_BUFFER_SIZE = 5;

	/**
	 * Artificial view sizes. I'm really not sure if will fit into other webcams
	 * but hope that OpenIMAJ can handle this.
	 */
	private final static Dimension[] DIMENSIONS = new Dimension[] {
		WebcamResolution.QQVGA.getSize(),
		WebcamResolution.QVGA.getSize(),
		WebcamResolution.VGA.getSize(),
	};

	private class NextFrameTask extends WebcamTask {

		private final AtomicInteger result = new AtomicInteger(0);

		public NextFrameTask(WebcamDevice device) {
			super(device);
		}

		public int nextFrame() {
			try {
				process();
			} catch (InterruptedException e) {
				LOG.debug("Image buffer request interrupted", e);
			}
			return result.get();
		}

		@Override
		protected void handle() {

			WebcamDefaultDevice device = (WebcamDefaultDevice) getDevice();
			if (!device.isOpen()) {
				return;
			}

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

	/**
	 * RGB offsets.
	 */
	private static final int[] BAND_OFFSETS = new int[] { 0, 1, 2 };

	/**
	 * Number of bytes in each pixel.
	 */
	private static final int[] BITS = { 8, 8, 8 };

	/**
	 * Image offset.
	 */
	private static final int[] OFFSET = new int[] { 0 };

	/**
	 * Data type used in image.
	 */
	private static final int DATA_TYPE = DataBuffer.TYPE_BYTE;

	/**
	 * Image color space.
	 */
	private static final ColorSpace COLOR_SPACE = ColorSpace.getInstance(ColorSpace.CS_sRGB);

	/**
	 * Maximum image acquisition time (in milliseconds).
	 */
	private int timeout = 5000;

	private OpenIMAJGrabber grabber = null;
	private Device device = null;
	private Dimension size = null;
	private ComponentSampleModel smodel = null;
	private ColorModel cmodel = null;
	private boolean failOnSizeMismatch = false;

	private final AtomicBoolean disposed = new AtomicBoolean(false);
	private final AtomicBoolean open = new AtomicBoolean(false);

	/**
	 * Is the last image fresh one.
	 */
	private final AtomicBoolean fresh = new AtomicBoolean(false);

	private Thread refresher = null;

	private String name = null;
	private String id = null;
	private String fullname = null;

	private long t1 = -1;
	private long t2 = -1;

	/**
	 * Current FPS.
	 */
	private volatile double fps = 0;

	protected WebcamDefaultDevice(Device device) {
		this.device = device;
		this.name = device.getNameStr();
		this.id = device.getIdentifierStr();
		this.fullname = String.format("%s %s", this.name, this.id);
	}

	@Override
	public String getName() {
		return fullname;
	}

	public String getDeviceName() {
		return name;
	}

	public String getDeviceId() {
		return id;
	}

	public Device getDeviceRef() {
		return device;
	}

	@Override
	public Dimension[] getResolutions() {
		return DIMENSIONS;
	}

	@Override
	public Dimension getResolution() {
		if (size == null) {
			size = getResolutions()[0];
		}
		return size;
	}

	@Override
	public void setResolution(Dimension size) {

		if (size == null) {
			throw new IllegalArgumentException("Size cannot be null");
		}

		if (open.get()) {
			throw new IllegalStateException("Cannot change resolution when webcam is open, please close it first");
		}

		this.size = size;
	}

	@Override
	public ByteBuffer getImageBytes() {

		if (disposed.get()) {
			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;
		}

		// 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;
		}

		int length = size.width * size.height * 3;

		LOG.trace("Webcam device get buffer, read {} bytes", length);

		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() {

		ByteBuffer buffer = getImageBytes();

		if (buffer == null) {
			LOG.error("Images bytes buffer is null!");
			return null;
		}

		byte[] bytes = new byte[size.width * size.height * 3];
		byte[][] data = new byte[][] { bytes };

		buffer.get(bytes);

		DataBufferByte dbuf = new DataBufferByte(data, bytes.length, OFFSET);
		WritableRaster raster = Raster.createWritableRaster(smodel, dbuf, null);

		BufferedImage bi = new BufferedImage(cmodel, raster, false, null);
		bi.flush();

		return bi;
	}

	@Override
	public void open() {

		if (disposed.get()) {
			return;
		}

		LOG.debug("Opening webcam device {}", getName());

		if (size == null) {
			size = getResolutions()[0];
		}
		if (size == null) {
			throw new RuntimeException("The resolution size cannot be null");
		}

		LOG.debug("Webcam device {} starting session, size {}", device.getIdentifierStr(), size);

		grabber = new OpenIMAJGrabber();

		// NOTE!

		// Following the note from OpenIMAJ code - it seams like there is some
		// issue on 32-bit systems which prevents grabber to find devices.
		// According to the mentioned note this for loop shall fix the problem.

		DeviceList list = grabber.getVideoDevices().get();
		for (Device d : list.asArrayList()) {
			d.getNameStr();
			d.getIdentifierStr();
		}

		boolean started = grabber.startSession(size.width, size.height, 50, Pointer.pointerTo(device));
		if (!started) {
			throw new WebcamException("Cannot start native grabber!");
		}

		LOG.debug("Webcam device session started");

		Dimension size2 = new Dimension(grabber.getWidth(), grabber.getHeight());

		int w1 = size.width;
		int w2 = size2.width;
		int h1 = size.height;
		int h2 = size2.height;

		if (w1 != w2 || h1 != h2) {

			if (failOnSizeMismatch) {
				throw new WebcamException(String.format("Different size obtained vs requested - [%dx%d] vs [%dx%d]", w1, h1, w2, h2));
			}

			Object[] args = new Object[] { w1, h1, w2, h2, w2, h2 };
			LOG.warn("Different size obtained vs requested - [{}x{}] vs [{}x{}]. Setting correct one. New size is [{}x{}]", args);

			size = new Dimension(w2, h2);
		}

		smodel = new ComponentSampleModel(DATA_TYPE, size.width, size.height, 3, size.width * 3, BAND_OFFSETS);
		cmodel = new ComponentColorModel(COLOR_SPACE, BITS, false, false, Transparency.OPAQUE, DATA_TYPE);

		// clear device memory buffer

		LOG.debug("Clear memory buffer");

		clearMemoryBuffer();

		// set device to open

		LOG.debug("Webcam device {} is now open", this);

		open.set(true);

		// start underlying frames refresher

		refresher = startFramesRefresher();
	}

	/**
	 * this is to clean up all frames from device memory buffer which causes
	 * initial frames to be completely blank (black images)
	 */
	private void clearMemoryBuffer() {
		for (int i = 0; i < DEVICE_BUFFER_SIZE; i++) {
			grabber.nextFrame();
		}
	}

	/**
	 * Start underlying frames refresher.
	 * 
	 * @return Refresher thread
	 */
	private Thread startFramesRefresher() {
		Thread refresher = new Thread(this, String.format("frames-refresher-[%s]", id));
		refresher.setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance());
		refresher.setDaemon(true);
		refresher.start();
		return refresher;
	}

	@Override
	public void close() {

		if (!open.compareAndSet(true, false)) {
			return;
		}

		LOG.debug("Closing webcam device");

		grabber.stopSession();
	}

	@Override
	public void dispose() {

		if (!disposed.compareAndSet(false, true)) {
			return;
		}

		LOG.debug("Disposing webcam device {}", getName());

		close();
	}

	/**
	 * Determines if device should fail when requested image size is different
	 * than actually received.
	 * 
	 * @param fail the fail on size mismatch flag, true or false
	 */
	public void setFailOnSizeMismatch(boolean fail) {
		this.failOnSizeMismatch = fail;
	}

	@Override
	public boolean isOpen() {
		return open.get();
	}

	/**
	 * Get timeout for image acquisition.
	 * 
	 * @return Value in milliseconds
	 */
	public int getTimeout() {
		return timeout;
	}

	/**
	 * Set timeout for image acquisition.
	 * 
	 * @param timeout the timeout value in milliseconds
	 */
	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() {

		do {

			if (Thread.interrupted()) {
				LOG.debug("Refresher has been interrupted");
				return;
			}

			if (!open.get()) {
				LOG.debug("Cancelling refresher");
				return;
			}

			updateFrameBuffer();

		} while (open.get());
	}

	@Override
	public double getFPS() {
		return fps;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy