Decouple TsExtractor's readers from TrackOutputs

This allows the injectable reader factory to be a stateless factory, allows
the seeking to be consistent and will allow multiple CC channel support later
on.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=135909712
This commit is contained in:
aquilescanta 2016-10-12 06:18:06 -07:00 committed by Oliver Woodman
parent 94c7ee7252
commit f18373eeb2
17 changed files with 259 additions and 180 deletions

View File

@ -16,6 +16,8 @@
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.TrackIdGenerator;
import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
import com.google.android.exoplayer2.testutil.FakeTrackOutput; import com.google.android.exoplayer2.testutil.FakeTrackOutput;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
@ -66,9 +68,12 @@ public class AdtsReaderTest extends TestCase {
@Override @Override
protected void setUp() throws Exception { protected void setUp() throws Exception {
adtsOutput = new FakeTrackOutput(); FakeExtractorOutput fakeExtractorOutput = new FakeExtractorOutput(true);
id3Output = new FakeTrackOutput(); adtsOutput = fakeExtractorOutput.track(0);
adtsReader = new AdtsReader(adtsOutput, id3Output); id3Output = fakeExtractorOutput.track(1);
adtsReader = new AdtsReader(true);
TrackIdGenerator idGenerator = new TrackIdGenerator(0, 1);
adtsReader.init(fakeExtractorOutput, idGenerator);
data = new ParsableByteArray(TEST_DATA); data = new ParsableByteArray(TEST_DATA);
firstFeed = true; firstFeed = true;
} }

View File

@ -22,6 +22,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.TimestampAdjuster; import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.EsInfo;
import com.google.android.exoplayer2.testutil.FakeExtractorInput; import com.google.android.exoplayer2.testutil.FakeExtractorInput;
import com.google.android.exoplayer2.testutil.FakeExtractorOutput; import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
import com.google.android.exoplayer2.testutil.FakeTrackOutput; import com.google.android.exoplayer2.testutil.FakeTrackOutput;
@ -72,7 +73,7 @@ public final class TsExtractorTest extends InstrumentationTestCase {
public void testCustomPesReader() throws Exception { public void testCustomPesReader() throws Exception {
CustomEsReaderFactory factory = new CustomEsReaderFactory(); CustomEsReaderFactory factory = new CustomEsReaderFactory();
TsExtractor tsExtractor = new TsExtractor(new TimestampAdjuster(0), factory); TsExtractor tsExtractor = new TsExtractor(new TimestampAdjuster(0), factory, false);
FakeExtractorInput input = new FakeExtractorInput.Builder() FakeExtractorInput input = new FakeExtractorInput.Builder()
.setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts")) .setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts"))
.setSimulateIOErrors(false) .setSimulateIOErrors(false)
@ -107,18 +108,25 @@ public final class TsExtractorTest extends InstrumentationTestCase {
private static final class CustomEsReader extends ElementaryStreamReader { private static final class CustomEsReader extends ElementaryStreamReader {
private final String language;
private TrackOutput output;
public int packetsRead = 0; public int packetsRead = 0;
public CustomEsReader(TrackOutput output, String language) { public CustomEsReader(String language) {
super(output); this.language = language;
output.format(Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0,
language, null, 0));
} }
@Override @Override
public void seek() { public void seek() {
} }
@Override
public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
output.format(Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0,
language, null, 0));
}
@Override @Override
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
} }
@ -148,16 +156,12 @@ public final class TsExtractorTest extends InstrumentationTestCase {
} }
@Override @Override
public ElementaryStreamReader onPmtEntry(int pid, int streamType, public ElementaryStreamReader createStreamReader(int streamType, EsInfo esInfo) {
ElementaryStreamReader.EsInfo esInfo, ExtractorOutput output) {
if (streamType == 3) { if (streamType == 3) {
// We need to manually avoid a duplicate custom reader creation. reader = new CustomEsReader(esInfo.language);
if (reader == null) {
reader = new CustomEsReader(output.track(pid), esInfo.language);
}
return reader; return reader;
} else { } else {
return defaultFactory.onPmtEntry(pid, streamType, esInfo, output); return defaultFactory.createStreamReader(streamType, esInfo);
} }
} }

View File

@ -23,6 +23,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
@ -117,7 +118,8 @@ public final class Ac3Extractor implements Extractor {
@Override @Override
public void init(ExtractorOutput output) { public void init(ExtractorOutput output) {
reader = new Ac3Reader(output.track(0)); // TODO: Add support for embedded ID3. reader = new Ac3Reader(); // TODO: Add support for embedded ID3.
reader.init(output, new TrackIdGenerator(0, 1));
output.endTracks(); output.endTracks();
output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET));
} }

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.Ac3Util; import com.google.android.exoplayer2.audio.Ac3Util;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
@ -37,6 +38,8 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private final ParsableByteArray headerScratchBytes; private final ParsableByteArray headerScratchBytes;
private final String language; private final String language;
private TrackOutput output;
private int state; private int state;
private int bytesRead; private int bytesRead;
@ -54,21 +57,17 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
/** /**
* Constructs a new reader for (E-)AC-3 elementary streams. * Constructs a new reader for (E-)AC-3 elementary streams.
*
* @param output Track output for extracted samples.
*/ */
public Ac3Reader(TrackOutput output) { public Ac3Reader() {
this(output, null); this(null);
} }
/** /**
* Constructs a new reader for (E-)AC-3 elementary streams. * Constructs a new reader for (E-)AC-3 elementary streams.
* *
* @param output Track output for extracted samples.
* @param language Track language. * @param language Track language.
*/ */
public Ac3Reader(TrackOutput output, String language) { public Ac3Reader(String language) {
super(output);
headerScratchBits = new ParsableBitArray(new byte[HEADER_SIZE]); headerScratchBits = new ParsableBitArray(new byte[HEADER_SIZE]);
headerScratchBytes = new ParsableByteArray(headerScratchBits.data); headerScratchBytes = new ParsableByteArray(headerScratchBits.data);
state = STATE_FINDING_SYNC; state = STATE_FINDING_SYNC;
@ -82,6 +81,11 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
lastByteWas0B = false; lastByteWas0B = false;
} }
@Override
public void init(ExtractorOutput extractorOutput, TrackIdGenerator generator) {
output = extractorOutput.track(generator.getNextId());
}
@Override @Override
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
timeUs = pesTimeUs; timeUs = pesTimeUs;

