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                            // calculate FPS
509    
510                            // +1 to avoid division by zero
511                            fps = (4 * fps + 1000 / (t2 - t1 + 1)) / 5;
512    
513                            // notify webcam listeners about new image available
514    
515                            updater.notifyWebcamImageObtained(this, image);
516    
517                            return image;
518                    }
519            }
520    
521            public boolean isImageNew() {
522                    assert updater != null;
523                    if (asynchronous) {
524                            return updater.isImageNew();
525                    }
526                    return true;
527            }
528    
529            protected double getFPS() {
530                    assert updater != null;
531                    if (asynchronous) {
532                            return updater.getFPS();
533                    } else {
534                            return fps;
535                    }
536            }
537    
538            /**
539             * Get RAW image ByteBuffer. It will always return buffer with 3 x 1 bytes
540             * per each pixel, where RGB components are on (0, 1, 2) and color space is
541             * sRGB.<br>
542             * <br>
543             * 
544             * <b>IMPORTANT!</b><br>
545             * Some drivers can return direct ByteBuffer, so there is no guarantee that
546             * underlying bytes will not be released in next read image operation.
547             * Therefore, to avoid potential bugs you should convert this ByteBuffer to
548             * bytes array before you fetch next image.
549             * 
550             * @return Byte buffer
551             */
552            public ByteBuffer getImageBytes() {
553    
554                    if (!isReady()) {
555                            return null;
556                    }
557    
558                    assert driver != null;
559                    assert device != null;
560    
561                    // some devices can support direct image buffers, and for those call
562                    // processor task, and for those which does not support direct image
563                    // buffers, just convert image to RGB byte array
564    
565                    if (device instanceof BufferAccess) {
566                            return new WebcamReadBufferTask(driver, device).getBuffer();
567                    } else {
568                            BufferedImage image = getImage();
569                            if (image != null) {
570                                    return ByteBuffer.wrap(ImageUtils.toRawByteArray(image));
571                            } else {
572                                    return null;
573                            }
574                    }
575            }
576    
577            /**
578             * Is webcam ready to be read.
579             * 
580             * @return True if ready, false otherwise
581             */
582            private boolean isReady() {
583    
584                    assert disposed != null;
585                    assert open != null;
586    
587                    if (disposed.get()) {
588                            LOG.warn("Cannot get image, webcam has been already disposed");
589                            return false;
590                    }
591    
592                    if (!open.get()) {
593                            if (autoOpen) {
594                                    open();
595                            } else {
596                                    return false;
597                            }
598                    }
599    
600                    return true;
601            }
602    
603            /**
604             * Get list of webcams to use. This method will wait predefined time
605             * interval for webcam devices to be discovered. By default this time is set
606             * to 1 minute.
607             * 
608             * @return List of webcams existing in the system
609             * @throws WebcamException when something is wrong
610             * @see Webcam#getWebcams(long, TimeUnit)
611             */
612            public static List<Webcam> getWebcams() throws WebcamException {
613    
614                    // timeout exception below will never be caught since user would have to
615                    // wait around three hundreds billion years for it to occur
616    
617                    try {
618                            return getWebcams(Long.MAX_VALUE);
619                    } catch (TimeoutException e) {
620                            throw new RuntimeException(e);
621                    }
622            }
623    
624            /**
625             * Get list of webcams to use. This method will wait given time interval for
626             * webcam devices to be discovered. Time argument is given in milliseconds.
627             * 
628             * @param timeout the time to wait for webcam devices to be discovered
629             * @return List of webcams existing in the ssytem
630             * @throws WebcamException when something is wrong
631             * @throws IllegalArgumentException when timeout is negative
632             * @see Webcam#getWebcams(long, TimeUnit)
633             */
634            public static List<Webcam> getWebcams(long timeout) throws TimeoutException, WebcamException {
635                    if (timeout < 0) {
636                            throw new IllegalArgumentException(String.format("Timeout cannot be negative (%d)", timeout));
637                    }
638                    return getWebcams(timeout, TimeUnit.MILLISECONDS);
639            }
640    
641            /**
642             * Get list of webcams to use. This method will wait given time interval for
643             * webcam devices to be discovered.
644             * 
645             * @param timeout the devices discovery timeout
646             * @param tunit the time unit
647             * @return List of webcams
648             * @throws TimeoutException when timeout has been exceeded
649             * @throws WebcamException when something is wrong
650             * @throws IllegalArgumentException when timeout is negative or tunit null
651             */
652            public static synchronized List<Webcam> getWebcams(long timeout, TimeUnit tunit) throws TimeoutException, WebcamException {
653    
654                    if (timeout < 0) {
655                            throw new IllegalArgumentException(String.format("Timeout cannot be negative (%d)", timeout));
656                    }
657                    if (tunit == null) {
658                            throw new IllegalArgumentException("Time unit cannot be null!");
659                    }
660    
661                    WebcamDiscoveryService discovery = getDiscoveryService();
662    
663                    assert discovery != null;
664    
665                    List<Webcam> webcams = discovery.getWebcams(timeout, tunit);
666                    if (!discovery.isRunning()) {
667                            discovery.start();
668                    }
669    
670                    return webcams;
671            }
672    
673            /**
674             * Will discover and return first webcam available in the system.
675             * 
676             * @return Default webcam (first from the list)
677             * @throws WebcamException if something is really wrong
678             * @see Webcam#getWebcams()
679             */
680            public static Webcam getDefault() throws WebcamException {
681    
682                    try {
683                            return getDefault(Long.MAX_VALUE);
684                    } catch (TimeoutException e) {
685                            // this should never happen since user would have to wait 300000000
686                            // years for it to occur
687                            throw new RuntimeException(e);
688                    }
689            }
690    
691            /**
692             * Will discover and return first webcam available in the system.
693             * 
694             * @param timeout the webcam discovery timeout (1 minute by default)
695             * @return Default webcam (first from the list)
696             * @throws TimeoutException when discovery timeout has been exceeded
697             * @throws WebcamException if something is really wrong
698             * @throws IllegalArgumentException when timeout is negative
699             * @see Webcam#getWebcams(long)
700             */
701            public static Webcam getDefault(long timeout) throws TimeoutException, WebcamException {
702                    if (timeout < 0) {
703                            throw new IllegalArgumentException(String.format("Timeout cannot be negative (%d)", timeout));
704                    }
705                    return getDefault(timeout, TimeUnit.MILLISECONDS);
706            }
707    
708            /**
709             * Will discover and return first webcam available in the system.
710             * 
711             * @param timeout the webcam discovery timeout (1 minute by default)
712             * @param tunit the time unit
713             * @return Default webcam (first from the list)
714             * @throws TimeoutException when discovery timeout has been exceeded
715             * @throws WebcamException if something is really wrong
716             * @throws IllegalArgumentException when timeout is negative or tunit null
717             * @see Webcam#getWebcams(long, TimeUnit)
718             */
719            public static Webcam getDefault(long timeout, TimeUnit tunit) throws TimeoutException, WebcamException {
720    
721                    if (timeout < 0) {
722                            throw new IllegalArgumentException(String.format("Timeout cannot be negative (%d)", timeout));
723                    }
724                    if (tunit == null) {
725                            throw new IllegalArgumentException("Time unit cannot be null!");
726                    }
727    
728                    List<Webcam> webcams = getWebcams(timeout, tunit);
729    
730                    assert webcams != null;
731    
732                    if (!webcams.isEmpty()) {
733                            return webcams.get(0);
734                    }
735    
736                    LOG.warn("No webcam has been detected!");
737    
738                    return null;
739            }
740    
741            /**
742             * Get webcam name (device name). The name of device depends on the value
743             * returned by the underlying data source, so in some cases it can be
744             * human-readable value and sometimes it can be some strange number.
745             * 
746             * @return Name
747             */
748            public String getName() {
749                    assert device != null;
750                    return device.getName();
751            }
752    
753            @Override
754            public String toString() {
755                    return String.format("Webcam %s", getName());
756            }
757    
758            /**
759             * Add webcam listener.
760             * 
761             * @param l the listener to be added
762             * @throws IllegalArgumentException when argument is null
763             */
764            public boolean addWebcamListener(WebcamListener l) {
765                    if (l == null) {
766                            throw new IllegalArgumentException("Webcam listener cannot be null!");
767                    }
768                    assert listeners != null;
769                    return listeners.add(l);
770            }
771    
772            /**
773             * @return All webcam listeners
774             */
775            public WebcamListener[] getWebcamListeners() {
776                    assert listeners != null;
777                    return listeners.toArray(new WebcamListener[listeners.size()]);
778            }
779    
780            /**
781             * @return Number of webcam listeners
782             */
783            public int getWebcamListenersCount() {
784                    assert listeners != null;
785                    return listeners.size();
786            }
787    
788            /**
789             * Removes webcam listener.
790             * 
791             * @param l the listener to be removed
792             * @return True if listener has been removed, false otherwise
793             */
794            public boolean removeWebcamListener(WebcamListener l) {
795                    assert listeners != null;
796                    return listeners.remove(l);
797            }
798    
799            /**
800             * Return webcam driver. Perform search if necessary.<br>
801             * <br>
802             * <b>This method is not thread-safe!</b>
803             * 
804             * @return Webcam driver
805             */
806            public static synchronized WebcamDriver getDriver() {
807    
808                    if (driver == null) {
809                            driver = WebcamDriverUtils.findDriver(DRIVERS_LIST, DRIVERS_CLASS_LIST);
810                    }
811    
812                    if (driver == null) {
813                            driver = new WebcamDefaultDriver();
814                    }
815    
816                    LOG.info("{} capture driver will be used", driver.getClass().getSimpleName());
817    
818                    return driver;
819            }
820    
821            /**
822             * Set new video driver to be used by webcam.<br>
823             * <br>
824             * <b>This method is not thread-safe!</b>
825             * 
826             * @param driver new webcam driver to be used (e.g. LtiCivil, JFM, FMJ, QTJ)
827             * @throws IllegalArgumentException when argument is null
828             */
829            public static synchronized void setDriver(WebcamDriver driver) {
830    
831                    if (driver == null) {
832                            throw new IllegalArgumentException("Webcam driver cannot be null!");
833                    }
834    
835                    resetDriver();
836    
837                    Webcam.driver = driver;
838            }
839    
840            /**
841             * Set new video driver class to be used by webcam. Class given in the
842             * argument shall extend {@link WebcamDriver} interface and should have
843             * public default constructor, so instance can be created by reflection.<br>
844             * <br>
845             * <b>This method is not thread-safe!</b>
846             * 
847             * @param driverClass new video driver class to use
848             * @throws IllegalArgumentException when argument is null
849             */
850            public static synchronized void setDriver(Class<? extends WebcamDriver> driverClass) {
851    
852                    if (driverClass == null) {
853                            throw new IllegalArgumentException("Webcam driver class cannot be null!");
854                    }
855    
856                    resetDriver();
857    
858                    try {
859                            driver = driverClass.newInstance();
860                    } catch (InstantiationException e) {
861                            throw new WebcamException(e);
862                    } catch (IllegalAccessException e) {
863                            throw new WebcamException(e);
864                    }
865            }
866    
867            /**
868             * Reset webcam driver.<br>
869             * <br>
870             * <b>This method is not thread-safe!</b>
871             */
872            public static synchronized void resetDriver() {
873    
874                    DRIVERS_LIST.clear();
875    
876                    if (discovery != null) {
877                            discovery.shutdown();
878                            discovery = null;
879                    }
880    
881                    driver = null;
882            }
883    
884            /**
885             * Register new webcam video driver.
886             * 
887             * @param clazz webcam video driver class
888             * @throws IllegalArgumentException when argument is null
889             */
890            public static void registerDriver(Class<? extends WebcamDriver> clazz) {
891                    if (clazz == null) {
892                            throw new IllegalArgumentException("Webcam driver class to register cannot be null!");
893                    }
894                    DRIVERS_CLASS_LIST.add(clazz);
895                    registerDriver(clazz.getCanonicalName());
896            }
897    
898            /**
899             * Register new webcam video driver.
900             * 
901             * @param clazzName webcam video driver class name
902             * @throws IllegalArgumentException when argument is null
903             */
904            public static void registerDriver(String clazzName) {
905                    if (clazzName == null) {
906                            throw new IllegalArgumentException("Webcam driver class name to register cannot be null!");
907                    }
908                    DRIVERS_LIST.add(clazzName);
909            }
910    
911            /**
912             * <b>CAUTION!!!</b><br>
913             * <br>
914             * This is experimental feature to be used mostly in in development phase.
915             * After you set handle term signal to true, and fetch capture devices,
916             * Webcam Capture API will listen for TERM signal and try to close all
917             * devices after it has been received. <b>This feature can be unstable on
918             * some systems!</b>
919             * 
920             * @param on signal handling will be enabled if true, disabled otherwise
921             */
922            public static void setHandleTermSignal(boolean on) {
923                    if (on) {
924                            LOG.warn("Automated deallocation on TERM signal is now enabled! Make sure to not use it in production!");
925                    }
926                    deallocOnTermSignal = on;
927            }
928    
929            /**
930             * Is TERM signal handler enabled.
931             * 
932             * @return True if enabled, false otherwise
933             */
934            public static boolean isHandleTermSignal() {
935                    return deallocOnTermSignal;
936            }
937    
938            /**
939             * Switch all webcams to auto open mode. In this mode, each webcam will be
940             * automatically open whenever user will try to get image from instance
941             * which has not yet been open. Please be aware of some side effects! In
942             * case of multi-threaded applications, there is no guarantee that one
943             * thread will not try to open webcam even if it was manually closed in
944             * different thread.
945             * 
946             * @param on true to enable, false to disable
947             */
948            public static void setAutoOpenMode(boolean on) {
949                    autoOpen = on;
950            }
951    
952            /**
953             * Is auto open mode enabled. Auto open mode will will automatically open
954             * webcam whenever user will try to get image from instance which has not
955             * yet been open. Please be aware of some side effects! In case of
956             * multi-threaded applications, there is no guarantee that one thread will
957             * not try to open webcam even if it was manually closed in different
958             * thread.
959             * 
960             * @return True if mode is enabled, false otherwise
961             */
962            public static boolean isAutoOpenMode() {
963                    return autoOpen;
964            }
965    
966            /**
967             * Add new webcam discovery listener.
968             * 
969             * @param l the listener to be added
970             * @return True, if listeners list size has been changed, false otherwise
971             * @throws IllegalArgumentException when argument is null
972             */
973            public static boolean addDiscoveryListener(WebcamDiscoveryListener l) {
974                    if (l == null) {
975                            throw new IllegalArgumentException("Webcam discovery listener cannot be null!");
976                    }
977                    return DISCOVERY_LISTENERS.add(l);
978            }
979    
980            public static WebcamDiscoveryListener[] getDiscoveryListeners() {
981                    return DISCOVERY_LISTENERS.toArray(new WebcamDiscoveryListener[DISCOVERY_LISTENERS.size()]);
982            }
983    
984            /**
985             * Remove discovery listener
986             * 
987             * @param l the listener to be removed
988             * @return True if listeners list contained the specified element
989             */
990            public static boolean removeDiscoveryListener(WebcamDiscoveryListener l) {
991                    return DISCOVERY_LISTENERS.remove(l);
992            }
993    
994            /**
995             * Return discovery service.
996             * 
997             * @return Discovery service
998             */
999            public static synchronized WebcamDiscoveryService getDiscoveryService() {
1000                    if (discovery == null) {
1001                            discovery = new WebcamDiscoveryService(getDriver());
1002                    }
1003                    return discovery;
1004            }
1005    
1006            /**
1007             * Return image transformer.
1008             * 
1009             * @return Transformer instance
1010             */
1011            public WebcamImageTransformer getImageTransformer() {
1012                    return transformer;
1013            }
1014    
1015            /**
1016             * Set image transformer.
1017             * 
1018             * @param transformer the transformer to be set
1019             */
1020            public void setImageTransformer(WebcamImageTransformer transformer) {
1021                    this.transformer = transformer;
1022            }
1023    }