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