mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Move MP4 getTrackSampleTables to AtomParsers
PiperOrigin-RevId: 318485946
This commit is contained in:
parent
b9511697f6
commit
4227c8f19f
2 changed files with 78 additions and 64 deletions
|
|
@ -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;
|
||||||
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;
|
||||||
* @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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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;
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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;
|
||||||
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;
|
||||||
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;
|
||||||
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 @@
|
||||||
*/
|
*/
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
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.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue