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: track 0:
format: format:
bitrate = -1 bitrate = -1
id = null id = 0
containerMimeType = null containerMimeType = null
sampleMimeType = audio/ac3 sampleMimeType = audio/ac3
maxInputSize = -1 maxInputSize = -1

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -61,6 +61,7 @@ import java.util.Collections;
private final ParsableByteArray id3HeaderBuffer; private final ParsableByteArray id3HeaderBuffer;
private final String language; private final String language;
private String formatId;
private TrackOutput output; private TrackOutput output;
private TrackOutput id3Output; private TrackOutput id3Output;
@ -108,11 +109,14 @@ import java.util.Collections;
@Override @Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId()); idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId());
if (exposeId3) { if (exposeId3) {
id3Output = extractorOutput.track(idGenerator.getNextId()); idGenerator.generateNewId();
id3Output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null, id3Output = extractorOutput.track(idGenerator.getTrackId());
Format.NO_VALUE, null)); id3Output.format(Format.createSampleFormat(idGenerator.getFormatId(),
MimeTypes.APPLICATION_ID3, null, Format.NO_VALUE, null));
} else { } else {
id3Output = new DummyTrackOutput(); id3Output = new DummyTrackOutput();
} }
@ -300,7 +304,7 @@ import java.util.Collections;
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig( Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
audioSpecificConfig); 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, Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first,
Collections.singletonList(audioSpecificConfig), null, 0, language); Collections.singletonList(audioSpecificConfig), null, 0, language);
// In this class a sample is an access unit, but the MediaFormat sample rate specifies the // 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 ParsableByteArray headerScratchBytes;
private final String language; private final String language;
private String formatId;
private TrackOutput output; private TrackOutput output;
private int state; private int state;
@ -79,7 +80,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
@Override @Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId()); idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId());
} }
@Override @Override
@ -165,7 +168,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private void parseHeader() { private void parseHeader() {
byte[] frameData = headerScratchBytes.data; byte[] frameData = headerScratchBytes.data;
if (format == null) { if (format == null) {
format = DtsUtil.parseDtsFormat(frameData, null, language, null); format = DtsUtil.parseDtsFormat(frameData, formatId, language, null);
output.format(format); output.format(format);
} }
sampleSize = DtsUtil.getDtsFrameSize(frameData); 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_EXTENSION = 0xB5;
private static final int START_GROUP = 0xB8; private static final int START_GROUP = 0xB8;
private String formatId;
private TrackOutput output; private TrackOutput output;
// Maps (frame_rate_code - 1) indices to values, as defined in ITU-T H.262 Table 6-4. // 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 @Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId()); idGenerator.generateNewId();
formatId = idGenerator.getFormatId();
output = extractorOutput.track(idGenerator.getTrackId());
} }
@Override @Override
@ -126,7 +129,7 @@ import java.util.Collections;
int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0; int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0;
if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) { if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) {
// The csd data is complete, so we can decode and output the media format. // 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); output.format(result.first);
frameDurationUs = result.second; frameDurationUs = result.second;
hasOutputFormat = true; hasOutputFormat = true;
@ -166,10 +169,11 @@ import java.util.Collections;
* Parses the {@link Format} and frame duration from a csd buffer. * Parses the {@link Format} and frame duration from a csd buffer.
* *
* @param csdBuffer The 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 * @return A pair consisting of the {@link Format} and the frame duration in microseconds, or
* 0 if the duration could not be determined. * 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); byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length);
int firstByte = csdData[4] & 0xFF; int firstByte = csdData[4] & 0xFF;
@ -195,7 +199,7 @@ import java.util.Collections;
break; 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, Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE,
Collections.singletonList(csdData), Format.NO_VALUE, pixelWidthHeightRatio, null); Collections.singletonList(csdData), Format.NO_VALUE, pixelWidthHeightRatio, null);

View File

@ -47,6 +47,7 @@ import java.util.List;
private long totalBytesWritten; private long totalBytesWritten;
private final boolean[] prefixFlags; private final boolean[] prefixFlags;
private String formatId;
private TrackOutput output; private TrackOutput output;
private SeiReader seiReader; private SeiReader seiReader;
private SampleReader sampleReader; private SampleReader sampleReader;
@ -88,9 +89,13 @@ import java.util.List;
@Override @Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { 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); 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 @Override
@ -175,7 +180,7 @@ import java.util.List;
initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength)); initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength));
NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength); NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength);
NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.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, Format.NO_VALUE, Format.NO_VALUE, spsData.width, spsData.height, Format.NO_VALUE,
initializationData, Format.NO_VALUE, spsData.pixelWidthAspectRatio, null)); initializationData, Format.NO_VALUE, spsData.pixelWidthAspectRatio, null));
hasOutputFormat = true; hasOutputFormat = true;

View File

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

View File

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

View File

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

View File

