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:

package com.mobvcasting.mjpegparser;

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());
	    }
	  }
}

ActionScript 3

import flash.display.Sprite;
    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();

		}
Share and Enjoy:
  • Digg
  • del.icio.us
  • co.mments
  • Furl
  • NewsVine
  • Reddit
  • Slashdot
  • StumbleUpon
  • TailRank
  • Technorati
  • YahooMyWeb

No Responses to “Motion JPEG in Flash and Java”  

  1. No Comments

Leave a Reply