package mp4.util.atom;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Arrays;
/**
* This class represents an atom in the mpeg-4 file. See the mpeg-4 file
* documentation:
* <ul>
* <li><a href="http://standards.iso.org/ittf/PubliclyAvailableStandards/index.html">ISO Standard 14496-12:2005</a>
* <li><a href="developer.apple.com/DOCUMENTATION/QuickTime/QTFF/qtff.pdf ">QuickTime File Format Specification</a>
* </ul>
*
* In the ISO spec, an Atom is a Box. We use the QuickTime name, Atom.
*/
public abstract class Atom {
// the raw mpeg4 data for the atom
protected ByteStream data;
// the size of the atom, an unsigned int so we use a long
protected long size;
// the type, represented using the characters in the byte stream
protected byte[] type;
// The basic unit size of an atom, in bytes
public static final int ATOM_WORD = 4;
// Number of bytes for 64-bit atom size
public static final int LARGE_SIZE_SIZE= 8;
// The canonical atom size, which includes the type and the size
public static final int ATOM_HEADER_SIZE = 8;
public static final byte COPYRIGHT_BYTE_VALUE = -87;
protected int headerSize = ATOM_HEADER_SIZE;
protected boolean is64BitAtom = false;
/**
* Create an atom with the specified size and type
* @param size the size inclusive of the size and type
* @param type the atom's type
*/
protected Atom(long size, byte[] type) {
this.size = size;
this.type = type;
}
/**
* Copy constructor.
* @param old the atom to copy
*/
protected Atom(Atom old) {
this.size = old.size;
this.type = old.type;
}
/**
* Create an atom but the size isn't specified, and needs
* to be filled in later.
* @param type the atom's type
*/
protected Atom(byte[] type) {
this.type = type;
}
/**
* Set the size of the atom
* @param size the atom's size
*/
public void setSize(long size) {
this.size = size;
}
/**
* Return the size of the atom
* @return
*/
public long size() {
return size;
}
/**
* Return the size of the data part of the atom
* @return the size of the atom's data part
*/
public long dataSize() {
if (size == 0)
return 0;
return size - headerSize;
}
public long pureDataSize() {
return dataSize();
}
/**
* Return the atom's type as an integer
* @return the atom's type as an integer
*/
public int getType() {
return getAtomValue(type);
}
/**
* @return true if this atom is a container atom
*/
public abstract boolean isContainer();
/**
* The visitor pattern accept method
* @param v an atom visitor
*/
public abstract void accept(AtomVisitor v) throws AtomException;
/**
* Write the atom header data to the output stream. The header data
* includes the size and type information.
* @param out where the output goes
* @throws IOException if there is an error writing the data
*/
public void writeHeader(DataOutput out) throws IOException {
byte[] sizeData = new byte[ATOM_WORD];
unsignedIntToByteArray(sizeData, 0, size);
out.write(sizeData);
out.write(type);
}
/**
* Return the atom as a string
* @return the string for the atom
*/
public String toString() {
return "Atom " + new String(type) + " size " + size;
}
/**
* Utility function that converts a byte array to an integer starting
* at the specified array index.
* The byte array must be at least 4 bytes in length.
* @param b the byte array
* @param off offset to start the conversion
* @return the integer value of the byte array
*/
public static final int byteArrayToInt(byte[] b, int off) {
return (int) byteArrayToUnsignedInt(b, off);
}
/**
* Java doesn't have unsigned types, so we need to use the next
* larger signed type.
* @param b the byte array
* @param off offset to start the conversion
* @return the unsigned integer value of the byte array
*/
public static final long byteArrayToUnsignedInt(byte[] b, int off) {
return ((long)(b[off] & 0xff) << 24) |
((long)(b[off+1] & 0xff) << 16) |
((long)(b[off+2] & 0xff) << 8) |
(long)(b[off+3] & 0xff);
}
/**
* Java doesn't have unsigned types, so we need to use the next
* larger signed type.
* @param b the byte array
* @param off offset to start the conversion
* @return the unsigned integer value of the byte array
*/
public static final long byteArrayToLong(byte[] b, int off) {
return ((long)(b[off] & 0xff) << 56) |
((long)(b[off+1] & 0xff) << 48) |
((long)(b[off+2] & 0xff) << 40) |
((long)(b[off+3] & 0xff) << 32) |
((long)(b[off+4] & 0xff) << 24) |
((long)(b[off+5] & 0xff) << 16) |
((long)(b[off+6] & 0xff) << 8) |
(long)(b[off+7] & 0xff);
}
/**
* Write the unsigned int to the byte array
* @param b the byte array
* @param off the offset into the byte array
* @param data the data
*/
public static final void unsignedIntToByteArray(byte b[], int off, long data) {
b[off] = (byte) ((data >> 24) & 0xff);
b[off+1] = (byte) ((data >> 16) & 0xff);
b[off+2] = (byte) ((data >> 8) & 0xff);
b[off+3] = (byte) (data & 0xff);
}
/**
* Convert the atom type characters to an integer value
* @param val the characters
* @return the integer
*/
public static final int getAtomValue(byte[] val) {
return Atom.byteArrayToInt(val, 0);
}
/**
* Convert the type name to a class name. The class name converts
* the first character of the type to uppercase, then prepends
* the package name, and appends 'Atom' string.
*
* @param typ the type represented as a byte array
* @return the class name for the type
*/
public static String typeToClassName(byte[] typ, String prefix) {
if (prefix == null)
prefix = "";
String str = new String(typ);
// \u00a9
StringBuilder strBuffer = new StringBuilder();
for (int i=0;i<str.length();i++) {
Character firstChar = str.charAt(i);
String firstStr;
if (typ[i] == COPYRIGHT_BYTE_VALUE) // (c) copyright sign
firstStr = "Cprt";
else if (Character.isLetter(firstChar)) {
firstStr= Character.toString(firstChar);
if (i == 0)
firstStr = firstStr.toUpperCase();
}
else if (Character.isDigit(firstChar)) {
if (i == 0)
firstStr = "N" + firstChar;
else
firstStr = firstChar.toString();
}
else
firstStr = "X" + (((int)typ[i])&0xff);
strBuffer.append(firstStr);
}
String clsName = strBuffer.toString();
return "mp4.util.atom." + prefix + new String(clsName) + "Atom";
}
/**
* Write the byte stream to the specified output.
* @param out where the output goes
* @throws IOException if there is a problem writing the data
*/
public void writeData(DataOutput out) throws IOException {
writeHeader(out);
if (data != null && data.length() > 0)
data.writeData(out);
}
/**
* Read the data from the input stream in to the atom.
* @param in the input stream
* @throws AtomException
*/
public void readData(DataInputStream in) throws AtomException {
data = new ByteStream(pureDataSize());
if (pureDataSize() > 0) {
try {
data.read(in);
} catch (IOException e) {
throw new AtomException("IOException while reading mp4 file");
}
}
}
/**
* Allocate space for the data needed by the atom.
* @param size the size of data in bytes
*/
public void allocateData(long size) {
assert data == null;
data = new ByteStream(size);
data.reserveSpace(size);
setSize(size + headerSize);
}
public boolean isLargeAtom() {
return is64BitAtom;
}
public void setLargeAtom(boolean big) {
is64BitAtom = big;
if (big)
headerSize = ATOM_HEADER_SIZE + LARGE_SIZE_SIZE;
else
headerSize = ATOM_HEADER_SIZE;
}
public static boolean typeEquals(byte[] t1, byte[] t2) {
return Arrays.equals(t1, t2);
}
public static Atom typeToAtom(byte[] word, String classPrefix) throws AtomException {
classPrefix = classPrefix.toLowerCase();
try {
Atom atom = null;
String typeForClass = Atom.typeToClassName(word, classPrefix);
try {
Class<?> cls = Class.forName(typeForClass);
atom = (Atom) cls.newInstance();
//MP4Log.log(getPrefix() + "AtomClass: " + cls + " (size:" + size + ")");
} catch (ClassNotFoundException e) {
if (classPrefix != null && classPrefix.length() > 0) {
typeForClass = Atom.typeToClassName(word, null);
try {
Class<?> cls = Class.forName(typeForClass);
atom = (Atom) cls.newInstance();
//MP4Log.log(getPrefix() + "AtomClass: " + cls + " (size:" + size + ")");
} catch (ClassNotFoundException e1) {
}
}
if (atom == null)
atom = new UnknownAtom(word);
}
return atom;
} catch (InstantiationException e) {
throw new AtomException("Unable to instantiate atom");
} catch (IllegalAccessException e) {
throw new AtomException("Unabel to access atom object");
}
}
public int getHeaderSize() {
return headerSize;
}
}