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}