package kaka.cakelight;
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Optional;
private File file;
private int bytesPerFrame;
private InputStream fileStream;
+ private final ByteArrayOutputStream bufferedBytes = new ByteArrayOutputStream();
private FrameGrabber() {
}
*/
public Optional<VideoFrame> grabFrame() {
try {
- byte[] data = new byte[bytesPerFrame];
- int count = fileStream.read(data);
- if (count != bytesPerFrame) {
- log("Expected to read " + bytesPerFrame + " bytes per frame but read " + count);
+ byte[] data;
+ if (config.video.mjpg) {
+ byte[] jpgData = readStreamingJpgData();
+ if (jpgData == null) {
+ return Optional.empty();
+ }
+ saveTemporaryJpgFile(jpgData);
+ byte[] bmpData = convertJpgFileToByteArray();
+ if (bmpData == null) {
+ return Optional.empty();
+ }
+ data = bmpData;
+ } else {
+ data = new byte[bytesPerFrame];
+ int count = fileStream.read(data);
+ if (count != bytesPerFrame) {
+ log("Expected to read " + bytesPerFrame + " bytes per frame but read " + count);
+ }
}
+
return Optional.of(VideoFrame.of(data, config));
} catch (IOException e) {
e.printStackTrace();
return Optional.empty();
}
+ private byte[] readStreamingJpgData() throws IOException {
+ byte[] data;
+ byte[] batch = new byte[1024];
+ boolean lastByteIsXX = false;
+ loop:
+ while (true) {
+ int batchCount = fileStream.read(batch);
+ if (batchCount == -1) {
+ return null;
+ }
+ if (lastByteIsXX) {
+ if (batch[0] == (byte) 0xd8) {
+ data = bufferedBytes.toByteArray();
+ bufferedBytes.reset();
+ bufferedBytes.write(0xff);
+ bufferedBytes.write(batch, 0, batchCount);
+ break;
+ }
+ bufferedBytes.write(0xff);
+ }
+ for (int i = 0; i < batchCount - 1; i++) {
+ if (batch[i] == (byte) 0xff && batch[i + 1] == (byte) 0xd8) { // start of jpeg
+ if (i > 0) {
+ bufferedBytes.write(batch, 0, i);
+ }
+ data = bufferedBytes.toByteArray();
+ bufferedBytes.reset();
+ bufferedBytes.write(batch, i, batchCount - i);
+ break loop;
+ }
+ }
+ lastByteIsXX = batch[batchCount - 1] == (byte) 0xff;
+ bufferedBytes.write(batch, 0, batchCount - (lastByteIsXX ? 1 : 0));
+ }
+ return data;
+ }
+
+ private void saveTemporaryJpgFile(byte[] data) throws IOException {
+ try (FileOutputStream fos = new FileOutputStream("/tmp/cakelight-video-stream.jpg")) {
+ fos.write(data);
+ }
+ }
+
+ private byte[] convertJpgFileToByteArray() throws IOException {
+ BufferedImage image = ImageIO.read(new File("/tmp/cakelight-video-stream.jpg"));
+ if (image != null) { // will almost always be null the first time
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ ImageIO.write(image, "bmp", baos);
+ baos.flush();
+ return baos.toByteArray();
+ }
+ } else {
+ return null;
+ }
+ }
+
@Override
public void close() throws IOException {
fileStream.close();
package kaka.cakelight;
+import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Size;
public static VideoFrame of(byte[] data, Configuration config) {
VideoFrame frame = new VideoFrame(data);
frame.config = config;
- frame.convert();
+ if (config.video.mjpg) {
+ frame.convertJpg();
+ } else {
+ frame.convert();
+ }
return frame;
}
+ private void convertJpg() {
+ Mat src = new Mat(config.video.height, config.video.width, CvType.CV_8UC3); // 8-bit, unsigned, 3 channels
+ src.put(0, 0, data);
+// save(src, "/home/kaka/test-src.data");
+
+ converted = new Mat();
+ Imgproc.cvtColor(src, converted, Imgproc.COLOR_BGR2RGB);
+
+ Core.flip(converted, converted, 0); // up-side down
+// save(converted, "/home/kaka/test-converted.data");
+ int mysteriousPixelShift = 18;
+ converted = converted.submat( // crop mysterious pixel shift
+ 0,
+ converted.rows(),
+ mysteriousPixelShift,
+ converted.cols() - mysteriousPixelShift
+ );
+// save(converted, "/home/kaka/test-croppedAgain.data");
+ model4(converted, Imgproc.INTER_AREA);
+ src.release();
+ converted.release();
+ }
+
private void convert() {
/* TODO: how to do this?
1) Resize to an image with the size of the number of leds and use config to define how many pixels deep into the screen to use.