Convert ElementaryStreamReaderFactory into TsPayloadReaderFactory
In this CL: * PesReader moves out of TsExtractor and becomes public. * ElementaryStreamReaderFactory becomes TsPayloadReaderFactory and is moved to TsPayloadReader. * The TsPayloadReaderFactory is in charge of wrapping any ElementaryStreamReaders with a PesReader. Incoming: * Extract SectionReader supperclass (analog to PesReader, but for sections) from Pat and Pmt readers. * Add a ScteReader, wrapped by a section reader, and include it in the DefaultTsPayloadReaderFactory. Issue:#726 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=136707706
This commit is contained in:
parent
0cff74dc70
commit
862552c2ed
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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()));
|
||||
|
@ -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()));
|
||||
|
@ -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));
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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]);
|
||||
|
@ -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<TsPayloadReader> 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.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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.
|
||||
* <p>
|
||||
* 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);
|
||||
|
||||
}
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user