Add package level NonNull to extractor.ts

Also remove most classes from the nullness blacklist

PiperOrigin-RevId: 288494712
This commit is contained in:
olly 2020-01-07 15:39:27 +00:00 committed by Oliver Woodman
parent fb42f818ec
commit 63f90adef0
23 changed files with 307 additions and 139 deletions

View file

@ -81,7 +81,10 @@ public final class DtsUtil {
* @return The DTS format parsed from data in the header.
*/
public static Format parseDtsFormat(
byte[] frame, String trackId, @Nullable String language, @Nullable DrmInitData drmInitData) {
byte[] frame,
@Nullable String trackId,
@Nullable String language,
@Nullable DrmInitData drmInitData) {
ParsableBitArray frameBits = getNormalizedFrameHeader(frame);
frameBits.skipBits(32 + 1 + 5 + 1 + 7 + 14); // SYNC, FTYPE, SHORT, CPF, NBLKS, FSIZE
int amode = frameBits.readBits(6);

View file

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.ts;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.Ac3Util;
@ -23,11 +24,15 @@ import com.google.android.exoplayer2.audio.Ac3Util.SyncFrameInfo;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
* Parses a continuous (E-)AC-3 byte stream and extracts individual samples.
@ -47,10 +52,10 @@ public final class Ac3Reader implements ElementaryStreamReader {
private final ParsableBitArray headerScratchBits;
private final ParsableByteArray headerScratchBytes;
private final String language;
@Nullable private final String language;
private String trackFormatId;
private TrackOutput output;
@MonotonicNonNull private String formatId;
@MonotonicNonNull private TrackOutput output;
@State private int state;
private int bytesRead;
@ -60,7 +65,7 @@ public final class Ac3Reader implements ElementaryStreamReader {
// Used when parsing the header.
private long sampleDurationUs;
private Format format;
@MonotonicNonNull private Format format;
private int sampleSize;
// Used when reading the samples.
@ -78,7 +83,7 @@ public final class Ac3Reader implements ElementaryStreamReader {
*
* @param language Track language.
*/
public Ac3Reader(String language) {
public Ac3Reader(@Nullable String language) {
headerScratchBits = new ParsableBitArray(new byte[HEADER_SIZE]);
headerScratchBytes = new ParsableByteArray(headerScratchBits.data);
state = STATE_FINDING_SYNC;
@ -95,7 +100,7 @@ public final class Ac3Reader implements ElementaryStreamReader {
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) {
generator.generateNewId();
trackFormatId = generator.getFormatId();
formatId = generator.getFormatId();
output = extractorOutput.track(generator.getTrackId(), C.TRACK_TYPE_AUDIO);
}
@ -106,6 +111,7 @@ public final class Ac3Reader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
while (data.bytesLeft() > 0) {
switch (state) {
case STATE_FINDING_SYNC:
@ -185,19 +191,28 @@ public final class Ac3Reader implements ElementaryStreamReader {
return false;
}
/**
* Parses the sample header.
*/
@SuppressWarnings("ReferenceEquality")
/** Parses the sample header. */
@RequiresNonNull("output")
private void parseHeader() {
headerScratchBits.setPosition(0);
SyncFrameInfo frameInfo = Ac3Util.parseAc3SyncframeInfo(headerScratchBits);
if (format == null || frameInfo.channelCount != format.channelCount
if (format == null
|| frameInfo.channelCount != format.channelCount
|| frameInfo.sampleRate != format.sampleRate
|| frameInfo.mimeType != format.sampleMimeType) {
format = Format.createAudioSampleFormat(trackFormatId, frameInfo.mimeType, null,
Format.NO_VALUE, Format.NO_VALUE, frameInfo.channelCount, frameInfo.sampleRate, null,
null, 0, language);
|| Util.areEqual(frameInfo.mimeType, format.sampleMimeType)) {
format =
Format.createAudioSampleFormat(
formatId,
frameInfo.mimeType,
null,
Format.NO_VALUE,
Format.NO_VALUE,
frameInfo.channelCount,
frameInfo.sampleRate,
null,
null,
0,
language);
output.format(format);
}
sampleSize = frameInfo.frameSize;

View file

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.ts;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.Ac4Util;
@ -23,12 +24,15 @@ import com.google.android.exoplayer2.audio.Ac4Util.SyncFrameInfo;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** Parses a continuous AC-4 byte stream and extracts individual samples. */
public final class Ac4Reader implements ElementaryStreamReader {
@ -44,10 +48,10 @@ public final class Ac4Reader implements ElementaryStreamReader {
private final ParsableBitArray headerScratchBits;
private final ParsableByteArray headerScratchBytes;
private final String language;
@Nullable private final String language;
private String trackFormatId;
private TrackOutput output;
@MonotonicNonNull private String formatId;
@MonotonicNonNull private TrackOutput output;
@State private int state;
private int bytesRead;
@ -58,7 +62,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
// Used when parsing the header.
private long sampleDurationUs;
private Format format;
@MonotonicNonNull private Format format;
private int sampleSize;
// Used when reading the samples.
@ -74,7 +78,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
*
* @param language Track language.
*/
public Ac4Reader(String language) {
public Ac4Reader(@Nullable String language) {
headerScratchBits = new ParsableBitArray(new byte[Ac4Util.HEADER_SIZE_FOR_PARSER]);
headerScratchBytes = new ParsableByteArray(headerScratchBits.data);
state = STATE_FINDING_SYNC;
@ -95,7 +99,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) {
generator.generateNewId();
trackFormatId = generator.getFormatId();
formatId = generator.getFormatId();
output = extractorOutput.track(generator.getTrackId(), C.TRACK_TYPE_AUDIO);
}
@ -106,6 +110,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
while (data.bytesLeft() > 0) {
switch (state) {
case STATE_FINDING_SYNC:
@ -185,7 +190,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
}
/** Parses the sample header. */
@SuppressWarnings("ReferenceEquality")
@RequiresNonNull("output")
private void parseHeader() {
headerScratchBits.setPosition(0);
SyncFrameInfo frameInfo = Ac4Util.parseAc4SyncframeInfo(headerScratchBits);
@ -195,7 +200,7 @@ public final class Ac4Reader implements ElementaryStreamReader {
|| !MimeTypes.AUDIO_AC4.equals(format.sampleMimeType)) {
format =
Format.createAudioSampleFormat(
trackFormatId,
formatId,
MimeTypes.AUDIO_AC4,
/* codecs= */ null,
/* bitrate= */ Format.NO_VALUE,

View file

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.ts;
import android.util.Pair;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
@ -23,13 +24,18 @@ import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.util.Arrays;
import java.util.Collections;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
* Parses a continuous ADTS byte stream and extracts individual frames.
@ -62,11 +68,11 @@ public final class AdtsReader implements ElementaryStreamReader {
private final boolean exposeId3;
private final ParsableBitArray adtsScratch;
private final ParsableByteArray id3HeaderBuffer;
private final String language;
@Nullable private final String language;
private String formatId;
private TrackOutput output;
private TrackOutput id3Output;
@MonotonicNonNull private String formatId;
@MonotonicNonNull private TrackOutput output;
@MonotonicNonNull private TrackOutput id3Output;
private int state;
private int bytesRead;
@ -90,7 +96,7 @@ public final class AdtsReader implements ElementaryStreamReader {
// Used when reading the samples.
private long timeUs;
private TrackOutput currentOutput;
@MonotonicNonNull private TrackOutput currentOutput;
private long currentSampleDuration;
/**
@ -104,7 +110,7 @@ public final class AdtsReader implements ElementaryStreamReader {
* @param exposeId3 True if the reader should expose ID3 information.
* @param language Track language.
*/
public AdtsReader(boolean exposeId3, String language) {
public AdtsReader(boolean exposeId3, @Nullable String language) {
adtsScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]);
id3HeaderBuffer = new ParsableByteArray(Arrays.copyOf(ID3_IDENTIFIER, ID3_HEADER_SIZE));
setFindingSampleState();
@ -130,6 +136,7 @@ public final class AdtsReader implements ElementaryStreamReader {
idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_AUDIO);
currentOutput = output;
if (exposeId3) {
idGenerator.generateNewId();
id3Output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_METADATA);
@ -147,6 +154,7 @@ public final class AdtsReader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) throws ParserException {
assertTracksCreated();
while (data.bytesLeft() > 0) {
switch (state) {
case STATE_FINDING_SAMPLE:
@ -425,9 +433,8 @@ public final class AdtsReader implements ElementaryStreamReader {
return true;
}
/**
* Parses the Id3 header.
*/
/** Parses the Id3 header. */
@RequiresNonNull("id3Output")
private void parseId3Header() {
id3Output.sampleData(id3HeaderBuffer, ID3_HEADER_SIZE);
id3HeaderBuffer.setPosition(ID3_SIZE_OFFSET);
@ -435,9 +442,8 @@ public final class AdtsReader implements ElementaryStreamReader {
id3HeaderBuffer.readSynchSafeInt() + ID3_HEADER_SIZE);
}
/**
* Parses the sample header.
*/
/** Parses the sample header. */
@RequiresNonNull("output")
private void parseAdtsHeader() throws ParserException {
adtsScratch.setPosition(0);
@ -487,9 +493,8 @@ public final class AdtsReader implements ElementaryStreamReader {
setReadingSampleState(output, sampleDurationUs, 0, sampleSize);
}
/**
* Reads the rest of the sample
*/
/** Reads the rest of the sample */
@RequiresNonNull("currentOutput")
private void readSample(ParsableByteArray data) {
int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead);
currentOutput.sampleData(data, bytesToRead);
@ -501,4 +506,10 @@ public final class AdtsReader implements ElementaryStreamReader {
}
}
@EnsuresNonNull({"output", "currentOutput", "id3Output"})
private void assertTracksCreated() {
Assertions.checkNotNull(output);
Util.castNonNull(currentOutput);
Util.castNonNull(id3Output);
}
}

View file

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.ts;
import android.util.SparseArray;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo;
import com.google.android.exoplayer2.text.cea.Cea708InitializationData;
@ -134,6 +135,7 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
return new SparseArray<>();
}
@Nullable
@Override
public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) {
switch (streamType) {
@ -247,7 +249,7 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
// Skip reserved (8).
scratchDescriptorData.skipBytes(1);
List<byte[]> initializationData = null;
@Nullable List<byte[]> initializationData = null;
// The wide_aspect_ratio flag only has meaning for CEA-708.
if (isDigital) {
boolean isWideAspectRatio = (flags & 0x40) != 0;

View file

@ -15,13 +15,17 @@
*/
package com.google.android.exoplayer2.extractor.ts;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.DtsUtil;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableByteArray;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
* Parses a continuous DTS byte stream and extracts individual samples.
@ -35,10 +39,10 @@ public final class DtsReader implements ElementaryStreamReader {
private static final int HEADER_SIZE = 18;
private final ParsableByteArray headerScratchBytes;
private final String language;
@Nullable private final String language;
private String formatId;
private TrackOutput output;
@MonotonicNonNull private String formatId;
@MonotonicNonNull private TrackOutput output;
private int state;
private int bytesRead;
@ -48,7 +52,7 @@ public final class DtsReader implements ElementaryStreamReader {
// Used when parsing the header.
private long sampleDurationUs;
private Format format;
@MonotonicNonNull private Format format;
private int sampleSize;
// Used when reading the samples.
@ -59,7 +63,7 @@ public final class DtsReader implements ElementaryStreamReader {
*
* @param language Track language.
*/
public DtsReader(String language) {
public DtsReader(@Nullable String language) {
headerScratchBytes = new ParsableByteArray(new byte[HEADER_SIZE]);
state = STATE_FINDING_SYNC;
this.language = language;
@ -86,6 +90,7 @@ public final class DtsReader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
while (data.bytesLeft() > 0) {
switch (state) {
case STATE_FINDING_SYNC:
@ -162,9 +167,8 @@ public final class DtsReader implements ElementaryStreamReader {
return false;
}
/**
* Parses the sample header.
*/
/** Parses the sample header. */
@RequiresNonNull("output")
private void parseHeader() {
byte[] frameData = headerScratchBytes.data;
if (format == null) {

View file

@ -64,12 +64,12 @@ public final class DvbSubtitleReader implements ElementaryStreamReader {
Format.createImageSampleFormat(
idGenerator.getFormatId(),
MimeTypes.APPLICATION_DVBSUBS,
null,
/* codecs= */ null,
Format.NO_VALUE,
0,
/* selectionFlags= */ 0,
Collections.singletonList(subtitleInfo.initializationData),
subtitleInfo.language,
null));
/* drmInitData= */ null));
outputs[i] = output;
}
}

View file

@ -16,16 +16,20 @@
package com.google.android.exoplayer2.extractor.ts;
import android.util.Pair;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.util.Arrays;
import java.util.Collections;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Parses a continuous H262 byte stream and extracts individual frames.
@ -38,27 +42,27 @@ public final class H262Reader implements ElementaryStreamReader {
private static final int START_GROUP = 0xB8;
private static final int START_USER_DATA = 0xB2;
private String formatId;
private TrackOutput output;
@MonotonicNonNull private String formatId;
@MonotonicNonNull private TrackOutput output;
// Maps (frame_rate_code - 1) indices to values, as defined in ITU-T H.262 Table 6-4.
private static final double[] FRAME_RATE_VALUES = new double[] {
24000d / 1001, 24, 25, 30000d / 1001, 30, 50, 60000d / 1001, 60};
@Nullable private final UserDataReader userDataReader;
@Nullable private final ParsableByteArray userDataParsable;
// State that should be reset on seek.
@Nullable private final NalUnitTargetBuffer userData;
private final boolean[] prefixFlags;
private final CsdBuffer csdBuffer;
private long totalBytesWritten;
private boolean startedFirstSample;
// State that should not be reset on seek.
private boolean hasOutputFormat;
private long frameDurationUs;
private final UserDataReader userDataReader;
private final ParsableByteArray userDataParsable;
// State that should be reset on seek.
private final boolean[] prefixFlags;
private final CsdBuffer csdBuffer;
private final NalUnitTargetBuffer userData;
private long totalBytesWritten;
private boolean startedFirstSample;
// Per packet state that gets reset at the start of each packet.
private long pesTimeUs;
@ -72,7 +76,7 @@ public final class H262Reader implements ElementaryStreamReader {
this(null);
}
/* package */ H262Reader(UserDataReader userDataReader) {
/* package */ H262Reader(@Nullable UserDataReader userDataReader) {
this.userDataReader = userDataReader;
prefixFlags = new boolean[4];
csdBuffer = new CsdBuffer(128);
@ -89,7 +93,7 @@ public final class H262Reader implements ElementaryStreamReader {
public void seek() {
NalUnitUtil.clearPrefixFlags(prefixFlags);
csdBuffer.reset();
if (userDataReader != null) {
if (userData != null) {
userData.reset();
}
totalBytesWritten = 0;
@ -114,6 +118,7 @@ public final class H262Reader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
int offset = data.getPosition();
int limit = data.limit();
byte[] dataArray = data.data;
@ -130,7 +135,7 @@ public final class H262Reader implements ElementaryStreamReader {
if (!hasOutputFormat) {
csdBuffer.onData(dataArray, offset, limit);
}
if (userDataReader != null) {
if (userData != null) {
userData.appendToNalUnit(dataArray, offset, limit);
}
return;
@ -157,7 +162,7 @@ public final class H262Reader implements ElementaryStreamReader {
hasOutputFormat = true;
}
}
if (userDataReader != null) {
if (userData != null) {
int bytesAlreadyPassed = 0;
if (lengthToStartCode > 0) {
userData.appendToNalUnit(dataArray, offset, startCodeOffset);
@ -167,8 +172,8 @@ public final class H262Reader implements ElementaryStreamReader {
if (userData.endNalUnit(bytesAlreadyPassed)) {
int unescapedLength = NalUnitUtil.unescapeStream(userData.nalData, userData.nalLength);
userDataParsable.reset(userData.nalData, unescapedLength);
userDataReader.consume(sampleTimeUs, userDataParsable);
Util.castNonNull(userDataParsable).reset(userData.nalData, unescapedLength);
Util.castNonNull(userDataReader).consume(sampleTimeUs, userDataParsable);
}
if (startCodeValue == START_USER_DATA && data.data[startCodeOffset + 2] == 0x1) {
@ -211,10 +216,10 @@ public final class H262Reader implements ElementaryStreamReader {
*
* @param csdBuffer The csd buffer.
* @param formatId The id for the generated format. May be null.
* @return A pair consisting of the {@link Format} and the frame duration in microseconds, or
* 0 if the duration could not be determined.
* @return A pair consisting of the {@link Format} and the frame duration in microseconds, or 0 if
* the duration could not be determined.
*/
private static Pair<Format, Long> parseCsdBuffer(CsdBuffer csdBuffer, String formatId) {
private static Pair<Format, Long> parseCsdBuffer(CsdBuffer csdBuffer, @Nullable String formatId) {
byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length);
int firstByte = csdData[4] & 0xFF;

View file

@ -23,15 +23,21 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.NalUnitUtil.SpsData;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.ParsableNalUnitBitArray;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
* Parses a continuous H264 byte stream and extracts individual frames.
@ -51,9 +57,9 @@ public final class H264Reader implements ElementaryStreamReader {
private long totalBytesWritten;
private final boolean[] prefixFlags;
private String formatId;
private TrackOutput output;
private SampleReader sampleReader;
@MonotonicNonNull private String formatId;
@MonotonicNonNull private TrackOutput output;
@MonotonicNonNull private SampleReader sampleReader;
// State that should not be reset on seek.
private boolean hasOutputFormat;
@ -87,13 +93,15 @@ public final class H264Reader implements ElementaryStreamReader {
@Override
public void seek() {
totalBytesWritten = 0;
randomAccessIndicator = false;
NalUnitUtil.clearPrefixFlags(prefixFlags);
sps.reset();
pps.reset();
sei.reset();
sampleReader.reset();
totalBytesWritten = 0;
randomAccessIndicator = false;
if (sampleReader != null) {
sampleReader.reset();
}
}
@Override
@ -113,6 +121,8 @@ public final class H264Reader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) {
assertTracksCreated();
int offset = data.getPosition();
int limit = data.limit();
byte[] dataArray = data.data;
@ -159,6 +169,7 @@ public final class H264Reader implements ElementaryStreamReader {
// Do nothing.
}
@RequiresNonNull("sampleReader")
private void startNalUnit(long position, int nalUnitType, long pesTimeUs) {
if (!hasOutputFormat || sampleReader.needsSpsPps()) {
sps.startNalUnit(nalUnitType);
@ -168,6 +179,7 @@ public final class H264Reader implements ElementaryStreamReader {
sampleReader.startNalUnit(position, nalUnitType, pesTimeUs);
}
@RequiresNonNull("sampleReader")
private void nalUnitData(byte[] dataArray, int offset, int limit) {
if (!hasOutputFormat || sampleReader.needsSpsPps()) {
sps.appendToNalUnit(dataArray, offset, limit);
@ -177,6 +189,7 @@ public final class H264Reader implements ElementaryStreamReader {
sampleReader.appendToNalUnit(dataArray, offset, limit);
}
@RequiresNonNull({"output", "sampleReader"})
private void endNalUnit(long position, int offset, int discardPadding, long pesTimeUs) {
if (!hasOutputFormat || sampleReader.needsSpsPps()) {
sps.endNalUnit(discardPadding);
@ -237,6 +250,12 @@ public final class H264Reader implements ElementaryStreamReader {
}
}
@EnsuresNonNull({"output", "sampleReader"})
private void assertTracksCreated() {
Assertions.checkStateNotNull(output);
Util.castNonNull(sampleReader);
}
/** Consumes a stream of NAL units and outputs samples. */
private static final class SampleReader {
@ -478,7 +497,7 @@ public final class H264Reader implements ElementaryStreamReader {
private boolean isComplete;
private boolean hasSliceType;
private SpsData spsData;
@Nullable private SpsData spsData;
private int nalRefIdc;
private int sliceType;
private int frameNum;
@ -542,6 +561,8 @@ public final class H264Reader implements ElementaryStreamReader {
private boolean isFirstVclNalUnitOfPicture(SliceHeaderData other) {
// See ISO 14496-10 subsection 7.4.1.2.4.
SpsData spsData = Assertions.checkStateNotNull(this.spsData);
SpsData otherSpsData = Assertions.checkStateNotNull(other.spsData);
return isComplete
&& (!other.isComplete
|| frameNum != other.frameNum
@ -552,15 +573,15 @@ public final class H264Reader implements ElementaryStreamReader {
&& bottomFieldFlag != other.bottomFieldFlag)
|| (nalRefIdc != other.nalRefIdc && (nalRefIdc == 0 || other.nalRefIdc == 0))
|| (spsData.picOrderCountType == 0
&& other.spsData.picOrderCountType == 0
&& otherSpsData.picOrderCountType == 0
&& (picOrderCntLsb != other.picOrderCntLsb
|| deltaPicOrderCntBottom != other.deltaPicOrderCntBottom))
|| (spsData.picOrderCountType == 1
&& other.spsData.picOrderCountType == 1
&& otherSpsData.picOrderCountType == 1
&& (deltaPicOrderCnt0 != other.deltaPicOrderCnt0
|| deltaPicOrderCnt1 != other.deltaPicOrderCnt1))
|| idrPicFlag != other.idrPicFlag
|| (idrPicFlag && other.idrPicFlag && idrPicId != other.idrPicId));
|| (idrPicFlag && idrPicId != other.idrPicId));
}
}
}

View file

@ -20,12 +20,18 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
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;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.ParsableNalUnitBitArray;
import com.google.android.exoplayer2.util.Util;
import java.util.Collections;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
* Parses a continuous H.265 byte stream and extracts individual frames.
@ -46,9 +52,9 @@ public final class H265Reader implements ElementaryStreamReader {
private final SeiReader seiReader;
private String formatId;
private TrackOutput output;
private SampleReader sampleReader;
@MonotonicNonNull private String formatId;
@MonotonicNonNull private TrackOutput output;
@MonotonicNonNull private SampleReader sampleReader;
// State that should not be reset on seek.
private boolean hasOutputFormat;
@ -84,14 +90,16 @@ public final class H265Reader implements ElementaryStreamReader {
@Override
public void seek() {
totalBytesWritten = 0;
NalUnitUtil.clearPrefixFlags(prefixFlags);
vps.reset();
sps.reset();
pps.reset();
prefixSei.reset();
suffixSei.reset();
sampleReader.reset();
totalBytesWritten = 0;
if (sampleReader != null) {
sampleReader.reset();
}
}
@Override
@ -111,6 +119,8 @@ public final class H265Reader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) {
assertTracksCreated();
while (data.bytesLeft() > 0) {
int offset = data.getPosition();
int limit = data.limit();
@ -160,6 +170,7 @@ public final class H265Reader implements ElementaryStreamReader {
// Do nothing.
}
@RequiresNonNull("sampleReader")
private void startNalUnit(long position, int offset, int nalUnitType, long pesTimeUs) {
if (hasOutputFormat) {
sampleReader.startNalUnit(position, offset, nalUnitType, pesTimeUs);
@ -172,6 +183,7 @@ public final class H265Reader implements ElementaryStreamReader {
suffixSei.startNalUnit(nalUnitType);
}
@RequiresNonNull("sampleReader")
private void nalUnitData(byte[] dataArray, int offset, int limit) {
if (hasOutputFormat) {
sampleReader.readNalUnitData(dataArray, offset, limit);
@ -184,6 +196,7 @@ public final class H265Reader implements ElementaryStreamReader {
suffixSei.appendToNalUnit(dataArray, offset, limit);
}
@RequiresNonNull({"output", "sampleReader"})
private void endNalUnit(long position, int offset, int discardPadding, long pesTimeUs) {
if (hasOutputFormat) {
sampleReader.endNalUnit(position, offset);
@ -214,8 +227,11 @@ public final class H265Reader implements ElementaryStreamReader {
}
}
private static Format parseMediaFormat(String formatId, NalUnitTargetBuffer vps,
NalUnitTargetBuffer sps, NalUnitTargetBuffer pps) {
private static Format parseMediaFormat(
@Nullable String formatId,
NalUnitTargetBuffer vps,
NalUnitTargetBuffer sps,
NalUnitTargetBuffer pps) {
// Build codec-specific data.
byte[] csd = new byte[vps.nalLength + sps.nalLength + pps.nalLength];
System.arraycopy(vps.nalData, 0, csd, 0, vps.nalLength);
@ -389,6 +405,12 @@ public final class H265Reader implements ElementaryStreamReader {
}
}
@EnsuresNonNull({"output", "sampleReader"})
private void assertTracksCreated() {
Assertions.checkStateNotNull(output);
Util.castNonNull(sampleReader);
}
private static final class SampleReader {
/**

View file

@ -23,9 +23,11 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
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.ParsableByteArray;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Parses ID3 data and extracts individual text information frames.
@ -36,7 +38,7 @@ public final class Id3Reader implements ElementaryStreamReader {
private final ParsableByteArray id3Header;
private TrackOutput output;
@MonotonicNonNull private TrackOutput output;
// State that should be reset on seek.
private boolean writingSample;
@ -76,6 +78,7 @@ public final class Id3Reader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
if (!writingSample) {
return;
}
@ -106,6 +109,7 @@ public final class Id3Reader implements ElementaryStreamReader {
@Override
public void packetFinished() {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
if (!writingSample || sampleSize == 0 || sampleBytesRead != sampleSize) {
return;
}

View file

@ -23,11 +23,14 @@ import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.util.Collections;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
* Parses and extracts samples from an AAC/LATM elementary stream.
@ -43,14 +46,14 @@ public final class LatmReader implements ElementaryStreamReader {
private static final int SYNC_BYTE_FIRST = 0x56;
private static final int SYNC_BYTE_SECOND = 0xE0;
private final String language;
@Nullable private final String language;
private final ParsableByteArray sampleDataBuffer;
private final ParsableBitArray sampleBitArray;
// Track output info.
private TrackOutput output;
private Format format;
private String formatId;
@MonotonicNonNull private TrackOutput output;
@MonotonicNonNull private String formatId;
@MonotonicNonNull private Format format;
// Parser state info.
private int state;
@ -99,6 +102,7 @@ public final class LatmReader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) throws ParserException {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
int bytesToRead;
while (data.bytesLeft() > 0) {
switch (state) {
@ -150,6 +154,7 @@ public final class LatmReader implements ElementaryStreamReader {
*
* @param data A {@link ParsableBitArray} containing the AudioMuxElement's bytes.
*/
@RequiresNonNull("output")
private void parseAudioMuxElement(ParsableBitArray data) throws ParserException {
boolean useSameStreamMux = data.readBit();
if (!useSameStreamMux) {
@ -173,9 +178,8 @@ public final class LatmReader implements ElementaryStreamReader {
}
}
/**
* Parses a StreamMuxConfig as defined in ISO/IEC 14496-3:2009 Section 1.7.3.1, Table 1.42.
*/
/** Parses a StreamMuxConfig as defined in ISO/IEC 14496-3:2009 Section 1.7.3.1, Table 1.42. */
@RequiresNonNull("output")
private void parseStreamMuxConfig(ParsableBitArray data) throws ParserException {
int audioMuxVersion = data.readBits(1);
audioMuxVersionA = audioMuxVersion == 1 ? data.readBits(1) : 0;
@ -198,9 +202,19 @@ public final class LatmReader implements ElementaryStreamReader {
data.setPosition(startPosition);
byte[] initData = new byte[(readBits + 7) / 8];
data.readBits(initData, 0, readBits);
Format format = Format.createAudioSampleFormat(formatId, MimeTypes.AUDIO_AAC, null,
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRateHz,
Collections.singletonList(initData), null, 0, language);
Format format =
Format.createAudioSampleFormat(
formatId,
MimeTypes.AUDIO_AAC,
/* codecs= */ null,
Format.NO_VALUE,
Format.NO_VALUE,
channelCount,
sampleRateHz,
Collections.singletonList(initData),
/* drmInitData= */ null,
/* selectionFlags= */ 0,
language);
if (!format.equals(this.format)) {
this.format = format;
sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / format.sampleRate;
@ -280,6 +294,7 @@ public final class LatmReader implements ElementaryStreamReader {
}
}
@RequiresNonNull("output")
private void parsePayloadMux(ParsableBitArray data, int muxLengthBytes) {
// The start of sample data in
int bitPosition = data.getPosition();

View file

@ -21,7 +21,11 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableByteArray;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
* Parses a continuous MPEG Audio byte stream and extracts individual frames.
@ -36,10 +40,10 @@ public final class MpegAudioReader implements ElementaryStreamReader {
private final ParsableByteArray headerScratch;
private final MpegAudioHeader header;
private final String language;
@Nullable private final String language;
private String formatId;
private TrackOutput output;
@MonotonicNonNull private TrackOutput output;
@MonotonicNonNull private String formatId;
private int state;
private int frameBytesRead;
@ -59,7 +63,7 @@ public final class MpegAudioReader implements ElementaryStreamReader {
this(null);
}
public MpegAudioReader(String language) {
public MpegAudioReader(@Nullable String language) {
state = STATE_FINDING_HEADER;
// The first byte of an MPEG Audio frame header is always 0xFF.
headerScratch = new ParsableByteArray(4);
@ -89,6 +93,7 @@ public final class MpegAudioReader implements ElementaryStreamReader {
@Override
public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
while (data.bytesLeft() > 0) {
switch (state) {
case STATE_FINDING_HEADER:
@ -146,20 +151,21 @@ public final class MpegAudioReader implements ElementaryStreamReader {
/**
* Attempts to read the remaining two bytes of the frame header.
* <p>
* If a frame header is read in full then the state is changed to {@link #STATE_READING_FRAME},
*
* <p>If a frame header is read in full then the state is changed to {@link #STATE_READING_FRAME},
* the media format is output if this has not previously occurred, the four header bytes are
* output as sample data, and the position of the source is advanced to the byte that immediately
* follows the header.
* <p>
* If a frame header is read in full but cannot be parsed then the state is changed to
* {@link #STATE_READING_HEADER}.
* <p>
* If a frame header is not read in full then the position of the source is advanced to the limit,
* and the method should be called again with the next source to continue the read.
*
* <p>If a frame header is read in full but cannot be parsed then the state is changed to {@link
* #STATE_READING_HEADER}.
*
* <p>If a frame header is not read in full then the position of the source is advanced to the
* limit, and the method should be called again with the next source to continue the read.
*
* @param source The source from which to read.
*/
@RequiresNonNull("output")
private void readHeaderRemainder(ParsableByteArray source) {
int bytesToRead = Math.min(source.bytesLeft(), HEADER_SIZE - frameBytesRead);
source.readBytes(headerScratch.data, frameBytesRead, bytesToRead);
@ -195,16 +201,17 @@ public final class MpegAudioReader implements ElementaryStreamReader {
/**
* Attempts to read the remainder of the frame.
* <p>
* If a frame is read in full then true is returned. The frame will have been output, and the
*
* <p>If a frame is read in full then true is returned. The frame will have been output, and the
* position of the source will have been advanced to the byte that immediately follows the end of
* the frame.
* <p>
* If a frame is not read in full then the position of the source will have been advanced to the
* limit, and the method should be called again with the next source to continue the read.
*
* <p>If a frame is not read in full then the position of the source will have been advanced to
* the limit, and the method should be called again with the next source to continue the read.
*
* @param source The source from which to read.
*/
@RequiresNonNull("output")
private void readFrameRemainder(ParsableByteArray source) {
int bytesToRead = Math.min(source.bytesLeft(), frameSize - frameBytesRead);
output.sampleData(source, bytesToRead);
@ -219,5 +226,4 @@ public final class MpegAudioReader implements ElementaryStreamReader {
frameBytesRead = 0;
state = STATE_FINDING_HEADER;
}
}

View file

@ -15,13 +15,17 @@
*/
package com.google.android.exoplayer2.extractor.ts;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
* Parses PES packet data and extracts samples.
@ -45,7 +49,7 @@ public final class PesReader implements TsPayloadReader {
private int state;
private int bytesRead;
private TimestampAdjuster timestampAdjuster;
@MonotonicNonNull private TimestampAdjuster timestampAdjuster;
private boolean ptsFlag;
private boolean dtsFlag;
private boolean seenFirstDts;
@ -79,6 +83,8 @@ public final class PesReader implements TsPayloadReader {
@Override
public final void consume(ParsableByteArray data, @Flags int flags) throws ParserException {
Assertions.checkStateNotNull(timestampAdjuster); // Asserts init has been called.
if ((flags & FLAG_PAYLOAD_UNIT_START_INDICATOR) != 0) {
switch (state) {
case STATE_FINDING_HEADER:
@ -119,7 +125,7 @@ public final class PesReader implements TsPayloadReader {
int readLength = Math.min(MAX_HEADER_EXTENSION_SIZE, extendedHeaderLength);
// Read as much of the extended header as we're interested in, and skip the rest.
if (continueRead(data, pesScratch.data, readLength)
&& continueRead(data, null, extendedHeaderLength)) {
&& continueRead(data, /* target= */ null, extendedHeaderLength)) {
parseHeaderExtension();
flags |= dataAlignmentIndicator ? FLAG_DATA_ALIGNMENT_INDICATOR : 0;
reader.packetStarted(timeUs, flags);
@ -162,7 +168,8 @@ public final class PesReader implements TsPayloadReader {
* @param targetLength The target length of the read.
* @return Whether the target length has been reached.
*/
private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) {
private boolean continueRead(
ParsableByteArray source, @Nullable byte[] target, int targetLength) {
int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead);
if (bytesToRead <= 0) {
return true;
@ -207,6 +214,7 @@ public final class PesReader implements TsPayloadReader {
return true;
}
@RequiresNonNull("timestampAdjuster")
private void parseHeaderExtension() {
pesScratch.setPosition(0);
timeUs = C.TIME_UNSET;

View file

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.ts;
import android.util.SparseArray;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.Extractor;
@ -25,10 +26,13 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.io.IOException;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
* Extracts data from the MPEG-2 PS container format.
@ -67,8 +71,8 @@ public final class PsExtractor implements Extractor {
private long lastTrackPosition;
// Accessed only by the loading thread.
private PsBinarySearchSeeker psBinarySearchSeeker;
private ExtractorOutput output;
@Nullable private PsBinarySearchSeeker psBinarySearchSeeker;
@MonotonicNonNull private ExtractorOutput output;
private boolean hasOutputSeekMap;
public PsExtractor() {
@ -160,6 +164,7 @@ public final class PsExtractor implements Extractor {
@Override
public int read(ExtractorInput input, PositionHolder seekPosition)
throws IOException, InterruptedException {
Assertions.checkStateNotNull(output); // Asserts init has been called.
long inputLength = input.getLength();
boolean canReadDuration = inputLength != C.LENGTH_UNSET;
@ -221,7 +226,7 @@ public final class PsExtractor implements Extractor {
PesReader payloadReader = psPayloadReaders.get(streamId);
if (!foundAllTracks) {
if (payloadReader == null) {
ElementaryStreamReader elementaryStreamReader = null;
@Nullable ElementaryStreamReader elementaryStreamReader = null;
if (streamId == PRIVATE_STREAM_1) {
// Private stream, used for AC3 audio.
// NOTE: This may need further parsing to determine if its DTS, but that's likely only
@ -278,6 +283,7 @@ public final class PsExtractor implements Extractor {
// Internals.
@RequiresNonNull("output")
private void maybeOutputSeekMap(long inputLength) {
if (!hasOutputSeekMap) {
hasOutputSeekMap = true;

View file

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.extractor.ts;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
@ -45,7 +46,7 @@ public final class SeiReader {
idGenerator.generateNewId();
TrackOutput output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
Format channelFormat = closedCaptionFormats.get(i);
String channelMimeType = channelFormat.sampleMimeType;
@Nullable String channelMimeType = channelFormat.sampleMimeType;
Assertions.checkArgument(MimeTypes.APPLICATION_CEA608.equals(channelMimeType)
|| MimeTypes.APPLICATION_CEA708.equals(channelMimeType),
"Invalid closed caption mime type provided: " + channelMimeType);
@ -69,5 +70,4 @@ public final class SeiReader {
public void consume(long pesTimeUs, ParsableByteArray seiBuffer) {
CeaUtil.consume(pesTimeUs, seiBuffer, outputs);
}
}

View file

@ -19,17 +19,21 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.android.exoplayer2.util.Util;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Parses splice info sections as defined by SCTE35.
*/
public final class SpliceInfoSectionReader implements SectionPayloadReader {
private TimestampAdjuster timestampAdjuster;
private TrackOutput output;
@MonotonicNonNull private TimestampAdjuster timestampAdjuster;
@MonotonicNonNull private TrackOutput output;
private boolean formatDeclared;
@Override
@ -44,6 +48,7 @@ public final class SpliceInfoSectionReader implements SectionPayloadReader {
@Override
public void consume(ParsableByteArray sectionData) {
assertInitialized();
if (!formatDeclared) {
if (timestampAdjuster.getTimestampOffsetUs() == C.TIME_UNSET) {
// There is not enough information to initialize the timestamp adjuster.
@ -59,4 +64,9 @@ public final class SpliceInfoSectionReader implements SectionPayloadReader {
sampleSize, 0, null);
}
@EnsuresNonNull({"timestampAdjuster", "output"})
private void assertInitialized() {
Assertions.checkStateNotNull(timestampAdjuster);
Util.castNonNull(output);
}
}

View file

@ -21,6 +21,7 @@ import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.Extractor;
@ -587,8 +588,11 @@ public final class TsExtractor implements Extractor {
continue;
}
TsPayloadReader reader = mode == MODE_HLS && streamType == TS_STREAM_TYPE_ID3 ? id3Reader
: payloadReaderFactory.createPayloadReader(streamType, esInfo);
@Nullable
TsPayloadReader reader =
mode == MODE_HLS && streamType == TS_STREAM_TYPE_ID3
? id3Reader
: payloadReaderFactory.createPayloadReader(streamType, esInfo);
if (mode != MODE_HLS
|| elementaryPid < trackIdToPidScratch.get(trackId, MAX_PID_PLUS_ONE)) {
trackIdToPidScratch.put(trackId, elementaryPid);
@ -602,7 +606,7 @@ public final class TsExtractor implements Extractor {
int trackPid = trackIdToPidScratch.valueAt(i);
trackIds.put(trackId, true);
trackPids.put(trackPid, true);
TsPayloadReader reader = trackIdToReaderScratch.valueAt(i);
@Nullable TsPayloadReader reader = trackIdToReaderScratch.valueAt(i);
if (reader != null) {
if (reader != id3Reader) {
reader.init(timestampAdjuster, output,

View file

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.ts;
import android.util.SparseArray;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
@ -53,11 +54,11 @@ public interface TsPayloadReader {
*
* @param streamType Stream type value as defined in the PMT entry or associated descriptors.
* @param esInfo Information associated to the elementary stream provided in the PMT.
* @return A {@link TsPayloadReader} for the packet stream carried by the provided pid.
* @return A {@link TsPayloadReader} for the packet stream carried by the provided pid, or
* {@code null} if the stream is not supported.
*/
@Nullable
TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo);
}
/**
@ -66,18 +67,21 @@ public interface TsPayloadReader {
final class EsInfo {
public final int streamType;
public final String language;
@Nullable public final String language;
public final List<DvbSubtitleInfo> dvbSubtitleInfos;
public final byte[] descriptorBytes;
/**
* @param streamType The type of the stream as defined by the
* {@link TsExtractor}{@code .TS_STREAM_TYPE_*}.
* @param streamType The type of the stream as defined by the {@link TsExtractor}{@code
* .TS_STREAM_TYPE_*}.
* @param language The language of the stream, as defined by ISO/IEC 13818-1, section 2.6.18.
* @param dvbSubtitleInfos Information about DVB subtitles associated to the stream.
* @param descriptorBytes The descriptor bytes associated to the stream.
*/
public EsInfo(int streamType, String language, List<DvbSubtitleInfo> dvbSubtitleInfos,
public EsInfo(
int streamType,
@Nullable String language,
@Nullable List<DvbSubtitleInfo> dvbSubtitleInfos,
byte[] descriptorBytes) {
this.streamType = streamType;
this.language = language;
@ -134,6 +138,7 @@ public interface TsPayloadReader {
this.firstTrackId = firstTrackId;
this.trackIdIncrement = trackIdIncrement;
trackId = ID_UNSET;
formatId = "";
}
/**

View file

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.extractor.ts;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
@ -44,7 +45,7 @@ import java.util.List;
idGenerator.generateNewId();
TrackOutput output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
Format channelFormat = closedCaptionFormats.get(i);
String channelMimeType = channelFormat.sampleMimeType;
@Nullable String channelMimeType = channelFormat.sampleMimeType;
Assertions.checkArgument(
MimeTypes.APPLICATION_CEA608.equals(channelMimeType)
|| MimeTypes.APPLICATION_CEA708.equals(channelMimeType),

View file

@ -0,0 +1,19 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@NonNullApi
package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.util.NonNullApi;

View file

@ -773,7 +773,7 @@ public final class DownloadHelper {
}
// Initialization of array of Lists.
@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "rawtypes"})
private void onMediaPrepared() {
Assertions.checkNotNull(mediaPreparer);
Assertions.checkNotNull(mediaPreparer.mediaPeriods);

View file

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.extractor.ts;
import static com.google.common.truth.Truth.assertThat;
import android.util.SparseArray;
import androidx.annotation.Nullable;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
@ -172,6 +173,7 @@ public final class TsExtractorTest {
}
}
@Nullable
@Override
public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) {
if (provideCustomEsReader && streamType == 3) {