001package com.github.sarxos.webcam.ds.vlcj;
002
003import java.awt.Dimension;
004import java.awt.image.BufferedImage;
005import java.util.List;
006
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010import uk.co.caprica.vlcj.medialist.MediaListItem;
011import uk.co.caprica.vlcj.player.MediaPlayer;
012import uk.co.caprica.vlcj.player.MediaPlayerFactory;
013
014import com.github.sarxos.webcam.WebcamDevice;
015import com.github.sarxos.webcam.WebcamException;
016import com.github.sarxos.webcam.WebcamResolution;
017
018
019/**
020 * Just a simple enumeration with supported (not yet confirmed) operating
021 * systems.
022 * 
023 * @author Bartosz Firyn (sarxos)
024 */
025enum OS {
026
027        /**
028         * Microsoft Windows
029         */
030        WIN,
031
032        /**
033         * Linux or UNIX.
034         */
035        NIX,
036
037        /**
038         * Mac OS X
039         */
040        OSX;
041
042        private static OS os = null;
043
044        /**
045         * Get operating system.
046         * 
047         * @return OS
048         */
049        public static final OS getOS() {
050                if (os == null) {
051                        String osp = System.getProperty("os.name").toLowerCase();
052                        if (osp.indexOf("win") >= 0) {
053                                os = WIN;
054                        } else if (osp.indexOf("mac") >= 0) {
055                                os = OSX;
056                        } else if (osp.indexOf("nix") >= 0 || osp.indexOf("nux") >= 0) {
057                                os = NIX;
058                        } else {
059                                throw new RuntimeException(osp + " is not supported");
060                        }
061                }
062                return os;
063        }
064
065}
066
067/**
068 * Capture driver which use vlcj project API to fetch images from camera. It
069 * should not be used when you need performance since vlcj saves snapshot image
070 * to disk prior it is returned - this affects performance and drop FPS rate
071 * down. In my case (HP Elitebook 8460p, 4 cores, 4 GB RAM, fast SSD disk) it
072 * was about ~12 FPS, which is very low when you compare it to the other capture
073 * drivers.
074 * 
075 * @author Bartosz Firyn (SarXos)
076 */
077public class VlcjDevice implements WebcamDevice {
078
079        private static final Logger LOG = LoggerFactory.getLogger(VlcjDevice.class);
080
081        /**
082         * Artificial view sizes. The vlcj is not able to detect resolutions
083         * supported by the webcam. If you would like to detect resolutions and have
084         * high-quality with good performance images streaming, you should rather
085         * use gstreamer or v4lvj capture drivers.
086         */
087        private final static Dimension[] DIMENSIONS = new Dimension[] {
088                WebcamResolution.QQVGA.getSize(),
089                WebcamResolution.QVGA.getSize(),
090                WebcamResolution.VGA.getSize(),
091        };
092
093        private final static String[] VLC_ARGS = {
094
095                // VLC args by Andrew Davison:
096                // http://fivedots.coe.psu.ac.th/~ad/jg/nui025/snapsWithoutJMF.pdf
097
098                // no interface
099                "--intf", "dummy",
100
101                // no video output
102                "--vout", "dummy",
103
104                // no audio decoding
105                "--no-audio",
106
107                // do not display title
108                "--no-video-title-show",
109
110                // no stats
111                "--no-stats",
112
113                // no subtitles
114                "--no-sub-autodetect-file",
115
116                // no snapshot previews
117                "--no-snapshot-preview",
118
119                // reduce capture lag/latency
120                "--live-caching=50",
121
122                // turn off warnings
123                "--quiet",
124        };
125
126        private Dimension size = null;
127        private MediaListItem item = null;
128        private MediaListItem sub = null;
129        private MediaPlayerFactory factory = null;
130        private MediaPlayer player = null;
131
132        private volatile boolean open = false;
133        private volatile boolean disposed = false;
134
135        protected VlcjDevice(MediaListItem item) {
136
137                if (item == null) {
138                        throw new IllegalArgumentException("Media list item cannot be null!");
139                }
140
141                List<MediaListItem> subs = item.subItems();
142
143                if (subs.isEmpty()) {
144                        throw new RuntimeException("Implementation does not support media list items which are empty!");
145                }
146
147                this.item = item;
148                this.sub = subs.get(0);
149        }
150
151        public String getCaptureDevice() {
152                switch (OS.getOS()) {
153                        case WIN:
154                                return "dshow://";
155                        case OSX:
156                                return "qtcapture://";
157                        case NIX:
158                                return "v4l2://";
159                        default:
160                                throw new RuntimeException("Capture device not supported on " + OS.getOS());
161                }
162        }
163
164        public MediaListItem getMediaListItem() {
165                return item;
166        }
167
168        public MediaListItem getMediaListItemSub() {
169                return sub;
170        }
171
172        @Override
173        public String getName() {
174                return sub.name();
175        }
176
177        public String getMRL() {
178                return sub.mrl();
179        }
180
181        public String getVDevice() {
182                return getMRL().replace(getCaptureDevice(), "");
183        }
184
185        @Override
186        public String toString() {
187                return String.format("%s[%s (%s)]", getClass().getSimpleName(), getName(), getMRL());
188        }
189
190        @Override
191        public Dimension[] getResolutions() {
192                return DIMENSIONS;
193        }
194
195        @Override
196        public Dimension getResolution() {
197                return size;
198        }
199
200        @Override
201        public void setResolution(Dimension size) {
202                this.size = size;
203        }
204
205        @Override
206        public BufferedImage getImage() {
207                if (!open) {
208                        throw new WebcamException("Cannot get image, webcam device is not open");
209                }
210                return player.getSnapshot();
211        }
212
213        @Override
214        public synchronized void open() {
215
216                if (disposed) {
217                        LOG.warn("Cannot open device because it has been disposed");
218                        return;
219                }
220
221                if (open) {
222                        return;
223                }
224
225                LOG.info("Opening webcam device");
226
227                factory = new MediaPlayerFactory(VLC_ARGS);
228                player = factory.newHeadlessMediaPlayer();
229
230                // for nix systems this should be changed dshow -> ... !!
231
232                String[] options = null;
233
234                switch (OS.getOS()) {
235                        case WIN:
236                                options = new String[] {
237                                        ":dshow-vdev=" + getName(),
238                                        ":dshow-size=" + size.width + "x" + size.height,
239                                        ":dshow-adev=none", // no audio device
240                                };
241                                break;
242                        case NIX:
243                                options = new String[] {
244                                        ":v4l-vdev=" + getVDevice(),
245                                        ":v4l-width=" + size.width,
246                                        ":v4l-height=" + size.height,
247                                        ":v4l-fps=30",
248                                        ":v4l-quality=20",
249                                        ":v4l-adev=none", // no audio device
250                                };
251                                break;
252                        case OSX:
253                                options = new String[] {
254                                        ":qtcapture-vdev=" + getVDevice(),
255                                        ":qtcapture-width=" + size.width,
256                                        ":qtcapture-height=" + size.height,
257                                        ":qtcapture-adev=none", // no audio device
258                                };
259                                break;
260                }
261
262                player.startMedia(getMRL(), options);
263
264                // wait for images
265
266                int max = 0;
267                do {
268
269                        BufferedImage im = player.getSnapshot(size.width, size.height);
270                        if (im != null && im.getWidth() > 0) {
271                                open = true;
272                                LOG.info("Webcam device is now open: " + getName());
273                                return;
274                        }
275
276                        try {
277                                Thread.sleep(1000);
278                        } catch (InterruptedException e) {
279                        }
280
281                } while (max++ < 10);
282
283                player.release();
284                factory.release();
285
286                open = false;
287        }
288
289        @Override
290        public synchronized void close() {
291
292                if (!open) {
293                        return;
294                }
295
296                LOG.info("Closing");
297
298                player.release();
299                factory.release();
300
301                open = false;
302        }
303
304        @Override
305        public synchronized void dispose() {
306                disposed = true;
307        }
308
309        @Override
310        public boolean isOpen() {
311                return open;
312        }
313
314        public MediaPlayer getPlayer() {
315                return player;
316        }
317
318        public MediaPlayerFactory getFactory() {
319                return factory;
320        }
321}