Commit 4227c8f1 by kimvde Committed by Oliver Woodman

Move MP4 getTrackSampleTables to AtomParsers

PiperOrigin-RevId: 318485946
parent b9511697
...@@ -43,7 +43,7 @@ import java.util.Collections; ...@@ -43,7 +43,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.compatqual.NullableType;
/** Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. */ /** Utility methods for parsing MP4 format atom payloads according to ISO/IEC 14496-12. */
@SuppressWarnings({"ConstantField"}) @SuppressWarnings({"ConstantField"})
/* package */ final class AtomParsers { /* package */ final class AtomParsers {
...@@ -83,7 +83,54 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -83,7 +83,54 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
private static final byte[] opusMagic = Util.getUtf8Bytes("OpusHead"); private static final byte[] opusMagic = Util.getUtf8Bytes("OpusHead");
/** /**
* Parses a trak atom (defined in 14496-12). * Parse the trak atoms in a moov atom (defined in ISO/IEC 14496-12).
*
* @param moov Moov atom to decode.
* @param gaplessInfoHolder Holder to populate with gapless playback information.
* @param ignoreEditLists Whether to ignore any edit lists in the trak boxes.
* @param isQuickTime True for QuickTime media. False otherwise.
* @return A list of {@link TrackSampleTable} instances.
* @throws ParserException Thrown if the trak atoms can't be parsed.
*/
public static List<TrackSampleTable> parseTraks(
Atom.ContainerAtom moov,
GaplessInfoHolder gaplessInfoHolder,
boolean ignoreEditLists,
boolean isQuickTime)
throws ParserException {
List<TrackSampleTable> trackSampleTables = new ArrayList<>();
for (int i = 0; i < moov.containerChildren.size(); i++) {
Atom.ContainerAtom atom = moov.containerChildren.get(i);
if (atom.type != Atom.TYPE_trak) {
continue;
}
@Nullable
Track track =
parseTrak(
atom,
moov.getLeafAtomOfType(Atom.TYPE_mvhd),
/* duration= */ C.TIME_UNSET,
/* drmInitData= */ null,
ignoreEditLists,
isQuickTime);
if (track == null) {
continue;
}
Atom.ContainerAtom stblAtom =
atom.getContainerAtomOfType(Atom.TYPE_mdia)
.getContainerAtomOfType(Atom.TYPE_minf)
.getContainerAtomOfType(Atom.TYPE_stbl);
TrackSampleTable trackSampleTable = parseStbl(track, stblAtom, gaplessInfoHolder);
if (trackSampleTable.sampleCount == 0) {
continue;
}
trackSampleTables.add(trackSampleTable);
}
return trackSampleTables;
}
/**
* Parses a trak atom (defined in ISO/IEC 14496-12).
* *
* @param trak Atom to decode. * @param trak Atom to decode.
* @param mvhd Movie header atom, used to get the timescale. * @param mvhd Movie header atom, used to get the timescale.
...@@ -93,6 +140,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -93,6 +140,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
* @param ignoreEditLists Whether to ignore any edit lists in the trak box. * @param ignoreEditLists Whether to ignore any edit lists in the trak box.
* @param isQuickTime True for QuickTime media. False otherwise. * @param isQuickTime True for QuickTime media. False otherwise.
* @return A {@link Track} instance, or {@code null} if the track's type isn't supported. * @return A {@link Track} instance, or {@code null} if the track's type isn't supported.
* @throws ParserException Thrown if the trak atom can't be parsed.
*/ */
@Nullable @Nullable
public static Track parseTrak( public static Track parseTrak(
...@@ -145,7 +193,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -145,7 +193,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} }
/** /**
* Parses an stbl atom (defined in 14496-12). * Parses an stbl atom (defined in ISO/IEC 14496-12).
* *
* @param track Track to which this sample table corresponds. * @param track Track to which this sample table corresponds.
* @param stblAtom stbl (sample table) atom to decode. * @param stblAtom stbl (sample table) atom to decode.
...@@ -275,11 +323,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -275,11 +323,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
if (ctts != null) { if (ctts != null) {
while (remainingSamplesAtTimestampOffset == 0 && remainingTimestampOffsetChanges > 0) { while (remainingSamplesAtTimestampOffset == 0 && remainingTimestampOffsetChanges > 0) {
remainingSamplesAtTimestampOffset = ctts.readUnsignedIntToInt(); remainingSamplesAtTimestampOffset = ctts.readUnsignedIntToInt();
// The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers // The BMFF spec (ISO/IEC 14496-12) states that sample offsets should be unsigned
// in version 0 ctts boxes, however some streams violate the spec and use signed // integers in version 0 ctts boxes, however some streams violate the spec and use
// integers instead. It's safe to always decode sample offsets as signed integers here, // signed integers instead. It's safe to always decode sample offsets as signed integers
// 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
// set, which is never true in practice because sample offsets are always small). // is set, which is never true in practice because sample offsets are always small).
timestampOffset = ctts.readInt(); timestampOffset = ctts.readInt();
remainingTimestampOffsetChanges--; remainingTimestampOffsetChanges--;
} }
...@@ -308,7 +356,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -308,7 +356,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
remainingSamplesAtTimestampDelta--; remainingSamplesAtTimestampDelta--;
if (remainingSamplesAtTimestampDelta == 0 && remainingTimestampDeltaChanges > 0) { if (remainingSamplesAtTimestampDelta == 0 && remainingTimestampDeltaChanges > 0) {
remainingSamplesAtTimestampDelta = stts.readUnsignedIntToInt(); remainingSamplesAtTimestampDelta = stts.readUnsignedIntToInt();
// The BMFF spec (ISO 14496-12) states that sample deltas should be unsigned integers // The BMFF spec (ISO/IEC 14496-12) states that sample deltas should be unsigned integers
// in stts boxes, however some streams violate the spec and use signed integers instead. // in stts boxes, however some streams violate the spec and use signed integers instead.
// See https://github.com/google/ExoPlayer/issues/3384. It's safe to always decode sample // See https://github.com/google/ExoPlayer/issues/3384. It's safe to always decode sample
// deltas as signed integers here, because unsigned integers will still be parsed // deltas as signed integers here, because unsigned integers will still be parsed
...@@ -382,12 +430,12 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -382,12 +430,12 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
track, offsets, sizes, maximumSize, timestamps, flags, durationUs); track, offsets, sizes, maximumSize, timestamps, flags, durationUs);
} }
// See the BMFF spec (ISO 14496-12) subsection 8.6.6. Edit lists that require prerolling from a // See the BMFF spec (ISO/IEC 14496-12) subsection 8.6.6. Edit lists that require prerolling
// sync sample after reordering are not supported. Partial audio sample truncation is only // from a sync sample after reordering are not supported. Partial audio sample truncation is
// supported in edit lists with one edit that removes less than MAX_GAPLESS_TRIM_SIZE_SAMPLES // only supported in edit lists with one edit that removes less than
// samples from the start/end of the track. This implementation handles simple // MAX_GAPLESS_TRIM_SIZE_SAMPLES samples from the start/end of the track. This implementation
// discarding/delaying of samples. The extractor may place further restrictions on what edited // handles simple discarding/delaying of samples. The extractor may place further restrictions
// streams are playable. // on what edited streams are playable.
if (track.editListDurations.length == 1 if (track.editListDurations.length == 1
&& track.type == C.TRACK_TYPE_AUDIO && track.type == C.TRACK_TYPE_AUDIO
...@@ -556,7 +604,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -556,7 +604,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
if (hdlrAtom == null if (hdlrAtom == null
|| keysAtom == null || keysAtom == null
|| ilstAtom == null || ilstAtom == null
|| AtomParsers.parseHdlr(hdlrAtom.data) != TYPE_mdta) { || parseHdlr(hdlrAtom.data) != TYPE_mdta) {
// There isn't enough information to parse the metadata, or the handler type is unexpected. // There isn't enough information to parse the metadata, or the handler type is unexpected.
return null; return null;
} }
...@@ -627,7 +675,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -627,7 +675,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} }
/** /**
* Parses a mvhd atom (defined in 14496-12), returning the timescale for the movie. * Parses a mvhd atom (defined in ISO/IEC 14496-12), returning the timescale for the movie.
* *
* @param mvhd Contents of the mvhd atom to be parsed. * @param mvhd Contents of the mvhd atom to be parsed.
* @return Timescale for the movie. * @return Timescale for the movie.
...@@ -641,7 +689,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -641,7 +689,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} }
/** /**
* Parses a tkhd atom (defined in 14496-12). * Parses a tkhd atom (defined in ISO/IEC 14496-12).
* *
* @return An object containing the parsed data. * @return An object containing the parsed data.
*/ */
...@@ -726,11 +774,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -726,11 +774,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} }
/** /**
* Parses an mdhd atom (defined in 14496-12). * Parses an mdhd atom (defined in ISO/IEC 14496-12).
* *
* @param mdhd The mdhd atom to decode. * @param mdhd The mdhd atom to decode.
* @return A pair consisting of the media timescale defined as the number of time units that pass * @return A pair consisting of the media timescale defined as the number of time units that pass
* in one second, and the language code. * in one second, and the language code.
*/ */
private static Pair<Long, String> parseMdhd(ParsableByteArray mdhd) { private static Pair<Long, String> parseMdhd(ParsableByteArray mdhd) {
mdhd.setPosition(Atom.HEADER_SIZE); mdhd.setPosition(Atom.HEADER_SIZE);
...@@ -749,7 +797,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -749,7 +797,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} }
/** /**
* Parses a stsd atom (defined in 14496-12). * Parses a stsd atom (defined in ISO/IEC 14496-12).
* *
* @param stsd The stsd atom to decode. * @param stsd The stsd atom to decode.
* @param trackId The track's identifier in its container. * @param trackId The track's identifier in its container.
...@@ -1025,7 +1073,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -1025,7 +1073,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} }
/** /**
* Parses the edts atom (defined in 14496-12 subsection 8.6.5). * Parses the edts atom (defined in ISO/IEC 14496-12 subsection 8.6.5).
* *
* @param edtsAtom edts (edit box) atom to decode. * @param edtsAtom edts (edit box) atom to decode.
* @return Pair of edit list durations and edit list media times, or {@code null} if they are not * @return Pair of edit list durations and edit list media times, or {@code null} if they are not
...@@ -1287,7 +1335,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -1287,7 +1335,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
private static Pair<@NullableType String, byte @NullableType []> parseEsdsFromParent( private static Pair<@NullableType String, byte @NullableType []> parseEsdsFromParent(
ParsableByteArray parent, int position) { ParsableByteArray parent, int position) {
parent.setPosition(position + Atom.HEADER_SIZE + 4); parent.setPosition(position + Atom.HEADER_SIZE + 4);
// Start of the ES_Descriptor (defined in 14496-1) // Start of the ES_Descriptor (defined in ISO/IEC 14496-1)
parent.skipBytes(1); // ES_Descriptor tag parent.skipBytes(1); // ES_Descriptor tag
parseExpandableClassSize(parent); parseExpandableClassSize(parent);
parent.skipBytes(2); // ES_ID parent.skipBytes(2); // ES_ID
...@@ -1303,11 +1351,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -1303,11 +1351,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
parent.skipBytes(2); parent.skipBytes(2);
} }
// Start of the DecoderConfigDescriptor (defined in 14496-1) // Start of the DecoderConfigDescriptor (defined in ISO/IEC 14496-1)
parent.skipBytes(1); // DecoderConfigDescriptor tag parent.skipBytes(1); // DecoderConfigDescriptor tag
parseExpandableClassSize(parent); parseExpandableClassSize(parent);
// Set the MIME type based on the object type indication (14496-1 table 5). // Set the MIME type based on the object type indication (ISO/IEC 14496-1 table 5).
int objectTypeIndication = parent.readUnsignedByte(); int objectTypeIndication = parent.readUnsignedByte();
String mimeType = getMimeTypeFromMp4ObjectType(objectTypeIndication); String mimeType = getMimeTypeFromMp4ObjectType(objectTypeIndication);
if (MimeTypes.AUDIO_MPEG.equals(mimeType) if (MimeTypes.AUDIO_MPEG.equals(mimeType)
...@@ -1448,9 +1496,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -1448,9 +1496,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
return null; return null;
} }
/** /** Parses the size of an expandable class, as specified by ISO/IEC 14496-1 subsection 8.3.3. */
* Parses the size of an expandable class, as specified by ISO 14496-1 subsection 8.3.3.
*/
private static int parseExpandableClassSize(ParsableByteArray data) { private static int parseExpandableClassSize(ParsableByteArray data) {
int currentByte = data.readUnsignedByte(); int currentByte = data.readUnsignedByte();
int size = currentByte & 0x7F; int size = currentByte & 0x7F;
......
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer2.extractor.mp4; package com.google.android.exoplayer2.extractor.mp4;
import static com.google.android.exoplayer2.extractor.mp4.AtomParsers.parseTraks;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
...@@ -406,8 +408,8 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -406,8 +408,8 @@ public final class Mp4Extractor implements Extractor, SeekMap {
} }
boolean ignoreEditLists = (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0; boolean ignoreEditLists = (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0;
ArrayList<TrackSampleTable> trackSampleTables = List<TrackSampleTable> trackSampleTables =
getTrackSampleTables(moov, gaplessInfoHolder, ignoreEditLists); parseTraks(moov, gaplessInfoHolder, ignoreEditLists, isQuickTime);
int trackCount = trackSampleTables.size(); int trackCount = trackSampleTables.size();
for (int i = 0; i < trackCount; i++) { for (int i = 0; i < trackCount; i++) {
...@@ -448,40 +450,6 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -448,40 +450,6 @@ public final class Mp4Extractor implements Extractor, SeekMap {
extractorOutput.seekMap(this); extractorOutput.seekMap(this);
} }
private ArrayList<TrackSampleTable> getTrackSampleTables(
ContainerAtom moov, GaplessInfoHolder gaplessInfoHolder, boolean ignoreEditLists)
throws ParserException {
ArrayList<TrackSampleTable> trackSampleTables = new ArrayList<>();
for (int i = 0; i < moov.containerChildren.size(); i++) {
Atom.ContainerAtom atom = moov.containerChildren.get(i);
if (atom.type != Atom.TYPE_trak) {
continue;
}
@Nullable
Track track =
AtomParsers.parseTrak(
atom,
moov.getLeafAtomOfType(Atom.TYPE_mvhd),
/* duration= */ C.TIME_UNSET,
/* drmInitData= */ null,
ignoreEditLists,
isQuickTime);
if (track == null) {
continue;
}
Atom.ContainerAtom stblAtom =
atom.getContainerAtomOfType(Atom.TYPE_mdia)
.getContainerAtomOfType(Atom.TYPE_minf)
.getContainerAtomOfType(Atom.TYPE_stbl);
TrackSampleTable trackSampleTable = AtomParsers.parseStbl(track, stblAtom, gaplessInfoHolder);
if (trackSampleTable.sampleCount == 0) {
continue;
}
trackSampleTables.add(trackSampleTable);
}
return trackSampleTables;
}
/** /**
* Attempts to extract the next sample in the current mdat atom for the specified track. * Attempts to extract the next sample in the current mdat atom for the specified track.
* *
......
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