Java Media Framework vs IP Camera JPEG/MJPEG

http://dragosc.itmcd.ro/uncategorized/java-media-framework-vs-ip-camera-jpegmjpeg-complete-overview-sources/

————————————————————————————————————————————————————————————————————

NOTE:

+ I will try not to re-write again all the informations I already wrote in “Java Media Framwork vs IP Camera JPEG/MJPEG” a few years ago. This article is only a review of what I wrote back then, plus more info about my implementation of DataSource for JPEG/MJPEG IP Cameras.

+ Another note is, that this project of mine, has been attached to my own company which I founded a few years ago and I’m still trying to grow it. Project licence will be LGPL.

OK… Since Oracle bought SUN a few years ago, but their website still doesn’t reflect all info about Java Media Framework, it’s still best do Google after the info about JMF, than give you a specific link.

The article that belongs to Chris Adamson, Java Media Development with QuickTime for Java, still exists and, believe me it’s pretty useful. It explains how to create a new JMF plugin using the Java lib for Quick Time, released by Apple. Only wish I had access to them, but that’s another problem.

Even though at first I kinda disliked working with JMF due to it’s huge lack of documentation (some would say, it has such thing, I would say: NO), I discovered in time that it’s not that complicated. The principles are pretty simple, and even though the library hasn’t seen another update since 2001, its extendability is pretty high. We’ve already seen a plugin from mr. Adamson for qt-java, and from what I’ve known there are other plugins there on the web.

The main idea in Chris Adamson’s article is to create two classes, one to extend DataSource and one to extend PushBufferStream or PullBufferStream from JMF.

The DataSource class is needed by JMF Players to read the info you need to display, while the BufferStream is actually the reader of the “video” stream.

In our case, we can say the BufferStream class is a bit more complicated, but as we look closer it is not. We do have to check two cases though.

  1. Cameras that do have a fully MJPEG stream.
  2. Cameras that only have JPEG ‘streams’. In this case, it’s not a real stream actually, and we have to simulate one by re-reading the source in a loop.

The only problem we couldn’t solve in a nice manner was creating a handler class, so we sticked to this:

package org.itmc.ipcamera.media.content.unknown;

public class Handler extends com.sun.media.content.unknown.Handler

{}

If you download the sources, you’ll discover that each class has a static function for testing, also including an example for how to use it.

You can download the latest sources from:

http://svn.itmcd.ro/trunk/jmf/ipcamera

user & pass: anonymous

We’re looking for help to improve our code, and make from “ipcamera” an API worth to use for all types of streaming used by ip cameras, so if you think you’re smart enough and want to help, or if you used this API in your application and want to share with us some of your code, please contact us. Leave us a comment to this post, and we will contact u ASAP. (The comment will not be published unless you request so.)

Also, if you think our code has helped you, please try and donate us a few coins. This is free LGPL work, but in order to continue our research, we do need some funds there.

————————————————————————————————————————————————————————————————————

以下为上面提到的老版本

http://dragosc.itmcd.ro/it-stuff/java-media-framework-vs-ip-camera-jpegmjpeg/

NOTE:

+ This article is pretty deprecated. I do not say that the info here is false, but I would like to write a new article about this. Please leave me comments with what you want to know, and I will try do add more info to the article. And ofcourse, more sources.

+ A Romanian translation for this article is found here.

I really don”t think I”m either the first or the last to try and obtain images from and ip camera and than use them with JMF. So… after a few days of reading and trying to understand how JMF works, as I already had a JPEG/MJPEG grabber, here is my solutions:

As Chris Adamson explains in his article, Java Media Development with QuickTime for Java, to create a new JMF plugin you need to create two classes, one to extend DataSource and one to extend PushBufferStream or PullBufferStream from JMF. For DataSource I used PushBufferDataSource, which implements the parent class: DataSource.

The most important element is the fact that Datasource must be placed int a package named smth like this: name1.name2.someothername.media.protocol.numeprotocol (i.e. com.sun.media.protocol.http, com.sun.media.protocol.rtp, com.ibm.media.protocol.file).

For the DataSource class I used the example given by SUN in their ScreenGrabber, and I only modified the streaming class name. I won”t reveal the image grabing class, as SUN forum is allready filled with such examples, but I will reveal the streaming class under a surogated protocol I called htmjpeg. I”m sure it won”t take long to you to understant that the protocol is just a simple name, and can be easily changed in my example:

