Commit 4a745b1c by Oliver Woodman

Optimize out quite a few allocations in FragmentedMp4Parser.

parent 005e98fc
...@@ -52,7 +52,7 @@ public abstract class Chunk implements Loadable { ...@@ -52,7 +52,7 @@ public abstract class Chunk implements Loadable {
/** /**
* @param dataSource The source from which the data should be loaded. * @param dataSource The source from which the data should be loaded.
* @param dataSpec Defines the data to be loaded. {@code dataSpec.length} must not exceed * @param dataSpec Defines the data to be loaded. {@code dataSpec.length} must not exceed
* {@link Integer#MAX_VALUE}. If {@code dataSpec.length == DataSpec.LENGTH_UNBOUNDED} then * {@link Integer#MAX_VALUE}. If {@code dataSpec.length == C.LENGTH_UNBOUNDED} then
* the length resolved by {@code dataSource.open(dataSpec)} must not exceed * the length resolved by {@code dataSource.open(dataSpec)} must not exceed
* {@link Integer#MAX_VALUE}. * {@link Integer#MAX_VALUE}.
* @param format See {@link #format}. * @param format See {@link #format}.
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package com.google.android.exoplayer.parser.mp4; package com.google.android.exoplayer.parser.mp4;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
/* package */ abstract class Atom { /* package */ abstract class Atom {
...@@ -24,7 +23,6 @@ import java.util.List; ...@@ -24,7 +23,6 @@ import java.util.List;
public static final int TYPE_avc3 = 0x61766333; public static final int TYPE_avc3 = 0x61766333;
public static final int TYPE_esds = 0x65736473; public static final int TYPE_esds = 0x65736473;
public static final int TYPE_mdat = 0x6D646174; public static final int TYPE_mdat = 0x6D646174;
public static final int TYPE_mfhd = 0x6D666864;
public static final int TYPE_mp4a = 0x6D703461; public static final int TYPE_mp4a = 0x6D703461;
public static final int TYPE_tfdt = 0x74666474; public static final int TYPE_tfdt = 0x74666474;
public static final int TYPE_tfhd = 0x74666864; public static final int TYPE_tfhd = 0x74666864;
...@@ -64,17 +62,13 @@ import java.util.List; ...@@ -64,17 +62,13 @@ import java.util.List;
public final static class LeafAtom extends Atom { public final static class LeafAtom extends Atom {
private final ParsableByteArray data; public final ParsableByteArray data;
public LeafAtom(int type, ParsableByteArray data) { public LeafAtom(int type, ParsableByteArray data) {
super(type); super(type);
this.data = data; this.data = data;
} }
public ParsableByteArray getData() {
return data;
}
} }
public final static class ContainerAtom extends Atom { public final static class ContainerAtom extends Atom {
...@@ -91,7 +85,8 @@ import java.util.List; ...@@ -91,7 +85,8 @@ import java.util.List;
} }
public LeafAtom getLeafAtomOfType(int type) { public LeafAtom getLeafAtomOfType(int type) {
for (int i = 0; i < children.size(); i++) { int childrenSize = children.size();
for (int i = 0; i < childrenSize; i++) {
Atom atom = children.get(i); Atom atom = children.get(i);
if (atom.type == type) { if (atom.type == type) {
return (LeafAtom) atom; return (LeafAtom) atom;
...@@ -101,7 +96,8 @@ import java.util.List; ...@@ -101,7 +96,8 @@ import java.util.List;
} }
public ContainerAtom getContainerAtomOfType(int type) { public ContainerAtom getContainerAtomOfType(int type) {
for (int i = 0; i < children.size(); i++) { int childrenSize = children.size();
for (int i = 0; i < childrenSize; i++) {
Atom atom = children.get(i); Atom atom = children.get(i);
if (atom.type == type) { if (atom.type == type) {
return (ContainerAtom) atom; return (ContainerAtom) atom;
...@@ -110,10 +106,6 @@ import java.util.List; ...@@ -110,10 +106,6 @@ import java.util.List;
return null; return null;
} }
public List<Atom> getChildren() {
return children;
}
} }
} }
...@@ -27,7 +27,7 @@ import java.util.List; ...@@ -27,7 +27,7 @@ import java.util.List;
/** /**
* Provides static utility methods for manipulating various types of codec specific data. * Provides static utility methods for manipulating various types of codec specific data.
*/ */
public class CodecSpecificDataUtil { public final class CodecSpecificDataUtil {
private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1}; private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
......
...@@ -110,7 +110,6 @@ public final class FragmentedMp4Extractor { ...@@ -110,7 +110,6 @@ public final class FragmentedMp4Extractor {
parsedAtoms.add(Atom.TYPE_hdlr); parsedAtoms.add(Atom.TYPE_hdlr);
parsedAtoms.add(Atom.TYPE_mdat); parsedAtoms.add(Atom.TYPE_mdat);
parsedAtoms.add(Atom.TYPE_mdhd); parsedAtoms.add(Atom.TYPE_mdhd);
parsedAtoms.add(Atom.TYPE_mfhd);
parsedAtoms.add(Atom.TYPE_moof); parsedAtoms.add(Atom.TYPE_moof);
parsedAtoms.add(Atom.TYPE_moov); parsedAtoms.add(Atom.TYPE_moov);
parsedAtoms.add(Atom.TYPE_mp4a); parsedAtoms.add(Atom.TYPE_mp4a);
...@@ -154,8 +153,10 @@ public final class FragmentedMp4Extractor { ...@@ -154,8 +153,10 @@ public final class FragmentedMp4Extractor {
// Parser state // Parser state
private final ParsableByteArray atomHeader; private final ParsableByteArray atomHeader;
private final byte[] extendedTypeScratch;
private final Stack<ContainerAtom> containerAtoms; private final Stack<ContainerAtom> containerAtoms;
private final Stack<Integer> containerAtomEndPoints; private final Stack<Integer> containerAtomEndPoints;
private final TrackFragment fragmentRun;
private int parserState; private int parserState;
private int atomBytesRead; private int atomBytesRead;
...@@ -175,9 +176,6 @@ public final class FragmentedMp4Extractor { ...@@ -175,9 +176,6 @@ public final class FragmentedMp4Extractor {
private Track track; private Track track;
private DefaultSampleValues extendsDefaults; private DefaultSampleValues extendsDefaults;
// Data parsed from the most recent moof atom
private TrackFragment fragmentRun;
public FragmentedMp4Extractor() { public FragmentedMp4Extractor() {
this(0); this(0);
} }
...@@ -190,8 +188,10 @@ public final class FragmentedMp4Extractor { ...@@ -190,8 +188,10 @@ public final class FragmentedMp4Extractor {
this.workaroundFlags = workaroundFlags; this.workaroundFlags = workaroundFlags;
parserState = STATE_READING_ATOM_HEADER; parserState = STATE_READING_ATOM_HEADER;
atomHeader = new ParsableByteArray(ATOM_HEADER_SIZE); atomHeader = new ParsableByteArray(ATOM_HEADER_SIZE);
extendedTypeScratch = new byte[16];
containerAtoms = new Stack<ContainerAtom>(); containerAtoms = new Stack<ContainerAtom>();
containerAtomEndPoints = new Stack<Integer>(); containerAtomEndPoints = new Stack<Integer>();
fragmentRun = new TrackFragment();
psshData = new HashMap<UUID, byte[]>(); psshData = new HashMap<UUID, byte[]>();
} }
...@@ -335,7 +335,7 @@ public final class FragmentedMp4Extractor { ...@@ -335,7 +335,7 @@ public final class FragmentedMp4Extractor {
private int readAtomHeader(NonBlockingInputStream inputStream) { private int readAtomHeader(NonBlockingInputStream inputStream) {
int remainingBytes = ATOM_HEADER_SIZE - atomBytesRead; int remainingBytes = ATOM_HEADER_SIZE - atomBytesRead;
int bytesRead = inputStream.read(atomHeader.getData(), atomBytesRead, remainingBytes); int bytesRead = inputStream.read(atomHeader.data, atomBytesRead, remainingBytes);
if (bytesRead == -1) { if (bytesRead == -1) {
return RESULT_END_OF_STREAM; return RESULT_END_OF_STREAM;
} }
...@@ -365,7 +365,7 @@ public final class FragmentedMp4Extractor { ...@@ -365,7 +365,7 @@ public final class FragmentedMp4Extractor {
containerAtomEndPoints.add(rootAtomBytesRead + atomSize - ATOM_HEADER_SIZE); containerAtomEndPoints.add(rootAtomBytesRead + atomSize - ATOM_HEADER_SIZE);
} else { } else {
atomData = new ParsableByteArray(atomSize); atomData = new ParsableByteArray(atomSize);
System.arraycopy(atomHeader.getData(), 0, atomData.getData(), 0, ATOM_HEADER_SIZE); System.arraycopy(atomHeader.data, 0, atomData.data, 0, ATOM_HEADER_SIZE);
enterState(STATE_READING_ATOM_PAYLOAD); enterState(STATE_READING_ATOM_PAYLOAD);
} }
} else { } else {
...@@ -379,7 +379,7 @@ public final class FragmentedMp4Extractor { ...@@ -379,7 +379,7 @@ public final class FragmentedMp4Extractor {
private int readAtomPayload(NonBlockingInputStream inputStream) { private int readAtomPayload(NonBlockingInputStream inputStream) {
int bytesRead; int bytesRead;
if (atomData != null) { if (atomData != null) {
bytesRead = inputStream.read(atomData.getData(), atomBytesRead, atomSize - atomBytesRead); bytesRead = inputStream.read(atomData.data, atomBytesRead, atomSize - atomBytesRead);
} else { } else {
bytesRead = inputStream.skip(atomSize - atomBytesRead); bytesRead = inputStream.skip(atomSize - atomBytesRead);
} }
...@@ -411,7 +411,7 @@ public final class FragmentedMp4Extractor { ...@@ -411,7 +411,7 @@ public final class FragmentedMp4Extractor {
if (!containerAtoms.isEmpty()) { if (!containerAtoms.isEmpty()) {
containerAtoms.peek().add(leaf); containerAtoms.peek().add(leaf);
} else if (leaf.type == Atom.TYPE_sidx) { } else if (leaf.type == Atom.TYPE_sidx) {
segmentIndex = parseSidx(leaf.getData()); segmentIndex = parseSidx(leaf.data);
return RESULT_READ_INDEX; return RESULT_READ_INDEX;
} }
return 0; return 0;
...@@ -430,11 +430,12 @@ public final class FragmentedMp4Extractor { ...@@ -430,11 +430,12 @@ public final class FragmentedMp4Extractor {
} }
private void onMoovContainerAtomRead(ContainerAtom moov) { private void onMoovContainerAtomRead(ContainerAtom moov) {
List<Atom> moovChildren = moov.getChildren(); List<Atom> moovChildren = moov.children;
for (int i = 0; i < moovChildren.size(); i++) { int moovChildrenSize = moovChildren.size();
for (int i = 0; i < moovChildrenSize; i++) {
Atom child = moovChildren.get(i); Atom child = moovChildren.get(i);
if (child.type == Atom.TYPE_pssh) { if (child.type == Atom.TYPE_pssh) {
ParsableByteArray psshAtom = ((LeafAtom) child).getData(); ParsableByteArray psshAtom = ((LeafAtom) child).data;
psshAtom.setPosition(FULL_ATOM_HEADER_SIZE); psshAtom.setPosition(FULL_ATOM_HEADER_SIZE);
UUID uuid = new UUID(psshAtom.readLong(), psshAtom.readLong()); UUID uuid = new UUID(psshAtom.readLong(), psshAtom.readLong());
int dataSize = psshAtom.readInt(); int dataSize = psshAtom.readInt();
...@@ -444,13 +445,13 @@ public final class FragmentedMp4Extractor { ...@@ -444,13 +445,13 @@ public final class FragmentedMp4Extractor {
} }
} }
ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex); ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex);
extendsDefaults = parseTrex(mvex.getLeafAtomOfType(Atom.TYPE_trex).getData()); extendsDefaults = parseTrex(mvex.getLeafAtomOfType(Atom.TYPE_trex).data);
track = parseTrak(moov.getContainerAtomOfType(Atom.TYPE_trak)); track = parseTrak(moov.getContainerAtomOfType(Atom.TYPE_trak));
} }
private void onMoofContainerAtomRead(ContainerAtom moof) { private void onMoofContainerAtomRead(ContainerAtom moof) {
fragmentRun = new TrackFragment(); fragmentRun.reset();
parseMoof(track, extendsDefaults, moof, fragmentRun, workaroundFlags); parseMoof(track, extendsDefaults, moof, fragmentRun, workaroundFlags, extendedTypeScratch);
sampleIndex = 0; sampleIndex = 0;
lastSyncSampleIndex = 0; lastSyncSampleIndex = 0;
pendingSeekSyncSampleIndex = 0; pendingSeekSyncSampleIndex = 0;
...@@ -484,21 +485,21 @@ public final class FragmentedMp4Extractor { ...@@ -484,21 +485,21 @@ public final class FragmentedMp4Extractor {
*/ */
private static Track parseTrak(ContainerAtom trak) { private static Track parseTrak(ContainerAtom trak) {
ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia); ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia);
int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).getData()); int trackType = parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data);
Assertions.checkState(trackType == Track.TYPE_AUDIO || trackType == Track.TYPE_VIDEO); Assertions.checkState(trackType == Track.TYPE_AUDIO || trackType == Track.TYPE_VIDEO);
Pair<Integer, Long> header = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).getData()); Pair<Integer, Long> header = parseTkhd(trak.getLeafAtomOfType(Atom.TYPE_tkhd).data);
int id = header.first; int id = header.first;
// TODO: This value should be used to set a duration field on the Track object // TODO: This value should be used to set a duration field on the Track object
// instantiated below, however we've found examples where the value is 0. Revisit whether we // instantiated below, however we've found examples where the value is 0. Revisit whether we
// should set it anyway (and just have it be wrong for bad media streams). // should set it anyway (and just have it be wrong for bad media streams).
// long duration = header.second; // long duration = header.second;
long timescale = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).getData()); long timescale = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data);
ContainerAtom stbl = mdia.getContainerAtomOfType(Atom.TYPE_minf) ContainerAtom stbl = mdia.getContainerAtomOfType(Atom.TYPE_minf)
.getContainerAtomOfType(Atom.TYPE_stbl); .getContainerAtomOfType(Atom.TYPE_stbl);
Pair<MediaFormat, TrackEncryptionBox[]> sampleDescriptions = Pair<MediaFormat, TrackEncryptionBox[]> sampleDescriptions =
parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).getData()); parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data);
return new Track(id, trackType, timescale, sampleDescriptions.first, sampleDescriptions.second); return new Track(id, trackType, timescale, sampleDescriptions.first, sampleDescriptions.second);
} }
...@@ -667,7 +668,7 @@ public final class FragmentedMp4Extractor { ...@@ -667,7 +668,7 @@ public final class FragmentedMp4Extractor {
int length = atom.readUnsignedShort(); int length = atom.readUnsignedShort();
int offset = atom.getPosition(); int offset = atom.getPosition();
atom.skip(length); atom.skip(length);
return CodecSpecificDataUtil.buildNalUnit(atom.getData(), offset, length); return CodecSpecificDataUtil.buildNalUnit(atom.data, offset, length);
} }
private static TrackEncryptionBox parseSinfFromParent(ParsableByteArray parent, int position, private static TrackEncryptionBox parseSinfFromParent(ParsableByteArray parent, int position,
...@@ -759,55 +760,41 @@ public final class FragmentedMp4Extractor { ...@@ -759,55 +760,41 @@ public final class FragmentedMp4Extractor {
} }
private static void parseMoof(Track track, DefaultSampleValues extendsDefaults, private static void parseMoof(Track track, DefaultSampleValues extendsDefaults,
ContainerAtom moof, TrackFragment out, int workaroundFlags) { ContainerAtom moof, TrackFragment out, int workaroundFlags, byte[] extendedTypeScratch) {
// TODO: Consider checking that the sequence number returned by parseMfhd is as expected.
parseMfhd(moof.getLeafAtomOfType(Atom.TYPE_mfhd).getData());
parseTraf(track, extendsDefaults, moof.getContainerAtomOfType(Atom.TYPE_traf), parseTraf(track, extendsDefaults, moof.getContainerAtomOfType(Atom.TYPE_traf),
out, workaroundFlags); out, workaroundFlags, extendedTypeScratch);
}
/**
* Parses an mfhd atom (defined in 14496-12).
*
* @param mfhd The mfhd atom to parse.
* @return The sequence number of the fragment.
*/
private static int parseMfhd(ParsableByteArray mfhd) {
mfhd.setPosition(FULL_ATOM_HEADER_SIZE);
return mfhd.readUnsignedIntToInt();
} }
/** /**
* Parses a traf atom (defined in 14496-12). * Parses a traf atom (defined in 14496-12).
*/ */
private static void parseTraf(Track track, DefaultSampleValues extendsDefaults, private static void parseTraf(Track track, DefaultSampleValues extendsDefaults,
ContainerAtom traf, TrackFragment out, int workaroundFlags) { ContainerAtom traf, TrackFragment out, int workaroundFlags, byte[] extendedTypeScratch) {
LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt); LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt);
long decodeTime = tfdtAtom == null ? 0 long decodeTime = tfdtAtom == null ? 0 : parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data);
: parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).getData());
LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd); LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
DefaultSampleValues fragmentHeader = parseTfhd(extendsDefaults, tfhd.getData()); DefaultSampleValues fragmentHeader = parseTfhd(extendsDefaults, tfhd.data);
out.setSampleDescriptionIndex(fragmentHeader.sampleDescriptionIndex); out.sampleDescriptionIndex = fragmentHeader.sampleDescriptionIndex;
LeafAtom trun = traf.getLeafAtomOfType(Atom.TYPE_trun); LeafAtom trun = traf.getLeafAtomOfType(Atom.TYPE_trun);
parseTrun(track, fragmentHeader, decodeTime, workaroundFlags, trun.getData(), out); parseTrun(track, fragmentHeader, decodeTime, workaroundFlags, trun.data, out);
TrackEncryptionBox trackEncryptionBox = TrackEncryptionBox trackEncryptionBox =
track.sampleDescriptionEncryptionBoxes[fragmentHeader.sampleDescriptionIndex]; track.sampleDescriptionEncryptionBoxes[fragmentHeader.sampleDescriptionIndex];
LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz); LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz);
if (saiz != null) { if (saiz != null) {
parseSaiz(trackEncryptionBox, saiz.getData(), out); parseSaiz(trackEncryptionBox, saiz.data, out);
} }
LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc); LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc);
if (senc != null) { if (senc != null) {
parseSenc(senc.getData(), out); parseSenc(senc.data, out);
} }
LeafAtom uuid = traf.getLeafAtomOfType(Atom.TYPE_uuid); LeafAtom uuid = traf.getLeafAtomOfType(Atom.TYPE_uuid);
if (uuid != null) { if (uuid != null) {
parseUuid(uuid.getData(), out); parseUuid(uuid.data, out, extendedTypeScratch);
} }
} }
...@@ -821,10 +808,15 @@ public final class FragmentedMp4Extractor { ...@@ -821,10 +808,15 @@ public final class FragmentedMp4Extractor {
saiz.skip(8); saiz.skip(8);
} }
int defaultSampleInfoSize = saiz.readUnsignedByte(); int defaultSampleInfoSize = saiz.readUnsignedByte();
int sampleCount = saiz.readUnsignedIntToInt(); int sampleCount = saiz.readUnsignedIntToInt();
if (sampleCount != out.length) {
throw new IllegalStateException("Length mismatch: " + sampleCount + ", " + out.length);
}
int totalSize = 0; int totalSize = 0;
boolean[] sampleHasSubsampleEncryptionTable = new boolean[sampleCount];
if (defaultSampleInfoSize == 0) { if (defaultSampleInfoSize == 0) {
boolean[] sampleHasSubsampleEncryptionTable = out.sampleHasSubsampleEncryptionTable;
for (int i = 0; i < sampleCount; i++) { for (int i = 0; i < sampleCount; i++) {
int sampleInfoSize = saiz.readUnsignedByte(); int sampleInfoSize = saiz.readUnsignedByte();
totalSize += sampleInfoSize; totalSize += sampleInfoSize;
...@@ -833,10 +825,9 @@ public final class FragmentedMp4Extractor { ...@@ -833,10 +825,9 @@ public final class FragmentedMp4Extractor {
} else { } else {
boolean subsampleEncryption = defaultSampleInfoSize > vectorSize; boolean subsampleEncryption = defaultSampleInfoSize > vectorSize;
totalSize += defaultSampleInfoSize * sampleCount; totalSize += defaultSampleInfoSize * sampleCount;
Arrays.fill(sampleHasSubsampleEncryptionTable, subsampleEncryption); Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption);
} }
out.setSampleEncryptionData(sampleHasSubsampleEncryptionTable, out.initEncryptionData(totalSize);
new ParsableByteArray(totalSize), true);
} }
/** /**
...@@ -895,10 +886,9 @@ public final class FragmentedMp4Extractor { ...@@ -895,10 +886,9 @@ public final class FragmentedMp4Extractor {
long decodeTime, int workaroundFlags, ParsableByteArray trun, TrackFragment out) { long decodeTime, int workaroundFlags, ParsableByteArray trun, TrackFragment out) {
trun.setPosition(ATOM_HEADER_SIZE); trun.setPosition(ATOM_HEADER_SIZE);
int fullAtom = trun.readInt(); int fullAtom = trun.readInt();
int version = parseFullAtomVersion(fullAtom);
int flags = parseFullAtomFlags(fullAtom); int flags = parseFullAtomFlags(fullAtom);
int numberOfEntries = trun.readUnsignedIntToInt(); int sampleCount = trun.readUnsignedIntToInt();
if ((flags & 0x01 /* data_offset_present */) != 0) { if ((flags & 0x01 /* data_offset_present */) != 0) {
trun.skip(4); trun.skip(4);
} }
...@@ -915,17 +905,18 @@ public final class FragmentedMp4Extractor { ...@@ -915,17 +905,18 @@ public final class FragmentedMp4Extractor {
boolean sampleCompositionTimeOffsetsPresent = boolean sampleCompositionTimeOffsetsPresent =
(flags & 0x800 /* sample_composition_time_offsets_present */) != 0; (flags & 0x800 /* sample_composition_time_offsets_present */) != 0;
int[] sampleSizeTable = new int[numberOfEntries]; out.initTables(sampleCount);
int[] sampleDecodingTimeTable = new int[numberOfEntries]; int[] sampleSizeTable = out.sampleSizeTable;
int[] sampleCompositionTimeOffsetTable = new int[numberOfEntries]; int[] sampleDecodingTimeTable = out.sampleDecodingTimeTable;
boolean[] sampleIsSyncFrameTable = new boolean[numberOfEntries]; int[] sampleCompositionTimeOffsetTable = out.sampleCompositionTimeOffsetTable;
boolean[] sampleIsSyncFrameTable = out.sampleIsSyncFrameTable;
long timescale = track.timescale; long timescale = track.timescale;
long cumulativeTime = decodeTime; long cumulativeTime = decodeTime;
boolean workaroundEveryVideoFrameIsSyncFrame = track.type == Track.TYPE_VIDEO boolean workaroundEveryVideoFrameIsSyncFrame = track.type == Track.TYPE_VIDEO
&& ((workaroundFlags & WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME) && ((workaroundFlags & WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME)
== WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME); == WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME);
for (int i = 0; i < numberOfEntries; i++) { for (int i = 0; i < sampleCount; i++) {
// Use trun values if present, otherwise tfhd, otherwise trex. // Use trun values if present, otherwise tfhd, otherwise trex.
int sampleDuration = sampleDurationsPresent ? trun.readUnsignedIntToInt() int sampleDuration = sampleDurationsPresent ? trun.readUnsignedIntToInt()
: defaultSampleValues.duration; : defaultSampleValues.duration;
...@@ -933,18 +924,15 @@ public final class FragmentedMp4Extractor { ...@@ -933,18 +924,15 @@ public final class FragmentedMp4Extractor {
int sampleFlags = (i == 0 && firstSampleFlagsPresent) ? firstSampleFlags int sampleFlags = (i == 0 && firstSampleFlagsPresent) ? firstSampleFlags
: sampleFlagsPresent ? trun.readInt() : defaultSampleValues.flags; : sampleFlagsPresent ? trun.readInt() : defaultSampleValues.flags;
if (sampleCompositionTimeOffsetsPresent) { if (sampleCompositionTimeOffsetsPresent) {
int sampleOffset;
if (version == 0) {
// The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers in // The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers in
// version 0 trun boxes, however a significant number of streams violate the spec and use // version 0 trun boxes, however a significant number of streams violate the spec and use
// signed integers instead. It's safe to always parse sample offsets as signed integers // signed integers instead. It's safe to always parse sample offsets as signed integers
// here, because unsigned integers will still be parsed correctly (unless their top bit is // here, because unsigned integers will still be parsed correctly (unless their top bit is
// set, which is never true in practice because sample offsets are always small). // set, which is never true in practice because sample offsets are always small).
sampleOffset = trun.readInt(); int sampleOffset = trun.readInt();
} else {
sampleOffset = trun.readInt();
}
sampleCompositionTimeOffsetTable[i] = (int) ((sampleOffset * 1000) / timescale); sampleCompositionTimeOffsetTable[i] = (int) ((sampleOffset * 1000) / timescale);
} else {
sampleCompositionTimeOffsetTable[i] = 0;
} }
sampleDecodingTimeTable[i] = (int) ((cumulativeTime * 1000) / timescale); sampleDecodingTimeTable[i] = (int) ((cumulativeTime * 1000) / timescale);
sampleSizeTable[i] = sampleSize; sampleSizeTable[i] = sampleSize;
...@@ -952,18 +940,15 @@ public final class FragmentedMp4Extractor { ...@@ -952,18 +940,15 @@ public final class FragmentedMp4Extractor {
&& (!workaroundEveryVideoFrameIsSyncFrame || i == 0); && (!workaroundEveryVideoFrameIsSyncFrame || i == 0);
cumulativeTime += sampleDuration; cumulativeTime += sampleDuration;
} }
out.setSampleTables(sampleSizeTable, sampleDecodingTimeTable, sampleCompositionTimeOffsetTable,
sampleIsSyncFrameTable);
} }
private static void parseUuid(ParsableByteArray uuid, TrackFragment out) { private static void parseUuid(ParsableByteArray uuid, TrackFragment out,
byte[] extendedTypeScratch) {
uuid.setPosition(ATOM_HEADER_SIZE); uuid.setPosition(ATOM_HEADER_SIZE);
byte[] extendedType = new byte[16]; uuid.readBytes(extendedTypeScratch, 0, 16);
uuid.readBytes(extendedType, 0, 16);
// Currently this parser only supports Microsoft's PIFF SampleEncryptionBox. // Currently this parser only supports Microsoft's PIFF SampleEncryptionBox.
if (!Arrays.equals(extendedType, PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE)) { if (!Arrays.equals(extendedTypeScratch, PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE)) {
return; return;
} }
...@@ -993,14 +978,9 @@ public final class FragmentedMp4Extractor { ...@@ -993,14 +978,9 @@ public final class FragmentedMp4Extractor {
throw new IllegalStateException("Length mismatch: " + sampleCount + ", " + out.length); throw new IllegalStateException("Length mismatch: " + sampleCount + ", " + out.length);
} }
boolean[] sampleHasSubsampleEncryptionTable = new boolean[sampleCount]; Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption);
Arrays.fill(sampleHasSubsampleEncryptionTable, subsampleEncryption); out.initEncryptionData(senc.length() - senc.getPosition());
out.fillEncryptionData(senc);
int sampleEncryptionDataLength = senc.length() - senc.getPosition();
ParsableByteArray sampleEncryptionData = new ParsableByteArray(sampleEncryptionDataLength);
senc.readBytes(sampleEncryptionData.getData(), 0, sampleEncryptionDataLength);
out.setSampleEncryptionData(sampleHasSubsampleEncryptionTable, sampleEncryptionData, false);
} }
/** /**
...@@ -1060,13 +1040,10 @@ public final class FragmentedMp4Extractor { ...@@ -1060,13 +1040,10 @@ public final class FragmentedMp4Extractor {
} }
private int readEncryptionData(NonBlockingInputStream inputStream) { private int readEncryptionData(NonBlockingInputStream inputStream) {
ParsableByteArray sampleEncryptionData = fragmentRun.sampleEncryptionData; boolean success = fragmentRun.fillEncryptionData(inputStream);
int sampleEncryptionDataLength = sampleEncryptionData.length(); if (!success) {
if (inputStream.getAvailableByteCount() < sampleEncryptionDataLength) {
return RESULT_NEED_MORE_DATA; return RESULT_NEED_MORE_DATA;
} }
inputStream.read(sampleEncryptionData.getData(), 0, sampleEncryptionDataLength);
fragmentRun.sampleEncryptionDataNeedsFill = false;
enterState(STATE_READING_SAMPLE); enterState(STATE_READING_SAMPLE);
return 0; return 0;
} }
...@@ -1105,8 +1082,8 @@ public final class FragmentedMp4Extractor { ...@@ -1105,8 +1082,8 @@ public final class FragmentedMp4Extractor {
} }
private int skipSample(NonBlockingInputStream inputStream, int sampleSize) { private int skipSample(NonBlockingInputStream inputStream, int sampleSize) {
if (fragmentRun.definesEncryptionData) {
ParsableByteArray sampleEncryptionData = fragmentRun.sampleEncryptionData; ParsableByteArray sampleEncryptionData = fragmentRun.sampleEncryptionData;
if (sampleEncryptionData != null) {
TrackEncryptionBox encryptionBox = TrackEncryptionBox encryptionBox =
track.sampleDescriptionEncryptionBoxes[fragmentRun.sampleDescriptionIndex]; track.sampleDescriptionEncryptionBoxes[fragmentRun.sampleDescriptionIndex];
int vectorSize = encryptionBox.initializationVectorSize; int vectorSize = encryptionBox.initializationVectorSize;
...@@ -1141,9 +1118,8 @@ public final class FragmentedMp4Extractor { ...@@ -1141,9 +1118,8 @@ public final class FragmentedMp4Extractor {
outputData = ByteBuffer.allocate(sampleSize); outputData = ByteBuffer.allocate(sampleSize);
out.data = outputData; out.data = outputData;
} }
ParsableByteArray sampleEncryptionData = fragmentRun.sampleEncryptionData; if (fragmentRun.definesEncryptionData) {
if (sampleEncryptionData != null) { readSampleEncryptionData(fragmentRun.sampleEncryptionData, out);
readSampleEncryptionData(sampleEncryptionData, out);
} }
if (outputData == null) { if (outputData == null) {
......
...@@ -23,17 +23,14 @@ import java.nio.ByteBuffer; ...@@ -23,17 +23,14 @@ import java.nio.ByteBuffer;
*/ */
/* package */ final class ParsableByteArray { /* package */ final class ParsableByteArray {
private final byte[] data; public byte[] data;
private int position; private int position;
public ParsableByteArray(int length) { public ParsableByteArray(int length) {
this.data = new byte[length]; this.data = new byte[length];
} }
public byte[] getData() {
return data;
}
public int length() { public int length() {
return data.length; return data.length;
} }
......
...@@ -18,7 +18,7 @@ package com.google.android.exoplayer.parser.mp4; ...@@ -18,7 +18,7 @@ package com.google.android.exoplayer.parser.mp4;
/** /**
* Encapsulates information parsed from a track encryption (tenc) box in an MP4 stream. * Encapsulates information parsed from a track encryption (tenc) box in an MP4 stream.
*/ */
public class TrackEncryptionBox { public final class TrackEncryptionBox {
/** /**
* Indicates the encryption state of the samples in the sample group. * Indicates the encryption state of the samples in the sample group.
......
...@@ -15,41 +15,136 @@ ...@@ -15,41 +15,136 @@
*/ */
package com.google.android.exoplayer.parser.mp4; package com.google.android.exoplayer.parser.mp4;
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
/** /**
* A holder for information corresponding to a single fragment of an mp4 file. * A holder for information corresponding to a single fragment of an mp4 file.
*/ */
/* package */ class TrackFragment { /* package */ final class TrackFragment {
public int sampleDescriptionIndex; public int sampleDescriptionIndex;
/**
* The number of samples contained by the fragment.
*/
public int length; public int length;
/**
* The size of each sample in the run.
*/
public int[] sampleSizeTable; public int[] sampleSizeTable;
/**
* The decoding time of each sample in the run.
*/
public int[] sampleDecodingTimeTable; public int[] sampleDecodingTimeTable;
/**
* The composition time offset of each sample in the run.
*/
public int[] sampleCompositionTimeOffsetTable; public int[] sampleCompositionTimeOffsetTable;
/**
* Indicates which samples are sync frames.
*/
public boolean[] sampleIsSyncFrameTable; public boolean[] sampleIsSyncFrameTable;
/**
* True if the fragment defines encryption data. False otherwise.
*/
public boolean definesEncryptionData;
/**
* If {@link #definesEncryptionData} is true, indicates which samples use sub-sample encryption.
* Undefined otherwise.
*/
public boolean[] sampleHasSubsampleEncryptionTable; public boolean[] sampleHasSubsampleEncryptionTable;
/**
* If {@link #definesEncryptionData} is true, indicates the length of the sample encryption data.
* Undefined otherwise.
*/
public int sampleEncryptionDataLength;
/**
* If {@link #definesEncryptionData} is true, contains binary sample encryption data. Undefined
* otherwise.
*/
public ParsableByteArray sampleEncryptionData; public ParsableByteArray sampleEncryptionData;
/**
* Whether {@link #sampleEncryptionData} needs populating with the actual encryption data.
*/
public boolean sampleEncryptionDataNeedsFill; public boolean sampleEncryptionDataNeedsFill;
public void setSampleDescriptionIndex(int sampleDescriptionIndex) { /**
this.sampleDescriptionIndex = sampleDescriptionIndex; * Resets the fragment.
* <p>
* The {@link #length} is set to 0, and both {@link #definesEncryptionData} and
* {@link #sampleEncryptionDataNeedsFill} is set to false.
*/
public void reset() {
length = 0;
definesEncryptionData = false;
sampleEncryptionDataNeedsFill = false;
}
/**
* Configures the fragment for the specified number of samples.
* <p>
* The {@link #length} of the fragment is set to the specified sample count, and the contained
* tables are resized if necessary such that they are at least this length.
*
* @param sampleCount The number of samples in the new run.
*/
public void initTables(int sampleCount) {
length = sampleCount;
if (sampleSizeTable == null || sampleSizeTable.length < length) {
// Size the tables 25% larger than needed, so as to make future resize operations less
// likely. The choice of 25% is relatively arbitrary.
int tableSize = (sampleCount * 125) / 100;
sampleSizeTable = new int[tableSize];
sampleDecodingTimeTable = new int[tableSize];
sampleCompositionTimeOffsetTable = new int[tableSize];
sampleIsSyncFrameTable = new boolean[tableSize];
sampleHasSubsampleEncryptionTable = new boolean[tableSize];
}
}
/**
* Configures the fragment to be one that defines encryption data of the specified length.
* <p>
* {@link #definesEncryptionData} is set to true, {@link #sampleEncryptionDataLength} is set to
* the specified length, and {@link #sampleEncryptionData} is resized if necessary such that it
* is at least this length.
*
* @param length The length in bytes of the encryption data.
*/
public void initEncryptionData(int length) {
if (sampleEncryptionData == null || sampleEncryptionData.length() < length) {
sampleEncryptionData = new ParsableByteArray(length);
}
sampleEncryptionDataLength = length;
definesEncryptionData = true;
sampleEncryptionDataNeedsFill = true;
} }
public void setSampleTables(int[] sampleSizeTable, int[] sampleDecodingTimeTable, /**
int[] sampleCompositionTimeOffsetTable, boolean[] sampleIsSyncFrameTable) { * Fills {@link #sampleEncryptionData} from the provided source.
this.sampleSizeTable = sampleSizeTable; *
this.sampleDecodingTimeTable = sampleDecodingTimeTable; * @param source A source from which to read the encryption data.
this.sampleCompositionTimeOffsetTable = sampleCompositionTimeOffsetTable; */
this.sampleIsSyncFrameTable = sampleIsSyncFrameTable; public void fillEncryptionData(ParsableByteArray source) {
this.length = sampleSizeTable.length; source.readBytes(sampleEncryptionData.data, 0, sampleEncryptionDataLength);
sampleEncryptionData.setPosition(0);
sampleEncryptionDataNeedsFill = false;
} }
public void setSampleEncryptionData(boolean[] sampleHasSubsampleEncryptionTable, /**
ParsableByteArray sampleEncryptionData, boolean sampleEncryptionDataNeedsFill) { * Fills {@link #sampleEncryptionData} for the current run from the provided source.
this.sampleHasSubsampleEncryptionTable = sampleHasSubsampleEncryptionTable; *
this.sampleEncryptionData = sampleEncryptionData; * @param source A source from which to read the encryption data.
this.sampleEncryptionDataNeedsFill = sampleEncryptionDataNeedsFill; * @return True if the encryption data was filled. False if the source had insufficient data.
*/
public boolean fillEncryptionData(NonBlockingInputStream source) {
if (source.getAvailableByteCount() < sampleEncryptionDataLength) {
return false;
}
source.read(sampleEncryptionData.data, 0, sampleEncryptionDataLength);
sampleEncryptionData.setPosition(0);
sampleEncryptionDataNeedsFill = false;
return true;
} }
public int getSamplePresentationTime(int index) { public int getSamplePresentationTime(int index) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment