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}