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 }