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

com.github.sarxos.webcam.ds.jmf.JmfDevice Maven / Gradle / Ivy

package com.github.sarxos.webcam.ds.jmf;

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Semaphore;

import javax.media.Buffer;
import javax.media.CaptureDeviceInfo;
import javax.media.Control;
import javax.media.Controller;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.Format;
import javax.media.Manager;
import javax.media.MediaLocator;
import javax.media.Player;
import javax.media.ResourceUnavailableEvent;
import javax.media.StartEvent;
import javax.media.control.FormatControl;
import javax.media.control.FrameGrabbingControl;
import javax.media.format.VideoFormat;
import javax.media.util.BufferToImage;

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

import com.github.sarxos.webcam.WebcamDevice;


/**
 * Webcam device - JMF and FMJ implementation.
 * 
 * @author Bartosz Firyn (SarXos)
 */
public class JmfDevice implements WebcamDevice {

	private static final Logger LOG = LoggerFactory.getLogger(JmfDevice.class);

	/**
	 * Control to control format.
	 */
	private final static String FORMAT_CTRL = "javax.media.control.FormatControl";

	/**
	 * Control to grab frames.
	 */
	private final static String GRABBING_CTRL = "javax.media.control.FrameGrabbingControl";

	/**
	 * Player starter.
	 */
	private class PlayerStarter extends Thread implements ControllerListener {

		public PlayerStarter(Player player) {
			setDaemon(true);
			player.addControllerListener(this);
			player.start();
		}

		@Override
		public void run() {

			// wait for start
			while (!started) {
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				started = player.getState() == Controller.Started;
				if (started) {
					break;
				}
			}

			// try to grab single image (wait 10 seconds)
			for (int i = 0; i < 100; i++) {
				Buffer buffer = grabber.grabFrame();
				if (buffer.getLength() == 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}

			semaphore.release();
		}

		@Override
		public void controllerUpdate(ControllerEvent ce) {
			if (ce instanceof StartEvent) {
				available = true;
				semaphore.release();
			}
			if (ce instanceof ResourceUnavailableEvent) {
				available = false;
				semaphore.release();
			}
		}
	}

	/**
	 * Is webcam open.
	 */
	private volatile boolean open = false;

	/**
	 * Is player available.
	 */
	private volatile boolean available = false;

	/**
	 * Is player started.
	 */
	private volatile boolean started = false;

	private volatile boolean disposed = false;

	private PlayerStarter starter = null;
	private Semaphore semaphore = new Semaphore(0, true);

	private CaptureDeviceInfo cdi = null;
	private List dimensions = null;
	private Dimension dimension = null;

	private CaptureDeviceInfo device = null;
	private MediaLocator locator = null;
	private Player player = null;
	private VideoFormat format = null;
	private FormatControl control = null;
	private FrameGrabbingControl grabber = null;
	private BufferToImage converter = null;

	private Dimension viewSize = null;

	public JmfDevice(CaptureDeviceInfo cdi) {
		this.cdi = cdi;
	}

	@Override
	public String getName() {
		return cdi.getName();
	}

	private VideoFormat createFormat(Dimension size) {
		if (size == null) {
			return getLargestVideoFormat();
		} else {
			return getSizedVideoFormat(size);
		}
	}

	/**
	 * Get player control.
	 * 
	 * @param name control name
	 * @return Player control
	 */
	private Control getControl(String name) {
		Control control = player.getControl(name);
		if (control == null) {
			throw new RuntimeException("Cannot find format control " + name);
		}
		return control;
	}

	/**
	 * Get video format for size.
	 * 
	 * @param device device to get format from
	 * @param size specific size to search
	 * @return VideoFormat
	 */
	private VideoFormat getSizedVideoFormat(Dimension size) {

		Format[] formats = device.getFormats();
		VideoFormat format = null;

		for (Format f : formats) {

			if (!"RGB".equalsIgnoreCase(f.getEncoding()) || !(f instanceof VideoFormat)) {
				continue;
			}

			Dimension d = ((VideoFormat) f).getSize();
			if (d.width == size.width && d.height == size.height) {
				format = (VideoFormat) f;
				break;
			}
		}

		return format;
	}

