Merge pull request #1607 from colinkho:atom

PiperOrigin-RevId: 665285219
This commit is contained in:
Copybara-Service 2024-08-20 04:03:45 -07:00
commit 643ec73e58
9 changed files with 95 additions and 89 deletions

View File

@ -34,6 +34,7 @@ import androidx.media3.common.util.Log;
import androidx.media3.common.util.NullableType; import androidx.media3.common.util.NullableType;
import androidx.media3.common.util.ParsableBitArray; import androidx.media3.common.util.ParsableBitArray;
import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.container.Mp4Box; import androidx.media3.container.Mp4Box;
import androidx.media3.container.Mp4Box.LeafBox; import androidx.media3.container.Mp4Box.LeafBox;
@ -60,11 +61,12 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
/** Utility methods for parsing MP4 format atom payloads according to ISO/IEC 14496-12. */ /** Utility methods for parsing MP4 format box payloads according to ISO/IEC 14496-12. */
@SuppressWarnings("ConstantField") @SuppressWarnings("ConstantField")
/* package */ final class AtomParsers { @UnstableApi
public final class BoxParser {
private static final String TAG = "AtomParsers"; private static final String TAG = "BoxParsers";
@SuppressWarnings("ConstantCaseForConstants") @SuppressWarnings("ConstantCaseForConstants")
private static final int TYPE_clcp = 0x636c6370; private static final int TYPE_clcp = 0x636c6370;
@ -105,29 +107,29 @@ import java.util.Objects;
/** The magic signature for an Opus Identification header, as defined in RFC-7845. */ /** The magic signature for an Opus Identification header, as defined in RFC-7845. */
private static final byte[] opusMagic = Util.getUtf8Bytes("OpusHead"); private static final byte[] opusMagic = Util.getUtf8Bytes("OpusHead");
/** Parses the version number out of the additional integer component of a full atom. */ /** Parses the version number out of the additional integer component of a full box. */
public static int parseFullAtomVersion(int fullAtomInt) { public static int parseFullBoxVersion(int fullBoxInt) {
return 0x000000FF & (fullAtomInt >> 24); return 0x000000FF & (fullBoxInt >> 24);
} }
/** Parses the atom flags out of the additional integer component of a full atom. */ /** Parses the box flags out of the additional integer component of a full box. */
public static int parseFullAtomFlags(int fullAtomInt) { public static int parseFullBoxFlags(int fullBoxInt) {
return 0x00FFFFFF & fullAtomInt; return 0x00FFFFFF & fullBoxInt;
} }
/** /**
* Parse the trak atoms in a moov atom (defined in ISO/IEC 14496-12). * Parse the trak boxes in a moov box (defined in ISO/IEC 14496-12).
* *
* @param moov Moov atom to decode. * @param moov Moov box to decode.
* @param gaplessInfoHolder Holder to populate with gapless playback information. * @param gaplessInfoHolder Holder to populate with gapless playback information.
* @param duration The duration in units of the timescale declared in the mvhd atom, or {@link * @param duration The duration in units of the timescale declared in the mvhd box, or {@link
* C#TIME_UNSET} if the duration should be parsed from the tkhd atom. * C#TIME_UNSET} if the duration should be parsed from the tkhd box.
* @param drmInitData {@link DrmInitData} to be included in the format, or {@code null}. * @param drmInitData {@link DrmInitData} to be included in the format, or {@code null}.
* @param ignoreEditLists Whether to ignore any edit lists in the trak boxes. * @param ignoreEditLists Whether to ignore any edit lists in the trak boxes.
* @param isQuickTime True for QuickTime media. False otherwise. * @param isQuickTime True for QuickTime media. False otherwise.
* @param modifyTrackFunction A function to apply to the {@link Track Tracks} in the result. * @param modifyTrackFunction A function to apply to the {@link Track Tracks} in the result.
* @return A list of {@link TrackSampleTable} instances. * @return A list of {@link TrackSampleTable} instances.
* @throws ParserException Thrown if the trak atoms can't be parsed. * @throws ParserException Thrown if the trak boxes can't be parsed.
*/ */
public static List<TrackSampleTable> parseTraks( public static List<TrackSampleTable> parseTraks(
Mp4Box.ContainerBox moov, Mp4Box.ContainerBox moov,
@ -170,13 +172,13 @@ import java.util.Objects;
} }
/** /**
* Parses a udta atom. * Parses a udta box.
* *
* @param udtaAtom The udta (user data) atom to decode. * @param udtaBox The udta (user data) box to decode.
* @return Parsed metadata. * @return Parsed metadata.
*/ */
public static Metadata parseUdta(LeafBox udtaAtom) { public static Metadata parseUdta(LeafBox udtaBox) {
ParsableByteArray udtaData = udtaAtom.data; ParsableByteArray udtaData = udtaBox.data;
udtaData.setPosition(Mp4Box.HEADER_SIZE); udtaData.setPosition(Mp4Box.HEADER_SIZE);
Metadata metadata = new Metadata(); Metadata metadata = new Metadata();
while (udtaData.bytesLeft() >= Mp4Box.HEADER_SIZE) { while (udtaData.bytesLeft() >= Mp4Box.HEADER_SIZE) {
@ -201,15 +203,15 @@ import java.util.Objects;
} }
/** /**
* Parses an mvhd atom (defined in ISO/IEC 14496-12). * Parses an mvhd box (defined in ISO/IEC 14496-12).
* *
* @param mvhd Contents of the mvhd atom to be parsed. * @param mvhd Contents of the mvhd box to be parsed.
* @return An object containing the parsed data. * @return An object containing the parsed data.
*/ */
public static Mp4TimestampData parseMvhd(ParsableByteArray mvhd) { public static Mp4TimestampData parseMvhd(ParsableByteArray mvhd) {
mvhd.setPosition(Mp4Box.HEADER_SIZE); mvhd.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = mvhd.readInt(); int fullAtom = mvhd.readInt();
int version = parseFullAtomVersion(fullAtom); int version = parseFullBoxVersion(fullAtom);
long creationTimestampSeconds; long creationTimestampSeconds;
long modificationTimestampSeconds; long modificationTimestampSeconds;
if (version == 0) { if (version == 0) {
@ -225,9 +227,9 @@ import java.util.Objects;
} }
/** /**
* Parses a metadata meta atom if it contains metadata with handler 'mdta'. * Parses a metadata meta box if it contains metadata with handler 'mdta'.
* *
* @param meta The metadata atom to decode. * @param meta The metadata box to decode.
* @return Parsed metadata, or null. * @return Parsed metadata, or null.
*/ */
@Nullable @Nullable
@ -280,17 +282,17 @@ import java.util.Objects;
} }
/** /**
* Possibly skips the version and flags fields (1+3 byte) of a full meta atom. * Possibly skips the version and flags fields (1+3 byte) of a full meta box.
* *
* <p>Atoms of type {@link Mp4Box#TYPE_meta} are defined to be full atoms which have four * <p>Boxes of type {@link Mp4Box#TYPE_meta} are defined to be full boxes which have four
* additional bytes for a version and a flags field (see 4.2 'Object Structure' in ISO/IEC * additional bytes for a version and a flags field (see 4.2 'Object Structure' in ISO/IEC
* 14496-12:2005). QuickTime do not have such a full box structure. Since some of these files are * 14496-12:2005). QuickTime do not have such a full box structure. Since some of these files are
* encoded wrongly, we can't rely on the file type though. Instead we must check the 8 bytes after * encoded wrongly, we can't rely on the file type though. Instead we must check the 8 bytes after
* the common header bytes ourselves. * the common header bytes ourselves.
* *
* @param meta The 8 or more bytes following the meta atom size and type. * @param meta The 8 or more bytes following the meta box size and type.
*/ */
public static void maybeSkipRemainingMetaAtomHeaderBytes(ParsableByteArray meta) { public static void maybeSkipRemainingMetaBoxHeaderBytes(ParsableByteArray meta) {
int endPosition = meta.getPosition(); int endPosition = meta.getPosition();
// The next 8 bytes can be either: // The next 8 bytes can be either:
// (iso) [1 byte version + 3 bytes flags][4 byte size of next atom] // (iso) [1 byte version + 3 bytes flags][4 byte size of next atom]
@ -304,20 +306,20 @@ import java.util.Objects;
} }
/** /**
* Parses a trak atom (defined in ISO/IEC 14496-12). * Parses a trak box (defined in ISO/IEC 14496-12).
* *
* @param trak Atom to decode. * @param trak Box to decode.
* @param mvhd Movie header atom, used to get the timescale. * @param mvhd Movie header box, used to get the timescale.
* @param duration The duration in units of the timescale declared in the mvhd atom, or {@link * @param duration The duration in units of the timescale declared in the mvhd box, or {@link
* C#TIME_UNSET} if the duration should be parsed from the tkhd atom. * C#TIME_UNSET} if the duration should be parsed from the tkhd box.
* @param drmInitData {@link DrmInitData} to be included in the format, or {@code null}. * @param drmInitData {@link DrmInitData} to be included in the format, or {@code null}.
* @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. * @throws ParserException Thrown if the trak box can't be parsed.
*/ */
@Nullable @Nullable
private static Track parseTrak( public static Track parseTrak(
Mp4Box.ContainerBox trak, Mp4Box.ContainerBox trak,
LeafBox mvhd, LeafBox mvhd,
long duration, long duration,
@ -393,23 +395,23 @@ import java.util.Objects;
} }
/** /**
* Parses an stbl atom (defined in ISO/IEC 14496-12). * Parses an stbl box (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 stblBox stbl (sample table) box to decode.
* @param gaplessInfoHolder Holder to populate with gapless playback information. * @param gaplessInfoHolder Holder to populate with gapless playback information.
* @return Sample table described by the stbl atom. * @return Sample table described by the stbl box.
* @throws ParserException Thrown if the stbl atom can't be parsed. * @throws ParserException Thrown if the stbl box can't be parsed.
*/ */
private static TrackSampleTable parseStbl( public static TrackSampleTable parseStbl(
Track track, Mp4Box.ContainerBox stblAtom, GaplessInfoHolder gaplessInfoHolder) Track track, Mp4Box.ContainerBox stblBox, GaplessInfoHolder gaplessInfoHolder)
throws ParserException { throws ParserException {
SampleSizeBox sampleSizeBox; SampleSizeBox sampleSizeBox;
@Nullable LeafBox stszAtom = stblAtom.getLeafBoxOfType(Mp4Box.TYPE_stsz); @Nullable LeafBox stszAtom = stblBox.getLeafBoxOfType(Mp4Box.TYPE_stsz);
if (stszAtom != null) { if (stszAtom != null) {
sampleSizeBox = new StszSampleSizeBox(stszAtom, track.format); sampleSizeBox = new StszSampleSizeBox(stszAtom, track.format);
} else { } else {
@Nullable LeafBox stz2Atom = stblAtom.getLeafBoxOfType(Mp4Box.TYPE_stz2); @Nullable LeafBox stz2Atom = stblBox.getLeafBoxOfType(Mp4Box.TYPE_stz2);
if (stz2Atom == null) { if (stz2Atom == null) {
throw ParserException.createForMalformedContainer( throw ParserException.createForMalformedContainer(
"Track has no sample table size information", /* cause= */ null); "Track has no sample table size information", /* cause= */ null);
@ -431,21 +433,21 @@ import java.util.Objects;
// Entries are byte offsets of chunks. // Entries are byte offsets of chunks.
boolean chunkOffsetsAreLongs = false; boolean chunkOffsetsAreLongs = false;
@Nullable LeafBox chunkOffsetsAtom = stblAtom.getLeafBoxOfType(Mp4Box.TYPE_stco); @Nullable LeafBox chunkOffsetsAtom = stblBox.getLeafBoxOfType(Mp4Box.TYPE_stco);
if (chunkOffsetsAtom == null) { if (chunkOffsetsAtom == null) {
chunkOffsetsAreLongs = true; chunkOffsetsAreLongs = true;
chunkOffsetsAtom = checkNotNull(stblAtom.getLeafBoxOfType(Mp4Box.TYPE_co64)); chunkOffsetsAtom = checkNotNull(stblBox.getLeafBoxOfType(Mp4Box.TYPE_co64));
} }
ParsableByteArray chunkOffsets = chunkOffsetsAtom.data; ParsableByteArray chunkOffsets = chunkOffsetsAtom.data;
// Entries are (chunk number, number of samples per chunk, sample description index). // Entries are (chunk number, number of samples per chunk, sample description index).
ParsableByteArray stsc = checkNotNull(stblAtom.getLeafBoxOfType(Mp4Box.TYPE_stsc)).data; ParsableByteArray stsc = checkNotNull(stblBox.getLeafBoxOfType(Mp4Box.TYPE_stsc)).data;
// Entries are (number of samples, timestamp delta between those samples). // Entries are (number of samples, timestamp delta between those samples).
ParsableByteArray stts = checkNotNull(stblAtom.getLeafBoxOfType(Mp4Box.TYPE_stts)).data; ParsableByteArray stts = checkNotNull(stblBox.getLeafBoxOfType(Mp4Box.TYPE_stts)).data;
// Entries are the indices of samples that are synchronization samples. // Entries are the indices of samples that are synchronization samples.
@Nullable LeafBox stssAtom = stblAtom.getLeafBoxOfType(Mp4Box.TYPE_stss); @Nullable LeafBox stssAtom = stblBox.getLeafBoxOfType(Mp4Box.TYPE_stss);
@Nullable ParsableByteArray stss = stssAtom != null ? stssAtom.data : null; @Nullable ParsableByteArray stss = stssAtom != null ? stssAtom.data : null;
// Entries are (number of samples, timestamp offset). // Entries are (number of samples, timestamp offset).
@Nullable LeafBox cttsAtom = stblAtom.getLeafBoxOfType(Mp4Box.TYPE_ctts); @Nullable LeafBox cttsAtom = stblBox.getLeafBoxOfType(Mp4Box.TYPE_ctts);
@Nullable ParsableByteArray ctts = cttsAtom != null ? cttsAtom.data : null; @Nullable ParsableByteArray ctts = cttsAtom != null ? cttsAtom.data : null;
// Prepare to read chunk information. // Prepare to read chunk information.
@ -794,7 +796,7 @@ import java.util.Objects;
@Nullable @Nullable
private static Metadata parseUdtaMeta(ParsableByteArray meta, int limit) { private static Metadata parseUdtaMeta(ParsableByteArray meta, int limit) {
meta.skipBytes(Mp4Box.HEADER_SIZE); meta.skipBytes(Mp4Box.HEADER_SIZE);
maybeSkipRemainingMetaAtomHeaderBytes(meta); maybeSkipRemainingMetaBoxHeaderBytes(meta);
while (meta.getPosition() < limit) { while (meta.getPosition() < limit) {
int atomPosition = meta.getPosition(); int atomPosition = meta.getPosition();
int atomSize = meta.readInt(); int atomSize = meta.readInt();
@ -851,7 +853,7 @@ import java.util.Objects;
private static TkhdData parseTkhd(ParsableByteArray tkhd) { private static TkhdData parseTkhd(ParsableByteArray tkhd) {
tkhd.setPosition(Mp4Box.HEADER_SIZE); tkhd.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = tkhd.readInt(); int fullAtom = tkhd.readInt();
int version = parseFullAtomVersion(fullAtom); int version = parseFullBoxVersion(fullAtom);
tkhd.skipBytes(version == 0 ? 8 : 16); tkhd.skipBytes(version == 0 ? 8 : 16);
int trackId = tkhd.readInt(); int trackId = tkhd.readInt();
@ -938,7 +940,7 @@ import java.util.Objects;
private static Pair<Long, String> parseMdhd(ParsableByteArray mdhd) { private static Pair<Long, String> parseMdhd(ParsableByteArray mdhd) {
mdhd.setPosition(Mp4Box.HEADER_SIZE); mdhd.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = mdhd.readInt(); int fullAtom = mdhd.readInt();
int version = parseFullAtomVersion(fullAtom); int version = parseFullBoxVersion(fullAtom);
mdhd.skipBytes(version == 0 ? 8 : 16); mdhd.skipBytes(version == 0 ? 8 : 16);
long timescale = mdhd.readUnsignedInt(); long timescale = mdhd.readUnsignedInt();
mdhd.skipBytes(version == 0 ? 4 : 8); mdhd.skipBytes(version == 0 ? 4 : 8);
@ -1647,7 +1649,7 @@ import java.util.Objects;
ParsableByteArray elstData = elstAtom.data; ParsableByteArray elstData = elstAtom.data;
elstData.setPosition(Mp4Box.HEADER_SIZE); elstData.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = elstData.readInt(); int fullAtom = elstData.readInt();
int version = parseFullAtomVersion(fullAtom); int version = parseFullBoxVersion(fullAtom);
int entryCount = elstData.readUnsignedIntToInt(); int entryCount = elstData.readUnsignedIntToInt();
long[] editListDurations = new long[entryCount]; long[] editListDurations = new long[entryCount];
long[] editListMediaTimes = new long[entryCount]; long[] editListMediaTimes = new long[entryCount];
@ -2199,7 +2201,7 @@ import java.util.Objects;
int childAtomType = parent.readInt(); int childAtomType = parent.readInt();
if (childAtomType == Mp4Box.TYPE_tenc) { if (childAtomType == Mp4Box.TYPE_tenc) {
int fullAtom = parent.readInt(); int fullAtom = parent.readInt();
int version = parseFullAtomVersion(fullAtom); int version = parseFullBoxVersion(fullAtom);
parent.skipBytes(1); // reserved = 0. parent.skipBytes(1); // reserved = 0.
int defaultCryptByteBlock = 0; int defaultCryptByteBlock = 0;
int defaultSkipByteBlock = 0; int defaultSkipByteBlock = 0;
@ -2274,7 +2276,7 @@ import java.util.Objects;
&& editEndTime <= duration; && editEndTime <= duration;
} }
private AtomParsers() { private BoxParser() {
// Prevent instantiation. // Prevent instantiation.
} }

View File

@ -19,7 +19,7 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState; import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Util.castNonNull; import static androidx.media3.common.util.Util.castNonNull;
import static androidx.media3.common.util.Util.nullSafeArrayCopy; import static androidx.media3.common.util.Util.nullSafeArrayCopy;
import static androidx.media3.extractor.mp4.AtomParsers.parseTraks; import static androidx.media3.extractor.mp4.BoxParser.parseTraks;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
@ -778,7 +778,7 @@ public class FragmentedMp4Extractor implements Extractor {
} }
atom.setPosition(Mp4Box.HEADER_SIZE); atom.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = atom.readInt(); int fullAtom = atom.readInt();
int version = AtomParsers.parseFullAtomVersion(fullAtom); int version = BoxParser.parseFullBoxVersion(fullAtom);
String schemeIdUri; String schemeIdUri;
String value; String value;
long timescale; long timescale;
@ -884,7 +884,7 @@ public class FragmentedMp4Extractor implements Extractor {
private static long parseMehd(ParsableByteArray mehd) { private static long parseMehd(ParsableByteArray mehd) {
mehd.setPosition(Mp4Box.HEADER_SIZE); mehd.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = mehd.readInt(); int fullAtom = mehd.readInt();
int version = AtomParsers.parseFullAtomVersion(fullAtom); int version = BoxParser.parseFullBoxVersion(fullAtom);
return version == 0 ? mehd.readUnsignedInt() : mehd.readUnsignedLongToLong(); return version == 0 ? mehd.readUnsignedInt() : mehd.readUnsignedLongToLong();
} }
@ -1006,7 +1006,7 @@ public class FragmentedMp4Extractor implements Extractor {
int vectorSize = encryptionBox.perSampleIvSize; int vectorSize = encryptionBox.perSampleIvSize;
saiz.setPosition(Mp4Box.HEADER_SIZE); saiz.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = saiz.readInt(); int fullAtom = saiz.readInt();
int flags = AtomParsers.parseFullAtomFlags(fullAtom); int flags = BoxParser.parseFullBoxFlags(fullAtom);
if ((flags & 0x01) == 1) { if ((flags & 0x01) == 1) {
saiz.skipBytes(8); saiz.skipBytes(8);
} }
@ -1050,7 +1050,7 @@ public class FragmentedMp4Extractor implements Extractor {
private static void parseSaio(ParsableByteArray saio, TrackFragment out) throws ParserException { private static void parseSaio(ParsableByteArray saio, TrackFragment out) throws ParserException {
saio.setPosition(Mp4Box.HEADER_SIZE); saio.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = saio.readInt(); int fullAtom = saio.readInt();
int flags = AtomParsers.parseFullAtomFlags(fullAtom); int flags = BoxParser.parseFullBoxFlags(fullAtom);
if ((flags & 0x01) == 1) { if ((flags & 0x01) == 1) {
saio.skipBytes(8); saio.skipBytes(8);
} }
@ -1062,7 +1062,7 @@ public class FragmentedMp4Extractor implements Extractor {
"Unexpected saio entry count: " + entryCount, /* cause= */ null); "Unexpected saio entry count: " + entryCount, /* cause= */ null);
} }
int version = AtomParsers.parseFullAtomVersion(fullAtom); int version = BoxParser.parseFullBoxVersion(fullAtom);
out.auxiliaryDataPosition += out.auxiliaryDataPosition +=
version == 0 ? saio.readUnsignedInt() : saio.readUnsignedLongToLong(); version == 0 ? saio.readUnsignedInt() : saio.readUnsignedLongToLong();
} }
@ -1084,7 +1084,7 @@ public class FragmentedMp4Extractor implements Extractor {
ParsableByteArray tfhd, SparseArray<TrackBundle> trackBundles, boolean haveSideloadedTrack) { ParsableByteArray tfhd, SparseArray<TrackBundle> trackBundles, boolean haveSideloadedTrack) {
tfhd.setPosition(Mp4Box.HEADER_SIZE); tfhd.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = tfhd.readInt(); int fullAtom = tfhd.readInt();
int atomFlags = AtomParsers.parseFullAtomFlags(fullAtom); int atomFlags = BoxParser.parseFullBoxFlags(fullAtom);
int trackId = tfhd.readInt(); int trackId = tfhd.readInt();
@Nullable @Nullable
TrackBundle trackBundle = TrackBundle trackBundle =
@ -1133,7 +1133,7 @@ public class FragmentedMp4Extractor implements Extractor {
private static long parseTfdt(ParsableByteArray tfdt) { private static long parseTfdt(ParsableByteArray tfdt) {
tfdt.setPosition(Mp4Box.HEADER_SIZE); tfdt.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = tfdt.readInt(); int fullAtom = tfdt.readInt();
int version = AtomParsers.parseFullAtomVersion(fullAtom); int version = BoxParser.parseFullBoxVersion(fullAtom);
return version == 1 ? tfdt.readUnsignedLongToLong() : tfdt.readUnsignedInt(); return version == 1 ? tfdt.readUnsignedLongToLong() : tfdt.readUnsignedInt();
} }
@ -1176,7 +1176,7 @@ public class FragmentedMp4Extractor implements Extractor {
throws ParserException { throws ParserException {
trun.setPosition(Mp4Box.HEADER_SIZE); trun.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = trun.readInt(); int fullAtom = trun.readInt();
int atomFlags = AtomParsers.parseFullAtomFlags(fullAtom); int atomFlags = BoxParser.parseFullBoxFlags(fullAtom);
Track track = trackBundle.moovSampleTable.track; Track track = trackBundle.moovSampleTable.track;
TrackFragment fragment = trackBundle.fragment; TrackFragment fragment = trackBundle.fragment;
@ -1287,7 +1287,7 @@ public class FragmentedMp4Extractor implements Extractor {
throws ParserException { throws ParserException {
senc.setPosition(Mp4Box.HEADER_SIZE + offset); senc.setPosition(Mp4Box.HEADER_SIZE + offset);
int fullAtom = senc.readInt(); int fullAtom = senc.readInt();
int flags = AtomParsers.parseFullAtomFlags(fullAtom); int flags = BoxParser.parseFullBoxFlags(fullAtom);
if ((flags & 0x01 /* override_track_encryption_box_parameters */) != 0) { if ((flags & 0x01 /* override_track_encryption_box_parameters */) != 0) {
// TODO: Implement this. // TODO: Implement this.
@ -1340,7 +1340,7 @@ public class FragmentedMp4Extractor implements Extractor {
} }
sbgp.setPosition(Mp4Box.HEADER_SIZE); sbgp.setPosition(Mp4Box.HEADER_SIZE);
int sbgpVersion = AtomParsers.parseFullAtomVersion(sbgp.readInt()); int sbgpVersion = BoxParser.parseFullBoxVersion(sbgp.readInt());
sbgp.skipBytes(4); // grouping_type == seig. sbgp.skipBytes(4); // grouping_type == seig.
if (sbgpVersion == 1) { if (sbgpVersion == 1) {
sbgp.skipBytes(4); // grouping_type_parameter. sbgp.skipBytes(4); // grouping_type_parameter.
@ -1351,7 +1351,7 @@ public class FragmentedMp4Extractor implements Extractor {
} }
sgpd.setPosition(Mp4Box.HEADER_SIZE); sgpd.setPosition(Mp4Box.HEADER_SIZE);
int sgpdVersion = AtomParsers.parseFullAtomVersion(sgpd.readInt()); int sgpdVersion = BoxParser.parseFullBoxVersion(sgpd.readInt());
sgpd.skipBytes(4); // grouping_type == seig. sgpd.skipBytes(4); // grouping_type == seig.
if (sgpdVersion == 1) { if (sgpdVersion == 1) {
if (sgpd.readUnsignedInt() == 0) { if (sgpd.readUnsignedInt() == 0) {
@ -1408,7 +1408,7 @@ public class FragmentedMp4Extractor implements Extractor {
throws ParserException { throws ParserException {
atom.setPosition(Mp4Box.HEADER_SIZE); atom.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = atom.readInt(); int fullAtom = atom.readInt();
int version = AtomParsers.parseFullAtomVersion(fullAtom); int version = BoxParser.parseFullBoxVersion(fullAtom);
atom.skipBytes(4); atom.skipBytes(4);
long timescale = atom.readUnsignedInt(); long timescale = atom.readUnsignedInt();

View File

@ -372,7 +372,7 @@ import com.google.common.collect.ImmutableList;
int atomType = data.readInt(); int atomType = data.readInt();
if (atomType == Mp4Box.TYPE_data) { if (atomType == Mp4Box.TYPE_data) {
int fullVersionInt = data.readInt(); int fullVersionInt = data.readInt();
int flags = AtomParsers.parseFullAtomFlags(fullVersionInt); int flags = BoxParser.parseFullBoxFlags(fullVersionInt);
@Nullable String mimeType = flags == 13 ? "image/jpeg" : flags == 14 ? "image/png" : null; @Nullable String mimeType = flags == 13 ? "image/jpeg" : flags == 14 ? "image/png" : null;
if (mimeType == null) { if (mimeType == null) {
Log.w(TAG, "Unrecognized cover art flags: " + flags); Log.w(TAG, "Unrecognized cover art flags: " + flags);

View File

@ -28,7 +28,7 @@ import static androidx.media3.container.Mp4Util.EDITABLE_TRACK_TYPE_DEPTH_INVERS
import static androidx.media3.container.Mp4Util.EDITABLE_TRACK_TYPE_DEPTH_LINEAR; import static androidx.media3.container.Mp4Util.EDITABLE_TRACK_TYPE_DEPTH_LINEAR;
import static androidx.media3.container.Mp4Util.EDITABLE_TRACK_TYPE_DEPTH_METADATA; import static androidx.media3.container.Mp4Util.EDITABLE_TRACK_TYPE_DEPTH_METADATA;
import static androidx.media3.container.Mp4Util.EDITABLE_TRACK_TYPE_SHARP; import static androidx.media3.container.Mp4Util.EDITABLE_TRACK_TYPE_SHARP;
import static androidx.media3.extractor.mp4.AtomParsers.parseTraks; import static androidx.media3.extractor.mp4.BoxParser.parseTraks;
import static androidx.media3.extractor.mp4.MetadataUtil.findMdtaMetadataEntryWithKey; import static androidx.media3.extractor.mp4.MetadataUtil.findMdtaMetadataEntryWithKey;
import static androidx.media3.extractor.mp4.Sniffer.BRAND_HEIC; import static androidx.media3.extractor.mp4.Sniffer.BRAND_HEIC;
import static androidx.media3.extractor.mp4.Sniffer.BRAND_QUICKTIME; import static androidx.media3.extractor.mp4.Sniffer.BRAND_QUICKTIME;
@ -657,7 +657,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
List<@C.AuxiliaryTrackType Integer> auxiliaryTrackTypesForEditableVideoTracks = List<@C.AuxiliaryTrackType Integer> auxiliaryTrackTypesForEditableVideoTracks =
new ArrayList<>(); new ArrayList<>();
if (meta != null) { if (meta != null) {
mdtaMetadata = AtomParsers.parseMdtaFromMeta(meta); mdtaMetadata = BoxParser.parseMdtaFromMeta(meta);
if (readingEditableVideoTracks) { if (readingEditableVideoTracks) {
checkStateNotNull(mdtaMetadata); checkStateNotNull(mdtaMetadata);
maybeSetDefaultSampleOffsetForEditableVideoTracks(mdtaMetadata); maybeSetDefaultSampleOffsetForEditableVideoTracks(mdtaMetadata);
@ -678,13 +678,13 @@ public final class Mp4Extractor implements Extractor, SeekMap {
@Nullable Metadata udtaMetadata = null; @Nullable Metadata udtaMetadata = null;
@Nullable Mp4Box.LeafBox udta = moov.getLeafBoxOfType(Mp4Box.TYPE_udta); @Nullable Mp4Box.LeafBox udta = moov.getLeafBoxOfType(Mp4Box.TYPE_udta);
if (udta != null) { if (udta != null) {
udtaMetadata = AtomParsers.parseUdta(udta); udtaMetadata = BoxParser.parseUdta(udta);
gaplessInfoHolder.setFromMetadata(udtaMetadata); gaplessInfoHolder.setFromMetadata(udtaMetadata);
} }
Metadata mvhdMetadata = Metadata mvhdMetadata =
new Metadata( new Metadata(
AtomParsers.parseMvhd(checkNotNull(moov.getLeafBoxOfType(Mp4Box.TYPE_mvhd)).data)); BoxParser.parseMvhd(checkNotNull(moov.getLeafBoxOfType(Mp4Box.TYPE_mvhd)).data));
boolean ignoreEditLists = (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0; boolean ignoreEditLists = (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0;
List<TrackSampleTable> trackSampleTables = List<TrackSampleTable> trackSampleTables =
@ -1056,7 +1056,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
private void maybeSkipRemainingMetaAtomHeaderBytes(ExtractorInput input) throws IOException { private void maybeSkipRemainingMetaAtomHeaderBytes(ExtractorInput input) throws IOException {
scratch.reset(8); scratch.reset(8);
input.peekFully(scratch.getData(), 0, 8); input.peekFully(scratch.getData(), 0, 8);
AtomParsers.maybeSkipRemainingMetaAtomHeaderBytes(scratch); BoxParser.maybeSkipRemainingMetaBoxHeaderBytes(scratch);
input.skipFully(scratch.getPosition()); input.skipFully(scratch.getPosition());
input.resetPeekPosition(); input.resetPeekPosition();
} }

View File

@ -176,7 +176,7 @@ public final class PsshAtomUtil {
Log.w(TAG, "Atom type is not pssh: " + atomType); Log.w(TAG, "Atom type is not pssh: " + atomType);
return null; return null;
} }
int atomVersion = AtomParsers.parseFullAtomVersion(atomData.readInt()); int atomVersion = BoxParser.parseFullBoxVersion(atomData.readInt());
if (atomVersion > 1) { if (atomVersion > 1) {
Log.w(TAG, "Unsupported pssh version: " + atomVersion); Log.w(TAG, "Unsupported pssh version: " + atomVersion);
return null; return null;

View File

@ -18,6 +18,7 @@ package androidx.media3.extractor.mp4;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.container.Mp4Box; import androidx.media3.container.Mp4Box;
import androidx.media3.extractor.ExtractorInput; import androidx.media3.extractor.ExtractorInput;
import androidx.media3.extractor.SniffFailure; import androidx.media3.extractor.SniffFailure;
@ -27,7 +28,8 @@ import java.io.IOException;
* Provides methods that peek data from an {@link ExtractorInput} and return whether the input * Provides methods that peek data from an {@link ExtractorInput} and return whether the input
* appears to be in MP4 format. * appears to be in MP4 format.
*/ */
/* package */ final class Sniffer { @UnstableApi
public final class Sniffer {
/** Brand stored in the ftyp atom for QuickTime media. */ /** Brand stored in the ftyp atom for QuickTime media. */
public static final int BRAND_QUICKTIME = 0x71742020; public static final int BRAND_QUICKTIME = 0x71742020;

View File

@ -17,10 +17,12 @@ package androidx.media3.extractor.mp4;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.util.Assertions; import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
/** Sample table for a track in an MP4 file. */ /** Sample table for a track in an MP4 file. */
/* package */ final class TrackSampleTable { @UnstableApi
public final class TrackSampleTable {
/** The track corresponding to this sample table. */ /** The track corresponding to this sample table. */
public final Track track; public final Track track;

View File

@ -26,9 +26,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
/** Tests for {@link AtomParsers}. */ /** Tests for {@link BoxParser}. */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public final class AtomParsersTest { public final class BoxParserTest {
private static final String ATOM_HEADER = "000000000000000000000000"; private static final String ATOM_HEADER = "000000000000000000000000";
private static final String SAMPLE_COUNT = "00000004"; private static final String SAMPLE_COUNT = "00000004";
@ -248,7 +248,7 @@ public final class AtomParsersTest {
0, 0, 0, 0, 88, 88, 88, 88 0, 0, 0, 0, 88, 88, 88, 88
}; // version (1), flags (3), 'xxxx' (4) }; // version (1), flags (3), 'xxxx' (4)
assertThat( assertThat(
AtomParsers.parseCommonEncryptionSinfFromParent( BoxParser.parseCommonEncryptionSinfFromParent(
new ParsableByteArray(cencSinf), 0, cencSinf.length)) new ParsableByteArray(cencSinf), 0, cencSinf.length))
.isNull(); .isNull();
} }
@ -271,17 +271,17 @@ public final class AtomParsersTest {
@Test @Test
public void vexuParsings() throws ParserException { public void vexuParsings() throws ParserException {
AtomParsers.VexuData vexuData = null; BoxParser.VexuData vexuData = null;
assertThat( assertThat(
vexuData = vexuData =
AtomParsers.parseVideoExtendedUsageBox( BoxParser.parseVideoExtendedUsageBox(
new ParsableByteArray(VEXU_DATA0), 0, VEXU_DATA0.length)) new ParsableByteArray(VEXU_DATA0), 0, VEXU_DATA0.length))
.isNotNull(); .isNotNull();
assertThat(vexuData).isNotNull(); assertThat(vexuData).isNotNull();
assertThat(vexuData.hasBothEyeViews()).isTrue(); assertThat(vexuData.hasBothEyeViews()).isTrue();
assertThat( assertThat(
vexuData = vexuData =
AtomParsers.parseVideoExtendedUsageBox( BoxParser.parseVideoExtendedUsageBox(
new ParsableByteArray(VEXU_DATA1), 0, VEXU_DATA1.length)) new ParsableByteArray(VEXU_DATA1), 0, VEXU_DATA1.length))
.isNotNull(); .isNotNull();
assertThat(vexuData).isNotNull(); assertThat(vexuData).isNotNull();
@ -289,7 +289,7 @@ public final class AtomParsersTest {
} }
private static void verifyStz2Parsing(Mp4Box.LeafBox stz2Atom) { private static void verifyStz2Parsing(Mp4Box.LeafBox stz2Atom) {
AtomParsers.Stz2SampleSizeBox box = new AtomParsers.Stz2SampleSizeBox(stz2Atom); BoxParser.Stz2SampleSizeBox box = new BoxParser.Stz2SampleSizeBox(stz2Atom);
assertThat(box.getSampleCount()).isEqualTo(4); assertThat(box.getSampleCount()).isEqualTo(4);
assertThat(box.getFixedSampleSize()).isEqualTo(C.LENGTH_UNSET); assertThat(box.getFixedSampleSize()).isEqualTo(C.LENGTH_UNSET);
for (int i = 0; i < box.getSampleCount(); i++) { for (int i = 0; i < box.getSampleCount(); i++) {

View File

@ -17,8 +17,8 @@ package androidx.media3.extractor.mp4;
import static androidx.media3.common.C.WIDEVINE_UUID; import static androidx.media3.common.C.WIDEVINE_UUID;
import static androidx.media3.container.Mp4Box.TYPE_pssh; import static androidx.media3.container.Mp4Box.TYPE_pssh;
import static androidx.media3.extractor.mp4.AtomParsers.parseFullAtomFlags; import static androidx.media3.extractor.mp4.BoxParser.parseFullBoxFlags;
import static androidx.media3.extractor.mp4.AtomParsers.parseFullAtomVersion; import static androidx.media3.extractor.mp4.BoxParser.parseFullBoxVersion;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import androidx.media3.common.C; import androidx.media3.common.C;
@ -42,8 +42,8 @@ public final class PsshAtomUtilTest {
assertThat(parsablePsshAtom.readUnsignedIntToInt()).isEqualTo(psshAtom.length); // length assertThat(parsablePsshAtom.readUnsignedIntToInt()).isEqualTo(psshAtom.length); // length
assertThat(parsablePsshAtom.readInt()).isEqualTo(TYPE_pssh); // type assertThat(parsablePsshAtom.readInt()).isEqualTo(TYPE_pssh); // type
int fullAtomInt = parsablePsshAtom.readInt(); // version + flags int fullAtomInt = parsablePsshAtom.readInt(); // version + flags
assertThat(parseFullAtomVersion(fullAtomInt)).isEqualTo(0); assertThat(parseFullBoxVersion(fullAtomInt)).isEqualTo(0);
assertThat(parseFullAtomFlags(fullAtomInt)).isEqualTo(0); assertThat(parseFullBoxFlags(fullAtomInt)).isEqualTo(0);
UUID systemId = new UUID(parsablePsshAtom.readLong(), parsablePsshAtom.readLong()); UUID systemId = new UUID(parsablePsshAtom.readLong(), parsablePsshAtom.readLong());
assertThat(systemId).isEqualTo(WIDEVINE_UUID); assertThat(systemId).isEqualTo(WIDEVINE_UUID);
assertThat(parsablePsshAtom.readUnsignedIntToInt()).isEqualTo(schemeData.length); assertThat(parsablePsshAtom.readUnsignedIntToInt()).isEqualTo(schemeData.length);
@ -65,8 +65,8 @@ public final class PsshAtomUtilTest {
assertThat(parsablePsshAtom.readUnsignedIntToInt()).isEqualTo(psshAtom.length); assertThat(parsablePsshAtom.readUnsignedIntToInt()).isEqualTo(psshAtom.length);
assertThat(parsablePsshAtom.readInt()).isEqualTo(TYPE_pssh); // type assertThat(parsablePsshAtom.readInt()).isEqualTo(TYPE_pssh); // type
int fullAtomInt = parsablePsshAtom.readInt(); // version + flags int fullAtomInt = parsablePsshAtom.readInt(); // version + flags
assertThat(parseFullAtomVersion(fullAtomInt)).isEqualTo(1); assertThat(parseFullBoxVersion(fullAtomInt)).isEqualTo(1);
assertThat(parseFullAtomFlags(fullAtomInt)).isEqualTo(0); assertThat(parseFullBoxFlags(fullAtomInt)).isEqualTo(0);
UUID systemId = new UUID(parsablePsshAtom.readLong(), parsablePsshAtom.readLong()); UUID systemId = new UUID(parsablePsshAtom.readLong(), parsablePsshAtom.readLong());
assertThat(systemId).isEqualTo(WIDEVINE_UUID); assertThat(systemId).isEqualTo(WIDEVINE_UUID);
assertThat(parsablePsshAtom.readUnsignedIntToInt()).isEqualTo(2); assertThat(parsablePsshAtom.readUnsignedIntToInt()).isEqualTo(2);