Commit 821d4fb1 by olly Committed by Oliver Woodman

Add NonNull at package level for extractor.mp4

PiperOrigin-RevId: 286191078
parent 7a03e8ed
...@@ -125,14 +125,16 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -125,14 +125,16 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
Pair<Long, String> mdhdData = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data); Pair<Long, String> mdhdData = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data);
StsdData stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, tkhdData.id, StsdData stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, tkhdData.id,
tkhdData.rotationDegrees, mdhdData.second, drmInitData, isQuickTime); tkhdData.rotationDegrees, mdhdData.second, drmInitData, isQuickTime);
long[] editListDurations = null; @Nullable long[] editListDurations = null;
long[] editListMediaTimes = null; @Nullable long[] editListMediaTimes = null;
if (!ignoreEditLists) { if (!ignoreEditLists) {
@Nullable @Nullable Atom.ContainerAtom edtsAtom = trak.getContainerAtomOfType(Atom.TYPE_edts);
Pair<long[], long[]> edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts)); if (edtsAtom != null) {
if (edtsData != null) { @Nullable Pair<long[], long[]> edtsData = parseEdts(edtsAtom);
editListDurations = edtsData.first; if (edtsData != null) {
editListMediaTimes = edtsData.second; editListDurations = edtsData.first;
editListMediaTimes = edtsData.second;
}
} }
} }
return stsdData.format == null ? null return stsdData.format == null ? null
...@@ -154,11 +156,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -154,11 +156,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
Track track, Atom.ContainerAtom stblAtom, GaplessInfoHolder gaplessInfoHolder) Track track, Atom.ContainerAtom stblAtom, GaplessInfoHolder gaplessInfoHolder)
throws ParserException { throws ParserException {
SampleSizeBox sampleSizeBox; SampleSizeBox sampleSizeBox;
Atom.LeafAtom stszAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stsz); @Nullable Atom.LeafAtom stszAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stsz);
if (stszAtom != null) { if (stszAtom != null) {
sampleSizeBox = new StszSampleSizeBox(stszAtom); sampleSizeBox = new StszSampleSizeBox(stszAtom);
} else { } else {
Atom.LeafAtom stz2Atom = stblAtom.getLeafAtomOfType(Atom.TYPE_stz2); @Nullable Atom.LeafAtom stz2Atom = stblAtom.getLeafAtomOfType(Atom.TYPE_stz2);
if (stz2Atom == null) { if (stz2Atom == null) {
throw new ParserException("Track has no sample table size information"); throw new ParserException("Track has no sample table size information");
} }
...@@ -179,7 +181,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -179,7 +181,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
// Entries are byte offsets of chunks. // Entries are byte offsets of chunks.
boolean chunkOffsetsAreLongs = false; boolean chunkOffsetsAreLongs = false;
Atom.LeafAtom chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stco); @Nullable Atom.LeafAtom chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stco);
if (chunkOffsetsAtom == null) { if (chunkOffsetsAtom == null) {
chunkOffsetsAreLongs = true; chunkOffsetsAreLongs = true;
chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_co64); chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_co64);
...@@ -190,11 +192,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -190,11 +192,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
// Entries are (number of samples, timestamp delta between those samples). // Entries are (number of samples, timestamp delta between those samples).
ParsableByteArray stts = stblAtom.getLeafAtomOfType(Atom.TYPE_stts).data; ParsableByteArray stts = stblAtom.getLeafAtomOfType(Atom.TYPE_stts).data;
// Entries are the indices of samples that are synchronization samples. // Entries are the indices of samples that are synchronization samples.
Atom.LeafAtom stssAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stss); @Nullable Atom.LeafAtom stssAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stss);
ParsableByteArray stss = stssAtom != null ? stssAtom.data : null; @Nullable ParsableByteArray stss = stssAtom != null ? stssAtom.data : null;
// Entries are (number of samples, timestamp offset). // Entries are (number of samples, timestamp offset).
Atom.LeafAtom cttsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_ctts); @Nullable Atom.LeafAtom cttsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_ctts);
ParsableByteArray ctts = cttsAtom != null ? cttsAtom.data : null; @Nullable ParsableByteArray ctts = cttsAtom != null ? cttsAtom.data : null;
// Prepare to read chunk information. // Prepare to read chunk information.
ChunkIterator chunkIterator = new ChunkIterator(stsc, chunkOffsets, chunkOffsetsAreLongs); ChunkIterator chunkIterator = new ChunkIterator(stsc, chunkOffsets, chunkOffsetsAreLongs);
...@@ -542,9 +544,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -542,9 +544,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
*/ */
@Nullable @Nullable
public static Metadata parseMdtaFromMeta(Atom.ContainerAtom meta) { public static Metadata parseMdtaFromMeta(Atom.ContainerAtom meta) {
Atom.LeafAtom hdlrAtom = meta.getLeafAtomOfType(Atom.TYPE_hdlr); @Nullable Atom.LeafAtom hdlrAtom = meta.getLeafAtomOfType(Atom.TYPE_hdlr);
Atom.LeafAtom keysAtom = meta.getLeafAtomOfType(Atom.TYPE_keys); @Nullable Atom.LeafAtom keysAtom = meta.getLeafAtomOfType(Atom.TYPE_keys);
Atom.LeafAtom ilstAtom = meta.getLeafAtomOfType(Atom.TYPE_ilst); @Nullable Atom.LeafAtom ilstAtom = meta.getLeafAtomOfType(Atom.TYPE_ilst);
if (hdlrAtom == null if (hdlrAtom == null
|| keysAtom == null || keysAtom == null
|| ilstAtom == null || ilstAtom == null
...@@ -575,6 +577,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -575,6 +577,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
int keyIndex = ilst.readInt() - 1; int keyIndex = ilst.readInt() - 1;
if (keyIndex >= 0 && keyIndex < keyNames.length) { if (keyIndex >= 0 && keyIndex < keyNames.length) {
String key = keyNames[keyIndex]; String key = keyNames[keyIndex];
@Nullable
Metadata.Entry entry = Metadata.Entry entry =
MetadataUtil.parseMdtaMetadataEntryFromIlst(ilst, atomPosition + atomSize, key); MetadataUtil.parseMdtaMetadataEntryFromIlst(ilst, atomPosition + atomSize, key);
if (entry != null) { if (entry != null) {
...@@ -609,7 +612,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -609,7 +612,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
ilst.skipBytes(Atom.HEADER_SIZE); ilst.skipBytes(Atom.HEADER_SIZE);
ArrayList<Metadata.Entry> entries = new ArrayList<>(); ArrayList<Metadata.Entry> entries = new ArrayList<>();
while (ilst.getPosition() < limit) { while (ilst.getPosition() < limit) {
Metadata.Entry entry = MetadataUtil.parseIlstElement(ilst); @Nullable Metadata.Entry entry = MetadataUtil.parseIlstElement(ilst);
if (entry != null) { if (entry != null) {
entries.add(entry); entries.add(entry);
} }
...@@ -817,12 +820,18 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -817,12 +820,18 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
return out; return out;
} }
private static void parseTextSampleEntry(ParsableByteArray parent, int atomType, int position, private static void parseTextSampleEntry(
int atomSize, int trackId, String language, StsdData out) throws ParserException { ParsableByteArray parent,
int atomType,
int position,
int atomSize,
int trackId,
String language,
StsdData out) {
parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE); parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE);
// Default values. // Default values.
List<byte[]> initializationData = null; @Nullable List<byte[]> initializationData = null;
long subsampleOffsetUs = Format.OFFSET_SAMPLE_RELATIVE; long subsampleOffsetUs = Format.OFFSET_SAMPLE_RELATIVE;
String mimeType; String mimeType;
...@@ -934,7 +943,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -934,7 +943,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
initializationData = hevcConfig.initializationData; initializationData = hevcConfig.initializationData;
out.nalUnitLengthFieldLength = hevcConfig.nalUnitLengthFieldLength; out.nalUnitLengthFieldLength = hevcConfig.nalUnitLengthFieldLength;
} else if (childAtomType == Atom.TYPE_dvcC || childAtomType == Atom.TYPE_dvvC) { } else if (childAtomType == Atom.TYPE_dvcC || childAtomType == Atom.TYPE_dvvC) {
DolbyVisionConfig dolbyVisionConfig = DolbyVisionConfig.parse(parent); @Nullable DolbyVisionConfig dolbyVisionConfig = DolbyVisionConfig.parse(parent);
if (dolbyVisionConfig != null) { if (dolbyVisionConfig != null) {
codecs = dolbyVisionConfig.codecs; codecs = dolbyVisionConfig.codecs;
mimeType = MimeTypes.VIDEO_DOLBY_VISION; mimeType = MimeTypes.VIDEO_DOLBY_VISION;
...@@ -1021,11 +1030,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -1021,11 +1030,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
*/ */
@Nullable @Nullable
private static Pair<long[], long[]> parseEdts(Atom.ContainerAtom edtsAtom) { private static Pair<long[], long[]> parseEdts(Atom.ContainerAtom edtsAtom) {
Atom.LeafAtom elst; @Nullable Atom.LeafAtom elstAtom = edtsAtom.getLeafAtomOfType(Atom.TYPE_elst);
if (edtsAtom == null || (elst = edtsAtom.getLeafAtomOfType(Atom.TYPE_elst)) == null) { if (elstAtom == null) {
return null; return null;
} }
ParsableByteArray elstData = elst.data; ParsableByteArray elstData = elstAtom.data;
elstData.setPosition(Atom.HEADER_SIZE); elstData.setPosition(Atom.HEADER_SIZE);
int fullAtom = elstData.readInt(); int fullAtom = elstData.readInt();
int version = Atom.parseFullAtomVersion(fullAtom); int version = Atom.parseFullAtomVersion(fullAtom);
...@@ -1328,8 +1337,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -1328,8 +1337,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
int childPosition = position + Atom.HEADER_SIZE; int childPosition = position + Atom.HEADER_SIZE;
int schemeInformationBoxPosition = C.POSITION_UNSET; int schemeInformationBoxPosition = C.POSITION_UNSET;
int schemeInformationBoxSize = 0; int schemeInformationBoxSize = 0;
String schemeType = null; @Nullable String schemeType = null;
Integer dataFormat = null; @Nullable Integer dataFormat = null;
while (childPosition - position < size) { while (childPosition - position < size) {
parent.setPosition(childPosition); parent.setPosition(childPosition);
int childAtomSize = parent.readInt(); int childAtomSize = parent.readInt();
...@@ -1352,9 +1361,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -1352,9 +1361,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
Assertions.checkStateNotNull(dataFormat, "frma atom is mandatory"); Assertions.checkStateNotNull(dataFormat, "frma atom is mandatory");
Assertions.checkState( Assertions.checkState(
schemeInformationBoxPosition != C.POSITION_UNSET, "schi atom is mandatory"); schemeInformationBoxPosition != C.POSITION_UNSET, "schi atom is mandatory");
TrackEncryptionBox encryptionBox = parseSchiFromParent(parent, schemeInformationBoxPosition, TrackEncryptionBox encryptionBox =
schemeInformationBoxSize, schemeType); Assertions.checkStateNotNull(
Assertions.checkStateNotNull(encryptionBox, "tenc atom is mandatory"); parseSchiFromParent(
parent, schemeInformationBoxPosition, schemeInformationBoxSize, schemeType),
"tenc atom is mandatory");
return Pair.create(dataFormat, encryptionBox); return Pair.create(dataFormat, encryptionBox);
} else { } else {
return null; return null;
......
...@@ -55,6 +55,7 @@ import java.util.Arrays; ...@@ -55,6 +55,7 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Extracts data from the FMP4 container format. */ /** Extracts data from the FMP4 container format. */
@SuppressWarnings("ConstantField") @SuppressWarnings("ConstantField")
...@@ -155,14 +156,14 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -155,14 +156,14 @@ public class FragmentedMp4Extractor implements Extractor {
private int atomType; private int atomType;
private long atomSize; private long atomSize;
private int atomHeaderBytesRead; private int atomHeaderBytesRead;
private ParsableByteArray atomData; @Nullable private ParsableByteArray atomData;
private long endOfMdatPosition; private long endOfMdatPosition;
private int pendingMetadataSampleBytes; private int pendingMetadataSampleBytes;
private long pendingSeekTimeUs; private long pendingSeekTimeUs;
private long durationUs; private long durationUs;
private long segmentIndexEarliestPresentationTimeUs; private long segmentIndexEarliestPresentationTimeUs;
private TrackBundle currentTrackBundle; @Nullable private TrackBundle currentTrackBundle;
private int sampleSize; private int sampleSize;
private int sampleBytesWritten; private int sampleBytesWritten;
private int sampleCurrentNalBytesRemaining; private int sampleCurrentNalBytesRemaining;
...@@ -170,7 +171,7 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -170,7 +171,7 @@ public class FragmentedMp4Extractor implements Extractor {
private boolean isAc4HeaderRequired; private boolean isAc4HeaderRequired;
// Extractor output. // Extractor output.
private ExtractorOutput extractorOutput; @MonotonicNonNull private ExtractorOutput extractorOutput;
private TrackOutput[] emsgTrackOutputs; private TrackOutput[] emsgTrackOutputs;
private TrackOutput[] cea608TrackOutputs; private TrackOutput[] cea608TrackOutputs;
...@@ -495,6 +496,7 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -495,6 +496,7 @@ public class FragmentedMp4Extractor implements Extractor {
for (int i = 0; i < moovContainerChildrenSize; i++) { for (int i = 0; i < moovContainerChildrenSize; i++) {
Atom.ContainerAtom atom = moov.containerChildren.get(i); Atom.ContainerAtom atom = moov.containerChildren.get(i);
if (atom.type == Atom.TYPE_trak) { if (atom.type == Atom.TYPE_trak) {
@Nullable
Track track = Track track =
modifyTrack( modifyTrack(
AtomParsers.parseTrak( AtomParsers.parseTrak(
...@@ -712,7 +714,7 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -712,7 +714,7 @@ public class FragmentedMp4Extractor implements Extractor {
private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray, private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray,
@Flags int flags, byte[] extendedTypeScratch) throws ParserException { @Flags int flags, byte[] extendedTypeScratch) throws ParserException {
LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd); LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray); @Nullable TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray);
if (trackBundle == null) { if (trackBundle == null) {
return; return;
} }
...@@ -721,33 +723,34 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -721,33 +723,34 @@ public class FragmentedMp4Extractor implements Extractor {
long decodeTime = fragment.nextFragmentDecodeTime; long decodeTime = fragment.nextFragmentDecodeTime;
trackBundle.reset(); trackBundle.reset();
LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt); @Nullable LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt);
if (tfdtAtom != null && (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) == 0) { if (tfdtAtom != null && (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) == 0) {
decodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data); decodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data);
} }
parseTruns(traf, trackBundle, decodeTime, flags); parseTruns(traf, trackBundle, decodeTime, flags);
TrackEncryptionBox encryptionBox = trackBundle.track @Nullable
.getSampleDescriptionEncryptionBox(fragment.header.sampleDescriptionIndex); TrackEncryptionBox encryptionBox =
trackBundle.track.getSampleDescriptionEncryptionBox(fragment.header.sampleDescriptionIndex);
LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz); @Nullable LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz);
if (saiz != null) { if (saiz != null) {
parseSaiz(encryptionBox, saiz.data, fragment); parseSaiz(encryptionBox, saiz.data, fragment);
} }
LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio); @Nullable LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio);
if (saio != null) { if (saio != null) {
parseSaio(saio.data, fragment); parseSaio(saio.data, fragment);
} }
LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc); @Nullable LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc);
if (senc != null) { if (senc != null) {
parseSenc(senc.data, fragment); parseSenc(senc.data, fragment);
} }
LeafAtom sbgp = traf.getLeafAtomOfType(Atom.TYPE_sbgp); @Nullable LeafAtom sbgp = traf.getLeafAtomOfType(Atom.TYPE_sbgp);
LeafAtom sgpd = traf.getLeafAtomOfType(Atom.TYPE_sgpd); @Nullable LeafAtom sgpd = traf.getLeafAtomOfType(Atom.TYPE_sgpd);
if (sbgp != null && sgpd != null) { if (sbgp != null && sgpd != null) {
parseSgpd(sbgp.data, sgpd.data, encryptionBox != null ? encryptionBox.schemeType : null, parseSgpd(sbgp.data, sgpd.data, encryptionBox != null ? encryptionBox.schemeType : null,
fragment); fragment);
...@@ -863,13 +866,14 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -863,13 +866,14 @@ public class FragmentedMp4Extractor implements Extractor {
* @return The {@link TrackBundle} to which the {@link TrackFragment} belongs, or null if the tfhd * @return The {@link TrackBundle} to which the {@link TrackFragment} belongs, or null if the tfhd
* does not refer to any {@link TrackBundle}. * does not refer to any {@link TrackBundle}.
*/ */
@Nullable
private static TrackBundle parseTfhd( private static TrackBundle parseTfhd(
ParsableByteArray tfhd, SparseArray<TrackBundle> trackBundles) { ParsableByteArray tfhd, SparseArray<TrackBundle> trackBundles) {
tfhd.setPosition(Atom.HEADER_SIZE); tfhd.setPosition(Atom.HEADER_SIZE);
int fullAtom = tfhd.readInt(); int fullAtom = tfhd.readInt();
int atomFlags = Atom.parseFullAtomFlags(fullAtom); int atomFlags = Atom.parseFullAtomFlags(fullAtom);
int trackId = tfhd.readInt(); int trackId = tfhd.readInt();
TrackBundle trackBundle = getTrackBundle(trackBundles, trackId); @Nullable TrackBundle trackBundle = getTrackBundle(trackBundles, trackId);
if (trackBundle == null) { if (trackBundle == null) {
return null; return null;
} }
...@@ -1053,8 +1057,12 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -1053,8 +1057,12 @@ public class FragmentedMp4Extractor implements Extractor {
out.fillEncryptionData(senc); out.fillEncryptionData(senc);
} }
private static void parseSgpd(ParsableByteArray sbgp, ParsableByteArray sgpd, String schemeType, private static void parseSgpd(
TrackFragment out) throws ParserException { ParsableByteArray sbgp,
ParsableByteArray sgpd,
@Nullable String schemeType,
TrackFragment out)
throws ParserException {
sbgp.setPosition(Atom.HEADER_SIZE); sbgp.setPosition(Atom.HEADER_SIZE);
int sbgpFullAtom = sbgp.readInt(); int sbgpFullAtom = sbgp.readInt();
if (sbgp.readInt() != SAMPLE_GROUP_TYPE_seig) { if (sbgp.readInt() != SAMPLE_GROUP_TYPE_seig) {
...@@ -1216,7 +1224,7 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -1216,7 +1224,7 @@ public class FragmentedMp4Extractor implements Extractor {
private boolean readSample(ExtractorInput input) throws IOException, InterruptedException { private boolean readSample(ExtractorInput input) throws IOException, InterruptedException {
if (parserState == STATE_READING_SAMPLE_START) { if (parserState == STATE_READING_SAMPLE_START) {
if (currentTrackBundle == null) { if (currentTrackBundle == null) {
TrackBundle currentTrackBundle = getNextFragmentRun(trackBundles); @Nullable TrackBundle currentTrackBundle = getNextFragmentRun(trackBundles);
if (currentTrackBundle == null) { if (currentTrackBundle == null) {
// We've run out of samples in the current mdat. Discard any trailing data and prepare to // We've run out of samples in the current mdat. Discard any trailing data and prepare to
// read the header of the next atom. // read the header of the next atom.
...@@ -1388,6 +1396,7 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -1388,6 +1396,7 @@ public class FragmentedMp4Extractor implements Extractor {
* Returns the {@link TrackBundle} whose fragment run has the earliest file position out of those * Returns the {@link TrackBundle} whose fragment run has the earliest file position out of those
* yet to be consumed, or null if all have been consumed. * yet to be consumed, or null if all have been consumed.
*/ */
@Nullable
private static TrackBundle getNextFragmentRun(SparseArray<TrackBundle> trackBundles) { private static TrackBundle getNextFragmentRun(SparseArray<TrackBundle> trackBundles) {
TrackBundle nextTrackBundle = null; TrackBundle nextTrackBundle = null;
long nextTrackRunOffset = Long.MAX_VALUE; long nextTrackRunOffset = Long.MAX_VALUE;
...@@ -1410,7 +1419,7 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -1410,7 +1419,7 @@ public class FragmentedMp4Extractor implements Extractor {
/** Returns DrmInitData from leaf atoms. */ /** Returns DrmInitData from leaf atoms. */
private static DrmInitData getDrmInitDataFromAtoms(List<Atom.LeafAtom> leafChildren) { private static DrmInitData getDrmInitDataFromAtoms(List<Atom.LeafAtom> leafChildren) {
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); LeafAtom child = leafChildren.get(i);
...@@ -1419,7 +1428,7 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -1419,7 +1428,7 @@ public class FragmentedMp4Extractor implements Extractor {
schemeDatas = new ArrayList<>(); schemeDatas = new ArrayList<>();
} }
byte[] psshData = child.data.data; byte[] psshData = child.data.data;
UUID uuid = PsshAtomUtil.parseUuid(psshData); @Nullable UUID uuid = PsshAtomUtil.parseUuid(psshData);
if (uuid == null) { if (uuid == null) {
Log.w(TAG, "Skipped pssh atom (failed to extract uuid)"); Log.w(TAG, "Skipped pssh atom (failed to extract uuid)");
} else { } else {
...@@ -1496,9 +1505,10 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -1496,9 +1505,10 @@ public class FragmentedMp4Extractor implements Extractor {
} }
public void updateDrmInitData(DrmInitData drmInitData) { public void updateDrmInitData(DrmInitData drmInitData) {
@Nullable
TrackEncryptionBox encryptionBox = TrackEncryptionBox encryptionBox =
track.getSampleDescriptionEncryptionBox(fragment.header.sampleDescriptionIndex); track.getSampleDescriptionEncryptionBox(fragment.header.sampleDescriptionIndex);
String schemeType = encryptionBox != null ? encryptionBox.schemeType : null; @Nullable String schemeType = encryptionBox != null ? encryptionBox.schemeType : null;
output.format(track.format.copyWithDrmInitData(drmInitData.copyWithSchemeType(schemeType))); output.format(track.format.copyWithDrmInitData(drmInitData.copyWithSchemeType(schemeType)));
} }
...@@ -1595,7 +1605,7 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -1595,7 +1605,7 @@ public class FragmentedMp4Extractor implements Extractor {
/** Skips the encryption data for the current sample. */ /** Skips the encryption data for the current sample. */
private void skipSampleEncryptionData() { private void skipSampleEncryptionData() {
TrackEncryptionBox encryptionBox = getEncryptionBoxIfEncrypted(); @Nullable TrackEncryptionBox encryptionBox = getEncryptionBoxIfEncrypted();
if (encryptionBox == null) { if (encryptionBox == null) {
return; return;
} }
...@@ -1609,8 +1619,10 @@ public class FragmentedMp4Extractor implements Extractor { ...@@ -1609,8 +1619,10 @@ public class FragmentedMp4Extractor implements Extractor {
} }
} }
@Nullable
private TrackEncryptionBox getEncryptionBoxIfEncrypted() { private TrackEncryptionBox getEncryptionBoxIfEncrypted() {
int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex; int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex;
@Nullable
TrackEncryptionBox encryptionBox = TrackEncryptionBox encryptionBox =
fragment.trackEncryptionBox != null fragment.trackEncryptionBox != null
? fragment.trackEncryptionBox ? fragment.trackEncryptionBox
......
...@@ -47,8 +47,7 @@ public final class MdtaMetadataEntry implements Metadata.Entry { ...@@ -47,8 +47,7 @@ public final class MdtaMetadataEntry implements Metadata.Entry {
private MdtaMetadataEntry(Parcel in) { private MdtaMetadataEntry(Parcel in) {
key = Util.castNonNull(in.readString()); key = Util.castNonNull(in.readString());
value = new byte[in.readInt()]; value = Util.castNonNull(in.createByteArray());
in.readByteArray(value);
localeIndicator = in.readInt(); localeIndicator = in.readInt();
typeIndicator = in.readInt(); typeIndicator = in.readInt();
} }
...@@ -88,7 +87,6 @@ public final class MdtaMetadataEntry implements Metadata.Entry { ...@@ -88,7 +87,6 @@ public final class MdtaMetadataEntry implements Metadata.Entry {
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeString(key); dest.writeString(key);
dest.writeInt(value.length);
dest.writeByteArray(value); dest.writeByteArray(value);
dest.writeInt(localeIndicator); dest.writeInt(localeIndicator);
dest.writeInt(typeIndicator); dest.writeInt(typeIndicator);
......
...@@ -325,8 +325,11 @@ import com.google.android.exoplayer2.util.ParsableByteArray; ...@@ -325,8 +325,11 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
@Nullable @Nullable
private static TextInformationFrame parseStandardGenreAttribute(ParsableByteArray data) { private static TextInformationFrame parseStandardGenreAttribute(ParsableByteArray data) {
int genreCode = parseUint8AttributeValue(data); int genreCode = parseUint8AttributeValue(data);
String genreString = (0 < genreCode && genreCode <= STANDARD_GENRES.length) @Nullable
? STANDARD_GENRES[genreCode - 1] : null; String genreString =
(0 < genreCode && genreCode <= STANDARD_GENRES.length)
? STANDARD_GENRES[genreCode - 1]
: null;
if (genreString != null) { if (genreString != null) {
return new TextInformationFrame("TCON", /* description= */ null, genreString); return new TextInformationFrame("TCON", /* description= */ null, genreString);
} }
...@@ -341,7 +344,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; ...@@ -341,7 +344,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
if (atomType == Atom.TYPE_data) { if (atomType == Atom.TYPE_data) {
int fullVersionInt = data.readInt(); int fullVersionInt = data.readInt();
int flags = Atom.parseFullAtomFlags(fullVersionInt); int flags = Atom.parseFullAtomFlags(fullVersionInt);
String mimeType = flags == 13 ? "image/jpeg" : flags == 14 ? "image/png" : null; @Nullable String mimeType = flags == 13 ? "image/jpeg" : flags == 14 ? "image/png" : null;
if (mimeType == null) { if (mimeType == null) {
Log.w(TAG, "Unrecognized cover art flags: " + flags); Log.w(TAG, "Unrecognized cover art flags: " + flags);
return null; return null;
...@@ -361,8 +364,8 @@ import com.google.android.exoplayer2.util.ParsableByteArray; ...@@ -361,8 +364,8 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
@Nullable @Nullable
private static Id3Frame parseInternalAttribute(ParsableByteArray data, int endPosition) { private static Id3Frame parseInternalAttribute(ParsableByteArray data, int endPosition) {
String domain = null; @Nullable String domain = null;
String name = null; @Nullable String name = null;
int dataAtomPosition = -1; int dataAtomPosition = -1;
int dataAtomSize = -1; int dataAtomSize = -1;
while (data.getPosition() < endPosition) { while (data.getPosition() < endPosition) {
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.mp4; package com.google.android.exoplayer2.extractor.mp4;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException; import com.google.android.exoplayer2.ParserException;
...@@ -42,6 +43,7 @@ import java.lang.annotation.RetentionPolicy; ...@@ -42,6 +43,7 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* Extracts data from the MP4 container format. * Extracts data from the MP4 container format.
...@@ -105,7 +107,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -105,7 +107,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
private int atomType; private int atomType;
private long atomSize; private long atomSize;
private int atomHeaderBytesRead; private int atomHeaderBytesRead;
private ParsableByteArray atomData; @Nullable private ParsableByteArray atomData;
private int sampleTrackIndex; private int sampleTrackIndex;
private int sampleBytesWritten; private int sampleBytesWritten;
...@@ -113,7 +115,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -113,7 +115,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
private boolean isAc4HeaderRequired; private boolean isAc4HeaderRequired;
// Extractor outputs. // Extractor outputs.
private ExtractorOutput extractorOutput; @MonotonicNonNull private ExtractorOutput extractorOutput;
private Mp4Track[] tracks; private Mp4Track[] tracks;
private long[][] accumulatedSampleSizes; private long[][] accumulatedSampleSizes;
private int firstVideoTrackIndex; private int firstVideoTrackIndex;
...@@ -290,8 +292,11 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -290,8 +292,11 @@ public final class Mp4Extractor implements Extractor, SeekMap {
// 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 && !containerAtoms.isEmpty()) { if (endPosition == C.LENGTH_UNSET) {
endPosition = containerAtoms.peek().endPosition; @Nullable ContainerAtom containerAtom = containerAtoms.peek();
if (containerAtom != null) {
endPosition = containerAtom.endPosition;
}
} }
if (endPosition != C.LENGTH_UNSET) { if (endPosition != C.LENGTH_UNSET) {
atomSize = endPosition - input.getPosition() + atomHeaderBytesRead; atomSize = endPosition - input.getPosition() + atomHeaderBytesRead;
...@@ -386,17 +391,17 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -386,17 +391,17 @@ public final class Mp4Extractor implements Extractor, SeekMap {
List<Mp4Track> tracks = new ArrayList<>(); List<Mp4Track> tracks = new ArrayList<>();
// Process metadata. // Process metadata.
Metadata udtaMetadata = null; @Nullable Metadata udtaMetadata = null;
GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder(); GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder();
Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta); @Nullable Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta);
if (udta != null) { if (udta != null) {
udtaMetadata = AtomParsers.parseUdta(udta, isQuickTime); udtaMetadata = AtomParsers.parseUdta(udta, isQuickTime);
if (udtaMetadata != null) { if (udtaMetadata != null) {
gaplessInfoHolder.setFromMetadata(udtaMetadata); gaplessInfoHolder.setFromMetadata(udtaMetadata);
} }
} }
Metadata mdtaMetadata = null; @Nullable Metadata mdtaMetadata = null;
Atom.ContainerAtom meta = moov.getContainerAtomOfType(Atom.TYPE_meta); @Nullable Atom.ContainerAtom meta = moov.getContainerAtomOfType(Atom.TYPE_meta);
if (meta != null) { if (meta != null) {
mdtaMetadata = AtomParsers.parseMdtaFromMeta(meta); mdtaMetadata = AtomParsers.parseMdtaFromMeta(meta);
} }
...@@ -453,6 +458,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { ...@@ -453,6 +458,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
if (atom.type != Atom.TYPE_trak) { if (atom.type != Atom.TYPE_trak) {
continue; continue;
} }
@Nullable
Track track = Track track =
AtomParsers.parseTrak( AtomParsers.parseTrak(
atom, atom,
......
...@@ -49,8 +49,6 @@ public final class PsshAtomUtil { ...@@ -49,8 +49,6 @@ public final class PsshAtomUtil {
* @param data The scheme specific data. * @param data The scheme specific data.
* @return The PSSH atom. * @return The PSSH atom.
*/ */
// dereference of possibly-null reference keyId
@SuppressWarnings({"ParameterNotNullable", "nullness:dereference.of.nullable"})
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;
...@@ -97,8 +95,9 @@ public final class PsshAtomUtil { ...@@ -97,8 +95,9 @@ public final class PsshAtomUtil {
* @return The parsed UUID. Null if the input is not a valid PSSH atom, or if the PSSH atom has an * @return The parsed UUID. Null if the input is not a valid PSSH atom, or if the PSSH atom has an
* unsupported version. * unsupported version.
*/ */
public static @Nullable UUID parseUuid(byte[] atom) { @Nullable
PsshAtom parsedAtom = parsePsshAtom(atom); public static UUID parseUuid(byte[] atom) {
@Nullable PsshAtom parsedAtom = parsePsshAtom(atom);
if (parsedAtom == null) { if (parsedAtom == null) {
return null; return null;
} }
...@@ -115,7 +114,7 @@ public final class PsshAtomUtil { ...@@ -115,7 +114,7 @@ public final class PsshAtomUtil {
* an unsupported version. * an unsupported version.
*/ */
public static int parseVersion(byte[] atom) { public static int parseVersion(byte[] atom) {
PsshAtom parsedAtom = parsePsshAtom(atom); @Nullable PsshAtom parsedAtom = parsePsshAtom(atom);
if (parsedAtom == null) { if (parsedAtom == null) {
return -1; return -1;
} }
...@@ -133,8 +132,9 @@ public final class PsshAtomUtil { ...@@ -133,8 +132,9 @@ public final class PsshAtomUtil {
* @return The parsed scheme specific data. Null if the input is not a valid PSSH atom, or if the * @return The parsed scheme specific data. Null if the input is not a valid PSSH atom, or if the
* PSSH atom has an unsupported version, or if the PSSH atom does not match the passed UUID. * PSSH atom has an unsupported version, or if the PSSH atom does not match the passed UUID.
*/ */
public static @Nullable byte[] parseSchemeSpecificData(byte[] atom, UUID uuid) { @Nullable
PsshAtom parsedAtom = parsePsshAtom(atom); public static byte[] parseSchemeSpecificData(byte[] atom, UUID uuid) {
@Nullable PsshAtom parsedAtom = parsePsshAtom(atom);
if (parsedAtom == null) { if (parsedAtom == null) {
return null; return null;
} }
...@@ -153,7 +153,8 @@ public final class PsshAtomUtil { ...@@ -153,7 +153,8 @@ public final class PsshAtomUtil {
* has an unsupported version. * has an unsupported version.
*/ */
// TODO: Support parsing of the key ids for version 1 PSSH atoms. // TODO: Support parsing of the key ids for version 1 PSSH atoms.
private static @Nullable PsshAtom parsePsshAtom(byte[] atom) { @Nullable
private 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() < Atom.FULL_HEADER_SIZE + 16 /* UUID */ + 4 /* DataSize */) {
// Data too short. // Data too short.
......
...@@ -15,19 +15,19 @@ ...@@ -15,19 +15,19 @@
*/ */
package com.google.android.exoplayer2.extractor.mp4; package com.google.android.exoplayer2.extractor.mp4;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.IOException; import java.io.IOException;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* A holder for information corresponding to a single fragment of an mp4 file. * A holder for information corresponding to a single fragment of an mp4 file.
*/ */
/* package */ final class TrackFragment { /* package */ final class TrackFragment {
/** /** The default values for samples from the track fragment header. */
* The default values for samples from the track fragment header. @MonotonicNonNull public DefaultSampleValues header;
*/
public DefaultSampleValues header;
/** /**
* The position (byte offset) of the start of fragment. * The position (byte offset) of the start of fragment.
*/ */
...@@ -81,20 +81,13 @@ import java.io.IOException; ...@@ -81,20 +81,13 @@ import java.io.IOException;
* Undefined otherwise. * Undefined otherwise.
*/ */
public boolean[] sampleHasSubsampleEncryptionTable; public boolean[] sampleHasSubsampleEncryptionTable;
/** /** Fragment specific track encryption. May be null. */
* Fragment specific track encryption. May be null. @Nullable public TrackEncryptionBox trackEncryptionBox;
*/
public TrackEncryptionBox trackEncryptionBox;
/**
* If {@link #definesEncryptionData} is true, indicates the length of the sample encryption data.
* Undefined otherwise.
*/
public int sampleEncryptionDataLength;
/** /**
* If {@link #definesEncryptionData} is true, contains binary sample encryption data. Undefined * If {@link #definesEncryptionData} is true, contains binary sample encryption data. Undefined
* otherwise. * otherwise.
*/ */
public ParsableByteArray sampleEncryptionData; public final ParsableByteArray sampleEncryptionData;
/** /**
* Whether {@link #sampleEncryptionData} needs populating with the actual encryption data. * Whether {@link #sampleEncryptionData} needs populating with the actual encryption data.
*/ */
...@@ -104,6 +97,17 @@ import java.io.IOException; ...@@ -104,6 +97,17 @@ import java.io.IOException;
*/ */
public long nextFragmentDecodeTime; public long nextFragmentDecodeTime;
public TrackFragment() {
trunDataPosition = new long[0];
trunLength = new int[0];
sampleSizeTable = new int[0];
sampleCompositionTimeOffsetTable = new int[0];
sampleDecodingTimeTable = new long[0];
sampleIsSyncFrameTable = new boolean[0];
sampleHasSubsampleEncryptionTable = new boolean[0];
sampleEncryptionData = new ParsableByteArray();
}
/** /**
* Resets the fragment. * Resets the fragment.
* <p> * <p>
...@@ -130,11 +134,11 @@ import java.io.IOException; ...@@ -130,11 +134,11 @@ import java.io.IOException;
public void initTables(int trunCount, int sampleCount) { public void initTables(int trunCount, int sampleCount) {
this.trunCount = trunCount; this.trunCount = trunCount;
this.sampleCount = sampleCount; this.sampleCount = sampleCount;
if (trunLength == null || trunLength.length < trunCount) { if (trunLength.length < trunCount) {
trunDataPosition = new long[trunCount]; trunDataPosition = new long[trunCount];
trunLength = new int[trunCount]; trunLength = new int[trunCount];
} }
if (sampleSizeTable == null || sampleSizeTable.length < sampleCount) { if (sampleSizeTable.length < sampleCount) {
// Size the tables 25% larger than needed, so as to make future resize operations less // Size the tables 25% larger than needed, so as to make future resize operations less
// likely. The choice of 25% is relatively arbitrary. // likely. The choice of 25% is relatively arbitrary.
int tableSize = (sampleCount * 125) / 100; int tableSize = (sampleCount * 125) / 100;
...@@ -148,18 +152,14 @@ import java.io.IOException; ...@@ -148,18 +152,14 @@ import java.io.IOException;
/** /**
* Configures the fragment to be one that defines encryption data of the specified length. * Configures the fragment to be one that defines encryption data of the specified length.
* <p> *
* {@link #definesEncryptionData} is set to true, {@link #sampleEncryptionDataLength} is set to * <p>{@link #definesEncryptionData} is set to true, and the {@link ParsableByteArray#limit()
* the specified length, and {@link #sampleEncryptionData} is resized if necessary such that it * limit} of {@link #sampleEncryptionData} is set to the specified length.
* is at least this length.
* *
* @param length The length in bytes of the encryption data. * @param length The length in bytes of the encryption data.
*/ */
public void initEncryptionData(int length) { public void initEncryptionData(int length) {
if (sampleEncryptionData == null || sampleEncryptionData.limit() < length) { sampleEncryptionData.reset(length);
sampleEncryptionData = new ParsableByteArray(length);
}
sampleEncryptionDataLength = length;
definesEncryptionData = true; definesEncryptionData = true;
sampleEncryptionDataNeedsFill = true; sampleEncryptionDataNeedsFill = true;
} }
...@@ -170,7 +170,7 @@ import java.io.IOException; ...@@ -170,7 +170,7 @@ import java.io.IOException;
* @param input An {@link ExtractorInput} from which to read the encryption data. * @param input An {@link ExtractorInput} from which to read the encryption data.
*/ */
public void fillEncryptionData(ExtractorInput input) throws IOException, InterruptedException { public void fillEncryptionData(ExtractorInput input) throws IOException, InterruptedException {
input.readFully(sampleEncryptionData.data, 0, sampleEncryptionDataLength); input.readFully(sampleEncryptionData.data, 0, sampleEncryptionData.limit());
sampleEncryptionData.setPosition(0); sampleEncryptionData.setPosition(0);
sampleEncryptionDataNeedsFill = false; sampleEncryptionDataNeedsFill = false;
} }
...@@ -181,7 +181,7 @@ import java.io.IOException; ...@@ -181,7 +181,7 @@ import java.io.IOException;
* @param source A source from which to read the encryption data. * @param source A source from which to read the encryption data.
*/ */
public void fillEncryptionData(ParsableByteArray source) { public void fillEncryptionData(ParsableByteArray source) {
source.readBytes(sampleEncryptionData.data, 0, sampleEncryptionDataLength); source.readBytes(sampleEncryptionData.data, 0, sampleEncryptionData.limit());
sampleEncryptionData.setPosition(0); sampleEncryptionData.setPosition(0);
sampleEncryptionDataNeedsFill = false; sampleEncryptionDataNeedsFill = false;
} }
......
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@NonNullApi
package com.google.android.exoplayer2.extractor.mp4;
import com.google.android.exoplayer2.util.NonNullApi;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment