1 package kaka.cakelight;
3 import javax.imageio.ImageIO;
4 import java.awt.image.BufferedImage;
6 import java.util.Optional;
8 import static kaka.cakelight.Main.log;
10 public class FrameGrabber implements Closeable {
11 private Configuration config;
13 private int bytesPerFrame;
14 private InputStream fileStream;
15 private final ByteArrayOutputStream bufferedBytes = new ByteArrayOutputStream();
17 private FrameGrabber() {
20 public static FrameGrabber from(File videoDevice, Configuration config) {
21 FrameGrabber fg = new FrameGrabber();
23 fg.file = videoDevice;
24 fg.bytesPerFrame = config.video.width * config.video.height * config.video.bpp;
29 private boolean prepare() {
31 fileStream = new FileInputStream(file);
33 } catch (FileNotFoundException e) {
34 // TODO: handle java.io.FileNotFoundException: /dev/video1 (Permission denied)
41 * Must be run in the same thread as {@link #prepare}.
43 public Optional<VideoFrame> grabFrame() {
46 if (config.video.mjpg) {
47 byte[] jpgData = readStreamingJpgData();
48 if (jpgData == null) {
49 return Optional.empty();
51 saveTemporaryJpgFile(jpgData);
52 byte[] bmpData = convertJpgFileToByteArray();
53 if (bmpData == null) {
54 return Optional.empty();
58 data = new byte[bytesPerFrame];
59 int count = fileStream.read(data);
60 if (count != bytesPerFrame) {
61 log("Expected to read " + bytesPerFrame + " bytes per frame but read " + count);
65 return Optional.of(VideoFrame.of(data, config));
66 } catch (IOException e) {
70 return Optional.empty();
73 private byte[] readStreamingJpgData() throws IOException {
75 byte[] batch = new byte[1024];
76 boolean lastByteIsXX = false;
79 int batchCount = fileStream.read(batch);
80 if (batchCount == -1) {
84 if (batch[0] == (byte) 0xd8) {
85 data = bufferedBytes.toByteArray();
86 bufferedBytes.reset();
87 bufferedBytes.write(0xff);
88 bufferedBytes.write(batch, 0, batchCount);
91 bufferedBytes.write(0xff);
93 for (int i = 0; i < batchCount - 1; i++) {
94 if (batch[i] == (byte) 0xff && batch[i + 1] == (byte) 0xd8) { // start of jpeg
96 bufferedBytes.write(batch, 0, i);
98 data = bufferedBytes.toByteArray();
99 bufferedBytes.reset();
100 bufferedBytes.write(batch, i, batchCount - i);
104 lastByteIsXX = batch[batchCount - 1] == (byte) 0xff;
105 bufferedBytes.write(batch, 0, batchCount - (lastByteIsXX ? 1 : 0));
110 private void saveTemporaryJpgFile(byte[] data) throws IOException {
111 try (FileOutputStream fos = new FileOutputStream("/tmp/cakelight-video-stream.jpg")) {
116 private byte[] convertJpgFileToByteArray() throws IOException {
117 BufferedImage image = ImageIO.read(new File("/tmp/cakelight-video-stream.jpg"));
118 if (image != null) { // will almost always be null the first time
119 try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
120 ImageIO.write(image, "bmp", baos);
122 return baos.toByteArray();
130 public void close() throws IOException {