X-Git-Url: http://dolda2000.com/gitweb/?a=blobdiff_plain;f=src%2Fkaka%2Fcakelight%2FFrameGrabber.java;h=5354ea69a8ab1a13dbb5bbf132fc491beee55b4b;hb=eba8feca754a0875f30b8284dab67c8c01f85ef6;hp=d228b7bc189faa2f17859f12c6f226e13cb4d470;hpb=e59e98fcf77a104e31dd97641b0ceea6d0a79e00;p=kaka%2Fcakelight.git diff --git a/src/kaka/cakelight/FrameGrabber.java b/src/kaka/cakelight/FrameGrabber.java index d228b7b..5354ea6 100644 --- a/src/kaka/cakelight/FrameGrabber.java +++ b/src/kaka/cakelight/FrameGrabber.java @@ -1,52 +1,133 @@ package kaka.cakelight; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; import java.io.*; +import java.util.Optional; -public class FrameGrabber { +import static kaka.cakelight.Main.log; + +public class FrameGrabber implements Closeable { private Configuration config; private File file; private int bytesPerFrame; private InputStream fileStream; + private final ByteArrayOutputStream bufferedBytes = new ByteArrayOutputStream(); private FrameGrabber() { } - public static FrameGrabber from(Configuration config) { + public static FrameGrabber from(File videoDevice, Configuration config) { FrameGrabber fg = new FrameGrabber(); fg.config = config; - fg.file = new File(config.video.device); + fg.file = videoDevice; fg.bytesPerFrame = config.video.width * config.video.height * config.video.bpp; + fg.prepare(); return fg; } - public boolean prepare() { + private boolean prepare() { try { fileStream = new FileInputStream(file); return true; } catch (FileNotFoundException e) { + // TODO: handle java.io.FileNotFoundException: /dev/video1 (Permission denied) e.printStackTrace(); return false; } } - public Frame grabFrame() { + /** + * Must be run in the same thread as {@link #prepare}. + */ + public Optional grabFrame() { try { - byte[] data = new byte[bytesPerFrame]; - int count = fileStream.read(data); - System.out.println("count = " + count); - return Frame.of(data, config); + 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 null; + return Optional.empty(); } - public void close() { - try { - fileStream.close(); - } catch (IOException e) { - e.printStackTrace(); + 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(); } }