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    }