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