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

The problem with delicious

I recently heard (well, read on a listserv) from a couple of folks I know that were looking for an alternative to Delicious.  These people have a lot invested in their bookmarks but are finding it difficult to re-find them for various reasons and have therefore decided to move on.

I too have been having that problem as of late.  Perhaps I am not careful enough to put every tag in that I should for every entry, perhaps I am not consistent enough in my tagging and so forth.  While I could blame myself, it seems there are a couple of things that could be done to help me out, for instance, if I bookmark something, perhaps allow me to search not only the tags I have entered but also the top tags as many people do a better job of tagging than one.  That’s the power of the crowd, no?

I know, I know, Delicious gives me the opportunity to use the top tags when bookmarking.  Unfortunately, the main way that Delicious has been failing me is not recognizing when I am linking to something that already exists because the URL is slightly different therefore not giving me the opportunity to use those tags.

I spend a lot of time reading articles in the times, some of them directly from the site, some of them via RSS and many of them via email (from various news alerts that I have setup).  Each of these methods, visiting the same story on the times site yields a slightly different URL ending:

?partner=rss&emc=rss

?sudsredirect=true

?emc=eta1

?hpw

?th&emc=th

Here is the main URL for an article which is the base but could contain any of the above at the end:

http://www.nytimes.com/2010/01/04/business/media/04click.html

It seems that I generally come upon NY Times articles in a different way than most other Delicious users as they never seem to be previously bookmarked.  Strange..  

Doing a tag search on Delicious for something obvious in the arlticle: “nytimes” and “seeclickfix” illustrates the problem:

There are at least 11 different entries with slightly different URLs..

Since mine will be the 12th version, I won’t have the benefit of having any tag suggestions from previous bookmarkers. This makes me sad and it probably means that I’ll never find the article again.

Come on..  Delicious..  I know it is easy to fall into the void when you are purchased by a company such as Yahoo but someone there must care a little bit..

Live iPhone Video

I recently read about an app for the iPhone called Knocking Video. It is apparently the first app that allows live streams from an iPhone (any iPhone model) that has been approved by Apple. The story I read went on to describe the saga of it’s struggle for approval and it seems was given the thumbs up from none other than Steve Jobs.

A great story and I love the concept of the app. Unfortunately I think it is doomed to failure. There are just too many barriers in it that are needlessly going to turn off potential users.

The first problem has to do with the sign-up portion of the app. It asks for first name, last name and email. The problem is that it’s error checking is just too aggressive and bug filled. For instance my last name is two words and that wasn’t allowed. Good luck people who want to find me on the app, you won’t be able to because I had to use a last name that isn’t correct. Perhaps you could try to find me via my email address? Guess again, it didn’t allow a dash in my domain name so again I had to use an alternate.

Second, once you join you have to figure out somehow if any of your friends are already using it. There is no way to test the app (as far as I can tell) without a friend “knocking”. They should at least have an echo or testing user that people could try it with.

Since I have no way to evaluate the app, I am not going to send emails to my friends asking them to join..

Ooh yeah, I went to the help and about screens to figure out how to let the company know my issues but the email address they list doesn’t exist.. Guess this blog post will have to suffice, perhaps they’ll read it.

Simple Flash Video By The Pixel

package
{
	import flash.display.Sprite;
	import flash.media.Camera;
	import flash.media.Video;
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.utils.ByteArray;
  	import flash.events.Event;
	import flash.utils.Timer;
	import flash.events.TimerEvent;

	public class VideoProcessing extends Sprite
	{
		// Camera
        private var camera:Camera;

        // My Video
        private var videoOut:Video; 

		// My Bitmapdata
		private var bmpd:BitmapData;
		
		// Bitmap
		private var bmp:Bitmap;
		
		private var timer:Timer;

        public function VideoProcessing()
        {
			trace("Starting");
			
			// Setup the camera
			camera = Camera.getCamera(); 

			// Video components
			videoOut = new Video();

			// Set positions
			videoOut.x = 0;
			videoOut.y = 0;

			// Attach camera to our video
            videoOut.attachCamera(camera); 
			addChild(videoOut);
			
			// Create the bitmapdata object
        	bmpd = new BitmapData(320,240);
			
       		// Create the bitmap image
			bmp = new Bitmap(bmpd);
       	
	       	// Add it to the stage
	       	bmp.x = 0;
    	   	bmp.y = 240;
       		addChild(bmp);			
			
			// Create timer
			timer = new Timer(10,0);
			timer.addEventListener(TimerEvent.TIMER, grabFrame);
			timer.start();

			trace("done starting");
			
		} 


        private function grabFrame(e:TimerEvent):void 
        {
			//trace("timer");
			
			// Save the frame to the bitmapdata object
			bmpd.draw(videoOut);

			// Modify the bmpd
			//http://itp.nyu.edu/~dbo3/cgi-bin/ClassWiki.cgi?ICMVideo#removeStillBg			
    		for (var row:int=0; row<bmpd.height; row=row+1) { //for each row
    			for(var col:int=0; col<bmpd.width; col=col+1) { //for each column
			      	//get the color of this pixels
      				var pix:uint = bmpd.getPixel(col,row);
					
					var red:int = pix >> 16;
					var green:int = pix >> 8 & 0xff;
					var blue:int = pix & 0xff

					if (red > 50 && green > 50 && blue > 50)
					{
						bmpd.setPixel(col,row,0);
				    }
				}
			}
    	
        }
	}
}

Podcast Aggregation..

I was just looking through the feeds that I subscribe to in iTunes (audio and video podcasts) and noticed that every single one of them had a little exclamation point next to it indicating that it stopped updating as I haven’t watched or listened in a while.

