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