001package com.github.sarxos.webcam.ds.ipcam.impl;
002
003import java.awt.image.BufferedImage;
004import java.io.BufferedInputStream;
005import java.io.BufferedReader;
006import java.io.ByteArrayInputStream;
007import java.io.DataInputStream;
008import java.io.IOException;
009import java.io.InputStream;
010import java.io.InputStreamReader;
011
012import javax.imageio.ImageIO;
013
014
015public 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}