View File

@ -22,6 +22,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
@ -126,7 +127,8 @@ public final class AdtsExtractor implements Extractor {
@Override @Override
public void init(ExtractorOutput output) { public void init(ExtractorOutput output) {
reader = new AdtsReader(output.track(0), output.track(1)); reader = new AdtsReader(true);
reader.init(output, new TrackIdGenerator(0, 1));
output.endTracks(); output.endTracks();
output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET));
} }

View File

@ -19,6 +19,8 @@ import android.util.Log;
import android.util.Pair; import android.util.Pair;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil; import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
@ -53,11 +55,14 @@ import java.util.Collections;
private static final int ID3_SIZE_OFFSET = 6; private static final int ID3_SIZE_OFFSET = 6;
private static final byte[] ID3_IDENTIFIER = {'I', 'D', '3'}; private static final byte[] ID3_IDENTIFIER = {'I', 'D', '3'};
private final boolean exposeId3;
private final ParsableBitArray adtsScratch; private final ParsableBitArray adtsScratch;
private final ParsableByteArray id3HeaderBuffer; private final ParsableByteArray id3HeaderBuffer;
private final TrackOutput id3Output;
private final String language; private final String language;
private TrackOutput output;
private TrackOutput id3Output;
private int state; private int state;
private int bytesRead; private int bytesRead;
@ -77,26 +82,21 @@ import java.util.Collections;
private long currentSampleDuration; private long currentSampleDuration;
/** /**
* @param output A {@link TrackOutput} to which AAC samples should be written. * @param exposeId3 True if the reader should expose ID3 information.
* @param id3Output A {@link TrackOutput} to which ID3 samples should be written.
*/ */
public AdtsReader(TrackOutput output, TrackOutput id3Output) { public AdtsReader(boolean exposeId3) {
this(output, id3Output, null); this(exposeId3, null);
} }
/** /**
* @param output A {@link TrackOutput} to which AAC samples should be written. * @param exposeId3 True if the reader should expose ID3 information.
* @param id3Output A {@link TrackOutput} to which ID3 samples should be written.
* @param language Track language. * @param language Track language.
*/ */
public AdtsReader(TrackOutput output, TrackOutput id3Output, String language) { public AdtsReader(boolean exposeId3, String language) {
super(output);
this.id3Output = id3Output;
id3Output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null,
Format.NO_VALUE, null));
adtsScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]); adtsScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]);
id3HeaderBuffer = new ParsableByteArray(Arrays.copyOf(ID3_IDENTIFIER, ID3_HEADER_SIZE)); id3HeaderBuffer = new ParsableByteArray(Arrays.copyOf(ID3_IDENTIFIER, ID3_HEADER_SIZE));
setFindingSampleState(); setFindingSampleState();
this.exposeId3 = exposeId3;
this.language = language; this.language = language;
} }
@ -105,6 +105,18 @@ import java.util.Collections;
setFindingSampleState(); setFindingSampleState();
} }
@Override
public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
if (exposeId3) {
id3Output = extractorOutput.track(idGenerator.getNextId());
id3Output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null,
Format.NO_VALUE, null));
} else {
id3Output = new DummyTrackOutput();
}
}
@Override @Override
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
timeUs = pesTimeUs; timeUs = pesTimeUs;

View File

