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
* limitations under the License.
*/
package androidx.media3.extractor.mp4;
package androidx.media3.container;
import androidx.annotation.Nullable;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/** A representation of an MP4 box (aka atom). */
@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;
/** Size of a full atom header, in bytes. */
/** Size of a full box header, in bytes. */
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;
/** 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;
/** 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;
@SuppressWarnings("ConstantCaseForConstants")
@ -451,43 +454,44 @@ import java.util.List;
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;
}
@Override
public String toString() {
return getAtomTypeString(type);
return getBoxTypeString(type);
}
/** An MP4 atom that is a leaf. */
/* package */ static final class LeafAtom extends Atom {
/** An MP4 box that is a leaf. */
public static final class LeafBox extends Mp4Box {
/** The atom data. */
/** The box data. */
public final ParsableByteArray data;
/**
* @param type The type of the atom.
* @param data The atom data.
* @param type The type of the box.
* @param data The box data.
*/
public LeafAtom(int type, ParsableByteArray data) {
public LeafBox(int type, ParsableByteArray data) {
super(type);
this.data = data;
}
}
/** An MP4 atom that has child atoms. */
/* package */ static final class ContainerAtom extends Atom {
/** An MP4 box that has child boxes. */
public static final class ContainerBox extends Mp4Box {
public final long endPosition;
public final List<LeafAtom> leafChildren;
public final List<ContainerAtom> containerChildren;
public final List<LeafBox> leafChildren;
public final List<ContainerBox> containerChildren;
/**
* @param type The type of the atom.
* @param endPosition The position of the first byte after the end of the atom.
* @param type The type of the box.
* @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);
this.endPosition = endPosition;
leafChildren = new ArrayList<>();
@ -497,19 +501,19 @@ import java.util.List;
/**
* Adds a child leaf to this container.
*
* @param atom The child to add.
* @param box The child to add.
*/
public void add(LeafAtom atom) {
leafChildren.add(atom);
public void add(LeafBox box) {
leafChildren.add(box);
}
/**
* Adds a child container to this container.
*
* @param atom The child to add.
* @param box The child to add.
*/
public void add(ContainerAtom atom) {
containerChildren.add(atom);
public void add(ContainerBox box) {
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.
*/
@Nullable
public LeafAtom getLeafAtomOfType(int type) {
public LeafBox getLeafBoxOfType(int type) {
int childrenSize = leafChildren.size();
for (int i = 0; i < childrenSize; i++) {
LeafAtom atom = leafChildren.get(i);
if (atom.type == type) {
return atom;
LeafBox box = leafChildren.get(i);
if (box.type == type) {
return box;
}
}
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.
*/
@Nullable
public ContainerAtom getContainerAtomOfType(int type) {
public ContainerBox getContainerBoxOfType(int type) {
int childrenSize = containerChildren.size();
for (int i = 0; i < childrenSize; i++) {
ContainerAtom atom = containerChildren.get(i);
if (atom.type == type) {
return atom;
ContainerBox box = containerChildren.get(i);
if (box.type == type) {
return box;
}
}
return null;
@ -556,7 +560,7 @@ import java.util.List;
@Override
public String toString() {
return getAtomTypeString(type)
return getBoxTypeString(type)
+ " leaves: "
+ Arrays.toString(leafChildren.toArray())
+ " 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.
*/
public static String getAtomTypeString(int type) {
public static String getBoxTypeString(int type) {
return ""
+ (char) ((type >> 24) & 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.UnstableApi;
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.ReorderingSeiMessageQueue;
import androidx.media3.extractor.Ac4Util;
@ -54,8 +57,6 @@ import androidx.media3.extractor.SniffFailure;
import androidx.media3.extractor.TrackOutput;
import androidx.media3.extractor.metadata.emsg.EventMessage;
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.SubtitleTranscodingExtractorOutput;
import com.google.common.collect.ImmutableList;
@ -208,7 +209,7 @@ public class FragmentedMp4Extractor implements Extractor {
// Parser state.
private final ParsableByteArray atomHeader;
private final ArrayDeque<ContainerAtom> containerAtoms;
private final ArrayDeque<ContainerBox> containerAtoms;
private final ArrayDeque<MetadataSampleInfo> pendingMetadataSampleInfos;
private final ReorderingSeiMessageQueue reorderingSeiMessageQueue;
@Nullable private final TrackOutput additionalEmsgTrackOutput;
@ -402,7 +403,7 @@ public class FragmentedMp4Extractor implements Extractor {
this.closedCaptionFormats = Collections.unmodifiableList(closedCaptionFormats);
this.additionalEmsgTrackOutput = additionalEmsgTrackOutput;
eventMessageEncoder = new EventMessageEncoder();
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
atomHeader = new ParsableByteArray(Mp4Box.LONG_HEADER_SIZE);
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
nalPrefix = new ParsableByteArray(5);
nalBuffer = new ParsableByteArray();
@ -517,22 +518,22 @@ public class FragmentedMp4Extractor implements Extractor {
private boolean readAtomHeader(ExtractorInput input) throws IOException {
if (atomHeaderBytesRead == 0) {
// 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;
}
atomHeaderBytesRead = Atom.HEADER_SIZE;
atomHeaderBytesRead = Mp4Box.HEADER_SIZE;
atomHeader.setPosition(0);
atomSize = atomHeader.readUnsignedInt();
atomType = atomHeader.readInt();
}
if (atomSize == Atom.DEFINES_LARGE_SIZE) {
if (atomSize == Mp4Box.DEFINES_LARGE_SIZE) {
// Read the large size.
int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE;
input.readFully(atomHeader.getData(), Atom.HEADER_SIZE, headerBytesRemaining);
int headerBytesRemaining = Mp4Box.LONG_HEADER_SIZE - Mp4Box.HEADER_SIZE;
input.readFully(atomHeader.getData(), Mp4Box.HEADER_SIZE, headerBytesRemaining);
atomHeaderBytesRead += headerBytesRemaining;
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
// work out its size even if the input length is unknown.
long endPosition = input.getLength();
@ -550,7 +551,7 @@ public class FragmentedMp4Extractor implements Extractor {
}
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) {
// This must be the first moof or mdat in the stream.
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.
int trackCount = trackBundles.size();
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;
endOfMdatPosition = atomPosition + atomSize;
parserState = STATE_READING_ENCRYPTION_DATA;
@ -577,8 +578,8 @@ public class FragmentedMp4Extractor implements Extractor {
}
if (shouldParseContainerAtom(atomType)) {
long endPosition = input.getPosition() + atomSize - Atom.HEADER_SIZE;
containerAtoms.push(new ContainerAtom(atomType, endPosition));
long endPosition = input.getPosition() + atomSize - Mp4Box.HEADER_SIZE;
containerAtoms.push(new ContainerBox(atomType, endPosition));
if (atomSize == atomHeaderBytesRead) {
processAtomEnded(endPosition);
} else {
@ -586,7 +587,7 @@ public class FragmentedMp4Extractor implements Extractor {
enterReadingAtomHeaderState();
}
} else if (shouldParseLeafAtom(atomType)) {
if (atomHeaderBytesRead != Atom.HEADER_SIZE) {
if (atomHeaderBytesRead != Mp4Box.HEADER_SIZE) {
throw ParserException.createForUnsupportedContainerFeature(
"Leaf atom defines extended atom size (unsupported).");
}
@ -595,7 +596,7 @@ public class FragmentedMp4Extractor implements Extractor {
"Leaf atom with length > 2147483647 (unsupported).");
}
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;
parserState = STATE_READING_ATOM_PAYLOAD;
} else {
@ -614,8 +615,8 @@ public class FragmentedMp4Extractor implements Extractor {
int atomPayloadSize = (int) atomSize - atomHeaderBytesRead;
@Nullable ParsableByteArray atomData = this.atomData;
if (atomData != null) {
input.readFully(atomData.getData(), Atom.HEADER_SIZE, atomPayloadSize);
onLeafAtomRead(new LeafAtom(atomType, atomData), input.getPosition());
input.readFully(atomData.getData(), Mp4Box.HEADER_SIZE, atomPayloadSize);
onLeafAtomRead(new LeafBox(atomType, atomData), input.getPosition());
} else {
input.skipFully(atomPayloadSize);
}
@ -629,45 +630,45 @@ public class FragmentedMp4Extractor implements Extractor {
enterReadingAtomHeaderState();
}
private void onLeafAtomRead(LeafAtom leaf, long inputPosition) throws ParserException {
private void onLeafAtomRead(LeafBox leaf, long inputPosition) throws ParserException {
if (!containerAtoms.isEmpty()) {
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);
segmentIndexEarliestPresentationTimeUs = result.first;
extractorOutput.seekMap(result.second);
haveOutputSeekMap = true;
} else if (leaf.type == Atom.TYPE_emsg) {
} else if (leaf.type == Mp4Box.TYPE_emsg) {
onEmsgLeafAtomRead(leaf.data);
}
}
private void onContainerAtomRead(ContainerAtom container) throws ParserException {
if (container.type == Atom.TYPE_moov) {
private void onContainerAtomRead(ContainerBox container) throws ParserException {
if (container.type == Mp4Box.TYPE_moov) {
onMoovContainerAtomRead(container);
} else if (container.type == Atom.TYPE_moof) {
} else if (container.type == Mp4Box.TYPE_moof) {
onMoofContainerAtomRead(container);
} else if (!containerAtoms.isEmpty()) {
containerAtoms.peek().add(container);
}
}
private void onMoovContainerAtomRead(ContainerAtom moov) throws ParserException {
private void onMoovContainerAtomRead(ContainerBox moov) throws ParserException {
checkState(sideloadedTrack == null, "Unexpected moov box.");
@Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moov.leafChildren);
// 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<>();
long duration = C.TIME_UNSET;
int mvexChildrenSize = mvex.leafChildren.size();
for (int i = 0; i < mvexChildrenSize; i++) {
Atom.LeafAtom atom = mvex.leafChildren.get(i);
if (atom.type == Atom.TYPE_trex) {
LeafBox atom = mvex.leafChildren.get(i);
if (atom.type == Mp4Box.TYPE_trex) {
Pair<Integer, DefaultSampleValues> trexData = parseTrex(atom.data);
defaultSampleValuesArray.put(trexData.first, trexData.second);
} else if (atom.type == Atom.TYPE_mehd) {
} else if (atom.type == Mp4Box.TYPE_mehd) {
duration = parseMehd(atom.data);
}
}
@ -725,7 +726,7 @@ public class FragmentedMp4Extractor implements Extractor {
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);
@Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moof.leafChildren);
@ -775,7 +776,7 @@ public class FragmentedMp4Extractor implements Extractor {
if (emsgTrackOutputs.length == 0) {
return;
}
atom.setPosition(Atom.HEADER_SIZE);
atom.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = atom.readInt();
int version = AtomParsers.parseFullAtomVersion(fullAtom);
String schemeIdUri;
@ -863,7 +864,7 @@ public class FragmentedMp4Extractor implements Extractor {
/** Parses a trex atom (defined in 14496-12). */
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 defaultSampleDescriptionIndex = trex.readInt() - 1;
int defaultSampleDuration = trex.readInt();
@ -881,14 +882,14 @@ public class FragmentedMp4Extractor implements Extractor {
/** Parses an mehd atom (defined in 14496-12). */
private static long parseMehd(ParsableByteArray mehd) {
mehd.setPosition(Atom.HEADER_SIZE);
mehd.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = mehd.readInt();
int version = AtomParsers.parseFullAtomVersion(fullAtom);
return version == 0 ? mehd.readUnsignedInt() : mehd.readUnsignedLongToLong();
}
private static void parseMoof(
ContainerAtom moof,
ContainerBox moof,
SparseArray<TrackBundle> trackBundles,
boolean haveSideloadedTrack,
@Flags int flags,
@ -896,9 +897,9 @@ public class FragmentedMp4Extractor implements Extractor {
throws ParserException {
int moofContainerChildrenSize = moof.containerChildren.size();
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.
if (child.type == Atom.TYPE_traf) {
if (child.type == Mp4Box.TYPE_traf) {
parseTraf(child, trackBundles, haveSideloadedTrack, flags, extendedTypeScratch);
}
}
@ -906,13 +907,13 @@ public class FragmentedMp4Extractor implements Extractor {
/** Parses a traf atom (defined in 14496-12). */
private static void parseTraf(
ContainerAtom traf,
ContainerBox traf,
SparseArray<TrackBundle> trackBundles,
boolean haveSideloadedTrack,
@Flags int flags,
byte[] extendedTypeScratch)
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);
if (trackBundle == null) {
return;
@ -923,7 +924,7 @@ public class FragmentedMp4Extractor implements Extractor {
boolean fragmentDecodeTimeIncludesMoov = fragment.nextFragmentDecodeTimeIncludesMoov;
trackBundle.resetFragmentInfo();
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) {
fragment.nextFragmentDecodeTime = parseTfdt(tfdtAtom.data);
fragment.nextFragmentDecodeTimeIncludesMoov = true;
@ -939,17 +940,17 @@ public class FragmentedMp4Extractor implements Extractor {
trackBundle.moovSampleTable.track.getSampleDescriptionEncryptionBox(
checkNotNull(fragment.header).sampleDescriptionIndex);
@Nullable LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz);
@Nullable LeafBox saiz = traf.getLeafBoxOfType(Mp4Box.TYPE_saiz);
if (saiz != null) {
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) {
parseSaio(saio.data, fragment);
}
@Nullable LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc);
@Nullable LeafBox senc = traf.getLeafBoxOfType(Mp4Box.TYPE_senc);
if (senc != null) {
parseSenc(senc.data, fragment);
}
@ -958,24 +959,24 @@ public class FragmentedMp4Extractor implements Extractor {
int leafChildrenSize = traf.leafChildren.size();
for (int i = 0; i < leafChildrenSize; i++) {
LeafAtom atom = traf.leafChildren.get(i);
if (atom.type == Atom.TYPE_uuid) {
LeafBox atom = traf.leafChildren.get(i);
if (atom.type == Mp4Box.TYPE_uuid) {
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 {
int trunCount = 0;
int totalSampleCount = 0;
List<LeafAtom> leafChildren = traf.leafChildren;
List<LeafBox> leafChildren = traf.leafChildren;
int leafChildrenSize = leafChildren.size();
for (int i = 0; i < leafChildrenSize; i++) {
LeafAtom atom = leafChildren.get(i);
if (atom.type == Atom.TYPE_trun) {
LeafBox atom = leafChildren.get(i);
if (atom.type == Mp4Box.TYPE_trun) {
ParsableByteArray trunData = atom.data;
trunData.setPosition(Atom.FULL_HEADER_SIZE);
trunData.setPosition(Mp4Box.FULL_HEADER_SIZE);
int trunSampleCount = trunData.readUnsignedIntToInt();
if (trunSampleCount > 0) {
totalSampleCount += trunSampleCount;
@ -991,8 +992,8 @@ public class FragmentedMp4Extractor implements Extractor {
int trunIndex = 0;
int trunStartPosition = 0;
for (int i = 0; i < leafChildrenSize; i++) {
LeafAtom trun = leafChildren.get(i);
if (trun.type == Atom.TYPE_trun) {
LeafBox trun = leafChildren.get(i);
if (trun.type == Mp4Box.TYPE_trun) {
trunStartPosition =
parseTrun(trackBundle, trunIndex++, flags, trun.data, trunStartPosition);
}
@ -1003,7 +1004,7 @@ public class FragmentedMp4Extractor implements Extractor {
TrackEncryptionBox encryptionBox, ParsableByteArray saiz, TrackFragment out)
throws ParserException {
int vectorSize = encryptionBox.perSampleIvSize;
saiz.setPosition(Atom.HEADER_SIZE);
saiz.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = saiz.readInt();
int flags = AtomParsers.parseFullAtomFlags(fullAtom);
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.
*/
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 flags = AtomParsers.parseFullAtomFlags(fullAtom);
if ((flags & 0x01) == 1) {
@ -1081,7 +1082,7 @@ public class FragmentedMp4Extractor implements Extractor {
@Nullable
private static TrackBundle parseTfhd(
ParsableByteArray tfhd, SparseArray<TrackBundle> trackBundles, boolean haveSideloadedTrack) {
tfhd.setPosition(Atom.HEADER_SIZE);
tfhd.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = tfhd.readInt();
int atomFlags = AtomParsers.parseFullAtomFlags(fullAtom);
int trackId = tfhd.readInt();
@ -1130,7 +1131,7 @@ public class FragmentedMp4Extractor implements Extractor {
* media, expressed in the media's timescale.
*/
private static long parseTfdt(ParsableByteArray tfdt) {
tfdt.setPosition(Atom.HEADER_SIZE);
tfdt.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = tfdt.readInt();
int version = AtomParsers.parseFullAtomVersion(fullAtom);
return version == 1 ? tfdt.readUnsignedLongToLong() : tfdt.readUnsignedInt();
@ -1173,7 +1174,7 @@ public class FragmentedMp4Extractor implements Extractor {
ParsableByteArray trun,
int trackRunStart)
throws ParserException {
trun.setPosition(Atom.HEADER_SIZE);
trun.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = trun.readInt();
int atomFlags = AtomParsers.parseFullAtomFlags(fullAtom);
@ -1264,7 +1265,7 @@ public class FragmentedMp4Extractor implements Extractor {
private static void parseUuid(
ParsableByteArray uuid, TrackFragment out, byte[] extendedTypeScratch)
throws ParserException {
uuid.setPosition(Atom.HEADER_SIZE);
uuid.setPosition(Mp4Box.HEADER_SIZE);
uuid.readBytes(extendedTypeScratch, 0, 16);
// 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)
throws ParserException {
senc.setPosition(Atom.HEADER_SIZE + offset);
senc.setPosition(Mp4Box.HEADER_SIZE + offset);
int fullAtom = senc.readInt();
int flags = AtomParsers.parseFullAtomFlags(fullAtom);
@ -1315,20 +1316,20 @@ public class FragmentedMp4Extractor implements Extractor {
}
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.
@Nullable ParsableByteArray sbgp = null;
@Nullable ParsableByteArray sgpd = null;
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;
if (leafAtom.type == Atom.TYPE_sbgp) {
leafAtomData.setPosition(Atom.FULL_HEADER_SIZE);
if (leafAtom.type == Mp4Box.TYPE_sbgp) {
leafAtomData.setPosition(Mp4Box.FULL_HEADER_SIZE);
if (leafAtomData.readInt() == SAMPLE_GROUP_TYPE_seig) {
sbgp = leafAtomData;
}
} else if (leafAtom.type == Atom.TYPE_sgpd) {
leafAtomData.setPosition(Atom.FULL_HEADER_SIZE);
} else if (leafAtom.type == Mp4Box.TYPE_sgpd) {
leafAtomData.setPosition(Mp4Box.FULL_HEADER_SIZE);
if (leafAtomData.readInt() == SAMPLE_GROUP_TYPE_seig) {
sgpd = leafAtomData;
}
@ -1338,7 +1339,7 @@ public class FragmentedMp4Extractor implements Extractor {
return;
}
sbgp.setPosition(Atom.HEADER_SIZE);
sbgp.setPosition(Mp4Box.HEADER_SIZE);
int sbgpVersion = AtomParsers.parseFullAtomVersion(sbgp.readInt());
sbgp.skipBytes(4); // grouping_type == seig.
if (sbgpVersion == 1) {
@ -1349,7 +1350,7 @@ public class FragmentedMp4Extractor implements Extractor {
"Entry count in sbgp != 1 (unsupported).");
}
sgpd.setPosition(Atom.HEADER_SIZE);
sgpd.setPosition(Mp4Box.HEADER_SIZE);
int sgpdVersion = AtomParsers.parseFullAtomVersion(sgpd.readInt());
sgpd.skipBytes(4); // grouping_type == seig.
if (sgpdVersion == 1) {
@ -1405,7 +1406,7 @@ public class FragmentedMp4Extractor implements Extractor {
*/
private static Pair<Long, ChunkIndex> parseSidx(ParsableByteArray atom, long inputPosition)
throws ParserException {
atom.setPosition(Atom.HEADER_SIZE);
atom.setPosition(Mp4Box.HEADER_SIZE);
int fullAtom = atom.readInt();
int version = AtomParsers.parseFullAtomVersion(fullAtom);
@ -1551,8 +1552,8 @@ public class FragmentedMp4Extractor implements Extractor {
if (trackBundle.moovSampleTable.track.sampleTransformation
== Track.TRANSFORMATION_CEA608_CDAT) {
sampleSize -= Atom.HEADER_SIZE;
input.skipFully(Atom.HEADER_SIZE);
sampleSize -= Mp4Box.HEADER_SIZE;
input.skipFully(Mp4Box.HEADER_SIZE);
}
if (MimeTypes.AUDIO_AC4.equals(trackBundle.moovSampleTable.track.format.sampleMimeType)) {
@ -1742,12 +1743,12 @@ public class FragmentedMp4Extractor implements Extractor {
/** Returns DrmInitData from leaf atoms. */
@Nullable
private static DrmInitData getDrmInitDataFromAtoms(List<Atom.LeafAtom> leafChildren) {
private static DrmInitData getDrmInitDataFromAtoms(List<LeafBox> leafChildren) {
@Nullable ArrayList<SchemeData> schemeDatas = null;
int leafChildrenSize = leafChildren.size();
for (int i = 0; i < leafChildrenSize; i++) {
LeafAtom child = leafChildren.get(i);
if (child.type == Atom.TYPE_pssh) {
LeafBox child = leafChildren.get(i);
if (child.type == Mp4Box.TYPE_pssh) {
if (schemeDatas == null) {
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}. */
private static boolean shouldParseLeafAtom(int atom) {
return atom == Atom.TYPE_hdlr
|| atom == Atom.TYPE_mdhd
|| atom == Atom.TYPE_mvhd
|| atom == Atom.TYPE_sidx
|| atom == Atom.TYPE_stsd
|| atom == Atom.TYPE_stts
|| atom == Atom.TYPE_ctts
|| atom == Atom.TYPE_stsc
|| atom == Atom.TYPE_stsz
|| atom == Atom.TYPE_stz2
|| atom == Atom.TYPE_stco
|| atom == Atom.TYPE_co64
|| atom == Atom.TYPE_stss
|| atom == Atom.TYPE_tfdt
|| atom == Atom.TYPE_tfhd
|| atom == Atom.TYPE_tkhd
|| atom == Atom.TYPE_trex
|| atom == Atom.TYPE_trun
|| atom == Atom.TYPE_pssh
|| atom == Atom.TYPE_saiz
|| atom == Atom.TYPE_saio
|| atom == Atom.TYPE_senc
|| atom == Atom.TYPE_uuid
|| atom == Atom.TYPE_sbgp
|| atom == Atom.TYPE_sgpd
|| atom == Atom.TYPE_elst
|| atom == Atom.TYPE_mehd
|| atom == Atom.TYPE_emsg;
return atom == Mp4Box.TYPE_hdlr
|| atom == Mp4Box.TYPE_mdhd
|| atom == Mp4Box.TYPE_mvhd
|| atom == Mp4Box.TYPE_sidx
|| atom == Mp4Box.TYPE_stsd
|| atom == Mp4Box.TYPE_stts
|| atom == Mp4Box.TYPE_ctts
|| atom == Mp4Box.TYPE_stsc
|| atom == Mp4Box.TYPE_stsz
|| atom == Mp4Box.TYPE_stz2
|| atom == Mp4Box.TYPE_stco
|| atom == Mp4Box.TYPE_co64
|| atom == Mp4Box.TYPE_stss
|| atom == Mp4Box.TYPE_tfdt
|| atom == Mp4Box.TYPE_tfhd
|| atom == Mp4Box.TYPE_tkhd
|| atom == Mp4Box.TYPE_trex
|| atom == Mp4Box.TYPE_trun
|| atom == Mp4Box.TYPE_pssh
|| atom == Mp4Box.TYPE_saiz
|| atom == Mp4Box.TYPE_saio
|| atom == Mp4Box.TYPE_senc
|| atom == Mp4Box.TYPE_uuid
|| atom == Mp4Box.TYPE_sbgp
|| atom == Mp4Box.TYPE_sgpd
|| atom == Mp4Box.TYPE_elst
|| atom == Mp4Box.TYPE_mehd
|| atom == Mp4Box.TYPE_emsg;
}
/** Returns whether the extractor should decode a container atom with type {@code atom}. */
private static boolean shouldParseContainerAtom(int atom) {
return atom == Atom.TYPE_moov
|| atom == Atom.TYPE_trak
|| atom == Atom.TYPE_mdia
|| atom == Atom.TYPE_minf
|| atom == Atom.TYPE_stbl
|| atom == Atom.TYPE_moof
|| atom == Atom.TYPE_traf
|| atom == Atom.TYPE_mvex
|| atom == Atom.TYPE_edts;
return atom == Mp4Box.TYPE_moov
|| atom == Mp4Box.TYPE_trak
|| atom == Mp4Box.TYPE_mdia
|| atom == Mp4Box.TYPE_minf
|| atom == Mp4Box.TYPE_stbl
|| atom == Mp4Box.TYPE_moof
|| atom == Mp4Box.TYPE_traf
|| atom == Mp4Box.TYPE_mvex
|| atom == Mp4Box.TYPE_edts;
}
/** 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.ParsableByteArray;
import androidx.media3.container.MdtaMetadataEntry;
import androidx.media3.container.Mp4Box;
import androidx.media3.extractor.GaplessInfoHolder;
import androidx.media3.extractor.metadata.id3.ApicFrame;
import androidx.media3.extractor.metadata.id3.CommentFrame;
@ -203,7 +204,7 @@ import com.google.common.collect.ImmutableList;
} else if (type == TYPE_INTERNAL) {
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;
} finally {
ilst.setPosition(endPosition);
@ -225,7 +226,7 @@ import com.google.common.collect.ImmutableList;
while ((atomPosition = ilst.getPosition()) < endPosition) {
int atomSize = ilst.readInt();
int atomType = ilst.readInt();
if (atomType == Atom.TYPE_data) {
if (atomType == Mp4Box.TYPE_data) {
int typeIndicator = ilst.readInt();
int localeIndicator = ilst.readInt();
int dataSize = atomSize - 16;
@ -264,12 +265,12 @@ import com.google.common.collect.ImmutableList;
int type, String id, ParsableByteArray data) {
int atomSize = 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)
String value = data.readNullTerminatedString(atomSize - 16);
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;
}
@ -277,12 +278,12 @@ import com.google.common.collect.ImmutableList;
private static CommentFrame parseCommentAttribute(int type, ParsableByteArray data) {
int atomSize = 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)
String value = data.readNullTerminatedString(atomSize - 16);
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;
}
@ -303,14 +304,14 @@ import com.google.common.collect.ImmutableList;
id, /* description= */ null, ImmutableList.of(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;
}
private static int parseIntegerAttribute(ParsableByteArray data) {
int atomSize = 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)
switch (atomSize - 16) {
case 1:
@ -334,7 +335,7 @@ import com.google.common.collect.ImmutableList;
int type, String attributeName, ParsableByteArray data) {
int atomSize = 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)
int index = data.readUnsignedShort();
if (index > 0) {
@ -347,7 +348,7 @@ import com.google.common.collect.ImmutableList;
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;
}
@ -369,7 +370,7 @@ import com.google.common.collect.ImmutableList;
private static ApicFrame parseCoverArt(ParsableByteArray data) {
int atomSize = data.readInt();
int atomType = data.readInt();
if (atomType == Atom.TYPE_data) {
if (atomType == Mp4Box.TYPE_data) {
int fullVersionInt = data.readInt();
int flags = AtomParsers.parseFullAtomFlags(fullVersionInt);
@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 atomType = data.readInt();
data.skipBytes(4); // version (1), flags (3)
if (atomType == Atom.TYPE_mean) {
if (atomType == Mp4Box.TYPE_mean) {
domain = data.readNullTerminatedString(atomSize - 12);
} else if (atomType == Atom.TYPE_name) {
} else if (atomType == Mp4Box.TYPE_name) {
name = data.readNullTerminatedString(atomSize - 12);
} else {
if (atomType == Atom.TYPE_data) {
if (atomType == Mp4Box.TYPE_data) {
dataAtomPosition = atomPosition;
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.UnstableApi;
import androidx.media3.container.MdtaMetadataEntry;
import androidx.media3.container.Mp4Box;
import androidx.media3.container.Mp4Box.ContainerBox;
import androidx.media3.container.NalUnitUtil;
import androidx.media3.extractor.Ac3Util;
import androidx.media3.extractor.Ac4Util;
@ -63,7 +65,6 @@ import androidx.media3.extractor.TrackOutput;
import androidx.media3.extractor.TrueHdSampleRechunker;
import androidx.media3.extractor.metadata.mp4.MotionPhotoMetadata;
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.SubtitleTranscodingExtractorOutput;
import com.google.common.collect.ImmutableList;
@ -229,7 +230,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
private final ParsableByteArray scratch;
private final ParsableByteArray atomHeader;
private final ArrayDeque<ContainerAtom> containerAtoms;
private final ArrayDeque<ContainerBox> containerAtoms;
private final SefReader sefReader;
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;
sefReader = new SefReader();
slowMotionMetadataEntries = new ArrayList<>();
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
atomHeader = new ParsableByteArray(Mp4Box.LONG_HEADER_SIZE);
containerAtoms = new ArrayDeque<>();
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
nalPrefix = new ParsableByteArray(5);
@ -493,28 +494,28 @@ public final class Mp4Extractor implements Extractor, SeekMap {
private boolean readAtomHeader(ExtractorInput input) throws IOException {
if (atomHeaderBytesRead == 0) {
// 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();
return false;
}
atomHeaderBytesRead = Atom.HEADER_SIZE;
atomHeaderBytesRead = Mp4Box.HEADER_SIZE;
atomHeader.setPosition(0);
atomSize = atomHeader.readUnsignedInt();
atomType = atomHeader.readInt();
}
if (atomSize == Atom.DEFINES_LARGE_SIZE) {
if (atomSize == Mp4Box.DEFINES_LARGE_SIZE) {
// Read the large size.
int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE;
input.readFully(atomHeader.getData(), Atom.HEADER_SIZE, headerBytesRemaining);
int headerBytesRemaining = Mp4Box.LONG_HEADER_SIZE - Mp4Box.HEADER_SIZE;
input.readFully(atomHeader.getData(), Mp4Box.HEADER_SIZE, headerBytesRemaining);
atomHeaderBytesRead += headerBytesRemaining;
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
// work out its size even if the input length is unknown.
long endPosition = input.getLength();
if (endPosition == C.LENGTH_UNSET) {
@Nullable ContainerAtom containerAtom = containerAtoms.peek();
@Nullable ContainerBox containerAtom = containerAtoms.peek();
if (containerAtom != null) {
endPosition = containerAtom.endPosition;
}
@ -531,10 +532,10 @@ public final class Mp4Extractor implements Extractor, SeekMap {
if (shouldParseContainerAtom(atomType)) {
long endPosition = input.getPosition() + atomSize - atomHeaderBytesRead;
if (atomSize != atomHeaderBytesRead && atomType == Atom.TYPE_meta) {
if (atomSize != atomHeaderBytesRead && atomType == Mp4Box.TYPE_meta) {
maybeSkipRemainingMetaAtomHeaderBytes(input);
}
containerAtoms.push(new ContainerAtom(atomType, endPosition));
containerAtoms.push(new ContainerBox(atomType, endPosition));
if (atomSize == atomHeaderBytesRead) {
processAtomEnded(endPosition);
} else {
@ -544,10 +545,10 @@ public final class Mp4Extractor implements Extractor, SeekMap {
} else if (shouldParseLeafAtom(atomType)) {
// We don't support parsing of leaf atoms that define extended atom sizes, or that have
// lengths greater than Integer.MAX_VALUE.
Assertions.checkState(atomHeaderBytesRead == Atom.HEADER_SIZE);
Assertions.checkState(atomHeaderBytesRead == Mp4Box.HEADER_SIZE);
Assertions.checkState(atomSize <= Integer.MAX_VALUE);
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;
parserState = STATE_READING_ATOM_PAYLOAD;
} else {
@ -573,14 +574,14 @@ public final class Mp4Extractor implements Extractor, SeekMap {
@Nullable ParsableByteArray atomData = this.atomData;
if (atomData != null) {
input.readFully(atomData.getData(), atomHeaderBytesRead, (int) atomPayloadSize);
if (atomType == Atom.TYPE_ftyp) {
if (atomType == Mp4Box.TYPE_ftyp) {
seenFtypAtom = true;
fileType = processFtypAtom(atomData);
} else if (!containerAtoms.isEmpty()) {
containerAtoms.peek().add(new Atom.LeafAtom(atomType, atomData));
containerAtoms.peek().add(new Mp4Box.LeafBox(atomType, atomData));
}
} 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.
// See https://developer.apple.com/standards/qtff-2001.pdf.
fileType = FILE_TYPE_QUICKTIME;
@ -614,8 +615,8 @@ public final class Mp4Extractor implements Extractor, SeekMap {
private void processAtomEnded(long atomEndPosition) throws ParserException {
while (!containerAtoms.isEmpty() && containerAtoms.peek().endPosition == atomEndPosition) {
Atom.ContainerAtom containerAtom = containerAtoms.pop();
if (containerAtom.type == Atom.TYPE_moov) {
ContainerBox containerAtom = containerAtoms.pop();
if (containerAtom.type == Mp4Box.TYPE_moov) {
// We've reached the end of the moov atom. Process it and prepare to read samples.
processMoovAtom(containerAtom);
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.
*/
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.
@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 =
new ArrayList<>();
if (meta != null) {
@ -654,7 +655,6 @@ public final class Mp4Extractor implements Extractor, SeekMap {
return;
}
}
int firstVideoTrackIndex = C.INDEX_UNSET;
long durationUs = C.TIME_UNSET;
List<Mp4Track> tracks = new ArrayList<>();
@ -663,7 +663,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
boolean isQuickTime = fileType == FILE_TYPE_QUICKTIME;
GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder();
@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) {
udtaMetadata = AtomParsers.parseUdta(udta);
gaplessInfoHolder.setFromMetadata(udtaMetadata);
@ -671,7 +671,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
Metadata mvhdMetadata =
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;
List<TrackSampleTable> trackSampleTables =
@ -863,8 +863,8 @@ public final class Mp4Extractor implements Extractor, SeekMap {
if (track.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) {
// The sample information is contained in a cdat atom. The header must be discarded for
// committing.
skipAmount += Atom.HEADER_SIZE;
sampleSize -= Atom.HEADER_SIZE;
skipAmount += Mp4Box.HEADER_SIZE;
sampleSize -= Mp4Box.HEADER_SIZE;
}
input.skipFully((int) skipAmount);
// 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. */
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
// V1.1.
motionPhotoMetadata =
@ -1148,7 +1148,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
* @return The {@link FileType}.
*/
private static @FileType int processFtypAtom(ParsableByteArray atomData) {
atomData.setPosition(Atom.HEADER_SIZE);
atomData.setPosition(Mp4Box.HEADER_SIZE);
int majorBrand = atomData.readInt();
@FileType int fileType = brandToFileType(majorBrand);
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}. */
private static boolean shouldParseLeafAtom(int atom) {
return atom == Atom.TYPE_mdhd
|| atom == Atom.TYPE_mvhd
|| atom == Atom.TYPE_hdlr
|| atom == Atom.TYPE_stsd
|| atom == Atom.TYPE_stts
|| atom == Atom.TYPE_stss
|| atom == Atom.TYPE_ctts
|| atom == Atom.TYPE_elst
|| atom == Atom.TYPE_stsc
|| atom == Atom.TYPE_stsz
|| atom == Atom.TYPE_stz2
|| atom == Atom.TYPE_stco
|| atom == Atom.TYPE_co64
|| atom == Atom.TYPE_tkhd
|| atom == Atom.TYPE_ftyp
|| atom == Atom.TYPE_udta
|| atom == Atom.TYPE_keys
|| atom == Atom.TYPE_ilst;
return atom == Mp4Box.TYPE_mdhd
|| atom == Mp4Box.TYPE_mvhd
|| atom == Mp4Box.TYPE_hdlr
|| atom == Mp4Box.TYPE_stsd
|| atom == Mp4Box.TYPE_stts
|| atom == Mp4Box.TYPE_stss
|| atom == Mp4Box.TYPE_ctts
|| atom == Mp4Box.TYPE_elst
|| atom == Mp4Box.TYPE_stsc
|| atom == Mp4Box.TYPE_stsz
|| atom == Mp4Box.TYPE_stz2
|| atom == Mp4Box.TYPE_stco
|| atom == Mp4Box.TYPE_co64
|| atom == Mp4Box.TYPE_tkhd
|| atom == Mp4Box.TYPE_ftyp
|| atom == Mp4Box.TYPE_udta
|| atom == Mp4Box.TYPE_keys
|| atom == Mp4Box.TYPE_ilst;
}
/** Returns whether the extractor should decode a container atom with type {@code atom}. */
private static boolean shouldParseContainerAtom(int atom) {
return atom == Atom.TYPE_moov
|| atom == Atom.TYPE_trak
|| atom == Atom.TYPE_mdia
|| atom == Atom.TYPE_minf
|| atom == Atom.TYPE_stbl
|| atom == Atom.TYPE_edts
|| atom == Atom.TYPE_meta
|| atom == Atom.TYPE_edvd;
return atom == Mp4Box.TYPE_moov
|| atom == Mp4Box.TYPE_trak
|| atom == Mp4Box.TYPE_mdia
|| atom == Mp4Box.TYPE_minf
|| atom == Mp4Box.TYPE_stbl
|| atom == Mp4Box.TYPE_edts
|| atom == Mp4Box.TYPE_meta
|| atom == Mp4Box.TYPE_edvd;
}
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.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.container.Mp4Box;
import java.nio.ByteBuffer;
import java.util.UUID;
@ -52,13 +53,13 @@ public final class PsshAtomUtil {
public static byte[] buildPsshAtom(
UUID systemId, @Nullable UUID[] keyIds, @Nullable byte[] data) {
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) {
psshBoxLength += 4 /* KID_count */ + (keyIds.length * 16) /* KIDs */;
}
ByteBuffer psshBox = ByteBuffer.allocate(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.putLong(systemId.getMostSignificantBits());
psshBox.putLong(systemId.getLeastSignificantBits());
@ -157,7 +158,7 @@ public final class PsshAtomUtil {
@Nullable
public static PsshAtom parsePsshAtom(byte[] 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.
return null;
}
@ -171,7 +172,7 @@ public final class PsshAtomUtil {
return null;
}
int atomType = atomData.readInt();
if (atomType != Atom.TYPE_pssh) {
if (atomType != Mp4Box.TYPE_pssh) {
Log.w(TAG, "Atom type is not pssh: " + atomType);
return null;
}

View File

@ -23,6 +23,7 @@ import androidx.media3.common.C;
import androidx.media3.common.Metadata;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.container.Mp4Box;
import androidx.media3.extractor.metadata.mp4.SmtaMetadataEntry;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@ -70,12 +71,12 @@ public final class SmtaAtomUtil {
/** Parses metadata from a Samsung smta atom. */
@Nullable
public static Metadata parseSmta(ParsableByteArray smta, int limit) {
smta.skipBytes(Atom.FULL_HEADER_SIZE);
smta.skipBytes(Mp4Box.FULL_HEADER_SIZE);
while (smta.getPosition() < limit) {
int atomPosition = smta.getPosition();
int atomSize = 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).
if (atomSize < 16) {
return null;
@ -126,13 +127,13 @@ public final class SmtaAtomUtil {
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;
}
int atomSize = 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;
}
// Capture frame rate is in Q16 format.

View File

@ -18,6 +18,7 @@ package androidx.media3.extractor.mp4;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.container.Mp4Box;
import androidx.media3.extractor.ExtractorInput;
import androidx.media3.extractor.SniffFailure;
import java.io.IOException;
@ -118,7 +119,7 @@ import java.io.IOException;
boolean isFragmented = false;
while (bytesSearched < bytesToSearch) {
// Read an atom header.
int headerSize = Atom.HEADER_SIZE;
int headerSize = Mp4Box.HEADER_SIZE;
buffer.reset(headerSize);
boolean success =
input.peekFully(buffer.getData(), 0, headerSize, /* allowEndOfInput= */ true);
@ -128,14 +129,14 @@ import java.io.IOException;
}
long atomSize = buffer.readUnsignedInt();
int atomType = buffer.readInt();
if (atomSize == Atom.DEFINES_LARGE_SIZE) {
if (atomSize == Mp4Box.DEFINES_LARGE_SIZE) {
// Read the large atom size.
headerSize = Atom.LONG_HEADER_SIZE;
headerSize = Mp4Box.LONG_HEADER_SIZE;
input.peekFully(
buffer.getData(), Atom.HEADER_SIZE, Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE);
buffer.setLimit(Atom.LONG_HEADER_SIZE);
buffer.getData(), Mp4Box.HEADER_SIZE, Mp4Box.LONG_HEADER_SIZE - Mp4Box.HEADER_SIZE);
buffer.setLimit(Mp4Box.LONG_HEADER_SIZE);
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.
long fileEndPosition = input.getLength();
if (fileEndPosition != C.LENGTH_UNSET) {
@ -149,7 +150,7 @@ import java.io.IOException;
}
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
// mvex atom because the moov's size exceeds the search length.
bytesToSearch += (int) atomSize;
@ -161,13 +162,13 @@ import java.io.IOException;
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.
isFragmented = true;
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.
// See https://developer.apple.com/standards/qtff-2001.pdf.
foundGoodFileType = true;
@ -180,7 +181,7 @@ import java.io.IOException;
int atomDataSize = (int) (atomSize - headerSize);
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.
if (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.util.ParsableByteArray;
import androidx.media3.common.util.Util;
import androidx.media3.container.Mp4Box;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -254,17 +255,18 @@ public final class AtomParsersTest {
@Test
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
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
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
@ -286,7 +288,7 @@ public final class AtomParsersTest {
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);
assertThat(box.getSampleCount()).isEqualTo(4);
assertThat(box.getFixedSampleSize()).isEqualTo(C.LENGTH_UNSET);

View File

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