Skip to content

Commit 5f6d528

Browse files
committed
Merge pull request #383 from ThomasJClark/video
Use OpenCV instead of javax.imageio to encode M-JPEG stream
2 parents abec11c + 08a764e commit 5f6d528

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)