JMF MJPEG Plugin (for Download)

[[wppald_inposts|Donation for JMF Work]]

package com.itmc.media.protocol.htmjpeg;

import com.itmc.ipcamera.mjpeg.grabber.mjpegGrabber;

import java.awt.*;

import java.awt.image.BufferedImage;

import javax.media.*;

import javax.media.format.*;

import javax.media.protocol.*;

import java.io.IOException;

public class mjpegStream extends mjpegGrabber implements PushBufferStream, Runnable {

protected ContentDescriptor cd = new ContentDescriptor(ContentDescriptor.RAW);

protected int maxDataLength;

protected int [] data;

protected Dimension size;

protected RGBFormat rgbFormat;

protected boolean started;

protected Thread thread;

protected float frameRate = 7.0f;

protected BufferTransferHandler transferHandler;

protected Control [] controls = new Control[0];

protected int x, y, width, height;

protected Robot robot = null;

protected BufferedImage im = null;

protected boolean mjpeg = false;

public mjpegStream(MediaLocator locator) {

super("http:" + locator.getRemainder());

System.out.println("http://" + locator.getRemainder());

try {

super.connect();

im = super.readJPEG();

super.disconnect();

} catch(Exception e) {

}

if (im == null) {

try {

super.connect();

im = super.readMJPEG();

mjpeg = true;

super.disconnect();

} catch(Exception e) {

}

}

size = new Dimension(im.getWidth(), im.getHeight());

// im = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);

// size = new Dimension(100, 100);

maxDataLength = size.width * size.height * 3;

rgbFormat = new RGBFormat(

size, maxDataLength,

Format.intArray,

frameRate,

32,

0xFF0000, 0xFF00, 0xFF,

1, size.width,

VideoFormat.FALSE,

Format.NOT_SPECIFIED

);

System.out.println(rgbFormat.getFrameRate());

// generate the data

data = new int[maxDataLength];

thread = new Thread(this, "Htmjpeg Grabber");

}

/***************************************************************************

* SourceStream

***************************************************************************/

public ContentDescriptor getContentDescriptor() {

return cd;

}

public long getContentLength() {

return LENGTH_UNKNOWN;

}

public boolean endOfStream() {

return false;

}

/***************************************************************************

* PushBufferStream

***************************************************************************/

int seqNo = 0;

public Format getFormat() {

return rgbFormat;

}

public void read(Buffer buffer) throws IOException {

synchronized (this) {

super.connect();

Object outdata = buffer.getData();

if (outdata == null || !(outdata.getClass() == Format.intArray) ||

((int[])outdata).length < maxDataLength) {

outdata = new int[maxDataLength];

buffer.setData(outdata);

}

buffer.setFormat( rgbFormat );

buffer.setTimeStamp( (long) (seqNo * (1000 / frameRate) * 1000000) );

BufferedImage bi = mjpeg?super.readMJPEG():super.readJPEG();

bi.getRGB(0, 0, size.width, size.height, (int[])outdata, 0, size.width);

buffer.setSequenceNumber( seqNo );

buffer.setLength(maxDataLength);

buffer.setFlags(Buffer.FLAG_KEY_FRAME);

buffer.setHeader( null );

seqNo++;

if (!mjpeg) super.disconnect();

}

}

public void setTransferHandler(BufferTransferHandler transferHandler) {

synchronized (this) {

this.transferHandler = transferHandler;

notifyAll();

}

}

void start(boolean started) {

synchronized ( this ) {

this.started = started;

if (started && !thread.isAlive()) {

thread = new Thread(this);

thread.start();

}

notifyAll();

}

}

/***************************************************************************

* Runnable

***************************************************************************/

public void run() {

while (started) {

synchronized (this) {

while (transferHandler == null && started) {

try {

wait(1000);

} catch (InterruptedException ie) {

}

} // while

}

if (started && transferHandler != null) {

transferHandler.transferData(this);

try {

Thread.currentThread().sleep( 10 );

} catch (InterruptedException ise) {

}

}

} // while (started)

} // run

// Controls

public Object [] getControls() {

return controls;

}

public Object getControl(String controlType) {

try {

Class cls = Class.forName(controlType);

Object cs[] = getControls();

for (int i = 0; i < cs.length; i++) {

if (cls.isInstance(cs[i]))

return cs[i];

}

return null;

} catch (Exception e) { // no such controlType or such control

return null;

}

}

}