package com.github.sarxos.webcam.ds.v4l4j; import java.awt.Dimension; import java.awt.image.BufferedImage; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import au.edu.jcu.v4l4j.CaptureCallback; import au.edu.jcu.v4l4j.DeviceInfo; import au.edu.jcu.v4l4j.FrameGrabber; import au.edu.jcu.v4l4j.ImageFormat; import au.edu.jcu.v4l4j.ImageFormatList; import au.edu.jcu.v4l4j.ResolutionInfo; import au.edu.jcu.v4l4j.ResolutionInfo.DiscreteResolution; import au.edu.jcu.v4l4j.V4L4JConstants; import au.edu.jcu.v4l4j.VideoDevice; import au.edu.jcu.v4l4j.VideoFrame; import au.edu.jcu.v4l4j.exceptions.StateException; import au.edu.jcu.v4l4j.exceptions.UnsupportedMethod; import au.edu.jcu.v4l4j.exceptions.V4L4JException; import com.github.sarxos.webcam.WebcamDevice; import com.github.sarxos.webcam.WebcamException; public class V4l4jDevice implements WebcamDevice, CaptureCallback, WebcamDevice.FPSSource { private static final Logger LOG = LoggerFactory.getLogger( V4l4jDevice.class ); private final File vfile; private VideoDevice vd = null; private DeviceInfo di = null; private ImageFormatList ifl = null; private FrameGrabber grabber = null; private List<ImageFormat> formats = null; private List<Dimension> resolutions = new ArrayList<Dimension>(); private Dimension resolution = null; private AtomicBoolean open = new AtomicBoolean( false ); private AtomicBoolean disposed = new AtomicBoolean( false ); private CountDownLatch latch = new CountDownLatch( 1 ); private volatile BufferedImage image = null; private volatile V4L4JException exception = null; /* used to calculate fps */ private long t1 = -1; private long t2 = -1; private volatile double fps = 0; public V4l4jDevice(File vfile) { this.vfile = vfile; LOG.debug( "Creating V4L4J devuce" ); try { vd = new VideoDevice( vfile.getAbsolutePath() ); } catch ( V4L4JException e ) { throw new WebcamException( String.format( "Cannot instantiate V4L4J device from %s", vfile ), e ); } try { di = vd.getDeviceInfo(); } catch ( V4L4JException e ) { throw new WebcamException( String.format( "Cannot get V4L4J device info from %s", vfile ), e ); } ifl = di.getFormatList(); formats = ifl.getYUVEncodableFormats(); for ( ImageFormat format : formats ) { String name = format.getName(); //System.out.println( "Found format " + name ); if ( name.startsWith( "YU" ) ) { ResolutionInfo ri = format.getResolutionInfo(); //System.out.println( "Resolution info " + name + " " + ri ); try { for ( DiscreteResolution dr : ri.getDiscreteResolutions() ) { resolutions.add( new Dimension( dr.getWidth(), dr.getHeight() ) ); } } catch ( UnsupportedMethod e ) { try { resolutions.add( new Dimension( ri.getStepwiseResolution().getWidthStep(), ri.getStepwiseResolution().getHeightStep() ) ); } catch ( UnsupportedMethod e1 ) { resolutions.add( new Dimension( 640, 480 ) ); } } } } } @Override public String getName() { return vfile.getAbsolutePath(); } @Override public Dimension[] getResolutions() { return resolutions.toArray( new Dimension[resolutions.size()] ); } @Override public Dimension getResolution() { if ( resolution == null ) { if ( resolutions.isEmpty() ) { throw new WebcamException( "No valid resolution detected for " + vfile ); } resolution = resolutions.get( 0 ); } return resolution; } @Override public void setResolution( Dimension size ) { resolution = size; } @Override public BufferedImage getImage() { if ( !open.get() ) { throw new RuntimeException( "Cannot get image from closed device" ); } V4L4JException ex = null; if ( exception != null ) { throw new WebcamException( ex ); } try { latch.await(); } catch ( InterruptedException e ) { LOG.trace( "Await has been interrupted", e ); return null; } return image; } @Override public synchronized void open() { if ( disposed.get() ) { throw new WebcamException( "Cannot open device because it has been already disposed" ); } if ( !open.compareAndSet( false, true ) ) { return; } LOG.debug( "Opening V4L4J device {}", vfile ); Dimension d = getResolution(); LOG.debug( "Constructing V4L4J frame grabber" ); try { grabber = vd.getJPEGFrameGrabber( d.width, d.height, 0, V4L4JConstants.STANDARD_NTSC, 70 ); } catch ( V4L4JException e ) { throw new WebcamException( e ); } grabber.setCaptureCallback( this ); int w1 = d.width; int h1 = d.height; int w2 = grabber.getWidth(); int h2 = grabber.getHeight(); if ( w1 != w2 || h1 != h2 ) { LOG.error( String.format( "Resolution mismatch %dx%d vs %dx%d, setting new one", w1, h1, w2, h2 ) ); resolution = new Dimension( w2, h2 ); } LOG.debug( "Starting V4L4J frame grabber" ); try { grabber.startCapture(); } catch ( V4L4JException e ) { throw new WebcamException( e ); } LOG.debug( "Webcam V4L4J is now open" ); } @Override public synchronized void close() { if ( !open.compareAndSet( true, false ) ) { return; } LOG.debug( "Closing V4L4J device {}", vfile ); try { grabber.stopCapture(); } catch ( StateException e ) { LOG.trace( "State exception on close", e ); // ignore } finally { image = null; latch.countDown(); } grabber = null; vd.releaseFrameGrabber(); LOG.debug( "V4L4J device {} has been closed", vfile ); } @Override public void dispose() { if ( !disposed.compareAndSet( false, true ) ) { return; } LOG.debug( "Disposing V4L4J device {}", vfile ); if ( open.get() ) { close(); } vd.releaseControlList(); vd.release(); LOG.debug( "V4L4J device {} has been disposed", vfile ); } @Override public boolean isOpen() { return open.get(); } @Override public void nextFrame( VideoFrame frame ) { if ( !open.get() ) { return; } if ( t1 == -1 || t2 == -1 ) { t1 = System.currentTimeMillis(); t2 = System.currentTimeMillis(); } try { image = frame.getBufferedImage(); } finally { try { frame.recycle(); } finally { latch.countDown(); } } t1 = t2; t2 = System.currentTimeMillis(); fps = ( 4 * fps + 1000 / ( t2 - t1 + 1 ) ) / 5; } @Override public void exceptionReceived( V4L4JException e ) { e.printStackTrace(); LOG.error( "Exception received from V4L4J", e ); exception = e; } @Override public double getFPS() { return fps; } }