Skip to content

Commit

Permalink
Make WebcamPanel resistant to unexpected exceptions, refs #65
Browse files Browse the repository at this point in the history
  • Loading branch information
sarxos committed Mar 15, 2013
1 parent 83e460f commit d177e7b
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 50 deletions.
47 changes: 36 additions & 11 deletions webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java
Expand Up @@ -135,8 +135,8 @@ protected Webcam(WebcamDevice device) {
*
* @see #open(boolean)
*/
public void open() {
open(false);
public boolean open() {
return open(false);
}

/**
Expand All @@ -161,12 +161,21 @@ public void open() {
*
* @param asynchronous true for non-blocking mode, false for blocking
*/
public void open(boolean async) {
public boolean open(boolean async) {

if (open.compareAndSet(false, true)) {

WebcamOpenTask task = new WebcamOpenTask(driver, device);
task.open();
try {
task.open();
} catch (InterruptedException e) {
open.set(false);
LOG.error("Processor has been interrupted before webcam was open!", e);
return false;
} catch (WebcamException e) {
open.set(false);
throw e;
}

LOG.debug("Webcam is now open {}", getName());

Expand Down Expand Up @@ -196,23 +205,33 @@ public void open(boolean async) {
}
}

return;
} else {
LOG.debug("Webcam is already open {}", getName());
}

LOG.debug("Webcam is already open {}", getName());
return true;
}

/**
* Close the webcam.
*/
public void close() {
public boolean close() {

if (open.compareAndSet(true, false)) {

// close webcam

WebcamCloseTask task = new WebcamCloseTask(driver, device);
task.close();
try {
task.close();
} catch (InterruptedException e) {
open.set(true);
LOG.error("Processor has been interrupted before webcam was closed!", e);
return false;
} catch (WebcamException e) {
open.set(false);
throw e;
}

// stop updater

Expand All @@ -235,10 +254,11 @@ public void close() {
}
}

return;
} else {
LOG.debug("Webcam is already closed {}", getName());
}

LOG.debug("Webcam is already closed {}", getName());
return true;
}

/**
Expand Down Expand Up @@ -749,7 +769,12 @@ protected void dispose() {
LOG.info("Disposing webcam {}", getName());

WebcamDisposeTask task = new WebcamDisposeTask(driver, device);
task.dispose();
try {
task.dispose();
} catch (InterruptedException e) {
LOG.error("Processor has been interrupted before webcam was disposed!", e);
return;
}

WebcamEvent we = new WebcamEvent(this);
for (WebcamListener l : listeners) {
Expand Down
Expand Up @@ -9,9 +9,9 @@
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.swing.JPanel;
Expand Down Expand Up @@ -88,7 +88,14 @@ public void paintPanel(WebcamPanel owner, Graphics2D g2) {
g2.drawLine(0, 0, getWidth(), getHeight());
g2.drawLine(0, getHeight(), getWidth(), 0);

String str = starting ? "Initializing" : "No Image";
String str = null;

if (!errored) {
str = starting ? "Initializing Device" : "No Image";
} else {
str = "Device Error";
}

FontMetrics metrics = g2.getFontMetrics(getFont());
int w = metrics.stringWidth(str);
int h = metrics.getHeight();
Expand Down Expand Up @@ -159,9 +166,9 @@ public void paintImage(WebcamPanel owner, BufferedImage image, Graphics2D g2) {
private static final double MAX_FREQUENCY = 50; // 50 frames per second

/**
* Yep, this is timer.
* Scheduled executor acting as timer.
*/
private Timer timer = new Timer();
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

/**
* Repainter updates panel when it is being started.
Expand All @@ -188,10 +195,14 @@ public void run() {
}
}

if (isFPSLimited()) {
timer.scheduleAtFixedRate(updater, new Date(), (long) (1000 / frequency));
if (webcam.isOpen()) {
if (isFPSLimited()) {
executor.scheduleAtFixedRate(updater, 0, (long) (1000 / frequency), TimeUnit.MILLISECONDS);
} else {
executor.schedule(updater, 0, TimeUnit.MILLISECONDS);
}
} else {
timer.schedule(updater, new Date(), 1);
executor.schedule(this, 500, TimeUnit.MILLISECONDS);
}
}

Expand All @@ -202,7 +213,7 @@ public void run() {
*
* @author Bartosz Firyn (SarXos)
*/
private class ImageUpdater extends TimerTask {
private class ImageUpdater implements Runnable {

public ImageUpdater() {
}
Expand Down Expand Up @@ -276,6 +287,11 @@ public void run() {
*/
private volatile boolean paused = false;

/**
* Is there any problem with webcam?
*/
private volatile boolean errored = false;

/**
* Webcam has been started.
*/
Expand Down Expand Up @@ -341,10 +357,13 @@ public WebcamPanel(Webcam webcam, Dimension size, boolean start) {
}

if (start) {
if (!webcam.isOpen()) {
webcam.open();
}
updater.start();
try {
errored = !webcam.open();
} catch (WebcamException e) {
errored = true;
throw e;
}
}
}

Expand Down Expand Up @@ -388,7 +407,6 @@ public void webcamOpen(WebcamEvent we) {
@Override
public void webcamClosed(WebcamEvent we) {
if (updater != null) {
updater.cancel();
updater = null;
}
}
Expand All @@ -413,13 +431,16 @@ public void start() {
updater = new ImageUpdater();
}

updater.start();

try {
updater.start();
webcam.open();
errored = !webcam.open();
} catch (WebcamException e) {
errored = true;
throw e;
} finally {
starting = false;
}

}

/**
Expand All @@ -428,7 +449,12 @@ public void start() {
public void stop() {
if (started.compareAndSet(true, false)) {
image = null;
webcam.close();
try {
errored = !webcam.close();
} catch (WebcamException e) {
errored = true;
throw e;
}
}
}

Expand Down
Expand Up @@ -43,9 +43,14 @@ private static final class AtomicProcessor implements Runnable {
* @return Processed task
* @throws InterruptedException when thread has been interrupted
*/
public WebcamTask process(WebcamTask task) throws InterruptedException {
public void process(WebcamTask task) throws InterruptedException {

inbound.put(task);
return outbound.take();

Throwable t = outbound.take().getThrowable();
if (t != null) {
throw new WebcamException("Cannot execute task", t);
}
}

@Override
Expand All @@ -56,6 +61,8 @@ public void run() {
(t = inbound.take()).handle();
} catch (InterruptedException e) {
break;
} catch (Throwable e) {
t.setThrowable(e);
} finally {
try {
if (t != null) {
Expand Down Expand Up @@ -96,16 +103,13 @@ private WebcamProcessor() {
* Process single webcam task.
*
* @param task the task to be processed
* @throws InterruptedException when thread has been interrupted
*/
public void process(WebcamTask task) {
public void process(WebcamTask task) throws InterruptedException {
if (started.compareAndSet(false, true)) {
runner.execute(processor);
}
try {
processor.process(task);
} catch (InterruptedException e) {
throw new WebcamException("Processing interrupted", e);
}
processor.process(task);
}

public static synchronized WebcamProcessor getInstance() {
Expand Down
Expand Up @@ -5,7 +5,7 @@ public abstract class WebcamTask {
private boolean sync = true;
private WebcamProcessor processor = null;
private WebcamDevice device = null;
private WebcamException exception = null;
private Throwable throwable = null;

public WebcamTask(WebcamDriver driver, WebcamDevice device) {

Expand All @@ -26,8 +26,9 @@ public WebcamDevice getDevice() {
* Process task by processor thread.
*
* @param processor the processor to be used to process this task
* @throws InterruptedException when thread has been interrupted
*/
public void process() {
public void process() throws InterruptedException {
if (sync) {
if (processor == null) {
throw new RuntimeException("Driver should be synchronized, but processor is null");
Expand All @@ -38,12 +39,12 @@ public void process() {
}
}

public WebcamException getException() {
return exception;
public Throwable getThrowable() {
return throwable;
}

public void setException(WebcamException exception) {
this.exception = exception;
public void setThrowable(Throwable t) {
this.throwable = t;
}

protected abstract void handle();
Expand Down
@@ -1,6 +1,7 @@
package com.github.sarxos.webcam.ds.buildin;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

Expand Down Expand Up @@ -41,7 +42,12 @@ public WebcamNewGrabberTask(WebcamDriver driver) {
}

public OpenIMAJGrabber newGrabber() {
process();
try {
process();
} catch (InterruptedException e) {
LOG.error("Processor has been interrupted");
return null;
}
return grabber.get();
}

Expand All @@ -60,9 +66,23 @@ public GetDevicesTask(WebcamDriver driver) {
super(driver, null);
}

/**
* Return camera devices.
*
* @param grabber the native grabber to use for search
* @return Camera devices.
*/
public List<WebcamDevice> getDevices(OpenIMAJGrabber grabber) {

this.grabber = grabber;
process();

try {
process();
} catch (InterruptedException e) {
LOG.error("Processor has been interrupted");
return Collections.emptyList();
}

return devices;
}

Expand Down Expand Up @@ -93,8 +113,13 @@ public List<WebcamDevice> getDevices() {
LOG.debug("Searching devices");

if (grabber == null) {

WebcamNewGrabberTask task = new WebcamNewGrabberTask(this);
grabber = task.newGrabber();

if (grabber == null) {
return Collections.emptyList();
}
}

List<WebcamDevice> devices = new GetDevicesTask(this).getDevices(grabber);
Expand Down
Expand Up @@ -16,7 +16,7 @@ public WebcamCloseTask(WebcamDriver driver, WebcamDevice device) {
super(driver, device);
}

public void close() {
public void close() throws InterruptedException {
process();
}

Expand Down

0 comments on commit d177e7b

Please sign in to comment.