@ -16,9 +16,7 @@
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.util.SparseBooleanArray; import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.EsInfo;
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
@ -28,80 +26,54 @@ import java.lang.annotation.RetentionPolicy;
public final class DefaultStreamReaderFactory implements ElementaryStreamReader.Factory { public final class DefaultStreamReaderFactory implements ElementaryStreamReader.Factory {
/** /**
* Flags controlling what workarounds are enabled for elementary stream readers. * Flags controlling elementary stream readers behaviour.
*/ */
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {WORKAROUND_ALLOW_NON_IDR_KEYFRAMES, WORKAROUND_IGNORE_AAC_STREAM, @IntDef(flag = true, value = {FLAG_ALLOW_NON_IDR_KEYFRAMES, FLAG_IGNORE_AAC_STREAM,
WORKAROUND_IGNORE_H264_STREAM, WORKAROUND_DETECT_ACCESS_UNITS, WORKAROUND_MAP_BY_TYPE}) FLAG_IGNORE_H264_STREAM, FLAG_DETECT_ACCESS_UNITS})
public @interface WorkaroundFlags { public @interface Flags {
} }
public static final int WORKAROUND_ALLOW_NON_IDR_KEYFRAMES = 1; public static final int FLAG_ALLOW_NON_IDR_KEYFRAMES = 1;
public static final int WORKAROUND_IGNORE_AAC_STREAM = 2; public static final int FLAG_IGNORE_AAC_STREAM = 2;
public static final int WORKAROUND_IGNORE_H264_STREAM = 4; public static final int FLAG_IGNORE_H264_STREAM = 4;
public static final int WORKAROUND_DETECT_ACCESS_UNITS = 8; public static final int FLAG_DETECT_ACCESS_UNITS = 8;
public static final int WORKAROUND_MAP_BY_TYPE = 16;
private static final int BASE_EMBEDDED_TRACK_ID = 0x2000; // 0xFF + 1. @Flags
private final int flags;
private final SparseBooleanArray trackIds;
@WorkaroundFlags
private final int workaroundFlags;
private Id3Reader id3Reader;
private int nextEmbeddedTrackId = BASE_EMBEDDED_TRACK_ID;
public DefaultStreamReaderFactory() { public DefaultStreamReaderFactory() {
this(0); this(0);
} }
public DefaultStreamReaderFactory(int workaroundFlags) { public DefaultStreamReaderFactory(@Flags int flags) {
trackIds = new SparseBooleanArray(); this.flags = flags;
this.workaroundFlags = workaroundFlags;
} }
@Override @Override
public ElementaryStreamReader onPmtEntry(int pid, int streamType, public ElementaryStreamReader createStreamReader(int streamType, EsInfo esInfo) {
ElementaryStreamReader.EsInfo esInfo, ExtractorOutput output) {
if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 && id3Reader == null) {
// Setup an ID3 track regardless of whether there's a corresponding entry, in case one
// appears intermittently during playback. See b/20261500.
id3Reader = new Id3Reader(output.track(TsExtractor.TS_STREAM_TYPE_ID3));
}
int trackId = (workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0 ? streamType : pid;
if (trackIds.get(trackId)) {
return null;
}
trackIds.put(trackId, true);
switch (streamType) { switch (streamType) {
case TsExtractor.TS_STREAM_TYPE_MPA: case TsExtractor.TS_STREAM_TYPE_MPA:
case TsExtractor.TS_STREAM_TYPE_MPA_LSF: case TsExtractor.TS_STREAM_TYPE_MPA_LSF:
return new MpegAudioReader(output.track(trackId), esInfo.language); return new MpegAudioReader(esInfo.language);
case TsExtractor.TS_STREAM_TYPE_AAC: case TsExtractor.TS_STREAM_TYPE_AAC:
return (workaroundFlags & WORKAROUND_IGNORE_AAC_STREAM) != 0 ? null return (flags & FLAG_IGNORE_AAC_STREAM) != 0 ? null
: new AdtsReader(output.track(trackId), new DummyTrackOutput(), esInfo.language); : new AdtsReader(false, esInfo.language);
case TsExtractor.TS_STREAM_TYPE_AC3: case TsExtractor.TS_STREAM_TYPE_AC3:
case TsExtractor.TS_STREAM_TYPE_E_AC3: case TsExtractor.TS_STREAM_TYPE_E_AC3:
return new Ac3Reader(output.track(trackId), esInfo.language); return new Ac3Reader(esInfo.language);
case TsExtractor.TS_STREAM_TYPE_DTS: case TsExtractor.TS_STREAM_TYPE_DTS:
case TsExtractor.TS_STREAM_TYPE_HDMV_DTS: case TsExtractor.TS_STREAM_TYPE_HDMV_DTS:
return new DtsReader(output.track(trackId), esInfo.language); return new DtsReader(esInfo.language);
case TsExtractor.TS_STREAM_TYPE_H262: case TsExtractor.TS_STREAM_TYPE_H262:
return new H262Reader(output.track(trackId)); return new H262Reader();
case TsExtractor.TS_STREAM_TYPE_H264: case TsExtractor.TS_STREAM_TYPE_H264:
return (workaroundFlags & WORKAROUND_IGNORE_H264_STREAM) != 0 return (flags & FLAG_IGNORE_H264_STREAM) != 0 ? null
? null : new H264Reader(output.track(trackId), : new H264Reader((flags & FLAG_ALLOW_NON_IDR_KEYFRAMES) != 0,
new SeiReader(output.track(nextEmbeddedTrackId++)), (flags & FLAG_DETECT_ACCESS_UNITS) != 0);
(workaroundFlags & WORKAROUND_ALLOW_NON_IDR_KEYFRAMES) != 0,
(workaroundFlags & WORKAROUND_DETECT_ACCESS_UNITS) != 0);
case TsExtractor.TS_STREAM_TYPE_H265: case TsExtractor.TS_STREAM_TYPE_H265:
return new H265Reader(output.track(trackId), return new H265Reader();
new SeiReader(output.track(nextEmbeddedTrackId++)));
case TsExtractor.TS_STREAM_TYPE_ID3: case TsExtractor.TS_STREAM_TYPE_ID3:
if ((workaroundFlags & WORKAROUND_MAP_BY_TYPE) != 0) { return new Id3Reader();
return id3Reader;
} else {
return new Id3Reader(output.track(nextEmbeddedTrackId++));
}
default: default:
return null; return null;
} }

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.audio.DtsUtil; 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.TrackOutput;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
@ -37,6 +38,8 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private final ParsableByteArray headerScratchBytes; private final ParsableByteArray headerScratchBytes;
private final String language; private final String language;
private TrackOutput output;
private int state; private int state;
private int bytesRead; private int bytesRead;
@ -54,20 +57,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
/** /**
* Constructs a new reader for DTS elementary streams. * Constructs a new reader for DTS elementary streams.
* *
* @param output Track output for extracted samples.
*/
public DtsReader(TrackOutput output) {
this(output, null);
}
/**
* Constructs a new reader for DTS elementary streams.
*
* @param output Track output for extracted samples.
* @param language Track language. * @param language Track language.
*/ */
public DtsReader(TrackOutput output, String language) { public DtsReader(String language) {
super(output);
headerScratchBytes = new ParsableByteArray(new byte[HEADER_SIZE]); headerScratchBytes = new ParsableByteArray(new byte[HEADER_SIZE]);
headerScratchBytes.data[0] = (byte) ((SYNC_VALUE >> 24) & 0xFF); headerScratchBytes.data[0] = (byte) ((SYNC_VALUE >> 24) & 0xFF);
headerScratchBytes.data[1] = (byte) ((SYNC_VALUE >> 16) & 0xFF); headerScratchBytes.data[1] = (byte) ((SYNC_VALUE >> 16) & 0xFF);
@ -84,6 +76,11 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
syncBytes = 0; syncBytes = 0;
} }
@Override
public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
}
@Override @Override
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
timeUs = pesTimeUs; timeUs = pesTimeUs;

View File

@ -33,17 +33,12 @@ public abstract class ElementaryStreamReader {
* Returns an {@link ElementaryStreamReader} for a given PMT entry. May return null if the * Returns an {@link ElementaryStreamReader} for a given PMT entry. May return null if the
* stream type is not supported or if the stream already has a reader assigned to it. * stream type is not supported or if the stream already has a reader assigned to it.
* *
* @param pid The pid for the PMT entry. * @param streamType Stream type value as defined in the PMT entry or associated descriptors.
* @param streamType One of the {@link TsExtractor}{@code .TS_STREAM_TYPE_*} constants defining * @param esInfo Information associated to the elementary stream provided in the PMT.
* the type of the stream.
* @param esInfo The descriptor information linked to the elementary stream.
* @param output The {@link ExtractorOutput} that provides the {@link TrackOutput}s for the
* created readers.
* @return An {@link ElementaryStreamReader} for the elementary streams carried by the provided * @return An {@link ElementaryStreamReader} for the elementary streams carried by the provided
* pid. {@code null} if the stream is not supported or if it should be ignored. * pid. {@code null} if the stream is not supported or if it should be ignored.
*/ */
ElementaryStreamReader onPmtEntry(int pid, int streamType, EsInfo esInfo, ElementaryStreamReader createStreamReader(int streamType, EsInfo esInfo);
ExtractorOutput output);
} }
@ -70,13 +65,24 @@ public abstract class ElementaryStreamReader {
} }
protected final TrackOutput output;
/** /**
* @param output A {@link TrackOutput} to which samples should be written. * Generates track ids for initializing {@link ElementaryStreamReader}s' {@link TrackOutput}s.
*/ */
protected ElementaryStreamReader(TrackOutput output) { public static final class TrackIdGenerator {
this.output = output;
private final int firstId;
private final int idIncrement;
private int generatedIdCount;
public TrackIdGenerator(int firstId, int idIncrement) {
this.firstId = firstId;
this.idIncrement = idIncrement;
}
public int getNextId() {
return firstId + idIncrement * generatedIdCount++;
}
} }
/** /**
@ -84,6 +90,15 @@ public abstract class ElementaryStreamReader {
*/ */
public abstract void seek(); public abstract void seek();
/**
* Initializes the reader by providing outputs and ids for the tracks.
*
* @param extractorOutput The {@link ExtractorOutput} that receives the extracted data.
* @param idGenerator A {@link TrackIdGenerator} that generates unique track ids for the
* {@link TrackOutput}s.
*/
public abstract void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator);
/** /**
* Called when a packet starts. * Called when a packet starts.
* *

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.extractor.ts;
import android.util.Pair; import android.util.Pair;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.NalUnitUtil;
@ -35,6 +36,8 @@ 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 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.
private static final double[] FRAME_RATE_VALUES = new double[] { private static final double[] FRAME_RATE_VALUES = new double[] {
24000d / 1001, 24, 25, 30000d / 1001, 30, 50, 60000d / 1001, 60}; 24000d / 1001, 24, 25, 30000d / 1001, 30, 50, 60000d / 1001, 60};
@ -58,8 +61,7 @@ import java.util.Collections;
private long framePosition; private long framePosition;
private long frameTimeUs; private long frameTimeUs;
public H262Reader(TrackOutput output) { public H262Reader() {
super(output);
prefixFlags = new boolean[4]; prefixFlags = new boolean[4];
csdBuffer = new CsdBuffer(128); csdBuffer = new CsdBuffer(128);
} }
@ -73,6 +75,11 @@ import java.util.Collections;
totalBytesWritten = 0; totalBytesWritten = 0;
} }
@Override
public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
}
@Override @Override
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
pesPtsUsAvailable = pesTimeUs != C.TIME_UNSET; pesPtsUsAvailable = pesTimeUs != C.TIME_UNSET;

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.extractor.ts;
import android.util.SparseArray; import android.util.SparseArray;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.NalUnitUtil;
@ -37,17 +38,20 @@ import java.util.List;
private static final int NAL_UNIT_TYPE_SPS = 7; // Sequence parameter set private static final int NAL_UNIT_TYPE_SPS = 7; // Sequence parameter set
private static final int NAL_UNIT_TYPE_PPS = 8; // Picture parameter set private static final int NAL_UNIT_TYPE_PPS = 8; // Picture parameter set
// State that should not be reset on seek. private final boolean allowNonIdrKeyframes;
private boolean hasOutputFormat; private final boolean detectAccessUnits;
// State that should be reset on seek.
private final SeiReader seiReader;
private final boolean[] prefixFlags;
private final SampleReader sampleReader;
private final NalUnitTargetBuffer sps; private final NalUnitTargetBuffer sps;
private final NalUnitTargetBuffer pps; private final NalUnitTargetBuffer pps;
private final NalUnitTargetBuffer sei; private final NalUnitTargetBuffer sei;
private long totalBytesWritten; private long totalBytesWritten;
private final boolean[] prefixFlags;
private TrackOutput output;
private SeiReader seiReader;
private SampleReader sampleReader;
// State that should not be reset on seek.
private boolean hasOutputFormat;
// Per packet state that gets reset at the start of each packet. // Per packet state that gets reset at the start of each packet.
private long pesTimeUs; private long pesTimeUs;
@ -56,19 +60,15 @@ import java.util.List;
private final ParsableByteArray seiWrapper; private final ParsableByteArray seiWrapper;
/** /**
* @param output A {@link TrackOutput} to which H.264 samples should be written.
* @param seiReader A reader for CEA-608 samples in SEI NAL units.
* @param allowNonIdrKeyframes Whether to treat samples consisting of non-IDR I slices as * @param allowNonIdrKeyframes Whether to treat samples consisting of non-IDR I slices as
* synchronization samples (key-frames). * synchronization samples (key-frames).
* @param detectAccessUnits Whether to split the input stream into access units (samples) based on * @param detectAccessUnits Whether to split the input stream into access units (samples) based on
* slice headers. Pass {@code false} if the stream contains access unit delimiters (AUDs). * slice headers. Pass {@code false} if the stream contains access unit delimiters (AUDs).
*/ */
public H264Reader(TrackOutput output, SeiReader seiReader, boolean allowNonIdrKeyframes, public H264Reader(boolean allowNonIdrKeyframes, boolean detectAccessUnits) {
boolean detectAccessUnits) {
super(output);
this.seiReader = seiReader;
prefixFlags = new boolean[3]; prefixFlags = new boolean[3];
sampleReader = new SampleReader(output, allowNonIdrKeyframes, detectAccessUnits); this.allowNonIdrKeyframes = allowNonIdrKeyframes;
this.detectAccessUnits = detectAccessUnits;
sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128); sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128);
pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128); pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128);
sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128); sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128);
@ -85,6 +85,13 @@ import java.util.List;
totalBytesWritten = 0; totalBytesWritten = 0;
} }
@Override
public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
sampleReader = new SampleReader(output, allowNonIdrKeyframes, detectAccessUnits);
seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId()));
}
@Override @Override
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
this.pesTimeUs = pesTimeUs; this.pesTimeUs = pesTimeUs;

