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