Add support for multiple programs in a single TS

* Prevents calling endTracks() before all PMTs have been
  processed.
* Adds a unique ID to the format of each track. This allows
  the track selector to identify which track belongs to
  each program. The format of each id is
  "<program number>/<track output id>".

Note: This CL will break malformed TS files whose PAT
declares more PMTs than it actually contains, which
previously were supported. This does not apply for HLS
mode.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=146151642
This commit is contained in:
aquilescanta 2017-01-31 12:33:43 -08:00 committed by Oliver Woodman
parent 4301606200
commit feeec77407
18 changed files with 175 additions and 73 deletions

View File

@ -6,7 +6,7 @@ numberOfTracks = 1
track 0:
format:
bitrate = -1
id = null
id = 0
containerMimeType = null
sampleMimeType = audio/ac3
maxInputSize = -1

View File

@ -6,7 +6,7 @@ numberOfTracks = 2
track 0:
format:
bitrate = -1
id = null
id = 0
containerMimeType = null
sampleMimeType = audio/mp4a-latm
maxInputSize = -1
@ -606,7 +606,7 @@ track 0:
track 1:
format:
bitrate = -1
id = null
id = 1
containerMimeType = null
sampleMimeType = application/id3
maxInputSize = -1

View File

@ -6,7 +6,7 @@ numberOfTracks = 2
track 192:
format:
bitrate = -1
id = null
id = 192
containerMimeType = null
sampleMimeType = audio/mpeg-L2
maxInputSize = 4096
@ -45,7 +45,7 @@ track 192:
track 224:
format:
bitrate = -1
id = null
id = 224
containerMimeType = null
sampleMimeType = video/mpeg2
maxInputSize = -1

View File

@ -6,7 +6,7 @@ numberOfTracks = 2
track 256:
format:
bitrate = -1
id = null
id = 1/256
containerMimeType = null
sampleMimeType = video/mpeg2
maxInputSize = -1
@ -38,7 +38,7 @@ track 256:
track 257:
format:
bitrate = -1
id = null
id = 1/257
containerMimeType = null
sampleMimeType = audio/mpeg-L2
maxInputSize = 4096

View File

@ -92,7 +92,7 @@ public final class TsExtractorTest extends InstrumentationTestCase {
TrackOutput trackOutput = reader.getTrackOutput();
assertTrue(trackOutput == output.trackOutputs.get(257 /* PID of audio track. */));
assertEquals(
Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0, "und", null, 0),
Format.createTextSampleFormat("1/257", "mime", null, 0, 0, "und", null, 0),
((FakeTrackOutput) trackOutput).format);
}
@ -178,8 +178,9 @@ public final class TsExtractorTest extends InstrumentationTestCase {
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
output.format(Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0,
idGenerator.generateNewId();
output = extractorOutput.track(idGenerator.getTrackId());
output.format(Format.createTextSampleFormat(idGenerator.getFormatId(), "mime", null, 0, 0,
language, null, 0));
}

View File

@ -39,6 +39,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private final ParsableByteArray headerScratchBytes;
private final String language;
private String trackFormatId;
private TrackOutput output;
private int state;
@ -84,7 +85,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) {
output = extractorOutput.track(generator.getNextId());
generator.generateNewId();
trackFormatId = generator.getFormatId();
output = extractorOutput.track(generator.getTrackId());
}
@Override
@ -180,8 +183,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
headerScratchBits.skipBits(40);
isEac3 = headerScratchBits.readBits(5) == 16;
headerScratchBits.setPosition(headerScratchBits.getPosition() - 45);
format = isEac3 ? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, null, language , null)
: Ac3Util.parseAc3SyncframeFormat(headerScratchBits, null, language, null);
format = isEac3
? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, trackFormatId, language , null)
: Ac3Util.parseAc3SyncframeFormat(headerScratchBits, trackFormatId, language, null);
output.format(format);
}
sampleSize = isEac3 ? Ac3Util.parseEAc3SyncframeSize(headerScratchBits.data)

