diff --git a/pom.xml b/pom.xml index 45816021..81736e7f 100644 --- a/pom.xml +++ b/pom.xml @@ -31,16 +31,6 @@ webcam-capture ${version}-SNAPSHOT - - org.slf4j - slf4j-api - 1.6.4 - - - ch.qos.logback - logback-classic - 0.9.18 - diff --git a/webcam-capture-driver-civil/.classpath b/webcam-capture-driver-civil/.classpath index 8fe0193e..f32149e8 100644 --- a/webcam-capture-driver-civil/.classpath +++ b/webcam-capture-driver-civil/.classpath @@ -1,8 +1,31 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/webcam-capture-driver-ipcam/.classpath b/webcam-capture-driver-ipcam/.classpath index 756245a6..c29db95a 100644 --- a/webcam-capture-driver-ipcam/.classpath +++ b/webcam-capture-driver-ipcam/.classpath @@ -1,7 +1,31 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/webcam-capture-driver-ipcam/README.md b/webcam-capture-driver-ipcam/README.md new file mode 100644 index 00000000..1dec6a8b --- /dev/null +++ b/webcam-capture-driver-ipcam/README.md @@ -0,0 +1,100 @@ +# webcam-capture-driver-ipcam + +This is IP camera driver for Webcam Capture project. It allows Webcam Capture to +handle pictures from IP cameras supporting JPEG and MJPEG (Motion JPEG) compression +and therefore can work in two modes - ```PULL``` for JPEG and ```PUSH``` for MJPEG. + +What are the differences between those two modes: + +* ```PULL``` - will request new JPEG image each time when it is required, +* ```PUSH``` - stream Motion JPEG in real time and serve newest image on-demand. + +Support for few IP cameras is build in and the list includes: + +* [IP Robocam 641](http://www.marmitek.com/en/product-details/home-automation-security/ip-cameras/ip-robocam-641.php) by [Marmitek](http://www.marmitek.com/) (MJPEG) +* [Speed Dome X104S](http://www.ipcctv.com/product.php?xProd=10&xSec=26) by [Xvision](http://www.ipcctv.com/) (MJPEG) +* [B7210](http://www.zavio.com/product.php?id=45) by [Zavio](http://www.zavio.com/) (JPEG) +* [F3201](http://www.zavio.com/product.php?id=28) by [Zavio](http://www.zavio.com/) (JPEG) + +## Code Examples + +Example of how to display image from B7210 bullet IP camera by [Zavio](http://www.zavio.com/product.php?id=45) +in ```JPanel``` inside ```JFrame``` window ([QVGA](http://en.wikipedia.org/wiki/Graphics_display_resolution#QVGA_.28320.C3.97240.29) +image size is used). + +```java +IpCamDevice ipcam = new B7210("B7210", "114.32.216.24"); +ipcam.setAuth(new IpCamAuth("demo", "demo")); +ipcam.setSize(B7210.SIZE_QVGA); + +IpCamDriver driver = new IpCamDriver(); +driver.register(ipcam); + +Webcam.setDriver(driver); + +WebcamPanel panel = new WebcamPanel(Webcam.getDefault()); +panel.setFPS(0.5); // 1 frame per 2 seconds + +JFrame f = new JFrame(); +f.add(panel); +f.pack(); +f.setVisible(true); +f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); +``` + +Example of how to handle image from *any* IP camera supporting JPEG compression: + +```java +String address = "http://www.dasding.de/ext/webcam/webcam770.php?cam=1"; +IpCamDevice livecam = new IpCamDevice("dasding", new URL(address), IpCamMode.PULL); + +IpCamDriver driver = new IpCamDriver(); +driver.register(livecam); + +Webcam.setDriver(driver); + +WebcamPanel panel = new WebcamPanel(Webcam.getWebcams().get(0)); +panel.setFPS(5); + +JFrame f = new JFrame("Dasding Studio Live IP Camera"); +f.add(panel); +f.pack(); +f.setVisible(true); +f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); +``` + +Example of how to handle image from *any* IP camera supporting MJPEG compression. +Here we are handling stream from the beach of Lignano (Italy) from an AXIS 213 +PTZ Network Camera. + +```java +String address = "http://88.37.116.138/mjpg/video.mjpg "; +IpCamDevice livecam = new IpCamDevice("Lignano Beach", new URL(address), IpCamMode.PUSH); + +IpCamDriver driver = new IpCamDriver(); +driver.register(livecam); + +Webcam.setDriver(driver); + +WebcamPanel panel = new WebcamPanel(Webcam.getWebcams().get(0)); +panel.setFPS(1); + +JFrame f = new JFrame("Live Views From Lignano Beach (Italy)"); +f.add(panel); +f.pack(); +f.setVisible(true); +f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); +``` + +## 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-driver-ipcam/bin/.classpath b/webcam-capture-driver-ipcam/bin/.classpath deleted file mode 100644 index 293efd69..00000000 --- a/webcam-capture-driver-ipcam/bin/.classpath +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/webcam-capture-driver-ipcam/bin/.project b/webcam-capture-driver-ipcam/bin/.project deleted file mode 100644 index f6dee771..00000000 --- a/webcam-capture-driver-ipcam/bin/.project +++ /dev/null @@ -1,23 +0,0 @@ - - - webcam-capture-driver-ipcam - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.m2e.core.maven2Builder - - - - - - org.eclipse.m2e.core.maven2Nature - org.eclipse.jdt.core.javanature - - diff --git a/webcam-capture-driver-ipcam/bin/pom.xml b/webcam-capture-driver-ipcam/bin/pom.xml deleted file mode 100644 index 4b4ee5e5..00000000 --- a/webcam-capture-driver-ipcam/bin/pom.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - 4.0.0 - - - com.github.sarxos - webcam-capture-parent - 0.3.4 - - - webcam-capture-driver-ipcam - Webcam Capture - IP Camera Driver - Webcam Capture driver to be used for IP cameras - - \ No newline at end of file diff --git a/webcam-capture-driver-ipcam/pom.xml b/webcam-capture-driver-ipcam/pom.xml index 083372fd..c605e332 100644 --- a/webcam-capture-driver-ipcam/pom.xml +++ b/webcam-capture-driver-ipcam/pom.xml @@ -13,6 +13,14 @@ Webcam Capture driver to be used for IP cameras + + com.github.sarxos webcam-capture @@ -30,4 +38,17 @@ + + + + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + + + + + \ No newline at end of file diff --git a/webcam-capture-driver-ipcam/src/etc/resources/dasding-live.png b/webcam-capture-driver-ipcam/src/etc/resources/dasding-live.png new file mode 100644 index 00000000..7eb7e7df Binary files /dev/null and b/webcam-capture-driver-ipcam/src/etc/resources/dasding-live.png differ diff --git a/webcam-capture-driver-ipcam/src/etc/resources/lignano-beach.png b/webcam-capture-driver-ipcam/src/etc/resources/lignano-beach.png new file mode 100644 index 00000000..27ab53a6 Binary files /dev/null and b/webcam-capture-driver-ipcam/src/etc/resources/lignano-beach.png differ diff --git a/webcam-capture-driver-ipcam/src/etc/resources/night-tree.png b/webcam-capture-driver-ipcam/src/etc/resources/night-tree.png new file mode 100644 index 00000000..1ff47708 Binary files /dev/null and b/webcam-capture-driver-ipcam/src/etc/resources/night-tree.png differ diff --git a/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/IpCamDevice.java b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/IpCamDevice.java index fb44b16d..1831c6f4 100644 --- a/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/IpCamDevice.java +++ b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/IpCamDevice.java @@ -2,13 +2,28 @@ import java.awt.Dimension; import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import javax.imageio.ImageIO; +import org.apache.http.Header; import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScope; +import org.apache.http.client.AuthCache; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.protocol.ClientContext; +import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.impl.client.BasicAuthCache; +import org.apache.http.protocol.BasicHttpContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.github.sarxos.webcam.WebcamDevice; import com.github.sarxos.webcam.WebcamException; @@ -24,31 +39,137 @@ */ public class IpCamDevice implements WebcamDevice { + /** + * Logger. + */ + private static final Logger LOG = LoggerFactory.getLogger(IpCamDevice.class); + + private final class PushImageReader implements Runnable { + + private final Object lock = new Object(); + private IpCamMJPEGStream stream = null; + private BufferedImage image = null; + private boolean running = true; + private WebcamException exception = null; + + public PushImageReader(InputStream is) { + stream = new IpCamMJPEGStream(is); + } + + @Override + public void run() { + while (running) { + + if (stream.isClosed()) { + break; + } + + try { + + LOG.trace("Reading MJPEG frame"); + + BufferedImage image = stream.readFrame(); + + if (image != null) { + this.image = image; + synchronized (lock) { + lock.notifyAll(); + } + } + + } catch (IOException e) { + + // case when someone manually closed stream, do not log + // exception, this is normal behavior + if (stream.isClosed()) { + return; + } + + LOG.error("Cannot read MJPEG frame", e); + + if (failOnError) { + exception = new WebcamException("Cannot read MJPEG frame", e); + throw exception; + } + } + } + + try { + stream.close(); + } catch (IOException e) { + LOG.debug("Some nasty exception when closing MJPEG stream", e); + } + + } + + public BufferedImage getImage() { + if (exception != null) { + throw exception; + } + try { + if (image == null) { + synchronized (lock) { + lock.wait(); + } + } + } catch (InterruptedException e) { + throw new WebcamException("Reader thread interrupted", e); + } + return image; + } + + public void stop() { + running = false; + } + } + private String name = null; private URL url = null; private IpCamMode mode = null; private IpCamAuth auth = null; - private IpCamHttpClient client = IpCamHttpClient.getIstance(); + private IpCamHttpClient client = new IpCamHttpClient(); + private PushImageReader pushReader = null; + private boolean open = false; + private boolean failOnError = false; - public IpCamDevice(String name, URL url) { - this(name, url, IpCamMode.PULL); - } + private Dimension[] sizes = null; + private Dimension size = null; public IpCamDevice(String name, URL url, IpCamMode mode) { - this(name, url, mode, IpCamAuth.NONE); + this(name, url, mode, null); } public IpCamDevice(String name, URL url, IpCamMode mode, IpCamAuth auth) { + if (name == null) { throw new IllegalArgumentException("Name cannot be null"); } - if (url == null) { - throw new IllegalArgumentException("URL cannot be null"); - } + this.name = name; this.url = url; this.mode = mode; this.auth = auth; + + if (auth != null) { + AuthScope scope = new AuthScope(new HttpHost(url.toString())); + client.getCredentialsProvider().setCredentials(scope, auth); + } + } + + protected static final URL toURL(String url) { + + String base = null; + if (url.startsWith("http://")) { + base = url; + } else { + base = String.format("http://%s", url); + } + + try { + return new URL(base); + } catch (MalformedURLException e) { + throw new WebcamException(String.format("Incorrect URL '%s'", url), e); + } } @Override @@ -58,52 +179,201 @@ public String getName() { @Override public Dimension[] getSizes() { - // TODO Auto-generated method stub - return null; + + if (sizes != null) { + return sizes; + } + + if (!open) { + open(); + } + + BufferedImage img = getImage(); + int w = img.getWidth(); + int h = img.getHeight(); + + sizes = new Dimension[] { new Dimension(w, h) }; + + close(); + + return sizes; + } + + protected void setSizes(Dimension[] sizes) { + this.sizes = sizes; } @Override public Dimension getSize() { - // TODO Auto-generated method stub - return null; + if (size == null) { + size = getSizes()[0]; + } + return size; } @Override public void setSize(Dimension size) { - // TODO Auto-generated method stub - + this.size = size; } @Override public BufferedImage getImage() { - HttpGet get = null; - try { - get = new HttpGet(url.toURI()); - HttpResponse respone = client.execute(get); - HttpEntity entity = respone.getEntity(); - return ImageIO.read(entity.getContent()); - } catch (Exception e) { - throw new WebcamException("Cannot download image", e); - } finally { - if (get != null) { - get.releaseConnection(); + + if (!open) { + throw new WebcamException("IpCam device not open"); + } + + switch (mode) { + case PULL: + return getImagePullMode(); + case PUSH: + return getImagePushMode(); + } + + throw new WebcamException(String.format("Unsupported mode %s", mode)); + } + + private BufferedImage getImagePushMode() { + + if (pushReader == null) { + + synchronized (this) { + + InputStream is = null; + + URI uri = null; + + try { + uri = getURL().toURI(); + } catch (URISyntaxException e) { + throw new WebcamException(String.format("Incorrect URI syntax '%s'", uri), e); + } + + BasicHttpContext context = new BasicHttpContext(); + + IpCamAuth auth = getAuth(); + if (auth != null) { + AuthCache cache = new BasicAuthCache(); + cache.put(new HttpHost(uri.getHost()), new BasicScheme()); + context.setAttribute(ClientContext.AUTH_CACHE, cache); + } + + try { + HttpGet get = new HttpGet(uri); + HttpResponse respone = client.execute(get, context); + HttpEntity entity = respone.getEntity(); + + Header ct = entity.getContentType(); + if (ct == null) { + throw new WebcamException("Content Type header is missing"); + } + + if (ct.getValue().startsWith("image/")) { + throw new WebcamException("Cannot read images in PUSH mode, change mode to PULL"); + } + + is = entity.getContent(); + + } catch (Exception e) { + throw new WebcamException("Cannot download image", e); + } + + pushReader = new PushImageReader(is); + + // TODO: change to executor + + Thread thread = new Thread(pushReader, String.format("%s-reader", getName())); + thread.setDaemon(true); + thread.start(); + } + } + + return pushReader.getImage(); + } + + private BufferedImage getImagePullMode() { + synchronized (this) { + + HttpGet get = null; + URI uri = null; + + try { + uri = getURL().toURI(); + } catch (URISyntaxException e) { + throw new WebcamException(String.format("Incorrect URI syntax '%s'", uri), e); + } + + BasicHttpContext context = new BasicHttpContext(); + + IpCamAuth auth = getAuth(); + if (auth != null) { + AuthCache cache = new BasicAuthCache(); + cache.put(new HttpHost(uri.getHost()), new BasicScheme()); + context.setAttribute(ClientContext.AUTH_CACHE, cache); + } + + try { + get = new HttpGet(uri); + + HttpResponse respone = client.execute(get, context); + HttpEntity entity = respone.getEntity(); + + Header ct = entity.getContentType(); + if (ct == null) { + throw new WebcamException("Content Type header is missing"); + } + + if (ct.getValue().startsWith("multipart/")) { + throw new WebcamException("Cannot read MJPEG stream in PULL mode, change mode to PUSH"); + } + + InputStream is = entity.getContent(); + if (is == null) { + return null; + } + + return ImageIO.read(is); + + } catch (IOException e) { + + // fall thru, it means we closed stream + if (e.getMessage().equals("closed")) { + return null; + } + + throw new WebcamException("Cannot download image", e); + + } catch (Exception e) { + throw new WebcamException("Cannot download image", e); + } finally { + if (get != null) { + get.releaseConnection(); + } } } } @Override public void open() { - // TODO Auto-generated method stub - + open = true; } @Override public void close() { - // TODO Auto-generated method stub + if (!open) { + return; + } + + if (pushReader != null) { + pushReader.stop(); + pushReader = null; + } + + open = false; } - public URL getUrl() { + public URL getURL() { return url; } @@ -114,4 +384,20 @@ public IpCamMode getMode() { public IpCamAuth getAuth() { return auth; } + + public void setAuth(IpCamAuth auth) { + if (auth != null) { + URL url = getURL(); + AuthScope scope = new AuthScope(url.getHost(), url.getPort()); + client.getCredentialsProvider().setCredentials(scope, auth); + } + } + + public void resetAuth() { + client.getCredentialsProvider().clear(); + } + + public void setFailOnError(boolean failOnError) { + this.failOnError = failOnError; + } } diff --git a/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/IpCamDriver.java b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/IpCamDriver.java index 485f0c47..692e6889 100644 --- a/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/IpCamDriver.java +++ b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/IpCamDriver.java @@ -1,19 +1,15 @@ package com.github.sarxos.webcam.ds.ipcam; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import javax.imageio.ImageIO; - import com.github.sarxos.webcam.WebcamDevice; import com.github.sarxos.webcam.WebcamDriver; import com.github.sarxos.webcam.WebcamException; +import com.github.sarxos.webcam.ds.ipcam.http.IpCamMode; /** @@ -30,28 +26,22 @@ public List getDevices() { return Collections.unmodifiableList(DEVICES); } - public void registerDevice(IpCamDevice device) { + public void register(IpCamDevice device) { + for (WebcamDevice d : DEVICES) { + String name = device.getName(); + if (d.getName().equals(name)) { + throw new WebcamException(String.format("Name '%s' is already in use", name)); + } + } DEVICES.add(device); } - public void registerURL(String name, URL url) { - DEVICES.add(new IpCamDevice(name, url)); - } - - public void registerURL(String name, String url) { + public void registerURL(String name, String url, IpCamMode mode) { try { - DEVICES.add(new IpCamDevice(name, new URL(url))); + DEVICES.add(new IpCamDevice(name, new URL(url), mode)); } catch (MalformedURLException e) { - throw new WebcamException("Incorrect URL", e); + throw new WebcamException(String.format("Incorrect URL '%s'", url), e); } } - public static void main(String[] args) throws IOException { - IpCamDriver driver = new IpCamDriver(); - driver.registerURL("Test", "http://www.dasding.de/ext/webcam/webcam770.php?cam=1"); - WebcamDevice device = driver.getDevices().get(0); - BufferedImage image = device.getImage(); - ImageIO.write(image, "jpg", new File("ninonap.jpg")); - } - } diff --git a/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/IpCamMJPEGStream.java b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/IpCamMJPEGStream.java new file mode 100644 index 00000000..71edf3ed --- /dev/null +++ b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/IpCamMJPEGStream.java @@ -0,0 +1,140 @@ +package com.github.sarxos.webcam.ds.ipcam; + +import java.awt.image.BufferedImage; +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import javax.imageio.ImageIO; + + +public class IpCamMJPEGStream extends DataInputStream { + + /** + * The first two bytes of every JPEG stream are the Start Of Image (SOI) + * marker values FFh D8h. + */ + private final byte[] SOI_MARKER = { (byte) 0xFF, (byte) 0xD8 }; + + /** + * All JPEG data streams end with the End Of Image (EOI) marker values FFh + * D9h. + */ + private final byte[] EOI_MARKER = { (byte) 0xFF, (byte) 0xD9 }; + + /** + * Name of content length header. + */ + private final String CONTENT_LENGTH = "Content-Length"; + + /** + * Maximum header length. + */ + private final static int HEADER_MAX_LENGTH = 100; + + /** + * Max frame length (100kB). + */ + private final static int FRAME_MAX_LENGTH = 100000 + HEADER_MAX_LENGTH; + + private boolean open = true; + + public IpCamMJPEGStream(InputStream in) { + super(new BufferedInputStream(in, FRAME_MAX_LENGTH)); + } + + private int getEndOfSeqeunce(DataInputStream in, byte[] sequence) throws IOException { + int s = 0; + byte c; + for (int i = 0; i < FRAME_MAX_LENGTH; i++) { + c = (byte) in.readUnsignedByte(); + if (c == sequence[s]) { + s++; + if (s == sequence.length) { + return i + 1; + } + } else { + s = 0; + } + } + return -1; + } + + private int getStartOfSequence(DataInputStream in, byte[] sequence) throws IOException { + int end = getEndOfSeqeunce(in, sequence); + return end < 0 ? -1 : end - sequence.length; + } + + private int parseContentLength(byte[] headerBytes) throws IOException, NumberFormatException { + + ByteArrayInputStream bais = new ByteArrayInputStream(headerBytes); + InputStreamReader isr = new InputStreamReader(bais); + BufferedReader br = new BufferedReader(isr); + + String line = null; + while ((line = br.readLine()) != null) { + if (line.startsWith(CONTENT_LENGTH)) { + String[] parts = line.split(":"); + if (parts.length == 2) { + return Integer.parseInt(parts[1].trim()); + } + } + } + + return 0; + } + + public BufferedImage readFrame() throws IOException { + + if (!open) { + return null; + } + + byte[] header = null; + byte[] frame = null; + + mark(FRAME_MAX_LENGTH); + + int n = getStartOfSequence(this, SOI_MARKER); + + reset(); + + header = new byte[n]; + + readFully(header); + + int length = -1; + try { + length = parseContentLength(header); + } catch (NumberFormatException e) { + length = getEndOfSeqeunce(this, EOI_MARKER); + } + + reset(); + + frame = new byte[length]; + + skipBytes(n); + readFully(frame); + + try { + return ImageIO.read(new ByteArrayInputStream(frame)); + } catch (IOException e) { + return null; + } + } + + @Override + public void close() throws IOException { + open = false; + super.close(); + } + + public boolean isClosed() { + return !open; + } +} \ No newline at end of file diff --git a/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/TestGitExc.java b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/TestGitExc.java new file mode 100644 index 00000000..5f4238f1 --- /dev/null +++ b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/TestGitExc.java @@ -0,0 +1,51 @@ +package com.github.sarxos.webcam.ds.ipcam; + +import java.io.IOException; +import java.net.URL; + +import javax.swing.JFrame; + +import com.github.sarxos.webcam.Webcam; +import com.github.sarxos.webcam.WebcamPanel; +import com.github.sarxos.webcam.ds.ipcam.http.IpCamHttpClient; +import com.github.sarxos.webcam.ds.ipcam.http.IpCamMode; + + +public class TestGitExc { + + public static void main(String[] args) throws IOException, InterruptedException { + + System.setProperty(IpCamHttpClient.PROXY_HOST_KEY, "global.proxy.lucent.com"); + System.setProperty(IpCamHttpClient.PROXY_PORT_KEY, "8000"); + + // WebcamLogConfigurator.configure("src/test/resources/logback.xml"); + + // IpCamDevice da = new X104S("X104S", "bikersschool.dyndns.org"); + // da.setSize(X104S.SIZE_VGA); + // + // IpCamDevice db = new IPRobocam641("IPRobocam641", + // "iprobocam.marmitek.com"); + // db.setAuth(new IpCamAuth("user", "user")); + // + // IpCamDevice dc = new B7210("B7210", "114.32.216.24"); + // dc.setAuth(new IpCamAuth("demo", "demo")); + // dc.setSize(B7210.SIZE_QVGA); + + String address = "http://88.37.116.138/mjpg/video.mjpg "; + IpCamDevice livecam = new IpCamDevice("Lignano Beach", new URL(address), IpCamMode.PUSH); + + IpCamDriver driver = new IpCamDriver(); + driver.register(livecam); + + Webcam.setDriver(driver); + + WebcamPanel panel = new WebcamPanel(Webcam.getWebcams().get(0)); + panel.setFPS(1); + + JFrame f = new JFrame("Live Views From Lignano Beach (Italy)"); + f.add(panel); + f.pack(); + f.setVisible(true); + f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + } +} diff --git a/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/device/marmitek/IPRobocam641.java b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/device/marmitek/IPRobocam641.java new file mode 100644 index 00000000..220fc5d8 --- /dev/null +++ b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/device/marmitek/IPRobocam641.java @@ -0,0 +1,34 @@ +package com.github.sarxos.webcam.ds.ipcam.device.marmitek; + +import java.net.MalformedURLException; +import java.net.URL; + +import com.github.sarxos.webcam.WebcamException; +import com.github.sarxos.webcam.ds.ipcam.IpCamDevice; +import com.github.sarxos.webcam.ds.ipcam.http.IpCamMode; + + +public class IPRobocam641 extends IpCamDevice { + + private URL base = null; + + public IPRobocam641(String name, String urlBase) { + this(name, toURL(urlBase)); + } + + public IPRobocam641(String name, URL base) { + super(name, null, IpCamMode.PUSH); + this.base = base; + } + + @Override + public URL getURL() { + String url = String.format("%s/cgi/mjpg/mjpg.cgi", base); + try { + return new URL(url); + } catch (MalformedURLException e) { + throw new WebcamException(String.format("Incorrect URL %s", url), e); + } + } + +} diff --git a/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/device/xvision/X104S.java b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/device/xvision/X104S.java new file mode 100644 index 00000000..95e84344 --- /dev/null +++ b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/device/xvision/X104S.java @@ -0,0 +1,103 @@ +package com.github.sarxos.webcam.ds.ipcam.device.xvision; + +import java.awt.Dimension; +import java.net.MalformedURLException; +import java.net.URL; + +import com.github.sarxos.webcam.WebcamException; +import com.github.sarxos.webcam.ds.ipcam.IpCamDevice; +import com.github.sarxos.webcam.ds.ipcam.http.IpCamMode; + + +/** + * Speed Dome X104S IP Camera by XVision. + * + * @author Bartosz Firyn (SarXos) + */ +public class X104S extends IpCamDevice { + + public static final Dimension SIZE_SXGA = new Dimension(1280, 1024); + public static final Dimension SIZE_VGA = new Dimension(640, 480); + public static final Dimension SIZE_QVGA = new Dimension(320, 240); + public static final Dimension SIZE_QQVGA = new Dimension(160, 128); + + //@formatter:off + private static final Dimension[] SIZES = new Dimension[] { + SIZE_SXGA, + SIZE_VGA, + SIZE_QVGA, + SIZE_QQVGA, + }; + //@formatter:on + + private URL base = null; + + public X104S(String name, String urlBase) { + this(name, toURL(urlBase)); + } + + public X104S(String name, URL base) { + super(name, null, IpCamMode.PUSH); + this.base = base; + } + + @Override + public Dimension[] getSizes() { + return SIZES; + } + + @Override + public void setSize(Dimension size) { + + int index = -1; + for (int i = 0; i < SIZES.length; i++) { + if (SIZES[i].equals(size)) { + index = i; + break; + } + } + + if (index == -1) { + throw new IllegalArgumentException(String.format("Incorrect size %s", size)); + } + + super.setSize(size); + } + + @Override + public URL getURL() { + + int index = -1; + for (int i = 0; i < SIZES.length; i++) { + if (SIZES[i].equals(getSize())) { + index = i; + break; + } + } + + String r = ""; + switch (index) { + case 0: + r = "sxga"; + break; + case 1: + r = "vga"; + break; + case 2: + r = "qvga"; + break; + case 3: + r = "qqvga"; + break; + } + + String url = String.format("%s/video.cgi?resolution=%s&random=0.%s", base, r, System.currentTimeMillis()); + + try { + return new URL(url); + } catch (MalformedURLException e) { + throw new WebcamException(String.format("Incorrect URL %s", url), e); + } + } + +} diff --git a/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/device/zavio/B7210.java b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/device/zavio/B7210.java new file mode 100644 index 00000000..ff3cf60e --- /dev/null +++ b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/device/zavio/B7210.java @@ -0,0 +1,99 @@ +package com.github.sarxos.webcam.ds.ipcam.device.zavio; + +import java.awt.Dimension; +import java.net.MalformedURLException; +import java.net.URL; + +import com.github.sarxos.webcam.WebcamException; +import com.github.sarxos.webcam.ds.ipcam.IpCamDevice; +import com.github.sarxos.webcam.ds.ipcam.http.IpCamMode; + + +/** + * B7210 2M Bullet IP Camera from Zavio. + * + * @author Bartosz Firyn (SarXos) + */ +public class B7210 extends IpCamDevice { + + public static final Dimension SIZE_HD_1080 = new Dimension(1280, 1024); + public static final Dimension SIZE_43_960 = new Dimension(1280, 960); + public static final Dimension SIZE_QVGA = new Dimension(320, 240); + + //@formatter:off + private static final Dimension[] SIZES = new Dimension[] { + SIZE_HD_1080, + SIZE_43_960, + SIZE_QVGA, + }; + //@formatter:on + + private URL base = null; + + public B7210(String name, String urlBase) { + this(name, toURL(urlBase)); + } + + public B7210(String name, URL base) { + super(name, null, IpCamMode.PULL); + this.base = base; + } + + @Override + public Dimension[] getSizes() { + return SIZES; + } + + @Override + public void setSize(Dimension size) { + + int index = -1; + for (int i = 0; i < SIZES.length; i++) { + if (SIZES[i].equals(size)) { + index = i; + break; + } + } + + if (index == -1) { + throw new IllegalArgumentException(String.format("Incorrect size %s", size)); + } + + super.setSize(size); + } + + @Override + public URL getURL() { + + int index = -1; + for (int i = 0; i < SIZES.length; i++) { + if (SIZES[i].equals(getSize())) { + index = i; + break; + } + } + + int res = 0; + switch (index) { + case 0: + res = 0; + break; + case 1: + res = 3; + break; + case 2: + res = 4; + break; + } + + long time = System.currentTimeMillis(); + + String url = String.format("%s/cgi-bin/view/image?pro_%d&%d", base, res, time); + try { + return new URL(url); + } catch (MalformedURLException e) { + throw new WebcamException(String.format("Incorrect URL %s", url), e); + } + } + +} diff --git a/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/device/zavio/F3201.java b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/device/zavio/F3201.java new file mode 100644 index 00000000..7cef3238 --- /dev/null +++ b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/device/zavio/F3201.java @@ -0,0 +1,99 @@ +package com.github.sarxos.webcam.ds.ipcam.device.zavio; + +import java.awt.Dimension; +import java.net.MalformedURLException; +import java.net.URL; + +import com.github.sarxos.webcam.WebcamException; +import com.github.sarxos.webcam.ds.ipcam.IpCamDevice; +import com.github.sarxos.webcam.ds.ipcam.http.IpCamMode; + + +/** + * F3201 Compact IP Camera from Zavio. + * + * @author Bartosz Firyn (SarXos) + */ +public class F3201 extends IpCamDevice { + + public static final Dimension SIZE_HD_1080 = new Dimension(1280, 1024); + public static final Dimension SIZE_HD_720 = new Dimension(1280, 720); + public static final Dimension SIZE_QVGA = new Dimension(320, 240); + + //@formatter:off + private static final Dimension[] SIZES = new Dimension[] { + SIZE_HD_1080, + SIZE_HD_720, + SIZE_QVGA, + }; + //@formatter:on + + private URL base = null; + + public F3201(String name, String urlBase) { + this(name, toURL(urlBase)); + } + + public F3201(String name, URL base) { + super(name, null, IpCamMode.PULL); + this.base = base; + } + + @Override + public Dimension[] getSizes() { + return SIZES; + } + + @Override + public void setSize(Dimension size) { + + int index = -1; + for (int i = 0; i < SIZES.length; i++) { + if (SIZES[i].equals(size)) { + index = i; + break; + } + } + + if (index == -1) { + throw new IllegalArgumentException(String.format("Incorrect size %s", size)); + } + + super.setSize(size); + } + + @Override + public URL getURL() { + + int index = -1; + for (int i = 0; i < SIZES.length; i++) { + if (SIZES[i].equals(getSize())) { + index = i; + break; + } + } + + int profile = 0; + switch (index) { + case 0: + profile = 0; + break; + case 1: + profile = 3; + break; + case 2: + profile = 4; + break; + } + + long time = System.currentTimeMillis(); + + String url = String.format("%s/cgi-bin/view/image?pro_%d&%d", base, profile, time); + try { + return new URL(url); + } catch (MalformedURLException e) { + throw new WebcamException(String.format("Incorrect URL %s", url), e); + } + } + +} diff --git a/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/http/IpCamAuth.java b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/http/IpCamAuth.java index a057f9de..220d5d5e 100644 --- a/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/http/IpCamAuth.java +++ b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/http/IpCamAuth.java @@ -1,25 +1,13 @@ package com.github.sarxos.webcam.ds.ipcam.http; -/** - * HTTP authentication type. - * - * @author Bartosz Firyn (SarXos) - */ -public enum IpCamAuth { +import org.apache.http.auth.UsernamePasswordCredentials; - /** - * No authentication. - */ - NONE, - /** - * Basic auth. - */ - BASIC, +public class IpCamAuth extends UsernamePasswordCredentials { - /** - * WSSE auth. - */ - WSSE, + private static final long serialVersionUID = 807247154917333425L; + public IpCamAuth(String user, String password) { + super(user, password); + } } diff --git a/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/http/IpCamHttpClient.java b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/http/IpCamHttpClient.java index 3ccb34df..079cc23b 100644 --- a/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/http/IpCamHttpClient.java +++ b/webcam-capture-driver-ipcam/src/main/java/com/github/sarxos/webcam/ds/ipcam/http/IpCamHttpClient.java @@ -1,26 +1,52 @@ package com.github.sarxos.webcam.ds.ipcam.http; -import java.awt.image.BufferedImage; -import java.net.URL; - +import org.apache.http.HttpHost; +import org.apache.http.conn.params.ConnRoutePNames; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.PoolingClientConnectionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class IpCamHttpClient extends DefaultHttpClient { - private static IpCamHttpClient istance = new IpCamHttpClient(); + /** + * Logger. + */ + private static final Logger LOG = LoggerFactory.getLogger(IpCamHttpClient.class); + + /** + * Key for the proxy host property. + */ + public static final String PROXY_HOST_KEY = "http.proxyHost"; + + /** + * Key for the proxy port number property. + */ + public static final String PROXY_PORT_KEY = "http.proxyPort"; + + private HttpHost proxy = null; public IpCamHttpClient() { + super(new PoolingClientConnectionManager()); - } - public static IpCamHttpClient getIstance() { - return istance; - } + // configure proxy if any + + String proxyHost = System.getProperty(PROXY_HOST_KEY); + String proxyPort = System.getProperty(PROXY_PORT_KEY); + + if (proxyHost != null && proxyPort != null) { - public BufferedImage getImage(URL url) { + LOG.debug("Setting proxy '{}:{}'", proxyHost, proxyPort); + + proxy = new HttpHost(proxyHost, Integer.parseInt(proxyPort), "http"); + + setProxy(proxy); + } + } - return null; + public void setProxy(HttpHost proxy) { + getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); } } diff --git a/webcam-capture-driver-ipcam/src/test/resources/logback.xml b/webcam-capture-driver-ipcam/src/test/resources/logback.xml new file mode 100644 index 00000000..8bae4eb7 --- /dev/null +++ b/webcam-capture-driver-ipcam/src/test/resources/logback.xml @@ -0,0 +1,20 @@ + + + + + + %d{yyyy-MM-dd HH:mm:ss} %-5p [%t] %c - %m%n + + + + + + + + + + + + + + diff --git a/webcam-capture-driver-javacv/.classpath b/webcam-capture-driver-javacv/.classpath index 756245a6..c9464c0f 100644 --- a/webcam-capture-driver-javacv/.classpath +++ b/webcam-capture-driver-javacv/.classpath @@ -1,7 +1,26 @@ - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/webcam-capture-driver-jmf/.classpath b/webcam-capture-driver-jmf/.classpath index 756245a6..c9464c0f 100644 --- a/webcam-capture-driver-jmf/.classpath +++ b/webcam-capture-driver-jmf/.classpath @@ -1,7 +1,26 @@ - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/webcam-capture-driver-openimaj/.classpath b/webcam-capture-driver-openimaj/.classpath index 756245a6..c9464c0f 100644 --- a/webcam-capture-driver-openimaj/.classpath +++ b/webcam-capture-driver-openimaj/.classpath @@ -1,7 +1,26 @@ - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/webcam-capture-driver-vlcj/.classpath b/webcam-capture-driver-vlcj/.classpath index 756245a6..c9464c0f 100644 --- a/webcam-capture-driver-vlcj/.classpath +++ b/webcam-capture-driver-vlcj/.classpath @@ -1,7 +1,26 @@ - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/webcam-capture/.classpath b/webcam-capture/.classpath index 2e618775..22f27c9c 100644 --- a/webcam-capture/.classpath +++ b/webcam-capture/.classpath @@ -1,11 +1,33 @@ - - - + + + + + + + + + + + + + + + + + - - + + + + + + + + + + diff --git a/webcam-capture/LICENSE.txt b/webcam-capture/LICENSE.txt new file mode 100644 index 00000000..c6908756 --- /dev/null +++ b/webcam-capture/LICENSE.txt @@ -0,0 +1,20 @@ +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/pom.xml b/webcam-capture/pom.xml index 5b27569d..71992510 100644 --- a/webcam-capture/pom.xml +++ b/webcam-capture/pom.xml @@ -87,15 +87,15 @@ bridj 0.6.1 - + org.slf4j slf4j-api - 1.6.4 + 1.7.2 - ch.qos.logback - logback-classic - 0.9.18 + ch.qos.logback + logback-classic + 1.0.7 junit @@ -290,27 +290,27 @@ 1.6 - - org.apache.maven.plugins - maven-enforcer-plugin - 1.0 - - - enforce-maven-version - - enforce - - - - - [3.0,) - you-must-run-maven-3.0-or-above - - - - - - + + org.apache.maven.plugins + maven-enforcer-plugin + 1.0 + + + enforce-maven-version + + enforce + + + + + [3.0,) + you-must-run-maven-3.0-or-above + + + + + + org.apache.felix maven-bundle-plugin diff --git a/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamPanel.java b/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamPanel.java index 04ab76d4..8cd1a33f 100644 --- a/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamPanel.java +++ b/webcam-capture/src/main/java/com/github/sarxos/webcam/WebcamPanel.java @@ -5,6 +5,9 @@ import javax.swing.JPanel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Simply implementation of JPanel allowing users to render pictures taken with @@ -16,13 +19,14 @@ public class WebcamPanel extends JPanel implements WebcamListener { private static final long serialVersionUID = 5792962512394656227L; - private int frequency = 65; // Hz + private static final Logger LOG = LoggerFactory.getLogger(WebcamPanel.class); + + private double frequency = 65; // Hz private class Repainter extends Thread { public Repainter() { setDaemon(true); - } @Override @@ -32,6 +36,9 @@ public void run() { while (webcam.isOpen()) { image = webcam.getImage(); + if (image == null) { + LOG.error("Image is null"); + } try { if (paused) { @@ -39,7 +46,9 @@ public void run() { this.wait(); } } - Thread.sleep(1000 / frequency); + + Thread.sleep((long) (1000 / frequency)); + } catch (InterruptedException e) { e.printStackTrace(); } @@ -56,14 +65,16 @@ public WebcamPanel(Webcam webcam) { this.webcam = webcam; this.webcam.addWebcamListener(this); - this.repainter = new Repainter(); - if (!webcam.isOpen()) { webcam.open(); } setPreferredSize(webcam.getViewSize()); - repainter.start(); + + if (repainter == null) { + repainter = new Repainter(); + repainter.start(); + } } @Override @@ -82,18 +93,22 @@ protected void paintComponent(Graphics g) { public void webcamOpen(WebcamEvent we) { if (repainter == null) { repainter = new Repainter(); + repainter.start(); } - repainter.start(); setPreferredSize(webcam.getViewSize()); } @Override public void webcamClosed(WebcamEvent we) { - try { - repainter.join(); + if (repainter != null) { + if (repainter.isAlive()) { + try { + repainter.join(1000); + } catch (InterruptedException e) { + throw new WebcamException("Thread interrupted", e); + } + } repainter = null; - } catch (InterruptedException e) { - e.printStackTrace(); } } @@ -107,7 +122,6 @@ public void pause() { return; } paused = true; - System.out.println("paused"); } /** @@ -121,27 +135,30 @@ public void resume() { repainter.notifyAll(); } paused = false; - System.out.println("resumed"); } /** * @return Rendering frequency (in Hz or FPS). */ - public int getFrequency() { + public double getFrequency() { return frequency; } + private static final double MIN_FREQUENCY = 0.016; // 1 frame per minute + private static final double MAX_FREQUENCY = 25; // 25 frames per second + /** - * Set rendering frequency (in Hz or FPS). Min is 1 and max is 100. + * Set rendering frequency (in Hz or FPS). Minimum frequency is 0.016 (1 + * frame per minute) and maximum is 25 (25 frames per second). * - * @param frequency + * @param frequency the frequency */ - public void setFrequency(int frequency) { - if (frequency > 100) { - frequency = 100; + public void setFPS(double frequency) { + if (frequency > MAX_FREQUENCY) { + frequency = MAX_FREQUENCY; } - if (frequency < 1) { - frequency = 1; + if (frequency < MIN_FREQUENCY) { + frequency = MIN_FREQUENCY; } this.frequency = frequency; }