From 6960dde8a8c17356273c7e57aaa7de6404234d10 Mon Sep 17 00:00:00 2001 From: kimvde Date: Fri, 21 Aug 2020 14:01:35 +0100 Subject: [PATCH] Add nullness annotations to FragmentedMp4Extractor PiperOrigin-RevId: 327797428 --- .../android/exoplayer2/util/NalUnitUtil.java | 8 +- .../exoplayer2/extractor/ExtractorOutput.java | 28 +++- .../extractor/mp4/FragmentedMp4Extractor.java | 145 +++++++++--------- 3 files changed, 106 insertions(+), 75 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java b/library/common/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java index 6edbecf1ea..4831ec59e2 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.util; +import androidx.annotation.Nullable; import java.nio.ByteBuffer; import java.util.Arrays; @@ -219,11 +220,12 @@ public final class NalUnitUtil { * Returns whether the NAL unit with the specified header contains supplemental enhancement * information. * - * @param mimeType The sample MIME type. + * @param mimeType The sample MIME type, or {@code null} if unknown. * @param nalUnitHeaderFirstByte The first byte of nal_unit(). - * @return Whether the NAL unit with the specified header is an SEI NAL unit. + * @return Whether the NAL unit with the specified header is an SEI NAL unit. False is returned if + * the {@code MimeType} is {@code null}. */ - public static boolean isNalUnitSei(String mimeType, byte nalUnitHeaderFirstByte) { + public static boolean isNalUnitSei(@Nullable String mimeType, byte nalUnitHeaderFirstByte) { return (MimeTypes.VIDEO_H264.equals(mimeType) && (nalUnitHeaderFirstByte & 0x1F) == H264_NAL_UNIT_TYPE_SEI) || (MimeTypes.VIDEO_H265.equals(mimeType) diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java index a59cb1d1f2..95b1daeb6e 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ExtractorOutput.java @@ -20,10 +20,34 @@ package com.google.android.exoplayer2.extractor; */ public interface ExtractorOutput { + /** + * Placeholder {@link ExtractorOutput} implementation throwing an {@link + * UnsupportedOperationException} in each method. + */ + ExtractorOutput PLACEHOLDER = + new ExtractorOutput() { + + @Override + public TrackOutput track(int id, int type) { + throw new UnsupportedOperationException(); + } + + @Override + public void endTracks() { + throw new UnsupportedOperationException(); + } + + @Override + public void seekMap(SeekMap seekMap) { + throw new UnsupportedOperationException(); + } + }; + /** * Called by the {@link Extractor} to get the {@link TrackOutput} for a specific track. - *

- * The same {@link TrackOutput} is returned if multiple calls are made with the same {@code id}. + * + *

The same {@link TrackOutput} is returned if multiple calls are made with the same {@code + * id}. * * @param id A track identifier. * @param type The type of the track. Typically one of the {@link com.google.android.exoplayer2.C} diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index 756cd43fcc..859ce49b26 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -16,6 +16,10 @@ package com.google.android.exoplayer2.extractor.mp4; import static com.google.android.exoplayer2.extractor.mp4.AtomParsers.parseTraks; +import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.android.exoplayer2.util.Assertions.checkState; +import static com.google.android.exoplayer2.util.Util.castNonNull; +import static com.google.android.exoplayer2.util.Util.nullSafeArrayCopy; import static java.lang.Math.max; import android.util.Pair; @@ -42,7 +46,6 @@ import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom; import com.google.android.exoplayer2.extractor.mp4.Atom.LeafAtom; import com.google.android.exoplayer2.metadata.emsg.EventMessage; import com.google.android.exoplayer2.metadata.emsg.EventMessageEncoder; -import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.NalUnitUtil; @@ -59,7 +62,6 @@ 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") @@ -175,7 +177,7 @@ public class FragmentedMp4Extractor implements Extractor { private boolean processSeiNalUnitPayload; // Outputs. - private @MonotonicNonNull ExtractorOutput extractorOutput; + private ExtractorOutput extractorOutput; private TrackOutput[] emsgTrackOutputs; private TrackOutput[] ceaTrackOutputs; @@ -270,9 +272,9 @@ public class FragmentedMp4Extractor implements Extractor { durationUs = C.TIME_UNSET; pendingSeekTimeUs = C.TIME_UNSET; segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET; + extractorOutput = ExtractorOutput.PLACEHOLDER; emsgTrackOutputs = new TrackOutput[0]; ceaTrackOutputs = new TrackOutput[0]; - enterReadingAtomHeaderState(); } @Override @@ -283,6 +285,7 @@ public class FragmentedMp4Extractor implements Extractor { @Override public void init(ExtractorOutput output) { extractorOutput = output; + enterReadingAtomHeaderState(); initExtraTracks(); if (sideloadedTrack != null) { TrackBundle bundle = @@ -429,8 +432,9 @@ public class FragmentedMp4Extractor implements Extractor { if (atomSize > Integer.MAX_VALUE) { throw new ParserException("Leaf atom with length > 2147483647 (unsupported)."); } - atomData = new ParsableByteArray((int) atomSize); + ParsableByteArray atomData = new ParsableByteArray((int) atomSize); System.arraycopy(atomHeader.getData(), 0, atomData.getData(), 0, Atom.HEADER_SIZE); + this.atomData = atomData; parserState = STATE_READING_ATOM_PAYLOAD; } else { if (atomSize > Integer.MAX_VALUE) { @@ -445,6 +449,7 @@ public class FragmentedMp4Extractor implements Extractor { private void readAtomPayload(ExtractorInput input) throws IOException { 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()); @@ -485,12 +490,12 @@ public class FragmentedMp4Extractor implements Extractor { } private void onMoovContainerAtomRead(ContainerAtom moov) throws ParserException { - Assertions.checkState(sideloadedTrack == null, "Unexpected moov box."); + checkState(sideloadedTrack == null, "Unexpected moov box."); @Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moov.leafChildren); - // Read declaration of track fragments in the Moov box. - ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex); + // Read declaration of track fragments in the moov box. + ContainerAtom mvex = checkNotNull(moov.getContainerAtomOfType(Atom.TYPE_mvex)); SparseArray defaultSampleValuesArray = new SparseArray<>(); long duration = C.TIME_UNSET; int mvexChildrenSize = mvex.leafChildren.size(); @@ -531,7 +536,7 @@ public class FragmentedMp4Extractor implements Extractor { } extractorOutput.endTracks(); } else { - Assertions.checkState(trackBundles.size() == trackCount); + checkState(trackBundles.size() == trackCount); for (int i = 0; i < trackCount; i++) { TrackSampleTable sampleTable = sampleTables.get(i); Track track = sampleTable.track; @@ -554,7 +559,7 @@ public class FragmentedMp4Extractor implements Extractor { // See https://github.com/google/ExoPlayer/issues/4477. return defaultSampleValuesArray.valueAt(/* index= */ 0); } - return Assertions.checkNotNull(defaultSampleValuesArray.get(trackId)); + return checkNotNull(defaultSampleValuesArray.get(trackId)); } private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException { @@ -589,7 +594,7 @@ public class FragmentedMp4Extractor implements Extractor { emsgTrackOutputs[emsgTrackOutputCount++] = extractorOutput.track(nextExtraTrackId++, C.TRACK_TYPE_METADATA); } - emsgTrackOutputs = Arrays.copyOf(emsgTrackOutputs, emsgTrackOutputCount); + emsgTrackOutputs = nullSafeArrayCopy(emsgTrackOutputs, emsgTrackOutputCount); for (TrackOutput eventMessageTrackOutput : emsgTrackOutputs) { eventMessageTrackOutput.format(EMSG_FORMAT); } @@ -604,7 +609,7 @@ public class FragmentedMp4Extractor implements Extractor { /** Handles an emsg atom (defined in 23009-1). */ private void onEmsgLeafAtomRead(ParsableByteArray atom) { - if (emsgTrackOutputs == null || emsgTrackOutputs.length == 0) { + if (emsgTrackOutputs.length == 0) { return; } atom.setPosition(Atom.HEADER_SIZE); @@ -619,8 +624,8 @@ public class FragmentedMp4Extractor implements Extractor { long id; switch (version) { case 0: - schemeIdUri = Assertions.checkNotNull(atom.readNullTerminatedString()); - value = Assertions.checkNotNull(atom.readNullTerminatedString()); + schemeIdUri = checkNotNull(atom.readNullTerminatedString()); + value = checkNotNull(atom.readNullTerminatedString()); timescale = atom.readUnsignedInt(); presentationTimeDeltaUs = Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MICROS_PER_SECOND, timescale); @@ -638,8 +643,8 @@ public class FragmentedMp4Extractor implements Extractor { durationMs = Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MILLIS_PER_SECOND, timescale); id = atom.readUnsignedInt(); - schemeIdUri = Assertions.checkNotNull(atom.readNullTerminatedString()); - value = Assertions.checkNotNull(atom.readNullTerminatedString()); + schemeIdUri = checkNotNull(atom.readNullTerminatedString()); + value = checkNotNull(atom.readNullTerminatedString()); break; default: Log.w(TAG, "Skipping unsupported emsg version: " + version); @@ -717,7 +722,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); + LeafAtom tfhd = checkNotNull(traf.getLeafAtomOfType(Atom.TYPE_tfhd)); @Nullable TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray); if (trackBundle == null) { return; @@ -730,7 +735,7 @@ public class FragmentedMp4Extractor implements Extractor { trackBundle.currentlyInFragment = true; @Nullable LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt); if (tfdtAtom != null && (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) == 0) { - fragment.nextFragmentDecodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data); + fragment.nextFragmentDecodeTime = parseTfdt(tfdtAtom.data); fragment.nextFragmentDecodeTimeIncludesMoov = true; } else { fragment.nextFragmentDecodeTime = fragmentDecodeTime; @@ -742,11 +747,11 @@ public class FragmentedMp4Extractor implements Extractor { @Nullable TrackEncryptionBox encryptionBox = trackBundle.moovSampleTable.track.getSampleDescriptionEncryptionBox( - fragment.header.sampleDescriptionIndex); + checkNotNull(fragment.header).sampleDescriptionIndex); @Nullable LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz); if (saiz != null) { - parseSaiz(encryptionBox, saiz.data, fragment); + parseSaiz(checkNotNull(encryptionBox), saiz.data, fragment); } @Nullable LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio); @@ -964,7 +969,7 @@ public class FragmentedMp4Extractor implements Extractor { Track track = trackBundle.moovSampleTable.track; TrackFragment fragment = trackBundle.fragment; - DefaultSampleValues defaultSampleValues = fragment.header; + DefaultSampleValues defaultSampleValues = castNonNull(fragment.header); fragment.trunLength[index] = trun.readUnsignedIntToInt(); fragment.trunDataPosition[index] = fragment.dataPosition; @@ -994,7 +999,7 @@ public class FragmentedMp4Extractor implements Extractor { && track.editListDurations[0] == 0) { edtsOffsetUs = Util.scaleLargeTimestamp( - track.editListMediaTimes[0], C.MICROS_PER_SECOND, track.timescale); + castNonNull(track.editListMediaTimes)[0], C.MICROS_PER_SECOND, track.timescale); } int[] sampleSizeTable = fragment.sampleSizeTable; @@ -1161,7 +1166,7 @@ public class FragmentedMp4Extractor implements Extractor { int perSampleIvSize = sgpd.readUnsignedByte(); byte[] keyId = new byte[16]; sgpd.readBytes(keyId, 0, keyId.length); - byte[] constantIv = null; + @Nullable byte[] constantIv = null; if (perSampleIvSize == 0) { int constantIvSize = sgpd.readUnsignedByte(); constantIv = new byte[constantIvSize]; @@ -1238,7 +1243,7 @@ public class FragmentedMp4Extractor implements Extractor { } private void readEncryptionData(ExtractorInput input) throws IOException { - TrackBundle nextTrackBundle = null; + @Nullable TrackBundle nextTrackBundle = null; long nextDataOffset = Long.MAX_VALUE; int trackBundlesSize = trackBundles.size(); for (int i = 0; i < trackBundlesSize; i++) { @@ -1277,71 +1282,70 @@ public class FragmentedMp4Extractor implements Extractor { * @throws IOException If an error occurs reading from the input. */ private boolean readSample(ExtractorInput input) throws IOException { - if (parserState == STATE_READING_SAMPLE_START) { - if (currentTrackBundle == null) { - @Nullable TrackBundle currentTrackBundle = getNextTrackBundle(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. - int bytesToSkip = (int) (endOfMdatPosition - input.getPosition()); - if (bytesToSkip < 0) { - throw new ParserException("Offset to end of mdat was negative."); - } - input.skipFully(bytesToSkip); - enterReadingAtomHeaderState(); - return false; - } - - long nextDataPosition = currentTrackBundle.getCurrentSampleOffset(); - // We skip bytes preceding the next sample to read. - int bytesToSkip = (int) (nextDataPosition - input.getPosition()); + @Nullable TrackBundle trackBundle = currentTrackBundle; + if (trackBundle == null) { + trackBundle = getNextTrackBundle(trackBundles); + if (trackBundle == 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. + int bytesToSkip = (int) (endOfMdatPosition - input.getPosition()); if (bytesToSkip < 0) { - // Assume the sample data must be contiguous in the mdat with no preceding data. - Log.w(TAG, "Ignoring negative offset to sample data."); - bytesToSkip = 0; + throw new ParserException("Offset to end of mdat was negative."); } input.skipFully(bytesToSkip); - this.currentTrackBundle = currentTrackBundle; + enterReadingAtomHeaderState(); + return false; } - sampleSize = currentTrackBundle.getCurrentSampleSize(); + long nextDataPosition = trackBundle.getCurrentSampleOffset(); + // We skip bytes preceding the next sample to read. + int bytesToSkip = (int) (nextDataPosition - input.getPosition()); + if (bytesToSkip < 0) { + // Assume the sample data must be contiguous in the mdat with no preceding data. + Log.w(TAG, "Ignoring negative offset to sample data."); + bytesToSkip = 0; + } + input.skipFully(bytesToSkip); + currentTrackBundle = trackBundle; + } + if (parserState == STATE_READING_SAMPLE_START) { + sampleSize = trackBundle.getCurrentSampleSize(); - if (currentTrackBundle.currentSampleIndex < currentTrackBundle.firstSampleToOutputIndex) { + if (trackBundle.currentSampleIndex < trackBundle.firstSampleToOutputIndex) { input.skipFully(sampleSize); - currentTrackBundle.skipSampleEncryptionData(); - if (!currentTrackBundle.next()) { + trackBundle.skipSampleEncryptionData(); + if (!trackBundle.next()) { currentTrackBundle = null; } parserState = STATE_READING_SAMPLE_START; return true; } - if (currentTrackBundle.moovSampleTable.track.sampleTransformation + if (trackBundle.moovSampleTable.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) { sampleSize -= Atom.HEADER_SIZE; input.skipFully(Atom.HEADER_SIZE); } - if (MimeTypes.AUDIO_AC4.equals( - currentTrackBundle.moovSampleTable.track.format.sampleMimeType)) { + if (MimeTypes.AUDIO_AC4.equals(trackBundle.moovSampleTable.track.format.sampleMimeType)) { // AC4 samples need to be prefixed with a clear sample header. sampleBytesWritten = - currentTrackBundle.outputSampleEncryptionData(sampleSize, Ac4Util.SAMPLE_HEADER_SIZE); + trackBundle.outputSampleEncryptionData(sampleSize, Ac4Util.SAMPLE_HEADER_SIZE); Ac4Util.getAc4SampleHeader(sampleSize, scratch); - currentTrackBundle.output.sampleData(scratch, Ac4Util.SAMPLE_HEADER_SIZE); + trackBundle.output.sampleData(scratch, Ac4Util.SAMPLE_HEADER_SIZE); sampleBytesWritten += Ac4Util.SAMPLE_HEADER_SIZE; } else { sampleBytesWritten = - currentTrackBundle.outputSampleEncryptionData(sampleSize, /* clearHeaderSize= */ 0); + trackBundle.outputSampleEncryptionData(sampleSize, /* clearHeaderSize= */ 0); } sampleSize += sampleBytesWritten; parserState = STATE_READING_SAMPLE_CONTINUE; sampleCurrentNalBytesRemaining = 0; } - Track track = currentTrackBundle.moovSampleTable.track; - TrackOutput output = currentTrackBundle.output; - long sampleTimeUs = currentTrackBundle.getCurrentSamplePresentationTimeUs(); + Track track = trackBundle.moovSampleTable.track; + TrackOutput output = trackBundle.output; + long sampleTimeUs = trackBundle.getCurrentSamplePresentationTimeUs(); if (timestampAdjuster != null) { sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs); } @@ -1407,11 +1411,11 @@ public class FragmentedMp4Extractor implements Extractor { } } - @C.BufferFlags int sampleFlags = currentTrackBundle.getCurrentSampleFlags(); + @C.BufferFlags int sampleFlags = trackBundle.getCurrentSampleFlags(); // Encryption data. - TrackOutput.CryptoData cryptoData = null; - TrackEncryptionBox encryptionBox = currentTrackBundle.getEncryptionBoxIfEncrypted(); + @Nullable TrackOutput.CryptoData cryptoData = null; + @Nullable TrackEncryptionBox encryptionBox = trackBundle.getEncryptionBoxIfEncrypted(); if (encryptionBox != null) { cryptoData = encryptionBox.cryptoData; } @@ -1420,7 +1424,7 @@ public class FragmentedMp4Extractor implements Extractor { // After we have the sampleTimeUs, we can commit all the pending metadata samples outputPendingMetadataSamples(sampleTimeUs); - if (!currentTrackBundle.next()) { + if (!trackBundle.next()) { currentTrackBundle = null; } parserState = STATE_READING_SAMPLE_START; @@ -1452,7 +1456,7 @@ public class FragmentedMp4Extractor implements Extractor { */ @Nullable private static TrackBundle getNextTrackBundle(SparseArray trackBundles) { - TrackBundle nextTrackBundle = null; + @Nullable TrackBundle nextTrackBundle = null; long nextSampleOffset = Long.MAX_VALUE; int trackBundlesSize = trackBundles.size(); @@ -1579,6 +1583,8 @@ public class FragmentedMp4Extractor implements Extractor { TrackSampleTable moovSampleTable, DefaultSampleValues defaultSampleValues) { this.output = output; + this.moovSampleTable = moovSampleTable; + this.defaultSampleValues = defaultSampleValues; fragment = new TrackFragment(); scratch = new ParsableByteArray(); encryptionSignalByte = new ParsableByteArray(1); @@ -1587,9 +1593,8 @@ public class FragmentedMp4Extractor implements Extractor { } public void reset(TrackSampleTable moovSampleTable, DefaultSampleValues defaultSampleValues) { - Assertions.checkNotNull(moovSampleTable.track); this.moovSampleTable = moovSampleTable; - this.defaultSampleValues = Assertions.checkNotNull(defaultSampleValues); + this.defaultSampleValues = defaultSampleValues; output.format(moovSampleTable.track.format); resetFragmentInfo(); } @@ -1598,7 +1603,7 @@ public class FragmentedMp4Extractor implements Extractor { @Nullable TrackEncryptionBox encryptionBox = moovSampleTable.track.getSampleDescriptionEncryptionBox( - fragment.header.sampleDescriptionIndex); + castNonNull(fragment.header).sampleDescriptionIndex); @Nullable String schemeType = encryptionBox != null ? encryptionBox.schemeType : null; DrmInitData updatedDrmInitData = drmInitData.copyWithSchemeType(schemeType); Format format = @@ -1706,7 +1711,7 @@ public class FragmentedMp4Extractor implements Extractor { * @return The number of written bytes. */ public int outputSampleEncryptionData(int sampleSize, int clearHeaderSize) { - TrackEncryptionBox encryptionBox = getEncryptionBoxIfEncrypted(); + @Nullable TrackEncryptionBox encryptionBox = getEncryptionBoxIfEncrypted(); if (encryptionBox == null) { return 0; } @@ -1718,7 +1723,7 @@ public class FragmentedMp4Extractor implements Extractor { vectorSize = encryptionBox.perSampleIvSize; } else { // The default initialization vector should be used. - byte[] initVectorData = encryptionBox.defaultInitializationVector; + byte[] initVectorData = castNonNull(encryptionBox.defaultInitializationVector); defaultInitializationVector.reset(initVectorData, initVectorData.length); initializationVectorData = defaultInitializationVector; vectorSize = initVectorData.length; @@ -1815,7 +1820,7 @@ public class FragmentedMp4Extractor implements Extractor { // Encryption is not supported yet for samples specified in the sample table. return null; } - int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex; + int sampleDescriptionIndex = castNonNull(fragment.header).sampleDescriptionIndex; @Nullable TrackEncryptionBox encryptionBox = fragment.trackEncryptionBox != null