View File

@ -61,6 +61,7 @@ import java.util.Collections;
private final ParsableByteArray id3HeaderBuffer;
private final String language;
private String formatId;
private TrackOutput output;
private TrackOutput id3Output;
@ -108,11 +109,14 @@ import java.util.Collections;
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId());
if (exposeId3) {
id3Output = extractorOutput.track(idGenerator.getNextId());
id3Output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null,
Format.NO_VALUE, null));
idGenerator.generateNewId();
id3Output = extractorOutput.track(idGenerator.getTrackId());
id3Output.format(Format.createSampleFormat(idGenerator.getFormatId(),
MimeTypes.APPLICATION_ID3, null, Format.NO_VALUE, null));
} else {
id3Output = new DummyTrackOutput();
}
@ -300,7 +304,7 @@ import java.util.Collections;
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
audioSpecificConfig);
Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC, null,
Format format = Format.createAudioSampleFormat(formatId, MimeTypes.AUDIO_AAC, null,
Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first,
Collections.singletonList(audioSpecificConfig), null, 0, language);
// In this class a sample is an access unit, but the MediaFormat sample rate specifies the

View File

@ -39,6 +39,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private final ParsableByteArray headerScratchBytes;
private final String language;
private String formatId;
private TrackOutput output;
private int state;
@ -79,7 +80,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId());
}
@Override
@ -165,7 +168,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private void parseHeader() {
byte[] frameData = headerScratchBytes.data;
if (format == null) {
format = DtsUtil.parseDtsFormat(frameData, null, language, null);
format = DtsUtil.parseDtsFormat(frameData, formatId, language, null);
output.format(format);
}
sampleSize = DtsUtil.getDtsFrameSize(frameData);

View File

@ -37,6 +37,7 @@ import java.util.Collections;
private static final int START_EXTENSION = 0xB5;
private static final int START_GROUP = 0xB8;
private String formatId;
private TrackOutput output;
// Maps (frame_rate_code - 1) indices to values, as defined in ITU-T H.262 Table 6-4.
@ -78,7 +79,9 @@ import java.util.Collections;
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId());
}
@Override
@ -126,7 +129,7 @@ import java.util.Collections;
int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0;
if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) {
// The csd data is complete, so we can decode and output the media format.
Pair<Format, Long> result = parseCsdBuffer(csdBuffer);
Pair<Format, Long> result = parseCsdBuffer(csdBuffer, formatId);
output.format(result.first);
frameDurationUs = result.second;
hasOutputFormat = true;
@ -166,10 +169,11 @@ import java.util.Collections;
* Parses the {@link Format} and frame duration from a csd buffer.
*
* @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.
*/
private static Pair<Format, Long> parseCsdBuffer(CsdBuffer csdBuffer) {
private static Pair<Format, Long> parseCsdBuffer(CsdBuffer csdBuffer, String formatId) {
byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length);
int firstByte = csdData[4] & 0xFF;
@ -195,7 +199,7 @@ import java.util.Collections;
break;
}
Format format = Format.createVideoSampleFormat(null, MimeTypes.VIDEO_MPEG2, null,
Format format = Format.createVideoSampleFormat(formatId, MimeTypes.VIDEO_MPEG2, null,
Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE,
Collections.singletonList(csdData), Format.NO_VALUE, pixelWidthHeightRatio, null);

View File

@ -47,6 +47,7 @@ import java.util.List;
private long totalBytesWritten;
private final boolean[] prefixFlags;
private String formatId;
private TrackOutput output;
private SeiReader seiReader;
private SampleReader sampleReader;
@ -88,9 +89,13 @@ import java.util.List;
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId());
sampleReader = new SampleReader(output, allowNonIdrKeyframes, detectAccessUnits);
seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId()));
idGenerator.generateNewId();
seiReader = new SeiReader(extractorOutput.track(idGenerator.getTrackId()),
idGenerator.getFormatId());
}
@Override
@ -175,7 +180,7 @@ import java.util.List;
initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength));
NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength);
NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.nalLength);
output.format(Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264, null,
output.format(Format.createVideoSampleFormat(formatId, MimeTypes.VIDEO_H264, null,
Format.NO_VALUE, Format.NO_VALUE, spsData.width, spsData.height, Format.NO_VALUE,
initializationData, Format.NO_VALUE, spsData.pixelWidthAspectRatio, null));
hasOutputFormat = true;

View File

@ -44,6 +44,7 @@ import java.util.Collections;
private static final int PREFIX_SEI_NUT = 39;
private static final int SUFFIX_SEI_NUT = 40;
private String formatId;
private TrackOutput output;
private SampleReader sampleReader;
private SeiReader seiReader;
@ -90,9 +91,13 @@ import java.util.Collections;
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId());
sampleReader = new SampleReader(output);
seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId()));
idGenerator.generateNewId();
seiReader = new SeiReader(extractorOutput.track(idGenerator.getTrackId()),
idGenerator.getFormatId());
}
@Override
@ -183,7 +188,7 @@ import java.util.Collections;
sps.endNalUnit(discardPadding);
pps.endNalUnit(discardPadding);
if (vps.isCompleted() && sps.isCompleted() && pps.isCompleted()) {
output.format(parseMediaFormat(vps, sps, pps));
output.format(parseMediaFormat(formatId, vps, sps, pps));
hasOutputFormat = true;
}
}
@ -205,8 +210,8 @@ import java.util.Collections;
}
}
private static Format parseMediaFormat(NalUnitTargetBuffer vps, NalUnitTargetBuffer sps,
NalUnitTargetBuffer pps) {
private static Format parseMediaFormat(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);
@ -311,7 +316,7 @@ import java.util.Collections;
}
}
return Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H265, null, Format.NO_VALUE,
return Format.createVideoSampleFormat(formatId, MimeTypes.VIDEO_H265, null, Format.NO_VALUE,
Format.NO_VALUE, picWidthInLumaSamples, picHeightInLumaSamples, Format.NO_VALUE,
Collections.singletonList(csd), Format.NO_VALUE, pixelWidthHeightRatio, null);
}