This is interesting for a couple of reasons. First, I really really do enjoy watching and listening to many of these. Second, I have listened and watch some of these recently, just not through iTunes (or my iPhone). Most through their website or through online radio (NPR shows on WNYC).

With broadband pretty ubiquitous and even phones being able to be used for listening to or watching online audio/video, aggregators are becoming much less useful (and increasingly wasteful when considering bandwidth usage).

Since really the only reason I still have to use iTunes as an aggregator is to sync things to my iPhone for viewing on the subway (where I don’t have network access), I decided to pare down the list quite a bit.

What I took off and instead will just watch/listen to online:
Alive in Baghdad
Rocketboom
Ask A Ninja

(and a bunch that are defunct such as Boing Boing Boing, EFF Line Noise, The Show with Ze Frank, We Are The Media, WGBH Lab Showcase, <sniff>)

What I left on for iPhone consumption (mostly audio since I typically am doing something else like email on my iPhone on the subway):
Joe Frank Radio
NPR Science Friday
On The Media
The Onion Radio News
The TV of Tomorrow Show with Tracy Swedlow
They Might Be Giants Podcast
StreamingMedia.com Podcast
TEDTalks (Video)
This American Life

I also have a bunch that I haven’t decided yet for one reason or another. Mostly they are done by friends of mine and I just love to see their updates (bandwidth and space be damned):
Tech Trek TV, pouringdown, Ryan is Hungry, momentshowing, jonnygoldstein.com

(Nothing here is new, just wanted to take note of it)

Android phones hands down over iPhone

For one simple reason: Apps can run in the background.

Simple

The iPhone is amazing, the apps that have been developed for it are incredible and useful. The user interface is a dream. It is truly unfortunate that Apple hasn’t been staying true to it’s roots as an operating system and computer company and is bowing to the desires of the overly protectionist mobile industry. The development environment is good, the fact that they were forced to open it up is kind of sad. They should have had the foresight to do so themselves. Unfortunately they are dictatorial and don’t encourage innovation beyond the framework they have setup.

The Android platform on the other hand has serious user interface issues (though not nearly as bad as Windows Mobile or Symbian S60). There are some good apps, it doesn’t have the clout that Apple has garnered over the past year and a half or so and so on. The big difference, they are open, open, open at least in terms of app development. You don’t have to distribute your App through Google or the carriers, you can simply allow people to download it directly from you.

Now, Android isn’t perfect, it still has to work with the carriers and therefore bow to them in many ways but having just played with a friend’s G1, I have seen the light.

You can background applications..! This means that theoretically I can have additional services running in the background all the time! Twitter can alert me when I get a direct message, I can continue listening to AOL Radio while I check my email.. Apple limits these capabilities to their built-in apps and services (phone calls, SMS). Google seems to have it right.

Mobile, 5 Years in the future

I was just interviewed for an upcoming book and one of the questions was:
Fast forward 5 years into the future, can you paint me picture of the mobile world?

Here is what I said:
I am going to beg out of this one and instead paint a picture of my utopia.

My mobile utopia 5 years from now:

Carriers have accepted the fact that they are too large and slow to beat the current crop of DIY wireless systems that are being built. They have realized that the cost of maintaining service such as the little used voice platform is not worthwhile when all that anyone cares about is the openness and speed of their internet connection. Besides, they are sick of battling the hackers who continually figure out how to bypass their restrictions and really sick of spending their lobbying money to battle Googlezon and the like over whether or not they have to carry 3rd party data without charge.

They have finally realized and accepted their place in the world as “dumb pipes”, wireless ISPs.

They have given up on locking down phones. Nobody will sign a 2 year contract anymore for a free phone that they can’t install any of the open source software on.

On the other side of the coin, Googlezon, DIYers, hackers and hipsters are developing and deploying game changing hardware and applications at a phenomenal pace.

A prolific open source community has introduced a kit based mobile phone with every feature imaginable and battery life that puts devices from 5 years ago to shame. Tourists are carrying around monstrous looking home built teleconferencing systems with them as they gawk at the Naked Cowboy in Time Square and talk with their relatives and friends back home.

Hipsters in Bushwick no longer carry laptops and projectors to their VJ gigs but rather bring their mobile projector enabled high-speed wireless video mixing system and no longer have to be hunched over a keyboard and mouse. They simply mingle with the crowd or dance until they drop with every movement being tracked by sensors programmed to project and mix particular clips or dynamically generated visuals.

I can’t think of anyone who uses a laptop computer anymore. Everyone seems to have adopted the projected keyboard and gesture controlled interfaces that are common on mobile devices now.

Data flows pretty seamlessly and just by pointing to a contact in the sky a voice, data or text channel is opened to that person.

Wow.. Things are different now that the networks have been broken..

(Perhaps we can dream..)

“Google’s Idea of Primetime”

Over at Shelly Palmer’s blog I wrote a comment in response to his thoughts on a recent Google presentation where it was noted that kids and teenagers weren’t consuming YouTube as much as previously assumed. He discussed some possible alternatives (video games, comic books, Long Tail) but missed one very important point. (I left the following as a comment on his blog but the formatting was terrible so it is below.)

Regarding the consumption of media by teenagers, I think you are missing one very important aspect of online life, particularly as it relates to teenagers: Talking.. Every teenager that I have talked to and asked what they do online has said one of the three following things: MySpace, Facebook and AIM.

I break this down as follows: Constructing identity, meeting people and talking with them. Sharing media and consuming media, I believe are aspects of that but take a back seat to the primary socializing behavior. I think it is possible that we are entering an era much more radical than the rise of the “Long Tail”, we just might be going back to individual and small group storytelling as the primary media.

Come to think about it, it isn’t that radical, only seems different from the norm if you were born between 1940 and 1990.