001 package com.github.sarxos.webcam.ds.ipcam.impl;
002
003 import java.awt.image.BufferedImage;
004 import java.io.BufferedInputStream;
005 import java.io.BufferedReader;
006 import java.io.ByteArrayInputStream;
007 import java.io.DataInputStream;
008 import java.io.IOException;
009 import java.io.InputStream;
010 import java.io.InputStreamReader;
011
012 import javax.imageio.ImageIO;
013
014
015 public class IpCamMJPEGStream extends DataInputStream {
016
017 /**
018 * The first two bytes of every JPEG stream are the Start Of Image (SOI)
019 * marker values FFh D8h.
020 */
021 private final byte[] SOI_MARKER = { (byte) 0xFF, (byte) 0xD8 };
022
023 /**
024 * All JPEG data streams end with the End Of Image (EOI) marker values FFh
025 * D9h.
026 */
027 private final byte[] EOI_MARKER = { (byte) 0xFF, (byte) 0xD9 };
028
029 /**
030 * Name of content length header.
031 */
032 private final String CONTENT_LENGTH = "Content-Length";
033
034 /**
035 * Maximum header length.
036 */
037 private final static int HEADER_MAX_LENGTH = 100;
038
039 /**
040 * Max frame length (100kB).
041 */
042 private final static int FRAME_MAX_LENGTH = 100000 + HEADER_MAX_LENGTH;
043
044 private boolean open = true;
045
046 public IpCamMJPEGStream(InputStream in) {
047 super(new BufferedInputStream(in, FRAME_MAX_LENGTH));
048 }
049
050 private int getEndOfSeqeunce(DataInputStream in, byte[] sequence) throws IOException {
051 int s = 0;
052 byte c;
053 for (int i = 0; i < FRAME_MAX_LENGTH; i++) {
054 c = (byte) in.readUnsignedByte();
055 if (c == sequence[s]) {
056 s++;
057 if (s == sequence.length) {
058 return i + 1;
059 }
060 } else {
061 s = 0;
062 }
063 }
064 return -1;
065 }
066
067 private int getStartOfSequence(DataInputStream in, byte[] sequence) throws IOException {
068 int end = getEndOfSeqeunce(in, sequence);
069 return end < 0 ? -1 : end - sequence.length;
070 }
071
072 private int parseContentLength(byte[] headerBytes) throws IOException, NumberFormatException {
073
074 ByteArrayInputStream bais = new ByteArrayInputStream(headerBytes);
075 InputStreamReader isr = new InputStreamReader(bais);
076 BufferedReader br = new BufferedReader(isr);
077
078 String line = null;
079 while ((line = br.readLine()) != null) {
080 if (line.startsWith(CONTENT_LENGTH)) {
081 String[] parts = line.split(":");
082 if (parts.length == 2) {
083 return Integer.parseInt(parts[1].trim());
084 }
085 }
086 }
087
088 return 0;
089 }
090
091 public BufferedImage readFrame() throws IOException {
092
093 if (!open) {
094 return null;
095 }
096
097 byte[] header = null;
098 byte[] frame = null;
099
100 mark(FRAME_MAX_LENGTH);
101
102 int n = getStartOfSequence(this, SOI_MARKER);
103
104 reset();
105
106 header = new byte[n];
107
108 readFully(header);
109
110 int length = -1;
111 try {
112 length = parseContentLength(header);
113 } catch (NumberFormatException e) {
114 length = getEndOfSeqeunce(this, EOI_MARKER);
115 }
116
117 reset();
118
119 frame = new byte[length];
120
121 skipBytes(n);
122 readFully(frame);
123
124 try {
125 return ImageIO.read(new ByteArrayInputStream(frame));
126 } catch (IOException e) {
127 return null;
128 }
129 }
130
131 @Override
132 public void close() throws IOException {
133 open = false;
134 super.close();
135 }
136
137 public boolean isClosed() {
138 return !open;
139 }
140 }