package mp4.util.atom;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.IOException;
/**
* A byte stream class contains the data from the mp4 file. We use this
* class instead of the byte[] because we need
* - an array that can grow & shrink
* - to keep track of the current index value in order to append easily
* - create a stream that's larger than 2B elements (Integer.MAX_VALUE)
*/
public class ByteStream {
// the byte stream data
private byte[] data;
// the number of bytes used in the stream
private int used;
// Initial size if one is not specified
private static final int INIT_SIZE = 4096;
// The amount we increase the array by
private static final int GROW_FACTOR = 2;
/**
* Construct an empty byte stream with the default initial size
*/
public ByteStream() {
this(INIT_SIZE);
}
/**
* Construct an empty byte stream with the specified initial size
* @param size the initial size
*/
public ByteStream(long size) {
if (size > Integer.MAX_VALUE) {
throw new AtomError("Unable to handle data size larger than int");
}
data = new byte[(int) size];
this.used = 0;
}
/**
* Copy constructor for a byte stream.
* @param old the stream to copy
*/
public ByteStream(ByteStream old) {
data = new byte[old.data.length];
System.arraycopy(old.data, 0, data, 0, data.length);
used = old.used;
}
/**
* Return the space used by the byte stream
* @return the space used by the byte stream
*/
public long length() {
return used;
}
/**
* Reserve space in the byte stream
* @param size the number of bytes to reserve
*/
public void reserveSpace(long size) {
if (used + size > data.length) {
grow();
}
used += size;
}
/**
* Read data from an input stream into the byte stream
* @param in the input stream
* @return the number of bytes read
*/
public int read(DataInputStream in) throws IOException {
int num = in.read(data);
used += num;
return num;
}
/**
* Add a byte to the end of the array
* @param b the byte to add
*/
public void addData(byte b) {
data[used++] = b;
if (used == data.length) {
grow();
}
}
/**
* Add a byte to the byte stream at the specified offset.
* @param offset the offset from the start
* @param b the value to add
*/
public final void addData(int offset, byte b) {
assert offset + 1 <= used;
data[offset] = b;
}
/**
* Add a byte array to the end of the byte stream
* @param b the byte array
* @param len the number of bytes from the byte array
*/
public final void addData(byte[] b, int len) {
if (used + len >= data.length) {
grow();
}
System.arraycopy(b, 0, data, used, len);
}
/**
* Add the data to the byte stream at the specified offset
* @param offset the offset in the byte stream
* @param b the byte array whose contends are added to the byte stream
*/
public final void addData(int offset, byte[] b) {
assert offset + b.length <= used;
System.arraycopy(b, 0, data, offset, b.length);
}
/**
* Get the byte value at the specified offset in the stream
* @param offset the offset
* @return the byte value at the specified offset
*/
public final byte getData(int offset) {
assert offset <= used;
return data[offset];
}
/**
* Return data of the specified size in a byte array.
* @param from the starting offset of the data
* @param to the ending offset of the data
* @return a copy of the byte data from the stream
*/
public final byte[] getData(int from, int to) {
assert from + to <= used;
// copyOfRange not supported in JDK1.5
//return Arrays.copyOfRange(data, from, to);
byte[] na = new byte[to-from];
System.arraycopy(data, from, na, 0, to-from);
return na;
}
/**
* 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 final long getUnsignedInt(int off) {
return ((long)(data[off] & 0xff) << 24) |
((long)(data[off+1] & 0xff) << 16) |
((long)(data[off+2] & 0xff) << 8) |
(long)(data[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 final int getUnsignedShort(int off) {
return ((data[off] & 0xff) << 8) | (data[off+1] & 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 final long getLong(int off) {
return ((long)(data[off] & 0xff) << 56) |
((long)(data[off+1] & 0xff) << 48) |
((long)(data[off+2] & 0xff) << 40) |
((long)(data[off+3] & 0xff) << 32) |
((long)(data[off+4] & 0xff) << 24) |
((long)(data[off+5] & 0xff) << 16) |
((long)(data[off+6] & 0xff) << 8) |
(long)(data[off+7] & 0xff);
}
/**
* Get a fixed point value from two 16 bit values located in a
* 32-bit word.
* @param off the offset in the byte array where value is located
* @return the fixed point value of the 32-bit data.
*/
public final double getFixedPoint(int off) {
int integerPart = ((data[off] & 0xff) << 8) |
((data[off+1] & 0xff));
int fractionPart = ((data[off+2] & 0xff) << 8) |
((data[off+3] & 0xff));
double val = Double.valueOf(integerPart + "." + fractionPart).doubleValue();
return val;
}
/**
* Add a fixed point value to the byte stream at the specified index.
* @param off the offset
* @param integerPart the integer part of the fixed point value
* @param fractionPart the fraction part of the fixed point value
*/
public void addFixedPoint(int off, int integerPart, int fractionPart) {
addUnsignedShort(off, integerPart);
addUnsignedShort(off+2, fractionPart);
}
/**
* Add an unsigned integer (4 bytes) to the byte stream.
* @param data the data
*/
public void addUnsignedInt(long val) {
used += 4;
if (used >= data.length) {
grow();
}
addUnsignedInt(0, val);
}
/**
* Add an unsigned integer to the byte stream at the specified offset.
* This method assumes the space has already been allocated by advancing
* the used pointer.
* @param offset the offset from the start of the byte stream
* @param val the integer value to add to the stream
*/
public void addUnsignedInt(int offset, long val) {
if (offset + 4 > used) {
throw new AtomError("Not enough space allocated for the data");
}
data[offset++] = (byte) ((val >> 24) & 0xff);
data[offset++] = (byte) ((val >> 16) & 0xff);
data[offset++] = (byte) ((val >> 8) & 0xff);
data[offset] = (byte) (val & 0xff);
}
/**
* Add along to byte stream at the specified offset.
* This method assumes the space has already been allocated by advancing
* the used pointer.
* @param offset the offset from the start of the byte stream
* @param val the integer value to add to the stream
*/
public void addLong(int offset, long val) {
if (offset + 8 > used) {
throw new AtomError("Not enough space allocated for the data");
}
data[offset++] = (byte) ((val >> 56) & 0xff);
data[offset++] = (byte) ((val >> 48) & 0xff);
data[offset++] = (byte) ((val >> 40) & 0xff);
data[offset++] = (byte) ((val >> 32) & 0xff);
data[offset++] = (byte) ((val >> 24) & 0xff);
data[offset++] = (byte) ((val >> 16) & 0xff);
data[offset++] = (byte) ((val >> 8) & 0xff);
data[offset] = (byte) (val & 0xff);
}
/**
* Add an unsigned short (16-bits) to the byte stream at the specified offset.
* @param offset the byte stream offset
* @param val the value to add.
*/
public void addUnsignedShort(int offset, int val) {
if (offset + 2 > used) {
throw new AtomError("Not enough space allocated for the data");
}
data[offset++] = (byte) ((val >> 8) & 0xff);
data[offset] = (byte) (val & 0xff);
}
/**
* Write the byte stream data to the specified location.
* @param out where the data goes
* @throws IOException if there is an error writing the data
*/
public void writeData(DataOutput out) throws IOException {
out.write(data, 0, used);
}
/**
* Grow the array and copy the data from the old to the new array
*/
private void grow() {
byte[] newdata = new byte[data.length * GROW_FACTOR];
System.arraycopy(data, 0, newdata, 0, used);
data = newdata;
}
public void collapse64To32(int offset) {
System.arraycopy(data, offset+4, data, offset, used-(offset+4));
used -= 4;
}
}