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 bf05424b7f..919fd80b06 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 @@ -40,6 +40,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.checkerframework.checker.nullness.compatqual.NullableType; /** Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. */ @SuppressWarnings({"ConstantField"}) @@ -85,15 +86,21 @@ import java.util.List; * * @param trak Atom to decode. * @param mvhd Movie header atom, used to get the timescale. - * @param duration The duration in units of the timescale declared in the mvhd atom, or - * {@link C#TIME_UNSET} if the duration should be parsed from the tkhd atom. - * @param drmInitData {@link DrmInitData} to be included in the format. + * @param duration The duration in units of the timescale declared in the mvhd atom, or {@link + * C#TIME_UNSET} if the duration should be parsed from the tkhd atom. + * @param drmInitData {@link DrmInitData} to be included in the format, or {@code null}. * @param ignoreEditLists Whether to ignore any edit lists in the trak box. * @param isQuickTime True for QuickTime media. False otherwise. * @return A {@link Track} instance, or {@code null} if the track's type isn't supported. */ - public static Track parseTrak(Atom.ContainerAtom trak, Atom.LeafAtom mvhd, long duration, - DrmInitData drmInitData, boolean ignoreEditLists, boolean isQuickTime) + @Nullable + public static Track parseTrak( + Atom.ContainerAtom trak, + Atom.LeafAtom mvhd, + long duration, + @Nullable DrmInitData drmInitData, + boolean ignoreEditLists, + boolean isQuickTime) throws ParserException { Atom.ContainerAtom mdia = trak.getContainerAtomOfType(Atom.TYPE_mdia); int trackType = getTrackTypeForHdlr(parseHdlr(mdia.getLeafAtomOfType(Atom.TYPE_hdlr).data)); @@ -121,9 +128,12 @@ import java.util.List; long[] editListDurations = null; long[] editListMediaTimes = null; if (!ignoreEditLists) { + @Nullable Pair edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts)); - editListDurations = edtsData.first; - editListMediaTimes = edtsData.second; + if (edtsData != null) { + editListDurations = edtsData.first; + editListMediaTimes = edtsData.second; + } } return stsdData.format == null ? null : new Track(tkhdData.id, trackType, mdhdData.first, movieTimescale, durationUs, @@ -736,19 +746,25 @@ import java.util.List; * @param trackId The track's identifier in its container. * @param rotationDegrees The rotation of the track in degrees. * @param language The language of the track. - * @param drmInitData {@link DrmInitData} to be included in the format. + * @param drmInitData {@link DrmInitData} to be included in the format, or {@code null}. * @param isQuickTime True for QuickTime media. False otherwise. * @return An object containing the parsed data. */ - private static StsdData parseStsd(ParsableByteArray stsd, int trackId, int rotationDegrees, - String language, DrmInitData drmInitData, boolean isQuickTime) throws ParserException { + private static StsdData parseStsd( + ParsableByteArray stsd, + int trackId, + int rotationDegrees, + String language, + @Nullable DrmInitData drmInitData, + boolean isQuickTime) + throws ParserException { stsd.setPosition(Atom.FULL_HEADER_SIZE); int numberOfEntries = stsd.readInt(); StsdData out = new StsdData(numberOfEntries); for (int i = 0; i < numberOfEntries; i++) { int childStartPosition = stsd.getPosition(); int childAtomSize = stsd.readInt(); - Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); + Assertions.checkState(childAtomSize > 0, "childAtomSize should be positive"); int childAtomType = stsd.readInt(); if (childAtomType == Atom.TYPE_avc1 || childAtomType == Atom.TYPE_avc3 @@ -846,9 +862,17 @@ import java.util.List; initializationData); } - private static void parseVideoSampleEntry(ParsableByteArray parent, int atomType, int position, - int size, int trackId, int rotationDegrees, DrmInitData drmInitData, StsdData out, - int entryIndex) throws ParserException { + private static void parseVideoSampleEntry( + ParsableByteArray parent, + int atomType, + int position, + int size, + int trackId, + int rotationDegrees, + @Nullable DrmInitData drmInitData, + StsdData out, + int entryIndex) + throws ParserException { parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE); parent.skipBytes(16); @@ -860,8 +884,9 @@ import java.util.List; int childPosition = parent.getPosition(); if (atomType == Atom.TYPE_encv) { - Pair sampleEntryEncryptionData = parseSampleEntryEncryptionData( - parent, position, size); + @Nullable + Pair sampleEntryEncryptionData = + parseSampleEntryEncryptionData(parent, position, size); if (sampleEntryEncryptionData != null) { atomType = sampleEntryEncryptionData.first; drmInitData = drmInitData == null ? null @@ -875,10 +900,10 @@ import java.util.List; // drmInitData = null; // } - List initializationData = null; - String mimeType = null; - String codecs = null; - byte[] projectionData = null; + @Nullable List initializationData = null; + @Nullable String mimeType = null; + @Nullable String codecs = null; + @Nullable byte[] projectionData = null; @C.StereoMode int stereoMode = Format.NO_VALUE; while (childPosition - position < size) { @@ -889,7 +914,7 @@ import java.util.List; // Handle optional terminating four zero bytes in MOV files. break; } - Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); + Assertions.checkState(childAtomSize > 0, "childAtomSize should be positive"); int childAtomType = parent.readInt(); if (childAtomType == Atom.TYPE_avcC) { Assertions.checkState(mimeType == null); @@ -925,10 +950,13 @@ import java.util.List; mimeType = MimeTypes.VIDEO_H263; } else if (childAtomType == Atom.TYPE_esds) { Assertions.checkState(mimeType == null); - Pair mimeTypeAndInitializationData = + Pair<@NullableType String, byte @NullableType []> mimeTypeAndInitializationDataBytes = parseEsdsFromParent(parent, childStartPosition); - mimeType = mimeTypeAndInitializationData.first; - initializationData = Collections.singletonList(mimeTypeAndInitializationData.second); + mimeType = mimeTypeAndInitializationDataBytes.first; + @Nullable byte[] initializationDataBytes = mimeTypeAndInitializationDataBytes.second; + if (initializationDataBytes != null) { + initializationData = Collections.singletonList(initializationDataBytes); + } } else if (childAtomType == Atom.TYPE_pasp) { pixelWidthHeightRatio = parsePaspFromParent(parent, childStartPosition); pixelWidthHeightRatioFromPasp = true; @@ -988,13 +1016,14 @@ import java.util.List; * Parses the edts atom (defined in 14496-12 subsection 8.6.5). * * @param edtsAtom edts (edit box) atom to decode. - * @return Pair of edit list durations and edit list media times, or a pair of nulls if they are - * not present. + * @return Pair of edit list durations and edit list media times, or {@code null} if they are not + * present. */ + @Nullable private static Pair parseEdts(Atom.ContainerAtom edtsAtom) { Atom.LeafAtom elst; if (edtsAtom == null || (elst = edtsAtom.getLeafAtomOfType(Atom.TYPE_elst)) == null) { - return Pair.create(null, null); + return null; } ParsableByteArray elstData = elst.data; elstData.setPosition(Atom.HEADER_SIZE); @@ -1024,9 +1053,18 @@ import java.util.List; return (float) hSpacing / vSpacing; } - private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType, int position, - int size, int trackId, String language, boolean isQuickTime, DrmInitData drmInitData, - StsdData out, int entryIndex) throws ParserException { + private static void parseAudioSampleEntry( + ParsableByteArray parent, + int atomType, + int position, + int size, + int trackId, + String language, + boolean isQuickTime, + @Nullable DrmInitData drmInitData, + StsdData out, + int entryIndex) + throws ParserException { parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE); int quickTimeSoundDescriptionVersion = 0; @@ -1064,8 +1102,9 @@ import java.util.List; int childPosition = parent.getPosition(); if (atomType == Atom.TYPE_enca) { - Pair sampleEntryEncryptionData = parseSampleEntryEncryptionData( - parent, position, size); + @Nullable + Pair sampleEntryEncryptionData = + parseSampleEntryEncryptionData(parent, position, size); if (sampleEntryEncryptionData != null) { atomType = sampleEntryEncryptionData.first; drmInitData = drmInitData == null ? null @@ -1080,7 +1119,7 @@ import java.util.List; // } // If the atom type determines a MIME type, set it immediately. - String mimeType = null; + @Nullable String mimeType = null; if (atomType == Atom.TYPE_ac_3) { mimeType = MimeTypes.AUDIO_AC3; } else if (atomType == Atom.TYPE_ec_3) { @@ -1113,21 +1152,21 @@ import java.util.List; mimeType = MimeTypes.AUDIO_FLAC; } - byte[] initializationData = null; + @Nullable byte[] initializationData = null; while (childPosition - position < size) { parent.setPosition(childPosition); int childAtomSize = parent.readInt(); - Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); + Assertions.checkState(childAtomSize > 0, "childAtomSize should be positive"); int childAtomType = parent.readInt(); if (childAtomType == Atom.TYPE_esds || (isQuickTime && childAtomType == Atom.TYPE_wave)) { int esdsAtomPosition = childAtomType == Atom.TYPE_esds ? childPosition : findEsdsPosition(parent, childPosition, childAtomSize); if (esdsAtomPosition != C.POSITION_UNSET) { - Pair mimeTypeAndInitializationData = + Pair<@NullableType String, byte @NullableType []> mimeTypeAndInitializationData = parseEsdsFromParent(parent, esdsAtomPosition); mimeType = mimeTypeAndInitializationData.first; initializationData = mimeTypeAndInitializationData.second; - if (MimeTypes.AUDIO_AAC.equals(mimeType)) { + if (MimeTypes.AUDIO_AAC.equals(mimeType) && initializationData != null) { // Update sampleRate and channelCount from the AudioSpecificConfig initialization data, // which is more reliable. See [Internal: b/10903778]. Pair audioSpecificConfig = @@ -1204,7 +1243,7 @@ import java.util.List; while (childAtomPosition - position < size) { parent.setPosition(childAtomPosition); int childAtomSize = parent.readInt(); - Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); + Assertions.checkState(childAtomSize > 0, "childAtomSize should be positive"); int childType = parent.readInt(); if (childType == Atom.TYPE_esds) { return childAtomPosition; @@ -1214,10 +1253,9 @@ import java.util.List; return C.POSITION_UNSET; } - /** - * Returns codec-specific initialization data contained in an esds box. - */ - private static Pair parseEsdsFromParent(ParsableByteArray parent, int position) { + /** Returns codec-specific initialization data contained in an esds box. */ + private static Pair<@NullableType String, byte @NullableType []> parseEsdsFromParent( + ParsableByteArray parent, int position) { parent.setPosition(position + Atom.HEADER_SIZE + 4); // Start of the ES_Descriptor (defined in 14496-1) parent.skipBytes(1); // ES_Descriptor tag @@ -1263,13 +1301,14 @@ import java.util.List; * unencrypted atom type and a {@link TrackEncryptionBox}. Null is returned if no common * encryption sinf atom was present. */ + @Nullable private static Pair parseSampleEntryEncryptionData( ParsableByteArray parent, int position, int size) { int childPosition = parent.getPosition(); while (childPosition - position < size) { parent.setPosition(childPosition); int childAtomSize = parent.readInt(); - Assertions.checkArgument(childAtomSize > 0, "childAtomSize should be positive"); + Assertions.checkState(childAtomSize > 0, "childAtomSize should be positive"); int childAtomType = parent.readInt(); if (childAtomType == Atom.TYPE_sinf) { Pair result = parseCommonEncryptionSinfFromParent(parent, @@ -1283,6 +1322,7 @@ import java.util.List; return null; } + @Nullable /* package */ static Pair parseCommonEncryptionSinfFromParent( ParsableByteArray parent, int position, int size) { int childPosition = position + Atom.HEADER_SIZE; @@ -1309,20 +1349,21 @@ import java.util.List; if (C.CENC_TYPE_cenc.equals(schemeType) || C.CENC_TYPE_cbc1.equals(schemeType) || C.CENC_TYPE_cens.equals(schemeType) || C.CENC_TYPE_cbcs.equals(schemeType)) { - Assertions.checkArgument(dataFormat != null, "frma atom is mandatory"); - Assertions.checkArgument(schemeInformationBoxPosition != C.POSITION_UNSET, - "schi atom is mandatory"); + 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.checkArgument(encryptionBox != null, "tenc atom is mandatory"); + Assertions.checkStateNotNull(encryptionBox, "tenc atom is mandatory"); return Pair.create(dataFormat, encryptionBox); } else { return null; } } - private static TrackEncryptionBox parseSchiFromParent(ParsableByteArray parent, int position, - int size, String schemeType) { + @Nullable + private static TrackEncryptionBox parseSchiFromParent( + ParsableByteArray parent, int position, int size, String schemeType) { int childPosition = position + Atom.HEADER_SIZE; while (childPosition - position < size) { parent.setPosition(childPosition); @@ -1359,9 +1400,8 @@ import java.util.List; return null; } - /** - * Parses the proj box from sv3d box, as specified by https://github.com/google/spatial-media. - */ + /** Parses the proj box from sv3d box, as specified by https://github.com/google/spatial-media. */ + @Nullable private static byte[] parseProjFromParent(ParsableByteArray parent, int position, int size) { int childPosition = position + Atom.HEADER_SIZE; while (childPosition - position < size) { @@ -1477,10 +1517,9 @@ import java.util.List; public final TrackEncryptionBox[] trackEncryptionBoxes; - public Format format; + @Nullable public Format format; public int nalUnitLengthFieldLength; - @Track.Transformation - public int requiredSampleTransformation; + @Track.Transformation public int requiredSampleTransformation; public StsdData(int numberOfEntries) { trackEncryptionBoxes = new TrackEncryptionBox[numberOfEntries];