Project Description
During the last project iteration (2nd half of 2007), we study ways to stream
raw data along with video and audio. The key point is to keep data in sync
with the video in a precise manner. The domain of application is wide, and we
focus us on geo-localisation of sensors : for example streaming the position and field
of view of a camera in real-time in order to store geo-localized video or project
it back in a 3D environnment.
We use live555 library, to stream
data and plan to customize a player in order to support live streaming (for example
VLC Media player)
Protocol Parsing with Java
We also used to work with Javatm (Rich client, RCP client, server,..). We face in numerous
situations where we have to parse headers in protocols, files.. which is seemly easier in C/C++
using struct.
There is no equivalent to structs in Java, but Java5 bring us annotations, which could be used
to emulate them. Note that we are aware that the solution shown here is not a replacement,
leak features, performs probably bad and slow.. but we (pretentious java fanboys) think it's still
worth 2 minutes to look at it.
Annotations
Our proposal is to define a class with public fields in order to model the header to parse.
All fields in class are described with a new annotation BitArrayField that describe
the field with its positioning and it length (in bit). In our example we took the RTP header
specified in RFC 3550.
package rtp;
import org.dreier.bitarray.BitArrayField;
/**
* RTP packet header.
* @author Frederic Dreier
*
*/
public class RtpHeaderBA {
@BitArrayField(offset = 0, length = 2)
public int version;
@BitArrayField(offset = 2, length = 1)
public boolean padding;
@BitArrayField(offset = 3, length = 1)
public boolean extension;
@BitArrayField(offset = 4, length = 4)
public int ccCsrc;
@BitArrayField(offset = 8, length = 1)
public boolean marker;
@BitArrayField(offset = 9, length = 7)
public int payloadType;
@BitArrayField(offset = 16, length = 16)
public int sequenceNbr;
@BitArrayField(offset = 32, length = 32)
public long timestamp;
@BitArrayField(offset = 64, length = 32)
public long ssrc;
@BitArrayField(offset = 96, length = 32)
public long csrc;
}
Then using it is straightforward just put your data in a byte array and give it for parsing. No
config needed, no XML. The header will be initialized with the buffer data.
byte[] data = ...;
// prepare empty structure
RtpHeaderBA header = new RtpHeaderBA();
// extract it
BitExtractor b = new BitExtractor();
b.parse(data, header);
// plenty of place for other code here !!
...
To run this example all you need is the BitArrayField annotation and the
BitExtractor class. We provide you our quick and dirty implementation.
The BitArrayField is kept as simple as possible as you will see:
/*
* (C) Copyright 2007-2008, by Frederic Dreier
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
* License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this library; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* [Java is a trademark or registered trademark of Sun Microsystems, Inc.
* in the United States and other countries.]
*/
package org.dreier.bitarray;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
/**
* Annotation used by BitExtractor.
* @author dreier Frederic Dreier
*/
public @interface BitArrayField {
int offset();
int length();
boolean unsigned() default true;
}
The BitExtractor is a little bit more complex. It could be improved, extended, refactored..
Or just use it has is :-)
/*
* (C) Copyright 2007-2008, by Frederic Dreier
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
* License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this library; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*
* [Java is a trademark or registered trademark of Sun Microsystems, Inc.
* in the United States and other countries.]
*/
package org.dreier.bitarray;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Quick and Dirty implementation of a Bit Extractor.
*
* @author Frederic Dreier
*/
public class BitExtractor {
private static final int[] MASK_A = { 0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01 };
private static final int[] MASK_P = { 0xFF, 0xFE, 0xFC, 0xF8, 0xF0, 0xE0, 0xC0, 0x80, 0x00 };
private HashMap mapping = new HashMap();
public MetaData register(Class clazz) {
synchronized (mapping) {
MetaData md = mapping.get(clazz);
if (md == null) {
// retieve annotations for this class
md = new MetaData();
for (Field fld : clazz.getDeclaredFields()) {
int cnt = 0;
for (Annotation ant : fld.getAnnotations()) {
if (ant instanceof BitArrayField) {
// check consistency.
if (cnt > 0) {
throw new RuntimeException("Field[" + fld.getName() + "] of class [" + clazz
+ "] should only have one annotation of type 'BitArrayField'.");
}
cnt++;
// update metadata
BitArrayField bfAnt = (BitArrayField) ant;
MetaField mf = new MetaField(bfAnt.offset(), bfAnt.length(), bfAnt.unsigned(), fld
.getType(), fld);
md.add(mf);
}
}
}
// cache meta data
mapping.put(clazz, md);
}
return md;
}
}
/**
* Parse data hand update header.
*
* @param data
* @param struct
*/
public void parse(byte[] data, Object struct) {
// get MetaData
MetaData md = register(struct.getClass());
for (MetaField f : md.fields) {
if (f.type.equals(int.class) || f.type.equals(long.class)) {
// integer field...
int bByte = f.offset / 8;
int bMask = MASK_A[f.offset % 8];
int end = f.offset + f.length;
int pads = 8 - (f.offset + f.length) % 8;
int eByte = end / 8;
// first byte
long acc = data[bByte] & bMask;
if (bByte == eByte) {
acc = acc >> pads;
} else {
// loop bytes
for (int ptr = bByte + 1; ptr < eByte; ptr++) {
acc = (acc << 8) | (data[ptr] & 0xFF);
}
// last byte (could be smaller than 8 bits)
acc = (data[eByte] & MASK_P[pads] | (acc << 8)) >> pads;
}
// set value in struct
try {
if (f.type.equals(int.class)) {
// int
f.classField.setInt(struct, (int) acc);
} else {
// long
f.classField.setLong(struct, acc);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} else if (f.type.equals(boolean.class)) {
// boolean
if (f.length != 1) {
throw new RuntimeException("field [" + f.classField.getName()
+ "] is a boolean and should have a length of '1'.");
}
int bIdx = f.offset / 8;
int b = f.offset - bIdx * 8;
int mask = 1 << (7 - b);
int acc = data[bIdx] & mask;
// set value in struct
try {
f.classField.setBoolean(struct, acc != 0);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} else if (f.type.equals(String.class)) {
// String (only on plain byte)
if (f.length % 8 != 0) {
throw new RuntimeException("field [" + f.classField.getName()
+ "] is a String and should have a length of 'n*8'.");
}
if (f.offset % 8 != 0) {
throw new RuntimeException("field [" + f.classField.getName()
+ "] is a String and should have an offset of 'n*8'.");
}
int charCnt = f.length / 8;
int bByte = f.offset / 8;
String result = new String(data, bByte, charCnt);
// set value in struct
try {
f.classField.set(struct, result);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} else {
throw new RuntimeException("Unsupported class [" + f.type + "]");
}
}
}
// ================================================================
// Inner classes
// ================================================================
/**
* A class meta data.
*
* @author Frederic Dreier
*/
public class MetaData {
private ArrayList fields = new ArrayList();
/**
* Add a field. Covenient method
*
* @param field
*/
public void add(MetaField field) {
fields.add(field);
}
/**
* Get fields.
*
* @return
*/
public List getFields() {
return fields;
}
}
/**
* Bit array field.
*
* @author dreier
*/
public class MetaField {
private int offset;
private int length;
private Field classField;
private boolean unsigned;
private Class type;
public MetaField(int offset, int length, boolean unsigned, Class type, Field classField) {
this.offset = offset;
this.length = length;
this.unsigned = unsigned;
this.type = type;
this.classField = classField;
}
public int getLength() {
return length;
}
public int getOffset() {
return offset;
}
public Class getType() {
return type;
}
public boolean isUnsigned() {
return unsigned;
}
}
}
Screenshots
-
Downloads
-
Links
Armasuisse
Frédéric Dreier AI/AL Homepage
Annotation in Java (SUN)
|