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                            Runtime.getRuntime().removeShutdownHook(hook);
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                    // hook can be null because there is a possibility that webcam has never
321                    // been open and therefore hook was not created
322                    if (hook != null) {
323                            try {
324                                    Runtime.getRuntime().removeShutdownHook(hook);
325                            } catch (IllegalStateException e) {
326                                    LOG.trace("Shutdown in progress, cannot remove hook");
327                            }
328                    }
329    
330                    LOG.debug("Webcam disposed {}", getName());
331            }
332    
333            /**
334             * TRansform image using image transformer. If image transformer has not
335             * been set, this method return instance passed in the argument, without any
336             * modifications.
337             * 
338             * @param image the image to be transformed
339             * @return Transformed image (if transformer is set)
340             */
341            protected BufferedImage transform(BufferedImage image) {
342                    if (image != null) {
343                            WebcamImageTransformer tr = getImageTransformer();
344                            if (tr != null) {
345                                    return tr.transform(image);
346                            }
347                    }
348                    return image;
349            }
350    
351            /**
352             * Is webcam open?
353             * 
354             * @return true if open, false otherwise
355             */
356            public boolean isOpen() {
357                    return open.get();
358            }
359    
360            /**
361             * Get current webcam resolution in pixels.
362             * 
363             * @return Webcam resolution (picture size) in pixels.
364             */
365            public Dimension getViewSize() {
366                    return device.getResolution();
367            }
368    
369            /**
370             * Return list of supported view sizes. It can differ between vary webcam
371             * data sources.
372             * 
373             * @return Array of supported dimensions
374             */
375            public Dimension[] getViewSizes() {
376                    return device.getResolutions();
377            }
378    
379            /**
380             * Set custom resolution. If you are using this method you have to make sure
381             * that your webcam device can support this specific resolution.
382             * 
383             * @param sizes the array of custom resolutions to be supported by webcam
384             */
385            public void setCustomViewSizes(Dimension[] sizes) {
386                    assert customSizes != null;
387                    if (sizes == null) {
388                            customSizes.clear();
389                            return;
390                    }
391                    customSizes = Arrays.asList(sizes);
392            }
393    
394            public Dimension[] getCustomViewSizes() {
395                    assert customSizes != null;
396                    return customSizes.toArray(new Dimension[customSizes.size()]);
397            }
398    
399            /**
400             * Set new view size. New size has to exactly the same as one of the default
401             * sized or exactly the same as one of the custom ones.
402             * 
403             * @param size the new view size to be set
404             * @see Webcam#setCustomViewSizes(Dimension[])
405             * @see Webcam#getViewSizes()
406             */
407            public void setViewSize(Dimension size) {
408    
409                    if (size == null) {
410                            throw new IllegalArgumentException("Resolution cannot be null!");
411                    }
412    
413                    if (open.get()) {
414                            throw new IllegalStateException("Cannot change resolution when webcam is open, please close it first");
415                    }
416    
417                    // check if new resolution is the same as current one
418    
419                    Dimension current = getViewSize();
420                    if (current != null && current.width == size.width && current.height == size.height) {
421                            return;
422                    }
423    
424                    // check if new resolution is valid
425    
426                    Dimension[] predefined = getViewSizes();
427                    Dimension[] custom = getCustomViewSizes();
428    
429                    assert predefined != null;
430                    assert custom != null;
431    
432                    boolean ok = false;
433                    for (Dimension d : predefined) {
434                            if (d.width == size.width && d.height == size.height) {
435                                    ok = true;
436                                    break;
437                            }
438                    }
439                    if (!ok) {
440                            for (Dimension d : custom) {
441                                    if (d.width == size.width && d.height == size.height) {
442                                            ok = true;
443                                            break;
444                                    }
445                            }
446                    }
447    
448                    if (!ok) {
449                            StringBuilder sb = new StringBuilder("Incorrect dimension [");
450                            sb.append(size.width).append("x").append(size.height).append("] ");
451                            sb.append("possible ones are ");
452                            for (Dimension d : predefined) {
453                                    sb.append("[").append(d.width).append("x").append(d.height).append("] ");
454                            }
455                            for (Dimension d : custom) {
456                                    sb.append("[").append(d.width).append("x").append(d.height).append("] ");
457                            }
458                            throw new IllegalArgumentException(sb.toString());
459                    }
460    
461                    LOG.debug("Setting new resolution {}x{}", size.width, size.height);
462    
463                    device.setResolution(size);
464            }
465    
466            /**
467             * Capture image from webcam and return it. Will return image object or null
468             * if webcam is closed or has been already disposed by JVM.<br>
469             * <br>
470             * <b>IMPORTANT NOTE!!!</b><br>
471             * <br>
472             * There are two possible behaviors of what webcam should do when you try to
473             * get image and webcam is actually closed. Normally it will return null,
474             * but there is a special flag which can be statically set to switch all
475             * webcams to auto open mode. In this mode, webcam will be automatically
476             * open, when you try to get image from closed webcam. Please be aware of
477             * some side effects! In case of multi-threaded applications, there is no
478             * guarantee that one thread will not try to open webcam even if it was
479             * manually closed in different thread.
480             * 
481             * @return Captured image or null if webcam is closed or disposed by JVM
482             */
483            public BufferedImage getImage() {
484    
485                    if (!isReady()) {
486                            return null;
487                    }
488    
489                    long t1 = 0;
490                    long t2 = 0;
491    
492                    assert updater != null;
493    
494                    if (asynchronous) {
495                            return updater.getImage();
496                    } else {
497    
498                            // get image
499    
500                            t1 = System.currentTimeMillis();
501                            BufferedImage image = transform(new WebcamReadImageTask(driver, device).getImage());
502                            t2 = System.currentTimeMillis();
503    
504                            if (image == null) {
505                                    return null;
506                            }
507    
508                            // get FPS
509    
510                            if (device instanceof WebcamDevice.FPSSource) {
511                                    fps = ((WebcamDevice.FPSSource) device).getFPS();
512                            } else {
513                                    // +1 to avoid division by zero
514                                    fps = (4 * fps + 1000 / (t2 - t1 + 1)) / 5;
515                            }
516    
517                            // notify webcam listeners about new image available
518    
519                            updater.notifyWebcamImageObtained(this, image);
520    
521                            return image;
522                    }
523            }
524    
525            public boolean isImageNew() {
526                    assert updater != null;
527                    if (asynchronous) {
528                            return updater.isImageNew();
529                    }
530                    return true;
531            }
532    
533            protected double getFPS() {
534                    assert updater != null;
535                    if (asynchronous) {
536                            return updater.getFPS();
537                    } else {
538                            return fps;
539                    }
540            }
541    
542            /**
543             * Get RAW image ByteBuffer. It will always return buffer with 3 x 1 bytes
544             * per each pixel, where RGB components are on (0, 1, 2) and color space is
545             * sRGB.<br>
546             * <br>
547             * 
548             * <b>IMPORTANT!</b><br>
549             * Some drivers can return direct ByteBuffer, so there is no guarantee that
550             * underlying bytes will not be released in next read image operation.
551             * Therefore, to avoid potential bugs you should convert this ByteBuffer to
552             * bytes array before you fetch next image.
553             * 
554             * @return Byte buffer
555             */
556            public ByteBuffer getImageBytes() {
557    
558                    if (!isReady()) {
559                            return null;
560                    }
561    
562                    assert driver != null;
563                    assert device != null;
564    
565                    // some devices can support direct image buffers, and for those call
566                    // processor task, and for those which does not support direct image
567                    // buffers, just convert image to RGB byte array
568    
569                    if (device instanceof BufferAccess) {
570                            return new WebcamReadBufferTask(driver, device).getBuffer();
571                    } else {
572                            BufferedImage image = getImage();
573                            if (image != null) {
574                                    return ByteBuffer.wrap(ImageUtils.toRawByteArray(image));
575                            } else {
576                                    return null;
577                            }
578                    }
579            }
580    
581            /**
582             * Is webcam ready to be read.
583             * 
584             * @return True if ready, false otherwise
585             */
586            private boolean isReady() {
587    
588                    assert disposed != null;
589                    assert open != null;
590    
591                    if (disposed.get()) {
592                            LOG.warn("Cannot get image, webcam has been already disposed");
593                            return false;
594                    }
595    
596                    if (!open.get()) {
597                            if (autoOpen) {
598                                    open();
599                            } else {
600                                    return false;
601                            }
602                    }
603    
604                    return true;
605            }
606    
607            /**
608             * Get list of webcams to use. This method will wait predefined time
609             * interval for webcam devices to be discovered. By default this time is set
610             * to 1 minute.
611             * 
612             * @return List of webcams existing in the system
613             * @throws WebcamException when something is wrong
614             * @see Webcam#getWebcams(long, TimeUnit)
615             */
616            public static List<Webcam> getWebcams() throws WebcamException {
617    
618                    // timeout exception below will never be caught since user would have to
619                    // wait around three hundreds billion years for it to occur
620    
621                    try {
622                            return getWebcams(Long.MAX_VALUE);
623                    } catch (TimeoutException e) {
624                            throw new RuntimeException(e);
625                    }
626            }
627    
628            /**
629             * Get list of webcams to use. This method will wait given time interval for
630             * webcam devices to be discovered. Time argument is given in milliseconds.
631             * 
632             * @param timeout the time to wait for webcam devices to be discovered
633             * @return List of webcams existing in the ssytem
634             * @throws WebcamException when something is wrong
635             * @throws IllegalArgumentException when timeout is negative
636             * @see Webcam#getWebcams(long, TimeUnit)
637             */
638            public static List<Webcam> getWebcams(long timeout) throws TimeoutException, WebcamException {
639                    if (timeout < 0) {
640                            throw new IllegalArgumentException(String.format("Timeout cannot be negative (%d)", timeout));
641                    }
642                    return getWebcams(timeout, TimeUnit.MILLISECONDS);
643            }
644    
645            /**
646             * Get list of webcams to use. This method will wait given time interval for
647             * webcam devices to be discovered.
648             * 
649             * @param timeout the devices discovery timeout
650             * @param tunit the time unit
651             * @return List of webcams
652             * @throws TimeoutException when timeout has been exceeded
653             * @throws WebcamException when something is wrong
654             * @throws IllegalArgumentException when timeout is negative or tunit null
655             */
656            public static synchronized List<Webcam> getWebcams(long timeout, TimeUnit tunit) throws TimeoutException, WebcamException {
657    
658                    if (timeout < 0) {
659                            throw new IllegalArgumentException(String.format("Timeout cannot be negative (%d)", timeout));
660                    }
661                    if (tunit == null) {
662                            throw new IllegalArgumentException("Time unit cannot be null!");
663                    }
664    
665                    WebcamDiscoveryService discovery = getDiscoveryService();
666    
667                    assert discovery != null;
668    
669                    List<Webcam> webcams = discovery.getWebcams(timeout, tunit);
670                    if (!discovery.isRunning()) {
671                            discovery.start();
672                    }
673    
674                    return webcams;
675            }
676    
677            /**
678             * Will discover and return first webcam available in the system.
679             * 
680             * @return Default webcam (first from the list)
681             * @throws WebcamException if something is really wrong
682             * @see Webcam#getWebcams()
683             */
684            public static Webcam getDefault() throws WebcamException {
685    
686                    try {
687                            return getDefault(Long.MAX_VALUE);
688                    } catch (TimeoutException e) {
689                            // this should never happen since user would have to wait 300000000
690                            // years for it to occur
691                            throw new RuntimeException(e);
692                    }
693            }
694    
695            /**
696             * Will discover and return first webcam available in the system.
697             * 
698             * @param timeout the webcam discovery timeout (1 minute by default)
699             * @return Default webcam (first from the list)
700             * @throws TimeoutException when discovery timeout has been exceeded
701             * @throws WebcamException if something is really wrong
702             * @throws IllegalArgumentException when timeout is negative
703             * @see Webcam#getWebcams(long)
704             */
705            public static Webcam getDefault(long timeout) throws TimeoutException, WebcamException {
706                    if (timeout < 0) {
707                            throw new IllegalArgumentException(String.format("Timeout cannot be negative (%d)", timeout));
708                    }
709                    return getDefault(timeout, TimeUnit.MILLISECONDS);
710            }
711    
712            /**
713             * Will discover and return first webcam available in the system.
714             * 
715             * @param timeout the webcam discovery timeout (1 minute by default)
716             * @param tunit the time unit
717             * @return Default webcam (first from the list)
718             * @throws TimeoutException when discovery timeout has been exceeded
719             * @throws WebcamException if something is really wrong
720             * @throws IllegalArgumentException when timeout is negative or tunit null
721             * @see Webcam#getWebcams(long, TimeUnit)
722             */
723            public static Webcam getDefault(long timeout, TimeUnit tunit) throws TimeoutException, WebcamException {
724    
725                    if (timeout < 0) {
726                            throw new IllegalArgumentException(String.format("Timeout cannot be negative (%d)", timeout));
727                    }
728                    if (tunit == null) {
729                            throw new IllegalArgumentException("Time unit cannot be null!");
730                    }
731    
732                    List<Webcam> webcams = getWebcams(timeout, tunit);
733    
734                    assert webcams != null;
735    
736                    if (!webcams.isEmpty()) {
737                            return webcams.get(0);
738                    }
739    
740                    LOG.warn("No webcam has been detected!");
741    
742                    return null;
743            }
744    
745            /**
746             * Get webcam name (device name). The name of device depends on the value
747             * returned by the underlying data source, so in some cases it can be
748             * human-readable value and sometimes it can be some strange number.
749             * 
750             * @return Name
751             */
752            public String getName() {
753                    assert device != null;
754                    return device.getName();
755            }
756    
757            @Override
758            public String toString() {
759                    return String.format("Webcam %s", getName());
760            }
761    
762            /**
763             * Add webcam listener.
764             * 
765             * @param l the listener to be added
766             * @throws IllegalArgumentException when argument is null
767             */
768            public boolean addWebcamListener(WebcamListener l) {
769                    if (l == null) {
770                            throw new IllegalArgumentException("Webcam listener cannot be null!");
771                    }
772                    assert listeners != null;
773                    return listeners.add(l);
774            }
775    
776            /**
777             * @return All webcam listeners
778             */
779            public WebcamListener[] getWebcamListeners() {
780                    assert listeners != null;
781                    return listeners.toArray(new WebcamListener[listeners.size()]);
782            }
783    
784            /**
785             * @return Number of webcam listeners
786             */
787            public int getWebcamListenersCount() {
788                    assert listeners != null;
789                    return listeners.size();
790            }
791    
792            /**
793             * Removes webcam listener.
794             * 
795             * @param l the listener to be removed
796             * @return True if listener has been removed, false otherwise
797             */
798            public boolean removeWebcamListener(WebcamListener l) {
799                    assert listeners != null;
800                    return listeners.remove(l);
801            }
802    
803            /**
804             * Return webcam driver. Perform search if necessary.<br>
805             * <br>
806             * <b>This method is not thread-safe!</b>
807             * 
808             * @return Webcam driver
809             */
810            public static synchronized WebcamDriver getDriver() {
811    
812                    if (driver == null) {
813                            driver = WebcamDriverUtils.findDriver(DRIVERS_LIST, DRIVERS_CLASS_LIST);
814                    }
815    
816                    if (driver == null) {
817                            driver = new WebcamDefaultDriver();
818                    }
819    
820                    LOG.info("{} capture driver will be used", driver.getClass().getSimpleName());
821    
822                    return driver;
823            }
824    
825            /**
826             * Set new video driver to be used by webcam.<br>
827             * <br>
828             * <b>This method is not thread-safe!</b>
829             * 
830             * @param driver new webcam driver to be used (e.g. LtiCivil, JFM, FMJ, QTJ)
831             * @throws IllegalArgumentException when argument is null
832             */
833            public static synchronized void setDriver(WebcamDriver driver) {
834    
835                    if (driver == null) {
836                            throw new IllegalArgumentException("Webcam driver cannot be null!");
837                    }
838    
839                    resetDriver();
840    
841                    Webcam.driver = driver;
842            }
843    
844            /**
845             * Set new video driver class to be used by webcam. Class given in the
846             * argument shall extend {@link WebcamDriver} interface and should have
847             * public default constructor, so instance can be created by reflection.<br>
848             * <br>
849             * <b>This method is not thread-safe!</b>
850             * 
851             * @param driverClass new video driver class to use
852             * @throws IllegalArgumentException when argument is null
853             */
854            public static synchronized void setDriver(Class<? extends WebcamDriver> driverClass) {
855    
856                    if (driverClass == null) {
857                            throw new IllegalArgumentException("Webcam driver class cannot be null!");
858                    }
859    
860                    resetDriver();
861    
862                    try {
863                            driver = driverClass.newInstance();
864                    } catch (InstantiationException e) {
865                            throw new WebcamException(e);
866                    } catch (IllegalAccessException e) {
867                            throw new WebcamException(e);
868                    }
869            }
870    
871            /**
872             * Reset webcam driver.<br>
873             * <br>
874             * <b>This method is not thread-safe!</b>
875             */
876            public static synchronized void resetDriver() {
877    
878                    DRIVERS_LIST.clear();
879    
880                    if (discovery != null) {
881                            discovery.shutdown();
882                            discovery = null;
883                    }
884    
885                    driver = null;
886            }
887    
888            /**
889             * Register new webcam video driver.
890             * 
891             * @param clazz webcam video driver class
892             * @throws IllegalArgumentException when argument is null
893             */
894            public static void registerDriver(Class<? extends WebcamDriver> clazz) {
895                    if (clazz == null) {
896                            throw new IllegalArgumentException("Webcam driver class to register cannot be null!");
897                    }
898                    DRIVERS_CLASS_LIST.add(clazz);
899                    registerDriver(clazz.getCanonicalName());
900            }
901    
902            /**
903             * Register new webcam video driver.
904             * 
905             * @param clazzName webcam video driver class name
906             * @throws IllegalArgumentException when argument is null
907             */
908            public static void registerDriver(String clazzName) {
909                    if (clazzName == null) {
910                            throw new IllegalArgumentException("Webcam driver class name to register cannot be null!");
911                    }
912                    DRIVERS_LIST.add(clazzName);
913            }
914    
915            /**
916             * <b>CAUTION!!!</b><br>
917             * <br>
918             * This is experimental feature to be used mostly in in development phase.
919             * After you set handle term signal to true, and fetch capture devices,
920             * Webcam Capture API will listen for TERM signal and try to close all
921             * devices after it has been received. <b>This feature can be unstable on
922             * some systems!</b>
923             * 
924             * @param on signal handling will be enabled if true, disabled otherwise
925             */
926            public static void setHandleTermSignal(boolean on) {
927                    if (on) {
928                            LOG.warn("Automated deallocation on TERM signal is now enabled! Make sure to not use it in production!");
929                    }
930                    deallocOnTermSignal = on;
931            }
932    
933            /**
934             * Is TERM signal handler enabled.
935             * 
936             * @return True if enabled, false otherwise
937             */
938            public static boolean isHandleTermSignal() {
939                    return deallocOnTermSignal;
940            }
941    
942            /**
943             * Switch all webcams to auto open mode. In this mode, each webcam will be
944             * automatically open whenever user will try to get image from instance
945             * which has not yet been open. Please be aware of some side effects! In
946             * case of multi-threaded applications, there is no guarantee that one
947             * thread will not try to open webcam even if it was manually closed in
948             * different thread.
949             * 
950             * @param on true to enable, false to disable
951             */
952            public static void setAutoOpenMode(boolean on) {
953                    autoOpen = on;
954            }
955    
956            /**
957             * Is auto open mode enabled. Auto open mode will will automatically open
958             * webcam whenever user will try to get image from instance which has not
959             * yet been open. Please be aware of some side effects! In case of
960             * multi-threaded applications, there is no guarantee that one thread will
961             * not try to open webcam even if it was manually closed in different
962             * thread.
963             * 
964             * @return True if mode is enabled, false otherwise
965             */
966            public static boolean isAutoOpenMode() {
967                    return autoOpen;
968            }
969    
970            /**
971             * Add new webcam discovery listener.
972             * 
973             * @param l the listener to be added
974             * @return True, if listeners list size has been changed, false otherwise
975             * @throws IllegalArgumentException when argument is null
976             */
977            public static boolean addDiscoveryListener(WebcamDiscoveryListener l) {
978                    if (l == null) {
979                            throw new IllegalArgumentException("Webcam discovery listener cannot be null!");
980                    }
981                    return DISCOVERY_LISTENERS.add(l);
982            }
983    
984            public static WebcamDiscoveryListener[] getDiscoveryListeners() {
985                    return DISCOVERY_LISTENERS.toArray(new WebcamDiscoveryListener[DISCOVERY_LISTENERS.size()]);
986            }
987    
988            /**
989             * Remove discovery listener
990             * 
991             * @param l the listener to be removed
992             * @return True if listeners list contained the specified element
993             */
994            public static boolean removeDiscoveryListener(WebcamDiscoveryListener l) {
995                    return DISCOVERY_LISTENERS.remove(l);
996            }
997    
998            /**
999             * Return discovery service.
1000             * 
1001             * @return Discovery service
1002             */
1003            public static synchronized WebcamDiscoveryService getDiscoveryService() {
1004                    if (discovery == null) {
1005                            discovery = new WebcamDiscoveryService(getDriver());
1006                    }
1007                    return discovery;
1008            }
1009    
1010            /**
1011             * Return image transformer.
1012             * 
1013             * @return Transformer instance
1014             */
1015            public WebcamImageTransformer getImageTransformer() {
1016                    return transformer;
1017            }
1018    
1019            /**
1020             * Set image transformer.
1021             * 
1022             * @param transformer the transformer to be set
1023             */
1024            public void setImageTransformer(WebcamImageTransformer transformer) {
1025                    this.transformer = transformer;
1026            }
1027    }