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                this.item = item;
100                this.sub = subs.isEmpty() ? item : subs.get(0);
101        }
102
103        public String getCaptureDevice() {
104                switch (OsUtils.getOS()) {
105                        case WIN:
106                                return "dshow://";
107                        case OSX:
108                                return "qtcapture://";
109                        case NIX:
110                                return "v4l2://";
111                        default:
112                                throw new RuntimeException("Capture device not supported on " + OsUtils.getOS());
113                }
114        }
115
116        public MediaListItem getMediaListItem() {
117                return item;
118        }
119
120        public MediaListItem getMediaListItemSub() {
121                return sub;
122        }
123
124        @Override
125        public String getName() {
126                return sub.name();
127        }
128
129        public String getMRL() {
130                return sub.mrl();
131        }
132
133        public String getVDevice() {
134                return getMRL().replace(getCaptureDevice(), "");
135        }
136
137        @Override
138        public String toString() {
139                return String.format("%s[%s (%s)]", getClass().getSimpleName(), getName(), getMRL());
140        }
141
142        @Override
143        public Dimension[] getResolutions() {
144                return DIMENSIONS;
145        }
146
147        @Override
148        public Dimension getResolution() {
149                return size;
150        }
151
152        @Override
153        public void setResolution(Dimension size) {
154                this.size = size;
155        }
156
157        @Override
158        public BufferedImage getImage() {
159                if (!open) {
160                        throw new WebcamException("Cannot get image, webcam device is not open");
161                }
162                return player.getSnapshot(size.width, size.height);
163        }
164
165        @Override
166        public synchronized void open() {
167
168                if (disposed) {
169                        LOG.warn("Cannot open device because it has been disposed");
170                        return;
171                }
172
173                if (open) {
174                        return;
175                }
176
177                LOG.info("Opening webcam device");
178
179                factory = getFactory();
180                player = factory.newHeadlessMediaPlayer();
181
182                String[] options = null;
183
184                switch (OsUtils.getOS()) {
185                        case WIN:
186                                LOG.debug("Open VLC device {}", getName());
187                                options = new String[] {
188                                        ":dshow-vdev=" + getName(),
189                                        ":dshow-size=" + size.width + "x" + size.height,
190                                        ":dshow-adev=none", // no audio device
191                                };
192                                break;
193                        case NIX:
194                                LOG.debug("Open VLC device {}", getVDevice());
195                                options = new String[] {
196                                        ":v4l-vdev=" + getVDevice(),
197                                        ":v4l-width=" + size.width,
198                                        ":v4l-height=" + size.height,
199                                        ":v4l-fps=30",
200                                        ":v4l-quality=20",
201                                        ":v4l-adev=none", // no audio device
202                                };
203                                break;
204                        case OSX:
205                                LOG.debug("Open VLC device {}", getVDevice());
206                                options = new String[] {
207                                        ":qtcapture-vdev=" + getVDevice(),
208                                        ":qtcapture-width=" + size.width,
209                                        ":qtcapture-height=" + size.height,
210                                        ":qtcapture-adev=none", // no audio device
211                                };
212                                break;
213                }
214
215                player.startMedia(getMRL(), options);
216
217                // wait for images
218
219                int max = 0;
220                do {
221
222                        BufferedImage im = player.getSnapshot(size.width, size.height);
223                        if (im != null && im.getWidth() > 0) {
224                                open = true;
225                                LOG.info("Webcam device is now open: " + getName());
226                                return;
227                        }
228
229                        try {
230                                Thread.sleep(1000);
231                        } catch (InterruptedException e) {
232                                return;
233                        }
234
235                } while (max++ < 10);
236
237                open = false;
238        }
239
240        @Override
241        public synchronized void close() {
242
243                if (!open) {
244                        return;
245                }
246
247                LOG.info("Closing");
248
249                if (player != null) {
250                        LOG.debug("Releasing player");
251                        player.release();
252                }
253                if (factory != null) {
254                        LOG.debug("Releasing player");
255                        factory.release();
256                }
257
258                open = false;
259        }
260
261        @Override
262        public synchronized void dispose() {
263                disposed = true;
264        }
265
266        @Override
267        public boolean isOpen() {
268                return open;
269        }
270
271        public MediaPlayer getPlayer() {
272                return player;
273        }
274
275        public MediaPlayerFactory getFactory() {
276                if (factory == null) {
277                        factory = new MediaPlayerFactory(VLC_ARGS);
278                }
279                return factory;
280        }
281}