001    package com.github.sarxos.webcam;
002    
003    import java.awt.Dimension;
004    import java.awt.image.BufferedImage;
005    import java.nio.ByteBuffer;
006    import java.util.ArrayList;
007    import java.util.Arrays;
008    import java.util.Collections;
009    import java.util.List;
010    import java.util.concurrent.TimeUnit;
011    import java.util.concurrent.TimeoutException;
012    import java.util.concurrent.atomic.AtomicBoolean;
013    
014    import org.slf4j.Logger;
015    import org.slf4j.LoggerFactory;
016    
017    import com.github.sarxos.webcam.WebcamDevice.BufferAccess;
018    import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDevice;
019    import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDriver;
020    import com.github.sarxos.webcam.ds.cgt.WebcamCloseTask;
021    import com.github.sarxos.webcam.ds.cgt.WebcamDisposeTask;
022    import com.github.sarxos.webcam.ds.cgt.WebcamOpenTask;
023    import com.github.sarxos.webcam.ds.cgt.WebcamReadBufferTask;
024    import com.github.sarxos.webcam.ds.cgt.WebcamReadImageTask;
025    import com.github.sarxos.webcam.util.ImageUtils;
026    
027    
028    /**
029     * Webcam class. It wraps webcam device obtained from webcam driver.
030     * 
031     * @author Bartosz Firyn (bfiryn)
032     */
033    public class Webcam {
034    
035            /**
036             * Logger instance.
037             */
038            private static final Logger LOG = LoggerFactory.getLogger(Webcam.class);
039    
040            /**
041             * List of driver classes names to search for.
042             */
043            private static final List<String> DRIVERS_LIST = new ArrayList<String>();
044    
045            /**
046             * List of driver classes to search for.
047             */
048            private static final List<Class<?>> DRIVERS_CLASS_LIST = new ArrayList<Class<?>>();
049    
050            /**
051             * Discovery listeners.
052             */
053            private static final List<WebcamDiscoveryListener> DISCOVERY_LISTENERS = Collections.synchronizedList(new ArrayList<WebcamDiscoveryListener>());
054    
055            /**
056             * Webcam driver (LtiCivil, JMF, FMJ, JQT, OpenCV, VLCj, etc).
057             */
058            private static WebcamDriver driver = null;
059    
060            /**
061             * Webcam discovery service.
062             */
063            private static volatile WebcamDiscoveryService discovery = null;
064    
065            /**
066             * Is automated deallocation on TERM signal enabled.
067             */
068            private static boolean deallocOnTermSignal = false;
069    
070            /**
071             * Is auto-open feature enabled?
072             */
073            private static boolean autoOpen = false;
074    
075            /**
076             * Webcam listeners.
077             */
078            private List<WebcamListener> listeners = Collections.synchronizedList(new ArrayList<WebcamListener>());
079    
080            /**
081             * List of custom resolution sizes supported by webcam instance.
082             */
083            private List<Dimension> customSizes = new ArrayList<Dimension>();
084    
085            /**
086             * Shutdown hook.
087             */
088            private WebcamShutdownHook hook = null;
089    
090            /**
091             * Underlying webcam device.
092             */
093            private WebcamDevice device = null;
094    
095            /**
096             * Is webcam open?
097             */
098            private AtomicBoolean open = new AtomicBoolean(false);
099    
100            /**
101             * Is webcam already disposed?
102             */
103            private AtomicBoolean disposed = new AtomicBoolean(false);
104    
105            /**
106             * Is non-blocking (asynchronous) access enabled?
107             */
108            private volatile boolean asynchronous = false;
109    
110            /**
111             * Current FPS.
112             */
113            private volatile double fps = 0;
114    
115            /**
116             * Webcam image updater.
117             */
118            private WebcamUpdater updater = new WebcamUpdater(this);
119    
120            /**
121             * IMage transformer.
122             */
123            private volatile WebcamImageTransformer transformer = null;
124    
125            /**
126             * Webcam class.
127             * 
128             * @param device - device to be used as webcam
129             * @throws IllegalArgumentException when device argument is null
130             */
131            protected Webcam(WebcamDevice device) {
132                    if (device == null) {
133                            throw new IllegalArgumentException("Webcam device cannot be null");
134                    }
135                    this.device = device;
136            }
137    
138            /**
139             * Open the webcam in blocking (synchronous) mode.
140             * 
141             * @see #open(boolean)
142             */
143            public boolean open() {
144                    return open(false);
145            }
146    
147            /**
148             * Open the webcam in either blocking (synchronous) or non-blocking
149             * (asynchronous) mode.The difference between those two modes lies in the
150             * image acquisition mechanism.<br>
151             * <br>
152             * In blocking mode, when user calls {@link #getImage()} method, device is
153             * being queried for new image buffer and user have to wait for it to be
154             * available.<br>
155             * <br>
156             * In non-blocking mode, there is a special thread running in the background
157             * which constantly fetch new images and cache them internally for further
158             * use. This cached instance is returned every time when user request new
159             * image. Because of that it can be used when timeing is very important,
160             * because all users calls for new image do not have to wait on device
161             * response. By using this mode user should be aware of the fact that in
162             * some cases, when two consecutive calls to get new image are executed more
163             * often than webcam device can serve them, the same image instance will be
164             * returned. User should use {@link #isImageNew()} method to distinguish if
165             * returned image is not the same as the previous one.
166             * 
167             * @param async true for non-blocking mode, false for blocking
168             */
169            public boolean open(boolean async) {
170    
171                    if (open.compareAndSet(false, true)) {
172    
173                            assert updater != null;
174    
175                            WebcamOpenTask task = new WebcamOpenTask(driver, device);
176                            try {
177                                    task.open();
178                            } catch (InterruptedException e) {
179                                    open.set(false);
180                                    LOG.error("Processor has been interrupted before webcam was open!", e);
181                                    return false;
182                            } catch (WebcamException e) {
183                                    open.set(false);
184                                    throw e;
185                            }
186    
187                            LOG.debug("Webcam is now open {}", getName());
188    
189                            // setup non-blocking configuration
190    
191                            asynchronous = async;
192    
193                            if (async) {
194                                    updater.start();
195                            }
196    
197                            // install shutdown hook
198    
199                            Runtime.getRuntime().addShutdownHook(hook = new WebcamShutdownHook(this));
200    
201                            // notify listeners
202    
203                            WebcamEvent we = new WebcamEvent(WebcamEventType.OPEN, this);
204                            for (WebcamListener l : getWebcamListeners()) {
205                                    try {
206                                            l.webcamOpen(we);
207                                    } catch (Exception e) {
208                                            LOG.error(String.format("Notify webcam open, exception when calling listener %s", l.getClass()), e);
209                                    }
210                            }
211    
212                    } else {
213                            LOG.debug("Webcam is already open {}", getName());
214                    }
215    
216                    return true;
217            }
218    
219            /**
220             * Close the webcam.
221             */
222            public boolean close() {
223    
224                    if (open.compareAndSet(true, false)) {
225    
226                            assert updater != null;
227    
228                            // close webcam
229    
230                            WebcamCloseTask task = new WebcamCloseTask(driver, device);
231                            try {
232                                    task.close();
233                            } catch (InterruptedException e) {
234                                    open.set(true);
235                                    LOG.error("Processor has been interrupted before webcam was closed!", e);
236                                    return false;
237                            } catch (WebcamException e) {
238                                    open.set(false);
239                                    throw e;
240                            }
241    
242                            // stop updater
243    
244                            if (asynchronous) {
245                                    updater.stop();
246                            }
247    
248                            // remove shutdown hook (it's not more necessary)
249    
250                            removeShutdownHook();
251    
252                            // notify listeners
253    
254                            WebcamEvent we = new WebcamEvent(WebcamEventType.CLOSED, this);
255                            for (WebcamListener l : getWebcamListeners()) {
256                                    try {
257                                            l.webcamClosed(we);
258                                    } catch (Exception e) {
259                                            LOG.error(String.format("Notify webcam closed, exception when calling %s listener", l.getClass()), e);
260                                    }
261                            }
262    
263                    } else {
264                            LOG.debug("Webcam is already closed {}", getName());
265                    }
266    
267                    return true;
268            }
269    
270            /**
271             * Return underlying webcam device. Depending on the driver used to discover
272             * devices, this method can return instances of different class. By default
273             * {@link WebcamDefaultDevice} is returned when no external driver is used.
274             * 
275             * @return Underlying webcam device instance
276             */
277            public WebcamDevice getDevice() {
278                    assert device != null;
279                    return device;
280            }
281    
282            /**
283             * Completely dispose capture device. After this operation webcam cannot be
284             * used any more and full reinstantiation is required.
285             */
286            protected void dispose() {
287    
288                    assert disposed != null;
289                    assert open != null;
290                    assert driver != null;
291                    assert device != null;
292                    assert listeners != null;
293    
294                    if (!disposed.compareAndSet(false, true)) {
295                            return;
296                    }
297    
298                    open.set(false);
299    
300                    LOG.info("Disposing webcam {}", getName());
301    
302                    WebcamDisposeTask task = new WebcamDisposeTask(driver, device);
303                    try {
304                            task.dispose();
305                    } catch (InterruptedException e) {
306                            LOG.error("Processor has been interrupted before webcam was disposed!", e);
307                            return;
308                    }
309    
310                    WebcamEvent we = new WebcamEvent(WebcamEventType.DISPOSED, this);
311                    for (WebcamListener l : listeners) {
312                            try {
313                                    l.webcamClosed(we);
314                                    l.webcamDisposed(we);
315                            } catch (Exception e) {
316                                    LOG.error(String.format("Notify webcam disposed, exception when calling %s listener", l.getClass()), e);
317                            }
318                    }
319    
320                    removeShutdownHook();
321    
322                    LOG.debug("Webcam disposed {}", getName());
323            }
324    
325            private void removeShutdownHook() {
326    
327                    // hook can be null because there is a possibility that webcam has never
328                    // been open and therefore hook was not created
329    
330                    if (hook != null) {
331                            try {
332                                    Runtime.getRuntime().removeShutdownHook(hook);
333                            } catch (IllegalStateException e) {
334                                    LOG.trace("Shutdown in progress, cannot remove hook");
335                            }
336                    }
337            }
338    
339            /**
340             * TRansform image using image transformer. If image transformer has not
341             * been set, this method return instance passed in the argument, without any
342             * modifications.
343             * 
344             * @param image the image to be transformed
345             * @return Transformed image (if transformer is set)
346             */
347            protected BufferedImage transform(BufferedImage image) {
348                    if (image != null) {
349                            WebcamImageTransformer tr = getImageTransformer();
350                            if (tr != null) {
351                                    return tr.transform(image);
352                            }
353                    }
354                    return image;
355            }
356    
357            /**
358             * Is webcam open?
359             * 
360             * @return true if open, false otherwise
361             */
362            public boolean isOpen() {
363                    return open.get();
364            }
365    
366            /**
367             * Get current webcam resolution in pixels.
368             * 
369             * @return Webcam resolution (picture size) in pixels.
370             */
371            public Dimension getViewSize() {
372                    return device.getResolution();
373            }
374    
375            /**
376             * Return list of supported view sizes. It can differ between vary webcam
377             * data sources.
378             * 
379             * @return Array of supported dimensions
380             */
381            public Dimension[] getViewSizes() {
382                    return device.getResolutions();
383            }
384    
385            /**
386             * Set custom resolution. If you are using this method you have to make sure
387             * that your webcam device can support this specific resolution.
388             * 
389             * @param sizes the array of custom resolutions to be supported by webcam
390             */
391            public void setCustomViewSizes(Dimension[] sizes) {
392                    assert customSizes != null;
393                    if (sizes == null) {
394                            customSizes.clear();
395                            return;
396                    }
397                    customSizes = Arrays.asList(sizes);
398            }
399    
400            public Dimension[] getCustomViewSizes() {
401                    assert customSizes != null;
402                    return customSizes.toArray(new Dimension[customSizes.size()]);
403            }
404    
405            /**
406             * Set new view size. New size has to exactly the same as one of the default
407             * sized or exactly the same as one of the custom ones.
408             * 
409             * @param size the new view size to be set
410             * @see Webcam#setCustomViewSizes(Dimension[])
411             * @see Webcam#getViewSizes()
412             */
413            public void setViewSize(Dimension size) {
414    
415                    if (size == null) {
416                            throw new IllegalArgumentException("Resolution cannot be null!");
417                    }
418    
419                    if (open.get()) {
420                            throw new IllegalStateException("Cannot change resolution when webcam is open, please close it first");
421                    }
422    
423                    // check if new resolution is the same as current one
424    
425                    Dimension current = getViewSize();
426                    if (current != null && current.width == size.width && current.height == size.height) {
427                            return;
428                    }
429    
430                    // check if new resolution is valid
431    
432                    Dimension[] predefined = getViewSizes();
433                    Dimension[] custom = getCustomViewSizes();
434    
435                    assert predefined != null;
436                    assert custom != null;
437    
438                    boolean ok = false;
439                    for (Dimension d : predefined) {
440                            if (d.width == size.width && d.height == size.height) {
441                                    ok = true;
442                                    break;
443                            }
444                    }
445                    if (!ok) {
446                            for (Dimension d : custom) {
447                                    if (d.width == size.width && d.height == size.height) {
448                                            ok = true;
449                                            break;
450                                    }
451                            }
452                    }
453    
454                    if (!ok) {
455                            StringBuilder sb = new StringBuilder("Incorrect dimension [");
456                            sb.append(size.width).append("x").append(size.height).append("] ");
457                            sb.append("possible ones are ");
458                            for (Dimension d : predefined) {
459                                    sb.append("[").append(d.width).append("x").append(d.height).append("] ");
460                            }
461                            for (Dimension d : custom) {
462                                    sb.append("[").append(d.width).append("x").append(d.height).append("] ");
463                            }
464                            throw new IllegalArgumentException(sb.toString());
465                    }
466    
467                    LOG.debug("Setting new resolution {}x{}", size.width, size.height);
468    
469                    device.setResolution(size);
470            }
471    
472            /**
473             * Capture image from webcam and return it. Will return image object or null
474             * if webcam is closed or has been already disposed by JVM.<br>
475             * <br>
476             * <b>IMPORTANT NOTE!!!</b><br>
477             * <br>
478             * There are two possible behaviors of what webcam should do when you try to
479             * get image and webcam is actually closed. Normally it will return null,
480             * but there is a special flag which can be statically set to switch all
481             * webcams to auto open mode. In this mode, webcam will be automatically
482             * open, when you try to get image from closed webcam. Please be aware of
483             * some side effects! In case of multi-threaded applications, there is no
484             * guarantee that one thread will not try to open webcam even if it was
485             * manually closed in different thread.
486             * 
487             * @return Captured image or null if webcam is closed or disposed by JVM
488             */
489            public BufferedImage getImage() {
490    
491                    if (!isReady()) {
492                            return null;
493                    }
494    
495                    long t1 = 0;
496                    long t2 = 0;
497    
498                    assert updater != null;
499    
500                    if (asynchronous) {
501                            return updater.getImage();
502                    } else {
503    
504                            // get image
505    
506                            t1 = System.currentTimeMillis();
507                            BufferedImage image = transform(new WebcamReadImageTask(driver, device).getImage());
508                            t2 = System.currentTimeMillis();
509    
510                            if (image == null) {
511                                    return null;
512                            }
513    
514                            // get FPS
515    
516                            if (device instanceof WebcamDevice.FPSSource) {
517                                    fps = ((WebcamDevice.FPSSource) device).getFPS();
518                            } else {
519                                    // +1 to avoid division by zero
520                                    fps = (4 * fps + 1000 / (t2 - t1 + 1)) / 5;
521                            }
522    
523                            // notify webcam listeners about new image available
524    
525                            updater.notifyWebcamImageObtained(this, image);
526    
527                            return image;
528                    }
529            }
530    
531            public boolean isImageNew() {
532                    assert updater != null;
533                    if (asynchronous) {
534                            return updater.isImageNew();
535                    }
536                    return true;
537            }
538    
539            protected double getFPS() {
540                    assert updater != null;
541                    if (asynchronous) {
542                            return updater.getFPS();
543                    } else {
544                            return fps;
545                    }
546            }
547    
548            /**
549             * Get RAW image ByteBuffer. It will always return buffer with 3 x 1 bytes
550             * per each pixel, where RGB components are on (0, 1, 2) and color space is
551             * sRGB.<br>
552             * <br>
553             * 
554             * <b>IMPORTANT!</b><br>
555             * Some drivers can return direct ByteBuffer, so there is no guarantee that
556             * underlying bytes will not be released in next read image operation.
557             * Therefore, to avoid potential bugs you should convert this ByteBuffer to
558             * bytes array before you fetch next image.
559             * 
560             * @return Byte buffer
561             */
562            public ByteBuffer getImageBytes() {
563    
564                    if (!isReady()) {
565                            return null;
566                    }
567    
568                    assert driver != null;
569                    assert device != null;
570    
571                    // some devices can support direct image buffers, and for those call
572                    // processor task, and for those which does not support direct image
573                    // buffers, just convert image to RGB byte array
574    
575                    if (device instanceof BufferAccess) {
576                            return new WebcamReadBufferTask(driver, device).getBuffer();
577                    } else {
578                            BufferedImage image = getImage();
579                            if (image != null) {
580                                    return ByteBuffer.wrap(ImageUtils.toRawByteArray(image));
581                            } else {
582                                    return null;
583                            }
584                    }
585            }
586    
587            /**
588             * Is webcam ready to be read.
589             * 
590             * @return True if ready, false otherwise
591             */
592            private boolean isReady() {
593    
594                    assert disposed != null;
595                    assert open != null;
596    
597                    if (disposed.get()) {
598                            LOG.warn("Cannot get image, webcam has been already disposed");
599                            return false;
600                    }
601    
602                    if (!open.get()) {
603                            if (autoOpen) {
604                                    open();
605                            } else {
606                                    return false;
607                            }
608                    }
609    
610                    return true;
611            }
612    
613            /**
614             * Get list of webcams to use. This method will wait predefined time
615             * interval for webcam devices to be discovered. By default this time is set
616             * to 1 minute.
617             * 
618             * @return List of webcams existing in the system
619             * @throws WebcamException when something is wrong
620             * @see Webcam#getWebcams(long, TimeUnit)
621             */
622            public static List<Webcam> getWebcams() throws WebcamException {
623    
624                    // timeout exception below will never be caught since user would have to
625                    // wait around three hundreds billion years for it to occur
626    
627                    try {
628                            return getWebcams(Long.MAX_VALUE);
629                    } catch (TimeoutException e) {
630                            throw new RuntimeException(e);
631                    }
632            }
633    
634            /**
635             * Get list of webcams to use. This method will wait given time interval for
636             * webcam devices to be discovered. Time argument is given in milliseconds.
637             * 
638             * @param timeout the time to wait for webcam devices to be discovered
639             * @return List of webcams existing in the ssytem
640             * @throws WebcamException when something is wrong
641             * @throws IllegalArgumentException when timeout is negative
642             * @see Webcam#getWebcams(long, TimeUnit)
643             */
644            public static List<Webcam> getWebcams(long timeout) throws TimeoutException, WebcamException {
645                    if (timeout < 0) {
646                            throw new IllegalArgumentException(String.format("Timeout cannot be negative (%d)", timeout));
647                    }
648                    return getWebcams(timeout, TimeUnit.MILLISECONDS);
649            }
650    
651            /**
652             * Get list of webcams to use. This method will wait given time interval for
653             * webcam devices to be discovered.
654             * 
655             * @param timeout the devices discovery timeout
656             * @param tunit the time unit
657             * @return List of webcams
658             * @throws TimeoutException when timeout has been exceeded
659             * @throws WebcamException when something is wrong
660             * @throws IllegalArgumentException when timeout is negative or tunit null
661             */
662            public static synchronized List<Webcam> getWebcams(long timeout, TimeUnit tunit) throws TimeoutException, WebcamException {
663    
664                    if (timeout < 0) {
665                            throw new IllegalArgumentException(String.format("Timeout cannot be negative (%d)", timeout));
666                    }
667                    if (tunit == null) {
668                            throw new IllegalArgumentException("Time unit cannot be null!");
669                    }
670    
671                    WebcamDiscoveryService discovery = getDiscoveryService();
672    
673                    assert discovery != null;
674    
675                    List<Webcam> webcams = discovery.getWebcams(timeout, tunit);
676                    if (!discovery.isRunning()) {
677                            discovery.start();
678                    }
679    
680                    return webcams;
681            }
682    
683            /**
684             * Will discover and return first webcam available in the system.
685             * 
686             * @return Default webcam (first from the list)
687             * @throws WebcamException if something is really wrong
688             * @see Webcam#getWebcams()
689             */
690            public static Webcam getDefault() throws WebcamException {
691    
692                    try {
693                            return getDefault(Long.MAX_VALUE);
694                    } catch (TimeoutException e) {
695                            // this should never happen since user would have to wait 300000000
696                            // years for it to occur
697                            throw new RuntimeException(e);
698                    }
699            }
700    
701            /**
702             * Will discover and return first webcam available in the system.
703             * 
704             * @param timeout the webcam discovery timeout (1 minute by default)
705             * @return Default webcam (first from the list)
706             * @throws TimeoutException when discovery timeout has been exceeded
707             * @throws WebcamException if something is really wrong
708             * @throws IllegalArgumentException when timeout is negative
709             * @see Webcam#getWebcams(long)
710             */
711            public static Webcam getDefault(long timeout) throws TimeoutException, WebcamException {
712                    if (timeout < 0) {
713                            throw new IllegalArgumentException(String.format("Timeout cannot be negative (%d)", timeout));
714                    }
715                    return getDefault(timeout, TimeUnit.MILLISECONDS);
716            }
717    
718            /**
719             * Will discover and return first webcam available in the system.
720             * 
721             * @param timeout the webcam discovery timeout (1 minute by default)
722             * @param tunit the time unit
723             * @return Default webcam (first from the list)
724             * @throws TimeoutException when discovery timeout has been exceeded
725             * @throws WebcamException if something is really wrong
726             * @throws IllegalArgumentException when timeout is negative or tunit null
727             * @see Webcam#getWebcams(long, TimeUnit)
728             */
729            public static Webcam getDefault(long timeout, TimeUnit tunit) throws TimeoutException, WebcamException {
730    
731                    if (timeout < 0) {
732                            throw new IllegalArgumentException(String.format("Timeout cannot be negative (%d)", timeout));
733                    }
734                    if (tunit == null) {
735                            throw new IllegalArgumentException("Time unit cannot be null!");
736                    }
737    
738                    List<Webcam> webcams = getWebcams(timeout, tunit);
739    
740                    assert webcams != null;
741    
742                    if (!webcams.isEmpty()) {
743                            return webcams.get(0);
744                    }
745    
746                    LOG.warn("No webcam has been detected!");
747    
748                    return null;
749            }
750    
751            /**
752             * Get webcam name (device name). The name of device depends on the value
753             * returned by the underlying data source, so in some cases it can be
754             * human-readable value and sometimes it can be some strange number.
755             * 
756             * @return Name
757             */
758            public String getName() {
759                    assert device != null;
760                    return device.getName();
761            }
762    
763            @Override
764            public String toString() {
765                    return String.format("Webcam %s", getName());
766            }
767    
768            /**
769             * Add webcam listener.
770             * 
771             * @param l the listener to be added
772             * @throws IllegalArgumentException when argument is null
773             */
774            public boolean addWebcamListener(WebcamListener l) {
775                    if (l == null) {
776                            throw new IllegalArgumentException("Webcam listener cannot be null!");
777                    }
778                    assert listeners != null;
779                    return listeners.add(l);
780            }
781    
782            /**
783             * @return All webcam listeners
784             */
785            public WebcamListener[] getWebcamListeners() {
786                    assert listeners != null;
787                    return listeners.toArray(new WebcamListener[listeners.size()]);
788            }
789    
790            /**
791             * @return Number of webcam listeners
792             */
793            public int getWebcamListenersCount() {
794                    assert listeners != null;
795                    return listeners.size();
796            }
797    
798            /**
799             * Removes webcam listener.
800             * 
801             * @param l the listener to be removed
802             * @return True if listener has been removed, false otherwise
803             */
804            public boolean removeWebcamListener(WebcamListener l) {
805                    assert listeners != null;
806                    return listeners.remove(l);
807            }
808    
809            /**
810             * Return webcam driver. Perform search if necessary.<br>
811             * <br>
812             * <b>This method is not thread-safe!</b>
813             * 
814             * @return Webcam driver
815             */
816            public static synchronized WebcamDriver getDriver() {
817    
818                    if (driver != null) {
819                            return driver;
820                    }
821    
822                    if (driver == null) {
823                            driver = WebcamDriverUtils.findDriver(DRIVERS_LIST, DRIVERS_CLASS_LIST);
824                    }
825                    if (driver == null) {
826                            driver = new WebcamDefaultDriver();
827                    }
828    
829                    LOG.info("{} capture driver will be used", driver.getClass().getSimpleName());
830    
831                    return driver;
832            }
833    
834            /**
835             * Set new video driver to be used by webcam.<br>
836             * <br>
837             * <b>This method is not thread-safe!</b>
838             * 
839             * @param driver new webcam driver to be used (e.g. LtiCivil, JFM, FMJ, QTJ)
840             * @throws IllegalArgumentException when argument is null
841             */
842            public static synchronized void setDriver(WebcamDriver driver) {
843    
844                    if (driver == null) {
845                            throw new IllegalArgumentException("Webcam driver cannot be null!");
846                    }
847    
848                    resetDriver();
849    
850                    Webcam.driver = driver;
851            }
852    
853            /**
854             * Set new video driver class to be used by webcam. Class given in the
855             * argument shall extend {@link WebcamDriver} interface and should have
856             * public default constructor, so instance can be created by reflection.<br>
857             * <br>
858             * <b>This method is not thread-safe!</b>
859             * 
860             * @param driverClass new video driver class to use
861             * @throws IllegalArgumentException when argument is null
862             */
863            public static synchronized void setDriver(Class<? extends WebcamDriver> driverClass) {
864    
865                    if (driverClass == null) {
866                            throw new IllegalArgumentException("Webcam driver class cannot be null!");
867                    }
868    
869                    resetDriver();
870    
871                    try {
872                            driver = driverClass.newInstance();
873                    } catch (InstantiationException e) {
874                            throw new WebcamException(e);
875                    } catch (IllegalAccessException e) {
876                            throw new WebcamException(e);
877                    }
878            }
879    
880            /**
881             * Reset webcam driver.<br>
882             * <br>
883             * <b>This method is not thread-safe!</b>
884             */
885            public static synchronized void resetDriver() {
886    
887                    DRIVERS_LIST.clear();
888    
889                    if (discovery != null) {
890                            discovery.shutdown();
891                            discovery = null;
892                    }
893    
894                    driver = null;
895            }
896    
897            /**
898             * Register new webcam video driver.
899             * 
900             * @param clazz webcam video driver class
901             * @throws IllegalArgumentException when argument is null
902             */
903            public static void registerDriver(Class<? extends WebcamDriver> clazz) {
904                    if (clazz == null) {
905                            throw new IllegalArgumentException("Webcam driver class to register cannot be null!");
906                    }
907                    DRIVERS_CLASS_LIST.add(clazz);
908                    registerDriver(clazz.getCanonicalName());
909            }
910    
911            /**
912             * Register new webcam video driver.
913             * 
914             * @param clazzName webcam video driver class name
915             * @throws IllegalArgumentException when argument is null
916             */
917            public static void registerDriver(String clazzName) {
918                    if (clazzName == null) {
919                            throw new IllegalArgumentException("Webcam driver class name to register cannot be null!");
920                    }
921                    DRIVERS_LIST.add(clazzName);
922            }
923    
924            /**
925             * <b>CAUTION!!!</b><br>
926             * <br>
927             * This is experimental feature to be used mostly in in development phase.
928             * After you set handle term signal to true, and fetch capture devices,
929             * Webcam Capture API will listen for TERM signal and try to close all
930             * devices after it has been received. <b>This feature can be unstable on
931             * some systems!</b>
932             * 
933             * @param on signal handling will be enabled if true, disabled otherwise
934             */
935            public static void setHandleTermSignal(boolean on) {
936                    if (on) {
937                            LOG.warn("Automated deallocation on TERM signal is now enabled! Make sure to not use it in production!");
938                    }
939                    deallocOnTermSignal = on;
940            }
941    
942            /**
943             * Is TERM signal handler enabled.
944             * 
945             * @return True if enabled, false otherwise
946             */
947            public static boolean isHandleTermSignal() {
948                    return deallocOnTermSignal;
949            }
950    
951            /**
952             * Switch all webcams to auto open mode. In this mode, each webcam will be
953             * automatically open whenever user will try to get image from instance
954             * which has not yet been open. Please be aware of some side effects! In
955             * case of multi-threaded applications, there is no guarantee that one
956             * thread will not try to open webcam even if it was manually closed in
957             * different thread.
958             * 
959             * @param on true to enable, false to disable
960             */
961            public static void setAutoOpenMode(boolean on) {
962                    autoOpen = on;
963            }
964    
965            /**
966             * Is auto open mode enabled. Auto open mode will will automatically open
967             * webcam whenever user will try to get image from instance which has not
968             * yet been open. Please be aware of some side effects! In case of
969             * multi-threaded applications, there is no guarantee that one thread will
970             * not try to open webcam even if it was manually closed in different
971             * thread.
972             * 
973             * @return True if mode is enabled, false otherwise
974             */
975            public static boolean isAutoOpenMode() {
976                    return autoOpen;
977            }
978    
979            /**
980             * Add new webcam discovery listener.
981             * 
982             * @param l the listener to be added
983             * @return True, if listeners list size has been changed, false otherwise
984             * @throws IllegalArgumentException when argument is null
985             */
986            public static boolean addDiscoveryListener(WebcamDiscoveryListener l) {
987                    if (l == null) {
988                            throw new IllegalArgumentException("Webcam discovery listener cannot be null!");
989                    }
990                    return DISCOVERY_LISTENERS.add(l);
991            }
992    
993            public static WebcamDiscoveryListener[] getDiscoveryListeners() {
994                    return DISCOVERY_LISTENERS.toArray(new WebcamDiscoveryListener[DISCOVERY_LISTENERS.size()]);
995            }
996    
997            /**
998             * Remove discovery listener
999             * 
1000             * @param l the listener to be removed
1001             * @return True if listeners list contained the specified element
1002             */
1003            public static boolean removeDiscoveryListener(WebcamDiscoveryListener l) {
1004                    return DISCOVERY_LISTENERS.remove(l);
1005            }
1006    
1007            /**
1008             * Return discovery service.
1009             * 
1010             * @return Discovery service
1011             */
1012            public static synchronized WebcamDiscoveryService getDiscoveryService() {
1013                    if (discovery == null) {
1014                            discovery = new WebcamDiscoveryService(getDriver());
1015                    }
1016                    return discovery;
1017            }
1018    
1019            /**
1020             * Return image transformer.
1021             * 
1022             * @return Transformer instance
1023             */
1024            public WebcamImageTransformer getImageTransformer() {
1025                    return transformer;
1026            }
1027    
1028            /**
1029             * Set image transformer.
1030             * 
1031             * @param transformer the transformer to be set
1032             */
1033            public void setImageTransformer(WebcamImageTransformer transformer) {
1034                    this.transformer = transformer;
1035            }
1036    }