Motion JPEG in Flash and Java

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

6 thoughts on “Motion JPEG in Flash and Java”

  1. Dear,

    I’m trying to compile the AS3 script via the adobe flex 3. But I find some mistakes. You have compiled this script in the Flex 3? This script is very important to me. Thank you for your contribution!

    Best Regards,

    JORGE ORENGO

  2. I think the code got cut off in the formatting of the page. You may have to view the source and copy/paste from there (removing any funny characters).

  3. Hello,
    I’m trying to get working the as3 code in adobe flex 3. But I didn’t get any response when the load is made.
    What could I be making wrong? Do I need a proxy?

  4. Hello,

    I’m trying the code but I get an error:
    Error #2044: Unhandled IOErrorEvent:. text=Error #2032: Stream Error.

    Do you know what that might be?

  5. What the heck this page starts?? everytime I open this page starts a new java process and eats my cpu!!!

Leave a Reply

Your email address will not be published. Required fields are marked *