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<Dimension> 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<Dimension>();
Format[] formats = cdi.getFormats();
for (Format format : formats) {
if ("RGB".equalsIgnoreCase(format.getEncoding())) {
dimensions.add(((VideoFormat) format).getSize());
}
}
Collections.sort(dimensions, new Comparator<Dimension>() {
@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;
}
}