View File

@ -56,9 +56,10 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null, Format.NO_VALUE,
null));
idGenerator.generateNewId();
output = extractorOutput.track(idGenerator.getTrackId());
output.format(Format.createSampleFormat(idGenerator.getFormatId(), MimeTypes.APPLICATION_ID3,
null, Format.NO_VALUE, null));
}
@Override

View File

@ -38,6 +38,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private final MpegAudioHeader header;
private final String language;
private String formatId;
private TrackOutput output;
private int state;
@ -76,7 +77,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId());
}
@Override
@ -176,9 +179,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
frameSize = header.frameSize;
if (!hasOutputFormat) {
frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate;
Format format = Format.createAudioSampleFormat(null, header.mimeType, null, Format.NO_VALUE,
MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels, header.sampleRate, null, null, 0,
language);
Format format = Format.createAudioSampleFormat(formatId, header.mimeType, null,
Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels, header.sampleRate,
null, null, 0, language);
output.format(format);
hasOutputFormat = true;
}

View File

@ -28,9 +28,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private final TrackOutput output;
public SeiReader(TrackOutput output) {
public SeiReader(TrackOutput output, String formatId) {
this.output = output;
output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, null,
output.format(Format.createTextSampleFormat(formatId, MimeTypes.APPLICATION_CEA608, null,
Format.NO_VALUE, 0, null, null));
}

View File

@ -36,9 +36,10 @@ public final class SpliceInfoSectionReader implements SectionPayloadReader {
public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput,
TsPayloadReader.TrackIdGenerator idGenerator) {
this.timestampAdjuster = timestampAdjuster;
output = extractorOutput.track(idGenerator.getNextId());
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_SCTE35, null,
Format.NO_VALUE, null));
idGenerator.generateNewId();
output = extractorOutput.track(idGenerator.getTrackId());
output.format(Format.createSampleFormat(idGenerator.getFormatId(), MimeTypes.APPLICATION_SCTE35,
null, Format.NO_VALUE, null));
}
@Override