@ -28,9 +28,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private final TrackOutput output; private final TrackOutput output;
public SeiReader(TrackOutput output) { public SeiReader(TrackOutput output, String formatId) {
this.output = output; 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)); 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, public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput,
TsPayloadReader.TrackIdGenerator idGenerator) { TsPayloadReader.TrackIdGenerator idGenerator) {
this.timestampAdjuster = timestampAdjuster; this.timestampAdjuster = timestampAdjuster;
output = extractorOutput.track(idGenerator.getNextId()); idGenerator.generateNewId();
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_SCTE35, null, output = extractorOutput.track(idGenerator.getTrackId());
Format.NO_VALUE, null)); output.format(Format.createSampleFormat(idGenerator.getFormatId(), MimeTypes.APPLICATION_SCTE35,
null, Format.NO_VALUE, null));
} }
@Override @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.TimestampAdjuster;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/** /**
* Facilitates the extraction of data from the MPEG-2 TS container format. * 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 static final int BUFFER_SIZE = TS_PACKET_SIZE * BUFFER_PACKET_COUNT;
private final boolean hlsMode; private final boolean hlsMode;
private final TimestampAdjuster timestampAdjuster; private final List<TimestampAdjuster> timestampAdjusters;
private final ParsableByteArray tsPacketBuffer; private final ParsableByteArray tsPacketBuffer;
private final ParsableBitArray tsScratch; private final ParsableBitArray tsScratch;
private final SparseIntArray continuityCounters; private final SparseIntArray continuityCounters;
@ -89,18 +92,12 @@ public final class TsExtractor implements Extractor {
// Accessed only by the loading thread. // Accessed only by the loading thread.
private ExtractorOutput output; private ExtractorOutput output;
private int remainingPmts;
private boolean tracksEnded; private boolean tracksEnded;
private TsPayloadReader id3Reader; private TsPayloadReader id3Reader;
public TsExtractor() { public TsExtractor() {
this(new TimestampAdjuster(0)); this(new TimestampAdjuster(0), new DefaultTsPayloadReaderFactory(), false);
}
/**
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
*/
public TsExtractor(TimestampAdjuster timestampAdjuster) {
this(timestampAdjuster, new DefaultTsPayloadReaderFactory(), false);
} }
/** /**
@ -111,7 +108,12 @@ public final class TsExtractor implements Extractor {
*/ */
public TsExtractor(TimestampAdjuster timestampAdjuster, public TsExtractor(TimestampAdjuster timestampAdjuster,
TsPayloadReader.Factory payloadReaderFactory, boolean hlsMode) { 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.payloadReaderFactory = Assertions.checkNotNull(payloadReaderFactory);
this.hlsMode = hlsMode; this.hlsMode = hlsMode;
tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE); tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE);
@ -150,7 +152,10 @@ public final class TsExtractor implements Extractor {
@Override @Override
public void seek(long position, long timeUs) { 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(); tsPacketBuffer.reset();
continuityCounters.clear(); continuityCounters.clear();
// Elementary stream readers' state should be cleared to get consistent behaviours when seeking. // Elementary stream readers' state should be cleared to get consistent behaviours when seeking.
@ -307,8 +312,12 @@ public final class TsExtractor implements Extractor {
} else { } else {
int pid = patScratch.readBits(13); int pid = patScratch.readBits(13);
tsPayloadReaders.put(pid, new SectionReader(new PmtReader(pid))); 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. // See ISO/IEC 13818-1, section 2.4.4.4 for more information on table id assignment.
return; return;
} }
// section_syntax_indicator(1), '0'(1), reserved(2), section_length(12), program_number (16), // TimestampAdjuster assignment.
// reserved (2), version_number (5), current_next_indicator (1), // section_number (8), 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) // last_section_number (8), reserved (3), PCR_PID (13)
sectionData.skipBytes(9); sectionData.skipBytes(5);
// Read program_info_length. // Read program_info_length.
sectionData.readBytes(pmtScratch, 2); 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]); EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, new byte[0]);
id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo); id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo);
id3Reader.init(timestampAdjuster, output, 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(); int remainingEntriesLength = sectionData.bytesLeft();
@ -393,7 +413,8 @@ public final class TsExtractor implements Extractor {
} else { } else {
reader = payloadReaderFactory.createPayloadReader(streamType, esInfo); reader = payloadReaderFactory.createPayloadReader(streamType, esInfo);
if (reader != null) { 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 (hlsMode) {
if (!tracksEnded) { if (!tracksEnded) {
output.endTracks(); output.endTracks();
remainingPmts = 0;
tracksEnded = true;
} }
} else { } else {
tsPayloadReaders.remove(TS_PAT_PID);
tsPayloadReaders.remove(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 { final class TrackIdGenerator {
private final int firstId; private static final int ID_UNSET = Integer.MIN_VALUE;
private final int idIncrement;
private int generatedIdCount;
public TrackIdGenerator(int firstId, int idIncrement) { private final String formatIdPrefix;
this.firstId = firstId; private final int firstTrackId;
this.idIncrement = idIncrement; private final int trackIdIncrement;
private int trackId;
private String formatId;
public TrackIdGenerator(int firstTrackId, int trackIdIncrement) {
this(ID_UNSET, firstTrackId, trackIdIncrement);
} }
public int getNextId() { public TrackIdGenerator(int programNumber, int firstTrackId, int trackIdIncrement) {
return firstId + idIncrement * generatedIdCount++; 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 static final long MAX_PTS_PLUS_ONE = 0x200000000L;
private final long firstSampleTimestampUs; public final long firstSampleTimestampUs;
private long timestampOffsetUs; private long timestampOffsetUs;