001package com.github.sarxos.webcam.ds.v4l4j; 002 003import java.awt.Dimension; 004import java.awt.image.BufferedImage; 005import java.io.File; 006import java.util.ArrayList; 007import java.util.List; 008import java.util.concurrent.CountDownLatch; 009import java.util.concurrent.atomic.AtomicBoolean; 010 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013 014import au.edu.jcu.v4l4j.CaptureCallback; 015import au.edu.jcu.v4l4j.DeviceInfo; 016import au.edu.jcu.v4l4j.FrameGrabber; 017import au.edu.jcu.v4l4j.ImageFormat; 018import au.edu.jcu.v4l4j.ImageFormatList; 019import au.edu.jcu.v4l4j.ResolutionInfo; 020import au.edu.jcu.v4l4j.ResolutionInfo.DiscreteResolution; 021import au.edu.jcu.v4l4j.VideoDevice; 022import au.edu.jcu.v4l4j.VideoFrame; 023import au.edu.jcu.v4l4j.exceptions.StateException; 024import au.edu.jcu.v4l4j.exceptions.V4L4JException; 025 026import com.github.sarxos.webcam.WebcamDevice; 027import com.github.sarxos.webcam.WebcamException; 028 029 030public class V4l4jDevice implements WebcamDevice, CaptureCallback, WebcamDevice.FPSSource { 031 032 private static final Logger LOG = LoggerFactory.getLogger(V4l4jDevice.class); 033 034 private final File vfile; 035 036 private VideoDevice vd = null; 037 private DeviceInfo di = null; 038 private ImageFormatList ifl = null; 039 private FrameGrabber grabber = null; 040 041 private List<ImageFormat> formats = null; 042 private List<Dimension> resolutions = new ArrayList<Dimension>(); 043 private Dimension resolution = null; 044 045 private AtomicBoolean open = new AtomicBoolean(false); 046 private AtomicBoolean disposed = new AtomicBoolean(false); 047 private CountDownLatch latch = new CountDownLatch(1); 048 049 private volatile BufferedImage image = null; 050 private volatile V4L4JException exception = null; 051 052 /* used to calculate fps */ 053 054 private long t1 = -1; 055 private long t2 = -1; 056 057 private volatile double fps = 0; 058 059 public V4l4jDevice(File vfile) { 060 061 this.vfile = vfile; 062 063 LOG.debug("Creating V4L4J devuce"); 064 065 try { 066 vd = new VideoDevice(vfile.getAbsolutePath()); 067 } catch (V4L4JException e) { 068 throw new WebcamException(String.format("Cannot instantiate V4L4J device from %s", vfile), e); 069 } 070 071 try { 072 di = vd.getDeviceInfo(); 073 } catch (V4L4JException e) { 074 throw new WebcamException(String.format("Cannot get V4L4J device info from %s", vfile), e); 075 } 076 077 ifl = di.getFormatList(); 078 formats = ifl.getYUVEncodableFormats(); 079 080 for (ImageFormat format : formats) { 081 082 String name = format.getName(); 083 LOG.debug("Found format {}", name); 084 085 if (name.startsWith("YU")) { 086 087 ResolutionInfo ri = format.getResolutionInfo(); 088 LOG.debug("Resolution info {} {}", name, ri); 089 090 for (DiscreteResolution dr : ri.getDiscreteResolutions()) { 091 resolutions.add(new Dimension(dr.getWidth(), dr.getHeight())); 092 } 093 } 094 } 095 } 096 097 @Override 098 public String getName() { 099 return vfile.getAbsolutePath(); 100 } 101 102 @Override 103 public Dimension[] getResolutions() { 104 return resolutions.toArray(new Dimension[resolutions.size()]); 105 } 106 107 @Override 108 public Dimension getResolution() { 109 if (resolution == null) { 110 if (resolutions.isEmpty()) { 111 throw new WebcamException("No valid resolution detected for " + vfile); 112 } 113 resolution = resolutions.get(0); 114 } 115 return resolution; 116 } 117 118 @Override 119 public void setResolution(Dimension size) { 120 resolution = size; 121 } 122 123 @Override 124 public BufferedImage getImage() { 125 126 if (!open.get()) { 127 throw new RuntimeException("Cannot get image from closed device"); 128 } 129 130 V4L4JException ex = null; 131 if (exception != null) { 132 throw new WebcamException(ex); 133 } 134 135 try { 136 latch.await(); 137 } catch (InterruptedException e) { 138 LOG.trace("Await has been interrupted", e); 139 return null; 140 } 141 142 return image; 143 } 144 145 @Override 146 public synchronized void open() { 147 148 if (disposed.get()) { 149 throw new WebcamException("Cannot open device because it has been already disposed"); 150 } 151 152 if (!open.compareAndSet(false, true)) { 153 return; 154 } 155 156 LOG.debug("Opening V4L4J device {}", vfile); 157 158 Dimension d = getResolution(); 159 160 LOG.debug("Constructing V4L4J frame grabber"); 161 162 try { 163 grabber = vd.getJPEGFrameGrabber(d.width, d.height, 0, 0, 80); 164 } catch (V4L4JException e) { 165 throw new WebcamException(e); 166 } 167 168 grabber.setCaptureCallback(this); 169 170 int w1 = d.width; 171 int h1 = d.height; 172 int w2 = grabber.getWidth(); 173 int h2 = grabber.getHeight(); 174 175 if (w1 != w2 || h1 != h2) { 176 LOG.error(String.format("Resolution mismatch %dx%d vs %dx%d, setting new one", w1, h1, w2, h2)); 177 resolution = new Dimension(w2, h2); 178 } 179 180 LOG.debug("Starting V4L4J frame grabber"); 181 182 try { 183 grabber.startCapture(); 184 } catch (V4L4JException e) { 185 throw new WebcamException(e); 186 } 187 188 LOG.debug("Webcam V4L4J is now open"); 189 } 190 191 @Override 192 public synchronized void close() { 193 194 if (!open.compareAndSet(true, false)) { 195 return; 196 } 197 198 LOG.debug("Closing V4L4J device {}", vfile); 199 200 try { 201 grabber.stopCapture(); 202 } catch (StateException e) { 203 LOG.trace("State exception on close", e); // ignore 204 } finally { 205 image = null; 206 latch.countDown(); 207 } 208 209 grabber = null; 210 vd.releaseFrameGrabber(); 211 212 LOG.debug("V4L4J device {} has been closed", vfile); 213 214 } 215 216 @Override 217 public void dispose() { 218 219 if (!disposed.compareAndSet(false, true)) { 220 return; 221 } 222 223 LOG.debug("Disposing V4L4J device {}", vfile); 224 225 if (open.get()) { 226 close(); 227 } 228 229 vd.releaseControlList(); 230 vd.release(); 231 232 LOG.debug("V4L4J device {} has been disposed", vfile); 233 } 234 235 @Override 236 public boolean isOpen() { 237 return open.get(); 238 } 239 240 @Override 241 public void nextFrame(VideoFrame frame) { 242 243 if (!open.get()) { 244 return; 245 } 246 247 if (t1 == -1 || t2 == -1) { 248 t1 = System.currentTimeMillis(); 249 t2 = System.currentTimeMillis(); 250 } 251 252 try { 253 image = frame.getBufferedImage(); 254 } finally { 255 try { 256 frame.recycle(); 257 } finally { 258 latch.countDown(); 259 } 260 } 261 262 t1 = t2; 263 t2 = System.currentTimeMillis(); 264 265 fps = (4 * fps + 1000 / (t2 - t1 + 1)) / 5; 266 } 267 268 @Override 269 public void exceptionReceived(V4L4JException e) { 270 e.printStackTrace(); 271 LOG.error("Exception received from V4L4J", e); 272 exception = e; 273 } 274 275 @Override 276 public double getFPS() { 277 return fps; 278 } 279}