	/**
	 * Get suitable video format to use (the largest one by default, but this
	 * can be easily changed).
	 * 
	 * @param device device to get video format for
	 * @return Suitable video format
	 */
	private VideoFormat getLargestVideoFormat() {

		Format[] formats = device.getFormats();
		VideoFormat format = null;
		int area = 0;

		// find the largest picture format
		for (Format f : formats) {

			if (!(f instanceof VideoFormat) || !"RGB".equalsIgnoreCase(f.getEncoding())) {
				continue;
			}

			VideoFormat vf = (VideoFormat) f;
			Dimension dim = vf.getSize();

			int a = dim.width * dim.height;
			if (a > area) {
				area = a;
				format = vf;
			}
		}

		return format;
	}

	@Override
	public Dimension[] getResolutions() {

		if (dimensions == null) {
			dimensions = new ArrayList();

			Format[] formats = cdi.getFormats();
			for (Format format : formats) {
				if ("RGB".equalsIgnoreCase(format.getEncoding())) {
					dimensions.add(((VideoFormat) format).getSize());
				}
			}

			Collections.sort(dimensions, new Comparator() {

				@Override
				public int compare(Dimension a, Dimension b) {
					int apx = a.width * a.height;
					int bpx = b.width * b.height;
					if (apx > bpx) {
						return 1;
					} else if (apx < bpx) {
						return -1;
					} else {
						return 0;
					}
				}
			});
		}

		return dimensions.toArray(new Dimension[dimensions.size()]);
	}

	@Override
	public Dimension getResolution() {
		return dimension;
	}

	@Override
	public void setResolution(Dimension size) {
		this.dimension = size;
	}

	@Override
	public BufferedImage getImage() {

		if (!open) {
			throw new RuntimeException("Webcam has to be open to get image");
		}

		Buffer buffer = grabber.grabFrame();
		Image image = converter.createImage(buffer);

		if (image == null) {
			throw new RuntimeException("Cannot get image");
		}

		int width = image.getWidth(null);
		int height = image.getHeight(null);
		int type = BufferedImage.TYPE_INT_RGB;

		BufferedImage buffered = new BufferedImage(width, height, type);

		Graphics2D g2 = buffered.createGraphics();
		g2.drawImage(image, null, null);
		g2.dispose();
		buffered.flush();

		return buffered;
	}

	@Override
	public void open() {

		if (disposed) {
			LOG.warn("Cannot open device because it's already disposed");
			return;
		}

		if (open) {
			LOG.debug("Opening webcam - already open!");
			return;
		}

		LOG.debug("Opening webcam");

		locator = device.getLocator();
		format = createFormat(viewSize);
		converter = new BufferToImage(format);

		try {
			player = Manager.createRealizedPlayer(locator);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}

		control = (FormatControl) getControl(FORMAT_CTRL);
		grabber = (FrameGrabbingControl) getControl(GRABBING_CTRL);

		if (control.setFormat(format) == null) {
			throw new RuntimeException("Cannot change video format");
		}

		starter = new PlayerStarter(player);
		starter.start();

		try {
			semaphore.acquire(2);
			starter.join();
		} catch (InterruptedException e) {
		}

		do {
			BufferedImage image = getImage();
			if (image != null) {
				break;
			}
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
			}
		} while (true);

		open = started && available;
	}

	@Override
	public void close() {

		if (!started && !available && !open) {
			LOG.debug("Cannot close webcam");
			return;
		}

		LOG.debug("Closing webcam");

		open = false;

		if (started || available) {

			started = false;
			available = false;

			player.stop();
			player.close();
			player.deallocate();
		}
	}

	@Override
	public void dispose() {
		disposed = true;
	}

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

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy