Skip to content

Commit

Permalink
Improvements to Mp4Extractor and FragmentedMp4Extractor.
Browse files Browse the repository at this point in the history
- Make Mp4Extractor more robust when resuming from read failures.
- Made FragmentedMp4Extractor handle atoms with extended sizes.

Issue google#652
  • Loading branch information
ojw28 committed Jul 29, 2015
1 parent 2f0aec4 commit 98ecc20
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,10 @@ int read(ExtractorInput input, PositionHolder seekPosition)
* Notifies the extractor that a seek has occurred.
* <p>
* Following a call to this method, the {@link ExtractorInput} passed to the next invocation of
* {@link #read(ExtractorInput, PositionHolder)} is required to provide data starting from any
* random access position in the stream. Random access positions can be obtained from a
* {@link SeekMap} that has been extracted and passed to the {@link ExtractorOutput}.
* {@link #read(ExtractorInput, PositionHolder)} is required to provide data starting from a
* random access position in the stream. Valid random access positions are the start of the
* stream and positions that can be obtained from any {@link SeekMap} passed to the
* {@link ExtractorOutput}.
*/
void seek();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,24 @@

/* package*/ abstract class Atom {

/** Size of an atom header, in bytes. */
/**
* Size of an atom header, in bytes.
*/
public static final int HEADER_SIZE = 8;

/** Size of a full atom header, in bytes. */
/**
* Size of a full atom header, in bytes.
*/
public static final int FULL_HEADER_SIZE = 12;

/** Size of a long atom header, in bytes. */
/**
* Size of a long atom header, in bytes.
*/
public static final int LONG_HEADER_SIZE = 16;

/** Value for the first 32 bits of atomSize when the atom size is actually a long value. */
/**
* Value for the first 32 bits of atomSize when the atom size is actually a long value.
*/
public static final int LONG_SIZE_PREFIX = 1;

public static final int TYPE_ftyp = Util.getIntegerCodeForString("ftyp");
Expand Down Expand Up @@ -97,7 +105,7 @@

public final int type;

Atom(int type) {
public Atom(int type) {
this.type = type;
}

Expand All @@ -106,41 +114,74 @@ public String toString() {
return getAtomTypeString(type);
}

/** An MP4 atom that is a leaf. */
public static final class LeafAtom extends Atom {
/**
* An MP4 atom that is a leaf.
*/
/* package */ static final class LeafAtom extends Atom {

/**
* The atom data.
*/
public final ParsableByteArray data;

/**
* @param type The type of the atom.
* @param data The atom data.
*/
public LeafAtom(int type, ParsableByteArray data) {
super(type);
this.data = data;
}

}

/** An MP4 atom that has child atoms. */
public static final class ContainerAtom extends Atom {
/**
* An MP4 atom that has child atoms.
*/
/* package */ static final class ContainerAtom extends Atom {

public final long endByteOffset;
public final long endPosition;
public final List<LeafAtom> leafChildren;
public final List<ContainerAtom> containerChildren;

public ContainerAtom(int type, long endByteOffset) {
/**
* @param type The type of the atom.
* @param endPosition The position of the first byte after the end of the atom.
*/
public ContainerAtom(int type, long endPosition) {
super(type);

this.endPosition = endPosition;
leafChildren = new ArrayList<>();
containerChildren = new ArrayList<>();
this.endByteOffset = endByteOffset;
}

/**
* Adds a child leaf to this container.
*
* @param atom The child to add.
*/
public void add(LeafAtom atom) {
leafChildren.add(atom);
}

/**
* Adds a child container to this container.
*
* @param atom The child to add.
*/
public void add(ContainerAtom atom) {
containerChildren.add(atom);
}

/**
* Gets the child leaf of the given type.
* <p>
* If no child exists with the given type then null is returned. If multiple children exist with
* the given type then the first one to have been added is returned.
*
* @param type The leaf type.
* @return The child leaf of the given type, or null if no such child exists.
*/
public LeafAtom getLeafAtomOfType(int type) {
int childrenSize = leafChildren.size();
for (int i = 0; i < childrenSize; i++) {
Expand All @@ -152,6 +193,15 @@ public LeafAtom getLeafAtomOfType(int type) {
return null;
}

/**
* Gets the child container of the given type.
* <p>
* If no child exists with the given type then null is returned. If multiple children exist with
* the given type then the first one to have been added is returned.
*
* @param type The container type.
* @return The child container of the given type, or null if no such child exists.
*/
public ContainerAtom getContainerAtomOfType(int type) {
int childrenSize = containerChildren.size();
for (int i = 0; i < childrenSize; i++) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ public final class FragmentedMp4Extractor implements Extractor {
private final TrackFragment fragmentRun;

private int parserState;
private int rootAtomBytesRead;
private int atomType;
private int atomSize;
private long atomSize;
private int atomHeaderBytesRead;
private ParsableByteArray atomData;

private int sampleIndex;
Expand Down Expand Up @@ -108,14 +108,14 @@ public FragmentedMp4Extractor() {
*/
public FragmentedMp4Extractor(int workaroundFlags) {
this.workaroundFlags = workaroundFlags;
atomHeader = new ParsableByteArray(Atom.HEADER_SIZE);
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
nalLength = new ParsableByteArray(4);
encryptionSignalByte = new ParsableByteArray(1);
extendedTypeScratch = new byte[16];
containerAtoms = new Stack<>();
fragmentRun = new TrackFragment();
parserState = STATE_READING_ATOM_HEADER;
enterReadingAtomHeaderState();
}

@Override
Expand Down Expand Up @@ -147,8 +147,7 @@ public void init(ExtractorOutput output) {
@Override
public void seek() {
containerAtoms.clear();
rootAtomBytesRead = 0;
parserState = STATE_READING_ATOM_HEADER;
enterReadingAtomHeaderState();
}

@Override
Expand All @@ -175,15 +174,30 @@ public int read(ExtractorInput input, PositionHolder seekPosition)
}
}

private void enterReadingAtomHeaderState() {
parserState = STATE_READING_ATOM_HEADER;
atomHeaderBytesRead = 0;
}

private boolean readAtomHeader(ExtractorInput input) throws IOException, InterruptedException {
if (!input.readFully(atomHeader.data, 0, Atom.HEADER_SIZE, true)) {
return false;
if (atomHeaderBytesRead == 0) {
// Read the standard length atom header.
if (!input.readFully(atomHeader.data, 0, Atom.HEADER_SIZE, true)) {
return false;
}
atomHeaderBytesRead = Atom.HEADER_SIZE;
atomHeader.setPosition(0);
atomSize = atomHeader.readUnsignedInt();
atomType = atomHeader.readInt();
}

rootAtomBytesRead += Atom.HEADER_SIZE;
atomHeader.setPosition(0);
atomSize = atomHeader.readInt();
atomType = atomHeader.readInt();
if (atomSize == Atom.LONG_SIZE_PREFIX) {
// Read the extended atom size.
int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE;
input.readFully(atomHeader.data, Atom.HEADER_SIZE, headerBytesRemaining);
atomHeaderBytesRead += headerBytesRemaining;
atomSize = atomHeader.readUnsignedLongToLong();
}

if (atomType == Atom.TYPE_mdat) {
if (!haveOutputSeekMap) {
Expand All @@ -200,15 +214,21 @@ private boolean readAtomHeader(ExtractorInput input) throws IOException, Interru

if (shouldParseAtom(atomType)) {
if (shouldParseContainerAtom(atomType)) {
parserState = STATE_READING_ATOM_HEADER;
containerAtoms.add(new ContainerAtom(atomType,
rootAtomBytesRead + atomSize - Atom.HEADER_SIZE));
long endPosition = input.getPosition() + atomSize - Atom.HEADER_SIZE;
containerAtoms.add(new ContainerAtom(atomType, endPosition));
enterReadingAtomHeaderState();
} else {
atomData = new ParsableByteArray(atomSize);
// We don't support parsing of leaf atoms that define extended atom sizes, or that have
// lengths greater than Integer.MAX_VALUE.
Assertions.checkState(atomHeaderBytesRead == Atom.HEADER_SIZE);
Assertions.checkState(atomSize <= Integer.MAX_VALUE);
atomData = new ParsableByteArray((int) atomSize);
System.arraycopy(atomHeader.data, 0, atomData.data, 0, Atom.HEADER_SIZE);
parserState = STATE_READING_ATOM_PAYLOAD;
}
} else {
// We don't support skipping of atoms that have lengths greater than Integer.MAX_VALUE.
Assertions.checkState(atomSize <= Integer.MAX_VALUE);
atomData = null;
parserState = STATE_READING_ATOM_PAYLOAD;
}
Expand All @@ -217,22 +237,18 @@ private boolean readAtomHeader(ExtractorInput input) throws IOException, Interru
}

private void readAtomPayload(ExtractorInput input) throws IOException, InterruptedException {
int payloadLength = atomSize - Atom.HEADER_SIZE;
int atomPayloadSize = (int) atomSize - atomHeaderBytesRead;
if (atomData != null) {
input.readFully(atomData.data, Atom.HEADER_SIZE, payloadLength);
rootAtomBytesRead += payloadLength;
input.readFully(atomData.data, Atom.HEADER_SIZE, atomPayloadSize);
onLeafAtomRead(new LeafAtom(atomType, atomData), input.getPosition());
} else {
input.skipFully(payloadLength);
rootAtomBytesRead += payloadLength;
input.skipFully(atomPayloadSize);
}
while (!containerAtoms.isEmpty() && containerAtoms.peek().endByteOffset == rootAtomBytesRead) {
long currentPosition = input.getPosition();
while (!containerAtoms.isEmpty() && containerAtoms.peek().endPosition == currentPosition) {
onContainerAtomRead(containerAtoms.pop());
}
if (containerAtoms.isEmpty()) {
rootAtomBytesRead = 0;
}
parserState = STATE_READING_ATOM_HEADER;
enterReadingAtomHeaderState();
}

private void onLeafAtomRead(LeafAtom leaf, long inputPosition) {
Expand Down Expand Up @@ -606,7 +622,7 @@ private void readEncryptionData(ExtractorInput input) throws IOException, Interr
private boolean readSample(ExtractorInput input) throws IOException, InterruptedException {
if (sampleIndex >= fragmentRun.length) {
// We've run out of samples in the current mdat atom.
parserState = STATE_READING_ATOM_HEADER;
enterReadingAtomHeaderState();
return false;
}

Expand Down
Loading

0 comments on commit 98ecc20

Please sign in to comment.