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    }