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}