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