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 }