/**
* MediaFrame is an Open Source streaming media platform in Java
* which provides a fast, easy to implement and extremely small applet
* that enables to view your audio/video content without having
* to rely on external player applications or bulky plug-ins.
*
* Copyright (C) 2004/5 MediaFrame (http://www.mediaframe.org).
*
* This program 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
* of the License, or (at your option) any later version.
*
* This program 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 program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
/**
This software module was originally developed by Apple Computer, Inc.
in the course of development of MPEG-4.
This software module is an implementation of a part of one or
more MPEG-4 tools as specified by MPEG-4.
ISO/IEC gives users of MPEG-4 free license to this
software module or modifications thereof for use in hardware
or software products claiming conformance to MPEG-4.
Those intending to use this software module in hardware or software
products are advised that its use may infringe existing patents.
The original developer of this software module and his/her company,
the subsequent editors and their companies, and ISO/IEC have no
liability for use of this software module or modifications thereof
in an implementation.
Copyright is not released for non MPEG-4 conforming
products. Apple Computer, Inc. retains full right to use the code for its own
purpose, assign or donate the code to a third party and to
inhibit third parties from using the code for non
MPEG-4 conforming products.
This copyright notice must be included in all copies or
derivative works. Copyright (c) 1999.
*/
package mediaframe.mpeg4.isofile;
import java.util.Vector;
import java.util.Date;
import java.io.IOException;
import org.ripple.power.sound.DataStream;
/**
* The <code>MP4Atom</code> object represents the smallest information block of
* the MP4 file. It could contain other atoms as children.
*/
public class MP4Atom {
/** Constanta, the type of the MP4 Atom. */
public final static int MP4AudioSampleEntryAtomType = MP4Atom
.typeToInt("mp4a");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4ChunkLargeOffsetAtomType = MP4Atom
.typeToInt("co64");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4ChunkOffsetAtomType = MP4Atom.typeToInt("stco");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4DataInformationAtomType = MP4Atom
.typeToInt("dinf");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4ESDAtomType = MP4Atom.typeToInt("esds");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4ExtendedAtomType = MP4Atom.typeToInt("uuid");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4HandlerAtomType = MP4Atom.typeToInt("hdlr");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4MediaAtomType = MP4Atom.typeToInt("mdia");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4MediaHeaderAtomType = MP4Atom.typeToInt("mdhd");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4MediaInformationAtomType = MP4Atom
.typeToInt("minf");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4MovieAtomType = MP4Atom.typeToInt("moov");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4MovieHeaderAtomType = MP4Atom.typeToInt("mvhd");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4SampleDescriptionAtomType = MP4Atom
.typeToInt("stsd");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4SampleSizeAtomType = MP4Atom.typeToInt("stsz");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4CompactSampleSizeAtomType = MP4Atom
.typeToInt("stz2");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4SampleTableAtomType = MP4Atom.typeToInt("stbl");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4SampleToChunkAtomType = MP4Atom
.typeToInt("stsc");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4SoundMediaHeaderAtomType = MP4Atom
.typeToInt("smhd");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4TrackAtomType = MP4Atom.typeToInt("trak");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4TrackHeaderAtomType = MP4Atom.typeToInt("tkhd");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4VideoMediaHeaderAtomType = MP4Atom
.typeToInt("vmhd");
/** Constanta, the type of the MP4 Atom. */
public final static int MP4VisualSampleEntryAtomType = MP4Atom
.typeToInt("mp4v");
/** The size of the atom. */
protected long size;
/** The type of the atom. */
protected int type;
/** The user's extended type of the atom. */
protected String uuid;
/** The amount of bytes that readed from the mpeg stream. */
protected long readed;
/** The childrend of this atom. */
protected Vector<MP4Atom> children = new Vector<MP4Atom>();
public MP4Atom(long size, int type, String uuid, long readed) {
super();
this.size = size;
this.type = type;
this.uuid = uuid;
this.readed = readed;
}
/**
* Constructs an <code>Atom</code> object from the data in the bitstream.
*
* @param bitstream
* the input bitstream
* @return the constructed atom.
*/
public static MP4Atom createAtom(DataStream bitstream) throws IOException {
String uuid = null;
long size = bitstream.readBytes(4);
if (size == 0) {
throw new IOException("Invalid size");
}
int type = (int) bitstream.readBytes(4);
long readed = 8;
if (type == MP4ExtendedAtomType) {
uuid = bitstream.readString(16);
readed += 16;
}
// large size
if (size == 1) {
size = bitstream.readBytes(8);
readed += 8;
}
MP4Atom atom = new MP4Atom(size, type, uuid, readed);
if ((type == MP4MediaAtomType) || (type == MP4DataInformationAtomType)
|| (type == MP4MovieAtomType)
|| (type == MP4MediaInformationAtomType)
|| (type == MP4SampleTableAtomType)
|| (type == MP4TrackAtomType)) {
readed = atom.create_composite_atom(bitstream);
} else if (type == MP4AudioSampleEntryAtomType) {
readed = atom.create_audio_sample_entry_atom(bitstream);
} else if (type == MP4ChunkLargeOffsetAtomType) {
readed = atom.create_chunk_large_offset_atom(bitstream);
} else if (type == MP4ChunkOffsetAtomType) {
readed = atom.create_chunk_offset_atom(bitstream);
} else if (type == MP4HandlerAtomType) {
readed = atom.create_handler_atom(bitstream);
} else if (type == MP4MediaHeaderAtomType) {
readed = atom.create_media_header_atom(bitstream);
} else if (type == MP4MovieHeaderAtomType) {
readed = atom.create_movie_header_atom(bitstream);
} else if (type == MP4SampleDescriptionAtomType) {
readed = atom.create_sample_description_atom(bitstream);
} else if (type == MP4SampleSizeAtomType) {
readed = atom.create_sample_size_atom(bitstream);
} else if (type == MP4CompactSampleSizeAtomType) {
readed = atom.create_compact_sample_size_atom(bitstream);
} else if (type == MP4SampleToChunkAtomType) {
readed = atom.create_sample_to_chunk_atom(bitstream);
/*
* } else if(type == MP4SoundMediaHeaderAtomType){ readed =
* atom.create_sound_media_header_atom(bitstream); } else if(type ==
* MP4TrackHeaderAtomType){ readed =
* atom.create_track_header_atom(bitstream); } else if(type ==
* MP4VideoMediaHeaderAtomType){ readed =
* atom.create_video_media_header_atom(bitstream);
*/
} else if (type == MP4VisualSampleEntryAtomType) {
readed = atom.create_visual_sample_entry_atom(bitstream);
} else if (type == MP4ESDAtomType) {
readed = atom.create_esd_atom(bitstream);
}
// System.out.println("Atom: type = " + intToType(type) + " size = " +
// size);
bitstream.skipBytes(size - readed);
return atom;
}
protected int version = 0;
protected int flags = 0;
/**
* Loads the version of the full atom from the input bitstream.
*
* @param bitstream
* the input bitstream
* @return the number of bytes which was being loaded.
*/
public long create_full_atom(DataStream bitstream) throws IOException {
long value = bitstream.readBytes(4);
version = (int) value >> 24;
flags = (int) value & 0xffffff;
readed += 4;
return readed;
}
/**
* Loads the composite atom from the input bitstream.
*
* @param bitstream
* the input bitstream
* @return the number of bytes which was being loaded.
*/
public long create_composite_atom(DataStream bitstream) throws IOException {
while (readed < size) {
MP4Atom child = MP4Atom.createAtom(bitstream);
this.children.addElement(child);
readed += child.getSize();
}
return readed;
}
/**
* Lookups for a child atom with the specified <code>type</code>, skips the
* <code>number</code> children with the same type before finding a result.
*
* @param type
* the type of the atom.
* @param number
* the number of atoms to skip
* @return the atom which was being searched.
*/
public MP4Atom lookup(long type, int number) {
int position = 0;
for (int i = 0; i < children.size(); i++) {
MP4Atom atom = (MP4Atom) children.elementAt(i);
if (atom.getType() == type) {
if (position >= number) {
return atom;
}
position++;
}
}
return null;
}
/**
* Loads AudioSampleEntry atom from the input bitstream.
*
* @param bitstream
* the input bitstream
* @return the number of bytes which was being loaded.
*/
public long create_audio_sample_entry_atom(DataStream bitstream)
throws IOException {
bitstream.skipBytes(6);
bitstream.readBytes(2);
bitstream.skipBytes(8);
bitstream.readBytes(2);
bitstream.readBytes(2);
bitstream.skipBytes(4);
bitstream.readBytes(2);
bitstream.skipBytes(2);
readed += 28;
MP4Atom child = MP4Atom.createAtom(bitstream);
this.children.addElement(child);
readed += child.getSize();
return readed;
}
protected int entryCount;
/** The decoding time to sample table. */
protected Vector<Long> chunks = new Vector<Long>();
/**
* Loads ChunkLargeOffset atom from the input bitstream.
*
* @param bitstream
* the input bitstream
* @return the number of bytes which was being loaded.
*/
public long create_chunk_large_offset_atom(DataStream bitstream)
throws IOException {
create_full_atom(bitstream);
entryCount = (int) bitstream.readBytes(4);
readed += 8;
for (int i = 0; i < entryCount; i++) {
long chunkOffset = bitstream.readBytes(8);
chunks.addElement(new Long(chunkOffset));
readed += 8;
}
return readed;
}
public Vector<Long> getChunks() {
return chunks;
}
/**
* Loads ChunkOffset atom from the input bitstream.
*
* @param bitstream
* the input bitstream
* @return the number of bytes which was being loaded.
*/
public long create_chunk_offset_atom(DataStream bitstream)
throws IOException {
create_full_atom(bitstream);
entryCount = (int) bitstream.readBytes(4);
readed += 4;
for (int i = 0; i < entryCount; i++) {
long chunkOffset = bitstream.readBytes(4);
chunks.addElement(new Long(chunkOffset));
readed += 4;
}
return readed;
}
protected int handlerType;
/**
* Loads Handler atom from the input bitstream.
*
* @param bitstream
* the input bitstream
* @return the number of bytes which was being loaded.
*/
public long create_handler_atom(DataStream bitstream) throws IOException {
create_full_atom(bitstream);
bitstream.readBytes(4);
handlerType = (int) bitstream.readBytes(4);
bitstream.readBytes(4);
bitstream.readBytes(4);
bitstream.readBytes(4);
readed += 20;
int length = (int) (size - readed - 1);
bitstream.readString(length);
readed += length;
return readed;
}
/**
* Gets the handler type.
*
* @return the handler type.
*/
public int getHandlerType() {
return handlerType;
}
protected Date creationTime;
protected Date modificationTime;
protected int timeScale;
protected long duration;
/**
* Loads MediaHeader atom from the input bitstream.
*
* @param bitstream
* the input bitstream
* @return the number of bytes which was being loaded.
*/
public long create_media_header_atom(DataStream bitstream)
throws IOException {
create_full_atom(bitstream);
if (version == 1) {
creationTime = createDate(bitstream.readBytes(8));
modificationTime = createDate(bitstream.readBytes(8));
timeScale = (int) bitstream.readBytes(4);
duration = bitstream.readBytes(8);
readed += 28;
} else {
creationTime = createDate(bitstream.readBytes(4));
modificationTime = createDate(bitstream.readBytes(4));
timeScale = (int) bitstream.readBytes(4);
duration = bitstream.readBytes(4);
readed += 16;
}
bitstream.readBytes(2);
bitstream.readBytes(2);
readed += 4;
return readed;
}
public long getDuration() {
return duration;
}
public int getTimeScale() {
return timeScale;
}
/**
* Loads MovieHeader atom from the input bitstream.
*
* @param bitstream
* the input bitstream
* @return the number of bytes which was being loaded.
*/
public long create_movie_header_atom(DataStream bitstream)
throws IOException {
create_full_atom(bitstream);
if (version == 1) {
creationTime = createDate(bitstream.readBytes(8));
modificationTime = createDate(bitstream.readBytes(8));
timeScale = (int) bitstream.readBytes(4);
duration = bitstream.readBytes(8);
readed += 28;
} else {
creationTime = createDate(bitstream.readBytes(4));
modificationTime = createDate(bitstream.readBytes(4));
timeScale = (int) bitstream.readBytes(4);
duration = bitstream.readBytes(4);
readed += 16;
}
bitstream.readBytes(4);
bitstream.readBytes(2);
bitstream.skipBytes(10);
bitstream.readBytes(4);
bitstream.readBytes(4);
bitstream.readBytes(4);
bitstream.readBytes(4);
bitstream.readBytes(4);
bitstream.readBytes(4);
bitstream.readBytes(4);
bitstream.readBytes(4);
bitstream.readBytes(4);
bitstream.readBytes(4);
bitstream.readBytes(4);
bitstream.readBytes(4);
bitstream.readBytes(4);
bitstream.readBytes(4);
bitstream.readBytes(4);
bitstream.readBytes(4);
readed += 80;
return readed;
}
/**
* Loads SampleDescription atom from the input bitstream.
*
* @param bitstream
* the input bitstream
* @return the number of bytes which was being loaded.
*/
public long create_sample_description_atom(DataStream bitstream)
throws IOException {
create_full_atom(bitstream);
entryCount = (int) bitstream.readBytes(4);
readed += 4;
for (int i = 0; i < entryCount; i++) {
MP4Atom child = MP4Atom.createAtom(bitstream);
this.children.addElement(child);
readed += child.getSize();
}
return readed;
}
protected int sampleSize;
protected int sampleCount;
/** The decoding time to sample table. */
protected Vector<Integer> samples = new Vector<Integer>();
/**
* Loads MP4SampleSizeAtom atom from the input bitstream.
*
* @param bitstream
* the input bitstream
* @return the number of bytes which was being loaded.
*/
public long create_sample_size_atom(DataStream bitstream)
throws IOException {
create_full_atom(bitstream);
sampleSize = (int) bitstream.readBytes(4);
sampleCount = (int) bitstream.readBytes(4);
readed += 8;
if (sampleSize == 0) {
for (int i = 0; i < sampleCount; i++) {
int size = (int) bitstream.readBytes(4);
samples.addElement(new Integer(size));
readed += 4;
}
}
return readed;
}
public Vector<Integer> getSamples() {
return samples;
}
public int getSampleSize() {
return sampleSize;
}
protected int fieldSize;
/**
* Loads CompactSampleSize atom from the input stream.
*
* @param stream
* the input stream
* @return the number of bytes which was being loaded.
*/
public long create_compact_sample_size_atom(DataStream stream)
throws IOException {
create_full_atom(stream);
stream.skipBytes(3);
sampleSize = 0;
fieldSize = (int) stream.readBytes(1);
sampleCount = (int) stream.readBytes(4);
readed += 8;
for (int i = 0; i < sampleCount; i++) {
int size = 0;
switch (fieldSize) {
case 4:
size = (int) stream.readBytes(1);
// TODO check the following code
samples.addElement(new Integer(size & 0x0f));
size = (size >> 4) & 0x0f;
i++;
readed += 1;
break;
case 8:
size = (int) stream.readBytes(1);
readed += 1;
break;
case 16:
size = (int) stream.readBytes(2);
readed += 2;
break;
}
if (i < sampleCount) {
samples.addElement(new Integer(size));
}
}
return readed;
}
public class Record {
private int firstChunk;
private int samplesPerChunk;
private int sampleDescriptionIndex;
public Record(int firstChunk, int samplesPerChunk,
int sampleDescriptionIndex) {
this.firstChunk = firstChunk;
this.samplesPerChunk = samplesPerChunk;
this.sampleDescriptionIndex = sampleDescriptionIndex;
}
public int getFirstChunk() {
return firstChunk;
}
public int getSamplesPerChunk() {
return samplesPerChunk;
}
public int getSampleDescriptionIndex() {
return sampleDescriptionIndex;
}
}
/** The decoding time to sample table. */
protected Vector<Record> records = new Vector<Record>();
public Vector<Record> getRecords() {
return records;
}
/**
* Loads MP4SampleToChunkAtom atom from the input bitstream.
*
* @param bitstream
* the input bitstream
* @return the number of bytes which was being loaded.
*/
public long create_sample_to_chunk_atom(DataStream bitstream)
throws IOException {
create_full_atom(bitstream);
entryCount = (int) bitstream.readBytes(4);
readed += 4;
for (int i = 0; i < entryCount; i++) {
int firstChunk = (int) bitstream.readBytes(4);
int samplesPerChunk = (int) bitstream.readBytes(4);
int sampleDescriptionIndex = (int) bitstream.readBytes(4);
records.addElement(new Record(firstChunk, samplesPerChunk,
sampleDescriptionIndex));
readed += 12;
}
return readed;
}
/*
* protected int balance;
*
* /** Loads MP4SoundMediaHeaderAtom atom from the input bitstream.
*
* @param bitstream the input bitstream
*
* @return the number of bytes which was being loaded. / public long
* create_sound_media_header_atom(DataStream bitstream) throws IOException {
* create_full_atom(bitstream); balance = (int)bitstream.readBytes(2);
* bitstream.skipBytes(2); readed += 4; return readed; }
*
* protected long trackId; protected int qt_trackWidth; protected int
* qt_trackHeight;
*
* /** Loads MP4TrackHeaderAtom atom from the input bitstream.
*
* @param bitstream the input bitstream
*
* @return the number of bytes which was being loaded. / public long
* create_track_header_atom(DataStream bitstream) throws IOException {
* create_full_atom(bitstream); if(version == 1) { creationTime =
* createDate(bitstream.readBytes(8)); modificationTime =
* createDate(bitstream.readBytes(8)); trackId = bitstream.readBytes(4);
* bitstream.skipBytes(4); duration = bitstream.readBytes(8); readed += 32;
* } else { creationTime = createDate(bitstream.readBytes(4));
* modificationTime = createDate(bitstream.readBytes(4)); trackId =
* bitstream.readBytes(4); bitstream.skipBytes(4); duration =
* bitstream.readBytes(4); readed += 20; } bitstream.skipBytes(8); int
* qt_layer = (int)bitstream.readBytes(2); int qt_alternateGroup =
* (int)bitstream.readBytes(2); int qt_volume = (int)bitstream.readBytes(2);
* bitstream.skipBytes(2); long qt_matrixA = bitstream.readBytes(4); long
* qt_matrixB = bitstream.readBytes(4); long qt_matrixU =
* bitstream.readBytes(4); long qt_matrixC = bitstream.readBytes(4); long
* qt_matrixD = bitstream.readBytes(4); long qt_matrixV =
* bitstream.readBytes(4); long qt_matrixX = bitstream.readBytes(4); long
* qt_matrixY = bitstream.readBytes(4); long qt_matrixW =
* bitstream.readBytes(4); qt_trackWidth = (int)bitstream.readBytes(4);
* qt_trackHeight = (int)bitstream.readBytes(4); readed += 60; return
* readed; }
*
* protected int graphicsMode; protected int opColorRed; protected int
* opColorGreen; protected int opColorBlue;
*
* /** Loads MP4VideoMediaHeaderAtom atom from the input bitstream.
*
* @param bitstream the input bitstream
*
* @return the number of bytes which was being loaded. / public long
* create_video_media_header_atom(DataStream bitstream) throws IOException {
* create_full_atom(bitstream); if((size - readed) == 8) { graphicsMode =
* (int)bitstream.readBytes(2); opColorRed = (int)bitstream.readBytes(2);
* opColorGreen = (int)bitstream.readBytes(2); opColorBlue =
* (int)bitstream.readBytes(2); readed += 8; } return readed; }
*/
protected int width;
protected int height;
/**
* Loads MP4VisualSampleEntryAtom atom from the input bitstream.
*
* @param bitstream
* the input bitstream
* @return the number of bytes which was being loaded.
*/
public long create_visual_sample_entry_atom(DataStream bitstream)
throws IOException {
bitstream.skipBytes(24);
width = (int) bitstream.readBytes(2);
height = (int) bitstream.readBytes(2);
bitstream.skipBytes(50);
readed += 78;
MP4Atom child = MP4Atom.createAtom(bitstream);
this.children.addElement(child);
readed += child.getSize();
return readed;
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
protected MP4Descriptor esd_descriptor;
/**
* Loads M4ESDAtom atom from the input bitstream.
*
* @param bitstream
* the input bitstream
* @return the number of bytes which was being loaded.
*/
public long create_esd_atom(DataStream bitstream) throws IOException {
create_full_atom(bitstream);
esd_descriptor = MP4Descriptor.createDescriptor(bitstream);
readed += esd_descriptor.getReaded();
return readed;
}
/**
* Returns the ESD descriptor.
*/
public MP4Descriptor getEsd_descriptor() {
return esd_descriptor;
}
/**
* Converts the time in seconds since midnight 1 Jan 1904 to the
* <code>Date</code>.
*
* @param movieTime
* the time in milliseconds since midnight 1 Jan 1904.
* @return the <code>Date</code> object.
*/
public static final Date createDate(long movieTime) {
return new Date(movieTime * 1000 - 2082850791998L);
}
public static int typeToInt(String type) {
int result = ((int) type.charAt(0) << 24)
+ ((int) type.charAt(1) << 16) + ((int) type.charAt(2) << 8)
+ ((int) type.charAt(3));
return result;
}
public static String intToType(int type) {
StringBuffer st = new StringBuffer();
st.append((char) ((type >> 24) & 0xff));
st.append((char) ((type >> 16) & 0xff));
st.append((char) ((type >> 8) & 0xff));
st.append((char) (type & 0xff));
return st.toString();
}
/**
* Gets children from this atom.
*
* @return children from this atom.
*/
public Vector<MP4Atom> getChildren() {
return children;
}
/**
* Gets the size of this atom.
*
* @return the size of this atom.
*/
public long getSize() {
return size;
}
/**
* Returns the type of this atom.
*/
public int getType() {
return type;
}
/**
* Returns the name of this atom.
*/
public String toString() {
return intToType(type);
}
}