001    package com.github.sarxos.webcam.ds.vlcj;
002    
003    import java.awt.Dimension;
004    import java.awt.image.BufferedImage;
005    import java.util.List;
006    
007    import org.slf4j.Logger;
008    import org.slf4j.LoggerFactory;
009    
010    import uk.co.caprica.vlcj.medialist.MediaListItem;
011    import uk.co.caprica.vlcj.player.MediaPlayer;
012    import uk.co.caprica.vlcj.player.MediaPlayerFactory;
013    
014    import com.github.sarxos.webcam.WebcamDevice;
015    import com.github.sarxos.webcam.WebcamException;
016    import 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     */
025    enum 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     */
077    public 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    }