From 2b5a567a6156a1c767089d780206538bab8945c1 Mon Sep 17 00:00:00 2001 From: Bartosz Firyn Date: Sun, 21 Jul 2013 23:44:47 +0200 Subject: [PATCH] Spy camera addon, work in progress, PHP not ready yet --- webcam-capture-addons/pom.xml | 1 + .../webcam-capture-addon-spycam/.classpath | 22 ++ .../webcam-capture-addon-spycam/.project | 23 ++ .../webcam-capture-addon-spycam/README.md | 16 + .../webcam-capture-addon-spycam/pom.xml | 43 +++ .../com/github/sarxos/spycam/SpycamMain.java | 285 ++++++++++++++++++ .../src/main/php/config.php | 15 + .../src/main/php/index.php | 38 +++ .../src/main/php/uploads/README.txt | 1 + .../src/main/resources/logback.xml | 10 + .../sarxos/webcam/DetectMotionExample.java | 2 +- .../src/example/java/DetectMotionExample.java | 2 +- .../sarxos/webcam/WebcamMotionDetector.java | 137 +++++---- 13 files changed, 534 insertions(+), 61 deletions(-) create mode 100644 webcam-capture-addons/webcam-capture-addon-spycam/.classpath create mode 100644 webcam-capture-addons/webcam-capture-addon-spycam/.project create mode 100644 webcam-capture-addons/webcam-capture-addon-spycam/README.md create mode 100644 webcam-capture-addons/webcam-capture-addon-spycam/pom.xml create mode 100644 webcam-capture-addons/webcam-capture-addon-spycam/src/main/java/com/github/sarxos/spycam/SpycamMain.java create mode 100644 webcam-capture-addons/webcam-capture-addon-spycam/src/main/php/config.php create mode 100644 webcam-capture-addons/webcam-capture-addon-spycam/src/main/php/index.php create mode 100644 webcam-capture-addons/webcam-capture-addon-spycam/src/main/php/uploads/README.txt create mode 100644 webcam-capture-addons/webcam-capture-addon-spycam/src/main/resources/logback.xml diff --git a/webcam-capture-addons/pom.xml b/webcam-capture-addons/pom.xml index 7cadfced..f0bb9fd8 100644 --- a/webcam-capture-addons/pom.xml +++ b/webcam-capture-addons/pom.xml @@ -17,6 +17,7 @@ webcam-capture-addon-swt + webcam-capture-addon-spycam diff --git a/webcam-capture-addons/webcam-capture-addon-spycam/.classpath b/webcam-capture-addons/webcam-capture-addon-spycam/.classpath new file mode 100644 index 00000000..0e40d298 --- /dev/null +++ b/webcam-capture-addons/webcam-capture-addon-spycam/.classpath @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/webcam-capture-addons/webcam-capture-addon-spycam/.project b/webcam-capture-addons/webcam-capture-addon-spycam/.project new file mode 100644 index 00000000..661eed15 --- /dev/null +++ b/webcam-capture-addons/webcam-capture-addon-spycam/.project @@ -0,0 +1,23 @@ + + + webcam-capture-addon-spycam + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/webcam-capture-addons/webcam-capture-addon-spycam/README.md b/webcam-capture-addons/webcam-capture-addon-spycam/README.md new file mode 100644 index 00000000..0d55cf1c --- /dev/null +++ b/webcam-capture-addons/webcam-capture-addon-spycam/README.md @@ -0,0 +1,16 @@ +# webcam-capture-addon-spycam + +Java/PHP set to setup spy camera. + +Work in progress. STAY TUNED! + +## License + +Copyright (C) 2012 Bartosz Firyn + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/webcam-capture-addons/webcam-capture-addon-spycam/pom.xml b/webcam-capture-addons/webcam-capture-addon-spycam/pom.xml new file mode 100644 index 00000000..48bf6a83 --- /dev/null +++ b/webcam-capture-addons/webcam-capture-addon-spycam/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + + com.github.sarxos + webcam-capture-addons + 0.3.10-SNAPSHOT + + + webcam-capture-addon-spycam + + Webcam Capture - Spy Camera PHP Service Addon + + Simple PHP implementation of real-time webcam image streaming from + the webcam provider to the streaming server, where several clients + can view images fater logged in + + + + + com.github.sarxos + webcam-capture + ${project.version} + + + ch.qos.logback + logback-classic + 1.0.9 + + + org.apache.httpcomponents + httpclient + 4.1.3 + + + org.apache.httpcomponents + httpmime + 4.1.3 + + + + diff --git a/webcam-capture-addons/webcam-capture-addon-spycam/src/main/java/com/github/sarxos/spycam/SpycamMain.java b/webcam-capture-addons/webcam-capture-addon-spycam/src/main/java/com/github/sarxos/spycam/SpycamMain.java new file mode 100644 index 00000000..7a14946d --- /dev/null +++ b/webcam-capture-addons/webcam-capture-addon-spycam/src/main/java/com/github/sarxos/spycam/SpycamMain.java @@ -0,0 +1,285 @@ +package com.github.sarxos.spycam; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.lang.Thread.UncaughtExceptionHandler; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +import javax.imageio.ImageIO; +import javax.swing.JFrame; +import javax.swing.SwingUtilities; + +import org.apache.http.HttpResponse; +import org.apache.http.ParseException; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntity; +import org.apache.http.entity.mime.content.FileBody; +import org.apache.http.entity.mime.content.StringBody; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.github.sarxos.webcam.Webcam; +import com.github.sarxos.webcam.WebcamEvent; +import com.github.sarxos.webcam.WebcamListener; +import com.github.sarxos.webcam.WebcamMotionDetector; +import com.github.sarxos.webcam.WebcamMotionEvent; +import com.github.sarxos.webcam.WebcamMotionListener; +import com.github.sarxos.webcam.WebcamPanel; +import com.github.sarxos.webcam.WebcamPicker; +import com.github.sarxos.webcam.WebcamResolution; + + +/** + * Proof of concept of how to handle webcam video stream from Java + * + * @author Bartosz Firyn (SarXos) + */ +public class SpycamMain extends JFrame implements Runnable, WebcamListener, WindowListener, UncaughtExceptionHandler, ItemListener, ThreadFactory, WebcamMotionListener { + + private static final long serialVersionUID = 1L; + private static final Logger LOG = LoggerFactory.getLogger(SpycamMain.class); + + private final ExecutorService executor = Executors.newCachedThreadPool(this); + private final AtomicInteger counter = new AtomicInteger(0); + private final AtomicLong idnum = new AtomicLong(1000000000); + private final DefaultHttpClient client = new DefaultHttpClient(); + private final String uri = "http://webcam-capture.sarxos.pl/upload-demo/upload.php"; + private final Dimension size = WebcamResolution.QVGA.getSize(); + + private Webcam webcam = null; + private WebcamPanel panel = null; + private WebcamPicker picker = null; + private WebcamMotionDetector detector = null; + + public SpycamMain() { + + super(); + + setTitle("Spy Camera Service"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setLayout(new BorderLayout()); + + addWindowListener(this); + + picker = new WebcamPicker(); + picker.addItemListener(this); + + webcam = Webcam.getDefault(); + webcam.setViewSize(size); + + panel = new WebcamPanel(webcam, false); + + add(picker, BorderLayout.NORTH); + add(panel, BorderLayout.CENTER); + + pack(); + setVisible(true); + + executor.execute(new Runnable() { + + @Override + public void run() { + pick(picker.getSelectedWebcam()); + } + }); + } + + @Override + public void run() { + + } + + public static void main(String[] args) { + + if (Webcam.getWebcams().isEmpty()) { + System.err.println("No webcams detected in the system"); + System.exit(1); + } + + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + new SpycamMain(); + } + }); + } + + @Override + public void webcamOpen(WebcamEvent we) { + } + + @Override + public void webcamClosed(WebcamEvent we) { + } + + @Override + public void webcamDisposed(WebcamEvent we) { + } + + @Override + public void webcamImageObtained(WebcamEvent we) { + // do nothing + } + + @Override + public void windowActivated(WindowEvent e) { + } + + @Override + public void windowClosed(WindowEvent e) { + LOG.debug("Spycam window has been closed"); + webcam.close(); + } + + @Override + public void windowClosing(WindowEvent e) { + } + + @Override + public void windowOpened(WindowEvent e) { + } + + @Override + public void windowDeactivated(WindowEvent e) { + } + + @Override + public void windowIconified(WindowEvent e) { + LOG.debug("Spycam window has been iconified"); + panel.pause(); + } + + @Override + public void windowDeiconified(WindowEvent e) { + LOG.debug("Spycam window has been deiconified"); + panel.resume(); + } + + @Override + public void uncaughtException(Thread t, Throwable e) { + System.err.println(String.format("Exception in thread %s", t.getName())); + e.printStackTrace(); + LOG.error("Exception when running spycam", e); + } + + private void pick(Webcam w) { + + if (w == null) { + throw new IllegalArgumentException("Selected webcam cannot be null"); + } + + if (panel != null) { + panel.stop(); + } + + if (webcam != null) { + webcam.removeWebcamListener(this); + webcam.close(); + } + + if (detector != null) { + detector.removeMotionListener(this); + detector.stop(); + } + + LOG.info("Selected {}", webcam); + + webcam = w; + webcam.setViewSize(size); + webcam.addWebcamListener(this); + + detector = new WebcamMotionDetector(webcam); + detector.setCheckInterval(5000); + detector.addMotionListener(this); + detector.start(); + + executor.execute(new Runnable() { + + @Override + public void run() { + remove(panel); + add(panel = new WebcamPanel(webcam), BorderLayout.CENTER); + pack(); + } + }); + + } + + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getItem() != webcam) { + pick((Webcam) e.getItem()); + } + } + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, String.format("gui-executor-thread-%d", counter.incrementAndGet())); + t.setUncaughtExceptionHandler(this); + t.setDaemon(true); + return t; + } + + @Override + public void motionDetected(WebcamMotionEvent wme) { + + LOG.info("{}: motion {}", idnum.incrementAndGet(), wme.getStrength()); + + BufferedImage image = webcam.getImage(); + if (image == null) { + return; + } + + File tmp = null; + try { + tmp = File.createTempFile("spycam-tmp-picture", null); + ImageIO.write(image, "JPG", tmp); + upload(tmp); + } catch (Exception e) { + LOG.error("Exception while uploading picture", e); + } finally { + if (tmp != null && !tmp.delete()) { + tmp.deleteOnExit(); + } + } + } + + private void upload(File file) throws ClientProtocolException, IOException, ParseException, URISyntaxException { + + LOG.debug("Uploading picture {} to {}", file, uri); + + MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); + entity.addPart("picture", new FileBody(file, "image/jpg")); + entity.addPart("passwd", new StringBody("test1234")); + + HttpPost post = new HttpPost(new URI(uri)); + post.setEntity(entity); + + HttpResponse response = client.execute(post); + + int code = response.getStatusLine().getStatusCode(); + if (code == 200) { + LOG.info("Tick picture stored as {} ", EntityUtils.toString(response.getEntity())); + } else { + LOG.error("Error {}", response.getStatusLine()); + } + } +} diff --git a/webcam-capture-addons/webcam-capture-addon-spycam/src/main/php/config.php b/webcam-capture-addons/webcam-capture-addon-spycam/src/main/php/config.php new file mode 100644 index 00000000..23dba2cf --- /dev/null +++ b/webcam-capture-addons/webcam-capture-addon-spycam/src/main/php/config.php @@ -0,0 +1,15 @@ + 'test1234', + + /** + * Directory where pictures will be stored. + */ + 'dir' => 'uploads', + +); \ No newline at end of file diff --git a/webcam-capture-addons/webcam-capture-addon-spycam/src/main/php/index.php b/webcam-capture-addons/webcam-capture-addon-spycam/src/main/php/index.php new file mode 100644 index 00000000..041e5696 --- /dev/null +++ b/webcam-capture-addons/webcam-capture-addon-spycam/src/main/php/index.php @@ -0,0 +1,38 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + 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 06764569..85502370 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 @@ -68,7 +68,7 @@ public static void main(String[] args) throws InterruptedException { public void run() { WebcamMotionDetector detector = new WebcamMotionDetector(webcam, threshold, inertia); - detector.setInterval(INTERVAL); + detector.setCheckInterval(INTERVAL); detector.start(); while (true) { diff --git a/webcam-capture/src/example/java/DetectMotionExample.java b/webcam-capture/src/example/java/DetectMotionExample.java index 52bc3994..8217e30d 100644 --- a/webcam-capture/src/example/java/DetectMotionExample.java +++ b/webcam-capture/src/example/java/DetectMotionExample.java @@ -17,7 +17,7 @@ public class DetectMotionExample implements WebcamMotionListener { public DetectMotionExample() { WebcamMotionDetector detector = new WebcamMotionDetector(Webcam.getDefault()); - detector.setInterval(100); // one check per 100 ms + detector.setCheckInterval(100); // one check per 100 ms detector.addMotionListener(this); detector.start(); } diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamMotionDetector.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamMotionDetector.java index bf2ba16c..bd316653 100644 --- a/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamMotionDetector.java +++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamMotionDetector.java @@ -6,6 +6,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,8 +23,26 @@ */ public class WebcamMotionDetector { + /** + * Logger. + */ private static final Logger LOG = LoggerFactory.getLogger(WebcamMotionDetector.class); + /** + * Thread number in pool. + */ + private static final AtomicInteger NT = new AtomicInteger(0); + + /** + * Thread factory. + */ + private static final ThreadFactory THREAD_FACTORY = new DetectorThreadFactory(); + + /** + * Executor. + */ + private static final ExecutorService EXECUTOR = Executors.newCachedThreadPool(THREAD_FACTORY); + public static final int DEFAULT_THREASHOLD = 25; /** @@ -32,11 +52,9 @@ public class WebcamMotionDetector { */ private static final class DetectorThreadFactory implements ThreadFactory { - private static int number = 0; - @Override public Thread newThread(Runnable runnable) { - Thread t = new Thread(runnable, "motion-detector-" + (++number)); + Thread t = new Thread(runnable, String.format("motion-detector-%d", NT.incrementAndGet())); t.setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance()); t.setDaemon(true); return t; @@ -53,8 +71,8 @@ private class Runner implements Runnable { @Override public void run() { - running = true; - while (running && webcam.isOpen()) { + running.set(true); + while (running.get() && webcam.isOpen()) { detect(); try { Thread.sleep(interval); @@ -70,33 +88,33 @@ public void run() { * * @author Bartosz Firyn (SarXos) */ - private class Changer implements Runnable { + private class Revert implements Runnable { @Override public void run() { - int time = inertia == 0 ? interval + interval / 2 : inertia; + + int time = inertia <= 0 ? (int) (0.5 * interval) : inertia; + LOG.debug("Motion change has been sheduled in " + time + "ms"); + try { Thread.sleep(time); } catch (InterruptedException e) { - throw new RuntimeException(e); - } - synchronized (mutex) { - motion = false; + return; } + + motion.set(false); } } - private List listeners = new ArrayList(); - - private Object mutex = new Object(); + private final List listeners = new ArrayList(); - private boolean running = false; + private final AtomicBoolean running = new AtomicBoolean(false); /** * Is motion? */ - private boolean motion = false; + private final AtomicBoolean motion = new AtomicBoolean(false); /** * Previously captured image. @@ -111,17 +129,17 @@ public void run() { /** * Motion check interval (1000 ms by default). */ - private int interval = 1000; + private volatile int interval = 1000; /** * Pixel intensity threshold (0 - 255). */ - private int threshold = 10; + private volatile int threshold = 10; /** * How long motion is valid. */ - private int inertia = 10000; + private volatile int inertia = 0; /** * Motion strength (0 = no motion). @@ -138,16 +156,6 @@ public void run() { */ private JHGrayFilter gray = new JHGrayFilter(); - /** - * Thread factory. - */ - private ThreadFactory threadFactory = new DetectorThreadFactory(); - - /** - * Executor. - */ - private ExecutorService executor = Executors.newCachedThreadPool(threadFactory); - /** * Create motion detector. Will open webcam if it is closed. * @@ -182,16 +190,14 @@ public WebcamMotionDetector(Webcam webcam) { } public void start() { - if (!webcam.isOpen()) { + if (running.compareAndSet(false, true)) { webcam.open(); + EXECUTOR.submit(new Runner()); } - LOG.debug("Starting motion detector"); - executor.submit(new Runner()); } public void stop() { - running = false; - if (webcam.isOpen()) { + if (running.compareAndSet(true, false)) { webcam.close(); } } @@ -202,7 +208,7 @@ protected void detect() { LOG.debug(WebcamMotionDetector.class.getSimpleName() + ".detect()"); } - if (motion) { + if (motion.get()) { LOG.debug("Motion detector still in inertia state, no need to check"); return; } @@ -219,41 +225,38 @@ protected void detect() { int strength = 0; - synchronized (mutex) { - for (int i = 0; i < w; i++) { - for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + for (int j = 0; j < h; j++) { - int c = current.getRGB(i, j); - int p = previous.getRGB(i, j); + int c = current.getRGB(i, j); + int p = previous.getRGB(i, j); - int rgb = combinePixels(c, p); + int rgb = combinePixels(c, p); - int cr = (rgb & 0x00ff0000) >> 16; - int cg = (rgb & 0x0000ff00) >> 8; - int cb = (rgb & 0x000000ff); + int cr = (rgb & 0x00ff0000) >> 16; + int cg = (rgb & 0x0000ff00) >> 8; + int cb = (rgb & 0x000000ff); - int max = Math.max(Math.max(cr, cg), cb); + int max = Math.max(Math.max(cr, cg), cb); - if (max > threshold) { + if (max > threshold) { - if (!motion) { - executor.submit(new Changer()); - motion = true; - } - - strength++; // unit = 1 / px^2 + if (motion.compareAndSet(false, true)) { + EXECUTOR.submit(new Revert()); } + + strength++; // unit = 1 / px^2 } } this.strength = strength; - - if (motion) { - notifyMotionListeners(); - } } } + if (motion.get()) { + notifyMotionListeners(); + } + previous = current; } @@ -305,7 +308,12 @@ public int getInterval() { return interval; } - public void setInterval(int interval) { + /** + * Motion check interval in milliseconds. + * + * @param interval the new motion check interval (ms) + */ + public void setCheckInterval(int interval) { this.interval = interval; } @@ -314,10 +322,10 @@ public Webcam getWebcam() { } public boolean isMotion() { - if (!running) { + if (!running.get()) { LOG.warn("Motion cannot be detected when detector is not running!"); } - return motion; + return motion.get(); } public int getMotionStrength() { @@ -363,4 +371,15 @@ private static int clamp(int c) { } return c; } + + /** + * How long motion should be valid. Value is in milliseconds. If less than + * 0, then inertia is calculated as 0.5 interval value, so motion is invalid + * at the next detector tick. + * + * @param inertia the new inertia value (milliseconds) + */ + public void setInertia(int inertia) { + this.inertia = inertia; + } }