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