diff --git a/webcam-capture-examples/webcam-capture-motiondetector/src/main/java/com/github/sarxos/webcam/DetectMotionExample.java b/webcam-capture-examples/webcam-capture-motiondetector/src/main/java/com/github/sarxos/webcam/DetectMotionExample.java index 0484b787..7622dc34 100644 --- a/webcam-capture-examples/webcam-capture-motiondetector/src/main/java/com/github/sarxos/webcam/DetectMotionExample.java +++ b/webcam-capture-examples/webcam-capture-motiondetector/src/main/java/com/github/sarxos/webcam/DetectMotionExample.java @@ -18,9 +18,6 @@ */ public class DetectMotionExample extends JFrame implements Runnable { - /** - * - */ private static final long serialVersionUID = -585739158170333370L; private static final int INTERVAL = 100; // ms diff --git a/webcam-capture/src/example/java/com/github/sarxos/webcam/ConcurrentThreadsExample.java b/webcam-capture/src/example/java/com/github/sarxos/webcam/ConcurrentThreadsExample.java new file mode 100644 index 00000000..4a9df16d --- /dev/null +++ b/webcam-capture/src/example/java/com/github/sarxos/webcam/ConcurrentThreadsExample.java @@ -0,0 +1,43 @@ +package com.github.sarxos.webcam; + +import java.util.concurrent.atomic.AtomicInteger; + + +public class ConcurrentThreadsExample { + + private static AtomicInteger counter = new AtomicInteger(0); + + private static final class Capture extends Thread { + + private static final AtomicInteger number = new AtomicInteger(0); + + public Capture() { + super("capture-" + number.incrementAndGet()); + } + + @Override + public void run() { + while (true) { + Webcam.getDefault().getImage(); + int value = counter.incrementAndGet(); + if (value != 0 && value % 10 == 0) { + System.out.println(Thread.currentThread().getName() + ": Frames captured: " + value); + } + } + } + } + + public static void main(String[] args) { + + /** + * This example will start several concurrent threads which use single + * webcam instance. + */ + + int n = Runtime.getRuntime().availableProcessors() * 4; + for (int i = 0; i < n; i++) { + System.out.println("Thread: " + i); + new Capture().start(); + } + } +} diff --git a/webcam-capture/src/example/java/com/github/sarxos/webcam/PureDefaultDeviceExample.java b/webcam-capture/src/example/java/com/github/sarxos/webcam/PureDefaultDeviceExample.java new file mode 100644 index 00000000..d71b30ad --- /dev/null +++ b/webcam-capture/src/example/java/com/github/sarxos/webcam/PureDefaultDeviceExample.java @@ -0,0 +1,45 @@ +package com.github.sarxos.webcam; + +import org.bridj.Pointer; + +import com.github.sarxos.webcam.ds.buildin.natives.Device; +import com.github.sarxos.webcam.ds.buildin.natives.DeviceList; +import com.github.sarxos.webcam.ds.buildin.natives.OpenIMAJGrabber; + + +public class PureDefaultDeviceExample { + + public static void main(String[] args) { + + /** + * This example show how to use native OpenIMAJ API to capture raw bytes + * data as byte[] array. It also calculates current FPS. + */ + + OpenIMAJGrabber grabber = new OpenIMAJGrabber(); + + Device device = null; + Pointer devices = grabber.getVideoDevices(); + for (Device d : devices.get().asArrayList()) { + device = d; + break; + } + + grabber.startSession(320, 240, 30, Pointer.pointerTo(device)); + + long t1 = System.currentTimeMillis() / 1000; + + int n = 100; + int i = 0; + do { + grabber.nextFrame(); + grabber.getImage().getBytes(320 * 240 * 3); // byte[] + } while (++i < n); + + long t2 = System.currentTimeMillis() / 1000; + + System.out.println("FPS: " + ((double) n / (t2 - t1))); + + grabber.stopSession(); + } +} diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java index da57ebee..aa956945 100644 --- a/webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java +++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/Webcam.java @@ -73,12 +73,12 @@ public void run() { /** * Webcam driver (LtiCivil, JMF, FMJ, JQT, OpenCV, VLCj, etc). */ - private static WebcamDriver driver = null; + private static volatile WebcamDriver driver = null; /** * Webcam discovery service. */ - private static WebcamDiscoveryService discovery = null; + private static volatile WebcamDiscoveryService discovery = null; /** * Is automated deallocation on TERM signal enabled. @@ -331,13 +331,14 @@ public BufferedImage getImage() { return null; } - synchronized (this) { - if (!open) { - LOG.debug("Try to get image on closed webcam, opening it automatically"); + if (!open) { + LOG.debug("Try to get image on closed webcam, opening it automatically"); + synchronized (this) { open(); } - return device.getImage(); } + + return device.getImage(); } /** @@ -382,7 +383,7 @@ public static List getWebcams(long timeout) throws TimeoutException, Web * @throws TimeoutException when timeout has been exceeded * @throws WebcamException when something is wrong */ - public static List getWebcams(long timeout, TimeUnit tunit) throws TimeoutException, WebcamException { + public static synchronized List getWebcams(long timeout, TimeUnit tunit) throws TimeoutException, WebcamException { WebcamDiscoveryService discovery = getDiscoveryService(); List webcams = discovery.getWebcams(timeout, tunit); @@ -402,6 +403,7 @@ public static List getWebcams(long timeout, TimeUnit tunit) throws Timeo * @see Webcam#getWebcams() */ public static Webcam getDefault() throws WebcamException { + try { return getDefault(Long.MAX_VALUE); } catch (TimeoutException e) { @@ -507,7 +509,7 @@ public boolean removeWebcamListener(WebcamListener l) { * * @return Webcam driver */ - public static WebcamDriver getDriver() { + public static synchronized WebcamDriver getDriver() { if (driver == null) { driver = WebcamDriverUtils.findDriver(DRIVERS_LIST, DRIVERS_CLASS_LIST); @@ -529,11 +531,14 @@ public static WebcamDriver getDriver() { * @param driver new webcam driver to be used (e.g. LtiCivil, JFM, FMJ, QTJ) * @throws IllegalArgumentException when argument is null */ - public static void setDriver(WebcamDriver driver) { + public static synchronized void setDriver(WebcamDriver driver) { + if (driver == null) { throw new IllegalArgumentException("Webcam driver cannot be null!"); } + resetDriver(); + Webcam.driver = driver; } @@ -547,7 +552,7 @@ public static void setDriver(WebcamDriver driver) { * @param driver new video driver class to use * @throws IllegalArgumentException when argument is null */ - public static void setDriver(Class driverClass) { + public static synchronized void setDriver(Class driverClass) { resetDriver(); @@ -718,7 +723,7 @@ public static boolean removeDiscoveryListener(WebcamDiscoveryListener l) { * * @return Discovery service */ - public static WebcamDiscoveryService getDiscoveryService() { + public static synchronized WebcamDiscoveryService getDiscoveryService() { if (discovery == null) { discovery = new WebcamDiscoveryService(getDriver()); } diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamDiscoveryService.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamDiscoveryService.java index f351215c..58212f50 100644 --- a/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamDiscoveryService.java +++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamDiscoveryService.java @@ -78,11 +78,7 @@ private static List getDevices(List webcams) { return devices; } - public List getWebcams() throws TimeoutException { - return getWebcams(Webcam.getDiscoveryTimeout(), TimeUnit.MILLISECONDS); - } - - public List getWebcams(long timeout, TimeUnit tunit) throws TimeoutException { + public synchronized List getWebcams(long timeout, TimeUnit tunit) throws TimeoutException { if (timeout < 0) { throw new IllegalArgumentException("Timeout cannot be negative"); @@ -162,7 +158,7 @@ public void run() { List tmpold = null; try { - tmpold = getDevices(getWebcams(Webcam.getDiscoveryTimeout(), TimeUnit.MILLISECONDS)); + tmpold = getDevices(getWebcams(Long.MAX_VALUE, TimeUnit.MILLISECONDS)); } catch (TimeoutException e) { throw new WebcamException(e); } diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/WebcamGrabberProcessor.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/WebcamGrabberProcessor.java index 6cb8b543..1a532655 100644 --- a/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/WebcamGrabberProcessor.java +++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/WebcamGrabberProcessor.java @@ -2,12 +2,12 @@ import java.util.concurrent.Executor; import java.util.concurrent.Executors; -import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import com.github.sarxos.webcam.WebcamException; +import com.github.sarxos.webcam.ds.buildin.cgt.NewGrabberTask; import com.github.sarxos.webcam.ds.buildin.natives.OpenIMAJGrabber; @@ -28,10 +28,17 @@ public final class WebcamGrabberProcessor { */ private static final class ProcessorThreadFactory implements ThreadFactory { + private static int number = 0; + @Override public Thread newThread(Runnable r) { - Thread t = new Thread(r, getClass().getSimpleName()); + + // should always be 1, but add some unique name for debugging + // purpose just in case if there is some bug in my understanding + + Thread t = new Thread(r, "processor-" + (++number)); t.setDaemon(true); + return t; } } @@ -44,50 +51,78 @@ public Thread newThread(Runnable r) { */ private static final class StaticProcessor implements Runnable { - @Override - public void run() { - while (true) { - WebcamGrabberTask task = null; - try { - (task = tasks.take()).handle(); - } catch (InterruptedException e) { - throw new WebcamException("Take interrupted", e); - } finally { - synchronized (task) { - task.notifyAll(); - } - } + private class IO { + + public AtomicReference input = new AtomicReference(); + public AtomicReference output = new AtomicReference(); + + public IO(WebcamGrabberTask task) { + input.set(task); } } - } - /** - * Internal task used to create new grabber. Yeah, native grabber - * construction, same as all other methods invoked on its instance, also has - * to be super-synchronized. - * - * @author Bartosz Firyn (SarXos) - */ - private static final class NewGrabberTask extends WebcamGrabberTask { + private final AtomicReference ioref = new AtomicReference(); - private volatile OpenIMAJGrabber grabber = null; + /** + * Process task. + * + * @param task the task to be processed + * @return Processed task + * @throws InterruptedException when thread has been interrupted + */ + public WebcamGrabberTask process(WebcamGrabberTask task) throws InterruptedException { - public OpenIMAJGrabber getGrabber() { - return grabber; + IO io = new IO(task); + + // submit task wrapped in IO pair + synchronized (ioref) { + while (!ioref.compareAndSet(null, io)) { + ioref.wait(); + } + } + + // obtain processed task + synchronized (io.output) { + while ((task = io.output.getAndSet(null)) == null) { + io.output.wait(); + } + } + + return task; } @Override - protected void handle() { - grabber = new OpenIMAJGrabber(); + public void run() { + + WebcamGrabberTask t = null; + IO io = null; + + while (true) { + + if ((io = ioref.getAndSet(null)) != null) { + + synchronized (ioref) { + ioref.notify(); + } + + (t = io.input.get()).handle(); + io.output.set(t); + + synchronized (io.output) { + io.output.notify(); + } + + } else { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + } } } - /** - * Synchronous queue used to exchange tasks between threads and static - * processor. - */ - private static final SynchronousQueue tasks = new SynchronousQueue(true); - /** * Execution service. */ @@ -117,6 +152,20 @@ protected WebcamGrabberProcessor() { } } + /** + * Create native grabber. + * + * @return New grabber + */ + private OpenIMAJGrabber createGrabber() { + NewGrabberTask ngt = new NewGrabberTask(); + try { + return ((NewGrabberTask) processor.process(ngt)).getGrabber(); + } catch (InterruptedException e) { + throw new WebcamException("Grabber creation interrupted", e); + } + } + /** * Process task. * @@ -126,31 +175,18 @@ protected void process(WebcamGrabberTask task) { // construct grabber if not available yet - synchronized (this) { - if (grabber == null) { - NewGrabberTask grabberTask = new NewGrabberTask(); - try { - synchronized (grabberTask) { - tasks.offer(grabberTask); - grabberTask.wait(); - grabber = grabberTask.getGrabber(); - } - } catch (InterruptedException e) { - throw new WebcamException("Grabber creation interrupted", e); - } - } + if (grabber == null) { + grabber = createGrabber(); } - // run task and wait for it to be completed + // process task and wait for it to be completed + + task.setGrabber(grabber); try { - synchronized (task) { - task.setGrabber(grabber); - tasks.offer(task, 30, TimeUnit.MINUTES); - task.wait(); - } + processor.process(task); } catch (InterruptedException e) { - throw new WebcamException("Offer interrupted", e); + throw new WebcamException("Processing interrupted", e); } } } diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/WebcamGrabberTask.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/WebcamGrabberTask.java index 7906f8ec..41188684 100644 --- a/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/WebcamGrabberTask.java +++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/WebcamGrabberTask.java @@ -16,7 +16,7 @@ public abstract class WebcamGrabberTask { protected volatile OpenIMAJGrabber grabber = null; /** - * Process task by processor thread. + * ne = true; Process task by processor thread. * * @param processor the processor to be used to process this task */ diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/cgt/NewGrabberTask.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/cgt/NewGrabberTask.java new file mode 100644 index 00000000..d61e7343 --- /dev/null +++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/ds/buildin/cgt/NewGrabberTask.java @@ -0,0 +1,26 @@ +package com.github.sarxos.webcam.ds.buildin.cgt; + +import com.github.sarxos.webcam.ds.buildin.WebcamGrabberTask; +import com.github.sarxos.webcam.ds.buildin.natives.OpenIMAJGrabber; + + +/** + * Internal task used to create new grabber. Yeah, native grabber construction, + * same as all other methods invoked on its instance, also has to be + * super-synchronized. + * + * @author Bartosz Firyn (SarXos) + */ +public class NewGrabberTask extends WebcamGrabberTask { + + private volatile OpenIMAJGrabber grabber = null; + + public OpenIMAJGrabber getGrabber() { + return grabber; + } + + @Override + protected void handle() { + grabber = new OpenIMAJGrabber(); + } +} \ No newline at end of file