Skip to content

Commit 9e51025

Browse files
committed
Fix CameraSource for headless mode
This includes a modified version of IPCameraFrameGrabber with much better memory usage as well as a fix for loading camera sources from files.
1 parent 29b5619 commit 9e51025

File tree

2 files changed

+223
-1
lines changed

2 files changed

+223
-1
lines changed

core/src/main/java/edu/wpi/grip/core/sources/CameraSource.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ public Properties getProperties() {
103103

104104
@Override
105105
public void createFromProperties(EventBus eventBus, Properties properties) throws IOException {
106+
this.properties = properties;
107+
106108
final String deviceNumberProperty = properties.getProperty(DEVICE_NUMBER_PROPERTY);
107109
final String addressProperty = properties.getProperty(ADDRESS_PROPERTY);
108110

@@ -167,7 +169,7 @@ private synchronized void startVideo(FrameGrabber grabber) throws IOException {
167169
frameRateOutputSocket.setValue(1000 / (thisMoment - lastFrame));
168170
lastFrame = thisMoment;
169171
}
170-
});
172+
}, "Camera");
171173
frameExecutor.setUncaughtExceptionHandler(
172174
(thread, exception) -> {
173175
// TODO Pass Exception to the UI.
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
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

Comments
 (0)