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 }