Skip to content

Commit 08a764e

Browse files
committed
Use OpenCV instead of javax.imageio to encode M-JPEG stream
javax.imageio is not supported on the embedded JRE on the roboRIO. Also, since we can convert Mats directly instead of converting them into AWT images, this is probably faster and uses less memory.
1 parent abec11c commit 08a764e

1 file changed

Lines changed: 20 additions & 33 deletions

File tree

core/src/main/java/edu/wpi/grip/core/operations/composite/PublishVideoOperation.java

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,21 @@
77
import edu.wpi.grip.core.OutputSocket;
88
import edu.wpi.grip.core.SocketHints;
99
import edu.wpi.grip.core.events.StepRemovedEvent;
10-
import org.bytedeco.javacv.FrameConverter;
11-
import org.bytedeco.javacv.Java2DFrameConverter;
12-
import org.bytedeco.javacv.OpenCVFrameConverter;
13-
14-
import javax.imageio.IIOImage;
15-
import javax.imageio.ImageIO;
16-
import javax.imageio.ImageWriteParam;
17-
import javax.imageio.ImageWriter;
18-
import java.awt.image.BufferedImage;
19-
import java.awt.image.RenderedImage;
20-
import java.io.*;
10+
import org.bytedeco.javacpp.BytePointer;
11+
import org.bytedeco.javacpp.IntPointer;
12+
13+
import java.io.DataInputStream;
14+
import java.io.DataOutputStream;
15+
import java.io.IOException;
16+
import java.io.InputStream;
2117
import java.net.ServerSocket;
2218
import java.net.Socket;
2319
import java.util.Optional;
2420
import java.util.logging.Level;
2521
import java.util.logging.Logger;
2622

2723
import static org.bytedeco.javacpp.opencv_core.Mat;
24+
import static org.bytedeco.javacpp.opencv_imgcodecs.*;
2825

2926
/**
3027
* Publish an M-JPEG stream with the protocol used by SmartDashboard and the FRC Dashboard. This allows FRC teams to
@@ -41,37 +38,31 @@ public class PublishVideoOperation implements Operation {
4138
private static final byte[] MAGIC_NUMBER = {0x01, 0x00, 0x00, 0x00};
4239

4340
private final Object imageLock = new Object();
44-
private RenderedImage image = null;
41+
private final BytePointer imagePointer = new BytePointer();
4542
private Optional<Thread> serverThread = Optional.empty();
46-
private volatile float compressionQuality = 0.5f;
4743
private volatile boolean connected = false;
4844
private volatile int numSteps = 0;
4945

5046
/**
5147
* Listens for incoming connections on port 1180 and writes JPEG data whenever there's a new frame.
5248
*/
5349
private final Runnable runServer = () -> {
54-
ByteArrayOutputStream baos = new ByteArrayOutputStream();
55-
5650
// Loop forever (or at least until the thread is interrupted). This lets us recover from the dashboard
5751
// disconnecting or the network connection going away temporarily.
5852
while (!Thread.currentThread().isInterrupted()) {
5953
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
6054
logger.info("Starting camera server");
6155

62-
ImageWriter jpegWriter = ImageIO.getImageWritersByFormatName("jpeg").next();
63-
jpegWriter.setOutput(ImageIO.createImageOutputStream(baos));
64-
65-
ImageWriteParam jpegWriteParam = jpegWriter.getDefaultWriteParam();
66-
jpegWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
67-
6856
try (Socket socket = serverSocket.accept()) {
6957
logger.info("Got connection from " + socket.getInetAddress());
7058
connected = true;
7159

7260
DataOutputStream socketOutputStream = new DataOutputStream(socket.getOutputStream());
7361
DataInputStream socketInputStream = new DataInputStream(socket.getInputStream());
7462

63+
byte[] buffer = new byte[128 * 1024];
64+
int bufferSize;
65+
7566
final int fps = socketInputStream.readInt();
7667
final int compression = socketInputStream.readInt();
7768
final int size = socketInputStream.readInt();
@@ -84,21 +75,22 @@ public class PublishVideoOperation implements Operation {
8475
long startTime = System.nanoTime();
8576

8677
while (!socket.isClosed() && !Thread.currentThread().isInterrupted()) {
87-
baos.reset();
88-
8978
// Wait for the main thread to put a new image. This happens whenever perform() is called with
9079
// a new input.
9180
synchronized (imageLock) {
9281
imageLock.wait();
93-
jpegWriteParam.setCompressionQuality(compressionQuality / 100.0f);
94-
jpegWriter.write(null, new IIOImage(image, null, null), jpegWriteParam);
82+
83+
// Copy the image data into a pre-allocated buffer, growing it if necessary
84+
bufferSize = imagePointer.limit();
85+
if (bufferSize > buffer.length) buffer = new byte[imagePointer.limit()];
86+
imagePointer.get(buffer, 0, bufferSize);
9587
}
9688

9789
// The FRC dashboard image protocol consists of a magic number, the size of the image data,
9890
// and the image data itself.
9991
socketOutputStream.write(MAGIC_NUMBER);
100-
socketOutputStream.writeInt(baos.size());
101-
baos.writeTo(socketOutputStream);
92+
socketOutputStream.writeInt(bufferSize);
93+
socketOutputStream.write(buffer, 0, bufferSize);
10294

10395
// Limit the FPS to whatever the dashboard requested
10496
int remainingTime = (int) (frameDuration - (System.nanoTime() - startTime));
@@ -175,13 +167,8 @@ public void perform(InputSocket<?>[] inputs, OutputSocket<?>[] outputs) {
175167
throw new IllegalArgumentException("Input image must not be empty");
176168
}
177169

178-
FrameConverter<Mat> frameConverter = new OpenCVFrameConverter.ToMat();
179-
FrameConverter<BufferedImage> imageConverter = new Java2DFrameConverter();
180-
BufferedImage image = imageConverter.convert(frameConverter.convert(input));
181-
182170
synchronized (imageLock) {
183-
this.image = image;
184-
this.compressionQuality = quality.floatValue();
171+
imencode(".jpeg", input, imagePointer, new IntPointer(CV_IMWRITE_JPEG_QUALITY, quality.intValue()));
185172
imageLock.notify();
186173
}
187174
}

0 commit comments

Comments
 (0)