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