001package com.github.sarxos.webcam; 002 003import java.awt.Dimension; 004import java.awt.image.BufferedImage; 005import java.nio.ByteBuffer; 006import java.util.ArrayList; 007import java.util.Arrays; 008import java.util.Collections; 009import java.util.Iterator; 010import java.util.List; 011import java.util.concurrent.CopyOnWriteArrayList; 012import java.util.concurrent.TimeUnit; 013import java.util.concurrent.TimeoutException; 014import java.util.concurrent.atomic.AtomicBoolean; 015 016import org.slf4j.Logger; 017import org.slf4j.LoggerFactory; 018 019import com.github.sarxos.webcam.WebcamDevice.BufferAccess; 020import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDevice; 021import com.github.sarxos.webcam.ds.buildin.WebcamDefaultDriver; 022import com.github.sarxos.webcam.ds.cgt.WebcamCloseTask; 023import com.github.sarxos.webcam.ds.cgt.WebcamDisposeTask; 024import com.github.sarxos.webcam.ds.cgt.WebcamOpenTask; 025import com.github.sarxos.webcam.ds.cgt.WebcamReadBufferTask; 026import com.github.sarxos.webcam.ds.cgt.WebcamReadImageTask; 027import com.github.sarxos.webcam.util.ImageUtils; 028 029 030/** 031 * Webcam class. It wraps webcam device obtained from webcam driver. 032 * 033 * @author Bartosz Firyn (bfiryn) 034 */ 035public class Webcam { 036 037 /** 038 * Logger instance. 039 */ 040 private static final Logger LOG = LoggerFactory.getLogger(Webcam.class); 041 042 /** 043 * List of driver classes names to search for. 044 */ 045 private static final List<String> DRIVERS_LIST = new ArrayList<String>(); 046 047 /** 048 * List of driver classes to search for. 049 */ 050 private static final List<Class<?>> DRIVERS_CLASS_LIST = new ArrayList<Class<?>>(); 051 052 /** 053 * Discovery listeners. 054 */ 055 private static final List<WebcamDiscoveryListener> DISCOVERY_LISTENERS = Collections.synchronizedList(new ArrayList<WebcamDiscoveryListener>()); 056 057 /** 058 * Webcam driver (LtiCivil, JMF, FMJ, JQT, OpenCV, VLCj, etc). 059 */ 060 private static WebcamDriver driver = null; 061 062 /** 063 * Webcam discovery service. 064 */ 065 private static volatile WebcamDiscoveryService discovery = null; 066 067 /** 068 * Is automated deallocation on TERM signal enabled. 069 */ 070 private static boolean deallocOnTermSignal = false; 071 072 /** 073 * Is auto-open feature enabled? 074 */ 075 private static boolean autoOpen = false; 076 077 /** 078 * Webcam listeners. 079 */ 080 private List<WebcamListener> listeners = new CopyOnWriteArrayList<WebcamListener>(); 081 082 /** 083 * List of custom resolution sizes supported by webcam instance. 084 */ 085 private List<Dimension> customSizes = new ArrayList<Dimension>(); 086 087 /** 088 * Shutdown hook. 089 */ 090 private WebcamShutdownHook hook = null; 091 092 /** 093 * Underlying webcam device. 094 */ 095 private WebcamDevice device = null; 096 097 /** 098 * Is webcam open? 099 */ 100 private AtomicBoolean open = new AtomicBoolean(false); 101 102 /** 103 * Is webcam already disposed? 104 */ 105 private AtomicBoolean disposed = new AtomicBoolean(false); 106 107 /** 108 * Is non-blocking (asynchronous) access enabled? 109 */ 110 private volatile boolean asynchronous = false; 111 112 /** 113 * Current FPS. 114 */ 115 private volatile double fps = 0; 116 117 /** 118 * Webcam image updater. 119 */ 120 private WebcamUpdater updater = new WebcamUpdater(this); 121 122 /** 123 * IMage transformer. 124 */ 125 private volatile WebcamImageTransformer transformer = null; 126 127 private WebcamLock lock = null; 128 129 /** 130 * Webcam class. 131 * 132 * @param device - device to be used as webcam 133 * @throws IllegalArgumentException when device argument is null 134 */ 135 protected Webcam(WebcamDevice device) { 136 if (device == null) { 137 throw new IllegalArgumentException("Webcam device cannot be null"); 138 } 139 this.device = device; 140 this.lock = new WebcamLock(this); 141 } 142 143 /** 144 * Open the webcam in blocking (synchronous) mode. 145 * 146 * @see #open(boolean) 147 */ 148 public boolean open() { 149 return open(false); 150 } 151 152 /** 153 * Open the webcam in either blocking (synchronous) or non-blocking 154 * (asynchronous) mode.The difference between those two modes lies in the 155 * image acquisition mechanism.<br> 156 * <br> 157 * In blocking mode, when user calls {@link #getImage()} method, device is 158 * being queried for new image buffer and user have to wait for it to be 159 * available.<br> 160 * <br> 161 * In non-blocking mode, there is a special thread running in the background 162 * which constantly fetch new images and cache them internally for further 163 * use. This cached instance is returned every time when user request new 164 * image. Because of that it can be used when timeing is very important, 165 * because all users calls for new image do not have to wait on device 166 * response. By using this mode user should be aware of the fact that in 167 * some cases, when two consecutive calls to get new image are executed more 168 * often than webcam device can serve them, the same image instance will be 169 * returned. User should use {@link #isImageNew()} method to distinguish if 170 * returned image is not the same as the previous one. 171 * 172 * @param async true for non-blocking mode, false for blocking 173 */ 174 public boolean open(boolean async) { 175 176 if (open.compareAndSet(false, true)) { 177 178 assert updater != null; 179 assert lock != null; 180 181 // lock webcam for other Java (only) processes 182 183 lock.lock(); 184 185 // open webcam device 186 187 WebcamOpenTask task = new WebcamOpenTask(driver, device); 188 try { 189 task.open(); 190 } catch (InterruptedException e) { 191 lock.unlock(); 192 open.set(false); 193 LOG.debug("Thread has been interrupted in the middle of webcam opening process!", e); 194 return false; 195 } catch (WebcamException e) { 196 lock.unlock(); 197 open.set(false); 198 throw e; 199 } 200 201 LOG.debug("Webcam is now open {}", getName()); 202 203 // setup non-blocking configuration 204 205 asynchronous = async; 206 207 if (async) { 208 updater.start(); 209 } 210 211 // install shutdown hook 212 213 Runtime.getRuntime().addShutdownHook(hook = new WebcamShutdownHook(this)); 214 215 // notify listeners 216 217 WebcamEvent we = new WebcamEvent(WebcamEventType.OPEN, this); 218 Iterator<WebcamListener> wli = listeners.iterator(); 219 WebcamListener l = null; 220 221 while (wli.hasNext()) { 222 l = wli.next(); 223 try { 224 l.webcamOpen(we); 225 } catch (Exception e) { 226 LOG.error(String.format("Notify webcam open, exception when calling listener %s", l.getClass()), e); 227 } 228 } 229 230 } else { 231 LOG.debug("Webcam is already open {}", getName()); 232 } 233 234 return true; 235 } 236 237 /** 238 * Close the webcam. 239 */ 240 public boolean close() { 241 242 if (open.compareAndSet(true, false)) { 243 244 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}