diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 919fd80b06..f6b4f4d463 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -125,14 +125,16 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; Pair mdhdData = parseMdhd(mdia.getLeafAtomOfType(Atom.TYPE_mdhd).data); StsdData stsdData = parseStsd(stbl.getLeafAtomOfType(Atom.TYPE_stsd).data, tkhdData.id, tkhdData.rotationDegrees, mdhdData.second, drmInitData, isQuickTime); - long[] editListDurations = null; - long[] editListMediaTimes = null; + @Nullable long[] editListDurations = null; + @Nullable long[] editListMediaTimes = null; if (!ignoreEditLists) { - @Nullable - Pair edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts)); - if (edtsData != null) { - editListDurations = edtsData.first; - editListMediaTimes = edtsData.second; + @Nullable Atom.ContainerAtom edtsAtom = trak.getContainerAtomOfType(Atom.TYPE_edts); + if (edtsAtom != null) { + @Nullable Pair edtsData = parseEdts(edtsAtom); + if (edtsData != null) { + editListDurations = edtsData.first; + editListMediaTimes = edtsData.second; + } } } return stsdData.format == null ? null @@ -154,11 +156,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; Track track, Atom.ContainerAtom stblAtom, GaplessInfoHolder gaplessInfoHolder) throws ParserException { SampleSizeBox sampleSizeBox; - Atom.LeafAtom stszAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stsz); + @Nullable Atom.LeafAtom stszAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stsz); if (stszAtom != null) { sampleSizeBox = new StszSampleSizeBox(stszAtom); } else { - Atom.LeafAtom stz2Atom = stblAtom.getLeafAtomOfType(Atom.TYPE_stz2); + @Nullable Atom.LeafAtom stz2Atom = stblAtom.getLeafAtomOfType(Atom.TYPE_stz2); if (stz2Atom == null) { throw new ParserException("Track has no sample table size information"); } @@ -179,7 +181,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; // Entries are byte offsets of chunks. boolean chunkOffsetsAreLongs = false; - Atom.LeafAtom chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stco); + @Nullable Atom.LeafAtom chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stco); if (chunkOffsetsAtom == null) { chunkOffsetsAreLongs = true; chunkOffsetsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_co64); @@ -190,11 +192,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; // Entries are (number of samples, timestamp delta between those samples). ParsableByteArray stts = stblAtom.getLeafAtomOfType(Atom.TYPE_stts).data; // Entries are the indices of samples that are synchronization samples. - Atom.LeafAtom stssAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stss); - ParsableByteArray stss = stssAtom != null ? stssAtom.data : null; + @Nullable Atom.LeafAtom stssAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stss); + @Nullable ParsableByteArray stss = stssAtom != null ? stssAtom.data : null; // Entries are (number of samples, timestamp offset). - Atom.LeafAtom cttsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_ctts); - ParsableByteArray ctts = cttsAtom != null ? cttsAtom.data : null; + @Nullable Atom.LeafAtom cttsAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_ctts); + @Nullable ParsableByteArray ctts = cttsAtom != null ? cttsAtom.data : null; // Prepare to read chunk information. ChunkIterator chunkIterator = new ChunkIterator(stsc, chunkOffsets, chunkOffsetsAreLongs); @@ -542,9 +544,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; */ @Nullable public static Metadata parseMdtaFromMeta(Atom.ContainerAtom meta) { - Atom.LeafAtom hdlrAtom = meta.getLeafAtomOfType(Atom.TYPE_hdlr); - Atom.LeafAtom keysAtom = meta.getLeafAtomOfType(Atom.TYPE_keys); - Atom.LeafAtom ilstAtom = meta.getLeafAtomOfType(Atom.TYPE_ilst); + @Nullable Atom.LeafAtom hdlrAtom = meta.getLeafAtomOfType(Atom.TYPE_hdlr); + @Nullable Atom.LeafAtom keysAtom = meta.getLeafAtomOfType(Atom.TYPE_keys); + @Nullable Atom.LeafAtom ilstAtom = meta.getLeafAtomOfType(Atom.TYPE_ilst); if (hdlrAtom == null || keysAtom == null || ilstAtom == null @@ -575,6 +577,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; int keyIndex = ilst.readInt() - 1; if (keyIndex >= 0 && keyIndex < keyNames.length) { String key = keyNames[keyIndex]; + @Nullable Metadata.Entry entry = MetadataUtil.parseMdtaMetadataEntryFromIlst(ilst, atomPosition + atomSize, key); if (entry != null) { @@ -609,7 +612,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ilst.skipBytes(Atom.HEADER_SIZE); ArrayList entries = new ArrayList<>(); while (ilst.getPosition() < limit) { - Metadata.Entry entry = MetadataUtil.parseIlstElement(ilst); + @Nullable Metadata.Entry entry = MetadataUtil.parseIlstElement(ilst); if (entry != null) { entries.add(entry); } @@ -817,12 +820,18 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; return out; } - private static void parseTextSampleEntry(ParsableByteArray parent, int atomType, int position, - int atomSize, int trackId, String language, StsdData out) throws ParserException { + private static void parseTextSampleEntry( + ParsableByteArray parent, + int atomType, + int position, + int atomSize, + int trackId, + String language, + StsdData out) { parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE); // Default values. - List initializationData = null; + @Nullable List initializationData = null; long subsampleOffsetUs = Format.OFFSET_SAMPLE_RELATIVE; String mimeType; @@ -934,7 +943,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; initializationData = hevcConfig.initializationData; out.nalUnitLengthFieldLength = hevcConfig.nalUnitLengthFieldLength; } else if (childAtomType == Atom.TYPE_dvcC || childAtomType == Atom.TYPE_dvvC) { - DolbyVisionConfig dolbyVisionConfig = DolbyVisionConfig.parse(parent); + @Nullable DolbyVisionConfig dolbyVisionConfig = DolbyVisionConfig.parse(parent); if (dolbyVisionConfig != null) { codecs = dolbyVisionConfig.codecs; mimeType = MimeTypes.VIDEO_DOLBY_VISION; @@ -1021,11 +1030,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; */ @Nullable private static Pair parseEdts(Atom.ContainerAtom edtsAtom) { - Atom.LeafAtom elst; - if (edtsAtom == null || (elst = edtsAtom.getLeafAtomOfType(Atom.TYPE_elst)) == null) { + @Nullable Atom.LeafAtom elstAtom = edtsAtom.getLeafAtomOfType(Atom.TYPE_elst); + if (elstAtom == null) { return null; } - ParsableByteArray elstData = elst.data; + ParsableByteArray elstData = elstAtom.data; elstData.setPosition(Atom.HEADER_SIZE); int fullAtom = elstData.readInt(); int version = Atom.parseFullAtomVersion(fullAtom); @@ -1328,8 +1337,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; int childPosition = position + Atom.HEADER_SIZE; int schemeInformationBoxPosition = C.POSITION_UNSET; int schemeInformationBoxSize = 0; - String schemeType = null; - Integer dataFormat = null; + @Nullable String schemeType = null; + @Nullable Integer dataFormat = null; while (childPosition - position < size) { parent.setPosition(childPosition); int childAtomSize = parent.readInt(); @@ -1352,9 +1361,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; Assertions.checkStateNotNull(dataFormat, "frma atom is mandatory"); Assertions.checkState( schemeInformationBoxPosition != C.POSITION_UNSET, "schi atom is mandatory"); - TrackEncryptionBox encryptionBox = parseSchiFromParent(parent, schemeInformationBoxPosition, - schemeInformationBoxSize, schemeType); - Assertions.checkStateNotNull(encryptionBox, "tenc atom is mandatory"); + TrackEncryptionBox encryptionBox = + Assertions.checkStateNotNull( + parseSchiFromParent( + parent, schemeInformationBoxPosition, schemeInformationBoxSize, schemeType), + "tenc atom is mandatory"); return Pair.create(dataFormat, encryptionBox); } else { return null; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index f6e0fb8bc9..1172f8665a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -55,6 +55,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.UUID; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** Extracts data from the FMP4 container format. */ @SuppressWarnings("ConstantField") @@ -155,14 +156,14 @@ public class FragmentedMp4Extractor implements Extractor { private int atomType; private long atomSize; private int atomHeaderBytesRead; - private ParsableByteArray atomData; + @Nullable private ParsableByteArray atomData; private long endOfMdatPosition; private int pendingMetadataSampleBytes; private long pendingSeekTimeUs; private long durationUs; private long segmentIndexEarliestPresentationTimeUs; - private TrackBundle currentTrackBundle; + @Nullable private TrackBundle currentTrackBundle; private int sampleSize; private int sampleBytesWritten; private int sampleCurrentNalBytesRemaining; @@ -170,7 +171,7 @@ public class FragmentedMp4Extractor implements Extractor { private boolean isAc4HeaderRequired; // Extractor output. - private ExtractorOutput extractorOutput; + @MonotonicNonNull private ExtractorOutput extractorOutput; private TrackOutput[] emsgTrackOutputs; private TrackOutput[] cea608TrackOutputs; @@ -495,6 +496,7 @@ public class FragmentedMp4Extractor implements Extractor { for (int i = 0; i < moovContainerChildrenSize; i++) { Atom.ContainerAtom atom = moov.containerChildren.get(i); if (atom.type == Atom.TYPE_trak) { + @Nullable Track track = modifyTrack( AtomParsers.parseTrak( @@ -712,7 +714,7 @@ public class FragmentedMp4Extractor implements Extractor { private static void parseTraf(ContainerAtom traf, SparseArray trackBundleArray, @Flags int flags, byte[] extendedTypeScratch) throws ParserException { LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd); - TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray); + @Nullable TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray); if (trackBundle == null) { return; } @@ -721,33 +723,34 @@ public class FragmentedMp4Extractor implements Extractor { long decodeTime = fragment.nextFragmentDecodeTime; 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) { decodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data); } parseTruns(traf, trackBundle, decodeTime, flags); - TrackEncryptionBox encryptionBox = trackBundle.track - .getSampleDescriptionEncryptionBox(fragment.header.sampleDescriptionIndex); + @Nullable + 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) { parseSaiz(encryptionBox, saiz.data, fragment); } - LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio); + @Nullable LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio); if (saio != null) { parseSaio(saio.data, fragment); } - LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc); + @Nullable LeafAtom senc = traf.getLeafAtomOfType(Atom.TYPE_senc); if (senc != null) { parseSenc(senc.data, fragment); } - LeafAtom sbgp = traf.getLeafAtomOfType(Atom.TYPE_sbgp); - LeafAtom sgpd = traf.getLeafAtomOfType(Atom.TYPE_sgpd); + @Nullable LeafAtom sbgp = traf.getLeafAtomOfType(Atom.TYPE_sbgp); + @Nullable LeafAtom sgpd = traf.getLeafAtomOfType(Atom.TYPE_sgpd); if (sbgp != null && sgpd != null) { parseSgpd(sbgp.data, sgpd.data, encryptionBox != null ? encryptionBox.schemeType : null, fragment); @@ -863,13 +866,14 @@ public class FragmentedMp4Extractor implements Extractor { * @return The {@link TrackBundle} to which the {@link TrackFragment} belongs, or null if the tfhd * does not refer to any {@link TrackBundle}. */ + @Nullable private static TrackBundle parseTfhd( ParsableByteArray tfhd, SparseArray trackBundles) { tfhd.setPosition(Atom.HEADER_SIZE); int fullAtom = tfhd.readInt(); int atomFlags = Atom.parseFullAtomFlags(fullAtom); int trackId = tfhd.readInt(); - TrackBundle trackBundle = getTrackBundle(trackBundles, trackId); + @Nullable TrackBundle trackBundle = getTrackBundle(trackBundles, trackId); if (trackBundle == null) { return null; } @@ -1053,8 +1057,12 @@ public class FragmentedMp4Extractor implements Extractor { out.fillEncryptionData(senc); } - private static void parseSgpd(ParsableByteArray sbgp, ParsableByteArray sgpd, String schemeType, - TrackFragment out) throws ParserException { + private static void parseSgpd( + ParsableByteArray sbgp, + ParsableByteArray sgpd, + @Nullable String schemeType, + TrackFragment out) + throws ParserException { sbgp.setPosition(Atom.HEADER_SIZE); int sbgpFullAtom = sbgp.readInt(); if (sbgp.readInt() != SAMPLE_GROUP_TYPE_seig) { @@ -1216,7 +1224,7 @@ public class FragmentedMp4Extractor implements Extractor { private boolean readSample(ExtractorInput input) throws IOException, InterruptedException { if (parserState == STATE_READING_SAMPLE_START) { if (currentTrackBundle == null) { - TrackBundle currentTrackBundle = getNextFragmentRun(trackBundles); + @Nullable TrackBundle currentTrackBundle = getNextFragmentRun(trackBundles); if (currentTrackBundle == null) { // We've run out of samples in the current mdat. Discard any trailing data and prepare to // read the header of the next atom. @@ -1388,6 +1396,7 @@ public class FragmentedMp4Extractor implements Extractor { * 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. */ + @Nullable private static TrackBundle getNextFragmentRun(SparseArray trackBundles) { TrackBundle nextTrackBundle = null; long nextTrackRunOffset = Long.MAX_VALUE; @@ -1410,7 +1419,7 @@ public class FragmentedMp4Extractor implements Extractor { /** Returns DrmInitData from leaf atoms. */ private static DrmInitData getDrmInitDataFromAtoms(List leafChildren) { - ArrayList schemeDatas = null; + @Nullable ArrayList schemeDatas = null; int leafChildrenSize = leafChildren.size(); for (int i = 0; i < leafChildrenSize; i++) { LeafAtom child = leafChildren.get(i); @@ -1419,7 +1428,7 @@ public class FragmentedMp4Extractor implements Extractor { schemeDatas = new ArrayList<>(); } byte[] psshData = child.data.data; - UUID uuid = PsshAtomUtil.parseUuid(psshData); + @Nullable UUID uuid = PsshAtomUtil.parseUuid(psshData); if (uuid == null) { Log.w(TAG, "Skipped pssh atom (failed to extract uuid)"); } else { @@ -1496,9 +1505,10 @@ public class FragmentedMp4Extractor implements Extractor { } public void updateDrmInitData(DrmInitData drmInitData) { + @Nullable TrackEncryptionBox encryptionBox = 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))); } @@ -1595,7 +1605,7 @@ public class FragmentedMp4Extractor implements Extractor { /** Skips the encryption data for the current sample. */ private void skipSampleEncryptionData() { - TrackEncryptionBox encryptionBox = getEncryptionBoxIfEncrypted(); + @Nullable TrackEncryptionBox encryptionBox = getEncryptionBoxIfEncrypted(); if (encryptionBox == null) { return; } @@ -1609,8 +1619,10 @@ public class FragmentedMp4Extractor implements Extractor { } } + @Nullable private TrackEncryptionBox getEncryptionBoxIfEncrypted() { int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex; + @Nullable TrackEncryptionBox encryptionBox = fragment.trackEncryptionBox != null ? fragment.trackEncryptionBox diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntry.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntry.java index e50fbd54f7..5ad2b63b4c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntry.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MdtaMetadataEntry.java @@ -47,8 +47,7 @@ public final class MdtaMetadataEntry implements Metadata.Entry { private MdtaMetadataEntry(Parcel in) { key = Util.castNonNull(in.readString()); - value = new byte[in.readInt()]; - in.readByteArray(value); + value = Util.castNonNull(in.createByteArray()); localeIndicator = in.readInt(); typeIndicator = in.readInt(); } @@ -88,7 +87,6 @@ public final class MdtaMetadataEntry implements Metadata.Entry { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(key); - dest.writeInt(value.length); dest.writeByteArray(value); dest.writeInt(localeIndicator); dest.writeInt(typeIndicator); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java index 174ae821ac..cf2d2c1f6e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/MetadataUtil.java @@ -325,8 +325,11 @@ import com.google.android.exoplayer2.util.ParsableByteArray; @Nullable private static TextInformationFrame parseStandardGenreAttribute(ParsableByteArray data) { int genreCode = parseUint8AttributeValue(data); - String genreString = (0 < genreCode && genreCode <= STANDARD_GENRES.length) - ? STANDARD_GENRES[genreCode - 1] : null; + @Nullable + String genreString = + (0 < genreCode && genreCode <= STANDARD_GENRES.length) + ? STANDARD_GENRES[genreCode - 1] + : null; if (genreString != null) { return new TextInformationFrame("TCON", /* description= */ null, genreString); } @@ -341,7 +344,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; if (atomType == Atom.TYPE_data) { int fullVersionInt = data.readInt(); 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) { Log.w(TAG, "Unrecognized cover art flags: " + flags); return null; @@ -361,8 +364,8 @@ import com.google.android.exoplayer2.util.ParsableByteArray; @Nullable private static Id3Frame parseInternalAttribute(ParsableByteArray data, int endPosition) { - String domain = null; - String name = null; + @Nullable String domain = null; + @Nullable String name = null; int dataAtomPosition = -1; int dataAtomSize = -1; while (data.getPosition() < endPosition) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index ad58e832aa..971cc27d13 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.extractor.mp4; import androidx.annotation.IntDef; +import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; @@ -42,6 +43,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * Extracts data from the MP4 container format. @@ -105,7 +107,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { private int atomType; private long atomSize; private int atomHeaderBytesRead; - private ParsableByteArray atomData; + @Nullable private ParsableByteArray atomData; private int sampleTrackIndex; private int sampleBytesWritten; @@ -113,7 +115,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { private boolean isAc4HeaderRequired; // Extractor outputs. - private ExtractorOutput extractorOutput; + @MonotonicNonNull private ExtractorOutput extractorOutput; private Mp4Track[] tracks; private long[][] accumulatedSampleSizes; private int firstVideoTrackIndex; @@ -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 // work out its size even if the input length is unknown. long endPosition = input.getLength(); - if (endPosition == C.LENGTH_UNSET && !containerAtoms.isEmpty()) { - endPosition = containerAtoms.peek().endPosition; + if (endPosition == C.LENGTH_UNSET) { + @Nullable ContainerAtom containerAtom = containerAtoms.peek(); + if (containerAtom != null) { + endPosition = containerAtom.endPosition; + } } if (endPosition != C.LENGTH_UNSET) { atomSize = endPosition - input.getPosition() + atomHeaderBytesRead; @@ -386,17 +391,17 @@ public final class Mp4Extractor implements Extractor, SeekMap { List tracks = new ArrayList<>(); // Process metadata. - Metadata udtaMetadata = null; + @Nullable Metadata udtaMetadata = null; GaplessInfoHolder gaplessInfoHolder = new GaplessInfoHolder(); - Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta); + @Nullable Atom.LeafAtom udta = moov.getLeafAtomOfType(Atom.TYPE_udta); if (udta != null) { udtaMetadata = AtomParsers.parseUdta(udta, isQuickTime); if (udtaMetadata != null) { gaplessInfoHolder.setFromMetadata(udtaMetadata); } } - Metadata mdtaMetadata = null; - Atom.ContainerAtom meta = moov.getContainerAtomOfType(Atom.TYPE_meta); + @Nullable Metadata mdtaMetadata = null; + @Nullable Atom.ContainerAtom meta = moov.getContainerAtomOfType(Atom.TYPE_meta); if (meta != null) { mdtaMetadata = AtomParsers.parseMdtaFromMeta(meta); } @@ -453,6 +458,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { if (atom.type != Atom.TYPE_trak) { continue; } + @Nullable Track track = AtomParsers.parseTrak( atom, diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java index b9ecaf174c..b4f537f0ce 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/PsshAtomUtil.java @@ -49,8 +49,6 @@ public final class PsshAtomUtil { * @param data The scheme specific data. * @return The PSSH atom. */ - // dereference of possibly-null reference keyId - @SuppressWarnings({"ParameterNotNullable", "nullness:dereference.of.nullable"}) public static byte[] buildPsshAtom( UUID systemId, @Nullable UUID[] keyIds, @Nullable byte[] data) { int dataLength = data != null ? data.length : 0; @@ -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 * unsupported version. */ - public static @Nullable UUID parseUuid(byte[] atom) { - PsshAtom parsedAtom = parsePsshAtom(atom); + @Nullable + public static UUID parseUuid(byte[] atom) { + @Nullable PsshAtom parsedAtom = parsePsshAtom(atom); if (parsedAtom == null) { return null; } @@ -115,7 +114,7 @@ public final class PsshAtomUtil { * an unsupported version. */ public static int parseVersion(byte[] atom) { - PsshAtom parsedAtom = parsePsshAtom(atom); + @Nullable PsshAtom parsedAtom = parsePsshAtom(atom); if (parsedAtom == null) { return -1; } @@ -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 * 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) { - PsshAtom parsedAtom = parsePsshAtom(atom); + @Nullable + public static byte[] parseSchemeSpecificData(byte[] atom, UUID uuid) { + @Nullable PsshAtom parsedAtom = parsePsshAtom(atom); if (parsedAtom == null) { return null; } @@ -153,7 +153,8 @@ public final class PsshAtomUtil { * has an unsupported version. */ // 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); if (atomData.limit() < Atom.FULL_HEADER_SIZE + 16 /* UUID */ + 4 /* DataSize */) { // Data too short. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java index 51ec2bf282..b4ad59127b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/TrackFragment.java @@ -15,19 +15,19 @@ */ package com.google.android.exoplayer2.extractor.mp4; +import androidx.annotation.Nullable; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.util.ParsableByteArray; import java.io.IOException; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** * A holder for information corresponding to a single fragment of an mp4 file. */ /* package */ final class TrackFragment { - /** - * The default values for samples from the track fragment header. - */ - public DefaultSampleValues header; + /** The default values for samples from the track fragment header. */ + @MonotonicNonNull public DefaultSampleValues header; /** * The position (byte offset) of the start of fragment. */ @@ -81,20 +81,13 @@ import java.io.IOException; * Undefined otherwise. */ public boolean[] sampleHasSubsampleEncryptionTable; - /** - * Fragment specific track encryption. May be null. - */ - public TrackEncryptionBox trackEncryptionBox; - /** - * If {@link #definesEncryptionData} is true, indicates the length of the sample encryption data. - * Undefined otherwise. - */ - public int sampleEncryptionDataLength; + /** Fragment specific track encryption. May be null. */ + @Nullable public TrackEncryptionBox trackEncryptionBox; /** * If {@link #definesEncryptionData} is true, contains binary sample encryption data. Undefined * otherwise. */ - public ParsableByteArray sampleEncryptionData; + public final ParsableByteArray sampleEncryptionData; /** * Whether {@link #sampleEncryptionData} needs populating with the actual encryption data. */ @@ -104,6 +97,17 @@ import java.io.IOException; */ 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. *

@@ -130,11 +134,11 @@ import java.io.IOException; public void initTables(int trunCount, int sampleCount) { this.trunCount = trunCount; this.sampleCount = sampleCount; - if (trunLength == null || trunLength.length < trunCount) { + if (trunLength.length < trunCount) { trunDataPosition = new long[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 // likely. The choice of 25% is relatively arbitrary. int tableSize = (sampleCount * 125) / 100; @@ -148,18 +152,14 @@ import java.io.IOException; /** * Configures the fragment to be one that defines encryption data of the specified length. - *

- * {@link #definesEncryptionData} is set to true, {@link #sampleEncryptionDataLength} is set to - * the specified length, and {@link #sampleEncryptionData} is resized if necessary such that it - * is at least this length. + * + *

{@link #definesEncryptionData} is set to true, and the {@link ParsableByteArray#limit() + * limit} of {@link #sampleEncryptionData} is set to the specified length. * * @param length The length in bytes of the encryption data. */ public void initEncryptionData(int length) { - if (sampleEncryptionData == null || sampleEncryptionData.limit() < length) { - sampleEncryptionData = new ParsableByteArray(length); - } - sampleEncryptionDataLength = length; + sampleEncryptionData.reset(length); definesEncryptionData = true; sampleEncryptionDataNeedsFill = true; } @@ -170,7 +170,7 @@ import java.io.IOException; * @param input An {@link ExtractorInput} from which to read the encryption data. */ public void fillEncryptionData(ExtractorInput input) throws IOException, InterruptedException { - input.readFully(sampleEncryptionData.data, 0, sampleEncryptionDataLength); + input.readFully(sampleEncryptionData.data, 0, sampleEncryptionData.limit()); sampleEncryptionData.setPosition(0); sampleEncryptionDataNeedsFill = false; } @@ -181,7 +181,7 @@ import java.io.IOException; * @param source A source from which to read the encryption data. */ public void fillEncryptionData(ParsableByteArray source) { - source.readBytes(sampleEncryptionData.data, 0, sampleEncryptionDataLength); + source.readBytes(sampleEncryptionData.data, 0, sampleEncryptionData.limit()); sampleEncryptionData.setPosition(0); sampleEncryptionDataNeedsFill = false; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/package-info.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/package-info.java new file mode 100644 index 0000000000..6d0ad27361 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/package-info.java @@ -0,0 +1,19 @@ +/* + * 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;