Rename Atom to Mp4Box, and move it to container module

This makes the class available to custom MP4-parsing implementations,
while also allowing it to be used by `muxer` in future.

'Box' is the term used throughout the ISO 14496-12 spec, while the
'Atom' nomenclature was used in an earlier form of the spec
(Quicktime).

This change moves it from `extractor.mp4.Atom` to `container.Mp4Box`,
to be consistent with existing MP4-specific types in the `container`
module like `Mp4TimestampData`.

PiperOrigin-RevId: 663274752
This commit is contained in:
ibaker 2024-08-15 05:41:37 -07:00 committed by Copybara-Service
parent 4df5ecb045
commit ee27334f06
10 changed files with 472 additions and 460 deletions

View File

@ -13,30 +13,33 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package androidx.media3.extractor.mp4; package androidx.media3.container;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
/** A representation of an MP4 box (aka atom). */
@SuppressWarnings("ConstantField") @SuppressWarnings("ConstantField")
/* package */ abstract class Atom { @UnstableApi
public abstract class Mp4Box {
/** Size of an atom header, in bytes. */ /** Size of a box header, in bytes. */
public static final int HEADER_SIZE = 8; public static final int HEADER_SIZE = 8;
/** Size of a full atom header, in bytes. */ /** Size of a full box header, in bytes. */
public static final int FULL_HEADER_SIZE = 12; public static final int FULL_HEADER_SIZE = 12;
/** Size of a long atom header, in bytes. */ /** Size of a long box header, in bytes. */
public static final int LONG_HEADER_SIZE = 16; public static final int LONG_HEADER_SIZE = 16;
/** Value for the size field in an atom that defines its size in the largesize field. */ /** Value for the size field in a box that defines its size in the largesize field. */
public static final int DEFINES_LARGE_SIZE = 1; public static final int DEFINES_LARGE_SIZE = 1;
/** Value for the size field in an atom that extends to the end of the file. */ /** Value for the size field in a box that extends to the end of the file. */
public static final int EXTENDS_TO_END_SIZE = 0; public static final int EXTENDS_TO_END_SIZE = 0;
@SuppressWarnings("ConstantCaseForConstants") @SuppressWarnings("ConstantCaseForConstants")
@ -451,43 +454,44 @@ import java.util.List;
public final int type; public final int type;
public Atom(int type) { // private to only allow sub-classing from within this file.
private Mp4Box(int type) {
this.type = type; this.type = type;
} }
@Override @Override
public String toString() { public String toString() {
return getAtomTypeString(type); return getBoxTypeString(type);
} }
/** An MP4 atom that is a leaf. */ /** An MP4 box that is a leaf. */
/* package */ static final class LeafAtom extends Atom { public static final class LeafBox extends Mp4Box {
/** The atom data. */ /** The box data. */
public final ParsableByteArray data; public final ParsableByteArray data;
/** /**
* @param type The type of the atom. * @param type The type of the box.
* @param data The atom data. * @param data The box data.
*/ */
public LeafAtom(int type, ParsableByteArray data) { public LeafBox(int type, ParsableByteArray data) {
super(type); super(type);
this.data = data; this.data = data;
} }
} }
/** An MP4 atom that has child atoms. */ /** An MP4 box that has child boxes. */
/* package */ static final class ContainerAtom extends Atom { public static final class ContainerBox extends Mp4Box {
public final long endPosition; public final long endPosition;
public final List<LeafAtom> leafChildren; public final List<LeafBox> leafChildren;
public final List<ContainerAtom> containerChildren; public final List<ContainerBox> containerChildren;
/** /**
* @param type The type of the atom. * @param type The type of the box.
* @param endPosition The position of the first byte after the end of the atom. * @param endPosition The position of the first byte after the end of the box.
*/ */
public ContainerAtom(int type, long endPosition) { public ContainerBox(int type, long endPosition) {
super(type); super(type);
this.endPosition = endPosition; this.endPosition = endPosition;
leafChildren = new ArrayList<>(); leafChildren = new ArrayList<>();
@ -497,19 +501,19 @@ import java.util.List;
/** /**
* Adds a child leaf to this container. * Adds a child leaf to this container.
* *
* @param atom The child to add. * @param box The child to add.
*/ */
public void add(LeafAtom atom) { public void add(LeafBox box) {
leafChildren.add(atom); leafChildren.add(box);
} }
/** /**
* Adds a child container to this container. * Adds a child container to this container.
* *
* @param atom The child to add. * @param box The child to add.
*/ */
public void add(ContainerAtom atom) { public void add(ContainerBox box) {
containerChildren.add(atom); containerChildren.add(box);
} }
/** /**
@ -522,12 +526,12 @@ import java.util.List;
* @return The child leaf of the given type, or null if no such child exists. * @return The child leaf of the given type, or null if no such child exists.
*/ */
@Nullable @Nullable
public LeafAtom getLeafAtomOfType(int type) { public LeafBox getLeafBoxOfType(int type) {
int childrenSize = leafChildren.size(); int childrenSize = leafChildren.size();
for (int i = 0; i < childrenSize; i++) { for (int i = 0; i < childrenSize; i++) {
LeafAtom atom = leafChildren.get(i); LeafBox box = leafChildren.get(i);
if (atom.type == type) { if (box.type == type) {
return atom; return box;
} }
} }
return null; return null;
@ -543,12 +547,12 @@ import java.util.List;
* @return The child container of the given type, or null if no such child exists. * @return The child container of the given type, or null if no such child exists.
*/ */
@Nullable @Nullable
public ContainerAtom getContainerAtomOfType(int type) { public ContainerBox getContainerBoxOfType(int type) {
int childrenSize = containerChildren.size(); int childrenSize = containerChildren.size();
for (int i = 0; i < childrenSize; i++) { for (int i = 0; i < childrenSize; i++) {
ContainerAtom atom = containerChildren.get(i); ContainerBox box = containerChildren.get(i);
if (atom.type == type) { if (box.type == type) {
return atom; return box;
} }
} }
return null; return null;
@ -556,7 +560,7 @@ import java.util.List;
@Override @Override
public String toString() { public String toString() {
return getAtomTypeString(type) return getBoxTypeString(type)
+ " leaves: " + " leaves: "
+ Arrays.toString(leafChildren.toArray()) + Arrays.toString(leafChildren.toArray())
+ " containers: " + " containers: "
@ -565,12 +569,12 @@ import java.util.List;
} }
/** /**
* Converts a numeric atom type to the corresponding four character string. * Converts a numeric box type to the corresponding four character string.
* *
* @param type The numeric atom type. * @param type The numeric box type.
* @return The corresponding four character string. * @return The corresponding four character string.
*/ */
public static String getAtomTypeString(int type) { public static String getBoxTypeString(int type) {
return "" return ""
+ (char) ((type >> 24) & 0xFF) + (char) ((type >> 24) & 0xFF)
+ (char) ((type >> 16) & 0xFF) + (char) ((type >> 16) & 0xFF)

View File

@ -38,6 +38,9 @@ import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.TimestampAdjuster; import androidx.media3.common.util.TimestampAdjuster;
import androidx.media3.common.util.UnstableApi; 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.ContainerBox;
import androidx.media3.container.Mp4Box.LeafBox;
import androidx.media3.container.NalUnitUtil; import androidx.media3.container.NalUnitUtil;
import androidx.media3.container.ReorderingSeiMessageQueue; import androidx.media3.container.ReorderingSeiMessageQueue;
import androidx.media3.extractor.Ac4Util; import androidx.media3.extractor.Ac4Util;
@ -54,8 +57,6 @@ import androidx.media3.extractor.SniffFailure;
import androidx.media3.extractor.TrackOutput; import androidx.media3.extractor.TrackOutput;
import androidx.media3.extractor.metadata.emsg.EventMessage; import androidx.media3.extractor.metadata.emsg.EventMessage;
import androidx.media3.extractor.metadata.emsg.EventMessageEncoder; import androidx.media3.extractor.metadata.emsg.EventMessageEncoder;
import androidx.media3.extractor.mp4.Atom.ContainerAtom;
import androidx.media3.extractor.mp4.Atom.LeafAtom;
import androidx.media3.extractor.text.SubtitleParser; import androidx.media3.extractor.text.SubtitleParser;
import androidx.media3.extractor.text.SubtitleTranscodingExtractorOutput; import androidx.media3.extractor.text.SubtitleTranscodingExtractorOutput;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -208,7 +209,7 @@ public class FragmentedMp4Extractor implements Extractor {
// Parser state. // Parser state.
private final ParsableByteArray atomHeader; private final ParsableByteArray atomHeader;
private final ArrayDeque<ContainerAtom> containerAtoms; private final ArrayDeque<ContainerBox> containerAtoms;
private final ArrayDeque<MetadataSampleInfo> pendingMetadataSampleInfos; private final ArrayDeque<MetadataSampleInfo> pendingMetadataSampleInfos;
private final ReorderingSeiMessageQueue reorderingSeiMessageQueue; private final ReorderingSeiMessageQueue reorderingSeiMessageQueue;
@Nullable private final TrackOutput additionalEmsgTrackOutput; @Nullable private final TrackOutput additionalEmsgTrackOutput;
@ -402,7 +403,7 @@ public class FragmentedMp4Extractor implements Extractor {
this.closedCaptionFormats = Collections.unmodifiableList(closedCaptionFormats); this.closedCaptionFormats = Collections.unmodifiableList(closedCaptionFormats);
this.additionalEmsgTrackOutput = additionalEmsgTrackOutput; this.additionalEmsgTrackOutput = additionalEmsgTrackOutput;
eventMessageEncoder = new EventMessageEncoder(); eventMessageEncoder = new EventMessageEncoder();
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); atomHeader = new ParsableByteArray(Mp4Box.LONG_HEADER_SIZE);
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
nalPrefix = new ParsableByteArray(5); nalPrefix = new ParsableByteArray(5);
nalBuffer = new ParsableByteArray(); nalBuffer = new ParsableByteArray();
@ -517,22 +518,22 @@ public class FragmentedMp4Extractor implements Extractor {
private boolean readAtomHeader(ExtractorInput input) throws IOException { private boolean readAtomHeader(ExtractorInput input) throws IOException {
if (atomHeaderBytesRead == 0) { if (atomHeaderBytesRead == 0) {
// Read the standard length atom header. // Read the standard length atom header.
if (!input.readFully(atomHeader.getData(), 0, Atom.HEADER_SIZE, true)) { if (!input.readFully(atomHeader.getData(), 0, Mp4Box.HEADER_SIZE, true)) {
return false; return false;
} }
atomHeaderBytesRead = Atom.HEADER_SIZE; atomHeaderBytesRead = Mp4Box.HEADER_SIZE;
atomHeader.setPosition(0); atomHeader.setPosition(0);
atomSize = atomHeader.readUnsignedInt(); atomSize = atomHeader.readUnsignedInt();
atomType = atomHeader.readInt(); atomType = atomHeader.readInt();
} }
if (atomSize == Atom.DEFINES_LARGE_SIZE) { if (atomSize == Mp4Box.DEFINES_LARGE_SIZE) {
// Read the large size. // Read the large size.
int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE; int headerBytesRemaining = Mp4Box.LONG_HEADER_SIZE - Mp4Box.HEADER_SIZE;
input.readFully(atomHeader.getData(), Atom.HEADER_SIZE, headerBytesRemaining); input.readFully(atomHeader.getData(), Mp4Box.HEADER_SIZE, headerBytesRemaining);
atomHeaderBytesRead += headerBytesRemaining; atomHeaderBytesRead += headerBytesRemaining;
atomSize = atomHeader.readUnsignedLongToLong(); atomSize = atomHeader.readUnsignedLongToLong();
} else if (atomSize == Atom.EXTENDS_TO_END_SIZE) { } else if (atomSize == Mp4Box.EXTENDS_TO_END_SIZE) {
// The atom extends to the end of the file. Note that if the atom is within a container we can // The atom extends to the end of the file. Note that if the atom is within a container we can
// work out its size even if the input length is unknown. // work out its size even if the input length is unknown.
long endPosition = input.getLength(); long endPosition = input.getLength();
@ -550,7 +551,7 @@ public class FragmentedMp4Extractor implements Extractor {
} }
long atomPosition = input.getPosition() - atomHeaderBytesRead; long atomPosition = input.getPosition() - atomHeaderBytesRead;
if (atomType == Atom.TYPE_moof || atomType == Atom.TYPE_mdat) { if (atomType == Mp4Box.TYPE_moof || atomType == Mp4Box.TYPE_mdat) {
if (!haveOutputSeekMap) { if (!haveOutputSeekMap) {
// This must be the first moof or mdat in the stream. // This must be the first moof or mdat in the stream.
extractorOutput.seekMap(new SeekMap.Unseekable(durationUs, atomPosition)); extractorOutput.seekMap(new SeekMap.Unseekable(durationUs, atomPosition));
@ -558,7 +559,7 @@ public class FragmentedMp4Extractor implements Extractor {
} }
} }
if (atomType == Atom.TYPE_moof) { if (atomType == Mp4Box.TYPE_moof) {
// The data positions may be updated when parsing the tfhd/trun. // The data positions may be updated when parsing the tfhd/trun.
int trackCount = trackBundles.size(); int trackCount = trackBundles.size();
for (int i = 0; i < trackCount; i++) { for (int i = 0; i < trackCount; i++) {
@ -569,7 +570,7 @@ public class FragmentedMp4Extractor implements Extractor {
} }
} }
if (atomType == Atom.TYPE_mdat) { if (atomType == Mp4Box.TYPE_mdat) {
currentTrackBundle = null; currentTrackBundle = null;
endOfMdatPosition = atomPosition + atomSize; endOfMdatPosition = atomPosition + atomSize;
parserState = STATE_READING_ENCRYPTION_DATA; parserState = STATE_READING_ENCRYPTION_DATA;
@ -577,8 +578,8 @@ public class FragmentedMp4Extractor implements Extractor {
} }
if (shouldParseContainerAtom(atomType)) { if (shouldParseContainerAtom(atomType)) {
long endPosition = input.getPosition() + atomSize - Atom.HEADER_SIZE; long endPosition = input.getPosition() + atomSize - Mp4Box.HEADER_SIZE;
containerAtoms.push(new ContainerAtom(atomType, endPosition)); containerAtoms.push(new ContainerBox(atomType, endPosition));
if (atomSize == atomHeaderBytesRead) { if (atomSize == atomHeaderBytesRead) {
processAtomEnded(endPosition); processAtomEnded(endPosition);
} else { } else {
@ -586,7 +587,7 @@ public class FragmentedMp4Extractor implements Extractor {
enterReadingAtomHeaderState(); enterReadingAtomHeaderState();
} }
} else if (shouldParseLeafAtom(atomType)) { } else if (shouldParseLeafAtom(atomType)) {
if (atomHeaderBytesRead != Atom.HEADER_SIZE) { if (atomHeaderBytesRead != Mp4Box.HEADER_SIZE) {
throw ParserException.createForUnsupportedContainerFeature( throw ParserException.createForUnsupportedContainerFeature(
"Leaf atom defines extended atom size (unsupported)."); "Leaf atom defines extended atom size (unsupported).");
} }
@ -595,7 +596,7 @@ public class FragmentedMp4Extractor implements Extractor {
"Leaf atom with length > 2147483647 (unsupported)."); "Leaf atom with length > 2147483647 (unsupported).");
} }
ParsableByteArray atomData = new ParsableByteArray((int) atomSize); ParsableByteArray atomData = new ParsableByteArray((int) atomSize);
System.arraycopy(atomHeader.getData(), 0, atomData.getData(), 0, Atom.HEADER_SIZE); System.arraycopy(atomHeader.getData(), 0, atomData.getData(), 0, Mp4Box.HEADER_SIZE);
this.atomData = atomData; this.atomData = atomData;
parserState = STATE_READING_ATOM_PAYLOAD; parserState = STATE_READING_ATOM_PAYLOAD;
} else { } else {
@ -614,8 +615,8 @@ public class FragmentedMp4Extractor implements Extractor {
int atomPayloadSize = (int) atomSize - atomHeaderBytesRead; int atomPayloadSize = (int) atomSize - atomHeaderBytesRead;
@Nullable ParsableByteArray atomData = this.atomData; @Nullable ParsableByteArray atomData = this.atomData;
if (atomData != null) { if (atomData != null) {
input.readFully(atomData.getData(), Atom.HEADER_SIZE, atomPayloadSize); input.readFully(atomData.getData(), Mp4Box.HEADER_SIZE, atomPayloadSize);
onLeafAtomRead(new LeafAtom(atomType, atomData), input.getPosition()); onLeafAtomRead(new LeafBox(atomType, atomData), input.getPosition());
} else { } else {
input.skipFully(atomPayloadSize); input.skipFully(atomPayloadSize);
} }
@ -629,45 +630,45 @@ public class FragmentedMp4Extractor implements Extractor {
enterReadingAtomHeaderState(); enterReadingAtomHeaderState();
} }
private void onLeafAtomRead(LeafAtom leaf, long inputPosition) throws ParserException { private void onLeafAtomRead(LeafBox leaf, long inputPosition) throws ParserException {
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 == Mp4Box.TYPE_sidx) {
Pair<Long, ChunkIndex> result = parseSidx(leaf.data, inputPosition); Pair<Long, ChunkIndex> result = parseSidx(leaf.data, inputPosition);
segmentIndexEarliestPresentationTimeUs = result.first; segmentIndexEarliestPresentationTimeUs = result.first;
extractorOutput.seekMap(result.second); extractorOutput.seekMap(result.second);
haveOutputSeekMap = true; haveOutputSeekMap = true;
} else if (leaf.type == Atom.TYPE_emsg) { } else if (leaf.type == Mp4Box.TYPE_emsg) {
onEmsgLeafAtomRead(leaf.data); onEmsgLeafAtomRead(leaf.data);
} }
} }
private void onContainerAtomRead(ContainerAtom container) throws ParserException { private void onContainerAtomRead(ContainerBox container) throws ParserException {
if (container.type == Atom.TYPE_moov) { if (container.type == Mp4Box.TYPE_moov) {
onMoovContainerAtomRead(container); onMoovContainerAtomRead(container);
} else if (container.type == Atom.TYPE_moof) { } else if (container.type == Mp4Box.TYPE_moof) {
onMoofContainerAtomRead(container); onMoofContainerAtomRead(container);
} else if (!containerAtoms.isEmpty()) { } else if (!containerAtoms.isEmpty()) {
containerAtoms.peek().add(container); containerAtoms.peek().add(container);
} }
} }
private void onMoovContainerAtomRead(ContainerAtom moov) throws ParserException { private void onMoovContainerAtomRead(ContainerBox moov) throws ParserException {
checkState(sideloadedTrack == null, "Unexpected moov box."); checkState(sideloadedTrack == null, "Unexpected moov box.");
@Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moov.leafChildren); @Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moov.leafChildren);
// Read declaration of track fragments in the moov box. // Read declaration of track fragments in the moov box.
ContainerAtom mvex = checkNotNull(moov.getContainerAtomOfType(Atom.TYPE_mvex)); ContainerBox mvex = checkNotNull(moov.getContainerBoxOfType(Mp4Box.TYPE_mvex));
SparseArray<DefaultSampleValues> defaultSampleValuesArray = new SparseArray<>(); SparseArray<DefaultSampleValues> defaultSampleValuesArray = new SparseArray<>();
long duration = C.TIME_UNSET; long duration = C.TIME_UNSET;
int mvexChildrenSize = mvex.leafChildren.size(); int mvexChildrenSize = mvex.leafChildren.size();
for (int i = 0; i < mvexChildrenSize; i++) { for (int i = 0; i < mvexChildrenSize; i++) {
Atom.LeafAtom atom = mvex.leafChildren.get(i); LeafBox atom = mvex.leafChildren.get(i);
if (atom.type == Atom.TYPE_trex) { if (atom.type == Mp4Box.TYPE_trex) {
Pair<Integer, DefaultSampleValues> trexData = parseTrex(atom.data); Pair<Integer, DefaultSampleValues> trexData = parseTrex(atom.data);
defaultSampleValuesArray.put(trexData.first, trexData.second); defaultSampleValuesArray.put(trexData.first, trexData.second);
} else if (atom.type == Atom.TYPE_mehd) { } else if (atom.type == Mp4Box.TYPE_mehd) {
duration = parseMehd(atom.data); duration = parseMehd(atom.data);
} }
} }
@ -725,7 +726,7 @@ public class FragmentedMp4Extractor implements Extractor {
return checkNotNull(defaultSampleValuesArray.get(trackId)); return checkNotNull(defaultSampleValuesArray.get(trackId));
} }
private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException { private void onMoofContainerAtomRead(ContainerBox moof) throws ParserException {
parseMoof(moof, trackBundles, sideloadedTrack != null, flags, scratchBytes); parseMoof(moof, trackBundles, sideloadedTrack != null, flags, scratchBytes);
@Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moof.leafChildren); @Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moof.leafChildren);
@ -775,7 +776,7 @@ public class FragmentedMp4Extractor implements Extractor {
if (emsgTrackOutputs.length == 0) { if (emsgTrackOutputs.length == 0) {
return; return;
} }
atom.setPosition(Atom.HEADER_SIZE); atom.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = atom.readInt(); int fullAtom = atom.readInt();
int version = AtomParsers.parseFullAtomVersion(fullAtom); int version = AtomParsers.parseFullAtomVersion(fullAtom);
String schemeIdUri; String schemeIdUri;
@ -863,7 +864,7 @@ public class FragmentedMp4Extractor implements Extractor {
/** Parses a trex atom (defined in 14496-12). */ /** Parses a trex atom (defined in 14496-12). */
private static Pair<Integer, DefaultSampleValues> parseTrex(ParsableByteArray trex) { private static Pair<Integer, DefaultSampleValues> parseTrex(ParsableByteArray trex) {
trex.setPosition(Atom.FULL_HEADER_SIZE); trex.setPosition(Mp4Box.FULL_HEADER_SIZE);
int trackId = trex.readInt(); int trackId = trex.readInt();
int defaultSampleDescriptionIndex = trex.readInt() - 1; int defaultSampleDescriptionIndex = trex.readInt() - 1;
int defaultSampleDuration = trex.readInt(); int defaultSampleDuration = trex.readInt();
@ -881,14 +882,14 @@ public class FragmentedMp4Extractor implements Extractor {
/** Parses an mehd atom (defined in 14496-12). */ /** Parses an mehd atom (defined in 14496-12). */
private static long parseMehd(ParsableByteArray mehd) { private static long parseMehd(ParsableByteArray mehd) {
mehd.setPosition(Atom.HEADER_SIZE); mehd.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = mehd.readInt(); int fullAtom = mehd.readInt();
int version = AtomParsers.parseFullAtomVersion(fullAtom); int version = AtomParsers.parseFullAtomVersion(fullAtom);
return version == 0 ? mehd.readUnsignedInt() : mehd.readUnsignedLongToLong(); return version == 0 ? mehd.readUnsignedInt() : mehd.readUnsignedLongToLong();
} }
private static void parseMoof( private static void parseMoof(
ContainerAtom moof, ContainerBox moof,
SparseArray<TrackBundle> trackBundles, SparseArray<TrackBundle> trackBundles,
boolean haveSideloadedTrack, boolean haveSideloadedTrack,
@Flags int flags, @Flags int flags,
@ -896,9 +897,9 @@ public class FragmentedMp4Extractor implements Extractor {
throws ParserException { throws ParserException {
int moofContainerChildrenSize = moof.containerChildren.size(); int moofContainerChildrenSize = moof.containerChildren.size();
for (int i = 0; i < moofContainerChildrenSize; i++) { for (int i = 0; i < moofContainerChildrenSize; i++) {
Atom.ContainerAtom child = moof.containerChildren.get(i); ContainerBox child = moof.containerChildren.get(i);
// TODO: Support multiple traf boxes per track in a single moof. // TODO: Support multiple traf boxes per track in a single moof.
if (child.type == Atom.TYPE_traf) { if (child.type == Mp4Box.TYPE_traf) {
parseTraf(child, trackBundles, haveSideloadedTrack, flags, extendedTypeScratch); parseTraf(child, trackBundles, haveSideloadedTrack, flags, extendedTypeScratch);
} }
} }
@ -906,13 +907,13 @@ public class FragmentedMp4Extractor implements Extractor {
/** Parses a traf atom (defined in 14496-12). */ /** Parses a traf atom (defined in 14496-12). */
private static void parseTraf( private static void parseTraf(
ContainerAtom traf, ContainerBox traf,
SparseArray<TrackBundle> trackBundles, SparseArray<TrackBundle> trackBundles,
boolean haveSideloadedTrack, boolean haveSideloadedTrack,
@Flags int flags, @Flags int flags,
byte[] extendedTypeScratch) byte[] extendedTypeScratch)
throws ParserException { throws ParserException {
LeafAtom tfhd = checkNotNull(traf.getLeafAtomOfType(Atom.TYPE_tfhd)); LeafBox tfhd = checkNotNull(traf.getLeafBoxOfType(Mp4Box.TYPE_tfhd));
@Nullable TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundles, haveSideloadedTrack); @Nullable TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundles, haveSideloadedTrack);
if (trackBundle == null) { if (trackBundle == null) {
return; return;
@ -923,7 +924,7 @@ public class FragmentedMp4Extractor implements Extractor {
boolean fragmentDecodeTimeIncludesMoov = fragment.nextFragmentDecodeTimeIncludesMoov; boolean fragmentDecodeTimeIncludesMoov = fragment.nextFragmentDecodeTimeIncludesMoov;
trackBundle.resetFragmentInfo(); trackBundle.resetFragmentInfo();
trackBundle.currentlyInFragment = true; trackBundle.currentlyInFragment = true;
@Nullable LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt); @Nullable LeafBox tfdtAtom = traf.getLeafBoxOfType(Mp4Box.TYPE_tfdt);
if (tfdtAtom != null && (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) == 0) { if (tfdtAtom != null && (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) == 0) {
fragment.nextFragmentDecodeTime = parseTfdt(tfdtAtom.data); fragment.nextFragmentDecodeTime = parseTfdt(tfdtAtom.data);
fragment.nextFragmentDecodeTimeIncludesMoov = true; fragment.nextFragmentDecodeTimeIncludesMoov = true;
@ -939,17 +940,17 @@ public class FragmentedMp4Extractor implements Extractor {
trackBundle.moovSampleTable.track.getSampleDescriptionEncryptionBox( trackBundle.moovSampleTable.track.getSampleDescriptionEncryptionBox(
checkNotNull(fragment.header).sampleDescriptionIndex); checkNotNull(fragment.header).sampleDescriptionIndex);
@Nullable LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz); @Nullable LeafBox saiz = traf.getLeafBoxOfType(Mp4Box.TYPE_saiz);
if (saiz != null) { if (saiz != null) {
parseSaiz(checkNotNull(encryptionBox), saiz.data, fragment); parseSaiz(checkNotNull(encryptionBox), saiz.data, fragment);
} }
@Nullable LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio); @Nullable LeafBox saio = traf.getLeafBoxOfType(Mp4Box.TYPE_saio);
if (saio != null) { if (saio != null) {
parseSaio(saio.data, fragment); parseSaio(saio.data, fragment);
} }
@Nullable LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc); @Nullable LeafBox senc = traf.getLeafBoxOfType(Mp4Box.TYPE_senc);
if (senc != null) { if (senc != null) {
parseSenc(senc.data, fragment); parseSenc(senc.data, fragment);
} }
@ -958,24 +959,24 @@ public class FragmentedMp4Extractor implements Extractor {
int leafChildrenSize = traf.leafChildren.size(); int leafChildrenSize = traf.leafChildren.size();
for (int i = 0; i < leafChildrenSize; i++) { for (int i = 0; i < leafChildrenSize; i++) {
LeafAtom atom = traf.leafChildren.get(i); LeafBox atom = traf.leafChildren.get(i);
if (atom.type == Atom.TYPE_uuid) { if (atom.type == Mp4Box.TYPE_uuid) {
parseUuid(atom.data, fragment, extendedTypeScratch); parseUuid(atom.data, fragment, extendedTypeScratch);
} }
} }
} }
private static void parseTruns(ContainerAtom traf, TrackBundle trackBundle, @Flags int flags) private static void parseTruns(ContainerBox traf, TrackBundle trackBundle, @Flags int flags)
throws ParserException { throws ParserException {
int trunCount = 0; int trunCount = 0;
int totalSampleCount = 0; int totalSampleCount = 0;
List<LeafAtom> leafChildren = traf.leafChildren; List<LeafBox> leafChildren = traf.leafChildren;
int leafChildrenSize = leafChildren.size(); int leafChildrenSize = leafChildren.size();
for (int i = 0; i < leafChildrenSize; i++) { for (int i = 0; i < leafChildrenSize; i++) {
LeafAtom atom = leafChildren.get(i); LeafBox atom = leafChildren.get(i);
if (atom.type == Atom.TYPE_trun) { if (atom.type == Mp4Box.TYPE_trun) {
ParsableByteArray trunData = atom.data; ParsableByteArray trunData = atom.data;
trunData.setPosition(Atom.FULL_HEADER_SIZE); trunData.setPosition(Mp4Box.FULL_HEADER_SIZE);
int trunSampleCount = trunData.readUnsignedIntToInt(); int trunSampleCount = trunData.readUnsignedIntToInt();
if (trunSampleCount > 0) { if (trunSampleCount > 0) {
totalSampleCount += trunSampleCount; totalSampleCount += trunSampleCount;
@ -991,8 +992,8 @@ public class FragmentedMp4Extractor implements Extractor {
int trunIndex = 0; int trunIndex = 0;
int trunStartPosition = 0; int trunStartPosition = 0;
for (int i = 0; i < leafChildrenSize; i++) { for (int i = 0; i < leafChildrenSize; i++) {
LeafAtom trun = leafChildren.get(i); LeafBox trun = leafChildren.get(i);
if (trun.type == Atom.TYPE_trun) { if (trun.type == Mp4Box.TYPE_trun) {
trunStartPosition = trunStartPosition =
parseTrun(trackBundle, trunIndex++, flags, trun.data, trunStartPosition); parseTrun(trackBundle, trunIndex++, flags, trun.data, trunStartPosition);
} }
@ -1003,7 +1004,7 @@ public class FragmentedMp4Extractor implements Extractor {
TrackEncryptionBox encryptionBox, ParsableByteArray saiz, TrackFragment out) TrackEncryptionBox encryptionBox, ParsableByteArray saiz, TrackFragment out)
throws ParserException { throws ParserException {
int vectorSize = encryptionBox.perSampleIvSize; int vectorSize = encryptionBox.perSampleIvSize;
saiz.setPosition(Atom.HEADER_SIZE); saiz.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = saiz.readInt(); int fullAtom = saiz.readInt();
int flags = AtomParsers.parseFullAtomFlags(fullAtom); int flags = AtomParsers.parseFullAtomFlags(fullAtom);
if ((flags & 0x01) == 1) { if ((flags & 0x01) == 1) {
@ -1047,7 +1048,7 @@ public class FragmentedMp4Extractor implements Extractor {
* @param out The {@link TrackFragment} to populate with data from the saio atom. * @param out The {@link TrackFragment} to populate with data from the saio atom.
*/ */
private static void parseSaio(ParsableByteArray saio, TrackFragment out) throws ParserException { private static void parseSaio(ParsableByteArray saio, TrackFragment out) throws ParserException {
saio.setPosition(Atom.HEADER_SIZE); saio.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = saio.readInt(); int fullAtom = saio.readInt();
int flags = AtomParsers.parseFullAtomFlags(fullAtom); int flags = AtomParsers.parseFullAtomFlags(fullAtom);
if ((flags & 0x01) == 1) { if ((flags & 0x01) == 1) {
@ -1081,7 +1082,7 @@ public class FragmentedMp4Extractor implements Extractor {
@Nullable @Nullable
private static TrackBundle parseTfhd( private static TrackBundle parseTfhd(
ParsableByteArray tfhd, SparseArray<TrackBundle> trackBundles, boolean haveSideloadedTrack) { ParsableByteArray tfhd, SparseArray<TrackBundle> trackBundles, boolean haveSideloadedTrack) {
tfhd.setPosition(Atom.HEADER_SIZE); tfhd.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = tfhd.readInt(); int fullAtom = tfhd.readInt();
int atomFlags = AtomParsers.parseFullAtomFlags(fullAtom); int atomFlags = AtomParsers.parseFullAtomFlags(fullAtom);
int trackId = tfhd.readInt(); int trackId = tfhd.readInt();
@ -1130,7 +1131,7 @@ public class FragmentedMp4Extractor implements Extractor {
* media, expressed in the media's timescale. * media, expressed in the media's timescale.
*/ */
private static long parseTfdt(ParsableByteArray tfdt) { private static long parseTfdt(ParsableByteArray tfdt) {
tfdt.setPosition(Atom.HEADER_SIZE); tfdt.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = tfdt.readInt(); int fullAtom = tfdt.readInt();
int version = AtomParsers.parseFullAtomVersion(fullAtom); int version = AtomParsers.parseFullAtomVersion(fullAtom);
return version == 1 ? tfdt.readUnsignedLongToLong() : tfdt.readUnsignedInt(); return version == 1 ? tfdt.readUnsignedLongToLong() : tfdt.readUnsignedInt();
@ -1173,7 +1174,7 @@ public class FragmentedMp4Extractor implements Extractor {
ParsableByteArray trun, ParsableByteArray trun,
int trackRunStart) int trackRunStart)
throws ParserException { throws ParserException {
trun.setPosition(Atom.HEADER_SIZE); trun.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = trun.readInt(); int fullAtom = trun.readInt();
int atomFlags = AtomParsers.parseFullAtomFlags(fullAtom); int atomFlags = AtomParsers.parseFullAtomFlags(fullAtom);
@ -1264,7 +1265,7 @@ public class FragmentedMp4Extractor implements Extractor {
private static void parseUuid( private static void parseUuid(
ParsableByteArray uuid, TrackFragment out, byte[] extendedTypeScratch) ParsableByteArray uuid, TrackFragment out, byte[] extendedTypeScratch)
throws ParserException { throws ParserException {
uuid.setPosition(Atom.HEADER_SIZE); uuid.setPosition(Mp4Box.HEADER_SIZE);
uuid.readBytes(extendedTypeScratch, 0, 16); uuid.readBytes(extendedTypeScratch, 0, 16);
// Currently this parser only supports Microsoft's PIFF SampleEncryptionBox. // Currently this parser only supports Microsoft's PIFF SampleEncryptionBox.
@ -1284,7 +1285,7 @@ public class FragmentedMp4Extractor implements Extractor {
private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment out) private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment out)
throws ParserException { throws ParserException {
senc.setPosition(Atom.HEADER_SIZE + offset); senc.setPosition(Mp4Box.HEADER_SIZE + offset);
int fullAtom = senc.readInt(); int fullAtom = senc.readInt();
int flags = AtomParsers.parseFullAtomFlags(fullAtom); int flags = AtomParsers.parseFullAtomFlags(fullAtom);
@ -1315,20 +1316,20 @@ public class FragmentedMp4Extractor implements Extractor {
} }
private static void parseSampleGroups( private static void parseSampleGroups(
ContainerAtom traf, @Nullable String schemeType, TrackFragment out) throws ParserException { ContainerBox traf, @Nullable String schemeType, TrackFragment out) throws ParserException {
// Find sbgp and sgpd boxes with grouping_type == seig. // Find sbgp and sgpd boxes with grouping_type == seig.
@Nullable ParsableByteArray sbgp = null; @Nullable ParsableByteArray sbgp = null;
@Nullable ParsableByteArray sgpd = null; @Nullable ParsableByteArray sgpd = null;
for (int i = 0; i < traf.leafChildren.size(); i++) { for (int i = 0; i < traf.leafChildren.size(); i++) {
LeafAtom leafAtom = traf.leafChildren.get(i); LeafBox leafAtom = traf.leafChildren.get(i);
ParsableByteArray leafAtomData = leafAtom.data; ParsableByteArray leafAtomData = leafAtom.data;
if (leafAtom.type == Atom.TYPE_sbgp) { if (leafAtom.type == Mp4Box.TYPE_sbgp) {
leafAtomData.setPosition(Atom.FULL_HEADER_SIZE); leafAtomData.setPosition(Mp4Box.FULL_HEADER_SIZE);
if (leafAtomData.readInt() == SAMPLE_GROUP_TYPE_seig) { if (leafAtomData.readInt() == SAMPLE_GROUP_TYPE_seig) {
sbgp = leafAtomData; sbgp = leafAtomData;
} }
} else if (leafAtom.type == Atom.TYPE_sgpd) { } else if (leafAtom.type == Mp4Box.TYPE_sgpd) {
leafAtomData.setPosition(Atom.FULL_HEADER_SIZE); leafAtomData.setPosition(Mp4Box.FULL_HEADER_SIZE);
if (leafAtomData.readInt() == SAMPLE_GROUP_TYPE_seig) { if (leafAtomData.readInt() == SAMPLE_GROUP_TYPE_seig) {
sgpd = leafAtomData; sgpd = leafAtomData;
} }
@ -1338,7 +1339,7 @@ public class FragmentedMp4Extractor implements Extractor {
return; return;
} }
sbgp.setPosition(Atom.HEADER_SIZE); sbgp.setPosition(Mp4Box.HEADER_SIZE);
int sbgpVersion = AtomParsers.parseFullAtomVersion(sbgp.readInt()); int sbgpVersion = AtomParsers.parseFullAtomVersion(sbgp.readInt());
sbgp.skipBytes(4); // grouping_type == seig. sbgp.skipBytes(4); // grouping_type == seig.
if (sbgpVersion == 1) { if (sbgpVersion == 1) {
@ -1349,7 +1350,7 @@ public class FragmentedMp4Extractor implements Extractor {
"Entry count in sbgp != 1 (unsupported)."); "Entry count in sbgp != 1 (unsupported).");
} }
sgpd.setPosition(Atom.HEADER_SIZE); sgpd.setPosition(Mp4Box.HEADER_SIZE);
int sgpdVersion = AtomParsers.parseFullAtomVersion(sgpd.readInt()); int sgpdVersion = AtomParsers.parseFullAtomVersion(sgpd.readInt());
sgpd.skipBytes(4); // grouping_type == seig. sgpd.skipBytes(4); // grouping_type == seig.
if (sgpdVersion == 1) { if (sgpdVersion == 1) {
@ -1405,7 +1406,7 @@ public class FragmentedMp4Extractor implements Extractor {
*/ */
private static Pair<Long, ChunkIndex> parseSidx(ParsableByteArray atom, long inputPosition) private static Pair<Long, ChunkIndex> parseSidx(ParsableByteArray atom, long inputPosition)
throws ParserException { throws ParserException {
atom.setPosition(Atom.HEADER_SIZE); atom.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = atom.readInt(); int fullAtom = atom.readInt();
int version = AtomParsers.parseFullAtomVersion(fullAtom); int version = AtomParsers.parseFullAtomVersion(fullAtom);
@ -1551,8 +1552,8 @@ public class FragmentedMp4Extractor implements Extractor {
if (trackBundle.moovSampleTable.track.sampleTransformation if (trackBundle.moovSampleTable.track.sampleTransformation
== Track.TRANSFORMATION_CEA608_CDAT) { == Track.TRANSFORMATION_CEA608_CDAT) {
sampleSize -= Atom.HEADER_SIZE; sampleSize -= Mp4Box.HEADER_SIZE;
input.skipFully(Atom.HEADER_SIZE); input.skipFully(Mp4Box.HEADER_SIZE);
} }
if (MimeTypes.AUDIO_AC4.equals(trackBundle.moovSampleTable.track.format.sampleMimeType)) { if (MimeTypes.AUDIO_AC4.equals(trackBundle.moovSampleTable.track.format.sampleMimeType)) {
@ -1742,12 +1743,12 @@ public class FragmentedMp4Extractor implements Extractor {
/** Returns DrmInitData from leaf atoms. */ /** Returns DrmInitData from leaf atoms. */
@Nullable @Nullable
private static DrmInitData getDrmInitDataFromAtoms(List<Atom.LeafAtom> leafChildren) { private static DrmInitData getDrmInitDataFromAtoms(List<LeafBox> leafChildren) {
@Nullable ArrayList<SchemeData> schemeDatas = null; @Nullable ArrayList<SchemeData> schemeDatas = null;
int leafChildrenSize = leafChildren.size(); int leafChildrenSize = leafChildren.size();
for (int i = 0; i < leafChildrenSize; i++) { for (int i = 0; i < leafChildrenSize; i++) {
LeafAtom child = leafChildren.get(i); LeafBox child = leafChildren.get(i);
if (child.type == Atom.TYPE_pssh) { if (child.type == Mp4Box.TYPE_pssh) {
if (schemeDatas == null) { if (schemeDatas == null) {
schemeDatas = new ArrayList<>(); schemeDatas = new ArrayList<>();
} }
@ -1765,47 +1766,47 @@ public class FragmentedMp4Extractor implements Extractor {
/** Returns whether the extractor should decode a leaf atom with type {@code atom}. */ /** Returns whether the extractor should decode a leaf atom with type {@code atom}. */
private static boolean shouldParseLeafAtom(int atom) { private static boolean shouldParseLeafAtom(int atom) {
return atom == Atom.TYPE_hdlr return atom == Mp4Box.TYPE_hdlr
|| atom == Atom.TYPE_mdhd || atom == Mp4Box.TYPE_mdhd
|| atom == Atom.TYPE_mvhd || atom == Mp4Box.TYPE_mvhd
|| atom == Atom.TYPE_sidx || atom == Mp4Box.TYPE_sidx
|| atom == Atom.TYPE_stsd || atom == Mp4Box.TYPE_stsd
|| atom == Atom.TYPE_stts || atom == Mp4Box.TYPE_stts
|| atom == Atom.TYPE_ctts || atom == Mp4Box.TYPE_ctts
|| atom == Atom.TYPE_stsc || atom == Mp4Box.TYPE_stsc
|| atom == Atom.TYPE_stsz || atom == Mp4Box.TYPE_stsz
|| atom == Atom.TYPE_stz2 || atom == Mp4Box.TYPE_stz2
|| atom == Atom.TYPE_stco || atom == Mp4Box.TYPE_stco
|| atom == Atom.TYPE_co64 || atom == Mp4Box.TYPE_co64
|| atom == Atom.TYPE_stss || atom == Mp4Box.TYPE_stss
|| atom == Atom.TYPE_tfdt || atom == Mp4Box.TYPE_tfdt
|| atom == Atom.TYPE_tfhd || atom == Mp4Box.TYPE_tfhd
|| atom == Atom.TYPE_tkhd || atom == Mp4Box.TYPE_tkhd
|| atom == Atom.TYPE_trex || atom == Mp4Box.TYPE_trex
|| atom == Atom.TYPE_trun || atom == Mp4Box.TYPE_trun
|| atom == Atom.TYPE_pssh || atom == Mp4Box.TYPE_pssh
|| atom == Atom.TYPE_saiz || atom == Mp4Box.TYPE_saiz
|| atom == Atom.TYPE_saio || atom == Mp4Box.TYPE_saio
|| atom == Atom.TYPE_senc || atom == Mp4Box.TYPE_senc
|| atom == Atom.TYPE_uuid || atom == Mp4Box.TYPE_uuid
|| atom == Atom.TYPE_sbgp || atom == Mp4Box.TYPE_sbgp
|| atom == Atom.TYPE_sgpd || atom == Mp4Box.TYPE_sgpd
|| atom == Atom.TYPE_elst || atom == Mp4Box.TYPE_elst
|| atom == Atom.TYPE_mehd || atom == Mp4Box.TYPE_mehd
|| atom == Atom.TYPE_emsg; || atom == Mp4Box.TYPE_emsg;
} }
/** Returns whether the extractor should decode a container atom with type {@code atom}. */ /** Returns whether the extractor should decode a container atom with type {@code atom}. */
private static boolean shouldParseContainerAtom(int atom) { private static boolean shouldParseContainerAtom(int atom) {
return atom == Atom.TYPE_moov return atom == Mp4Box.TYPE_moov
|| atom == Atom.TYPE_trak || atom == Mp4Box.TYPE_trak
|| atom == Atom.TYPE_mdia || atom == Mp4Box.TYPE_mdia
|| atom == Atom.TYPE_minf || atom == Mp4Box.TYPE_minf
|| atom == Atom.TYPE_stbl || atom == Mp4Box.TYPE_stbl
|| atom == Atom.TYPE_moof || atom == Mp4Box.TYPE_moof
|| atom == Atom.TYPE_traf || atom == Mp4Box.TYPE_traf
|| atom == Atom.TYPE_mvex || atom == Mp4Box.TYPE_mvex
|| atom == Atom.TYPE_edts; || atom == Mp4Box.TYPE_edts;
} }
/** Holds data corresponding to a metadata sample. */ /** Holds data corresponding to a metadata sample. */

View File

@ -25,6 +25,7 @@ import androidx.media3.common.util.Log;
import androidx.media3.common.util.NullableType; import androidx.media3.common.util.NullableType;
import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.container.MdtaMetadataEntry; import androidx.media3.container.MdtaMetadataEntry;
import androidx.media3.container.Mp4Box;
import androidx.media3.extractor.GaplessInfoHolder; import androidx.media3.extractor.GaplessInfoHolder;
import androidx.media3.extractor.metadata.id3.ApicFrame; import androidx.media3.extractor.metadata.id3.ApicFrame;
import androidx.media3.extractor.metadata.id3.CommentFrame; import androidx.media3.extractor.metadata.id3.CommentFrame;
@ -203,7 +204,7 @@ import com.google.common.collect.ImmutableList;
} else if (type == TYPE_INTERNAL) { } else if (type == TYPE_INTERNAL) {
return parseInternalAttribute(ilst, endPosition); return parseInternalAttribute(ilst, endPosition);
} }
Log.d(TAG, "Skipped unknown metadata entry: " + Atom.getAtomTypeString(type)); Log.d(TAG, "Skipped unknown metadata entry: " + Mp4Box.getBoxTypeString(type));
return null; return null;
} finally { } finally {
ilst.setPosition(endPosition); ilst.setPosition(endPosition);
@ -225,7 +226,7 @@ import com.google.common.collect.ImmutableList;
while ((atomPosition = ilst.getPosition()) < endPosition) { while ((atomPosition = ilst.getPosition()) < endPosition) {
int atomSize = ilst.readInt(); int atomSize = ilst.readInt();
int atomType = ilst.readInt(); int atomType = ilst.readInt();
if (atomType == Atom.TYPE_data) { if (atomType == Mp4Box.TYPE_data) {
int typeIndicator = ilst.readInt(); int typeIndicator = ilst.readInt();
int localeIndicator = ilst.readInt(); int localeIndicator = ilst.readInt();
int dataSize = atomSize - 16; int dataSize = atomSize - 16;
@ -264,12 +265,12 @@ import com.google.common.collect.ImmutableList;
int type, String id, ParsableByteArray data) { int type, String id, ParsableByteArray data) {
int atomSize = data.readInt(); int atomSize = data.readInt();
int atomType = data.readInt(); int atomType = data.readInt();
if (atomType == Atom.TYPE_data) { if (atomType == Mp4Box.TYPE_data) {
data.skipBytes(8); // version (1), flags (3), empty (4) data.skipBytes(8); // version (1), flags (3), empty (4)
String value = data.readNullTerminatedString(atomSize - 16); String value = data.readNullTerminatedString(atomSize - 16);
return new TextInformationFrame(id, /* description= */ null, ImmutableList.of(value)); return new TextInformationFrame(id, /* description= */ null, ImmutableList.of(value));
} }
Log.w(TAG, "Failed to parse text attribute: " + Atom.getAtomTypeString(type)); Log.w(TAG, "Failed to parse text attribute: " + Mp4Box.getBoxTypeString(type));
return null; return null;
} }
@ -277,12 +278,12 @@ import com.google.common.collect.ImmutableList;
private static CommentFrame parseCommentAttribute(int type, ParsableByteArray data) { private static CommentFrame parseCommentAttribute(int type, ParsableByteArray data) {
int atomSize = data.readInt(); int atomSize = data.readInt();
int atomType = data.readInt(); int atomType = data.readInt();
if (atomType == Atom.TYPE_data) { if (atomType == Mp4Box.TYPE_data) {
data.skipBytes(8); // version (1), flags (3), empty (4) data.skipBytes(8); // version (1), flags (3), empty (4)
String value = data.readNullTerminatedString(atomSize - 16); String value = data.readNullTerminatedString(atomSize - 16);
return new CommentFrame(C.LANGUAGE_UNDETERMINED, value, value); return new CommentFrame(C.LANGUAGE_UNDETERMINED, value, value);
} }
Log.w(TAG, "Failed to parse comment attribute: " + Atom.getAtomTypeString(type)); Log.w(TAG, "Failed to parse comment attribute: " + Mp4Box.getBoxTypeString(type));
return null; return null;
} }
@ -303,14 +304,14 @@ import com.google.common.collect.ImmutableList;
id, /* description= */ null, ImmutableList.of(Integer.toString(value))) id, /* description= */ null, ImmutableList.of(Integer.toString(value)))
: new CommentFrame(C.LANGUAGE_UNDETERMINED, id, Integer.toString(value)); : new CommentFrame(C.LANGUAGE_UNDETERMINED, id, Integer.toString(value));
} }
Log.w(TAG, "Failed to parse uint8 attribute: " + Atom.getAtomTypeString(type)); Log.w(TAG, "Failed to parse uint8 attribute: " + Mp4Box.getBoxTypeString(type));
return null; return null;
} }
private static int parseIntegerAttribute(ParsableByteArray data) { private static int parseIntegerAttribute(ParsableByteArray data) {
int atomSize = data.readInt(); int atomSize = data.readInt();
int atomType = data.readInt(); int atomType = data.readInt();
if (atomType == Atom.TYPE_data) { if (atomType == Mp4Box.TYPE_data) {
data.skipBytes(8); // version (1), flags (3), empty (4) data.skipBytes(8); // version (1), flags (3), empty (4)
switch (atomSize - 16) { switch (atomSize - 16) {
case 1: case 1:
@ -334,7 +335,7 @@ import com.google.common.collect.ImmutableList;
int type, String attributeName, ParsableByteArray data) { int type, String attributeName, ParsableByteArray data) {
int atomSize = data.readInt(); int atomSize = data.readInt();
int atomType = data.readInt(); int atomType = data.readInt();
if (atomType == Atom.TYPE_data && atomSize >= 22) { if (atomType == Mp4Box.TYPE_data && atomSize >= 22) {
data.skipBytes(10); // version (1), flags (3), empty (4), empty (2) data.skipBytes(10); // version (1), flags (3), empty (4), empty (2)
int index = data.readUnsignedShort(); int index = data.readUnsignedShort();
if (index > 0) { if (index > 0) {
@ -347,7 +348,7 @@ import com.google.common.collect.ImmutableList;
attributeName, /* description= */ null, ImmutableList.of(value)); attributeName, /* description= */ null, ImmutableList.of(value));
} }
} }
Log.w(TAG, "Failed to parse index/count attribute: " + Atom.getAtomTypeString(type)); Log.w(TAG, "Failed to parse index/count attribute: " + Mp4Box.getBoxTypeString(type));
return null; return null;
} }
@ -369,7 +370,7 @@ import com.google.common.collect.ImmutableList;
private static ApicFrame parseCoverArt(ParsableByteArray data) { private static ApicFrame parseCoverArt(ParsableByteArray data) {
int atomSize = data.readInt(); int atomSize = data.readInt();
int atomType = data.readInt(); int atomType = data.readInt();
if (atomType == Atom.TYPE_data) { if (atomType == Mp4Box.TYPE_data) {
int fullVersionInt = data.readInt(); int fullVersionInt = data.readInt();
int flags = AtomParsers.parseFullAtomFlags(fullVersionInt); int flags = AtomParsers.parseFullAtomFlags(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;
@ -401,12 +402,12 @@ import com.google.common.collect.ImmutableList;
int atomSize = data.readInt(); int atomSize = data.readInt();
int atomType = data.readInt(); int atomType = data.readInt();
data.skipBytes(4); // version (1), flags (3) data.skipBytes(4); // version (1), flags (3)
if (atomType == Atom.TYPE_mean) { if (atomType == Mp4Box.TYPE_mean) {
domain = data.readNullTerminatedString(atomSize - 12); domain = data.readNullTerminatedString(atomSize - 12);
} else if (atomType == Atom.TYPE_name) { } else if (atomType == Mp4Box.TYPE_name) {
name = data.readNullTerminatedString(atomSize - 12); name = data.readNullTerminatedString(atomSize - 12);
} else { } else {
if (atomType == Atom.TYPE_data) { if (atomType == Mp4Box.TYPE_data) {
dataAtomPosition = atomPosition; dataAtomPosition = atomPosition;
dataAtomSize = atomSize; dataAtomSize = atomSize;
} }

View File

@ -47,6 +47,8 @@ import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.container.MdtaMetadataEntry; import androidx.media3.container.MdtaMetadataEntry;
import androidx.media3.container.Mp4Box;
import androidx.media3.container.Mp4Box.ContainerBox;
import androidx.media3.container.NalUnitUtil; import androidx.media3.container.NalUnitUtil;
import androidx.media3.extractor.Ac3Util; import androidx.media3.extractor.Ac3Util;
import androidx.media3.extractor.Ac4Util; import androidx.media3.extractor.Ac4Util;
@ -63,7 +65,6 @@ import androidx.media3.extractor.TrackOutput;
import androidx.media3.extractor.TrueHdSampleRechunker; import androidx.media3.extractor.TrueHdSampleRechunker;
import androidx.media3.extractor.metadata.mp4.MotionPhotoMetadata; import androidx.media3.extractor.metadata.mp4.MotionPhotoMetadata;
import androidx.media3.extractor.metadata.mp4.SlowMotionData; import androidx.media3.extractor.metadata.mp4.SlowMotionData;
import androidx.media3.extractor.mp4.Atom.ContainerAtom;
import androidx.media3.extractor.text.SubtitleParser; import androidx.media3.extractor.text.SubtitleParser;
import androidx.media3.extractor.text.SubtitleTranscodingExtractorOutput; import androidx.media3.extractor.text.SubtitleTranscodingExtractorOutput;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -229,7 +230,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
private final ParsableByteArray scratch; private final ParsableByteArray scratch;
private final ParsableByteArray atomHeader; private final ParsableByteArray atomHeader;
private final ArrayDeque<ContainerAtom> containerAtoms; private final ArrayDeque<ContainerBox> containerAtoms;
private final SefReader sefReader; private final SefReader sefReader;
private final List<Metadata.Entry> slowMotionMetadataEntries; private final List<Metadata.Entry> slowMotionMetadataEntries;
@ -305,7 +306,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
((flags & FLAG_READ_SEF_DATA) != 0) ? STATE_READING_SEF : STATE_READING_ATOM_HEADER; ((flags & FLAG_READ_SEF_DATA) != 0) ? STATE_READING_SEF : STATE_READING_ATOM_HEADER;
sefReader = new SefReader(); sefReader = new SefReader();
slowMotionMetadataEntries = new ArrayList<>(); slowMotionMetadataEntries = new ArrayList<>();
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); atomHeader = new ParsableByteArray(Mp4Box.LONG_HEADER_SIZE);
containerAtoms = new ArrayDeque<>(); containerAtoms = new ArrayDeque<>();
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
nalPrefix = new ParsableByteArray(5); nalPrefix = new ParsableByteArray(5);
@ -493,28 +494,28 @@ public final class Mp4Extractor implements Extractor, SeekMap {
private boolean readAtomHeader(ExtractorInput input) throws IOException { private boolean readAtomHeader(ExtractorInput input) throws IOException {
if (atomHeaderBytesRead == 0) { if (atomHeaderBytesRead == 0) {
// Read the standard length atom header. // Read the standard length atom header.
if (!input.readFully(atomHeader.getData(), 0, Atom.HEADER_SIZE, true)) { if (!input.readFully(atomHeader.getData(), 0, Mp4Box.HEADER_SIZE, true)) {
processEndOfStreamReadingAtomHeader(); processEndOfStreamReadingAtomHeader();
return false; return false;
} }
atomHeaderBytesRead = Atom.HEADER_SIZE; atomHeaderBytesRead = Mp4Box.HEADER_SIZE;
atomHeader.setPosition(0); atomHeader.setPosition(0);
atomSize = atomHeader.readUnsignedInt(); atomSize = atomHeader.readUnsignedInt();
atomType = atomHeader.readInt(); atomType = atomHeader.readInt();
} }
if (atomSize == Atom.DEFINES_LARGE_SIZE) { if (atomSize == Mp4Box.DEFINES_LARGE_SIZE) {
// Read the large size. // Read the large size.
int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE; int headerBytesRemaining = Mp4Box.LONG_HEADER_SIZE - Mp4Box.HEADER_SIZE;
input.readFully(atomHeader.getData(), Atom.HEADER_SIZE, headerBytesRemaining); input.readFully(atomHeader.getData(), Mp4Box.HEADER_SIZE, headerBytesRemaining);
atomHeaderBytesRead += headerBytesRemaining; atomHeaderBytesRead += headerBytesRemaining;
atomSize = atomHeader.readUnsignedLongToLong(); atomSize = atomHeader.readUnsignedLongToLong();
} else if (atomSize == Atom.EXTENDS_TO_END_SIZE) { } else if (atomSize == Mp4Box.EXTENDS_TO_END_SIZE) {
// The atom extends to the end of the file. Note that if the atom is within a container we can // The atom extends to the end of the file. Note that if the atom is within a container we can
// work out its size even if the input length is unknown. // work out its size even if the input length is unknown.
long endPosition = input.getLength(); long endPosition = input.getLength();
if (endPosition == C.LENGTH_UNSET) { if (endPosition == C.LENGTH_UNSET) {
@Nullable ContainerAtom containerAtom = containerAtoms.peek(); @Nullable ContainerBox containerAtom = containerAtoms.peek();
if (containerAtom != null) { if (containerAtom != null) {
endPosition = containerAtom.endPosition; endPosition = containerAtom.endPosition;
} }
@ -531,10 +532,10 @@ public final class Mp4Extractor implements Extractor, SeekMap {
if (shouldParseContainerAtom(atomType)) { if (shouldParseContainerAtom(atomType)) {
long endPosition = input.getPosition() + atomSize - atomHeaderBytesRead; long endPosition = input.getPosition() + atomSize - atomHeaderBytesRead;
if (atomSize != atomHeaderBytesRead && atomType == Atom.TYPE_meta) { if (atomSize != atomHeaderBytesRead && atomType == Mp4Box.TYPE_meta) {
maybeSkipRemainingMetaAtomHeaderBytes(input); maybeSkipRemainingMetaAtomHeaderBytes(input);
} }
containerAtoms.push(new ContainerAtom(atomType, endPosition)); containerAtoms.push(new ContainerBox(atomType, endPosition));
if (atomSize == atomHeaderBytesRead) { if (atomSize == atomHeaderBytesRead) {
processAtomEnded(endPosition); processAtomEnded(endPosition);
} else { } else {
@ -544,10 +545,10 @@ public final class Mp4Extractor implements Extractor, SeekMap {
} else if (shouldParseLeafAtom(atomType)) { } else if (shouldParseLeafAtom(atomType)) {
// We don't support parsing of leaf atoms that define extended atom sizes, or that have // We don't support parsing of leaf atoms that define extended atom sizes, or that have
// lengths greater than Integer.MAX_VALUE. // lengths greater than Integer.MAX_VALUE.
Assertions.checkState(atomHeaderBytesRead == Atom.HEADER_SIZE); Assertions.checkState(atomHeaderBytesRead == Mp4Box.HEADER_SIZE);
Assertions.checkState(atomSize <= Integer.MAX_VALUE); Assertions.checkState(atomSize <= Integer.MAX_VALUE);
ParsableByteArray atomData = new ParsableByteArray((int) atomSize); ParsableByteArray atomData = new ParsableByteArray((int) atomSize);
System.arraycopy(atomHeader.getData(), 0, atomData.getData(), 0, Atom.HEADER_SIZE); System.arraycopy(atomHeader.getData(), 0, atomData.getData(), 0, Mp4Box.HEADER_SIZE);
this.atomData = atomData; this.atomData = atomData;
parserState = STATE_READING_ATOM_PAYLOAD; parserState = STATE_READING_ATOM_PAYLOAD;
} else { } else {
@ -573,14 +574,14 @@ public final class Mp4Extractor implements Extractor, SeekMap {
@Nullable ParsableByteArray atomData = this.atomData; @Nullable ParsableByteArray atomData = this.atomData;
if (atomData != null) { if (atomData != null) {
input.readFully(atomData.getData(), atomHeaderBytesRead, (int) atomPayloadSize); input.readFully(atomData.getData(), atomHeaderBytesRead, (int) atomPayloadSize);
if (atomType == Atom.TYPE_ftyp) { if (atomType == Mp4Box.TYPE_ftyp) {
seenFtypAtom = true; seenFtypAtom = true;
fileType = processFtypAtom(atomData); fileType = processFtypAtom(atomData);
} else if (!containerAtoms.isEmpty()) { } else if (!containerAtoms.isEmpty()) {
containerAtoms.peek().add(new Atom.LeafAtom(atomType, atomData)); containerAtoms.peek().add(new Mp4Box.LeafBox(atomType, atomData));
} }
} else { } else {
if (!seenFtypAtom && atomType == Atom.TYPE_mdat) { if (!seenFtypAtom && atomType == Mp4Box.TYPE_mdat) {
// The original QuickTime specification did not require files to begin with the ftyp atom. // The original QuickTime specification did not require files to begin with the ftyp atom.
// See https://developer.apple.com/standards/qtff-2001.pdf. // See https://developer.apple.com/standards/qtff-2001.pdf.
fileType = FILE_TYPE_QUICKTIME; fileType = FILE_TYPE_QUICKTIME;
@ -614,8 +615,8 @@ public final class Mp4Extractor implements Extractor, SeekMap {
private void processAtomEnded(long atomEndPosition) throws ParserException { private void processAtomEnded(long atomEndPosition) throws ParserException {
while (!containerAtoms.isEmpty() && containerAtoms.peek().endPosition == atomEndPosition) { while (!containerAtoms.isEmpty() && containerAtoms.peek().endPosition == atomEndPosition) {
Atom.ContainerAtom containerAtom = containerAtoms.pop(); ContainerBox containerAtom = containerAtoms.pop();
if (containerAtom.type == Atom.TYPE_moov) { if (containerAtom.type == Mp4Box.TYPE_moov) {
// We've reached the end of the moov atom. Process it and prepare to read samples. // We've reached the end of the moov atom. Process it and prepare to read samples.
processMoovAtom(containerAtom); processMoovAtom(containerAtom);
containerAtoms.clear(); containerAtoms.clear();
@ -636,10 +637,10 @@ public final class Mp4Extractor implements Extractor, SeekMap {
* *
* <p>The processing is aborted if the edvd.moov atom needs to be processed instead. * <p>The processing is aborted if the edvd.moov atom needs to be processed instead.
*/ */
private void processMoovAtom(ContainerAtom moov) throws ParserException { private void processMoovAtom(ContainerBox moov) throws ParserException {
// Process metadata first to determine whether to abort processing and seek to the edvd atom. // Process metadata first to determine whether to abort processing and seek to the edvd atom.
@Nullable Metadata mdtaMetadata = null; @Nullable Metadata mdtaMetadata = null;
@Nullable Atom.ContainerAtom meta = moov.getContainerAtomOfType(Atom.TYPE_meta); @Nullable Mp4Box.ContainerBox meta = moov.getContainerBoxOfType(Mp4Box.TYPE_meta);
List<@C.AuxiliaryTrackType Integer> auxiliaryTrackTypesForEditableVideoTracks = List<@C.AuxiliaryTrackType Integer> auxiliaryTrackTypesForEditableVideoTracks =
new ArrayList<>(); new ArrayList<>();
if (meta != null) { if (meta != null) {
@ -654,7 +655,6 @@ public final class Mp4Extractor implements Extractor, SeekMap {
return; return;
} }
} }
int firstVideoTrackIndex = C.INDEX_UNSET; int firstVideoTrackIndex = C.INDEX_UNSET;
long durationUs = C.TIME_UNSET; long durationUs = C.TIME_UNSET;
List<Mp4Track> tracks = new ArrayList<>(); List<Mp4Track> tracks = new ArrayList<>();
@ -663,7 +663,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
boolean isQuickTime = fileType == FILE_TYPE_QUICKTIME; boolean isQuickTime = fileType == FILE_TYPE_QUICKTIME;
GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder(); GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder();
@Nullable Metadata udtaMetadata = null; @Nullable Metadata udtaMetadata = null;
@Nullable Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta); @Nullable Mp4Box.LeafBox udta = moov.getLeafBoxOfType(Mp4Box.TYPE_udta);
if (udta != null) { if (udta != null) {
udtaMetadata = AtomParsers.parseUdta(udta); udtaMetadata = AtomParsers.parseUdta(udta);
gaplessInfoHolder.setFromMetadata(udtaMetadata); gaplessInfoHolder.setFromMetadata(udtaMetadata);
@ -671,7 +671,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
Metadata mvhdMetadata = Metadata mvhdMetadata =
new Metadata( new Metadata(
AtomParsers.parseMvhd(checkNotNull(moov.getLeafAtomOfType(Atom.TYPE_mvhd)).data)); AtomParsers.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 =
@ -863,8 +863,8 @@ public final class Mp4Extractor implements Extractor, SeekMap {
if (track.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) { if (track.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) {
// The sample information is contained in a cdat atom. The header must be discarded for // The sample information is contained in a cdat atom. The header must be discarded for
// committing. // committing.
skipAmount += Atom.HEADER_SIZE; skipAmount += Mp4Box.HEADER_SIZE;
sampleSize -= Atom.HEADER_SIZE; sampleSize -= Mp4Box.HEADER_SIZE;
} }
input.skipFully((int) skipAmount); input.skipFully((int) skipAmount);
// Treat all samples in non-H.264 codecs as depended on. // Treat all samples in non-H.264 codecs as depended on.
@ -1050,7 +1050,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
/** Processes an atom whose payload does not need to be parsed. */ /** Processes an atom whose payload does not need to be parsed. */
private void processUnparsedAtom(long atomStartPosition) { private void processUnparsedAtom(long atomStartPosition) {
if (atomType == Atom.TYPE_mpvd) { if (atomType == Mp4Box.TYPE_mpvd) {
// The input is an HEIC motion photo following the Google Photos Motion Photo File Format // The input is an HEIC motion photo following the Google Photos Motion Photo File Format
// V1.1. // V1.1.
motionPhotoMetadata = motionPhotoMetadata =
@ -1148,7 +1148,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
* @return The {@link FileType}. * @return The {@link FileType}.
*/ */
private static @FileType int processFtypAtom(ParsableByteArray atomData) { private static @FileType int processFtypAtom(ParsableByteArray atomData) {
atomData.setPosition(Atom.HEADER_SIZE); atomData.setPosition(Mp4Box.HEADER_SIZE);
int majorBrand = atomData.readInt(); int majorBrand = atomData.readInt();
@FileType int fileType = brandToFileType(majorBrand); @FileType int fileType = brandToFileType(majorBrand);
if (fileType != FILE_TYPE_MP4) { if (fileType != FILE_TYPE_MP4) {
@ -1177,36 +1177,36 @@ public final class Mp4Extractor implements Extractor, SeekMap {
/** Returns whether the extractor should decode a leaf atom with type {@code atom}. */ /** Returns whether the extractor should decode a leaf atom with type {@code atom}. */
private static boolean shouldParseLeafAtom(int atom) { private static boolean shouldParseLeafAtom(int atom) {
return atom == Atom.TYPE_mdhd return atom == Mp4Box.TYPE_mdhd
|| atom == Atom.TYPE_mvhd || atom == Mp4Box.TYPE_mvhd
|| atom == Atom.TYPE_hdlr || atom == Mp4Box.TYPE_hdlr
|| atom == Atom.TYPE_stsd || atom == Mp4Box.TYPE_stsd
|| atom == Atom.TYPE_stts || atom == Mp4Box.TYPE_stts
|| atom == Atom.TYPE_stss || atom == Mp4Box.TYPE_stss
|| atom == Atom.TYPE_ctts || atom == Mp4Box.TYPE_ctts
|| atom == Atom.TYPE_elst || atom == Mp4Box.TYPE_elst
|| atom == Atom.TYPE_stsc || atom == Mp4Box.TYPE_stsc
|| atom == Atom.TYPE_stsz || atom == Mp4Box.TYPE_stsz
|| atom == Atom.TYPE_stz2 || atom == Mp4Box.TYPE_stz2
|| atom == Atom.TYPE_stco || atom == Mp4Box.TYPE_stco
|| atom == Atom.TYPE_co64 || atom == Mp4Box.TYPE_co64
|| atom == Atom.TYPE_tkhd || atom == Mp4Box.TYPE_tkhd
|| atom == Atom.TYPE_ftyp || atom == Mp4Box.TYPE_ftyp
|| atom == Atom.TYPE_udta || atom == Mp4Box.TYPE_udta
|| atom == Atom.TYPE_keys || atom == Mp4Box.TYPE_keys
|| atom == Atom.TYPE_ilst; || atom == Mp4Box.TYPE_ilst;
} }
/** Returns whether the extractor should decode a container atom with type {@code atom}. */ /** Returns whether the extractor should decode a container atom with type {@code atom}. */
private static boolean shouldParseContainerAtom(int atom) { private static boolean shouldParseContainerAtom(int atom) {
return atom == Atom.TYPE_moov return atom == Mp4Box.TYPE_moov
|| atom == Atom.TYPE_trak || atom == Mp4Box.TYPE_trak
|| atom == Atom.TYPE_mdia || atom == Mp4Box.TYPE_mdia
|| atom == Atom.TYPE_minf || atom == Mp4Box.TYPE_minf
|| atom == Atom.TYPE_stbl || atom == Mp4Box.TYPE_stbl
|| atom == Atom.TYPE_edts || atom == Mp4Box.TYPE_edts
|| atom == Atom.TYPE_meta || atom == Mp4Box.TYPE_meta
|| atom == Atom.TYPE_edvd; || atom == Mp4Box.TYPE_edvd;
} }
private static final class Mp4Track { private static final class Mp4Track {

View File

@ -19,6 +19,7 @@ import androidx.annotation.Nullable;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.container.Mp4Box;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.UUID; import java.util.UUID;
@ -52,13 +53,13 @@ public final class PsshAtomUtil {
public static byte[] buildPsshAtom( public static byte[] buildPsshAtom(
UUID systemId, @Nullable UUID[] keyIds, @Nullable byte[] data) { UUID systemId, @Nullable UUID[] keyIds, @Nullable byte[] data) {
int dataLength = data != null ? data.length : 0; int dataLength = data != null ? data.length : 0;
int psshBoxLength = Atom.FULL_HEADER_SIZE + 16 /* SystemId */ + 4 /* DataSize */ + dataLength; int psshBoxLength = Mp4Box.FULL_HEADER_SIZE + 16 /* SystemId */ + 4 /* DataSize */ + dataLength;
if (keyIds != null) { if (keyIds != null) {
psshBoxLength += 4 /* KID_count */ + (keyIds.length * 16) /* KIDs */; psshBoxLength += 4 /* KID_count */ + (keyIds.length * 16) /* KIDs */;
} }
ByteBuffer psshBox = ByteBuffer.allocate(psshBoxLength); ByteBuffer psshBox = ByteBuffer.allocate(psshBoxLength);
psshBox.putInt(psshBoxLength); psshBox.putInt(psshBoxLength);
psshBox.putInt(Atom.TYPE_pssh); psshBox.putInt(Mp4Box.TYPE_pssh);
psshBox.putInt(keyIds != null ? 0x01000000 : 0 /* version=(buildV1Atom ? 1 : 0), flags=0 */); psshBox.putInt(keyIds != null ? 0x01000000 : 0 /* version=(buildV1Atom ? 1 : 0), flags=0 */);
psshBox.putLong(systemId.getMostSignificantBits()); psshBox.putLong(systemId.getMostSignificantBits());
psshBox.putLong(systemId.getLeastSignificantBits()); psshBox.putLong(systemId.getLeastSignificantBits());
@ -157,7 +158,7 @@ public final class PsshAtomUtil {
@Nullable @Nullable
public static PsshAtom parsePsshAtom(byte[] atom) { public static PsshAtom parsePsshAtom(byte[] atom) {
ParsableByteArray atomData = new ParsableByteArray(atom); ParsableByteArray atomData = new ParsableByteArray(atom);
if (atomData.limit() < Atom.FULL_HEADER_SIZE + 16 /* UUID */ + 4 /* DataSize */) { if (atomData.limit() < Mp4Box.FULL_HEADER_SIZE + 16 /* UUID */ + 4 /* DataSize */) {
// Data too short. // Data too short.
return null; return null;
} }
@ -171,7 +172,7 @@ public final class PsshAtomUtil {
return null; return null;
} }
int atomType = atomData.readInt(); int atomType = atomData.readInt();
if (atomType != Atom.TYPE_pssh) { if (atomType != Mp4Box.TYPE_pssh) {
Log.w(TAG, "Atom type is not pssh: " + atomType); Log.w(TAG, "Atom type is not pssh: " + atomType);
return null; return null;
} }

View File

@ -23,6 +23,7 @@ import androidx.media3.common.C;
import androidx.media3.common.Metadata; import androidx.media3.common.Metadata;
import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.container.Mp4Box;
import androidx.media3.extractor.metadata.mp4.SmtaMetadataEntry; import androidx.media3.extractor.metadata.mp4.SmtaMetadataEntry;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@ -70,12 +71,12 @@ public final class SmtaAtomUtil {
/** Parses metadata from a Samsung smta atom. */ /** Parses metadata from a Samsung smta atom. */
@Nullable @Nullable
public static Metadata parseSmta(ParsableByteArray smta, int limit) { public static Metadata parseSmta(ParsableByteArray smta, int limit) {
smta.skipBytes(Atom.FULL_HEADER_SIZE); smta.skipBytes(Mp4Box.FULL_HEADER_SIZE);
while (smta.getPosition() < limit) { while (smta.getPosition() < limit) {
int atomPosition = smta.getPosition(); int atomPosition = smta.getPosition();
int atomSize = smta.readInt(); int atomSize = smta.readInt();
int atomType = smta.readInt(); int atomType = smta.readInt();
if (atomType == Atom.TYPE_saut) { if (atomType == Mp4Box.TYPE_saut) {
// Size (4), Type (4), Author (4), Recording mode (2), SVC layer count (2). // Size (4), Type (4), Author (4), Recording mode (2), SVC layer count (2).
if (atomSize < 16) { if (atomSize < 16) {
return null; return null;
@ -126,13 +127,13 @@ public final class SmtaAtomUtil {
return C.RATE_UNSET_INT; return C.RATE_UNSET_INT;
} }
if (smta.bytesLeft() < Atom.HEADER_SIZE || smta.getPosition() + Atom.HEADER_SIZE > limit) { if (smta.bytesLeft() < Mp4Box.HEADER_SIZE || smta.getPosition() + Mp4Box.HEADER_SIZE > limit) {
return C.RATE_UNSET_INT; return C.RATE_UNSET_INT;
} }
int atomSize = smta.readInt(); int atomSize = smta.readInt();
int atomType = smta.readInt(); int atomType = smta.readInt();
if (atomSize < 12 || atomType != Atom.TYPE_srfr) { if (atomSize < 12 || atomType != Mp4Box.TYPE_srfr) {
return C.RATE_UNSET_INT; return C.RATE_UNSET_INT;
} }
// Capture frame rate is in Q16 format. // Capture frame rate is in Q16 format.

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.container.Mp4Box;
import androidx.media3.extractor.ExtractorInput; import androidx.media3.extractor.ExtractorInput;
import androidx.media3.extractor.SniffFailure; import androidx.media3.extractor.SniffFailure;
import java.io.IOException; import java.io.IOException;
@ -118,7 +119,7 @@ import java.io.IOException;
boolean isFragmented = false; boolean isFragmented = false;
while (bytesSearched < bytesToSearch) { while (bytesSearched < bytesToSearch) {
// Read an atom header. // Read an atom header.
int headerSize = Atom.HEADER_SIZE; int headerSize = Mp4Box.HEADER_SIZE;
buffer.reset(headerSize); buffer.reset(headerSize);
boolean success = boolean success =
input.peekFully(buffer.getData(), 0, headerSize, /* allowEndOfInput= */ true); input.peekFully(buffer.getData(), 0, headerSize, /* allowEndOfInput= */ true);
@ -128,14 +129,14 @@ import java.io.IOException;
} }
long atomSize = buffer.readUnsignedInt(); long atomSize = buffer.readUnsignedInt();
int atomType = buffer.readInt(); int atomType = buffer.readInt();
if (atomSize == Atom.DEFINES_LARGE_SIZE) { if (atomSize == Mp4Box.DEFINES_LARGE_SIZE) {
// Read the large atom size. // Read the large atom size.
headerSize = Atom.LONG_HEADER_SIZE; headerSize = Mp4Box.LONG_HEADER_SIZE;
input.peekFully( input.peekFully(
buffer.getData(), Atom.HEADER_SIZE, Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE); buffer.getData(), Mp4Box.HEADER_SIZE, Mp4Box.LONG_HEADER_SIZE - Mp4Box.HEADER_SIZE);
buffer.setLimit(Atom.LONG_HEADER_SIZE); buffer.setLimit(Mp4Box.LONG_HEADER_SIZE);
atomSize = buffer.readLong(); atomSize = buffer.readLong();
} else if (atomSize == Atom.EXTENDS_TO_END_SIZE) { } else if (atomSize == Mp4Box.EXTENDS_TO_END_SIZE) {
// The atom extends to the end of the file. // The atom extends to the end of the file.
long fileEndPosition = input.getLength(); long fileEndPosition = input.getLength();
if (fileEndPosition != C.LENGTH_UNSET) { if (fileEndPosition != C.LENGTH_UNSET) {
@ -149,7 +150,7 @@ import java.io.IOException;
} }
bytesSearched += headerSize; bytesSearched += headerSize;
if (atomType == Atom.TYPE_moov) { if (atomType == Mp4Box.TYPE_moov) {
// We have seen the moov atom. We increase the search size to make sure we don't miss an // We have seen the moov atom. We increase the search size to make sure we don't miss an
// mvex atom because the moov's size exceeds the search length. // mvex atom because the moov's size exceeds the search length.
bytesToSearch += (int) atomSize; bytesToSearch += (int) atomSize;
@ -161,13 +162,13 @@ import java.io.IOException;
continue; continue;
} }
if (atomType == Atom.TYPE_moof || atomType == Atom.TYPE_mvex) { if (atomType == Mp4Box.TYPE_moof || atomType == Mp4Box.TYPE_mvex) {
// The movie is fragmented. Stop searching as we must have read any ftyp atom already. // The movie is fragmented. Stop searching as we must have read any ftyp atom already.
isFragmented = true; isFragmented = true;
break; break;
} }
if (atomType == Atom.TYPE_mdat) { if (atomType == Mp4Box.TYPE_mdat) {
// The original QuickTime specification did not require files to begin with the ftyp atom. // The original QuickTime specification did not require files to begin with the ftyp atom.
// See https://developer.apple.com/standards/qtff-2001.pdf. // See https://developer.apple.com/standards/qtff-2001.pdf.
foundGoodFileType = true; foundGoodFileType = true;
@ -180,7 +181,7 @@ import java.io.IOException;
int atomDataSize = (int) (atomSize - headerSize); int atomDataSize = (int) (atomSize - headerSize);
bytesSearched += atomDataSize; bytesSearched += atomDataSize;
if (atomType == Atom.TYPE_ftyp) { if (atomType == Mp4Box.TYPE_ftyp) {
// Parse the atom and check the file type/brand is compatible with the extractors. // Parse the atom and check the file type/brand is compatible with the extractors.
if (atomDataSize < 8) { if (atomDataSize < 8) {
return new AtomSizeTooSmallSniffFailure(atomType, atomDataSize, 8); return new AtomSizeTooSmallSniffFailure(atomType, atomDataSize, 8);

View File

@ -21,6 +21,7 @@ import androidx.media3.common.C;
import androidx.media3.common.ParserException; import androidx.media3.common.ParserException;
import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.container.Mp4Box;
import androidx.test.ext.junit.runners.AndroidJUnit4; 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;
@ -254,17 +255,18 @@ public final class AtomParsersTest {
@Test @Test
public void stz2Parsing4BitFieldSize() { public void stz2Parsing4BitFieldSize() {
verifyStz2Parsing(new Atom.LeafAtom(Atom.TYPE_stsz, new ParsableByteArray(FOUR_BIT_STZ2))); verifyStz2Parsing(new Mp4Box.LeafBox(Mp4Box.TYPE_stsz, new ParsableByteArray(FOUR_BIT_STZ2)));
} }
@Test @Test
public void stz2Parsing8BitFieldSize() { public void stz2Parsing8BitFieldSize() {
verifyStz2Parsing(new Atom.LeafAtom(Atom.TYPE_stsz, new ParsableByteArray(EIGHT_BIT_STZ2))); verifyStz2Parsing(new Mp4Box.LeafBox(Mp4Box.TYPE_stsz, new ParsableByteArray(EIGHT_BIT_STZ2)));
} }
@Test @Test
public void stz2Parsing16BitFieldSize() { public void stz2Parsing16BitFieldSize() {
verifyStz2Parsing(new Atom.LeafAtom(Atom.TYPE_stsz, new ParsableByteArray(SIXTEEN_BIT_STZ2))); verifyStz2Parsing(
new Mp4Box.LeafBox(Mp4Box.TYPE_stsz, new ParsableByteArray(SIXTEEN_BIT_STZ2)));
} }
@Test @Test
@ -286,7 +288,7 @@ public final class AtomParsersTest {
assertThat(vexuData.hasBothEyeViews()).isTrue(); assertThat(vexuData.hasBothEyeViews()).isTrue();
} }
private static void verifyStz2Parsing(Atom.LeafAtom stz2Atom) { private static void verifyStz2Parsing(Mp4Box.LeafBox stz2Atom) {
AtomParsers.Stz2SampleSizeBox box = new AtomParsers.Stz2SampleSizeBox(stz2Atom); AtomParsers.Stz2SampleSizeBox box = new AtomParsers.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);

View File

@ -16,7 +16,7 @@
package androidx.media3.extractor.mp4; 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.extractor.mp4.Atom.TYPE_pssh; import static androidx.media3.container.Mp4Box.TYPE_pssh;
import static androidx.media3.extractor.mp4.AtomParsers.parseFullAtomFlags; import static androidx.media3.extractor.mp4.AtomParsers.parseFullAtomFlags;
import static androidx.media3.extractor.mp4.AtomParsers.parseFullAtomVersion; import static androidx.media3.extractor.mp4.AtomParsers.parseFullAtomVersion;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;