Commit 93b3f43e by krocard Committed by Ian Baker

Fix some extractor nullness checks

Fix Matroska, Heif, FLAC, Ogg, Opus, Vorbis
extractor nullness check.

There should be no functional change.
Every media that fail to be parsed should still fail.
Every media that parsed successfully should still succeed.

This refactor aims to push all nullness constraints up the call stack to clarify each API nullness contract. This ensures implementation and caller have to prove their respective contract close to where such logic is implemented. This also allows to fail early if an nullness contract is broken instead of deep in the call stack.

For example, by adding a requirement that all implementation of `StreamReader.readHeaders` have to initialize `setupData.format` if the return false, each overriding method is forced to prove this next to the logic initializing it. This also means the runtime check might not be needed because the nullnessChecker can prove itself the contract holds.

This is in contrast with adding a null check at the point of usage, which will not catch logic errors where they are produce, but later when they are perceived; making it harder to debug and catching the issue at run time instead of compile time.

#exofixit

PiperOrigin-RevId: 346163124
parent 42f5e53d
...@@ -15,6 +15,10 @@ ...@@ -15,6 +15,10 @@
*/ */
package com.google.android.exoplayer2.extractor.mkv; package com.google.android.exoplayer2.extractor.mkv;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
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.Assertions.checkStateNotNull;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.Math.min; import static java.lang.Math.min;
...@@ -39,7 +43,6 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory; ...@@ -39,7 +43,6 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.LongArray; import com.google.android.exoplayer2.util.LongArray;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
...@@ -50,6 +53,7 @@ import com.google.android.exoplayer2.video.AvcConfig; ...@@ -50,6 +53,7 @@ import com.google.android.exoplayer2.video.AvcConfig;
import com.google.android.exoplayer2.video.ColorInfo; import com.google.android.exoplayer2.video.ColorInfo;
import com.google.android.exoplayer2.video.DolbyVisionConfig; import com.google.android.exoplayer2.video.DolbyVisionConfig;
import com.google.android.exoplayer2.video.HevcConfig; import com.google.android.exoplayer2.video.HevcConfig;
import com.google.common.collect.ImmutableList;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
...@@ -65,7 +69,9 @@ import java.util.Locale; ...@@ -65,7 +69,9 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.compatqual.NullableType;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** Extracts data from the Matroska and WebM container formats. */ /** Extracts data from the Matroska and WebM container formats. */
public class MatroskaExtractor implements Extractor { public class MatroskaExtractor implements Extractor {
...@@ -370,7 +376,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -370,7 +376,7 @@ public class MatroskaExtractor implements Extractor {
private final ParsableByteArray encryptionInitializationVector; private final ParsableByteArray encryptionInitializationVector;
private final ParsableByteArray encryptionSubsampleData; private final ParsableByteArray encryptionSubsampleData;
private final ParsableByteArray blockAdditionalData; private final ParsableByteArray blockAdditionalData;
private ByteBuffer encryptionSubsampleDataBuffer; private @MonotonicNonNull ByteBuffer encryptionSubsampleDataBuffer;
private long segmentContentSize; private long segmentContentSize;
private long segmentContentPosition = C.POSITION_UNSET; private long segmentContentPosition = C.POSITION_UNSET;
...@@ -494,7 +500,9 @@ public class MatroskaExtractor implements Extractor { ...@@ -494,7 +500,9 @@ public class MatroskaExtractor implements Extractor {
} }
if (!continueReading) { if (!continueReading) {
for (int i = 0; i < tracks.size(); i++) { for (int i = 0; i < tracks.size(); i++) {
tracks.valueAt(i).outputPendingSampleMetadata(); Track track = tracks.valueAt(i);
track.assertOutputInitialized();
track.outputPendingSampleMetadata();
} }
return Extractor.RESULT_END_OF_INPUT; return Extractor.RESULT_END_OF_INPUT;
} }
...@@ -629,6 +637,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -629,6 +637,7 @@ public class MatroskaExtractor implements Extractor {
@CallSuper @CallSuper
protected void startMasterElement(int id, long contentPosition, long contentSize) protected void startMasterElement(int id, long contentPosition, long contentSize)
throws ParserException { throws ParserException {
assertInitialized();
switch (id) { switch (id) {
case ID_SEGMENT: case ID_SEGMENT:
if (segmentContentPosition != C.POSITION_UNSET if (segmentContentPosition != C.POSITION_UNSET
...@@ -670,13 +679,13 @@ public class MatroskaExtractor implements Extractor { ...@@ -670,13 +679,13 @@ public class MatroskaExtractor implements Extractor {
// TODO: check and fail if more than one content encoding is present. // TODO: check and fail if more than one content encoding is present.
break; break;
case ID_CONTENT_ENCRYPTION: case ID_CONTENT_ENCRYPTION:
currentTrack.hasContentEncryption = true; getCurrentTrack(id).hasContentEncryption = true;
break; break;
case ID_TRACK_ENTRY: case ID_TRACK_ENTRY:
currentTrack = new Track(); currentTrack = new Track();
break; break;
case ID_MASTERING_METADATA: case ID_MASTERING_METADATA:
currentTrack.hasColorInfo = true; getCurrentTrack(id).hasColorInfo = true;
break; break;
default: default:
break; break;
...@@ -690,6 +699,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -690,6 +699,7 @@ public class MatroskaExtractor implements Extractor {
*/ */
@CallSuper @CallSuper
protected void endMasterElement(int id) throws ParserException { protected void endMasterElement(int id) throws ParserException {
assertInitialized();
switch (id) { switch (id) {
case ID_SEGMENT_INFO: case ID_SEGMENT_INFO:
if (timecodeScale == C.TIME_UNSET) { if (timecodeScale == C.TIME_UNSET) {
...@@ -727,6 +737,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -727,6 +737,7 @@ public class MatroskaExtractor implements Extractor {
sampleOffset += blockSampleSizes[i]; sampleOffset += blockSampleSizes[i];
} }
Track track = tracks.get(blockTrackNumber); Track track = tracks.get(blockTrackNumber);
track.assertOutputInitialized();
for (int i = 0; i < blockSampleCount; i++) { for (int i = 0; i < blockSampleCount; i++) {
long sampleTimeUs = blockTimeUs + (i * track.defaultSampleDurationNs) / 1000; long sampleTimeUs = blockTimeUs + (i * track.defaultSampleDurationNs) / 1000;
int sampleFlags = blockFlags; int sampleFlags = blockFlags;
...@@ -742,6 +753,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -742,6 +753,7 @@ public class MatroskaExtractor implements Extractor {
blockState = BLOCK_STATE_START; blockState = BLOCK_STATE_START;
break; break;
case ID_CONTENT_ENCODING: case ID_CONTENT_ENCODING:
assertInTrackEntry(id);
if (currentTrack.hasContentEncryption) { if (currentTrack.hasContentEncryption) {
if (currentTrack.cryptoData == null) { if (currentTrack.cryptoData == null) {
throw new ParserException("Encrypted Track found but ContentEncKeyID was not found"); throw new ParserException("Encrypted Track found but ContentEncKeyID was not found");
...@@ -751,14 +763,24 @@ public class MatroskaExtractor implements Extractor { ...@@ -751,14 +763,24 @@ public class MatroskaExtractor implements Extractor {
} }
break; break;
case ID_CONTENT_ENCODINGS: case ID_CONTENT_ENCODINGS:
assertInTrackEntry(id);
if (currentTrack.hasContentEncryption && currentTrack.sampleStrippedBytes != null) { if (currentTrack.hasContentEncryption && currentTrack.sampleStrippedBytes != null) {
throw new ParserException("Combining encryption and compression is not supported"); throw new ParserException("Combining encryption and compression is not supported");
} }
break; break;
case ID_TRACK_ENTRY: case ID_TRACK_ENTRY:
if (isCodecSupported(currentTrack.codecId)) { if (currentTrack == null) {
currentTrack.initializeOutput(extractorOutput, currentTrack.number); throw new ParserException("TrackEntry ends without matching start");
tracks.put(currentTrack.number, currentTrack); } else {
Track currentTrack = this.currentTrack;
if (currentTrack.codecId == null) {
throw new ParserException("CodecId is missing in TrackEntry element");
} else {
if (isCodecSupported(currentTrack.codecId)) {
currentTrack.initializeOutput(extractorOutput, currentTrack.number);
tracks.put(currentTrack.number, currentTrack);
}
}
} }
currentTrack = null; currentTrack = null;
break; break;
...@@ -802,52 +824,52 @@ public class MatroskaExtractor implements Extractor { ...@@ -802,52 +824,52 @@ public class MatroskaExtractor implements Extractor {
timecodeScale = value; timecodeScale = value;
break; break;
case ID_PIXEL_WIDTH: case ID_PIXEL_WIDTH:
currentTrack.width = (int) value; getCurrentTrack(id).width = (int) value;
break; break;
case ID_PIXEL_HEIGHT: case ID_PIXEL_HEIGHT:
currentTrack.height = (int) value; getCurrentTrack(id).height = (int) value;
break; break;
case ID_DISPLAY_WIDTH: case ID_DISPLAY_WIDTH:
currentTrack.displayWidth = (int) value; getCurrentTrack(id).displayWidth = (int) value;
break; break;
case ID_DISPLAY_HEIGHT: case ID_DISPLAY_HEIGHT:
currentTrack.displayHeight = (int) value; getCurrentTrack(id).displayHeight = (int) value;
break; break;
case ID_DISPLAY_UNIT: case ID_DISPLAY_UNIT:
currentTrack.displayUnit = (int) value; getCurrentTrack(id).displayUnit = (int) value;
break; break;
case ID_TRACK_NUMBER: case ID_TRACK_NUMBER:
currentTrack.number = (int) value; getCurrentTrack(id).number = (int) value;
break; break;
case ID_FLAG_DEFAULT: case ID_FLAG_DEFAULT:
currentTrack.flagDefault = value == 1; getCurrentTrack(id).flagDefault = value == 1;
break; break;
case ID_FLAG_FORCED: case ID_FLAG_FORCED:
currentTrack.flagForced = value == 1; getCurrentTrack(id).flagForced = value == 1;
break; break;
case ID_TRACK_TYPE: case ID_TRACK_TYPE:
currentTrack.type = (int) value; getCurrentTrack(id).type = (int) value;
break; break;
case ID_DEFAULT_DURATION: case ID_DEFAULT_DURATION:
currentTrack.defaultSampleDurationNs = (int) value; getCurrentTrack(id).defaultSampleDurationNs = (int) value;
break; break;
case ID_MAX_BLOCK_ADDITION_ID: case ID_MAX_BLOCK_ADDITION_ID:
currentTrack.maxBlockAdditionId = (int) value; getCurrentTrack(id).maxBlockAdditionId = (int) value;
break; break;
case ID_BLOCK_ADD_ID_TYPE: case ID_BLOCK_ADD_ID_TYPE:
currentTrack.blockAddIdType = (int) value; getCurrentTrack(id).blockAddIdType = (int) value;
break; break;
case ID_CODEC_DELAY: case ID_CODEC_DELAY:
currentTrack.codecDelayNs = value; getCurrentTrack(id).codecDelayNs = value;
break; break;
case ID_SEEK_PRE_ROLL: case ID_SEEK_PRE_ROLL:
currentTrack.seekPreRollNs = value; getCurrentTrack(id).seekPreRollNs = value;
break; break;
case ID_CHANNELS: case ID_CHANNELS:
currentTrack.channelCount = (int) value; getCurrentTrack(id).channelCount = (int) value;
break; break;
case ID_AUDIO_BIT_DEPTH: case ID_AUDIO_BIT_DEPTH:
currentTrack.audioBitDepth = (int) value; getCurrentTrack(id).audioBitDepth = (int) value;
break; break;
case ID_REFERENCE_BLOCK: case ID_REFERENCE_BLOCK:
blockHasReferenceBlock = true; blockHasReferenceBlock = true;
...@@ -883,10 +905,12 @@ public class MatroskaExtractor implements Extractor { ...@@ -883,10 +905,12 @@ public class MatroskaExtractor implements Extractor {
} }
break; break;
case ID_CUE_TIME: case ID_CUE_TIME:
assertInCues(id);
cueTimesUs.add(scaleTimecodeToUs(value)); cueTimesUs.add(scaleTimecodeToUs(value));
break; break;
case ID_CUE_CLUSTER_POSITION: case ID_CUE_CLUSTER_POSITION:
if (!seenClusterPositionForCurrentCuePoint) { if (!seenClusterPositionForCurrentCuePoint) {
assertInCues(id);
// If there's more than one video/audio track, then there could be more than one // If there's more than one video/audio track, then there could be more than one
// CueTrackPositions within a single CuePoint. In such a case, ignore all but the first // CueTrackPositions within a single CuePoint. In such a case, ignore all but the first
// one (since the cluster position will be quite close for all the tracks). // one (since the cluster position will be quite close for all the tracks).
...@@ -902,6 +926,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -902,6 +926,7 @@ public class MatroskaExtractor implements Extractor {
break; break;
case ID_STEREO_MODE: case ID_STEREO_MODE:
int layout = (int) value; int layout = (int) value;
assertInTrackEntry(id);
switch (layout) { switch (layout) {
case 0: case 0:
currentTrack.stereoMode = C.STEREO_MODE_MONO; currentTrack.stereoMode = C.STEREO_MODE_MONO;
...@@ -920,6 +945,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -920,6 +945,7 @@ public class MatroskaExtractor implements Extractor {
} }
break; break;
case ID_COLOUR_PRIMARIES: case ID_COLOUR_PRIMARIES:
assertInTrackEntry(id);
currentTrack.hasColorInfo = true; currentTrack.hasColorInfo = true;
switch ((int) value) { switch ((int) value) {
case 1: case 1:
...@@ -939,6 +965,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -939,6 +965,7 @@ public class MatroskaExtractor implements Extractor {
} }
break; break;
case ID_COLOUR_TRANSFER: case ID_COLOUR_TRANSFER:
assertInTrackEntry(id);
switch ((int) value) { switch ((int) value) {
case 1: // BT.709. case 1: // BT.709.
case 6: // SMPTE 170M. case 6: // SMPTE 170M.
...@@ -956,6 +983,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -956,6 +983,7 @@ public class MatroskaExtractor implements Extractor {
} }
break; break;
case ID_COLOUR_RANGE: case ID_COLOUR_RANGE:
assertInTrackEntry(id);
switch((int) value) { switch((int) value) {
case 1: // Broadcast range. case 1: // Broadcast range.
currentTrack.colorRange = C.COLOR_RANGE_LIMITED; currentTrack.colorRange = C.COLOR_RANGE_LIMITED;
...@@ -968,12 +996,13 @@ public class MatroskaExtractor implements Extractor { ...@@ -968,12 +996,13 @@ public class MatroskaExtractor implements Extractor {
} }
break; break;
case ID_MAX_CLL: case ID_MAX_CLL:
currentTrack.maxContentLuminance = (int) value; getCurrentTrack(id).maxContentLuminance = (int) value;
break; break;
case ID_MAX_FALL: case ID_MAX_FALL:
currentTrack.maxFrameAverageLuminance = (int) value; getCurrentTrack(id).maxFrameAverageLuminance = (int) value;
break; break;
case ID_PROJECTION_TYPE: case ID_PROJECTION_TYPE:
assertInTrackEntry(id);
switch ((int) value) { switch ((int) value) {
case 0: case 0:
currentTrack.projectionType = C.PROJECTION_RECTANGULAR; currentTrack.projectionType = C.PROJECTION_RECTANGULAR;
...@@ -1011,46 +1040,46 @@ public class MatroskaExtractor implements Extractor { ...@@ -1011,46 +1040,46 @@ public class MatroskaExtractor implements Extractor {
durationTimecode = (long) value; durationTimecode = (long) value;
break; break;
case ID_SAMPLING_FREQUENCY: case ID_SAMPLING_FREQUENCY:
currentTrack.sampleRate = (int) value; getCurrentTrack(id).sampleRate = (int) value;
break; break;
case ID_PRIMARY_R_CHROMATICITY_X: case ID_PRIMARY_R_CHROMATICITY_X:
currentTrack.primaryRChromaticityX = (float) value; getCurrentTrack(id).primaryRChromaticityX = (float) value;
break; break;
case ID_PRIMARY_R_CHROMATICITY_Y: case ID_PRIMARY_R_CHROMATICITY_Y:
currentTrack.primaryRChromaticityY = (float) value; getCurrentTrack(id).primaryRChromaticityY = (float) value;
break; break;
case ID_PRIMARY_G_CHROMATICITY_X: case ID_PRIMARY_G_CHROMATICITY_X:
currentTrack.primaryGChromaticityX = (float) value; getCurrentTrack(id).primaryGChromaticityX = (float) value;
break; break;
case ID_PRIMARY_G_CHROMATICITY_Y: case ID_PRIMARY_G_CHROMATICITY_Y:
currentTrack.primaryGChromaticityY = (float) value; getCurrentTrack(id).primaryGChromaticityY = (float) value;
break; break;
case ID_PRIMARY_B_CHROMATICITY_X: case ID_PRIMARY_B_CHROMATICITY_X:
currentTrack.primaryBChromaticityX = (float) value; getCurrentTrack(id).primaryBChromaticityX = (float) value;
break; break;
case ID_PRIMARY_B_CHROMATICITY_Y: case ID_PRIMARY_B_CHROMATICITY_Y:
currentTrack.primaryBChromaticityY = (float) value; getCurrentTrack(id).primaryBChromaticityY = (float) value;
break; break;
case ID_WHITE_POINT_CHROMATICITY_X: case ID_WHITE_POINT_CHROMATICITY_X:
currentTrack.whitePointChromaticityX = (float) value; getCurrentTrack(id).whitePointChromaticityX = (float) value;
break; break;
case ID_WHITE_POINT_CHROMATICITY_Y: case ID_WHITE_POINT_CHROMATICITY_Y:
currentTrack.whitePointChromaticityY = (float) value; getCurrentTrack(id).whitePointChromaticityY = (float) value;
break; break;
case ID_LUMNINANCE_MAX: case ID_LUMNINANCE_MAX:
currentTrack.maxMasteringLuminance = (float) value; getCurrentTrack(id).maxMasteringLuminance = (float) value;
break; break;
case ID_LUMNINANCE_MIN: case ID_LUMNINANCE_MIN:
currentTrack.minMasteringLuminance = (float) value; getCurrentTrack(id).minMasteringLuminance = (float) value;
break; break;
case ID_PROJECTION_POSE_YAW: case ID_PROJECTION_POSE_YAW:
currentTrack.projectionPoseYaw = (float) value; getCurrentTrack(id).projectionPoseYaw = (float) value;
break; break;
case ID_PROJECTION_POSE_PITCH: case ID_PROJECTION_POSE_PITCH:
currentTrack.projectionPosePitch = (float) value; getCurrentTrack(id).projectionPosePitch = (float) value;
break; break;
case ID_PROJECTION_POSE_ROLL: case ID_PROJECTION_POSE_ROLL:
currentTrack.projectionPoseRoll = (float) value; getCurrentTrack(id).projectionPoseRoll = (float) value;
break; break;
default: default:
break; break;
...@@ -1072,13 +1101,13 @@ public class MatroskaExtractor implements Extractor { ...@@ -1072,13 +1101,13 @@ public class MatroskaExtractor implements Extractor {
} }
break; break;
case ID_NAME: case ID_NAME:
currentTrack.name = value; getCurrentTrack(id).name = value;
break; break;
case ID_CODEC_ID: case ID_CODEC_ID:
currentTrack.codecId = value; getCurrentTrack(id).codecId = value;
break; break;
case ID_LANGUAGE: case ID_LANGUAGE:
currentTrack.language = value; getCurrentTrack(id).language = value;
break; break;
default: default:
break; break;
...@@ -1100,17 +1129,20 @@ public class MatroskaExtractor implements Extractor { ...@@ -1100,17 +1129,20 @@ public class MatroskaExtractor implements Extractor {
seekEntryId = (int) seekEntryIdBytes.readUnsignedInt(); seekEntryId = (int) seekEntryIdBytes.readUnsignedInt();
break; break;
case ID_BLOCK_ADD_ID_EXTRA_DATA: case ID_BLOCK_ADD_ID_EXTRA_DATA:
handleBlockAddIDExtraData(currentTrack, input, contentSize); handleBlockAddIDExtraData(getCurrentTrack(id), input, contentSize);
break; break;
case ID_CODEC_PRIVATE: case ID_CODEC_PRIVATE:
assertInTrackEntry(id);
currentTrack.codecPrivate = new byte[contentSize]; currentTrack.codecPrivate = new byte[contentSize];
input.readFully(currentTrack.codecPrivate, 0, contentSize); input.readFully(currentTrack.codecPrivate, 0, contentSize);
break; break;
case ID_PROJECTION_PRIVATE: case ID_PROJECTION_PRIVATE:
assertInTrackEntry(id);
currentTrack.projectionData = new byte[contentSize]; currentTrack.projectionData = new byte[contentSize];
input.readFully(currentTrack.projectionData, 0, contentSize); input.readFully(currentTrack.projectionData, 0, contentSize);
break; break;
case ID_CONTENT_COMPRESSION_SETTINGS: case ID_CONTENT_COMPRESSION_SETTINGS:
assertInTrackEntry(id);
// This extractor only supports header stripping, so the payload is the stripped bytes. // This extractor only supports header stripping, so the payload is the stripped bytes.
currentTrack.sampleStrippedBytes = new byte[contentSize]; currentTrack.sampleStrippedBytes = new byte[contentSize];
input.readFully(currentTrack.sampleStrippedBytes, 0, contentSize); input.readFully(currentTrack.sampleStrippedBytes, 0, contentSize);
...@@ -1118,8 +1150,9 @@ public class MatroskaExtractor implements Extractor { ...@@ -1118,8 +1150,9 @@ public class MatroskaExtractor implements Extractor {
case ID_CONTENT_ENCRYPTION_KEY_ID: case ID_CONTENT_ENCRYPTION_KEY_ID:
byte[] encryptionKey = new byte[contentSize]; byte[] encryptionKey = new byte[contentSize];
input.readFully(encryptionKey, 0, contentSize); input.readFully(encryptionKey, 0, contentSize);
currentTrack.cryptoData = new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, encryptionKey, getCurrentTrack(id).cryptoData =
0, 0); // We assume patternless AES-CTR. new TrackOutput.CryptoData(
C.CRYPTO_MODE_AES_CTR, encryptionKey, 0, 0); // We assume patternless AES-CTR.
break; break;
case ID_SIMPLE_BLOCK: case ID_SIMPLE_BLOCK:
case ID_BLOCK: case ID_BLOCK:
...@@ -1145,6 +1178,8 @@ public class MatroskaExtractor implements Extractor { ...@@ -1145,6 +1178,8 @@ public class MatroskaExtractor implements Extractor {
return; return;
} }
track.assertOutputInitialized();
if (blockState == BLOCK_STATE_HEADER) { if (blockState == BLOCK_STATE_HEADER) {
// Read the relative timecode (2 bytes) and flags (1 byte). // Read the relative timecode (2 bytes) and flags (1 byte).
readScratch(input, 3); readScratch(input, 3);
...@@ -1295,6 +1330,26 @@ public class MatroskaExtractor implements Extractor { ...@@ -1295,6 +1330,26 @@ public class MatroskaExtractor implements Extractor {
} }
} }
@EnsuresNonNull("currentTrack")
private void assertInTrackEntry(int id) throws ParserException {
if (currentTrack == null) {
throw new ParserException("Element " + id + " must be in a TrackEntry");
}
}
@EnsuresNonNull({"cueTimesUs", "cueClusterPositions"})
private void assertInCues(int id) throws ParserException {
if (cueTimesUs == null || cueClusterPositions == null) {
throw new ParserException("Element " + id + " must be in a Cues");
}
}
private Track getCurrentTrack(int currentElementId) throws ParserException {
assertInTrackEntry(currentElementId);
return currentTrack;
}
@RequiresNonNull("#1.output")
private void commitSampleToOutput( private void commitSampleToOutput(
Track track, long timeUs, @C.BufferFlags int flags, int size, int offset) { Track track, long timeUs, @C.BufferFlags int flags, int size, int offset) {
if (track.trueHdSampleRechunker != null) { if (track.trueHdSampleRechunker != null) {
...@@ -1365,6 +1420,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -1365,6 +1420,7 @@ public class MatroskaExtractor implements Extractor {
* @return The final size of the written sample. * @return The final size of the written sample.
* @throws IOException If an error occurs reading from the input. * @throws IOException If an error occurs reading from the input.
*/ */
@RequiresNonNull("#2.output")
private int writeSampleData(ExtractorInput input, Track track, int size) throws IOException { private int writeSampleData(ExtractorInput input, Track track, int size) throws IOException {
if (CODEC_ID_SUBRIP.equals(track.codecId)) { if (CODEC_ID_SUBRIP.equals(track.codecId)) {
writeSubtitleSampleData(input, SUBRIP_PREFIX, size); writeSubtitleSampleData(input, SUBRIP_PREFIX, size);
...@@ -1522,7 +1578,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -1522,7 +1578,7 @@ public class MatroskaExtractor implements Extractor {
} }
} else { } else {
if (track.trueHdSampleRechunker != null) { if (track.trueHdSampleRechunker != null) {
Assertions.checkState(sampleStrippedBytes.limit() == 0); checkState(sampleStrippedBytes.limit() == 0);
track.trueHdSampleRechunker.startSample(input); track.trueHdSampleRechunker.startSample(input);
} }
while (sampleBytesRead < size) { while (sampleBytesRead < size) {
...@@ -1628,7 +1684,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -1628,7 +1684,7 @@ public class MatroskaExtractor implements Extractor {
*/ */
private static byte[] formatSubtitleTimecode( private static byte[] formatSubtitleTimecode(
long timeUs, String timecodeFormat, long lastTimecodeValueScalingFactor) { long timeUs, String timecodeFormat, long lastTimecodeValueScalingFactor) {
Assertions.checkArgument(timeUs != C.TIME_UNSET); checkArgument(timeUs != C.TIME_UNSET);
byte[] timeCodeData; byte[] timeCodeData;
int hours = (int) (timeUs / (3600 * C.MICROS_PER_SECOND)); int hours = (int) (timeUs / (3600 * C.MICROS_PER_SECOND));
timeUs -= (hours * 3600 * C.MICROS_PER_SECOND); timeUs -= (hours * 3600 * C.MICROS_PER_SECOND);
...@@ -1680,12 +1736,14 @@ public class MatroskaExtractor implements Extractor { ...@@ -1680,12 +1736,14 @@ public class MatroskaExtractor implements Extractor {
* information was missing or incomplete. * information was missing or incomplete.
*/ */
private SeekMap buildSeekMap() { private SeekMap buildSeekMap() {
LongArray cueTimesUs = this.cueTimesUs;
LongArray cueClusterPositions = this.cueClusterPositions;
if (segmentContentPosition == C.POSITION_UNSET || durationUs == C.TIME_UNSET if (segmentContentPosition == C.POSITION_UNSET || durationUs == C.TIME_UNSET
|| cueTimesUs == null || cueTimesUs.size() == 0 || cueTimesUs == null || cueTimesUs.size() == 0
|| cueClusterPositions == null || cueClusterPositions.size() != cueTimesUs.size()) { || cueClusterPositions == null || cueClusterPositions.size() != cueTimesUs.size()) {
// Cues information is missing or incomplete. // Cues information is missing or incomplete.
cueTimesUs = null; this.cueTimesUs = null;
cueClusterPositions = null; this.cueClusterPositions = null;
return new SeekMap.Unseekable(durationUs); return new SeekMap.Unseekable(durationUs);
} }
int cuePointsSize = cueTimesUs.size(); int cuePointsSize = cueTimesUs.size();
...@@ -1714,8 +1772,8 @@ public class MatroskaExtractor implements Extractor { ...@@ -1714,8 +1772,8 @@ public class MatroskaExtractor implements Extractor {
timesUs = Arrays.copyOf(timesUs, timesUs.length - 1); timesUs = Arrays.copyOf(timesUs, timesUs.length - 1);
} }
cueTimesUs = null; this.cueTimesUs = null;
cueClusterPositions = null; this.cueClusterPositions = null;
return new ChunkIndex(sizes, offsets, durationsUs, timesUs); return new ChunkIndex(sizes, offsets, durationsUs, timesUs);
} }
...@@ -1796,7 +1854,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -1796,7 +1854,7 @@ public class MatroskaExtractor implements Extractor {
* Returns an array that can store (at least) {@code length} elements, which will be either a new * Returns an array that can store (at least) {@code length} elements, which will be either a new
* array or {@code array} if it's not null and large enough. * array or {@code array} if it's not null and large enough.
*/ */
private static int[] ensureArrayCapacity(int[] array, int length) { private static int[] ensureArrayCapacity(@Nullable int[] array, int length) {
if (array == null) { if (array == null) {
return new int[length]; return new int[length];
} else if (array.length >= length) { } else if (array.length >= length) {
...@@ -1807,6 +1865,11 @@ public class MatroskaExtractor implements Extractor { ...@@ -1807,6 +1865,11 @@ public class MatroskaExtractor implements Extractor {
} }
} }
@EnsuresNonNull("extractorOutput")
private void assertInitialized() {
checkStateNotNull(extractorOutput);
}
/** Passes events through to the outer {@link MatroskaExtractor}. */ /** Passes events through to the outer {@link MatroskaExtractor}. */
private final class InnerEbmlProcessor implements EbmlProcessor { private final class InnerEbmlProcessor implements EbmlProcessor {
...@@ -1888,6 +1951,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -1888,6 +1951,7 @@ public class MatroskaExtractor implements Extractor {
foundSyncframe = true; foundSyncframe = true;
} }
@RequiresNonNull("#1.output")
public void sampleMetadata( public void sampleMetadata(
Track track, long timeUs, @C.BufferFlags int flags, int size, int offset) { Track track, long timeUs, @C.BufferFlags int flags, int size, int offset) {
if (!foundSyncframe) { if (!foundSyncframe) {
...@@ -1906,6 +1970,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -1906,6 +1970,7 @@ public class MatroskaExtractor implements Extractor {
} }
} }
@RequiresNonNull("#1.output")
public void outputPendingSampleMetadata(Track track) { public void outputPendingSampleMetadata(Track track) {
if (chunkSampleCount > 0) { if (chunkSampleCount > 0) {
track.output.sampleMetadata( track.output.sampleMetadata(
...@@ -1930,18 +1995,18 @@ public class MatroskaExtractor implements Extractor { ...@@ -1930,18 +1995,18 @@ public class MatroskaExtractor implements Extractor {
private static final int DEFAULT_MAX_FALL = 200; // nits. private static final int DEFAULT_MAX_FALL = 200; // nits.
// Common elements. // Common elements.
public String name; public @MonotonicNonNull String name;
public String codecId; public @MonotonicNonNull String codecId;
public int number; public int number;
public int type; public int type;
public int defaultSampleDurationNs; public int defaultSampleDurationNs;
public int maxBlockAdditionId; public int maxBlockAdditionId;
private int blockAddIdType; private int blockAddIdType;
public boolean hasContentEncryption; public boolean hasContentEncryption;
public byte[] sampleStrippedBytes; public byte @MonotonicNonNull [] sampleStrippedBytes;
public TrackOutput.CryptoData cryptoData; public TrackOutput.@MonotonicNonNull CryptoData cryptoData;
public byte[] codecPrivate; public byte @MonotonicNonNull [] codecPrivate;
public DrmInitData drmInitData; public @MonotonicNonNull DrmInitData drmInitData;
// Video elements. // Video elements.
public int width = Format.NO_VALUE; public int width = Format.NO_VALUE;
...@@ -1953,7 +2018,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -1953,7 +2018,7 @@ public class MatroskaExtractor implements Extractor {
public float projectionPoseYaw = 0f; public float projectionPoseYaw = 0f;
public float projectionPosePitch = 0f; public float projectionPosePitch = 0f;
public float projectionPoseRoll = 0f; public float projectionPoseRoll = 0f;
public byte[] projectionData = null; public byte @MonotonicNonNull [] projectionData = null;
@C.StereoMode @C.StereoMode
public int stereoMode = Format.NO_VALUE; public int stereoMode = Format.NO_VALUE;
public boolean hasColorInfo = false; public boolean hasColorInfo = false;
...@@ -1975,7 +2040,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -1975,7 +2040,7 @@ public class MatroskaExtractor implements Extractor {
public float whitePointChromaticityY = Format.NO_VALUE; public float whitePointChromaticityY = Format.NO_VALUE;
public float maxMasteringLuminance = Format.NO_VALUE; public float maxMasteringLuminance = Format.NO_VALUE;
public float minMasteringLuminance = Format.NO_VALUE; public float minMasteringLuminance = Format.NO_VALUE;
@Nullable public byte[] dolbyVisionConfigBytes; public byte @MonotonicNonNull [] dolbyVisionConfigBytes;
// Audio elements. Initially set to their default values. // Audio elements. Initially set to their default values.
public int channelCount = 1; public int channelCount = 1;
...@@ -1983,7 +2048,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -1983,7 +2048,7 @@ public class MatroskaExtractor implements Extractor {
public int sampleRate = 8000; public int sampleRate = 8000;
public long codecDelayNs = 0; public long codecDelayNs = 0;
public long seekPreRollNs = 0; public long seekPreRollNs = 0;
@Nullable public TrueHdSampleRechunker trueHdSampleRechunker; public @MonotonicNonNull TrueHdSampleRechunker trueHdSampleRechunker;
// Text elements. // Text elements.
public boolean flagForced; public boolean flagForced;
...@@ -1991,10 +2056,12 @@ public class MatroskaExtractor implements Extractor { ...@@ -1991,10 +2056,12 @@ public class MatroskaExtractor implements Extractor {
private String language = "eng"; private String language = "eng";
// Set when the output is initialized. nalUnitLengthFieldLength is only set for H264/H265. // Set when the output is initialized. nalUnitLengthFieldLength is only set for H264/H265.
public TrackOutput output; public @MonotonicNonNull TrackOutput output;
public int nalUnitLengthFieldLength; public int nalUnitLengthFieldLength;
/** Initializes the track with an output. */ /** Initializes the track with an output. */
@RequiresNonNull("codecId")
@EnsuresNonNull("this.output")
public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException { public void initializeOutput(ExtractorOutput output, int trackId) throws ParserException {
String mimeType; String mimeType;
int maxInputSize = Format.NO_VALUE; int maxInputSize = Format.NO_VALUE;
...@@ -2023,19 +2090,19 @@ public class MatroskaExtractor implements Extractor { ...@@ -2023,19 +2090,19 @@ public class MatroskaExtractor implements Extractor {
break; break;
case CODEC_ID_H264: case CODEC_ID_H264:
mimeType = MimeTypes.VIDEO_H264; mimeType = MimeTypes.VIDEO_H264;
AvcConfig avcConfig = AvcConfig.parse(new ParsableByteArray(codecPrivate)); AvcConfig avcConfig = AvcConfig.parse(new ParsableByteArray(getCodecPrivate(codecId)));
initializationData = avcConfig.initializationData; initializationData = avcConfig.initializationData;
nalUnitLengthFieldLength = avcConfig.nalUnitLengthFieldLength; nalUnitLengthFieldLength = avcConfig.nalUnitLengthFieldLength;
break; break;
case CODEC_ID_H265: case CODEC_ID_H265:
mimeType = MimeTypes.VIDEO_H265; mimeType = MimeTypes.VIDEO_H265;
HevcConfig hevcConfig = HevcConfig.parse(new ParsableByteArray(codecPrivate)); HevcConfig hevcConfig = HevcConfig.parse(new ParsableByteArray(getCodecPrivate(codecId)));
initializationData = hevcConfig.initializationData; initializationData = hevcConfig.initializationData;
nalUnitLengthFieldLength = hevcConfig.nalUnitLengthFieldLength; nalUnitLengthFieldLength = hevcConfig.nalUnitLengthFieldLength;
break; break;
case CODEC_ID_FOURCC: case CODEC_ID_FOURCC:
Pair<String, @NullableType List<byte[]>> pair = Pair<String, @NullableType List<byte[]>> pair =
parseFourCcPrivate(new ParsableByteArray(codecPrivate)); parseFourCcPrivate(new ParsableByteArray(getCodecPrivate(codecId)));
mimeType = pair.first; mimeType = pair.first;
initializationData = pair.second; initializationData = pair.second;
break; break;
...@@ -2047,13 +2114,13 @@ public class MatroskaExtractor implements Extractor { ...@@ -2047,13 +2114,13 @@ public class MatroskaExtractor implements Extractor {
case CODEC_ID_VORBIS: case CODEC_ID_VORBIS:
mimeType = MimeTypes.AUDIO_VORBIS; mimeType = MimeTypes.AUDIO_VORBIS;
maxInputSize = VORBIS_MAX_INPUT_SIZE; maxInputSize = VORBIS_MAX_INPUT_SIZE;
initializationData = parseVorbisCodecPrivate(codecPrivate); initializationData = parseVorbisCodecPrivate(getCodecPrivate(codecId));
break; break;
case CODEC_ID_OPUS: case CODEC_ID_OPUS:
mimeType = MimeTypes.AUDIO_OPUS; mimeType = MimeTypes.AUDIO_OPUS;
maxInputSize = OPUS_MAX_INPUT_SIZE; maxInputSize = OPUS_MAX_INPUT_SIZE;
initializationData = new ArrayList<>(3); initializationData = new ArrayList<>(3);
initializationData.add(codecPrivate); initializationData.add(getCodecPrivate(codecId));
initializationData.add( initializationData.add(
ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(codecDelayNs).array()); ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(codecDelayNs).array());
initializationData.add( initializationData.add(
...@@ -2061,7 +2128,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -2061,7 +2128,7 @@ public class MatroskaExtractor implements Extractor {
break; break;
case CODEC_ID_AAC: case CODEC_ID_AAC:
mimeType = MimeTypes.AUDIO_AAC; mimeType = MimeTypes.AUDIO_AAC;
initializationData = Collections.singletonList(codecPrivate); initializationData = Collections.singletonList(getCodecPrivate(codecId));
AacUtil.Config aacConfig = AacUtil.parseAudioSpecificConfig(codecPrivate); AacUtil.Config aacConfig = AacUtil.parseAudioSpecificConfig(codecPrivate);
// Update sampleRate and channelCount from the AudioSpecificConfig initialization data, // Update sampleRate and channelCount from the AudioSpecificConfig initialization data,
// which is more reliable. See [Internal: b/10903778]. // which is more reliable. See [Internal: b/10903778].
...@@ -2096,11 +2163,11 @@ public class MatroskaExtractor implements Extractor { ...@@ -2096,11 +2163,11 @@ public class MatroskaExtractor implements Extractor {
break; break;
case CODEC_ID_FLAC: case CODEC_ID_FLAC:
mimeType = MimeTypes.AUDIO_FLAC; mimeType = MimeTypes.AUDIO_FLAC;
initializationData = Collections.singletonList(codecPrivate); initializationData = Collections.singletonList(getCodecPrivate(codecId));
break; break;
case CODEC_ID_ACM: case CODEC_ID_ACM:
mimeType = MimeTypes.AUDIO_RAW; mimeType = MimeTypes.AUDIO_RAW;
if (parseMsAcmCodecPrivate(new ParsableByteArray(codecPrivate))) { if (parseMsAcmCodecPrivate(new ParsableByteArray(getCodecPrivate(codecId)))) {
pcmEncoding = Util.getPcmEncoding(audioBitDepth); pcmEncoding = Util.getPcmEncoding(audioBitDepth);
if (pcmEncoding == C.ENCODING_INVALID) { if (pcmEncoding == C.ENCODING_INVALID) {
pcmEncoding = Format.NO_VALUE; pcmEncoding = Format.NO_VALUE;
...@@ -2167,7 +2234,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -2167,7 +2234,7 @@ public class MatroskaExtractor implements Extractor {
break; break;
case CODEC_ID_VOBSUB: case CODEC_ID_VOBSUB:
mimeType = MimeTypes.APPLICATION_VOBSUB; mimeType = MimeTypes.APPLICATION_VOBSUB;
initializationData = Collections.singletonList(codecPrivate); initializationData = Collections.singletonList(getCodecPrivate(codecId));
break; break;
case CODEC_ID_PGS: case CODEC_ID_PGS:
mimeType = MimeTypes.APPLICATION_PGS; mimeType = MimeTypes.APPLICATION_PGS;
...@@ -2175,8 +2242,9 @@ public class MatroskaExtractor implements Extractor { ...@@ -2175,8 +2242,9 @@ public class MatroskaExtractor implements Extractor {
case CODEC_ID_DVBSUB: case CODEC_ID_DVBSUB:
mimeType = MimeTypes.APPLICATION_DVBSUBS; mimeType = MimeTypes.APPLICATION_DVBSUBS;
// Init data: composition_page (2), ancillary_page (2) // Init data: composition_page (2), ancillary_page (2)
initializationData = Collections.singletonList(new byte[] {codecPrivate[0], byte[] initializationDataBytes = new byte[4];
codecPrivate[1], codecPrivate[2], codecPrivate[3]}); System.arraycopy(getCodecPrivate(codecId), 0, initializationDataBytes, 0, 4);
initializationData = ImmutableList.of(initializationDataBytes);
break; break;
default: default:
throw new ParserException("Unrecognized codec identifier."); throw new ParserException("Unrecognized codec identifier.");
...@@ -2223,7 +2291,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -2223,7 +2291,7 @@ public class MatroskaExtractor implements Extractor {
} }
int rotationDegrees = Format.NO_VALUE; int rotationDegrees = Format.NO_VALUE;
if (TRACK_NAME_TO_ROTATION_DEGREES.containsKey(name)) { if (name != null && TRACK_NAME_TO_ROTATION_DEGREES.containsKey(name)) {
rotationDegrees = TRACK_NAME_TO_ROTATION_DEGREES.get(name); rotationDegrees = TRACK_NAME_TO_ROTATION_DEGREES.get(name);
} }
if (projectionType == C.PROJECTION_RECTANGULAR if (projectionType == C.PROJECTION_RECTANGULAR
...@@ -2253,9 +2321,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -2253,9 +2321,7 @@ public class MatroskaExtractor implements Extractor {
type = C.TRACK_TYPE_TEXT; type = C.TRACK_TYPE_TEXT;
} else if (MimeTypes.TEXT_SSA.equals(mimeType)) { } else if (MimeTypes.TEXT_SSA.equals(mimeType)) {
type = C.TRACK_TYPE_TEXT; type = C.TRACK_TYPE_TEXT;
initializationData = new ArrayList<>(2); initializationData = ImmutableList.of(SSA_DIALOGUE_FORMAT, getCodecPrivate(codecId));
initializationData.add(SSA_DIALOGUE_FORMAT);
initializationData.add(codecPrivate);
} else if (MimeTypes.APPLICATION_VOBSUB.equals(mimeType) } else if (MimeTypes.APPLICATION_VOBSUB.equals(mimeType)
|| MimeTypes.APPLICATION_PGS.equals(mimeType) || MimeTypes.APPLICATION_PGS.equals(mimeType)
|| MimeTypes.APPLICATION_DVBSUBS.equals(mimeType)) { || MimeTypes.APPLICATION_DVBSUBS.equals(mimeType)) {
...@@ -2264,7 +2330,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -2264,7 +2330,7 @@ public class MatroskaExtractor implements Extractor {
throw new ParserException("Unexpected MIME type."); throw new ParserException("Unexpected MIME type.");
} }
if (!TRACK_NAME_TO_ROTATION_DEGREES.containsKey(name)) { if (name != null && !TRACK_NAME_TO_ROTATION_DEGREES.containsKey(name)) {
formatBuilder.setLabel(name); formatBuilder.setLabel(name);
} }
...@@ -2285,6 +2351,7 @@ public class MatroskaExtractor implements Extractor { ...@@ -2285,6 +2351,7 @@ public class MatroskaExtractor implements Extractor {
} }
/** Forces any pending sample metadata to be flushed to the output. */ /** Forces any pending sample metadata to be flushed to the output. */
@RequiresNonNull("output")
public void outputPendingSampleMetadata() { public void outputPendingSampleMetadata() {
if (trueHdSampleRechunker != null) { if (trueHdSampleRechunker != null) {
trueHdSampleRechunker.outputPendingSampleMetadata(this); trueHdSampleRechunker.outputPendingSampleMetadata(this);
...@@ -2444,5 +2511,26 @@ public class MatroskaExtractor implements Extractor { ...@@ -2444,5 +2511,26 @@ public class MatroskaExtractor implements Extractor {
throw new ParserException("Error parsing MS/ACM codec private"); throw new ParserException("Error parsing MS/ACM codec private");
} }
} }
/**
* Checks that the track has an output.
*
* <p>It is unfortunately not possible to mark {@link MatroskaExtractor#tracks} as only
* containing tracks with output with the nullness checker. This method is used to check that
* fact at runtime.
*/
@EnsuresNonNull("output")
private void assertOutputInitialized() {
checkNotNull(output);
}
@EnsuresNonNull("codecPrivate")
private byte[] getCodecPrivate(String codecId) throws ParserException {
if (codecPrivate == null) {
throw new ParserException("Missing CodecPrivate for codec " + codecId);
}
return codecPrivate;
}
} }
} }
...@@ -138,7 +138,7 @@ public final class PsshAtomUtil { ...@@ -138,7 +138,7 @@ public final class PsshAtomUtil {
if (parsedAtom == null) { if (parsedAtom == null) {
return null; return null;
} }
if (uuid != null && !uuid.equals(parsedAtom.uuid)) { if (!uuid.equals(parsedAtom.uuid)) {
Log.w(TAG, "UUID mismatch. Expected: " + uuid + ", got: " + parsedAtom.uuid + "."); Log.w(TAG, "UUID mismatch. Expected: " + uuid + ", got: " + parsedAtom.uuid + ".");
return null; return null;
} }
......
...@@ -15,6 +15,9 @@ ...@@ -15,6 +15,9 @@
*/ */
package com.google.android.exoplayer2.extractor.ogg; package com.google.android.exoplayer2.extractor.ogg;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.FlacFrameReader; import com.google.android.exoplayer2.extractor.FlacFrameReader;
...@@ -23,11 +26,11 @@ import com.google.android.exoplayer2.extractor.FlacSeekTableSeekMap; ...@@ -23,11 +26,11 @@ import com.google.android.exoplayer2.extractor.FlacSeekTableSeekMap;
import com.google.android.exoplayer2.extractor.FlacStreamMetadata; import com.google.android.exoplayer2.extractor.FlacStreamMetadata;
import com.google.android.exoplayer2.extractor.FlacStreamMetadata.SeekTable; import com.google.android.exoplayer2.extractor.FlacStreamMetadata.SeekTable;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.FlacConstants; import com.google.android.exoplayer2.util.FlacConstants;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.util.Arrays; import java.util.Arrays;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
/** /**
* {@link StreamReader} to extract Flac data out of Ogg byte stream. * {@link StreamReader} to extract Flac data out of Ogg byte stream.
...@@ -68,6 +71,7 @@ import java.util.Arrays; ...@@ -68,6 +71,7 @@ import java.util.Arrays;
} }
@Override @Override
@EnsuresNonNullIf(expression = "#3.format", result = false)
protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) { protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) {
byte[] data = packet.getData(); byte[] data = packet.getData();
@Nullable FlacStreamMetadata streamMetadata = this.streamMetadata; @Nullable FlacStreamMetadata streamMetadata = this.streamMetadata;
...@@ -76,18 +80,26 @@ import java.util.Arrays; ...@@ -76,18 +80,26 @@ import java.util.Arrays;
this.streamMetadata = streamMetadata; this.streamMetadata = streamMetadata;
byte[] metadata = Arrays.copyOfRange(data, 9, packet.limit()); byte[] metadata = Arrays.copyOfRange(data, 9, packet.limit());
setupData.format = streamMetadata.getFormat(metadata, /* id3Metadata= */ null); setupData.format = streamMetadata.getFormat(metadata, /* id3Metadata= */ null);
} else if ((data[0] & 0x7F) == FlacConstants.METADATA_TYPE_SEEK_TABLE) { return true;
}
if ((data[0] & 0x7F) == FlacConstants.METADATA_TYPE_SEEK_TABLE) {
SeekTable seekTable = FlacMetadataReader.readSeekTableMetadataBlock(packet); SeekTable seekTable = FlacMetadataReader.readSeekTableMetadataBlock(packet);
streamMetadata = streamMetadata.copyWithSeekTable(seekTable); streamMetadata = streamMetadata.copyWithSeekTable(seekTable);
this.streamMetadata = streamMetadata; this.streamMetadata = streamMetadata;
flacOggSeeker = new FlacOggSeeker(streamMetadata, seekTable); flacOggSeeker = new FlacOggSeeker(streamMetadata, seekTable);
} else if (isAudioPacket(data)) { return true;
}
if (isAudioPacket(data)) {
if (flacOggSeeker != null) { if (flacOggSeeker != null) {
flacOggSeeker.setFirstFrameOffset(position); flacOggSeeker.setFirstFrameOffset(position);
setupData.oggSeeker = flacOggSeeker; setupData.oggSeeker = flacOggSeeker;
} }
checkNotNull(setupData.format);
return false; return false;
} }
return true; return true;
} }
...@@ -142,7 +154,7 @@ import java.util.Arrays; ...@@ -142,7 +154,7 @@ import java.util.Arrays;
@Override @Override
public SeekMap createSeekMap() { public SeekMap createSeekMap() {
Assertions.checkState(firstFrameOffset != -1); checkState(firstFrameOffset != -1);
return new FlacSeekTableSeekMap(streamMetadata, firstFrameOffset); return new FlacSeekTableSeekMap(streamMetadata, firstFrameOffset);
} }
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.extractor.ogg; package com.google.android.exoplayer2.extractor.ogg;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import static java.lang.Math.min; import static java.lang.Math.min;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
...@@ -25,7 +26,6 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; ...@@ -25,7 +26,6 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.Assertions;
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.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
...@@ -73,7 +73,7 @@ public class OggExtractor implements Extractor { ...@@ -73,7 +73,7 @@ public class OggExtractor implements Extractor {
@Override @Override
public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException { public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
Assertions.checkStateNotNull(output); // Asserts that init has been called. checkStateNotNull(output); // Check that init has been called.
if (streamReader == null) { if (streamReader == null) {
if (!sniffInternal(input)) { if (!sniffInternal(input)) {
throw new ParserException("Failed to determine bitstream type"); throw new ParserException("Failed to determine bitstream type");
......
...@@ -15,12 +15,15 @@ ...@@ -15,12 +15,15 @@
*/ */
package com.google.android.exoplayer2.extractor.ogg; package com.google.android.exoplayer2.extractor.ogg;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.OpusUtil; import com.google.android.exoplayer2.audio.OpusUtil;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
/** /**
* {@link StreamReader} to extract Opus data out of Ogg byte stream. * {@link StreamReader} to extract Opus data out of Ogg byte stream.
...@@ -55,6 +58,7 @@ import java.util.List; ...@@ -55,6 +58,7 @@ import java.util.List;
} }
@Override @Override
@EnsuresNonNullIf(expression = "#3.format", result = false)
protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) { protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) {
if (!headerRead) { if (!headerRead) {
byte[] headerBytes = Arrays.copyOf(packet.getData(), packet.limit()); byte[] headerBytes = Arrays.copyOf(packet.getData(), packet.limit());
...@@ -68,12 +72,13 @@ import java.util.List; ...@@ -68,12 +72,13 @@ import java.util.List;
.setInitializationData(initializationData) .setInitializationData(initializationData)
.build(); .build();
headerRead = true; headerRead = true;
return true;
} else { } else {
checkNotNull(setupData.format); // Has been set when the header was read.
boolean headerPacket = packet.readInt() == OPUS_CODE; boolean headerPacket = packet.readInt() == OPUS_CODE;
packet.setPosition(0); packet.setPosition(0);
return headerPacket; return headerPacket;
} }
return true;
} }
/** /**
......
...@@ -15,7 +15,9 @@ ...@@ -15,7 +15,9 @@
*/ */
package com.google.android.exoplayer2.extractor.ogg; package com.google.android.exoplayer2.extractor.ogg;
import androidx.annotation.Nullable; import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import static com.google.android.exoplayer2.util.Util.castNonNull;
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.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
...@@ -24,10 +26,12 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; ...@@ -24,10 +26,12 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.Assertions;
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.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** StreamReader abstract class. */ /** StreamReader abstract class. */
@SuppressWarnings("UngroupedOverloads") @SuppressWarnings("UngroupedOverloads")
...@@ -39,8 +43,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -39,8 +43,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private static final int STATE_END_OF_INPUT = 3; private static final int STATE_END_OF_INPUT = 3;
static class SetupData { static class SetupData {
Format format; @MonotonicNonNull Format format;
OggSeeker oggSeeker; @MonotonicNonNull OggSeeker oggSeeker;
} }
private final OggPacket oggPacket; private final OggPacket oggPacket;
...@@ -53,13 +57,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -53,13 +57,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private long currentGranule; private long currentGranule;
private int state; private int state;
private int sampleRate; private int sampleRate;
@Nullable private SetupData setupData; private SetupData setupData;
private long lengthOfReadPacket; private long lengthOfReadPacket;
private boolean seekMapSet; private boolean seekMapSet;
private boolean formatSet; private boolean formatSet;
public StreamReader() { public StreamReader() {
oggPacket = new OggPacket(); oggPacket = new OggPacket();
setupData = new SetupData();
} }
void init(ExtractorOutput output, TrackOutput trackOutput) { void init(ExtractorOutput output, TrackOutput trackOutput) {
...@@ -95,7 +100,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -95,7 +100,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} else { } else {
if (state != STATE_READ_HEADERS) { if (state != STATE_READ_HEADERS) {
targetGranule = convertTimeToGranule(timeUs); targetGranule = convertTimeToGranule(timeUs);
oggSeeker.startSeek(targetGranule); castNonNull(oggSeeker).startSeek(targetGranule);
state = STATE_READ_PAYLOAD; state = STATE_READ_PAYLOAD;
} }
} }
...@@ -103,14 +108,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -103,14 +108,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** @see Extractor#read(ExtractorInput, PositionHolder) */ /** @see Extractor#read(ExtractorInput, PositionHolder) */
final int read(ExtractorInput input, PositionHolder seekPosition) throws IOException { final int read(ExtractorInput input, PositionHolder seekPosition) throws IOException {
assertInitialized();
switch (state) { switch (state) {
case STATE_READ_HEADERS: case STATE_READ_HEADERS:
return readHeaders(input); return readHeadersAndUpdateState(input);
case STATE_SKIP_HEADERS: case STATE_SKIP_HEADERS:
input.skipFully((int) payloadStartPosition); input.skipFully((int) payloadStartPosition);
state = STATE_READ_PAYLOAD; state = STATE_READ_PAYLOAD;
return Extractor.RESULT_CONTINUE; return Extractor.RESULT_CONTINUE;
case STATE_READ_PAYLOAD: case STATE_READ_PAYLOAD:
castNonNull(oggSeeker);
return readPayload(input, seekPosition); return readPayload(input, seekPosition);
default: default:
// Never happens. // Never happens.
...@@ -118,20 +125,42 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -118,20 +125,42 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
} }
private int readHeaders(ExtractorInput input) throws IOException { @EnsuresNonNull({"trackOutput", "extractorOutput"})
boolean readingHeaders = true; private void assertInitialized() {
while (readingHeaders) { checkStateNotNull(trackOutput);
castNonNull(extractorOutput);
}
/**
* Read all header packets.
*
* @param input The {@link ExtractorInput} to read data from.
* @return {@code true} if all headers were read. {@code false} if end of the input is
* encountered.
* @throws IOException If reading from the input fails.
*/
@EnsuresNonNullIf(expression = "setupData.format", result = true)
private boolean readHeaders(ExtractorInput input) throws IOException {
while (true) {
if (!oggPacket.populate(input)) { if (!oggPacket.populate(input)) {
state = STATE_END_OF_INPUT; state = STATE_END_OF_INPUT;
return Extractor.RESULT_END_OF_INPUT; return false;
} }
lengthOfReadPacket = input.getPosition() - payloadStartPosition; lengthOfReadPacket = input.getPosition() - payloadStartPosition;
readingHeaders = readHeaders(oggPacket.getPayload(), payloadStartPosition, setupData); if (readHeaders(oggPacket.getPayload(), payloadStartPosition, setupData)) {
if (readingHeaders) {
payloadStartPosition = input.getPosition(); payloadStartPosition = input.getPosition();
} else {
return true; // Current packet is not a header, therefore all headers have been read.
} }
} }
}
@RequiresNonNull({"trackOutput"})
private int readHeadersAndUpdateState(ExtractorInput input) throws IOException {
if (!readHeaders(input)) {
return Extractor.RESULT_END_OF_INPUT;
}
sampleRate = setupData.format.sampleRate; sampleRate = setupData.format.sampleRate;
if (!formatSet) { if (!formatSet) {
...@@ -156,13 +185,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -156,13 +185,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
isLastPage); isLastPage);
} }
setupData = null;
state = STATE_READ_PAYLOAD; state = STATE_READ_PAYLOAD;
// First payload packet. Trim the payload array of the ogg packet after headers have been read. // First payload packet. Trim the payload array of the ogg packet after headers have been read.
oggPacket.trimPayload(); oggPacket.trimPayload();
return Extractor.RESULT_CONTINUE; return Extractor.RESULT_CONTINUE;
} }
@RequiresNonNull({"trackOutput", "oggSeeker", "extractorOutput"})
private int readPayload(ExtractorInput input, PositionHolder seekPosition) throws IOException { private int readPayload(ExtractorInput input, PositionHolder seekPosition) throws IOException {
long position = oggSeeker.read(input); long position = oggSeeker.read(input);
if (position >= 0) { if (position >= 0) {
...@@ -173,7 +202,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -173,7 +202,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
if (!seekMapSet) { if (!seekMapSet) {
SeekMap seekMap = Assertions.checkStateNotNull(oggSeeker.createSeekMap()); SeekMap seekMap = checkStateNotNull(oggSeeker.createSeekMap());
extractorOutput.seekMap(seekMap); extractorOutput.seekMap(seekMap);
seekMapSet = true; seekMapSet = true;
} }
...@@ -234,6 +263,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -234,6 +263,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* @param setupData Setup data to be filled. * @param setupData Setup data to be filled.
* @return Whether the packet contains header data. * @return Whether the packet contains header data.
*/ */
@EnsuresNonNullIf(expression = "#3.format", result = false)
protected abstract boolean readHeaders( protected abstract boolean readHeaders(
ParsableByteArray packet, long position, SetupData setupData) throws IOException; ParsableByteArray packet, long position, SetupData setupData) throws IOException;
......
...@@ -15,6 +15,9 @@ ...@@ -15,6 +15,9 @@
*/ */
package com.google.android.exoplayer2.extractor.ogg; package com.google.android.exoplayer2.extractor.ogg;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
...@@ -26,6 +29,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; ...@@ -26,6 +29,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
/** /**
* {@link StreamReader} to extract Vorbis data out of Ogg byte stream. * {@link StreamReader} to extract Vorbis data out of Ogg byte stream.
...@@ -74,7 +78,7 @@ import java.util.Arrays; ...@@ -74,7 +78,7 @@ import java.util.Arrays;
} }
// ... we need to decode the block size // ... we need to decode the block size
int packetBlockSize = decodeBlockSize(packet.getData()[0], vorbisSetup); int packetBlockSize = decodeBlockSize(packet.getData()[0], checkStateNotNull(vorbisSetup));
// a packet contains samples produced from overlapping the previous and current frame data // a packet contains samples produced from overlapping the previous and current frame data
// (https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-350001.3.2) // (https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-350001.3.2)
int samplesInPacket = seenFirstAudioPacket ? (packetBlockSize + previousPacketBlockSize) / 4 int samplesInPacket = seenFirstAudioPacket ? (packetBlockSize + previousPacketBlockSize) / 4
...@@ -89,9 +93,11 @@ import java.util.Arrays; ...@@ -89,9 +93,11 @@ import java.util.Arrays;
} }
@Override @Override
@EnsuresNonNullIf(expression = "#3.format", result = false)
protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData) protected boolean readHeaders(ParsableByteArray packet, long position, SetupData setupData)
throws IOException { throws IOException {
if (vorbisSetup != null) { if (vorbisSetup != null) {
checkNotNull(setupData.format);
return false; return false;
} }
...@@ -99,6 +105,7 @@ import java.util.Arrays; ...@@ -99,6 +105,7 @@ import java.util.Arrays;
if (vorbisSetup == null) { if (vorbisSetup == null) {
return true; return true;
} }
VorbisSetup vorbisSetup = this.vorbisSetup;
VorbisUtil.VorbisIdHeader idHeader = vorbisSetup.idHeader; VorbisUtil.VorbisIdHeader idHeader = vorbisSetup.idHeader;
...@@ -131,6 +138,8 @@ import java.util.Arrays; ...@@ -131,6 +138,8 @@ import java.util.Arrays;
commentHeader = VorbisUtil.readVorbisCommentHeader(scratch); commentHeader = VorbisUtil.readVorbisCommentHeader(scratch);
return null; return null;
} }
VorbisUtil.VorbisIdHeader vorbisIdHeader = this.vorbisIdHeader;
VorbisUtil.CommentHeader commentHeader = this.commentHeader;
// the third packet contains the setup header // the third packet contains the setup header
byte[] setupHeaderData = new byte[scratch.limit()]; byte[] setupHeaderData = new byte[scratch.limit()];
......
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