View File

@ -34,7 +34,10 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.TimestampAdjuster;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Facilitates the extraction of data from the MPEG-2 TS container format.
@ -79,7 +82,7 @@ public final class TsExtractor implements Extractor {
private static final int BUFFER_SIZE = TS_PACKET_SIZE * BUFFER_PACKET_COUNT;
private final boolean hlsMode;
private final TimestampAdjuster timestampAdjuster;
private final List<TimestampAdjuster> timestampAdjusters;
private final ParsableByteArray tsPacketBuffer;
private final ParsableBitArray tsScratch;
private final SparseIntArray continuityCounters;
@ -89,18 +92,12 @@ public final class TsExtractor implements Extractor {
// Accessed only by the loading thread.
private ExtractorOutput output;
private int remainingPmts;
private boolean tracksEnded;
private TsPayloadReader id3Reader;
public TsExtractor() {
this(new TimestampAdjuster(0));
}
/**
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
*/
public TsExtractor(TimestampAdjuster timestampAdjuster) {
this(timestampAdjuster, new DefaultTsPayloadReaderFactory(), false);
this(new TimestampAdjuster(0), new DefaultTsPayloadReaderFactory(), false);
}
/**
@ -111,7 +108,12 @@ public final class TsExtractor implements Extractor {
*/
public TsExtractor(TimestampAdjuster timestampAdjuster,
TsPayloadReader.Factory payloadReaderFactory, boolean hlsMode) {
this.timestampAdjuster = timestampAdjuster;
if (hlsMode) {
timestampAdjusters = Collections.singletonList(timestampAdjuster);
} else {
timestampAdjusters = new ArrayList<>();
timestampAdjusters.add(timestampAdjuster);
}
this.payloadReaderFactory = Assertions.checkNotNull(payloadReaderFactory);
this.hlsMode = hlsMode;
tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE);
@ -150,7 +152,10 @@ public final class TsExtractor implements Extractor {
@Override
public void seek(long position, long timeUs) {
timestampAdjuster.reset();
int timestampAdjustersCount = timestampAdjusters.size();
for (int i = 0; i < timestampAdjustersCount; i++) {
timestampAdjusters.get(i).reset();
}
tsPacketBuffer.reset();
continuityCounters.clear();
// Elementary stream readers' state should be cleared to get consistent behaviours when seeking.
@ -307,8 +312,12 @@ public final class TsExtractor implements Extractor {
} else {
int pid = patScratch.readBits(13);
tsPayloadReaders.put(pid, new SectionReader(new PmtReader(pid)));
remainingPmts++;
}
}
if (!hlsMode) {
tsPayloadReaders.remove(TS_PAT_PID);
}
}
}
@ -345,10 +354,21 @@ public final class TsExtractor implements Extractor {
// See ISO/IEC 13818-1, section 2.4.4.4 for more information on table id assignment.
return;
}
// section_syntax_indicator(1), '0'(1), reserved(2), section_length(12), program_number (16),
// reserved (2), version_number (5), current_next_indicator (1), // section_number (8),
// TimestampAdjuster assignment.
TimestampAdjuster timestampAdjuster;
if (hlsMode || remainingPmts == 1) {
timestampAdjuster = timestampAdjusters.get(0);
} else {
timestampAdjuster = new TimestampAdjuster(timestampAdjusters.get(0).firstSampleTimestampUs);
timestampAdjusters.add(timestampAdjuster);
}
// section_syntax_indicator(1), '0'(1), reserved(2), section_length(12)
sectionData.skipBytes(2);
int programNumber = sectionData.readUnsignedShort();
// reserved (2), version_number (5), current_next_indicator (1), section_number (8),
// last_section_number (8), reserved (3), PCR_PID (13)
sectionData.skipBytes(9);
sectionData.skipBytes(5);
// Read program_info_length.
sectionData.readBytes(pmtScratch, 2);
@ -364,7 +384,7 @@ public final class TsExtractor implements Extractor {
EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, new byte[0]);
id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo);
id3Reader.init(timestampAdjuster, output,
new TrackIdGenerator(TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE));
new TrackIdGenerator(programNumber, TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE));
}
int remainingEntriesLength = sectionData.bytesLeft();
@ -393,7 +413,8 @@ public final class TsExtractor implements Extractor {
} else {
reader = payloadReaderFactory.createPayloadReader(streamType, esInfo);
if (reader != null) {
reader.init(timestampAdjuster, output, new TrackIdGenerator(trackId, MAX_PID_PLUS_ONE));
reader.init(timestampAdjuster, output,
new TrackIdGenerator(programNumber, trackId, MAX_PID_PLUS_ONE));
}
}
@ -404,13 +425,17 @@ public final class TsExtractor implements Extractor {
if (hlsMode) {
if (!tracksEnded) {
output.endTracks();
remainingPmts = 0;
tracksEnded = true;
}
} else {
tsPayloadReaders.remove(TS_PAT_PID);
tsPayloadReaders.remove(pid);
output.endTracks();
remainingPmts--;
if (remainingPmts == 0) {
output.endTracks();
tracksEnded = true;
}
}
tracksEnded = true;
}
/**

View File

@ -81,17 +81,63 @@ public interface TsPayloadReader {
*/
final class TrackIdGenerator {
private final int firstId;
private final int idIncrement;
private int generatedIdCount;
private static final int ID_UNSET = Integer.MIN_VALUE;
public TrackIdGenerator(int firstId, int idIncrement) {
this.firstId = firstId;
this.idIncrement = idIncrement;
private final String formatIdPrefix;
private final int firstTrackId;
private final int trackIdIncrement;
private int trackId;
private String formatId;
public TrackIdGenerator(int firstTrackId, int trackIdIncrement) {
this(ID_UNSET, firstTrackId, trackIdIncrement);
}
public int getNextId() {
return firstId + idIncrement * generatedIdCount++;
public TrackIdGenerator(int programNumber, int firstTrackId, int trackIdIncrement) {
this.formatIdPrefix = programNumber != ID_UNSET ? programNumber + "/" : "";
this.firstTrackId = firstTrackId;
this.trackIdIncrement = trackIdIncrement;
trackId = ID_UNSET;
}
/**
* Generates a new set of track and track format ids. Must be called before {@code get*}
* methods.
*/
public void generateNewId() {
trackId = trackId == ID_UNSET ? firstTrackId : trackId + trackIdIncrement;
formatId = formatIdPrefix + trackId;
}
/**
* Returns the last generated track id. Must be called after the first {@link #generateNewId()}
* call.
*
* @return The last generated track id.
*/
public int getTrackId() {
maybeThrowUninitializedError();
return trackId;
}
/**
* Returns the last generated format id, with the format {@code "programNumber/trackId"}. If no
* {@code programNumber} was provided, the {@code trackId} alone is used as format id. Must be
* called after the first {@link #generateNewId()} call.
*
* @return The last generated format id, with the format {@code "programNumber/trackId"}. If no
* {@code programNumber} was provided, the {@code trackId} alone is used as
* format id.
*/
public String getFormatId() {
maybeThrowUninitializedError();
return formatId;
}
private void maybeThrowUninitializedError() {
if (trackId == ID_UNSET) {
throw new IllegalStateException("generateNewId() must be called before retrieving ids.");
}
}
}

View File

@ -34,7 +34,7 @@ public final class TimestampAdjuster {
*/
private static final long MAX_PTS_PLUS_ONE = 0x200000000L;
private final long firstSampleTimestampUs;
public final long firstSampleTimestampUs;
private long timestampOffsetUs;