View File

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.extractor.ts;
import android.util.Log; import android.util.Log;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.NalUnitUtil;
@ -42,11 +43,13 @@ 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 TrackOutput output;
private SeiReader seiReader;
// State that should not be reset on seek. // State that should not be reset on seek.
private boolean hasOutputFormat; private boolean hasOutputFormat;
// State that should be reset on seek. // State that should be reset on seek.
private final SeiReader seiReader;
private final boolean[] prefixFlags; private final boolean[] prefixFlags;
private final NalUnitTargetBuffer vps; private final NalUnitTargetBuffer vps;
private final NalUnitTargetBuffer sps; private final NalUnitTargetBuffer sps;
@ -62,13 +65,7 @@ import java.util.Collections;
// Scratch variables to avoid allocations. // Scratch variables to avoid allocations.
private final ParsableByteArray seiWrapper; private final ParsableByteArray seiWrapper;
/** public H265Reader() {
* @param output A {@link TrackOutput} to which H.265 samples should be written.
* @param seiReader A reader for CEA-608 samples in SEI NAL units.
*/
public H265Reader(TrackOutput output, SeiReader seiReader) {
super(output);
this.seiReader = seiReader;
prefixFlags = new boolean[3]; prefixFlags = new boolean[3];
vps = new NalUnitTargetBuffer(VPS_NUT, 128); vps = new NalUnitTargetBuffer(VPS_NUT, 128);
sps = new NalUnitTargetBuffer(SPS_NUT, 128); sps = new NalUnitTargetBuffer(SPS_NUT, 128);
@ -91,6 +88,12 @@ import java.util.Collections;
totalBytesWritten = 0; totalBytesWritten = 0;
} }
@Override
public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId()));
}
@Override @Override
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
this.pesTimeUs = pesTimeUs; this.pesTimeUs = pesTimeUs;

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
@ -30,6 +31,8 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private final ParsableByteArray id3Header; private final ParsableByteArray id3Header;
private TrackOutput output;
// State that should be reset on seek. // State that should be reset on seek.
private boolean writingSample; private boolean writingSample;
@ -38,10 +41,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private int sampleSize; private int sampleSize;
private int sampleBytesRead; private int sampleBytesRead;
public Id3Reader(TrackOutput output) { public Id3Reader() {
super(output);
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null, Format.NO_VALUE,
null));
id3Header = new ParsableByteArray(ID3_HEADER_SIZE); id3Header = new ParsableByteArray(ID3_HEADER_SIZE);
} }
@ -50,6 +50,13 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
writingSample = false; writingSample = false;
} }
@Override
public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null, Format.NO_VALUE,
null));
}
@Override @Override
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
if (!dataAlignmentIndicator) { if (!dataAlignmentIndicator) {

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.MpegAudioHeader; import com.google.android.exoplayer2.extractor.MpegAudioHeader;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
@ -36,6 +37,8 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
private final MpegAudioHeader header; private final MpegAudioHeader header;
private final String language; private final String language;
private TrackOutput output;
private int state; private int state;
private int frameBytesRead; private int frameBytesRead;
private boolean hasOutputFormat; private boolean hasOutputFormat;
@ -50,12 +53,11 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
// The timestamp to attach to the next sample in the current packet. // The timestamp to attach to the next sample in the current packet.
private long timeUs; private long timeUs;
public MpegAudioReader(TrackOutput output) { public MpegAudioReader() {
this(output, null); this(null);
} }
public MpegAudioReader(TrackOutput output, String language) { public MpegAudioReader(String language) {
super(output);
state = STATE_FINDING_HEADER; state = STATE_FINDING_HEADER;
// The first byte of an MPEG Audio frame header is always 0xFF. // The first byte of an MPEG Audio frame header is always 0xFF.
headerScratch = new ParsableByteArray(4); headerScratch = new ParsableByteArray(4);
@ -71,6 +73,11 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
lastByteWasFF = false; lastByteWasFF = false;
} }
@Override
public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
output = extractorOutput.track(idGenerator.getNextId());
}
@Override @Override
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
timeUs = pesTimeUs; timeUs = pesTimeUs;

View File

@ -24,6 +24,7 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TimestampAdjuster; import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.IOException; import java.io.IOException;
@ -49,6 +50,7 @@ public final class PsExtractor implements Extractor {
private static final int SYSTEM_HEADER_START_CODE = 0x000001BB; private static final int SYSTEM_HEADER_START_CODE = 0x000001BB;
private static final int PACKET_START_CODE_PREFIX = 0x000001; private static final int PACKET_START_CODE_PREFIX = 0x000001;
private static final int MPEG_PROGRAM_END_CODE = 0x000001B9; private static final int MPEG_PROGRAM_END_CODE = 0x000001B9;
private static final int MAX_STREAM_ID_PLUS_ONE = 0x100;
private static final long MAX_SEARCH_LENGTH = 1024 * 1024; private static final long MAX_SEARCH_LENGTH = 1024 * 1024;
public static final int PRIVATE_STREAM_1 = 0xBD; public static final int PRIVATE_STREAM_1 = 0xBD;
@ -189,16 +191,18 @@ public final class PsExtractor implements Extractor {
// Private stream, used for AC3 audio. // Private stream, used for AC3 audio.
// NOTE: This may need further parsing to determine if its DTS, but that's likely only // NOTE: This may need further parsing to determine if its DTS, but that's likely only
// valid for DVDs. // valid for DVDs.
elementaryStreamReader = new Ac3Reader(output.track(streamId)); elementaryStreamReader = new Ac3Reader();
foundAudioTrack = true; foundAudioTrack = true;
} else if (!foundAudioTrack && (streamId & AUDIO_STREAM_MASK) == AUDIO_STREAM) { } else if (!foundAudioTrack && (streamId & AUDIO_STREAM_MASK) == AUDIO_STREAM) {
elementaryStreamReader = new MpegAudioReader(output.track(streamId)); elementaryStreamReader = new MpegAudioReader();
foundAudioTrack = true; foundAudioTrack = true;
} else if (!foundVideoTrack && (streamId & VIDEO_STREAM_MASK) == VIDEO_STREAM) { } else if (!foundVideoTrack && (streamId & VIDEO_STREAM_MASK) == VIDEO_STREAM) {
elementaryStreamReader = new H262Reader(output.track(streamId)); elementaryStreamReader = new H262Reader();
foundVideoTrack = true; foundVideoTrack = true;
} }
if (elementaryStreamReader != null) { if (elementaryStreamReader != null) {
TrackIdGenerator idGenerator = new TrackIdGenerator(streamId, MAX_STREAM_ID_PLUS_ONE);
elementaryStreamReader.init(output, idGenerator);
payloadReader = new PesReader(elementaryStreamReader, timestampAdjuster); payloadReader = new PesReader(elementaryStreamReader, timestampAdjuster);
psPayloadReaders.put(streamId, payloadReader); psPayloadReaders.put(streamId, payloadReader);
} }

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.ts;
import android.util.Log; import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray; import android.util.SparseIntArray;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
@ -26,6 +27,9 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TimestampAdjuster; import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.EsInfo;
import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
@ -50,11 +54,6 @@ public final class TsExtractor implements Extractor {
}; };
private static final String TAG = "TsExtractor";
private static final int TS_PACKET_SIZE = 188;
private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet.
private static final int TS_PAT_PID = 0;
public static final int TS_STREAM_TYPE_MPA = 0x03; public static final int TS_STREAM_TYPE_MPA = 0x03;
public static final int TS_STREAM_TYPE_MPA_LSF = 0x04; public static final int TS_STREAM_TYPE_MPA_LSF = 0x04;
@ -68,6 +67,12 @@ public final class TsExtractor implements Extractor {
public static final int TS_STREAM_TYPE_H265 = 0x24; public static final int TS_STREAM_TYPE_H265 = 0x24;
public static final int TS_STREAM_TYPE_ID3 = 0x15; public static final int TS_STREAM_TYPE_ID3 = 0x15;
private static final String TAG = "TsExtractor";
private static final int TS_PACKET_SIZE = 188;
private static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet.
private static final int TS_PAT_PID = 0;
private static final int MAX_PID_PLUS_ONE = 0x2000;
private static final long AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("AC-3"); private static final long AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("AC-3");
private static final long E_AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("EAC3"); private static final long E_AC3_FORMAT_IDENTIFIER = Util.getIntegerCodeForString("EAC3");
@ -76,15 +81,18 @@ public final class TsExtractor implements Extractor {
private static final int BUFFER_PACKET_COUNT = 5; // Should be at least 2 private static final int BUFFER_PACKET_COUNT = 5; // Should be at least 2
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 mapByType;
private final TimestampAdjuster timestampAdjuster; private final TimestampAdjuster timestampAdjuster;
private final ParsableByteArray tsPacketBuffer; private final ParsableByteArray tsPacketBuffer;
private final ParsableBitArray tsScratch; private final ParsableBitArray tsScratch;
private final SparseIntArray continuityCounters; private final SparseIntArray continuityCounters;
private final ElementaryStreamReader.Factory streamReaderFactory; private final ElementaryStreamReader.Factory streamReaderFactory;
/* package */ final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid private final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid
private final SparseBooleanArray trackIds;
// Accessed only by the loading thread. // Accessed only by the loading thread.
private ExtractorOutput output; private ExtractorOutput output;
private ElementaryStreamReader id3Reader;
public TsExtractor() { public TsExtractor() {
this(new TimestampAdjuster(0)); this(new TimestampAdjuster(0));
@ -94,19 +102,23 @@ public final class TsExtractor implements Extractor {
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
*/ */
public TsExtractor(TimestampAdjuster timestampAdjuster) { public TsExtractor(TimestampAdjuster timestampAdjuster) {
this(timestampAdjuster, new DefaultStreamReaderFactory()); this(timestampAdjuster, new DefaultStreamReaderFactory(), false);
} }
/** /**
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
* @param customReaderFactory Factory for injecting a custom set of elementary stream readers. * @param customReaderFactory Factory for injecting a custom set of elementary stream readers.
* @param mapByType True if {@link TrackOutput}s should be mapped by their type, false to map them
* by their PID.
*/ */
public TsExtractor(TimestampAdjuster timestampAdjuster, public TsExtractor(TimestampAdjuster timestampAdjuster,
ElementaryStreamReader.Factory customReaderFactory) { ElementaryStreamReader.Factory customReaderFactory, boolean mapByType) {
this.timestampAdjuster = timestampAdjuster; this.timestampAdjuster = timestampAdjuster;
this.streamReaderFactory = Assertions.checkNotNull(customReaderFactory); this.streamReaderFactory = Assertions.checkNotNull(customReaderFactory);
this.mapByType = mapByType;
tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE); tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE);
tsScratch = new ParsableBitArray(new byte[3]); tsScratch = new ParsableBitArray(new byte[3]);
trackIds = new SparseBooleanArray();
tsPayloadReaders = new SparseArray<>(); tsPayloadReaders = new SparseArray<>();
tsPayloadReaders.put(TS_PAT_PID, new PatReader()); tsPayloadReaders.put(TS_PAT_PID, new PatReader());
continuityCounters = new SparseIntArray(); continuityCounters = new SparseIntArray();
@ -413,6 +425,14 @@ public final class TsExtractor implements Extractor {
// Skip the descriptors. // Skip the descriptors.
sectionData.skipBytes(programInfoLength); sectionData.skipBytes(programInfoLength);
if (mapByType && id3Reader == null) {
// Setup an ID3 track regardless of whether there's a corresponding entry, in case one
// appears intermittently during playback. See [Internal: b/20261500].
EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, new byte[0]);
id3Reader = streamReaderFactory.createStreamReader(TS_STREAM_TYPE_ID3, dummyEsInfo);
id3Reader.init(output, new TrackIdGenerator(TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE));
}
int remainingEntriesLength = sectionLength - 9 /* Length of fields before descriptors */ int remainingEntriesLength = sectionLength - 9 /* Length of fields before descriptors */
- programInfoLength - 4 /* CRC length */; - programInfoLength - 4 /* CRC length */;
while (remainingEntriesLength > 0) { while (remainingEntriesLength > 0) {
@ -422,17 +442,28 @@ public final class TsExtractor implements Extractor {
int elementaryPid = pmtScratch.readBits(13); int elementaryPid = pmtScratch.readBits(13);
pmtScratch.skipBits(4); // reserved pmtScratch.skipBits(4); // reserved
int esInfoLength = pmtScratch.readBits(12); // ES_info_length. int esInfoLength = pmtScratch.readBits(12); // ES_info_length.
ElementaryStreamReader.EsInfo esInfo = readEsInfo(sectionData, esInfoLength); EsInfo esInfo = readEsInfo(sectionData, esInfoLength);
if (streamType == 0x06) { if (streamType == 0x06) {
streamType = esInfo.streamType; streamType = esInfo.streamType;
} }
remainingEntriesLength -= esInfoLength + 5; remainingEntriesLength -= esInfoLength + 5;
ElementaryStreamReader pesPayloadReader = streamReaderFactory.onPmtEntry(elementaryPid,
streamType, esInfo, output); int trackId = mapByType ? streamType : elementaryPid;
if (trackIds.get(trackId)) {
continue;
}
trackIds.put(trackId, true);
ElementaryStreamReader pesPayloadReader;
if (mapByType && streamType == TS_STREAM_TYPE_ID3) {
pesPayloadReader = id3Reader;
} else {
pesPayloadReader = streamReaderFactory.createStreamReader(streamType, esInfo);
pesPayloadReader.init(output, new TrackIdGenerator(trackId, MAX_PID_PLUS_ONE));
}
if (pesPayloadReader != null) { if (pesPayloadReader != null) {
tsPayloadReaders.put(elementaryPid, tsPayloadReaders.put(elementaryPid, new PesReader(pesPayloadReader, timestampAdjuster));
new PesReader(pesPayloadReader, timestampAdjuster));
} }
} }
@ -447,7 +478,7 @@ public final class TsExtractor implements Extractor {
* @param length The length of descriptors to read from the current position in {@code data}. * @param length The length of descriptors to read from the current position in {@code data}.
* @return The stream info read from the available descriptors. * @return The stream info read from the available descriptors.
*/ */
private ElementaryStreamReader.EsInfo readEsInfo(ParsableByteArray data, int length) { private EsInfo readEsInfo(ParsableByteArray data, int length) {
int descriptorsStartPosition = data.getPosition(); int descriptorsStartPosition = data.getPosition();
int descriptorsEndPosition = descriptorsStartPosition + length; int descriptorsEndPosition = descriptorsStartPosition + length;
int streamType = -1; int streamType = -1;
@ -479,7 +510,7 @@ public final class TsExtractor implements Extractor {
data.skipBytes(positionOfNextDescriptor - data.getPosition()); data.skipBytes(positionOfNextDescriptor - data.getPosition());
} }
data.setPosition(descriptorsEndPosition); data.setPosition(descriptorsEndPosition);
return new ElementaryStreamReader.EsInfo(streamType, language, return new EsInfo(streamType, language,
Arrays.copyOfRange(sectionData.data, descriptorsStartPosition, descriptorsEndPosition)); Arrays.copyOfRange(sectionData.data, descriptorsStartPosition, descriptorsEndPosition));
} }

View File

@ -369,29 +369,29 @@ import java.util.Locale;
} }
} else if (needNewExtractor) { } else if (needNewExtractor) {
// MPEG-2 TS segments, but we need a new extractor. // MPEG-2 TS segments, but we need a new extractor.
// This flag ensures the change of pid between streams does not affect the sample queues.
@DefaultStreamReaderFactory.WorkaroundFlags
int workaroundFlags = DefaultStreamReaderFactory.WORKAROUND_MAP_BY_TYPE;
String codecs = variants[newVariantIndex].format.codecs;
if (!TextUtils.isEmpty(codecs)) {
// Sometimes AAC and H264 streams are declared in TS chunks even though they don't really
// exist. If we know from the codec attribute that they don't exist, then we can explicitly
// ignore them even if they're declared.
if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) {
workaroundFlags |= DefaultStreamReaderFactory.WORKAROUND_IGNORE_AAC_STREAM;
}
if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) {
workaroundFlags |= DefaultStreamReaderFactory.WORKAROUND_IGNORE_H264_STREAM;
}
}
isTimestampMaster = true; isTimestampMaster = true;
if (useInitializedExtractor) { if (useInitializedExtractor) {
extractor = lastLoadedInitializationChunk.extractor; extractor = lastLoadedInitializationChunk.extractor;
} else { } else {
timestampAdjuster = timestampAdjusterProvider.getAdjuster( timestampAdjuster = timestampAdjusterProvider.getAdjuster(
segment.discontinuitySequenceNumber, startTimeUs); segment.discontinuitySequenceNumber, startTimeUs);
// This flag ensures the change of pid between streams does not affect the sample queues.
@DefaultStreamReaderFactory.Flags
int esReaderFactoryFlags = 0;
String codecs = variants[newVariantIndex].format.codecs;
if (!TextUtils.isEmpty(codecs)) {
// Sometimes AAC and H264 streams are declared in TS chunks even though they don't really
// exist. If we know from the codec attribute that they don't exist, then we can
// explicitly ignore them even if they're declared.
if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) {
esReaderFactoryFlags |= DefaultStreamReaderFactory.FLAG_IGNORE_AAC_STREAM;
}
if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) {
esReaderFactoryFlags |= DefaultStreamReaderFactory.FLAG_IGNORE_H264_STREAM;
}
}
extractor = new TsExtractor(timestampAdjuster, extractor = new TsExtractor(timestampAdjuster,
new DefaultStreamReaderFactory(workaroundFlags)); new DefaultStreamReaderFactory(esReaderFactoryFlags), true);
} }
} else { } else {
// MPEG-2 TS segments, and we need to continue using the same extractor. // MPEG-2 TS segments, and we need to continue using the same extractor.