——————————————————————————————————————————————————————————————
I recently had the opportunity to develop a solution in both Java and Flash for pulling Motion JPEG streams from IP cameras and thought it might be nice to document a bit.
Motion JPEG is generally served via HTTP from IP cameras as a single file. Meaning, the connection stays open and the camera just keeps sending individual JPEG images down the pipe. The images should start with a MIME boundary message such as:
--myboundary
Content-Type: image/jpeg
Content-Length: 22517
or
--randomstring
Content-Type: image/jpeg
Content-Length: 22598
The key in the development is to find the boundary and save the bytes between each and treat that as a JPEG image. Neither of these snippets are great or even complete but they should give you a bit of a start.
Java:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URL;
public class MJPEGParser {
/**
* @param args
*/
public static void main(String[] args) {
MJPEGParser mp = new MJPEGParser("http://192.168.1.10/mjpg/video.mjpg", "username", "password");
}
public MJPEGParser(String mjpeg_url)
{
this(mjpeg_url,null,null);
}
public MJPEGParser(String mjpeg_url, String username, String password)
{
int imageCount = 0;
try {
if (username != null && password != null)
{
Authenticator.setDefault(new HTTPAuthenticator(username, password));
}
URL url = new URL(mjpeg_url);
BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
String inputLine;
int lineCount = 0;
boolean lineCountStart = false;
boolean saveImage = false;
while ((inputLine = in.readLine()) != null) {
// Should be checking just for "--" probably
if (inputLine.lastIndexOf("--myboundary") > -1)
{
// Got an image boundary, stop last image
// Start counting lines to get past:
// Content-Type: image/jpeg
// Content-Length: 22517
saveImage = false;
lineCountStart = true;
System.out.println("Got a new boundary");
System.out.println(inputLine);
}
else if (lineCountStart)
{
lineCount++;
if (lineCount >= 2)
{
lineCount = 0;
lineCountStart = false;
imageCount++;
saveImage = true;
System.out.println("Starting a new image");
}
}
else if (saveImage)
{
System.out.println("Saving an image line");
}
else {
System.out.println("What's this:");
System.out.println(inputLine);
}
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
static class HTTPAuthenticator extends Authenticator {
private String username, password;
public HTTPAuthenticator(String user, String pass) {
username = user;
password = pass;
}
protected PasswordAuthentication getPasswordAuthentication() {
System.out.println("Requesting Host : " + getRequestingHost());
System.out.println("Requesting Port : " + getRequestingPort());
System.out.println("Requesting Prompt : " + getRequestingPrompt());
System.out.println("Requesting Protocol: "
+ getRequestingProtocol());
System.out.println("Requesting Scheme : " + getRequestingScheme());
System.out.println("Requesting Site : " + getRequestingSite());
return new PasswordAuthentication(username, password.toCharArray());
}
}
}
import flash.errors.*;
import flash.events.*;
import flash.net.URLRequest;
import flash.net.URLStream;
import flash.utils.ByteArray;
var stream:URLStream;
var mjpegBuffer:ByteArray = new ByteArray();
// The actual image
var imageBytes:ByteArray; // = new ByteArray();
// The chars at the end of the image
var endPos:String = "\n--myboundary";
// Started to find, finished finding
var done:Boolean = false;
var started:Boolean = false;
// Don't know why I have to save these to a ByteArray to do the comparisons but it seems I do
var startBytes:ByteArray = new ByteArray();
var startByte:int = 0xFF;
var secondByte:int = 0xD8;
startBytes.writeByte(0xFF);
startBytes.writeByte(0xD8);
trace(startBytes.length);
var startNum:int = startBytes[0];
trace(startNum);
var nextNum:int = startBytes[1];
trace(nextNum);
// Open the stream
stream = new URLStream();
var request:URLRequest = new URLRequest("http://192.168.1.10/mjpg/video.mjpg?resolution=160x90&fps=1");
configureListeners(stream);
try {
stream.load(request);
} catch (error:Error) {
trace("Unable to load requested URL.");
}
function configureListeners(dispatcher:EventDispatcher):void {
dispatcher.addEventListener(ProgressEvent.PROGRESS, progressHandler);
}
function progressHandler(event:Event):void
{
trace("Running");
stream.readBytes(mjpegBuffer,mjpegBuffer.length,stream.bytesAvailable);
for (var i:int = 0; i < mjpegBuffer.length; i++)
{
var currentByte:int = mjpegBuffer[i];
var nextByte:int = mjpegBuffer[i+1];
var thirdByte:int = mjpegBuffer[i+2];
var fourthByte:int = mjpegBuffer[i+3];
//var randNum:Number = Math.random();
//if (randNum > .5 && randNum < .6) { trace(currentByte); }
if (!started)
{
if (currentByte == startNum && nextByte == nextNum)
{
trace("Started");
started = true;
imageBytes = new ByteArray();
imageBytes.writeByte(currentByte);
//imageBytes.writeByte(0xD8); // Gets written in the else
}
}
else
{
if (currentByte == endPos.charCodeAt(0) && nextByte == endPos.charCodeAt(1) && thirdByte == endPos.charCodeAt(2) && fourthByte == endPos.charCodeAt(3))
{
trace("done");
trace(imageBytes);
done = true;
started = false;
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onByteArrayLoaded)
loader.loadBytes(imageBytes);
//stream.close();
}
else
{
imageBytes.writeByte(currentByte);
}
}
}
}
function onByteArrayLoaded(e:Event):void
{
var loader:Loader = Loader(e.target.loader);
loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onByteArrayLoaded);
var bitmapData:BitmapData = Bitmap(e.target.content).bitmapData;
//sprLoaded.graphics.clear();
graphics.beginBitmapFill(bitmapData);
graphics.drawRect(0,0,bitmapData.width, bitmapData.height);
graphics.endFill();
}
傲轩游戏网