77import edu .wpi .grip .core .OutputSocket ;
88import edu .wpi .grip .core .SocketHints ;
99import 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 ;
2117import java .net .ServerSocket ;
2218import java .net .Socket ;
2319import java .util .Optional ;
2420import java .util .logging .Level ;
2521import java .util .logging .Logger ;
2622
2723import 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