001 package com.github.sarxos.webcam.ds.buildin; 002 003 import java.awt.Dimension; 004 import java.awt.Transparency; 005 import java.awt.color.ColorSpace; 006 import java.awt.image.BufferedImage; 007 import java.awt.image.ColorModel; 008 import java.awt.image.ComponentColorModel; 009 import java.awt.image.ComponentSampleModel; 010 import java.awt.image.DataBuffer; 011 import java.awt.image.DataBufferByte; 012 import java.awt.image.Raster; 013 import java.awt.image.WritableRaster; 014 import java.nio.ByteBuffer; 015 import java.util.concurrent.atomic.AtomicBoolean; 016 017 import org.bridj.Pointer; 018 import org.slf4j.Logger; 019 import org.slf4j.LoggerFactory; 020 021 import com.github.sarxos.webcam.WebcamDevice; 022 import com.github.sarxos.webcam.WebcamDevice.BufferAccess; 023 import com.github.sarxos.webcam.WebcamException; 024 import com.github.sarxos.webcam.WebcamResolution; 025 import com.github.sarxos.webcam.ds.buildin.natives.Device; 026 import com.github.sarxos.webcam.ds.buildin.natives.DeviceList; 027 import com.github.sarxos.webcam.ds.buildin.natives.OpenIMAJGrabber; 028 029 030 public class WebcamDefaultDevice implements WebcamDevice, BufferAccess { 031 032 /** 033 * Logger. 034 */ 035 private static final Logger LOG = LoggerFactory.getLogger(WebcamDefaultDevice.class); 036 037 /** 038 * Artificial view sizes. I'm really not sure if will fit into other webcams 039 * but hope that OpenIMAJ can handle this. 040 */ 041 // @formatter:off 042 private final static Dimension[] DIMENSIONS = new Dimension[] { 043 WebcamResolution.QQVGA.getSize(), 044 WebcamResolution.QVGA.getSize(), 045 WebcamResolution.CIF.getSize(), 046 WebcamResolution.HVGA.getSize(), 047 WebcamResolution.VGA.getSize(), 048 WebcamResolution.XGA.getSize(), 049 }; 050 // @formatter:on 051 052 /** 053 * RGB offsets. 054 */ 055 private static final int[] BAND_OFFSETS = new int[] { 0, 1, 2 }; 056 057 /** 058 * Number of bytes in each pixel. 059 */ 060 private static final int[] BITS = { 8, 8, 8 }; 061 062 /** 063 * Image offset. 064 */ 065 private static final int[] OFFSET = new int[] { 0 }; 066 067 /** 068 * Data type used in image. 069 */ 070 private static final int DATA_TYPE = DataBuffer.TYPE_BYTE; 071 072 /** 073 * Image color space. 074 */ 075 private static final ColorSpace COLOR_SPACE = ColorSpace.getInstance(ColorSpace.CS_sRGB); 076 077 private int timeout = 5000; 078 079 private OpenIMAJGrabber grabber = null; 080 private Device device = null; 081 private Dimension size = null; 082 private ComponentSampleModel smodel = null; 083 private ColorModel cmodel = null; 084 private boolean failOnSizeMismatch = false; 085 086 private AtomicBoolean disposed = new AtomicBoolean(false); 087 private AtomicBoolean open = new AtomicBoolean(false); 088 089 private String name = null; 090 private String id = null; 091 private String fullname = null; 092 093 private byte[] bytes = null; 094 private byte[][] data = null; 095 096 protected WebcamDefaultDevice(Device device) { 097 this.device = device; 098 this.name = device.getNameStr(); 099 this.id = device.getIdentifierStr(); 100 this.fullname = String.format("%s %s", this.name, this.id); 101 } 102 103 @Override 104 public String getName() { 105 return fullname; 106 } 107 108 @Override 109 public Dimension[] getResolutions() { 110 return DIMENSIONS; 111 } 112 113 @Override 114 public Dimension getResolution() { 115 return size; 116 } 117 118 @Override 119 public void setResolution(Dimension size) { 120 if (open.get()) { 121 throw new IllegalStateException("Cannot change resolution when webcam is open, please close it first"); 122 } 123 this.size = size; 124 } 125 126 @Override 127 public ByteBuffer getImageBytes() { 128 129 if (disposed.get()) { 130 LOG.debug("Webcam is disposed, image will be null"); 131 return null; 132 } 133 134 if (!open.get()) { 135 LOG.debug("Webcam is closed, image will be null"); 136 return null; 137 } 138 139 LOG.trace("Webcam device get image (next frame)"); 140 141 // set image acquisition timeout 142 143 grabber.setTimeout(timeout); 144 145 // grab next frame 146 147 int flag = grabber.nextFrame(); 148 if (flag == -1) { 149 LOG.error("Timeout when requesting image!"); 150 return null; 151 } else if (flag < -1) { 152 LOG.error("Error requesting new frame!"); 153 return null; 154 } 155 156 // get image buffer 157 158 Pointer<Byte> image = grabber.getImage(); 159 if (image == null) { 160 LOG.warn("Null array pointer found instead of image"); 161 return null; 162 } 163 164 int length = size.width * size.height * 3; 165 166 LOG.trace("Webcam device get buffer, read {} bytes", length); 167 168 return image.getByteBuffer(length); 169 } 170 171 @Override 172 public BufferedImage getImage() { 173 174 ByteBuffer buffer = getImageBytes(); 175 176 if (buffer == null) { 177 LOG.error("Images bytes buffer is null!"); 178 return null; 179 } 180 181 buffer.get(bytes); 182 183 DataBufferByte dbuf = new DataBufferByte(data, bytes.length, OFFSET); 184 WritableRaster raster = Raster.createWritableRaster(smodel, dbuf, null); 185 186 BufferedImage bi = new BufferedImage(cmodel, raster, false, null); 187 bi.flush(); 188 189 return bi; 190 } 191 192 @Override 193 public void open() { 194 195 if (disposed.get()) { 196 return; 197 } 198 199 LOG.debug("Opening webcam device {}", getName()); 200 201 if (size == null) { 202 size = getResolutions()[0]; 203 } 204 205 LOG.debug("Webcam device {} starting session, size {}", device.getIdentifierStr(), size); 206 207 grabber = new OpenIMAJGrabber(); 208 209 // NOTE! 210 211 // Following the note from OpenIMAJ code - it seams like there is some 212 // issue on 32-bit systems which prevents grabber to find devices. 213 // According to the mentioned note this for loop shall fix the problem. 214 215 DeviceList list = grabber.getVideoDevices().get(); 216 for (Device d : list.asArrayList()) { 217 d.getNameStr(); 218 d.getIdentifierStr(); 219 } 220 221 boolean started = grabber.startSession(size.width, size.height, 50, Pointer.pointerTo(device)); 222 if (!started) { 223 throw new WebcamException("Cannot start native grabber!"); 224 } 225 226 LOG.debug("Webcam device session started"); 227 228 Dimension size2 = new Dimension(grabber.getWidth(), grabber.getHeight()); 229 230 int w1 = size.width; 231 int w2 = size2.width; 232 int h1 = size.height; 233 int h2 = size2.height; 234 235 if (w1 != w2 || h1 != h2) { 236 237 if (failOnSizeMismatch) { 238 throw new WebcamException(String.format("Different size obtained vs requested - [%dx%d] vs [%dx%d]", w1, h1, w2, h2)); 239 } 240 241 LOG.warn("Different size obtained vs requested - [{}x{}] vs [{}x{}]. Setting correct one. New size is [{}x{}]", new Object[] { w1, h1, w2, h2, w2, h2 }); 242 size = new Dimension(w2, h2); 243 } 244 245 smodel = new ComponentSampleModel(DATA_TYPE, size.width, size.height, 3, size.width * 3, BAND_OFFSETS); 246 cmodel = new ComponentColorModel(COLOR_SPACE, BITS, false, false, Transparency.OPAQUE, DATA_TYPE); 247 248 LOG.debug("Initialize buffer"); 249 250 int i = 0; 251 do { 252 253 grabber.nextFrame(); 254 255 try { 256 Thread.sleep(1000); 257 } catch (InterruptedException e) { 258 LOG.error("Nasty interrupted exception", e); 259 } 260 261 } while (++i < 3); 262 263 LOG.debug("Webcam device is now open"); 264 265 bytes = new byte[size.width * size.height * 3]; 266 data = new byte[][] { bytes }; 267 268 open.set(true); 269 } 270 271 @Override 272 public void close() { 273 274 if (!open.compareAndSet(true, false)) { 275 return; 276 } 277 278 LOG.debug("Closing webcam device"); 279 280 grabber.stopSession(); 281 } 282 283 @Override 284 public void dispose() { 285 286 if (!disposed.compareAndSet(false, true)) { 287 return; 288 } 289 290 LOG.debug("Disposing webcam device {}", getName()); 291 292 close(); 293 } 294 295 /** 296 * Determines if device should fail when requested image size is different 297 * than actually received. 298 * 299 * @param fail the fail on size mismatch flag, true or false 300 */ 301 public void setFailOnSizeMismatch(boolean fail) { 302 this.failOnSizeMismatch = fail; 303 } 304 305 @Override 306 public boolean isOpen() { 307 return open.get(); 308 } 309 310 /** 311 * Get timeout for image acquisition. 312 * 313 * @return Value in milliseconds 314 */ 315 public int getTimeout() { 316 return timeout; 317 } 318 319 /** 320 * Set timeout for image acquisition. 321 * 322 * @param timeout the timeout value in milliseconds 323 */ 324 public void setTimeout(int timeout) { 325 this.timeout = timeout; 326 } 327 }