package mp4.util;
import java.io.DataInputStream;
import java.io.IOException;
import mp4.util.atom.Atom;
import mp4.util.atom.AtomException;
import mp4.util.atom.ContainerAtom;
import mp4.util.atom.DefaultAtomVisitor;
import mp4.util.atom.FtypAtom;
import mp4.util.atom.MdatAtom;
import mp4.util.atom.MoovAtom;
public class Mp4Parser extends DefaultAtomVisitor {
protected FtypAtom ftyp;
protected MoovAtom moov;
protected MdatAtom mdat;
protected DataInputStream mp4file;
boolean lastAtom = false;
private long lastAtomOffset = 0;
private int indent = 0;
private String classPrefix = "";
protected Mp4Parser() {
}
public Mp4Parser(DataInputStream mp4file) {
this.mp4file = mp4file;
}
@Override
protected void defaultAction(Atom atom) throws AtomException {
// Even some (hybrid) containers have data, so read it
atom.readData(mp4file);
lastAtomOffset += atom.pureDataSize();
if (atom.isContainer()) {
indent++;
String oldClassPrefix = classPrefix;
String cname = atom.getClass().getCanonicalName();
classPrefix = classPrefix + cname.substring(cname.lastIndexOf('.') + 1, cname.length() - 4) + ".";
long bytesToRead = atom.dataSize() - atom.pureDataSize();
while (bytesToRead >= Atom.ATOM_HEADER_SIZE) {
Atom child = parseAtom();
((ContainerAtom) atom).addChild(child);
//bytesRead += child.size();
bytesToRead -= child.size();
}
// If there is not enough room for a full atom, just skip extra data
// Some odd atoms are containers but terminate with 4 zero bytes.
// This should handle those cases
if (bytesToRead > 0) {
MP4Log.log("Skipping extra container bytes: " + bytesToRead);
try {
mp4file.skipBytes((int) bytesToRead);
lastAtomOffset += bytesToRead;
} catch (IOException e) {
throw new AtomException(e.getMessage());
}
}
indent--;
classPrefix = oldClassPrefix;
}
}
/**
* Don't read the mdat atom since that's the biggest segment of the
* file. It contains the video and sound data. Plus, we'll just
* skip over the beginning when we cut the movie.
*/
@Override
public void visit(MdatAtom atom) throws AtomException {
atom.setInputStream(mp4file);
try {
if (atom.dataSize() != 0 && mp4file.markSupported()) {
mp4file.skip(atom.dataSize());
} else {
lastAtom = true;
}
} catch (IOException e) {
throw new AtomException(e.getMessage());
}
lastAtomOffset += atom.dataSize();
}
private String getPrefix() {
//String ps = classPrefix.length() > 0 ? classPrefix.substring(0, classPrefix.length()-1) + ":" : "";
String ps = "";
StringBuilder x = new StringBuilder(ps);
for (int i = 0; i < indent; i++) {
x.append(" ");
}
return x.toString();
}
/**
* Parse an atom from the mpeg4 file.
*
* @return the number of bytes read
* @throws AtomException
*/
private Atom parseAtom() throws AtomException {
// get the atom size
if (lastAtom) {
throw new AtomException("Already parsed last atom!");
}
long aoff = lastAtomOffset;
//MP4Log.log("Reading atom at offset: " + lastAtomOffset);
byte[] word = new byte[Atom.ATOM_WORD];
int num;
try {
num = mp4file.read(word);
} catch (IOException e1) {
throw new AtomException("IOException while reading file");
}
// check for end of file
if (num == -1) {
return null;
}
if (num != Atom.ATOM_WORD) {
throw new AtomException("Unable to read enough bytes for atom");
}
long size = Atom.byteArrayToUnsignedInt(word, 0);
// get the atom type
try {
num = mp4file.read(word);
} catch (IOException e1) {
throw new AtomException("IOException while reading file");
}
if (num != Atom.ATOM_WORD) {
throw new AtomException("Unable to read enough bytes for atom");
}
lastAtomOffset += Atom.ATOM_HEADER_SIZE;
// handle 64-bit data
boolean isBig = false;
if (size == 1) {
isBig = true;
byte[] bigSize = new byte[Atom.LARGE_SIZE_SIZE];
try {
num = mp4file.read(bigSize);
} catch (IOException e1) {
throw new AtomException("IOException while reading file");
}
if (num != Atom.LARGE_SIZE_SIZE) {
throw new AtomException("Unable to read enough bytes for atom");
}
size = Atom.byteArrayToLong(bigSize, 0);
lastAtomOffset += Atom.LARGE_SIZE_SIZE;
}
Atom atom;
atom = Atom.typeToAtom(word, classPrefix);
/*
if (isBig)
MP4Log.log(getPrefix() + "^Large size atom");
*/
String atomName = atom.getClass().getCanonicalName();
String lgs = "";
if (isBig) {
lgs = "(LRG)";
}
String typeForClass = Atom.typeToClassName(word, null);
MP4Log.log(getPrefix() + atomName + "(" + ((int) word[0] & 0xff) + "," + ((int) word[1] & 0xff) + "," + ((int) word[2] & 0xff) + "," + ((int) word[3] & 0xff) + "): " + typeForClass.substring(typeForClass.lastIndexOf('.') + 1) + " (offset: " + aoff + ", size" + lgs + ":" + size + ")");
//MP4Log.log(getPrefix() + "UnknownAtom(" + ((int)word[0]&0xff) + "," + ((int)word[1]&0xff) + "," + ((int)word[2]&0xff) + "," + ((int)word[3]&0xff) + "): " + Atom.typeToClassName(word) + " (size:" + size + ")");
atom.setLargeAtom(isBig);
atom.setSize(size);
atom.accept(this);
return atom;
}
public long parseMp4() throws AtomException, IOException {
long mdatOffset = 0;
long offset = 0;
while (ftyp == null || moov == null || mdat == null) {
Atom atom = parseAtom();
if (atom == null) {
throw new IOException("Couldn't find all required MP4 atoms");
}
if (atom instanceof FtypAtom) {
ftyp = (FtypAtom) atom;
} else if (atom instanceof MoovAtom) {
moov = (MoovAtom) atom;
} else if (atom instanceof MdatAtom) {
mdatOffset = offset;
// mdatOffset is the start of the mdat atom's data section.
mdat = (MdatAtom) atom;
}
offset += atom.size();
}
return mdatOffset;
}
public MoovAtom getMoov() {
return moov;
}
}