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