All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bytedeco.javacv.IPCameraFrameGrabber Maven / Gradle / Ivy

There is a newer version: 1.5.11
Show newest version
/*
 * Copyright (C) 2013 Greg Perry
 *
 * Licensed either under the Apache License, Version 2.0, or (at your option)
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation (subject to the "Classpath" exception),
 * either version 2, or any later version (collectively, the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     http://www.gnu.org/licenses/
 *     http://www.gnu.org/software/classpath/license.html
 *
 * or as provided in the LICENSE.txt file that accompanied this code.
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.bytedeco.javacv;

import org.bytedeco.javacpp.BytePointer;
import org.bytedeco.javacpp.Loader;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.TimeUnit;

import static org.bytedeco.javacpp.opencv_core.*;
import static org.bytedeco.javacpp.opencv_imgcodecs.*;

public class IPCameraFrameGrabber extends FrameGrabber {

    /*
     * excellent reference - http://www.jpegcameras.com/ foscam url
     * http://host/videostream.cgi?user=username&pwd=password
     * http://192.168.0.59:60/videostream.cgi?user=admin&pwd=password android ip
     * cam http://192.168.0.57:8080/videofeed
     */

    private static Exception loadingException = null;

    public static void tryLoad() throws Exception {
        if (loadingException != null) {
            throw loadingException;
        } else {
            try {
                Loader.load(org.bytedeco.javacpp.opencv_highgui.class);
            } catch (Throwable t) {
                throw loadingException = new Exception("Failed to load " + IPCameraFrameGrabber.class, t);
            }
        }
    }

    private final FrameConverter converter = new OpenCVFrameConverter.ToIplImage();
    private final URL url;
    private final int connectionTimeout;
    private final int readTimeout;
    private DataInputStream input;
    private byte[] pixelBuffer = new byte[1024];
    private Mat decoded = null;

    /**
     * @param url          The URL to create the camera connection with.
     * @param startTimeout How long should this wait on the connection while trying to {@link #start()} before
     *                     timing out.
     *                     If this value is less than zero it will be ignored.
     *                     {@link URLConnection#setConnectTimeout(int)}
     * @param grabTimeout  How long should grab wait while reading the connection before timing out.
     *                     If this value is less than zero it will be ignored.
     *                     {@link URLConnection#setReadTimeout(int)}
     * @param timeUnit     The time unit to use for the connection and read timeout.
     *                     If this value is null then the start timeout and grab timeout will be ignored.
     */
    public IPCameraFrameGrabber(URL url, int startTimeout, int grabTimeout, TimeUnit timeUnit) {
        super(); // Always good practice to do this
        if (url == null) {
            throw new IllegalArgumentException("URL can not be null");
        }
        this.url = url;
        if (timeUnit != null) {
            this.connectionTimeout = toIntExact(TimeUnit.MILLISECONDS.convert(startTimeout, timeUnit));
            this.readTimeout = toIntExact(TimeUnit.MILLISECONDS.convert(grabTimeout, timeUnit));
        } else {
            this.connectionTimeout = -1;
            this.readTimeout = -1;
        }
    }

    public IPCameraFrameGrabber(String urlstr, int connectionTimeout, int readTimeout, TimeUnit timeUnit) throws MalformedURLException {
        this(new URL(urlstr), connectionTimeout, readTimeout, timeUnit);
    }

    /**
     * @param urlstr A string to be used to create the URL.
     * @throws MalformedURLException if the urlstr is a malformed URL
     * @deprecated By not setting the connection timeout and the read timeout if your network ever crashes
     * then {@link #start()} or {@link #grab()} can hang for upwards of 45 to 60 seconds before failing.
     * You should always explicitly set the connectionTimeout and readTimeout so that your application can
     * respond appropriately to a loss or failure to connect.
     */
    @Deprecated
    public IPCameraFrameGrabber(String urlstr) throws MalformedURLException {
        this(new URL(urlstr), -1, -1, null);
    }

    @Override
    public void start() throws Exception {
        try {
            /*
             * We don't need to keep a reference to the connection
             * after it is opened in the parent class.
             * It never uses it outside of start.
             */
            final URLConnection connection = url.openConnection();
            // If the class was initialized with timeout values then configure those
            if (connectionTimeout >= 0) {
                connection.setConnectTimeout(connectionTimeout);
            }
            if (readTimeout >= 0) {
                connection.setReadTimeout(readTimeout);
            }
            input = new DataInputStream(connection.getInputStream());
        } catch (IOException e) {
            throw new Exception(e.getMessage(), e);
        }
    }

    @Override
    public void stop() throws Exception {
        if (input != null) {
            try {
                input.close();
            } catch (IOException e) {
                throw new Exception(e.getMessage(), e);
            } finally {
                // Close may have failed but there's really nothing we can do about it at this point
                input = null;
                // Don't set the url to null, it may be needed to restart this object
                releaseDecoded();
            }
        }
    }

    @Override
    public void trigger() throws Exception {
    }

    @Override
    public Frame grab() throws Exception {
        try {
            final byte[] b = readImage();
            final Mat mat = new Mat(1, b.length, CV_8UC1, new BytePointer(b));
            releaseDecoded();
            return converter.convert(decoded = imdecode(mat, IMREAD_COLOR));
        } catch (IOException e) {
            throw new Exception(e.getMessage(), e);
        }
    }

    public BufferedImage grabBufferedImage() throws IOException {
        BufferedImage bi = ImageIO.read(new ByteArrayInputStream(readImage()));
        return bi;
    }

    /**
     * Ensures that if the decoded image is not null that it gets released and set to null.
     * If the image was not set to null then trying to release a null pointer will cause  a
     * segfault.
     */
    private void releaseDecoded() {
        if (decoded != null) {
            decoded.release();
            decoded = null;
        }
    }

    private byte[] readImage() throws IOException {
        final StringBuffer sb = new StringBuffer();
        int c;
        // read http subheader
        while ((c = input.read()) != -1) {
            if (c > 0) {
                sb.append((char) c);
                if (c == 13) {
                    sb.append((char) input.read());// '10'+
                    c = input.read();
                    sb.append((char) c);
                    if (c == 13) {
                        sb.append((char) input.read());// '10'
                        break; // done with subheader
                    }

                }
            }
        }
        // find embedded jpeg in stream
        /*
         * Some cameras return headers 'content-length' using different casing
         * Eg. Axis cameras return 'Content-Length:' while TrendNet cameras return 'content-length:'
         */
        final String subheader = sb.toString().toLowerCase();
        //log.debug(subheader);

        // Yay! - server was nice and sent content length
        int c0 = subheader.indexOf("content-length: ");
        final int c1 = subheader.indexOf('\r', c0);

        if (c0 < 0) {
            //log.info("no content length returning null");
            throw new EOFException("The camera stream ended unexpectedly");
        }

        c0 += 16;
        final int contentLength = Integer.parseInt(subheader.substring(c0, c1).trim());
        //log.debug("Content-Length: " + contentLength);

        // adaptive size - careful - don't want a 2G jpeg
        ensureBufferCapacity(contentLength);

        input.readFully(pixelBuffer, 0, contentLength);
        input.read();// \r
        input.read();// \n
        input.read();// \r
        input.read();// \n

        return pixelBuffer;
    }

    @Override
    public void release() throws Exception {
    }

    /**
     * Grow the pixel buffer if necessary.  Using this method instead of allocating a new buffer every time a frame
     * is grabbed improves performance by reducing the frequency of garbage collections.  In a simple test, the
     * original version of IPCameraFrameGrabber that allocated a 4096 element byte array for every read
     * caused about 200MB of allocations within 13 seconds.  In this version, almost no additional heap space
     * is typically allocated per frame.
     */
    private void ensureBufferCapacity(int desiredCapacity) {
        int capacity = pixelBuffer.length;

        while (capacity < desiredCapacity) {
            capacity *= 2;
        }

        if (capacity > pixelBuffer.length) {
            pixelBuffer = new byte[capacity];
        }
    }

    /**
     * Returns the value of the {@code long} argument;
     * throwing an exception if the value overflows an {@code int}.
     *
     * @param value the long value
     * @return the argument as an int
     * @throws ArithmeticException if the {@code argument} overflows an int
     * @see Java 8 Implementation
     */
    private static int toIntExact(long value) {
        if ((int) value != value) {
            throw new ArithmeticException("integer overflow");
        }
        return (int) value;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy