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

com.github.sarxos.webcam.WebcamUpdater 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;

import static com.github.sarxos.webcam.WebcamExceptionHandler.handle;

import java.awt.image.BufferedImage;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.sarxos.webcam.ds.cgt.WebcamGetImageTask;


/**
 * The goal of webcam updater class is to update image in parallel, so all calls to fetch image
 * invoked on webcam instance will be non-blocking (will return immediately).
 * 
 * @author Bartosz Firyn (sarxos)
 */
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 static 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
	 * > 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.
	 * 
	 * @author Bartosz Firyn (sarxos)
	 */
	private static final class UpdaterThreadFactory implements ThreadFactory {

		private static final AtomicInteger number = new AtomicInteger(0);

		@Override
		public Thread newThread(Runnable r) {
			Thread t = new Thread(r, String.format("webcam-updater-thread-%d", number.incrementAndGet()));
			t.setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance());
			t.setDaemon(true);
			return t;
		}

	}

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

	/**
	 * Target FPS.
	 */
	private static final int TARGET_FPS = 50;

	private static final UpdaterThreadFactory THREAD_FACTORY = new UpdaterThreadFactory();

	/**
	 * Executor service.
	 */
	private ScheduledExecutorService executor = null;

	/**
	 * Cached image.
	 */
	private final AtomicReference image = new AtomicReference();

	/**
	 * Webcam to which this updater is attached.
	 */
	private Webcam webcam = null;

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

	/**
	 * Is updater running.
	 */
	private AtomicBoolean running = new AtomicBoolean(false);

	private volatile boolean imageNew = false;

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

	/**
	 * Start updater.
	 */
	public void start() {

		if (running.compareAndSet(false, true)) {

			image.set(new WebcamGetImageTask(Webcam.getDriver(), webcam.getDevice()).getImage());

			executor = Executors.newSingleThreadScheduledExecutor(THREAD_FACTORY);
			executor.execute(this);

			LOG.debug("Webcam updater has been started");
		} else {
			LOG.debug("Webcam updater is already started");
		}
	}

	/**
	 * Stop updater.
	 */
	public void stop() {
		if (running.compareAndSet(true, false)) {

			executor.shutdown();
			while (!executor.isTerminated()) {
				try {
					executor.awaitTermination(100, TimeUnit.MILLISECONDS);
				} catch (InterruptedException e) {
					return;
				}
			}

			LOG.debug("Webcam updater has been stopped");
		} else {
			LOG.debug("Webcam updater is already stopped");
		}
	}

	@Override
	public void run() {

		if (!running.get()) {
			return;
		}

		try {
			tick();
		} catch (Throwable t) {
			handle(t);
		}
	}

	private void tick() {

		if (!webcam.isOpen()) {
			return;
		}

		// Calculate time required to fetch 1 picture.

		WebcamDriver driver = Webcam.getDriver();
		WebcamDevice device = webcam.getDevice();

		assert driver != null;
		assert device != null;

		boolean imageOk = false;
		long t1 = System.currentTimeMillis();
		try {
			image.set(webcam.transform(new WebcamGetImageTask(driver, device).getImage()));
			imageNew = true;
			imageOk = true;
		} catch (WebcamException e) {
			handle(e);
		}
		long t2 = System.currentTimeMillis();

		double deviceFps = -1;
		if (device instanceof WebcamDevice.FPSSource) {
			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;
		}

		// reschedule task

		if (webcam.isOpen()) {
			try {
				executor.schedule(this, delay, TimeUnit.MILLISECONDS);
			} catch (RejectedExecutionException e) {
				LOG.trace("Webcam update has been rejected", e);
			}
		}

		// notify webcam listeners about the new image available

		if (imageOk) {
			webcam.notifyWebcamImageAcquired(image.get());
		}
	}

	/**
	 * Return currently available image. This method will return immediately while it was been
	 * called after camera has been open. In case when there are parallel threads running and there
	 * is a possibility to call this method in the opening time, or before camera has been open at
	 * all, this method will block until webcam return first image. Maximum blocking time will be 10
	 * seconds, after this time method will return null.
	 * 
	 * @return Image stored in cache
	 */
	public BufferedImage getImage() {

		int i = 0;
		while (image.get() == null) {

			// Just in case if another thread starts calling this method before
			// updater has been properly started. This will loop while image is
			// not available.

			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}

			// Return null if more than 10 seconds passed (timeout).

			if (i++ > 100) {
				LOG.error("Image has not been found for more than 10 seconds");
				return null;
			}
		}

		imageNew = false;

		return image.get();
	}

	protected boolean isImageNew() {
		return imageNew;
	}

	/**
	 * Return current FPS number. It is calculated in real-time on the base of how often camera
	 * serve new image.
	 * 
	 * @return FPS number
	 */
	public double getFPS() {
		return fps;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy