001package com.github.sarxos.webcam.ds.jmf; 002 003import java.awt.Dimension; 004import java.awt.Graphics2D; 005import java.awt.Image; 006import java.awt.image.BufferedImage; 007import java.util.ArrayList; 008import java.util.Collections; 009import java.util.Comparator; 010import java.util.List; 011import java.util.concurrent.Semaphore; 012 013import javax.media.Buffer; 014import javax.media.CaptureDeviceInfo; 015import javax.media.Control; 016import javax.media.Controller; 017import javax.media.ControllerEvent; 018import javax.media.ControllerListener; 019import javax.media.Format; 020import javax.media.Manager; 021import javax.media.MediaLocator; 022import javax.media.Player; 023import javax.media.ResourceUnavailableEvent; 024import javax.media.StartEvent; 025import javax.media.control.FormatControl; 026import javax.media.control.FrameGrabbingControl; 027import javax.media.format.VideoFormat; 028import javax.media.util.BufferToImage; 029 030import org.slf4j.Logger; 031import org.slf4j.LoggerFactory; 032 033import com.github.sarxos.webcam.WebcamDevice; 034 035 036/** 037 * Webcam device - JMF and FMJ implementation. 038 * 039 * @author Bartosz Firyn (SarXos) 040 */ 041public class JmfDevice implements WebcamDevice { 042 043 private static final Logger LOG = LoggerFactory.getLogger(JmfDevice.class); 044 045 /** 046 * Control to control format. 047 */ 048 private final static String FORMAT_CTRL = "javax.media.control.FormatControl"; 049 050 /** 051 * Control to grab frames. 052 */ 053 private final static String GRABBING_CTRL = "javax.media.control.FrameGrabbingControl"; 054 055 /** 056 * Player starter. 057 */ 058 private class PlayerStarter extends Thread implements ControllerListener { 059 060 public PlayerStarter(Player player) { 061 setDaemon(true); 062 player.addControllerListener(this); 063 player.start(); 064 } 065 066 @Override 067 public void run() { 068 069 // wait for start 070 while (!started) { 071 try { 072 Thread.sleep(10); 073 } catch (InterruptedException e) { 074 e.printStackTrace(); 075 } 076 started = player.getState() == Controller.Started; 077 if (started) { 078 break; 079 } 080 } 081 082 // try to grab single image (wait 10 seconds) 083 for (int i = 0; i < 100; i++) { 084 Buffer buffer = grabber.grabFrame(); 085 if (buffer.getLength() == 0) { 086 try { 087 Thread.sleep(100); 088 } catch (InterruptedException e) { 089 e.printStackTrace(); 090 } 091 } 092 } 093 094 semaphore.release(); 095 } 096 097 @Override 098 public void controllerUpdate(ControllerEvent ce) { 099 if (ce instanceof StartEvent) { 100 available = true; 101 semaphore.release(); 102 } 103 if (ce instanceof ResourceUnavailableEvent) { 104 available = false; 105 semaphore.release(); 106 } 107 } 108 } 109 110 /** 111 * Is webcam open. 112 */ 113 private volatile boolean open = false; 114 115 /** 116 * Is player available. 117 */ 118 private volatile boolean available = false; 119 120 /** 121 * Is player started. 122 */ 123 private volatile boolean started = false; 124 125 private volatile boolean disposed = false; 126 127 private PlayerStarter starter = null; 128 private Semaphore semaphore = new Semaphore(0, true); 129 130 private CaptureDeviceInfo cdi = null; 131 private List<Dimension> dimensions = null; 132 private Dimension dimension = null; 133 134 private CaptureDeviceInfo device = null; 135 private MediaLocator locator = null; 136 private Player player = null; 137 private VideoFormat format = null; 138 private FormatControl control = null; 139 private FrameGrabbingControl grabber = null; 140 private BufferToImage converter = null; 141 142 private Dimension viewSize = null; 143 144 public JmfDevice(CaptureDeviceInfo cdi) { 145 this.cdi = cdi; 146 } 147 148 @Override 149 public String getName() { 150 return cdi.getName(); 151 } 152 153 private VideoFormat createFormat(Dimension size) { 154 if (size == null) { 155 return getLargestVideoFormat(); 156 } else { 157 return getSizedVideoFormat(size); 158 } 159 } 160 161 /** 162 * Get player control. 163 * 164 * @param name control name 165 * @return Player control 166 */ 167 private Control getControl(String name) { 168 Control control = player.getControl(name); 169 if (control == null) { 170 throw new RuntimeException("Cannot find format control " + name); 171 } 172 return control; 173 } 174 175 /** 176 * Get video format for size. 177 * 178 * @param device device to get format from 179 * @param size specific size to search 180 * @return VideoFormat 181 */ 182 private VideoFormat getSizedVideoFormat(Dimension size) { 183 184 Format[] formats = device.getFormats(); 185 VideoFormat format = null; 186 187 for (Format f : formats) { 188 189 if (!"RGB".equalsIgnoreCase(f.getEncoding()) || !(f instanceof VideoFormat)) { 190 continue; 191 } 192 193 Dimension d = ((VideoFormat) f).getSize(); 194 if (d.width == size.width && d.height == size.height) { 195 format = (VideoFormat) f; 196 break; 197 } 198 } 199 200 return format; 201 } 202 203 /** 204 * Get suitable video format to use (the largest one by default, but this 205 * can be easily changed). 206 * 207 * @param device device to get video format for 208 * @return Suitable video format 209 */ 210 private VideoFormat getLargestVideoFormat() { 211 212 Format[] formats = device.getFormats(); 213 VideoFormat format = null; 214 int area = 0; 215 216 // find the largest picture format 217 for (Format f : formats) { 218 219 if (!(f instanceof VideoFormat) || !"RGB".equalsIgnoreCase(f.getEncoding())) { 220 continue; 221 } 222 223 VideoFormat vf = (VideoFormat) f; 224 Dimension dim = vf.getSize(); 225 226 int a = dim.width * dim.height; 227 if (a > area) { 228 area = a; 229 format = vf; 230 } 231 } 232 233 return format; 234 } 235 236 @Override 237 public Dimension[] getResolutions() { 238 239 if (dimensions == null) { 240 dimensions = new ArrayList<Dimension>(); 241 242 Format[] formats = cdi.getFormats(); 243 for (Format format : formats) { 244 if ("RGB".equalsIgnoreCase(format.getEncoding())) { 245 dimensions.add(((VideoFormat) format).getSize()); 246 } 247 } 248 249 Collections.sort(dimensions, new Comparator<Dimension>() { 250 251 @Override 252 public int compare(Dimension a, Dimension b) { 253 int apx = a.width * a.height; 254 int bpx = b.width * b.height; 255 if (apx > bpx) { 256 return 1; 257 } else if (apx < bpx) { 258 return -1; 259 } else { 260 return 0; 261 } 262 } 263 }); 264 } 265 266 return dimensions.toArray(new Dimension[dimensions.size()]); 267 } 268 269 @Override 270 public Dimension getResolution() { 271 return dimension; 272 } 273 274 @Override 275 public void setResolution(Dimension size) { 276 this.dimension = size; 277 } 278 279 @Override 280 public BufferedImage getImage() { 281 282 if (!open) { 283 throw new RuntimeException("Webcam has to be open to get image"); 284 } 285 286 Buffer buffer = grabber.grabFrame(); 287 Image image = converter.createImage(buffer); 288 289 if (image == null) { 290 throw new RuntimeException("Cannot get image"); 291 } 292 293 int width = image.getWidth(null); 294 int height = image.getHeight(null); 295 int type = BufferedImage.TYPE_INT_RGB; 296 297 BufferedImage buffered = new BufferedImage(width, height, type); 298 299 Graphics2D g2 = buffered.createGraphics(); 300 g2.drawImage(image, null, null); 301 g2.dispose(); 302 buffered.flush(); 303 304 return buffered; 305 } 306 307 @Override 308 public void open() { 309 310 if (disposed) { 311 LOG.warn("Cannot open device because it's already disposed"); 312 return; 313 } 314 315 if (open) { 316 LOG.debug("Opening webcam - already open!"); 317 return; 318 } 319 320 LOG.debug("Opening webcam"); 321 322 locator = device.getLocator(); 323 format = createFormat(viewSize); 324 converter = new BufferToImage(format); 325 326 try { 327 player = Manager.createRealizedPlayer(locator); 328 } catch (Exception e) { 329 throw new RuntimeException(e); 330 } 331 332 control = (FormatControl) getControl(FORMAT_CTRL); 333 grabber = (FrameGrabbingControl) getControl(GRABBING_CTRL); 334 335 if (control.setFormat(format) == null) { 336 throw new RuntimeException("Cannot change video format"); 337 } 338 339 starter = new PlayerStarter(player); 340 starter.start(); 341 342 try { 343 semaphore.acquire(2); 344 starter.join(); 345 } catch (InterruptedException e) { 346 } 347 348 do { 349 BufferedImage image = getImage(); 350 if (image != null) { 351 break; 352 } 353 try { 354 Thread.sleep(100); 355 } catch (InterruptedException e) { 356 } 357 } while (true); 358 359 open = started && available; 360 } 361 362 @Override 363 public void close() { 364 365 if (!started && !available && !open) { 366 LOG.debug("Cannot close webcam"); 367 return; 368 } 369 370 LOG.debug("Closing webcam"); 371 372 open = false; 373 374 if (started || available) { 375 376 started = false; 377 available = false; 378 379 player.stop(); 380 player.close(); 381 player.deallocate(); 382 } 383 } 384 385 @Override 386 public void dispose() { 387 disposed = true; 388 } 389 390 @Override 391 public boolean isOpen() { 392 return open; 393 } 394 395}