diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java index e19de76466..ebb547810b 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java @@ -16,7 +16,7 @@ package com.google.android.exoplayer2.extractor.ts; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.TrackIdGenerator; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.testutil.FakeExtractorOutput; import com.google.android.exoplayer2.testutil.FakeTrackOutput; import com.google.android.exoplayer2.testutil.TestUtil; @@ -52,7 +52,7 @@ public class AdtsReaderTest extends TestCase { public static final byte[] ADTS_CONTENT = TestUtil.createByteArray( 0x20, 0x00, 0x20, 0x00, 0x00, 0x80, 0x0e); - private static final byte TEST_DATA[] = TestUtil.joinByteArrays( + private static final byte[] TEST_DATA = TestUtil.joinByteArrays( ID3_DATA_1, ID3_DATA_2, ADTS_HEADER, @@ -73,7 +73,7 @@ public class AdtsReaderTest extends TestCase { id3Output = fakeExtractorOutput.track(1); adtsReader = new AdtsReader(true); TrackIdGenerator idGenerator = new TrackIdGenerator(0, 1); - adtsReader.init(fakeExtractorOutput, idGenerator); + adtsReader.createTracks(fakeExtractorOutput, idGenerator); data = new ParsableByteArray(TEST_DATA); firstFeed = true; } diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java index 1f08507599..a455a3b841 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java @@ -22,7 +22,8 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.PositionHolder; 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.TsPayloadReader.EsInfo; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.testutil.FakeExtractorInput; import com.google.android.exoplayer2.testutil.FakeExtractorOutput; import com.google.android.exoplayer2.testutil.FakeTrackOutput; @@ -106,7 +107,7 @@ public final class TsExtractorTest extends InstrumentationTestCase { } } - private static final class CustomEsReader extends ElementaryStreamReader { + private static final class CustomEsReader implements ElementaryStreamReader { private final String language; private TrackOutput output; @@ -121,7 +122,7 @@ public final class TsExtractorTest extends InstrumentationTestCase { } @Override - public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { output = extractorOutput.track(idGenerator.getNextId()); output.format(Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0, language, null, 0)); @@ -146,22 +147,22 @@ public final class TsExtractorTest extends InstrumentationTestCase { } - private static final class CustomEsReaderFactory implements ElementaryStreamReader.Factory { + private static final class CustomEsReaderFactory implements TsPayloadReader.Factory { - private final ElementaryStreamReader.Factory defaultFactory; + private final TsPayloadReader.Factory defaultFactory; private CustomEsReader reader; public CustomEsReaderFactory() { - defaultFactory = new DefaultStreamReaderFactory(); + defaultFactory = new DefaultTsPayloadReaderFactory(); } @Override - public ElementaryStreamReader createStreamReader(int streamType, EsInfo esInfo) { + public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) { if (streamType == 3) { reader = new CustomEsReader(esInfo.language); - return reader; + return new PesReader(reader); } else { - return defaultFactory.createStreamReader(streamType, esInfo); + return defaultFactory.createPayloadReader(streamType, esInfo); } } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java index 7fc8b429a8..dad8214efa 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java @@ -23,7 +23,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; -import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.TrackIdGenerator; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; @@ -119,7 +119,7 @@ public final class Ac3Extractor implements Extractor { @Override public void init(ExtractorOutput output) { reader = new Ac3Reader(); // TODO: Add support for embedded ID3. - reader.init(output, new TrackIdGenerator(0, 1)); + reader.createTracks(output, new TrackIdGenerator(0, 1)); output.endTracks(); output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java index a9d3319f87..52faa8c673 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java @@ -20,13 +20,14 @@ import com.google.android.exoplayer2.Format; 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.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; /** * Parses a continuous (E-)AC-3 byte stream and extracts individual samples. */ -/* package */ final class Ac3Reader extends ElementaryStreamReader { +/* package */ final class Ac3Reader implements ElementaryStreamReader { private static final int STATE_FINDING_SYNC = 0; private static final int STATE_READING_HEADER = 1; @@ -82,7 +83,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; } @Override - public void init(ExtractorOutput extractorOutput, TrackIdGenerator generator) { + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) { output = extractorOutput.track(generator.getNextId()); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java index 7a9cbd4bb1..76bc4ce66e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java @@ -22,7 +22,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; -import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.TrackIdGenerator; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; @@ -128,7 +128,7 @@ public final class AdtsExtractor implements Extractor { @Override public void init(ExtractorOutput output) { reader = new AdtsReader(true); - reader.init(output, new TrackIdGenerator(0, 1)); + reader.createTracks(output, new TrackIdGenerator(0, 1)); output.endTracks(); output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java index d0474f7e44..47cb217fc7 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java @@ -22,6 +22,7 @@ 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.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.CodecSpecificDataUtil; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableBitArray; @@ -32,7 +33,7 @@ import java.util.Collections; /** * Parses a continuous ADTS byte stream and extracts individual frames. */ -/* package */ final class AdtsReader extends ElementaryStreamReader { +/* package */ final class AdtsReader implements ElementaryStreamReader { private static final String TAG = "AdtsReader"; @@ -106,7 +107,7 @@ import java.util.Collections; } @Override - public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { output = extractorOutput.track(idGenerator.getNextId()); if (exposeId3) { id3Output = extractorOutput.track(idGenerator.getNextId()); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultStreamReaderFactory.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java similarity index 68% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultStreamReaderFactory.java rename to library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index 58a0e55f02..5aabc29a5d 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultStreamReaderFactory.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -16,14 +16,14 @@ package com.google.android.exoplayer2.extractor.ts; import android.support.annotation.IntDef; -import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.EsInfo; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * Default implementation for {@link ElementaryStreamReader.Factory}. + * Default implementation for {@link TsPayloadReader.Factory}. */ -public final class DefaultStreamReaderFactory implements ElementaryStreamReader.Factory { +public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Factory { /** * Flags controlling elementary stream readers behaviour. @@ -41,39 +41,39 @@ public final class DefaultStreamReaderFactory implements ElementaryStreamReader. @Flags private final int flags; - public DefaultStreamReaderFactory() { + public DefaultTsPayloadReaderFactory() { this(0); } - public DefaultStreamReaderFactory(@Flags int flags) { + public DefaultTsPayloadReaderFactory(@Flags int flags) { this.flags = flags; } @Override - public ElementaryStreamReader createStreamReader(int streamType, EsInfo esInfo) { + public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) { switch (streamType) { case TsExtractor.TS_STREAM_TYPE_MPA: case TsExtractor.TS_STREAM_TYPE_MPA_LSF: - return new MpegAudioReader(esInfo.language); + return new PesReader(new MpegAudioReader(esInfo.language)); case TsExtractor.TS_STREAM_TYPE_AAC: return (flags & FLAG_IGNORE_AAC_STREAM) != 0 ? null - : new AdtsReader(false, esInfo.language); + : new PesReader(new AdtsReader(false, esInfo.language)); case TsExtractor.TS_STREAM_TYPE_AC3: case TsExtractor.TS_STREAM_TYPE_E_AC3: - return new Ac3Reader(esInfo.language); + return new PesReader(new Ac3Reader(esInfo.language)); case TsExtractor.TS_STREAM_TYPE_DTS: case TsExtractor.TS_STREAM_TYPE_HDMV_DTS: - return new DtsReader(esInfo.language); + return new PesReader(new DtsReader(esInfo.language)); case TsExtractor.TS_STREAM_TYPE_H262: - return new H262Reader(); + return new PesReader(new H262Reader()); case TsExtractor.TS_STREAM_TYPE_H264: return (flags & FLAG_IGNORE_H264_STREAM) != 0 ? null - : new H264Reader((flags & FLAG_ALLOW_NON_IDR_KEYFRAMES) != 0, - (flags & FLAG_DETECT_ACCESS_UNITS) != 0); + : new PesReader(new H264Reader((flags & FLAG_ALLOW_NON_IDR_KEYFRAMES) != 0, + (flags & FLAG_DETECT_ACCESS_UNITS) != 0)); case TsExtractor.TS_STREAM_TYPE_H265: - return new H265Reader(); + return new PesReader(new H265Reader()); case TsExtractor.TS_STREAM_TYPE_ID3: - return new Id3Reader(); + return new PesReader(new Id3Reader()); default: return null; } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java index 42223ef285..9707685295 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java @@ -20,12 +20,13 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.audio.DtsUtil; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.ParsableByteArray; /** * Parses a continuous DTS byte stream and extracts individual samples. */ -/* package */ final class DtsReader extends ElementaryStreamReader { +/* package */ final class DtsReader implements ElementaryStreamReader { private static final int STATE_FINDING_SYNC = 0; private static final int STATE_READING_HEADER = 1; @@ -77,7 +78,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; } @Override - public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { output = extractorOutput.track(idGenerator.getNextId()); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java index e2efbebb43..57bcf31fc5 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java @@ -22,82 +22,21 @@ import com.google.android.exoplayer2.util.ParsableByteArray; /** * Extracts individual samples from an elementary media stream, preserving original order. */ -public abstract class ElementaryStreamReader { - - /** - * Factory of {@link ElementaryStreamReader} instances. - */ - public interface Factory { - - /** - * 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. - * - * @param streamType Stream type value as defined in the PMT entry or associated descriptors. - * @param esInfo Information associated to the elementary stream provided in the PMT. - * @return 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. - */ - ElementaryStreamReader createStreamReader(int streamType, EsInfo esInfo); - - } - - /** - * Holds descriptor information associated with an elementary stream. - */ - public static final class EsInfo { - - public final int streamType; - public String language; - public byte[] descriptorBytes; - - /** - * @param streamType The type of the stream as defined by the - * {@link TsExtractor}{@code .TS_STREAM_TYPE_*}. - * @param language The language of the stream, as defined by ISO/IEC 13818-1, section 2.6.18. - * @param descriptorBytes The descriptor bytes associated to the stream. - */ - public EsInfo(int streamType, String language, byte[] descriptorBytes) { - this.streamType = streamType; - this.language = language; - this.descriptorBytes = descriptorBytes; - } - - } - - /** - * Generates track ids for initializing {@link ElementaryStreamReader}s' {@link TrackOutput}s. - */ - public static final class TrackIdGenerator { - - 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++; - } - - } +public interface ElementaryStreamReader { /** * Notifies the reader that a seek has occurred. */ - public abstract void seek(); + 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 + * @param idGenerator A {@link PesReader.TrackIdGenerator} that generates unique track ids for the * {@link TrackOutput}s. */ - public abstract void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator); + void createTracks(ExtractorOutput extractorOutput, PesReader.TrackIdGenerator idGenerator); /** * Called when a packet starts. @@ -105,18 +44,18 @@ public abstract class ElementaryStreamReader { * @param pesTimeUs The timestamp associated with the packet. * @param dataAlignmentIndicator The data alignment indicator associated with the packet. */ - public abstract void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator); + void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator); /** * Consumes (possibly partial) data from the current packet. * * @param data The data to consume. */ - public abstract void consume(ParsableByteArray data); + void consume(ParsableByteArray data); /** * Called when a packet ends. */ - public abstract void packetFinished(); + void packetFinished(); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java index fbfe7e1209..02ea6d7c4e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java @@ -20,6 +20,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.ParsableByteArray; @@ -29,7 +30,7 @@ import java.util.Collections; /** * Parses a continuous H262 byte stream and extracts individual frames. */ -/* package */ final class H262Reader extends ElementaryStreamReader { +/* package */ final class H262Reader implements ElementaryStreamReader { private static final int START_PICTURE = 0x00; private static final int START_SEQUENCE_HEADER = 0xB3; @@ -76,7 +77,7 @@ import java.util.Collections; } @Override - public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { output = extractorOutput.track(idGenerator.getNextId()); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java index 6fee9ea6d7..ed4682d9b9 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java @@ -20,6 +20,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.NalUnitUtil.SpsData; @@ -32,7 +33,7 @@ import java.util.List; /** * Parses a continuous H264 byte stream and extracts individual frames. */ -/* package */ final class H264Reader extends ElementaryStreamReader { +/* package */ final class H264Reader implements ElementaryStreamReader { private static final int NAL_UNIT_TYPE_SEI = 6; // Supplemental enhancement information private static final int NAL_UNIT_TYPE_SPS = 7; // Sequence parameter set @@ -86,7 +87,7 @@ import java.util.List; } @Override - public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { output = extractorOutput.track(idGenerator.getNextId()); sampleReader = new SampleReader(output, allowNonIdrKeyframes, detectAccessUnits); seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId())); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java index 57d7e77bb7..a78169a054 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java @@ -20,6 +20,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.ParsableByteArray; @@ -29,7 +30,7 @@ import java.util.Collections; /** * Parses a continuous H.265 byte stream and extracts individual frames. */ -/* package */ final class H265Reader extends ElementaryStreamReader { +/* package */ final class H265Reader implements ElementaryStreamReader { private static final String TAG = "H265Reader"; @@ -88,7 +89,7 @@ import java.util.Collections; } @Override - public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { output = extractorOutput.track(idGenerator.getNextId()); sampleReader = new SampleReader(output); seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId())); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java index 2c657d4aca..c58d847c44 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java @@ -19,13 +19,14 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; /** * Parses ID3 data and extracts individual text information frames. */ -/* package */ final class Id3Reader extends ElementaryStreamReader { +/* package */ final class Id3Reader implements ElementaryStreamReader { private static final int ID3_HEADER_SIZE = 10; @@ -51,7 +52,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; } @Override - public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + 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)); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java index d25d0703ae..c67e7ad0ab 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java @@ -20,12 +20,13 @@ 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.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.ParsableByteArray; /** * Parses a continuous MPEG Audio byte stream and extracts individual frames. */ -/* package */ final class MpegAudioReader extends ElementaryStreamReader { +/* package */ final class MpegAudioReader implements ElementaryStreamReader { private static final int STATE_FINDING_HEADER = 0; private static final int STATE_READING_HEADER = 1; @@ -74,7 +75,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; } @Override - public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { output = extractorOutput.track(idGenerator.getNextId()); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java new file mode 100644 index 0000000000..d6d2b91292 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.extractor.ts; + +import android.util.Log; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.extractor.ExtractorOutput; +import com.google.android.exoplayer2.extractor.TimestampAdjuster; +import com.google.android.exoplayer2.util.ParsableBitArray; +import com.google.android.exoplayer2.util.ParsableByteArray; + +/** + * Parses PES packet data and extracts samples. + */ +public final class PesReader implements TsPayloadReader { + + private static final String TAG = "PesReader"; + + private static final int STATE_FINDING_HEADER = 0; + private static final int STATE_READING_HEADER = 1; + private static final int STATE_READING_HEADER_EXTENSION = 2; + private static final int STATE_READING_BODY = 3; + + private static final int HEADER_SIZE = 9; + private static final int MAX_HEADER_EXTENSION_SIZE = 10; + private static final int PES_SCRATCH_SIZE = 10; // max(HEADER_SIZE, MAX_HEADER_EXTENSION_SIZE) + + private final ElementaryStreamReader reader; + private final ParsableBitArray pesScratch; + + private int state; + private int bytesRead; + + private TimestampAdjuster timestampAdjuster; + private boolean ptsFlag; + private boolean dtsFlag; + private boolean seenFirstDts; + private int extendedHeaderLength; + private int payloadSize; + private boolean dataAlignmentIndicator; + private long timeUs; + + public PesReader(ElementaryStreamReader reader) { + this.reader = reader; + pesScratch = new ParsableBitArray(new byte[PES_SCRATCH_SIZE]); + state = STATE_FINDING_HEADER; + } + + @Override + public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator) { + this.timestampAdjuster = timestampAdjuster; + reader.createTracks(extractorOutput, idGenerator); + } + + // TsPayloadReader implementation. + + @Override + public final void seek() { + state = STATE_FINDING_HEADER; + bytesRead = 0; + seenFirstDts = false; + reader.seek(); + } + + @Override + public final void consume(ParsableByteArray data, boolean payloadUnitStartIndicator, + ExtractorOutput output) { + if (payloadUnitStartIndicator) { + switch (state) { + case STATE_FINDING_HEADER: + case STATE_READING_HEADER: + // Expected. + break; + case STATE_READING_HEADER_EXTENSION: + Log.w(TAG, "Unexpected start indicator reading extended header"); + break; + case STATE_READING_BODY: + // If payloadSize == -1 then the length of the previous packet was unspecified, and so + // we only know that it's finished now that we've seen the start of the next one. This + // is expected. If payloadSize != -1, then the length of the previous packet was known, + // but we didn't receive that amount of data. This is not expected. + if (payloadSize != -1) { + Log.w(TAG, "Unexpected start indicator: expected " + payloadSize + " more bytes"); + } + // Either way, notify the reader that it has now finished. + reader.packetFinished(); + break; + } + setState(STATE_READING_HEADER); + } + + while (data.bytesLeft() > 0) { + switch (state) { + case STATE_FINDING_HEADER: + data.skipBytes(data.bytesLeft()); + break; + case STATE_READING_HEADER: + if (continueRead(data, pesScratch.data, HEADER_SIZE)) { + setState(parseHeader() ? STATE_READING_HEADER_EXTENSION : STATE_FINDING_HEADER); + } + break; + case STATE_READING_HEADER_EXTENSION: + int readLength = Math.min(MAX_HEADER_EXTENSION_SIZE, extendedHeaderLength); + // Read as much of the extended header as we're interested in, and skip the rest. + if (continueRead(data, pesScratch.data, readLength) + && continueRead(data, null, extendedHeaderLength)) { + parseHeaderExtension(); + reader.packetStarted(timeUs, dataAlignmentIndicator); + setState(STATE_READING_BODY); + } + break; + case STATE_READING_BODY: + readLength = data.bytesLeft(); + int padding = payloadSize == -1 ? 0 : readLength - payloadSize; + if (padding > 0) { + readLength -= padding; + data.setLimit(data.getPosition() + readLength); + } + reader.consume(data); + if (payloadSize != -1) { + payloadSize -= readLength; + if (payloadSize == 0) { + reader.packetFinished(); + setState(STATE_READING_HEADER); + } + } + break; + } + } + } + + private void setState(int state) { + this.state = state; + bytesRead = 0; + } + + /** + * Continues a read from the provided {@code source} into a given {@code target}. It's assumed + * that the data should be written into {@code target} starting from an offset of zero. + * + * @param source The source from which to read. + * @param target The target into which data is to be read, or {@code null} to skip. + * @param targetLength The target length of the read. + * @return Whether the target length has been reached. + */ + private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) { + int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead); + if (bytesToRead <= 0) { + return true; + } else if (target == null) { + source.skipBytes(bytesToRead); + } else { + source.readBytes(target, bytesRead, bytesToRead); + } + bytesRead += bytesToRead; + return bytesRead == targetLength; + } + + private boolean parseHeader() { + // Note: see ISO/IEC 13818-1, section 2.4.3.6 for detailed information on the format of + // the header. + pesScratch.setPosition(0); + int startCodePrefix = pesScratch.readBits(24); + if (startCodePrefix != 0x000001) { + Log.w(TAG, "Unexpected start code prefix: " + startCodePrefix); + payloadSize = -1; + return false; + } + + pesScratch.skipBits(8); // stream_id. + int packetLength = pesScratch.readBits(16); + pesScratch.skipBits(5); // '10' (2), PES_scrambling_control (2), PES_priority (1) + dataAlignmentIndicator = pesScratch.readBit(); + pesScratch.skipBits(2); // copyright (1), original_or_copy (1) + ptsFlag = pesScratch.readBit(); + dtsFlag = pesScratch.readBit(); + // ESCR_flag (1), ES_rate_flag (1), DSM_trick_mode_flag (1), + // additional_copy_info_flag (1), PES_CRC_flag (1), PES_extension_flag (1) + pesScratch.skipBits(6); + extendedHeaderLength = pesScratch.readBits(8); + + if (packetLength == 0) { + payloadSize = -1; + } else { + payloadSize = packetLength + 6 /* packetLength does not include the first 6 bytes */ + - HEADER_SIZE - extendedHeaderLength; + } + return true; + } + + private void parseHeaderExtension() { + pesScratch.setPosition(0); + timeUs = C.TIME_UNSET; + if (ptsFlag) { + pesScratch.skipBits(4); // '0010' or '0011' + long pts = (long) pesScratch.readBits(3) << 30; + pesScratch.skipBits(1); // marker_bit + pts |= pesScratch.readBits(15) << 15; + pesScratch.skipBits(1); // marker_bit + pts |= pesScratch.readBits(15); + pesScratch.skipBits(1); // marker_bit + if (!seenFirstDts && dtsFlag) { + pesScratch.skipBits(4); // '0011' + long dts = (long) pesScratch.readBits(3) << 30; + pesScratch.skipBits(1); // marker_bit + dts |= pesScratch.readBits(15) << 15; + pesScratch.skipBits(1); // marker_bit + dts |= pesScratch.readBits(15); + pesScratch.skipBits(1); // marker_bit + // Subsequent PES packets may have earlier presentation timestamps than this one, but they + // should all be greater than or equal to this packet's decode timestamp. We feed the + // decode timestamp to the adjuster here so that in the case that this is the first to be + // fed, the adjuster will be able to compute an offset to apply such that the adjusted + // presentation timestamps of all future packets are non-negative. + timestampAdjuster.adjustTsTimestamp(dts); + seenFirstDts = true; + } + timeUs = timestampAdjuster.adjustTsTimestamp(pts); + } + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java index b615a3e8ee..6e80f4c49f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java @@ -24,7 +24,7 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.TimestampAdjuster; -import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.TrackIdGenerator; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; import java.io.IOException; @@ -202,7 +202,7 @@ public final class PsExtractor implements Extractor { } if (elementaryStreamReader != null) { TrackIdGenerator idGenerator = new TrackIdGenerator(streamId, MAX_STREAM_ID_PLUS_ONE); - elementaryStreamReader.init(output, idGenerator); + elementaryStreamReader.createTracks(output, idGenerator); payloadReader = new PesReader(elementaryStreamReader, timestampAdjuster); psPayloadReaders.put(streamId, payloadReader); } @@ -253,8 +253,7 @@ public final class PsExtractor implements Extractor { private int extendedHeaderLength; private long timeUs; - public PesReader(ElementaryStreamReader pesPayloadReader, - TimestampAdjuster timestampAdjuster) { + public PesReader(ElementaryStreamReader pesPayloadReader, TimestampAdjuster timestampAdjuster) { this.pesPayloadReader = pesPayloadReader; this.timestampAdjuster = timestampAdjuster; pesScratch = new ParsableBitArray(new byte[PES_SCRATCH_SIZE]); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index 14b3a0cce6..09806eb343 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.extractor.ts; -import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; @@ -28,8 +27,6 @@ import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; 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.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; @@ -85,14 +82,14 @@ public final class TsExtractor implements Extractor { private final ParsableByteArray tsPacketBuffer; private final ParsableBitArray tsScratch; private final SparseIntArray continuityCounters; - private final ElementaryStreamReader.Factory streamReaderFactory; + private final TsPayloadReader.Factory payloadReaderFactory; private final SparseArray tsPayloadReaders; // Indexed by pid private final SparseBooleanArray trackIds; // Accessed only by the loading thread. private ExtractorOutput output; private boolean tracksEnded; - private ElementaryStreamReader id3Reader; + private TsPayloadReader id3Reader; public TsExtractor() { this(new TimestampAdjuster(0)); @@ -102,19 +99,19 @@ public final class TsExtractor implements Extractor { * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. */ public TsExtractor(TimestampAdjuster timestampAdjuster) { - this(timestampAdjuster, new DefaultStreamReaderFactory(), false); + this(timestampAdjuster, new DefaultTsPayloadReaderFactory(), false); } /** * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. - * @param customReaderFactory Factory for injecting a custom set of elementary stream readers. + * @param payloadReaderFactory Factory for injecting a custom set of payload 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, - ElementaryStreamReader.Factory customReaderFactory, boolean mapByType) { + TsPayloadReader.Factory payloadReaderFactory, boolean mapByType) { this.timestampAdjuster = timestampAdjuster; - this.streamReaderFactory = Assertions.checkNotNull(customReaderFactory); + this.payloadReaderFactory = Assertions.checkNotNull(payloadReaderFactory); this.mapByType = mapByType; tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE); tsScratch = new ParsableBitArray(new byte[3]); @@ -258,36 +255,10 @@ public final class TsExtractor implements Extractor { id3Reader = null; } - /** - * Parses TS packet payload data. - */ - private abstract static class TsPayloadReader { - - /** - * Notifies the reader that a seek has occurred. - *

- * Following a call to this method, the data passed to the next invocation of - * {@link #consume(ParsableByteArray, boolean, ExtractorOutput)} will not be a continuation of - * the data that was previously passed. Hence the reader should reset any internal state. - */ - public abstract void seek(); - - /** - * Consumes the payload of a TS packet. - * - * @param data The TS packet. The position will be set to the start of the payload. - * @param payloadUnitStartIndicator Whether payloadUnitStartIndicator was set on the TS packet. - * @param output The output to which parsed data should be written. - */ - public abstract void consume(ParsableByteArray data, boolean payloadUnitStartIndicator, - ExtractorOutput output); - - } - /** * Parses Program Association Table data. */ - private class PatReader extends TsPayloadReader { + private class PatReader implements TsPayloadReader { private final ParsableByteArray sectionData; private final ParsableBitArray patScratch; @@ -301,6 +272,12 @@ public final class TsExtractor implements Extractor { patScratch = new ParsableBitArray(new byte[4]); } + @Override + public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator) { + // Do nothing. + } + @Override public void seek() { // Do nothing. @@ -361,7 +338,7 @@ public final class TsExtractor implements Extractor { /** * Parses Program Map Table. */ - private class PmtReader extends TsPayloadReader { + private class PmtReader implements TsPayloadReader { private static final int TS_PMT_DESC_REGISTRATION = 0x05; private static final int TS_PMT_DESC_ISO639_LANG = 0x0A; @@ -383,6 +360,12 @@ public final class TsExtractor implements Extractor { this.pid = pid; } + @Override + public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator) { + // Do nothing. + } + @Override public void seek() { // Do nothing. @@ -437,8 +420,9 @@ public final class TsExtractor implements Extractor { // 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)); + id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo); + id3Reader.init(timestampAdjuster, output, + new TrackIdGenerator(TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE)); } int remainingEntriesLength = sectionLength - 9 /* Length of fields before descriptors */ @@ -462,18 +446,18 @@ public final class TsExtractor implements Extractor { } trackIds.put(trackId, true); - ElementaryStreamReader pesPayloadReader; + TsPayloadReader reader; if (mapByType && streamType == TS_STREAM_TYPE_ID3) { - pesPayloadReader = id3Reader; + reader = id3Reader; } else { - pesPayloadReader = streamReaderFactory.createStreamReader(streamType, esInfo); - if (pesPayloadReader != null) { - pesPayloadReader.init(output, new TrackIdGenerator(trackId, MAX_PID_PLUS_ONE)); + reader = payloadReaderFactory.createPayloadReader(streamType, esInfo); + if (reader != null) { + reader.init(timestampAdjuster, output, new TrackIdGenerator(trackId, MAX_PID_PLUS_ONE)); } } - if (pesPayloadReader != null) { - tsPayloadReaders.put(elementaryPid, new PesReader(pesPayloadReader, timestampAdjuster)); + if (reader != null) { + tsPayloadReaders.put(elementaryPid, reader); } } if (mapByType) { @@ -534,208 +518,5 @@ public final class TsExtractor implements Extractor { } - /** - * Parses PES packet data and extracts samples. - */ - private static final class PesReader extends TsPayloadReader { - - private static final int STATE_FINDING_HEADER = 0; - private static final int STATE_READING_HEADER = 1; - private static final int STATE_READING_HEADER_EXTENSION = 2; - private static final int STATE_READING_BODY = 3; - - private static final int HEADER_SIZE = 9; - private static final int MAX_HEADER_EXTENSION_SIZE = 10; - private static final int PES_SCRATCH_SIZE = 10; // max(HEADER_SIZE, MAX_HEADER_EXTENSION_SIZE) - - private final ElementaryStreamReader pesPayloadReader; - private final TimestampAdjuster timestampAdjuster; - private final ParsableBitArray pesScratch; - - private int state; - private int bytesRead; - - private boolean ptsFlag; - private boolean dtsFlag; - private boolean seenFirstDts; - private int extendedHeaderLength; - private int payloadSize; - private boolean dataAlignmentIndicator; - private long timeUs; - - public PesReader(ElementaryStreamReader pesPayloadReader, - TimestampAdjuster timestampAdjuster) { - this.pesPayloadReader = pesPayloadReader; - this.timestampAdjuster = timestampAdjuster; - pesScratch = new ParsableBitArray(new byte[PES_SCRATCH_SIZE]); - state = STATE_FINDING_HEADER; - } - - @Override - public void seek() { - state = STATE_FINDING_HEADER; - bytesRead = 0; - seenFirstDts = false; - pesPayloadReader.seek(); - } - - @Override - public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator, - ExtractorOutput output) { - if (payloadUnitStartIndicator) { - switch (state) { - case STATE_FINDING_HEADER: - case STATE_READING_HEADER: - // Expected. - break; - case STATE_READING_HEADER_EXTENSION: - Log.w(TAG, "Unexpected start indicator reading extended header"); - break; - case STATE_READING_BODY: - // If payloadSize == -1 then the length of the previous packet was unspecified, and so - // we only know that it's finished now that we've seen the start of the next one. This - // is expected. If payloadSize != -1, then the length of the previous packet was known, - // but we didn't receive that amount of data. This is not expected. - if (payloadSize != -1) { - Log.w(TAG, "Unexpected start indicator: expected " + payloadSize + " more bytes"); - } - // Either way, notify the reader that it has now finished. - pesPayloadReader.packetFinished(); - break; - } - setState(STATE_READING_HEADER); - } - - while (data.bytesLeft() > 0) { - switch (state) { - case STATE_FINDING_HEADER: - data.skipBytes(data.bytesLeft()); - break; - case STATE_READING_HEADER: - if (continueRead(data, pesScratch.data, HEADER_SIZE)) { - setState(parseHeader() ? STATE_READING_HEADER_EXTENSION : STATE_FINDING_HEADER); - } - break; - case STATE_READING_HEADER_EXTENSION: - int readLength = Math.min(MAX_HEADER_EXTENSION_SIZE, extendedHeaderLength); - // Read as much of the extended header as we're interested in, and skip the rest. - if (continueRead(data, pesScratch.data, readLength) - && continueRead(data, null, extendedHeaderLength)) { - parseHeaderExtension(); - pesPayloadReader.packetStarted(timeUs, dataAlignmentIndicator); - setState(STATE_READING_BODY); - } - break; - case STATE_READING_BODY: - readLength = data.bytesLeft(); - int padding = payloadSize == -1 ? 0 : readLength - payloadSize; - if (padding > 0) { - readLength -= padding; - data.setLimit(data.getPosition() + readLength); - } - pesPayloadReader.consume(data); - if (payloadSize != -1) { - payloadSize -= readLength; - if (payloadSize == 0) { - pesPayloadReader.packetFinished(); - setState(STATE_READING_HEADER); - } - } - break; - } - } - } - - private void setState(int state) { - this.state = state; - bytesRead = 0; - } - - /** - * Continues a read from the provided {@code source} into a given {@code target}. It's assumed - * that the data should be written into {@code target} starting from an offset of zero. - * - * @param source The source from which to read. - * @param target The target into which data is to be read, or {@code null} to skip. - * @param targetLength The target length of the read. - * @return Whether the target length has been reached. - */ - private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) { - int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead); - if (bytesToRead <= 0) { - return true; - } else if (target == null) { - source.skipBytes(bytesToRead); - } else { - source.readBytes(target, bytesRead, bytesToRead); - } - bytesRead += bytesToRead; - return bytesRead == targetLength; - } - - private boolean parseHeader() { - // Note: see ISO/IEC 13818-1, section 2.4.3.6 for detailed information on the format of - // the header. - pesScratch.setPosition(0); - int startCodePrefix = pesScratch.readBits(24); - if (startCodePrefix != 0x000001) { - Log.w(TAG, "Unexpected start code prefix: " + startCodePrefix); - payloadSize = -1; - return false; - } - - pesScratch.skipBits(8); // stream_id. - int packetLength = pesScratch.readBits(16); - pesScratch.skipBits(5); // '10' (2), PES_scrambling_control (2), PES_priority (1) - dataAlignmentIndicator = pesScratch.readBit(); - pesScratch.skipBits(2); // copyright (1), original_or_copy (1) - ptsFlag = pesScratch.readBit(); - dtsFlag = pesScratch.readBit(); - // ESCR_flag (1), ES_rate_flag (1), DSM_trick_mode_flag (1), - // additional_copy_info_flag (1), PES_CRC_flag (1), PES_extension_flag (1) - pesScratch.skipBits(6); - extendedHeaderLength = pesScratch.readBits(8); - - if (packetLength == 0) { - payloadSize = -1; - } else { - payloadSize = packetLength + 6 /* packetLength does not include the first 6 bytes */ - - HEADER_SIZE - extendedHeaderLength; - } - return true; - } - - private void parseHeaderExtension() { - pesScratch.setPosition(0); - timeUs = C.TIME_UNSET; - if (ptsFlag) { - pesScratch.skipBits(4); // '0010' or '0011' - long pts = (long) pesScratch.readBits(3) << 30; - pesScratch.skipBits(1); // marker_bit - pts |= pesScratch.readBits(15) << 15; - pesScratch.skipBits(1); // marker_bit - pts |= pesScratch.readBits(15); - pesScratch.skipBits(1); // marker_bit - if (!seenFirstDts && dtsFlag) { - pesScratch.skipBits(4); // '0011' - long dts = (long) pesScratch.readBits(3) << 30; - pesScratch.skipBits(1); // marker_bit - dts |= pesScratch.readBits(15) << 15; - pesScratch.skipBits(1); // marker_bit - dts |= pesScratch.readBits(15); - pesScratch.skipBits(1); // marker_bit - // Subsequent PES packets may have earlier presentation timestamps than this one, but they - // should all be greater than or equal to this packet's decode timestamp. We feed the - // decode timestamp to the adjuster here so that in the case that this is the first to be - // fed, the adjuster will be able to compute an offset to apply such that the adjusted - // presentation timestamps of all future packets are non-negative. - timestampAdjuster.adjustTsTimestamp(dts); - seenFirstDts = true; - } - timeUs = timestampAdjuster.adjustTsTimestamp(pts); - } - } - - } } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java new file mode 100644 index 0000000000..ac0a37fb7a --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.extractor.ts; + +import com.google.android.exoplayer2.extractor.ExtractorOutput; +import com.google.android.exoplayer2.extractor.TimestampAdjuster; +import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.util.ParsableByteArray; + +/** + * Parses TS packet payload data. + */ +public interface TsPayloadReader { + + /** + * Factory of {@link TsPayloadReader} instances. + */ + interface Factory { + + /** + * Returns a {@link TsPayloadReader} for a given stream type and elementary stream information. + * May return null if the stream type is not supported. + * + * @param streamType Stream type value as defined in the PMT entry or associated descriptors. + * @param esInfo Information associated to the elementary stream provided in the PMT. + * @return A {@link TsPayloadReader} for the packet stream carried by the provided pid. + * {@code null} if the stream is not supported. + */ + TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo); + + } + + /** + * Holds information associated with a PMT entry. + */ + final class EsInfo { + + public final int streamType; + public final String language; + public final byte[] descriptorBytes; + + /** + * @param streamType The type of the stream as defined by the + * {@link TsExtractor}{@code .TS_STREAM_TYPE_*}. + * @param language The language of the stream, as defined by ISO/IEC 13818-1, section 2.6.18. + * @param descriptorBytes The descriptor bytes associated to the stream. + */ + public EsInfo(int streamType, String language, byte[] descriptorBytes) { + this.streamType = streamType; + this.language = language; + this.descriptorBytes = descriptorBytes; + } + + } + + /** + * Generates track ids for initializing {@link TsPayloadReader}s' {@link TrackOutput}s. + */ + final class TrackIdGenerator { + + 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++; + } + + } + + /** + * Initializes the payload reader. + * + * @param timestampAdjuster + * @param extractorOutput + * @param idGenerator + */ + void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput, + TrackIdGenerator idGenerator); + + /** + * Notifies the reader that a seek has occurred. + *

+ * Following a call to this method, the data passed to the next invocation of + * {@link #consume(ParsableByteArray, boolean, ExtractorOutput)} will not be a continuation of + * the data that was previously passed. Hence the reader should reset any internal state. + */ + void seek(); + + /** + * Consumes the payload of a TS packet. + * + * @param data The TS packet. The position will be set to the start of the payload. + * @param payloadUnitStartIndicator Whether payloadUnitStartIndicator was set on the TS packet. + * @param output The output to which parsed data should be written. + */ + void consume(ParsableByteArray data, boolean payloadUnitStartIndicator, ExtractorOutput output); + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index a23ab3bae7..7ef16f361d 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -26,7 +26,7 @@ import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor; import com.google.android.exoplayer2.extractor.ts.Ac3Extractor; import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; -import com.google.android.exoplayer2.extractor.ts.DefaultStreamReaderFactory; +import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; import com.google.android.exoplayer2.extractor.ts.TsExtractor; import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.TrackGroup; @@ -384,7 +384,7 @@ import java.util.Locale; timestampAdjuster = timestampAdjusterProvider.getAdjuster( segment.discontinuitySequenceNumber, startTimeUs); // This flag ensures the change of pid between streams does not affect the sample queues. - @DefaultStreamReaderFactory.Flags + @DefaultTsPayloadReaderFactory.Flags int esReaderFactoryFlags = 0; String codecs = variants[newVariantIndex].format.codecs; if (!TextUtils.isEmpty(codecs)) { @@ -392,14 +392,14 @@ import java.util.Locale; // 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; + esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM; } if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) { - esReaderFactoryFlags |= DefaultStreamReaderFactory.FLAG_IGNORE_H264_STREAM; + esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM; } } extractor = new TsExtractor(timestampAdjuster, - new DefaultStreamReaderFactory(esReaderFactoryFlags), true); + new DefaultTsPayloadReaderFactory(esReaderFactoryFlags), true); } } else { // MPEG-2 TS segments, and we need to continue using the same extractor.