{"id":1706,"date":"2010-04-20T12:42:33","date_gmt":"2010-04-20T17:42:33","guid":{"rendered":"http:\/\/www.walking-productions.com\/notslop\/?p=1706"},"modified":"2010-04-20T12:43:21","modified_gmt":"2010-04-20T17:43:21","slug":"motion-jpeg-in-flash-and-java","status":"publish","type":"post","link":"http:\/\/www.walking-productions.com\/notslop\/2010\/04\/20\/motion-jpeg-in-flash-and-java\/","title":{"rendered":"Motion JPEG in Flash and Java"},"content":{"rendered":"<p>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.<\/p>\n<p>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:<\/p>\n<p><code><br \/>\n--myboundary<br \/>\nContent-Type: image\/jpeg<br \/>\nContent-Length: 22517<br \/>\n<\/code><\/p>\n<p>or<br \/>\n<code><br \/>\n--randomstring<br \/>\nContent-Type: image\/jpeg<br \/>\nContent-Length: 22598<br \/>\n<\/code><\/p>\n<p>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.<\/p>\n<p>Java:<\/p>\n<pre>\r\npackage com.mobvcasting.mjpegparser;\r\n\r\nimport java.io.BufferedReader;\r\nimport java.io.IOException;\r\nimport java.io.InputStreamReader;\r\nimport java.net.Authenticator;\r\nimport java.net.PasswordAuthentication;\r\nimport java.net.URL;\r\n\r\npublic class MJPEGParser {\r\n\r\n\t\/**\r\n\t * @param args\r\n\t *\/\r\n\tpublic static void main(String[] args) {\r\n\t\tMJPEGParser mp = new MJPEGParser(\"http:\/\/192.168.1.10\/mjpg\/video.mjpg\", \"username\", \"password\");\r\n\t}\r\n\r\n\tpublic MJPEGParser(String mjpeg_url)\r\n\t{\r\n\t\tthis(mjpeg_url,null,null);\r\n\t}\r\n\t\r\n\tpublic MJPEGParser(String mjpeg_url, String username, String password)\r\n\t{\r\n\t\tint imageCount = 0;\r\n\t\t\r\n\t\ttry {\r\n\t\t\tif (username != null && password != null)\r\n\t\t\t{\r\n\t\t\t    Authenticator.setDefault(new HTTPAuthenticator(username, password));\r\n\t\t\t}\r\n\t\t\t\r\n            URL url = new URL(mjpeg_url);\r\n            BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));\r\n            String inputLine;\r\n            int lineCount = 0;\r\n            boolean lineCountStart = false;\r\n            boolean saveImage = false;\r\n            while ((inputLine = in.readLine()) != null) {\r\n            \t\/\/ Should be checking just for \"--\" probably\r\n            \tif (inputLine.lastIndexOf(\"--myboundary\") > -1)\r\n            \t{\r\n            \t\t\/\/ Got an image boundary, stop last image\r\n            \t\t\/\/ Start counting lines to get past:\r\n            \t\t\/\/ Content-Type: image\/jpeg\r\n            \t\t\/\/ Content-Length: 22517\r\n\r\n            \t\tsaveImage = false;\r\n            \t\tlineCountStart = true;\r\n            \t\t\r\n            \t\tSystem.out.println(\"Got a new boundary\");\r\n            \t\tSystem.out.println(inputLine);\r\n            \t}\r\n            \telse if (lineCountStart)\r\n            \t{\r\n            \t\tlineCount++;\r\n            \t\tif (lineCount >= 2)\r\n            \t\t{\r\n            \t\t\tlineCount = 0;\r\n            \t\t\tlineCountStart = false;\r\n            \t\t\timageCount++;\r\n            \t\t\tsaveImage = true;\r\n                \t\tSystem.out.println(\"Starting a new image\");\r\n\r\n            \t\t}\r\n            \t}\r\n            \telse if (saveImage)\r\n            \t{\r\n            \t\tSystem.out.println(\"Saving an image line\");\r\n            \t}\r\n            \telse {\r\n            \t\t\r\n            \t\tSystem.out.println(\"What's this:\");\r\n            \t\tSystem.out.println(inputLine);\r\n            \t}\r\n            }\r\n            in.close();            \r\n        } catch (IOException e) {\r\n            e.printStackTrace();\r\n        }\t\t\r\n\t\t\r\n\t}\r\n\t\r\n\t\r\n\tstatic class HTTPAuthenticator extends Authenticator {\r\n\t    private String username, password;\r\n\r\n\t    public HTTPAuthenticator(String user, String pass) {\r\n\t      username = user;\r\n\t      password = pass;\r\n\t    }\r\n\r\n\t    protected PasswordAuthentication getPasswordAuthentication() {\r\n\t      System.out.println(\"Requesting Host  : \" + getRequestingHost());\r\n\t      System.out.println(\"Requesting Port  : \" + getRequestingPort());\r\n\t      System.out.println(\"Requesting Prompt : \" + getRequestingPrompt());\r\n\t      System.out.println(\"Requesting Protocol: \"\r\n\t          + getRequestingProtocol());\r\n\t      System.out.println(\"Requesting Scheme : \" + getRequestingScheme());\r\n\t      System.out.println(\"Requesting Site  : \" + getRequestingSite());\r\n\t      return new PasswordAuthentication(username, password.toCharArray());\r\n\t    }\r\n\t  }\t\r\n}\r\n<\/pre>\n<p>ActionScript 3<\/p>\n<pre>\r\nimport flash.display.Sprite;\r\n    import flash.errors.*;\r\n    import flash.events.*;\r\n    import flash.net.URLRequest;\r\n    import flash.net.URLStream;\r\n\timport flash.utils.ByteArray;\r\n\r\n\tvar stream:URLStream;\r\n\tvar mjpegBuffer:ByteArray = new ByteArray();\r\n\t\/\/ The actual image\r\n\tvar imageBytes:ByteArray; \/\/ = new ByteArray();\r\n\t\/\/ The chars at the end of the image\r\n\tvar endPos:String = \"\\n--myboundary\";\r\n\r\n\t\/\/ Started to find, finished finding\r\n\tvar done:Boolean = false;\r\n\tvar started:Boolean = false;\r\n\t\t\t\r\n\t\/\/ Don't know why I have to save these to a ByteArray to do the comparisons but it seems I do\r\n\tvar startBytes:ByteArray = new ByteArray();\r\n\tvar startByte:int = 0xFF;\r\n\tvar secondByte:int = 0xD8;\r\n\tstartBytes.writeByte(0xFF);\r\n\tstartBytes.writeByte(0xD8);\t\t\t\r\n\ttrace(startBytes.length);\r\n\tvar startNum:int = startBytes[0];\r\n\ttrace(startNum);\r\n\tvar nextNum:int = startBytes[1];\r\n\ttrace(nextNum);\r\n\r\n\t\/\/ Open the stream\t\t\t\r\n\tstream = new URLStream();\r\n    var request:URLRequest = new URLRequest(\"http:\/\/192.168.1.10\/mjpg\/video.mjpg?resolution=160x90&fps=1\");\r\n    configureListeners(stream);\r\n    try {\r\n    \tstream.load(request);\r\n    } catch (error:Error) {\r\n    \ttrace(\"Unable to load requested URL.\");\r\n    }\r\n\r\n\tfunction configureListeners(dispatcher:EventDispatcher):void {\r\n            dispatcher.addEventListener(ProgressEvent.PROGRESS, progressHandler);\r\n    }\r\n\r\n         function progressHandler(event:Event):void \r\n\t\t {\r\n\t\t \ttrace(\"Running\");\r\n\t\t\tstream.readBytes(mjpegBuffer,mjpegBuffer.length,stream.bytesAvailable);\r\n\t\t\tfor (var i:int = 0; i < mjpegBuffer.length; i++)\r\n\t\t\t{\r\n\t\t\t\tvar currentByte:int = mjpegBuffer[i];\r\n\t\t\t\tvar nextByte:int = mjpegBuffer[i+1];\r\n\t\t\t\tvar thirdByte:int = mjpegBuffer[i+2];\r\n\t\t\t\tvar fourthByte:int = mjpegBuffer[i+3];\r\n\r\n\t\t\t\t\/\/var randNum:Number = Math.random();\r\n\t\t\t\t\/\/if (randNum > .5 && randNum < .6) { trace(currentByte); }\r\n\t\t\t\t\r\n\t\t\t\tif (!started)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (currentByte == startNum &#038;&#038; nextByte == nextNum)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t\ttrace(\"Started\");\r\n\t\t\t\t\t\t\tstarted = true;\r\n\t\t\t\t\t\t\timageBytes = new ByteArray();\r\n\t\t\t\t\t\t\timageBytes.writeByte(currentByte);\r\n\t\t\t\t\t\t\t\/\/imageBytes.writeByte(0xD8); \/\/ Gets written in the else\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tif (currentByte == endPos.charCodeAt(0) &#038;&#038; nextByte == endPos.charCodeAt(1) &#038;&#038; thirdByte == endPos.charCodeAt(2) &#038;&#038; fourthByte == endPos.charCodeAt(3))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ttrace(\"done\");\r\n\t\t\t\t\t\ttrace(imageBytes);\r\n\t\t\t\t\t\tdone = true;\r\n\t\t\t\t\t\tstarted = false;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tvar loader:Loader = new Loader();\r\n\t\t\t\t\t\tloader.contentLoaderInfo.addEventListener(Event.COMPLETE, onByteArrayLoaded)\r\n\t\t\t\t\t\tloader.loadBytes(imageBytes);\r\n\t\t\t\t\t\t\/\/stream.close();\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\timageBytes.writeByte(currentByte);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n        }\r\n\t\t\r\n\t\tfunction onByteArrayLoaded(e:Event):void  \r\n\t\t{\r\n\t\t\tvar loader:Loader = Loader(e.target.loader);\r\n\t\t\tloader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onByteArrayLoaded);\r\n\r\n\t\t\tvar bitmapData:BitmapData = Bitmap(e.target.content).bitmapData;\r\n\t\t\t\r\n\t\t\t\/\/sprLoaded.graphics.clear();\r\n\t\t\tgraphics.beginBitmapFill(bitmapData);\r\n\t\t\tgraphics.drawRect(0,0,bitmapData.width, bitmapData.height);\r\n\t\t\tgraphics.endFill();\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t\r\n\t\t}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>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 &hellip; <a href=\"http:\/\/www.walking-productions.com\/notslop\/2010\/04\/20\/motion-jpeg-in-flash-and-java\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Motion JPEG in Flash and Java<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[47],"tags":[],"class_list":["post-1706","post","type-post","status-publish","format-standard","hentry","category-thoughts"],"_links":{"self":[{"href":"http:\/\/www.walking-productions.com\/notslop\/wp-json\/wp\/v2\/posts\/1706","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.walking-productions.com\/notslop\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.walking-productions.com\/notslop\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.walking-productions.com\/notslop\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.walking-productions.com\/notslop\/wp-json\/wp\/v2\/comments?post=1706"}],"version-history":[{"count":3,"href":"http:\/\/www.walking-productions.com\/notslop\/wp-json\/wp\/v2\/posts\/1706\/revisions"}],"predecessor-version":[{"id":1709,"href":"http:\/\/www.walking-productions.com\/notslop\/wp-json\/wp\/v2\/posts\/1706\/revisions\/1709"}],"wp:attachment":[{"href":"http:\/\/www.walking-productions.com\/notslop\/wp-json\/wp\/v2\/media?parent=1706"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.walking-productions.com\/notslop\/wp-json\/wp\/v2\/categories?post=1706"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.walking-productions.com\/notslop\/wp-json\/wp\/v2\/tags?post=1706"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}