|
| 1 | +/* |
| 2 | + * Copyright (C) 2013 Greg Perry |
| 3 | + * Modified for use in GRIP to reduce heap allocations |
| 4 | + * |
| 5 | + * Licensed either under the Apache License, Version 2.0, or (at your option) |
| 6 | + * under the terms of the GNU General Public License as published by |
| 7 | + * the Free Software Foundation (subject to the "Classpath" exception), |
| 8 | + * either version 2, or any later version (collectively, the "License"); |
| 9 | + * you may not use this file except in compliance with the License. |
| 10 | + * You may obtain a copy of the License at |
| 11 | + * |
| 12 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 13 | + * http://www.gnu.org/licenses/ |
| 14 | + * http://www.gnu.org/software/classpath/license.html |
| 15 | + * |
| 16 | + * or as provided in the LICENSE.txt file that accompanied this code. |
| 17 | + * Unless required by applicable law or agreed to in writing, software |
| 18 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 19 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 20 | + * See the License for the specific language governing permissions and |
| 21 | + * limitations under the License. |
| 22 | + */ |
| 23 | + |
| 24 | +package edu.wpi.grip.core.sources; |
| 25 | + |
| 26 | +import org.bytedeco.javacpp.BytePointer; |
| 27 | +import org.bytedeco.javacpp.Loader; |
| 28 | +import org.bytedeco.javacv.Frame; |
| 29 | +import org.bytedeco.javacv.FrameConverter; |
| 30 | +import org.bytedeco.javacv.FrameGrabber; |
| 31 | +import org.bytedeco.javacv.OpenCVFrameConverter; |
| 32 | + |
| 33 | +import javax.imageio.ImageIO; |
| 34 | +import java.awt.image.BufferedImage; |
| 35 | +import java.io.ByteArrayInputStream; |
| 36 | +import java.io.IOException; |
| 37 | +import java.io.InputStream; |
| 38 | +import java.net.MalformedURLException; |
| 39 | +import java.net.URL; |
| 40 | +import java.net.URLConnection; |
| 41 | +import java.util.List; |
| 42 | +import java.util.Map; |
| 43 | + |
| 44 | +import static org.bytedeco.javacpp.opencv_core.*; |
| 45 | +import static org.bytedeco.javacpp.opencv_imgcodecs.cvDecodeImage; |
| 46 | + |
| 47 | +public class IPCameraFrameGrabber extends FrameGrabber { |
| 48 | + |
| 49 | + /* |
| 50 | + * excellent reference - http://www.jpegcameras.com/ foscam url |
| 51 | + * http://host/videostream.cgi?user=username&pwd=password |
| 52 | + * http://192.168.0.59:60/videostream.cgi?user=admin&pwd=password android ip |
| 53 | + * cam http://192.168.0.57:8080/videofeed |
| 54 | + */ |
| 55 | + |
| 56 | + private static Exception loadingException = null; |
| 57 | + |
| 58 | + public static void tryLoad() throws Exception { |
| 59 | + if (loadingException != null) { |
| 60 | + throw loadingException; |
| 61 | + } else { |
| 62 | + try { |
| 63 | + Loader.load(org.bytedeco.javacpp.opencv_highgui.class); |
| 64 | + } catch (Throwable t) { |
| 65 | + throw loadingException = new Exception("Failed to load " + IPCameraFrameGrabber.class, t); |
| 66 | + } |
| 67 | + } |
| 68 | + } |
| 69 | + |
| 70 | + private URL url; |
| 71 | + |
| 72 | + private URLConnection connection; |
| 73 | + private InputStream input; |
| 74 | + private byte[] pixelBuffer = new byte[1024]; |
| 75 | + private Map<String, List<String>> headerfields; |
| 76 | + private String boundryKey; |
| 77 | + private IplImage decoded = null; |
| 78 | + private FrameConverter<IplImage> converter = new OpenCVFrameConverter.ToIplImage(); |
| 79 | + |
| 80 | + public IPCameraFrameGrabber(String urlstr) throws MalformedURLException { |
| 81 | + url = new URL(urlstr); |
| 82 | + } |
| 83 | + |
| 84 | + @Override |
| 85 | + public void start() { |
| 86 | + |
| 87 | + try { |
| 88 | + connection = url.openConnection(); |
| 89 | + headerfields = connection.getHeaderFields(); |
| 90 | + if (headerfields.containsKey("Content-Type")) { |
| 91 | + List<String> ct = headerfields.get("Content-Type"); |
| 92 | + for (int i = 0; i < ct.size(); ++i) { |
| 93 | + String key = ct.get(i); |
| 94 | + int j = key.indexOf("boundary="); |
| 95 | + if (j != -1) { |
| 96 | + boundryKey = key.substring(j + 9); // FIXME << fragile |
| 97 | + } |
| 98 | + } |
| 99 | + } |
| 100 | + input = connection.getInputStream(); |
| 101 | + } catch (IOException e) { |
| 102 | + e.printStackTrace(); |
| 103 | + } |
| 104 | + } |
| 105 | + |
| 106 | + @Override |
| 107 | + public void stop() throws Exception { |
| 108 | + try { |
| 109 | + input.close(); |
| 110 | + input = null; |
| 111 | + connection = null; |
| 112 | + url = null; |
| 113 | + if (decoded != null) { |
| 114 | + cvReleaseImage(decoded); |
| 115 | + } |
| 116 | + } catch (IOException e) { |
| 117 | + throw new Exception(e.getMessage(), e); |
| 118 | + } |
| 119 | + } |
| 120 | + |
| 121 | + @Override |
| 122 | + public void trigger() throws Exception { |
| 123 | + } |
| 124 | + |
| 125 | + @Override |
| 126 | + public Frame grab() throws Exception { |
| 127 | + try { |
| 128 | + byte[] b = readImage(); |
| 129 | + CvMat mat = cvMat(1, b.length, CV_8UC1, new BytePointer(b)); |
| 130 | + if (decoded != null) { |
| 131 | + cvReleaseImage(decoded); |
| 132 | + } |
| 133 | + return converter.convert(decoded = cvDecodeImage(mat)); |
| 134 | + } catch (IOException e) { |
| 135 | + throw new Exception(e.getMessage(), e); |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + public BufferedImage grabBufferedImage() throws IOException { |
| 140 | + BufferedImage bi = ImageIO.read(new ByteArrayInputStream(readImage())); |
| 141 | + return bi; |
| 142 | + } |
| 143 | + |
| 144 | + byte[] readImage() throws IOException { |
| 145 | + StringBuffer sb = new StringBuffer(); |
| 146 | + int c; |
| 147 | + // read http subheader |
| 148 | + while ((c = input.read()) != -1) { |
| 149 | + if (c > 0) { |
| 150 | + sb.append((char) c); |
| 151 | + if (c == 13) { |
| 152 | + sb.append((char) input.read());// '10'+ |
| 153 | + c = input.read(); |
| 154 | + sb.append((char) c); |
| 155 | + if (c == 13) { |
| 156 | + sb.append((char) input.read());// '10' |
| 157 | + break; // done with subheader |
| 158 | + } |
| 159 | + |
| 160 | + } |
| 161 | + } |
| 162 | + } |
| 163 | + // find embedded jpeg in stream |
| 164 | + String subheader = sb.toString(); |
| 165 | + //log.debug(subheader); |
| 166 | + int contentLength = -1; |
| 167 | + // if (boundryKey == null) |
| 168 | + // { |
| 169 | + // Yay! - server was nice and sent content length |
| 170 | + int c0 = subheader.indexOf("Content-Length: "); |
| 171 | + int c1 = subheader.indexOf('\r', c0); |
| 172 | + |
| 173 | + if (c0 < 0) { |
| 174 | + //log.info("no content length returning null"); |
| 175 | + return null; |
| 176 | + } |
| 177 | + |
| 178 | + c0 += 16; |
| 179 | + contentLength = Integer.parseInt(subheader.substring(c0, c1).trim()); |
| 180 | + //log.debug("Content-Length: " + contentLength); |
| 181 | + |
| 182 | + // adaptive size - careful - don't want a 2G jpeg |
| 183 | + ensureBufferCapacity(contentLength); |
| 184 | + |
| 185 | + while (input.available() < contentLength) ; |
| 186 | + input.read(pixelBuffer, 0, contentLength); |
| 187 | + input.read();// \r |
| 188 | + input.read();// \n |
| 189 | + input.read();// \r |
| 190 | + input.read();// \n |
| 191 | + |
| 192 | + return pixelBuffer; |
| 193 | + } |
| 194 | + |
| 195 | + /** |
| 196 | + * Grow the pixel buffer if necessary. Using this method instead of allocating a new buffer every time a frame |
| 197 | + * is grabbed improves performance by reducing the frequency of garbage collections. In a simple test, the |
| 198 | + * unmodified version of IPCameraFrameGrabber caused about 200MB of allocations within 13 seconds. In this |
| 199 | + * version, almost no additional heap space is typically allocated per frame. |
| 200 | + * <p> |
| 201 | + * The downside to this is that the returned frames can't really be modified, so this probably won't go upstream, |
| 202 | + * but it's useful for us because in GRIP we don't operate on images in-place. |
| 203 | + */ |
| 204 | + private void ensureBufferCapacity(int desiredCapacity) { |
| 205 | + int capacity = pixelBuffer.length; |
| 206 | + |
| 207 | + while (capacity < desiredCapacity) { |
| 208 | + capacity *= 2; |
| 209 | + } |
| 210 | + |
| 211 | + if (capacity > pixelBuffer.length) { |
| 212 | + pixelBuffer = new byte[capacity]; |
| 213 | + } |
| 214 | + } |
| 215 | + |
| 216 | + @Override |
| 217 | + public void release() throws Exception { |
| 218 | + } |
| 219 | + |
| 220 | +} |
0 commit comments