I'm receiving a motion JPEG stream via a servlet's post method. I cut out the JPEG images from the stream as they come and then write them to a Motion JPEG file (mjpeg), using the following structure:
--END
Content-Type: image/jpeg
Content-Length: <Length of following JPEG image in bytes>
<JPEG image data>
--END
After the last --END
part, the next image starts.
The mjpeg file is created in the following way:
private static final String BOUNDARY = "END";
private static final Charset CHARSET = StandardCharsets.UTF_8;
final byte[] PRE_BOUNDARY = ("--" + BOUNDARY).getBytes(CHARSET);
final byte[] CONTENT_TYPE = "Content-Type: image/jpeg".getBytes(CHARSET);
final byte[] CONTENT_LENGTH = "Content-Length: ".getBytes(CHARSET);
final byte[] LINE_FEED = "\n".getBytes(CHARSET);
try (OutputStream out = new BufferedOutputStream(new FileOutputStream(targetFile))) {
for every image received {
onImage(image, out);
}
}
public void onImage(byte[] image, OutputStream out) {
try {
out.write(PRE_BOUNDARY);
out.write(LINE_FEED);
out.write(CONTENT_TYPE);
out.write(LINE_FEED);
out.write(CONTENT_LENGTH);
out.write(String.valueOf(image.length).getBytes(CHARSET));
out.write(LINE_FEED);
out.write(LINE_FEED);
out.write(image);
out.write(LINE_FEED);
out.write(LINE_FEED);
} catch (IOException e) {
e.printStackTrace();
}
}
Here is an example file.
Now, I'd like to read the mjpeg files again and do some processing on the contained images. For this, I build the following reader:
package de.supportgis.stream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public class MediaConverter {
private static final String BOUNDARY = "END";
private static final Charset CHARSET = StandardCharsets.UTF_8;
private static final int READ_PRE_BOUNDARY = 1;
private static final int READ_CONTENT_TYPE = 2;
private static final int READ_CONTENT_LENGTH = 3;
private static final int READ_CONTENT = 4;
public static void createMovieFromMJPEG(String file) throws FileNotFoundException, IOException {
char LINE_FEED = '\n';
char[] PRE_BOUNDARY = new String("--" + BOUNDARY + LINE_FEED).toCharArray();
try (InputStream in = new FileInputStream(file);
Reader reader = new InputStreamReader(in, CHARSET);
Reader buffer = new BufferedReader(reader)) {
int r;
StringBuffer content_buf = new StringBuffer();
int mode = READ_PRE_BOUNDARY;
long content_length = 0;
int[] cmdBuf = new int[PRE_BOUNDARY.length];
int boundaryPointer = 0;
int counter = 0;
while ((r = reader.read()) != -1) {
System.out.print((char)r);
counter++;
if (mode == READ_PRE_BOUNDARY) {
if (r == PRE_BOUNDARY[boundaryPointer]) {
boundaryPointer++;
if (boundaryPointer >= PRE_BOUNDARY.length - 1) {
// Read a PRE_BOUNDARY
mode = READ_CONTENT_TYPE;
boundaryPointer = 0;
}
}
} else if (mode == READ_CONTENT_TYPE) {
if (r != LINE_FEED) {
content_buf.append((char)r);
} else {
if (content_buf.length() == 0) {
// leading line break, ignore...
} else {
mode = READ_CONTENT_LENGTH;
content_buf.setLength(0);
}
}
} else if (mode == READ_CONTENT_LENGTH) {
if (r != LINE_FEED) {
content_buf.append((char)r);
} else {
if (content_buf.length() == 0) {
// leading line break, ignore...
} else {
String number = content_buf.substring(content_buf.lastIndexOf(":") + 1).trim();
content_length = Long.valueOf(number);
content_buf.setLength(0);
mode = READ_CONTENT;
}
}
} else if (mode == READ_CONTENT) {
char[] fileBuf = new char[(int)content_length];
reader.read(fileBuf);
System.out.println(fileBuf);
mode = READ_PRE_BOUNDARY;
}
}
}
}
public static void main(String[] args) {
try {
createMovieFromMJPEG("video.mjpeg");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Note that this reader may not produce working JPEGs yet, as I'm still trying to debug the following error:
I read the value given at Content-length
. I furthermore expect that when I read <content-length>
of bytes after the Content-Length
part into fileBuf
(line 78), I end up with exactly the bytes of the image that I wrote in the prior step. However, fileBuf
contains the whole image, as well as the metadata and half the bytes of the next image, which means it reads way too much.
I know that when it comes to saving, reading and encoding binary data, there are many things that can go wrong. Which mistake do I have the pleasure of making here?
Thanks in advance.
The mistake was, as several comments suggested, using a Reader instead of an InputStream. I assumed InputStreamReader
to return provide the returned bytes of an Inputstream via a Reader interface, but instead it returned characters in a specificm encoding as seemingly all Readers do.
So, the saving logic for the mjpeg stream was ok and the corrected reader looks like this:
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public class MediaConverter {
private static final String BOUNDARY = "END";
private static final Charset CHARSET = StandardCharsets.UTF_8;
private static final int READ_PRE_BOUNDARY = 1;
private static final int READ_CONTENT_TYPE = 2;
private static final int READ_CONTENT_LENGTH = 3;
private static final int READ_CONTENT = 4;
public static void createMovieFromMJPEG(String file) throws FileNotFoundException, IOException {
char LINE_FEED = '\n';
char[] PRE_BOUNDARY = new String("--" + BOUNDARY + LINE_FEED).toCharArray();
try (InputStream in = new FileInputStream(file);
BufferedInputStream reader = new BufferedInputStream(in);) {
int r;
StringBuffer content_buf = new StringBuffer();
int mode = READ_PRE_BOUNDARY;
long content_length = 0;
int[] cmdBuf = new int[PRE_BOUNDARY.length];
int boundaryPointer = 0;
int counter = 0;
while ((r = reader.read()) != -1) {
System.out.print((char)r);
counter++;
if (mode == READ_PRE_BOUNDARY) {
if (r == PRE_BOUNDARY[boundaryPointer]) {
boundaryPointer++;
if (boundaryPointer >= PRE_BOUNDARY.length - 1) {
// Read a PRE_BOUNDARY
mode = READ_CONTENT_TYPE;
boundaryPointer = 0;
}
}
} else if (mode == READ_CONTENT_TYPE) {
if (r != LINE_FEED) {
content_buf.append((char)r);
} else {
if (content_buf.length() == 0) {
// leading line break, ignore...
} else {
mode = READ_CONTENT_LENGTH;
content_buf.setLength(0);
}
}
} else if (mode == READ_CONTENT_LENGTH) {
if (r != LINE_FEED) {
content_buf.append((char)r);
} else {
if (content_buf.length() == 0) {
// leading line break, ignore...
} else {
String number = content_buf.substring(content_buf.lastIndexOf(":") + 1).trim();
content_length = Long.valueOf(number);
content_buf.setLength(0);
mode = READ_CONTENT;
}
}
} else if (mode == READ_CONTENT) {
byte[] fileBuf = new byte[(int)content_length];
reader.read(fileBuf);
System.out.println(fileBuf);
mode = READ_PRE_BOUNDARY;
}
}
}
}
public static void main(String[] args) {
try {
createMovieFromMJPEG("video.mjpeg");
} catch (IOException e) {
e.printStackTrace();
}
}
}