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}