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