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 }