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    }