mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Merge branch 'dev-v2-id3' of persistent-https://github.com/google/ExoPlayer into dev-v2-id3
This commit is contained in:
commit
6dbce88102
53 changed files with 754 additions and 571 deletions
|
|
@ -19,7 +19,7 @@ buildscript {
|
||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:2.1.2'
|
classpath 'com.android.tools.build:gradle:2.2.1'
|
||||||
classpath 'com.novoda:bintray-release:0.3.4'
|
classpath 'com.novoda:bintray-release:0.3.4'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':library')
|
compile project(':library')
|
||||||
compile('com.squareup.okhttp3:okhttp:+') {
|
compile('com.squareup.okhttp3:okhttp:3.4.1') {
|
||||||
exclude group: 'org.json'
|
exclude group: 'org.json'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,8 @@ public class OkHttpDataSource implements HttpDataSource {
|
||||||
private long bytesRead;
|
private long bytesRead;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param callFactory A {@link Call.Factory} for use by the source.
|
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
|
||||||
|
* by the source.
|
||||||
* @param userAgent The User-Agent string that should be used.
|
* @param userAgent The User-Agent string that should be used.
|
||||||
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
|
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
|
||||||
* predicate then a InvalidContentTypeException} is thrown from {@link #open(DataSpec)}.
|
* predicate then a InvalidContentTypeException} is thrown from {@link #open(DataSpec)}.
|
||||||
|
|
@ -76,7 +77,8 @@ public class OkHttpDataSource implements HttpDataSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param callFactory A {@link Call.Factory} for use by the source.
|
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
|
||||||
|
* by the source.
|
||||||
* @param userAgent The User-Agent string that should be used.
|
* @param userAgent The User-Agent string that should be used.
|
||||||
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
|
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
|
||||||
* predicate then a {@link InvalidContentTypeException} is thrown from
|
* predicate then a {@link InvalidContentTypeException} is thrown from
|
||||||
|
|
@ -89,14 +91,14 @@ public class OkHttpDataSource implements HttpDataSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param callFactory An {@link Call.Factory} for use by the source.
|
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
|
||||||
|
* by the source.
|
||||||
* @param userAgent The User-Agent string that should be used.
|
* @param userAgent The User-Agent string that should be used.
|
||||||
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
|
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
|
||||||
* predicate then a {@link InvalidContentTypeException} is thrown from
|
* predicate then a {@link InvalidContentTypeException} is thrown from
|
||||||
* {@link #open(DataSpec)}.
|
* {@link #open(DataSpec)}.
|
||||||
* @param listener An optional listener.
|
* @param listener An optional listener.
|
||||||
* @param cacheControl An optional {@link CacheControl} which sets all requests' Cache-Control
|
* @param cacheControl An optional {@link CacheControl} for setting the Cache-Control header.
|
||||||
* header. For example, you could force the network response for all requests.
|
|
||||||
*/
|
*/
|
||||||
public OkHttpDataSource(Call.Factory callFactory, String userAgent,
|
public OkHttpDataSource(Call.Factory callFactory, String userAgent,
|
||||||
Predicate<String> contentTypePredicate, TransferListener<? super OkHttpDataSource> listener,
|
Predicate<String> contentTypePredicate, TransferListener<? super OkHttpDataSource> listener,
|
||||||
|
|
|
||||||
|
|
@ -28,25 +28,38 @@ public final class OkHttpDataSourceFactory implements Factory {
|
||||||
|
|
||||||
private final Call.Factory callFactory;
|
private final Call.Factory callFactory;
|
||||||
private final String userAgent;
|
private final String userAgent;
|
||||||
private final TransferListener<? super DataSource> transferListener;
|
private final TransferListener<? super DataSource> listener;
|
||||||
private final CacheControl cacheControl;
|
private final CacheControl cacheControl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
|
||||||
|
* by the sources created by the factory.
|
||||||
|
* @param userAgent The User-Agent string that should be used.
|
||||||
|
* @param listener An optional listener.
|
||||||
|
*/
|
||||||
public OkHttpDataSourceFactory(Call.Factory callFactory, String userAgent,
|
public OkHttpDataSourceFactory(Call.Factory callFactory, String userAgent,
|
||||||
TransferListener<? super DataSource> transferListener) {
|
TransferListener<? super DataSource> listener) {
|
||||||
this(callFactory, userAgent, transferListener, null);
|
this(callFactory, userAgent, listener, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
|
||||||
|
* by the sources created by the factory.
|
||||||
|
* @param userAgent The User-Agent string that should be used.
|
||||||
|
* @param listener An optional listener.
|
||||||
|
* @param cacheControl An optional {@link CacheControl} for setting the Cache-Control header.
|
||||||
|
*/
|
||||||
public OkHttpDataSourceFactory(Call.Factory callFactory, String userAgent,
|
public OkHttpDataSourceFactory(Call.Factory callFactory, String userAgent,
|
||||||
TransferListener<? super DataSource> transferListener, CacheControl cacheControl) {
|
TransferListener<? super DataSource> listener, CacheControl cacheControl) {
|
||||||
this.callFactory = callFactory;
|
this.callFactory = callFactory;
|
||||||
this.userAgent = userAgent;
|
this.userAgent = userAgent;
|
||||||
this.transferListener = transferListener;
|
this.listener = listener;
|
||||||
this.cacheControl = cacheControl;
|
this.cacheControl = cacheControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OkHttpDataSource createDataSource() {
|
public OkHttpDataSource createDataSource() {
|
||||||
return new OkHttpDataSource(callFactory, userAgent, null, transferListener, cacheControl);
|
return new OkHttpDataSource(callFactory, userAgent, null, listener, cacheControl);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,10 +73,13 @@ import javax.microedition.khronos.opengles.GL10;
|
||||||
private final int[] yuvTextures = new int[3];
|
private final int[] yuvTextures = new int[3];
|
||||||
private final AtomicReference<VpxOutputBuffer> pendingOutputBufferReference;
|
private final AtomicReference<VpxOutputBuffer> pendingOutputBufferReference;
|
||||||
|
|
||||||
|
// Kept in a field rather than a local variable so that it doesn't get garbage collected before
|
||||||
|
// glDrawArrays uses it.
|
||||||
|
@SuppressWarnings("FieldCanBeLocal")
|
||||||
|
private FloatBuffer textureCoords;
|
||||||
private int program;
|
private int program;
|
||||||
private int texLocation;
|
private int texLocation;
|
||||||
private int colorMatrixLocation;
|
private int colorMatrixLocation;
|
||||||
private FloatBuffer textureCoords;
|
|
||||||
private int previousWidth;
|
private int previousWidth;
|
||||||
private int previousStride;
|
private int previousStride;
|
||||||
|
|
||||||
|
|
|
||||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
#Thu Sep 01 11:39:15 BST 2016
|
#Mon Oct 24 14:40:37 BST 2016
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,11 @@ android {
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||||
}
|
}
|
||||||
debug {
|
// Re-enable test coverage when the following issue is fixed:
|
||||||
testCoverageEnabled = true
|
// https://code.google.com/p/android/issues/detail?id=226070
|
||||||
}
|
// debug {
|
||||||
|
// testCoverageEnabled = true
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
lintOptions {
|
lintOptions {
|
||||||
|
|
@ -55,7 +57,7 @@ dependencies {
|
||||||
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
|
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
|
||||||
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
|
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
|
||||||
androidTestCompile 'org.mockito:mockito-core:1.9.5'
|
androidTestCompile 'org.mockito:mockito-core:1.9.5'
|
||||||
compile 'com.android.support:support-annotations:24.2.0'
|
compile 'com.android.support:support-annotations:24.2.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
android.libraryVariants.all { variant ->
|
android.libraryVariants.all { variant ->
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,9 @@ import junit.framework.TestCase;
|
||||||
*/
|
*/
|
||||||
public final class DefaultOggSeekerTest extends TestCase {
|
public final class DefaultOggSeekerTest extends TestCase {
|
||||||
|
|
||||||
public void testSetupUnboundAudioLength() {
|
public void testSetupWithUnsetEndPositionFails() {
|
||||||
try {
|
try {
|
||||||
new DefaultOggSeeker(0, C.LENGTH_UNSET, new TestStreamReader());
|
new DefaultOggSeeker(0, C.LENGTH_UNSET, new TestStreamReader(), 1, 1);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
// ignored
|
// ignored
|
||||||
|
|
@ -43,11 +43,12 @@ public final class DefaultOggSeekerTest extends TestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSeeking(Random random) throws IOException, InterruptedException {
|
private void testSeeking(Random random) throws IOException, InterruptedException {
|
||||||
OggTestFile testFile = OggTestFile.generate(random, 1000);
|
OggTestFile testFile = OggTestFile.generate(random, 1000);
|
||||||
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(testFile.data).build();
|
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(testFile.data).build();
|
||||||
TestStreamReader streamReader = new TestStreamReader();
|
TestStreamReader streamReader = new TestStreamReader();
|
||||||
DefaultOggSeeker oggSeeker = new DefaultOggSeeker(0, testFile.data.length, streamReader);
|
DefaultOggSeeker oggSeeker = new DefaultOggSeeker(0, testFile.data.length, streamReader,
|
||||||
|
testFile.firstPayloadPageSize, testFile.firstPayloadPageGranulePosition);
|
||||||
OggPageHeader pageHeader = new OggPageHeader();
|
OggPageHeader pageHeader = new OggPageHeader();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ import junit.framework.TestCase;
|
||||||
public class DefaultOggSeekerUtilMethodsTest extends TestCase {
|
public class DefaultOggSeekerUtilMethodsTest extends TestCase {
|
||||||
|
|
||||||
private Random random = new Random(0);
|
private Random random = new Random(0);
|
||||||
|
|
||||||
public void testSkipToNextPage() throws Exception {
|
public void testSkipToNextPage() throws Exception {
|
||||||
FakeExtractorInput extractorInput = TestData.createInput(
|
FakeExtractorInput extractorInput = TestData.createInput(
|
||||||
TestUtil.joinByteArrays(
|
TestUtil.joinByteArrays(
|
||||||
|
|
@ -75,7 +75,7 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase {
|
||||||
private static void skipToNextPage(ExtractorInput extractorInput)
|
private static void skipToNextPage(ExtractorInput extractorInput)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
DefaultOggSeeker oggSeeker = new DefaultOggSeeker(0, extractorInput.getLength(),
|
DefaultOggSeeker oggSeeker = new DefaultOggSeeker(0, extractorInput.getLength(),
|
||||||
new FlacReader());
|
new FlacReader(), 1, 2);
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
oggSeeker.skipToNextPage(extractorInput);
|
oggSeeker.skipToNextPage(extractorInput);
|
||||||
|
|
@ -143,7 +143,7 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase {
|
||||||
|
|
||||||
private void skipToPageOfGranule(ExtractorInput input, long granule,
|
private void skipToPageOfGranule(ExtractorInput input, long granule,
|
||||||
long elapsedSamplesExpected) throws IOException, InterruptedException {
|
long elapsedSamplesExpected) throws IOException, InterruptedException {
|
||||||
DefaultOggSeeker oggSeeker = new DefaultOggSeeker(0, input.getLength(), new FlacReader());
|
DefaultOggSeeker oggSeeker = new DefaultOggSeeker(0, input.getLength(), new FlacReader(), 1, 2);
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
assertEquals(elapsedSamplesExpected, oggSeeker.skipToPageOfGranule(input, granule, -1));
|
assertEquals(elapsedSamplesExpected, oggSeeker.skipToPageOfGranule(input, granule, -1));
|
||||||
|
|
@ -193,7 +193,7 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase {
|
||||||
|
|
||||||
private void assertReadGranuleOfLastPage(FakeExtractorInput input, int expected)
|
private void assertReadGranuleOfLastPage(FakeExtractorInput input, int expected)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
DefaultOggSeeker oggSeeker = new DefaultOggSeeker(0, input.getLength(), new FlacReader());
|
DefaultOggSeeker oggSeeker = new DefaultOggSeeker(0, input.getLength(), new FlacReader(), 1, 2);
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
assertEquals(expected, oggSeeker.readGranuleOfLastPage(input));
|
assertEquals(expected, oggSeeker.readGranuleOfLastPage(input));
|
||||||
|
|
|
||||||
|
|
@ -33,12 +33,17 @@ import junit.framework.Assert;
|
||||||
long lastGranule;
|
long lastGranule;
|
||||||
int packetCount;
|
int packetCount;
|
||||||
int pageCount;
|
int pageCount;
|
||||||
|
int firstPayloadPageSize;
|
||||||
|
long firstPayloadPageGranulePosition;
|
||||||
|
|
||||||
private OggTestFile(byte[] data, long lastGranule, int packetCount, int pageCount) {
|
private OggTestFile(byte[] data, long lastGranule, int packetCount, int pageCount,
|
||||||
|
int firstPayloadPageSize, long firstPayloadPageGranulePosition) {
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.lastGranule = lastGranule;
|
this.lastGranule = lastGranule;
|
||||||
this.packetCount = packetCount;
|
this.packetCount = packetCount;
|
||||||
this.pageCount = pageCount;
|
this.pageCount = pageCount;
|
||||||
|
this.firstPayloadPageSize = firstPayloadPageSize;
|
||||||
|
this.firstPayloadPageGranulePosition = firstPayloadPageGranulePosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OggTestFile generate(Random random, int pageCount) {
|
public static OggTestFile generate(Random random, int pageCount) {
|
||||||
|
|
@ -47,6 +52,8 @@ import junit.framework.Assert;
|
||||||
long granule = 0;
|
long granule = 0;
|
||||||
int packetLength = -1;
|
int packetLength = -1;
|
||||||
int packetCount = 0;
|
int packetCount = 0;
|
||||||
|
int firstPayloadPageSize = 0;
|
||||||
|
long firstPayloadPageGranulePosition = 0;
|
||||||
|
|
||||||
for (int i = 0; i < pageCount; i++) {
|
for (int i = 0; i < pageCount; i++) {
|
||||||
int headerType = 0x00;
|
int headerType = 0x00;
|
||||||
|
|
@ -89,6 +96,10 @@ import junit.framework.Assert;
|
||||||
byte[] payload = TestUtil.buildTestData(bodySize, random);
|
byte[] payload = TestUtil.buildTestData(bodySize, random);
|
||||||
fileData.add(payload);
|
fileData.add(payload);
|
||||||
fileSize += payload.length;
|
fileSize += payload.length;
|
||||||
|
if (i == 0) {
|
||||||
|
firstPayloadPageSize = header.length + bodySize;
|
||||||
|
firstPayloadPageGranulePosition = granule;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] file = new byte[fileSize];
|
byte[] file = new byte[fileSize];
|
||||||
|
|
@ -97,7 +108,8 @@ import junit.framework.Assert;
|
||||||
System.arraycopy(data, 0, file, position, data.length);
|
System.arraycopy(data, 0, file, position, data.length);
|
||||||
position += data.length;
|
position += data.length;
|
||||||
}
|
}
|
||||||
return new OggTestFile(file, granule, packetCount, pageCount);
|
return new OggTestFile(file, granule, packetCount, pageCount, firstPayloadPageSize,
|
||||||
|
firstPayloadPageGranulePosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int findPreviousPageStart(long position) {
|
public int findPreviousPageStart(long position) {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
package com.google.android.exoplayer2.extractor.ts;
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.TrackIdGenerator;
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
|
import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
|
||||||
import com.google.android.exoplayer2.testutil.FakeTrackOutput;
|
import com.google.android.exoplayer2.testutil.FakeTrackOutput;
|
||||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||||
|
|
@ -52,7 +52,7 @@ public class AdtsReaderTest extends TestCase {
|
||||||
public static final byte[] ADTS_CONTENT = TestUtil.createByteArray(
|
public static final byte[] ADTS_CONTENT = TestUtil.createByteArray(
|
||||||
0x20, 0x00, 0x20, 0x00, 0x00, 0x80, 0x0e);
|
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_1,
|
||||||
ID3_DATA_2,
|
ID3_DATA_2,
|
||||||
ADTS_HEADER,
|
ADTS_HEADER,
|
||||||
|
|
@ -73,7 +73,7 @@ public class AdtsReaderTest extends TestCase {
|
||||||
id3Output = fakeExtractorOutput.track(1);
|
id3Output = fakeExtractorOutput.track(1);
|
||||||
adtsReader = new AdtsReader(true);
|
adtsReader = new AdtsReader(true);
|
||||||
TrackIdGenerator idGenerator = new TrackIdGenerator(0, 1);
|
TrackIdGenerator idGenerator = new TrackIdGenerator(0, 1);
|
||||||
adtsReader.init(fakeExtractorOutput, idGenerator);
|
adtsReader.createTracks(fakeExtractorOutput, idGenerator);
|
||||||
data = new ParsableByteArray(TEST_DATA);
|
data = new ParsableByteArray(TEST_DATA);
|
||||||
firstFeed = true;
|
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.PositionHolder;
|
||||||
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
|
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.EsInfo;
|
import com.google.android.exoplayer2.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.FakeExtractorInput;
|
||||||
import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
|
import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
|
||||||
import com.google.android.exoplayer2.testutil.FakeTrackOutput;
|
import com.google.android.exoplayer2.testutil.FakeTrackOutput;
|
||||||
|
|
@ -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 final String language;
|
||||||
private TrackOutput output;
|
private TrackOutput output;
|
||||||
|
|
@ -121,7 +122,7 @@ public final class TsExtractorTest extends InstrumentationTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
||||||
output = extractorOutput.track(idGenerator.getNextId());
|
output = extractorOutput.track(idGenerator.getNextId());
|
||||||
output.format(Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0,
|
output.format(Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0,
|
||||||
language, null, 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;
|
private CustomEsReader reader;
|
||||||
|
|
||||||
public CustomEsReaderFactory() {
|
public CustomEsReaderFactory() {
|
||||||
defaultFactory = new DefaultStreamReaderFactory();
|
defaultFactory = new DefaultTsPayloadReaderFactory();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ElementaryStreamReader createStreamReader(int streamType, EsInfo esInfo) {
|
public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) {
|
||||||
if (streamType == 3) {
|
if (streamType == 3) {
|
||||||
reader = new CustomEsReader(esInfo.language);
|
reader = new CustomEsReader(esInfo.language);
|
||||||
return reader;
|
return new PesReader(reader);
|
||||||
} else {
|
} else {
|
||||||
return defaultFactory.createStreamReader(streamType, esInfo);
|
return defaultFactory.createPayloadReader(streamType, esInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,7 @@ import java.io.IOException;
|
||||||
|
|
||||||
// Handler.Callback implementation.
|
// Handler.Callback implementation.
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public boolean handleMessage(Message msg) {
|
public boolean handleMessage(Message msg) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -335,8 +336,7 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void prepareInternal(MediaSource mediaSource, boolean resetPosition)
|
private void prepareInternal(MediaSource mediaSource, boolean resetPosition) {
|
||||||
throws ExoPlaybackException {
|
|
||||||
resetInternal();
|
resetInternal();
|
||||||
loadControl.onPrepared();
|
loadControl.onPrepared();
|
||||||
if (resetPosition) {
|
if (resetPosition) {
|
||||||
|
|
@ -884,8 +884,7 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void attemptRestart(Timeline newTimeline, Timeline oldTimeline,
|
private void attemptRestart(Timeline newTimeline, Timeline oldTimeline, int oldPeriodIndex) {
|
||||||
int oldPeriodIndex) throws ExoPlaybackException {
|
|
||||||
int newPeriodIndex = C.INDEX_UNSET;
|
int newPeriodIndex = C.INDEX_UNSET;
|
||||||
while (newPeriodIndex == C.INDEX_UNSET
|
while (newPeriodIndex == C.INDEX_UNSET
|
||||||
&& oldPeriodIndex < oldTimeline.getPeriodCount() - 1) {
|
&& oldPeriodIndex < oldTimeline.getPeriodCount() - 1) {
|
||||||
|
|
@ -1260,7 +1259,7 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long updatePeriodTrackSelection(long positionUs, LoadControl loadControl,
|
public long updatePeriodTrackSelection(long positionUs, LoadControl loadControl,
|
||||||
boolean forceRecreateStreams, boolean[] streamResetFlags) throws ExoPlaybackException {
|
boolean forceRecreateStreams, boolean[] streamResetFlags) {
|
||||||
for (int i = 0; i < trackSelections.length; i++) {
|
for (int i = 0; i < trackSelections.length; i++) {
|
||||||
mayRetainStreamFlags[i] = !forceRecreateStreams
|
mayRetainStreamFlags[i] = !forceRecreateStreams
|
||||||
&& Util.areEqual(periodTrackSelections == null ? null : periodTrackSelections.get(i),
|
&& Util.areEqual(periodTrackSelections == null ? null : periodTrackSelections.get(i),
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import android.os.ConditionVariable;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
@ -981,6 +982,9 @@ public final class AudioTrack {
|
||||||
case C.ENCODING_PCM_32BIT:
|
case C.ENCODING_PCM_32BIT:
|
||||||
resampledSize = size / 2;
|
resampledSize = size / 2;
|
||||||
break;
|
break;
|
||||||
|
case C.ENCODING_PCM_16BIT:
|
||||||
|
case C.ENCODING_INVALID:
|
||||||
|
case Format.NO_VALUE:
|
||||||
default:
|
default:
|
||||||
// Never happens.
|
// Never happens.
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
|
|
@ -1016,6 +1020,9 @@ public final class AudioTrack {
|
||||||
resampledBuffer.put(buffer.get(i + 3));
|
resampledBuffer.put(buffer.get(i + 3));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case C.ENCODING_PCM_16BIT:
|
||||||
|
case C.ENCODING_INVALID:
|
||||||
|
case Format.NO_VALUE:
|
||||||
default:
|
default:
|
||||||
// Never happens.
|
// Never happens.
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
||||||
* has obtained the keys necessary to decrypt encrypted regions of the media.
|
* has obtained the keys necessary to decrypt encrypted regions of the media.
|
||||||
*/
|
*/
|
||||||
public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector,
|
public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector,
|
||||||
DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys) {
|
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
|
||||||
|
boolean playClearSamplesWithoutKeys) {
|
||||||
this(mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, null, null);
|
this(mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ import java.io.IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see Extractor#sniff
|
* @see com.google.android.exoplayer2.extractor.Extractor#sniff(ExtractorInput)
|
||||||
*/
|
*/
|
||||||
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
|
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
|
||||||
long inputLength = input.getLength();
|
long inputLength = input.getLength();
|
||||||
|
|
|
||||||
|
|
@ -59,13 +59,21 @@ import java.io.IOException;
|
||||||
* @param startPosition Start position of the payload (inclusive).
|
* @param startPosition Start position of the payload (inclusive).
|
||||||
* @param endPosition End position of the payload (exclusive).
|
* @param endPosition End position of the payload (exclusive).
|
||||||
* @param streamReader StreamReader instance which owns this OggSeeker
|
* @param streamReader StreamReader instance which owns this OggSeeker
|
||||||
|
* @param firstPayloadPageSize The total size of the first payload page, in bytes.
|
||||||
|
* @param firstPayloadPageGranulePosition The granule position of the first payload page.
|
||||||
*/
|
*/
|
||||||
public DefaultOggSeeker(long startPosition, long endPosition, StreamReader streamReader) {
|
public DefaultOggSeeker(long startPosition, long endPosition, StreamReader streamReader,
|
||||||
|
int firstPayloadPageSize, long firstPayloadPageGranulePosition) {
|
||||||
Assertions.checkArgument(startPosition >= 0 && endPosition > startPosition);
|
Assertions.checkArgument(startPosition >= 0 && endPosition > startPosition);
|
||||||
this.streamReader = streamReader;
|
this.streamReader = streamReader;
|
||||||
this.startPosition = startPosition;
|
this.startPosition = startPosition;
|
||||||
this.endPosition = endPosition;
|
this.endPosition = endPosition;
|
||||||
this.state = STATE_SEEK_TO_END;
|
if (firstPayloadPageSize == endPosition - startPosition) {
|
||||||
|
totalGranules = firstPayloadPageGranulePosition;
|
||||||
|
state = STATE_IDLE;
|
||||||
|
} else {
|
||||||
|
state = STATE_SEEK_TO_END;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -77,9 +85,9 @@ import java.io.IOException;
|
||||||
positionBeforeSeekToEnd = input.getPosition();
|
positionBeforeSeekToEnd = input.getPosition();
|
||||||
state = STATE_READ_LAST_PAGE;
|
state = STATE_READ_LAST_PAGE;
|
||||||
// Seek to the end just before the last page of stream to get the duration.
|
// Seek to the end just before the last page of stream to get the duration.
|
||||||
long lastPagePosition = endPosition - OggPageHeader.MAX_PAGE_SIZE;
|
long lastPageSearchPosition = endPosition - OggPageHeader.MAX_PAGE_SIZE;
|
||||||
if (lastPagePosition > positionBeforeSeekToEnd) {
|
if (lastPageSearchPosition > positionBeforeSeekToEnd) {
|
||||||
return lastPagePosition;
|
return lastPageSearchPosition;
|
||||||
}
|
}
|
||||||
// Fall through.
|
// Fall through.
|
||||||
case STATE_READ_LAST_PAGE:
|
case STATE_READ_LAST_PAGE:
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,10 @@ import java.io.IOException;
|
||||||
} else if (input.getLength() == C.LENGTH_UNSET) {
|
} else if (input.getLength() == C.LENGTH_UNSET) {
|
||||||
oggSeeker = new UnseekableOggSeeker();
|
oggSeeker = new UnseekableOggSeeker();
|
||||||
} else {
|
} else {
|
||||||
oggSeeker = new DefaultOggSeeker(payloadStartPosition, input.getLength(), this);
|
OggPageHeader firstPayloadPageHeader = oggPacket.getPageHeader();
|
||||||
|
oggSeeker = new DefaultOggSeeker(payloadStartPosition, input.getLength(), this,
|
||||||
|
firstPayloadPageHeader.headerSize + firstPayloadPageHeader.bodySize,
|
||||||
|
firstPayloadPageHeader.granulePosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
setupData = null;
|
setupData = null;
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||||
/* package */ final class VorbisBitArray {
|
/* package */ final class VorbisBitArray {
|
||||||
|
|
||||||
public final byte[] data;
|
public final byte[] data;
|
||||||
private int limit;
|
private final int limit;
|
||||||
private int byteOffset;
|
private int byteOffset;
|
||||||
private int bitOffset;
|
private int bitOffset;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,6 @@ public final class RawCcExtractor implements Extractor {
|
||||||
|
|
||||||
private final ParsableByteArray dataScratch;
|
private final ParsableByteArray dataScratch;
|
||||||
|
|
||||||
private ExtractorOutput extractorOutput;
|
|
||||||
private TrackOutput trackOutput;
|
private TrackOutput trackOutput;
|
||||||
|
|
||||||
private int parserState;
|
private int parserState;
|
||||||
|
|
@ -63,10 +62,9 @@ public final class RawCcExtractor implements Extractor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(ExtractorOutput output) {
|
public void init(ExtractorOutput output) {
|
||||||
this.extractorOutput = output;
|
output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET));
|
||||||
extractorOutput.seekMap(new SeekMap.Unseekable(C.TIME_UNSET));
|
trackOutput = output.track(0);
|
||||||
trackOutput = extractorOutput.track(0);
|
output.endTracks();
|
||||||
extractorOutput.endTracks();
|
|
||||||
|
|
||||||
trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608,
|
trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608,
|
||||||
null, Format.NO_VALUE, 0, null, null));
|
null, Format.NO_VALUE, 0, null, null));
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||||
import com.google.android.exoplayer2.extractor.PositionHolder;
|
import com.google.android.exoplayer2.extractor.PositionHolder;
|
||||||
import com.google.android.exoplayer2.extractor.SeekMap;
|
import com.google.android.exoplayer2.extractor.SeekMap;
|
||||||
import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.TrackIdGenerator;
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
||||||
|
|
@ -119,7 +119,7 @@ public final class Ac3Extractor implements Extractor {
|
||||||
@Override
|
@Override
|
||||||
public void init(ExtractorOutput output) {
|
public void init(ExtractorOutput output) {
|
||||||
reader = new Ac3Reader(); // TODO: Add support for embedded ID3.
|
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.endTracks();
|
||||||
output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET));
|
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.audio.Ac3Util;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
import com.google.android.exoplayer2.util.ParsableBitArray;
|
import com.google.android.exoplayer2.util.ParsableBitArray;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a continuous (E-)AC-3 byte stream and extracts individual samples.
|
* 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_FINDING_SYNC = 0;
|
||||||
private static final int STATE_READING_HEADER = 1;
|
private static final int STATE_READING_HEADER = 1;
|
||||||
|
|
@ -82,7 +83,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(ExtractorOutput extractorOutput, TrackIdGenerator generator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) {
|
||||||
output = extractorOutput.track(generator.getNextId());
|
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.ExtractorsFactory;
|
||||||
import com.google.android.exoplayer2.extractor.PositionHolder;
|
import com.google.android.exoplayer2.extractor.PositionHolder;
|
||||||
import com.google.android.exoplayer2.extractor.SeekMap;
|
import com.google.android.exoplayer2.extractor.SeekMap;
|
||||||
import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.TrackIdGenerator;
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
import com.google.android.exoplayer2.util.ParsableBitArray;
|
import com.google.android.exoplayer2.util.ParsableBitArray;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
@ -128,7 +128,7 @@ public final class AdtsExtractor implements Extractor {
|
||||||
@Override
|
@Override
|
||||||
public void init(ExtractorOutput output) {
|
public void init(ExtractorOutput output) {
|
||||||
reader = new AdtsReader(true);
|
reader = new AdtsReader(true);
|
||||||
reader.init(output, new TrackIdGenerator(0, 1));
|
reader.createTracks(output, new TrackIdGenerator(0, 1));
|
||||||
output.endTracks();
|
output.endTracks();
|
||||||
output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET));
|
output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
|
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
|
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.ParsableBitArray;
|
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.
|
* 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";
|
private static final String TAG = "AdtsReader";
|
||||||
|
|
||||||
|
|
@ -106,7 +107,7 @@ import java.util.Collections;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
||||||
output = extractorOutput.track(idGenerator.getNextId());
|
output = extractorOutput.track(idGenerator.getNextId());
|
||||||
if (exposeId3) {
|
if (exposeId3) {
|
||||||
id3Output = extractorOutput.track(idGenerator.getNextId());
|
id3Output = extractorOutput.track(idGenerator.getNextId());
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,14 @@
|
||||||
package com.google.android.exoplayer2.extractor.ts;
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
import android.support.annotation.IntDef;
|
import android.support.annotation.IntDef;
|
||||||
import 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.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
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.
|
* Flags controlling elementary stream readers behaviour.
|
||||||
|
|
@ -41,39 +41,39 @@ public final class DefaultStreamReaderFactory implements ElementaryStreamReader.
|
||||||
@Flags
|
@Flags
|
||||||
private final int flags;
|
private final int flags;
|
||||||
|
|
||||||
public DefaultStreamReaderFactory() {
|
public DefaultTsPayloadReaderFactory() {
|
||||||
this(0);
|
this(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DefaultStreamReaderFactory(@Flags int flags) {
|
public DefaultTsPayloadReaderFactory(@Flags int flags) {
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ElementaryStreamReader createStreamReader(int streamType, EsInfo esInfo) {
|
public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) {
|
||||||
switch (streamType) {
|
switch (streamType) {
|
||||||
case TsExtractor.TS_STREAM_TYPE_MPA:
|
case TsExtractor.TS_STREAM_TYPE_MPA:
|
||||||
case TsExtractor.TS_STREAM_TYPE_MPA_LSF:
|
case TsExtractor.TS_STREAM_TYPE_MPA_LSF:
|
||||||
return new MpegAudioReader(esInfo.language);
|
return new PesReader(new MpegAudioReader(esInfo.language));
|
||||||
case TsExtractor.TS_STREAM_TYPE_AAC:
|
case TsExtractor.TS_STREAM_TYPE_AAC:
|
||||||
return (flags & FLAG_IGNORE_AAC_STREAM) != 0 ? null
|
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_AC3:
|
||||||
case TsExtractor.TS_STREAM_TYPE_E_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_DTS:
|
||||||
case TsExtractor.TS_STREAM_TYPE_HDMV_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:
|
case TsExtractor.TS_STREAM_TYPE_H262:
|
||||||
return new H262Reader();
|
return new PesReader(new H262Reader());
|
||||||
case TsExtractor.TS_STREAM_TYPE_H264:
|
case TsExtractor.TS_STREAM_TYPE_H264:
|
||||||
return (flags & FLAG_IGNORE_H264_STREAM) != 0 ? null
|
return (flags & FLAG_IGNORE_H264_STREAM) != 0 ? null
|
||||||
: new H264Reader((flags & FLAG_ALLOW_NON_IDR_KEYFRAMES) != 0,
|
: new PesReader(new H264Reader((flags & FLAG_ALLOW_NON_IDR_KEYFRAMES) != 0,
|
||||||
(flags & FLAG_DETECT_ACCESS_UNITS) != 0);
|
(flags & FLAG_DETECT_ACCESS_UNITS) != 0));
|
||||||
case TsExtractor.TS_STREAM_TYPE_H265:
|
case TsExtractor.TS_STREAM_TYPE_H265:
|
||||||
return new H265Reader();
|
return new PesReader(new H265Reader());
|
||||||
case TsExtractor.TS_STREAM_TYPE_ID3:
|
case TsExtractor.TS_STREAM_TYPE_ID3:
|
||||||
return new Id3Reader();
|
return new PesReader(new Id3Reader());
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -20,12 +20,13 @@ import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.audio.DtsUtil;
|
import com.google.android.exoplayer2.audio.DtsUtil;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a continuous DTS byte stream and extracts individual samples.
|
* 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_FINDING_SYNC = 0;
|
||||||
private static final int STATE_READING_HEADER = 1;
|
private static final int STATE_READING_HEADER = 1;
|
||||||
|
|
@ -77,7 +78,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
||||||
output = extractorOutput.track(idGenerator.getNextId());
|
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.
|
* Extracts individual samples from an elementary media stream, preserving original order.
|
||||||
*/
|
*/
|
||||||
public abstract class ElementaryStreamReader {
|
public interface 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++;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies the reader that a seek has occurred.
|
* 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.
|
* Initializes the reader by providing outputs and ids for the tracks.
|
||||||
*
|
*
|
||||||
* @param extractorOutput The {@link ExtractorOutput} that receives the extracted data.
|
* @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.
|
* {@link TrackOutput}s.
|
||||||
*/
|
*/
|
||||||
public abstract void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator);
|
void createTracks(ExtractorOutput extractorOutput, PesReader.TrackIdGenerator idGenerator);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a packet starts.
|
* Called when a packet starts.
|
||||||
|
|
@ -105,18 +44,18 @@ public abstract class ElementaryStreamReader {
|
||||||
* @param pesTimeUs The timestamp associated with the packet.
|
* @param pesTimeUs The timestamp associated with the packet.
|
||||||
* @param dataAlignmentIndicator The data alignment indicator 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.
|
* Consumes (possibly partial) data from the current packet.
|
||||||
*
|
*
|
||||||
* @param data The data to consume.
|
* @param data The data to consume.
|
||||||
*/
|
*/
|
||||||
public abstract void consume(ParsableByteArray data);
|
void consume(ParsableByteArray data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a packet ends.
|
* 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.Format;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.NalUnitUtil;
|
import com.google.android.exoplayer2.util.NalUnitUtil;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
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.
|
* 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_PICTURE = 0x00;
|
||||||
private static final int START_SEQUENCE_HEADER = 0xB3;
|
private static final int START_SEQUENCE_HEADER = 0xB3;
|
||||||
|
|
@ -76,7 +77,7 @@ import java.util.Collections;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
||||||
output = extractorOutput.track(idGenerator.getNextId());
|
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.Format;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.NalUnitUtil;
|
import com.google.android.exoplayer2.util.NalUnitUtil;
|
||||||
import com.google.android.exoplayer2.util.NalUnitUtil.SpsData;
|
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.
|
* 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_SEI = 6; // Supplemental enhancement information
|
||||||
private static final int NAL_UNIT_TYPE_SPS = 7; // Sequence parameter set
|
private static final int NAL_UNIT_TYPE_SPS = 7; // Sequence parameter set
|
||||||
|
|
@ -86,7 +87,7 @@ import java.util.List;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
||||||
output = extractorOutput.track(idGenerator.getNextId());
|
output = extractorOutput.track(idGenerator.getNextId());
|
||||||
sampleReader = new SampleReader(output, allowNonIdrKeyframes, detectAccessUnits);
|
sampleReader = new SampleReader(output, allowNonIdrKeyframes, detectAccessUnits);
|
||||||
seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId()));
|
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.Format;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.NalUnitUtil;
|
import com.google.android.exoplayer2.util.NalUnitUtil;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
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.
|
* 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";
|
private static final String TAG = "H265Reader";
|
||||||
|
|
||||||
|
|
@ -88,7 +89,7 @@ import java.util.Collections;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
||||||
output = extractorOutput.track(idGenerator.getNextId());
|
output = extractorOutput.track(idGenerator.getNextId());
|
||||||
sampleReader = new SampleReader(output);
|
sampleReader = new SampleReader(output);
|
||||||
seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId()));
|
seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId()));
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,21 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor.ts;
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
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.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses ID3 data and extracts individual text information frames.
|
* Parses ID3 data and extracts individual text information frames.
|
||||||
*/
|
*/
|
||||||
/* package */ final class Id3Reader extends ElementaryStreamReader {
|
/* package */ final class Id3Reader implements ElementaryStreamReader {
|
||||||
|
|
||||||
|
private static final String TAG = "Id3Reader";
|
||||||
|
|
||||||
private static final int ID3_HEADER_SIZE = 10;
|
private static final int ID3_HEADER_SIZE = 10;
|
||||||
|
|
||||||
|
|
@ -51,7 +55,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
||||||
output = extractorOutput.track(idGenerator.getNextId());
|
output = extractorOutput.track(idGenerator.getNextId());
|
||||||
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null, Format.NO_VALUE,
|
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null, Format.NO_VALUE,
|
||||||
null));
|
null));
|
||||||
|
|
@ -81,7 +85,14 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
headerBytesAvailable);
|
headerBytesAvailable);
|
||||||
if (sampleBytesRead + headerBytesAvailable == ID3_HEADER_SIZE) {
|
if (sampleBytesRead + headerBytesAvailable == ID3_HEADER_SIZE) {
|
||||||
// We've finished reading the ID3 header. Extract the sample size.
|
// We've finished reading the ID3 header. Extract the sample size.
|
||||||
id3Header.setPosition(6); // 'ID3' (3) + version (2) + flags (1)
|
id3Header.setPosition(0);
|
||||||
|
if ('I' != id3Header.readUnsignedByte() || 'D' != id3Header.readUnsignedByte()
|
||||||
|
|| '3' != id3Header.readUnsignedByte()) {
|
||||||
|
Log.w(TAG, "Discarding invalid ID3 tag");
|
||||||
|
writingSample = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
id3Header.skipBytes(3); // version (2) + flags (1)
|
||||||
sampleSize = ID3_HEADER_SIZE + id3Header.readSynchSafeInt();
|
sampleSize = ID3_HEADER_SIZE + id3Header.readSynchSafeInt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,13 @@ import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
import com.google.android.exoplayer2.extractor.ExtractorOutput;
|
||||||
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
import com.google.android.exoplayer2.extractor.MpegAudioHeader;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a continuous MPEG Audio byte stream and extracts individual frames.
|
* 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_FINDING_HEADER = 0;
|
||||||
private static final int STATE_READING_HEADER = 1;
|
private static final int STATE_READING_HEADER = 1;
|
||||||
|
|
@ -74,7 +75,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
||||||
output = extractorOutput.track(idGenerator.getNextId());
|
output = extractorOutput.track(idGenerator.getNextId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,236 @@
|
||||||
|
/*
|
||||||
|
* 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) {
|
||||||
|
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.PositionHolder;
|
||||||
import com.google.android.exoplayer2.extractor.SeekMap;
|
import com.google.android.exoplayer2.extractor.SeekMap;
|
||||||
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
|
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
|
||||||
import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.TrackIdGenerator;
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
import com.google.android.exoplayer2.util.ParsableBitArray;
|
import com.google.android.exoplayer2.util.ParsableBitArray;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -202,7 +202,7 @@ public final class PsExtractor implements Extractor {
|
||||||
}
|
}
|
||||||
if (elementaryStreamReader != null) {
|
if (elementaryStreamReader != null) {
|
||||||
TrackIdGenerator idGenerator = new TrackIdGenerator(streamId, MAX_STREAM_ID_PLUS_ONE);
|
TrackIdGenerator idGenerator = new TrackIdGenerator(streamId, MAX_STREAM_ID_PLUS_ONE);
|
||||||
elementaryStreamReader.init(output, idGenerator);
|
elementaryStreamReader.createTracks(output, idGenerator);
|
||||||
payloadReader = new PesReader(elementaryStreamReader, timestampAdjuster);
|
payloadReader = new PesReader(elementaryStreamReader, timestampAdjuster);
|
||||||
psPayloadReaders.put(streamId, payloadReader);
|
psPayloadReaders.put(streamId, payloadReader);
|
||||||
}
|
}
|
||||||
|
|
@ -253,8 +253,7 @@ public final class PsExtractor implements Extractor {
|
||||||
private int extendedHeaderLength;
|
private int extendedHeaderLength;
|
||||||
private long timeUs;
|
private long timeUs;
|
||||||
|
|
||||||
public PesReader(ElementaryStreamReader pesPayloadReader,
|
public PesReader(ElementaryStreamReader pesPayloadReader, TimestampAdjuster timestampAdjuster) {
|
||||||
TimestampAdjuster timestampAdjuster) {
|
|
||||||
this.pesPayloadReader = pesPayloadReader;
|
this.pesPayloadReader = pesPayloadReader;
|
||||||
this.timestampAdjuster = timestampAdjuster;
|
this.timestampAdjuster = timestampAdjuster;
|
||||||
pesScratch = new ParsableBitArray(new byte[PES_SCRATCH_SIZE]);
|
pesScratch = new ParsableBitArray(new byte[PES_SCRATCH_SIZE]);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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.util.ParsableByteArray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads section data.
|
||||||
|
*/
|
||||||
|
public interface SectionPayloadReader {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by a {@link SectionReader} when a full section is received.
|
||||||
|
*
|
||||||
|
* @param sectionData The data belonging to a section, including the section header but excluding
|
||||||
|
* the CRC_32 field.
|
||||||
|
*/
|
||||||
|
void consume(ParsableByteArray sectionData);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* 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.util.ParsableBitArray;
|
||||||
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads section data packets and feeds the whole sections to a given {@link SectionPayloadReader}.
|
||||||
|
*/
|
||||||
|
public final class SectionReader implements TsPayloadReader {
|
||||||
|
|
||||||
|
private static final int SECTION_HEADER_LENGTH = 3;
|
||||||
|
|
||||||
|
private final ParsableByteArray sectionData;
|
||||||
|
private final ParsableBitArray headerScratch;
|
||||||
|
private final SectionPayloadReader reader;
|
||||||
|
private int sectionLength;
|
||||||
|
private int sectionBytesRead;
|
||||||
|
|
||||||
|
public SectionReader(SectionPayloadReader reader) {
|
||||||
|
this.reader = reader;
|
||||||
|
sectionData = new ParsableByteArray();
|
||||||
|
headerScratch = new ParsableBitArray(new byte[SECTION_HEADER_LENGTH]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput,
|
||||||
|
TrackIdGenerator idGenerator) {
|
||||||
|
// TODO: Injectable section readers might want to generate metadata tracks.
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek() {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) {
|
||||||
|
// Skip pointer.
|
||||||
|
if (payloadUnitStartIndicator) {
|
||||||
|
int pointerField = data.readUnsignedByte();
|
||||||
|
data.skipBytes(pointerField);
|
||||||
|
|
||||||
|
// Note: see ISO/IEC 13818-1, section 2.4.4.3 for detailed information on the format of
|
||||||
|
// the header.
|
||||||
|
data.readBytes(headerScratch, SECTION_HEADER_LENGTH);
|
||||||
|
data.setPosition(data.getPosition() - SECTION_HEADER_LENGTH);
|
||||||
|
headerScratch.skipBits(12); // table_id (8), section_syntax_indicator (1), 0 (1), reserved (2)
|
||||||
|
sectionLength = headerScratch.readBits(12) + SECTION_HEADER_LENGTH;
|
||||||
|
sectionBytesRead = 0;
|
||||||
|
|
||||||
|
sectionData.reset(sectionLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
int bytesToRead = Math.min(data.bytesLeft(), sectionLength - sectionBytesRead);
|
||||||
|
data.readBytes(sectionData.data, sectionBytesRead, bytesToRead);
|
||||||
|
sectionBytesRead += bytesToRead;
|
||||||
|
if (sectionBytesRead < sectionLength) {
|
||||||
|
// Not yet fully read.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Util.crc(sectionData.data, 0, sectionLength, 0xFFFFFFFF) != 0) {
|
||||||
|
// CRC Invalid. The section gets discarded.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sectionData.setLimit(sectionData.limit() - 4); // Exclude the CRC_32 field.
|
||||||
|
reader.consume(sectionData);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor.ts;
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.util.SparseBooleanArray;
|
import android.util.SparseBooleanArray;
|
||||||
import android.util.SparseIntArray;
|
import android.util.SparseIntArray;
|
||||||
|
|
@ -28,8 +27,8 @@ import com.google.android.exoplayer2.extractor.PositionHolder;
|
||||||
import com.google.android.exoplayer2.extractor.SeekMap;
|
import com.google.android.exoplayer2.extractor.SeekMap;
|
||||||
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
|
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.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.ElementaryStreamReader.TrackIdGenerator;
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.ParsableBitArray;
|
import com.google.android.exoplayer2.util.ParsableBitArray;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
|
|
@ -85,14 +84,14 @@ public final class TsExtractor implements Extractor {
|
||||||
private final ParsableByteArray tsPacketBuffer;
|
private final ParsableByteArray tsPacketBuffer;
|
||||||
private final ParsableBitArray tsScratch;
|
private final ParsableBitArray tsScratch;
|
||||||
private final SparseIntArray continuityCounters;
|
private final SparseIntArray continuityCounters;
|
||||||
private final ElementaryStreamReader.Factory streamReaderFactory;
|
private final TsPayloadReader.Factory payloadReaderFactory;
|
||||||
private final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid
|
private final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid
|
||||||
private final SparseBooleanArray trackIds;
|
private final SparseBooleanArray trackIds;
|
||||||
|
|
||||||
// Accessed only by the loading thread.
|
// Accessed only by the loading thread.
|
||||||
private ExtractorOutput output;
|
private ExtractorOutput output;
|
||||||
private boolean tracksEnded;
|
private boolean tracksEnded;
|
||||||
private ElementaryStreamReader id3Reader;
|
private TsPayloadReader id3Reader;
|
||||||
|
|
||||||
public TsExtractor() {
|
public TsExtractor() {
|
||||||
this(new TimestampAdjuster(0));
|
this(new TimestampAdjuster(0));
|
||||||
|
|
@ -102,19 +101,19 @@ public final class TsExtractor implements Extractor {
|
||||||
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
|
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
|
||||||
*/
|
*/
|
||||||
public TsExtractor(TimestampAdjuster timestampAdjuster) {
|
public TsExtractor(TimestampAdjuster timestampAdjuster) {
|
||||||
this(timestampAdjuster, new DefaultStreamReaderFactory(), false);
|
this(timestampAdjuster, new DefaultTsPayloadReaderFactory(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
|
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
|
||||||
* @param customReaderFactory Factory for injecting a custom set of elementary stream readers.
|
* @param 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
|
* @param mapByType True if {@link TrackOutput}s should be mapped by their type, false to map them
|
||||||
* by their PID.
|
* by their PID.
|
||||||
*/
|
*/
|
||||||
public TsExtractor(TimestampAdjuster timestampAdjuster,
|
public TsExtractor(TimestampAdjuster timestampAdjuster,
|
||||||
ElementaryStreamReader.Factory customReaderFactory, boolean mapByType) {
|
TsPayloadReader.Factory payloadReaderFactory, boolean mapByType) {
|
||||||
this.timestampAdjuster = timestampAdjuster;
|
this.timestampAdjuster = timestampAdjuster;
|
||||||
this.streamReaderFactory = Assertions.checkNotNull(customReaderFactory);
|
this.payloadReaderFactory = Assertions.checkNotNull(payloadReaderFactory);
|
||||||
this.mapByType = mapByType;
|
this.mapByType = mapByType;
|
||||||
tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE);
|
tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE);
|
||||||
tsScratch = new ParsableBitArray(new byte[3]);
|
tsScratch = new ParsableBitArray(new byte[3]);
|
||||||
|
|
@ -239,7 +238,7 @@ public final class TsExtractor implements Extractor {
|
||||||
payloadReader.seek();
|
payloadReader.seek();
|
||||||
}
|
}
|
||||||
tsPacketBuffer.setLimit(endOfPacket);
|
tsPacketBuffer.setLimit(endOfPacket);
|
||||||
payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator, output);
|
payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator);
|
||||||
Assertions.checkState(tsPacketBuffer.getPosition() <= endOfPacket);
|
Assertions.checkState(tsPacketBuffer.getPosition() <= endOfPacket);
|
||||||
tsPacketBuffer.setLimit(limit);
|
tsPacketBuffer.setLimit(limit);
|
||||||
}
|
}
|
||||||
|
|
@ -254,95 +253,29 @@ public final class TsExtractor implements Extractor {
|
||||||
private void resetPayloadReaders() {
|
private void resetPayloadReaders() {
|
||||||
trackIds.clear();
|
trackIds.clear();
|
||||||
tsPayloadReaders.clear();
|
tsPayloadReaders.clear();
|
||||||
tsPayloadReaders.put(TS_PAT_PID, new PatReader());
|
tsPayloadReaders.put(TS_PAT_PID, new SectionReader(new PatReader()));
|
||||||
id3Reader = null;
|
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.
|
* Parses Program Association Table data.
|
||||||
*/
|
*/
|
||||||
private class PatReader extends TsPayloadReader {
|
private class PatReader implements SectionPayloadReader {
|
||||||
|
|
||||||
private final ParsableByteArray sectionData;
|
|
||||||
private final ParsableBitArray patScratch;
|
private final ParsableBitArray patScratch;
|
||||||
|
|
||||||
private int sectionLength;
|
|
||||||
private int sectionBytesRead;
|
|
||||||
private int crc;
|
|
||||||
|
|
||||||
public PatReader() {
|
public PatReader() {
|
||||||
sectionData = new ParsableByteArray();
|
|
||||||
patScratch = new ParsableBitArray(new byte[4]);
|
patScratch = new ParsableBitArray(new byte[4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seek() {
|
public void consume(ParsableByteArray sectionData) {
|
||||||
// Do nothing.
|
// table_id(8), section_syntax_indicator(1), '0'(1), reserved(2), section_length(12),
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
|
||||||
ExtractorOutput output) {
|
|
||||||
// Skip pointer.
|
|
||||||
if (payloadUnitStartIndicator) {
|
|
||||||
int pointerField = data.readUnsignedByte();
|
|
||||||
data.skipBytes(pointerField);
|
|
||||||
|
|
||||||
// Note: see ISO/IEC 13818-1, section 2.4.4.3 for detailed information on the format of
|
|
||||||
// the header.
|
|
||||||
data.readBytes(patScratch, 3);
|
|
||||||
patScratch.skipBits(12); // table_id (8), section_syntax_indicator (1), 0 (1), reserved (2)
|
|
||||||
sectionLength = patScratch.readBits(12);
|
|
||||||
sectionBytesRead = 0;
|
|
||||||
crc = Util.crc(patScratch.data, 0, 3, 0xFFFFFFFF);
|
|
||||||
|
|
||||||
sectionData.reset(sectionLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
int bytesToRead = Math.min(data.bytesLeft(), sectionLength - sectionBytesRead);
|
|
||||||
data.readBytes(sectionData.data, sectionBytesRead, bytesToRead);
|
|
||||||
sectionBytesRead += bytesToRead;
|
|
||||||
if (sectionBytesRead < sectionLength) {
|
|
||||||
// Not yet fully read.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Util.crc(sectionData.data, 0, sectionLength, crc) != 0) {
|
|
||||||
// CRC Invalid. The section gets discarded.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// transport_stream_id (16), reserved (2), version_number (5), current_next_indicator (1),
|
// transport_stream_id (16), reserved (2), version_number (5), current_next_indicator (1),
|
||||||
// section_number (8), last_section_number (8)
|
// section_number (8), last_section_number (8)
|
||||||
sectionData.skipBytes(5);
|
sectionData.skipBytes(8);
|
||||||
|
|
||||||
int programCount = (sectionLength - 9) / 4;
|
int programCount = sectionData.bytesLeft() / 4;
|
||||||
for (int i = 0; i < programCount; i++) {
|
for (int i = 0; i < programCount; i++) {
|
||||||
sectionData.readBytes(patScratch, 4);
|
sectionData.readBytes(patScratch, 4);
|
||||||
int programNumber = patScratch.readBits(16);
|
int programNumber = patScratch.readBits(16);
|
||||||
|
|
@ -351,7 +284,7 @@ public final class TsExtractor implements Extractor {
|
||||||
patScratch.skipBits(13); // network_PID (13)
|
patScratch.skipBits(13); // network_PID (13)
|
||||||
} else {
|
} else {
|
||||||
int pid = patScratch.readBits(13);
|
int pid = patScratch.readBits(13);
|
||||||
tsPayloadReaders.put(pid, new PmtReader(pid));
|
tsPayloadReaders.put(pid, new SectionReader(new PmtReader(pid)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -361,7 +294,7 @@ public final class TsExtractor implements Extractor {
|
||||||
/**
|
/**
|
||||||
* Parses Program Map Table.
|
* Parses Program Map Table.
|
||||||
*/
|
*/
|
||||||
private class PmtReader extends TsPayloadReader {
|
private class PmtReader implements SectionPayloadReader {
|
||||||
|
|
||||||
private static final int TS_PMT_DESC_REGISTRATION = 0x05;
|
private static final int TS_PMT_DESC_REGISTRATION = 0x05;
|
||||||
private static final int TS_PMT_DESC_ISO639_LANG = 0x0A;
|
private static final int TS_PMT_DESC_ISO639_LANG = 0x0A;
|
||||||
|
|
@ -370,60 +303,20 @@ public final class TsExtractor implements Extractor {
|
||||||
private static final int TS_PMT_DESC_DTS = 0x7B;
|
private static final int TS_PMT_DESC_DTS = 0x7B;
|
||||||
|
|
||||||
private final ParsableBitArray pmtScratch;
|
private final ParsableBitArray pmtScratch;
|
||||||
private final ParsableByteArray sectionData;
|
|
||||||
private final int pid;
|
private final int pid;
|
||||||
|
|
||||||
private int sectionLength;
|
|
||||||
private int sectionBytesRead;
|
|
||||||
private int crc;
|
|
||||||
|
|
||||||
public PmtReader(int pid) {
|
public PmtReader(int pid) {
|
||||||
pmtScratch = new ParsableBitArray(new byte[5]);
|
pmtScratch = new ParsableBitArray(new byte[5]);
|
||||||
sectionData = new ParsableByteArray();
|
|
||||||
this.pid = pid;
|
this.pid = pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seek() {
|
public void consume(ParsableByteArray sectionData) {
|
||||||
// Do nothing.
|
// table_id(8), section_syntax_indicator(1), '0'(1), reserved(2), section_length(12),
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
|
|
||||||
ExtractorOutput output) {
|
|
||||||
if (payloadUnitStartIndicator) {
|
|
||||||
// Skip pointer.
|
|
||||||
int pointerField = data.readUnsignedByte();
|
|
||||||
data.skipBytes(pointerField);
|
|
||||||
|
|
||||||
// Note: see ISO/IEC 13818-1, section 2.4.4.8 for detailed information on the format of
|
|
||||||
// the header.
|
|
||||||
data.readBytes(pmtScratch, 3);
|
|
||||||
pmtScratch.skipBits(12); // table_id (8), section_syntax_indicator (1), 0 (1), reserved (2)
|
|
||||||
sectionLength = pmtScratch.readBits(12);
|
|
||||||
sectionBytesRead = 0;
|
|
||||||
crc = Util.crc(pmtScratch.data, 0, 3, 0xFFFFFFFF);
|
|
||||||
|
|
||||||
sectionData.reset(sectionLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
int bytesToRead = Math.min(data.bytesLeft(), sectionLength - sectionBytesRead);
|
|
||||||
data.readBytes(sectionData.data, sectionBytesRead, bytesToRead);
|
|
||||||
sectionBytesRead += bytesToRead;
|
|
||||||
if (sectionBytesRead < sectionLength) {
|
|
||||||
// Not yet fully read.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Util.crc(sectionData.data, 0, sectionLength, crc) != 0) {
|
|
||||||
// CRC Invalid. The section gets discarded.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// program_number (16), reserved (2), version_number (5), current_next_indicator (1),
|
// program_number (16), reserved (2), version_number (5), current_next_indicator (1),
|
||||||
// section_number (8), last_section_number (8), reserved (3), PCR_PID (13)
|
// section_number (8), last_section_number (8), reserved (3), PCR_PID (13)
|
||||||
// Skip the rest of the PMT header.
|
// Skip the rest of the PMT header.
|
||||||
sectionData.skipBytes(7);
|
sectionData.skipBytes(10);
|
||||||
|
|
||||||
// Read program_info_length.
|
// Read program_info_length.
|
||||||
sectionData.readBytes(pmtScratch, 2);
|
sectionData.readBytes(pmtScratch, 2);
|
||||||
|
|
@ -437,12 +330,12 @@ public final class TsExtractor implements Extractor {
|
||||||
// Setup an ID3 track regardless of whether there's a corresponding entry, in case one
|
// Setup an ID3 track regardless of whether there's a corresponding entry, in case one
|
||||||
// appears intermittently during playback. See [Internal: b/20261500].
|
// appears intermittently during playback. See [Internal: b/20261500].
|
||||||
EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, new byte[0]);
|
EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, new byte[0]);
|
||||||
id3Reader = streamReaderFactory.createStreamReader(TS_STREAM_TYPE_ID3, dummyEsInfo);
|
id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo);
|
||||||
id3Reader.init(output, new TrackIdGenerator(TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE));
|
id3Reader.init(timestampAdjuster, output,
|
||||||
|
new TrackIdGenerator(TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE));
|
||||||
}
|
}
|
||||||
|
|
||||||
int remainingEntriesLength = sectionLength - 9 /* Length of fields before descriptors */
|
int remainingEntriesLength = sectionData.bytesLeft();
|
||||||
- programInfoLength - 4 /* CRC length */;
|
|
||||||
while (remainingEntriesLength > 0) {
|
while (remainingEntriesLength > 0) {
|
||||||
sectionData.readBytes(pmtScratch, 5);
|
sectionData.readBytes(pmtScratch, 5);
|
||||||
int streamType = pmtScratch.readBits(8);
|
int streamType = pmtScratch.readBits(8);
|
||||||
|
|
@ -462,16 +355,18 @@ public final class TsExtractor implements Extractor {
|
||||||
}
|
}
|
||||||
trackIds.put(trackId, true);
|
trackIds.put(trackId, true);
|
||||||
|
|
||||||
ElementaryStreamReader pesPayloadReader;
|
TsPayloadReader reader;
|
||||||
if (mapByType && streamType == TS_STREAM_TYPE_ID3) {
|
if (mapByType && streamType == TS_STREAM_TYPE_ID3) {
|
||||||
pesPayloadReader = id3Reader;
|
reader = id3Reader;
|
||||||
} else {
|
} else {
|
||||||
pesPayloadReader = streamReaderFactory.createStreamReader(streamType, esInfo);
|
reader = payloadReaderFactory.createPayloadReader(streamType, esInfo);
|
||||||
pesPayloadReader.init(output, new TrackIdGenerator(trackId, MAX_PID_PLUS_ONE));
|
if (reader != null) {
|
||||||
|
reader.init(timestampAdjuster, output, new TrackIdGenerator(trackId, MAX_PID_PLUS_ONE));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pesPayloadReader != null) {
|
if (reader != null) {
|
||||||
tsPayloadReaders.put(elementaryPid, new PesReader(pesPayloadReader, timestampAdjuster));
|
tsPayloadReaders.put(elementaryPid, reader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (mapByType) {
|
if (mapByType) {
|
||||||
|
|
@ -527,213 +422,10 @@ public final class TsExtractor implements Extractor {
|
||||||
}
|
}
|
||||||
data.setPosition(descriptorsEndPosition);
|
data.setPosition(descriptorsEndPosition);
|
||||||
return new EsInfo(streamType, language,
|
return new EsInfo(streamType, language,
|
||||||
Arrays.copyOfRange(sectionData.data, descriptorsStartPosition, descriptorsEndPosition));
|
Arrays.copyOfRange(data.data, descriptorsStartPosition, descriptorsEndPosition));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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,116 @@
|
||||||
|
/*
|
||||||
|
* 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)} 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.
|
||||||
|
*/
|
||||||
|
void consume(ParsableByteArray data, boolean payloadUnitStartIndicator);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,7 @@ import android.media.MediaCodecList;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
import android.util.SparseIntArray;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -63,8 +64,8 @@ public final class MediaCodecUtil {
|
||||||
|
|
||||||
// Codecs to constant mappings.
|
// Codecs to constant mappings.
|
||||||
// AVC.
|
// AVC.
|
||||||
private static final Map<Integer, Integer> AVC_PROFILE_NUMBER_TO_CONST;
|
private static final SparseIntArray AVC_PROFILE_NUMBER_TO_CONST;
|
||||||
private static final Map<Integer, Integer> AVC_LEVEL_NUMBER_TO_CONST;
|
private static final SparseIntArray AVC_LEVEL_NUMBER_TO_CONST;
|
||||||
private static final String CODEC_ID_AVC1 = "avc1";
|
private static final String CODEC_ID_AVC1 = "avc1";
|
||||||
private static final String CODEC_ID_AVC2 = "avc2";
|
private static final String CODEC_ID_AVC2 = "avc2";
|
||||||
// HEVC.
|
// HEVC.
|
||||||
|
|
@ -222,6 +223,7 @@ public final class MediaCodecUtil {
|
||||||
&& ("CIPAACDecoder".equals(name)
|
&& ("CIPAACDecoder".equals(name)
|
||||||
|| "CIPMP3Decoder".equals(name)
|
|| "CIPMP3Decoder".equals(name)
|
||||||
|| "CIPVorbisDecoder".equals(name)
|
|| "CIPVorbisDecoder".equals(name)
|
||||||
|
|| "CIPAMRNBDecoder".equals(name)
|
||||||
|| "AACDecoder".equals(name)
|
|| "AACDecoder".equals(name)
|
||||||
|| "MP3Decoder".equals(name))) {
|
|| "MP3Decoder".equals(name))) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -364,8 +366,8 @@ public final class MediaCodecUtil {
|
||||||
Log.w(TAG, "Ignoring malformed AVC codec string: " + codec);
|
Log.w(TAG, "Ignoring malformed AVC codec string: " + codec);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Integer profileInteger = null;
|
Integer profileInteger;
|
||||||
Integer levelInteger = null;
|
Integer levelInteger;
|
||||||
try {
|
try {
|
||||||
if (codecsParts[1].length() == 6) {
|
if (codecsParts[1].length() == 6) {
|
||||||
// Format: avc1.xxccyy, where xx is profile and yy level, both hexadecimal.
|
// Format: avc1.xxccyy, where xx is profile and yy level, both hexadecimal.
|
||||||
|
|
@ -555,13 +557,13 @@ public final class MediaCodecUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
AVC_PROFILE_NUMBER_TO_CONST = new HashMap<>();
|
AVC_PROFILE_NUMBER_TO_CONST = new SparseIntArray();
|
||||||
AVC_PROFILE_NUMBER_TO_CONST.put(66, CodecProfileLevel.AVCProfileBaseline);
|
AVC_PROFILE_NUMBER_TO_CONST.put(66, CodecProfileLevel.AVCProfileBaseline);
|
||||||
AVC_PROFILE_NUMBER_TO_CONST.put(77, CodecProfileLevel.AVCProfileMain);
|
AVC_PROFILE_NUMBER_TO_CONST.put(77, CodecProfileLevel.AVCProfileMain);
|
||||||
AVC_PROFILE_NUMBER_TO_CONST.put(88, CodecProfileLevel.AVCProfileExtended);
|
AVC_PROFILE_NUMBER_TO_CONST.put(88, CodecProfileLevel.AVCProfileExtended);
|
||||||
AVC_PROFILE_NUMBER_TO_CONST.put(100, CodecProfileLevel.AVCProfileHigh);
|
AVC_PROFILE_NUMBER_TO_CONST.put(100, CodecProfileLevel.AVCProfileHigh);
|
||||||
|
|
||||||
AVC_LEVEL_NUMBER_TO_CONST = new HashMap<>();
|
AVC_LEVEL_NUMBER_TO_CONST = new SparseIntArray();
|
||||||
AVC_LEVEL_NUMBER_TO_CONST.put(10, CodecProfileLevel.AVCLevel1);
|
AVC_LEVEL_NUMBER_TO_CONST.put(10, CodecProfileLevel.AVCLevel1);
|
||||||
// TODO: Find int for CodecProfileLevel.AVCLevel1b.
|
// TODO: Find int for CodecProfileLevel.AVCLevel1b.
|
||||||
AVC_LEVEL_NUMBER_TO_CONST.put(11, CodecProfileLevel.AVCLevel11);
|
AVC_LEVEL_NUMBER_TO_CONST.put(11, CodecProfileLevel.AVCLevel11);
|
||||||
|
|
|
||||||
|
|
@ -149,9 +149,9 @@ import java.util.IdentityHashMap;
|
||||||
}
|
}
|
||||||
// It must be possible to seek enabled periods to the new position, if there is one.
|
// It must be possible to seek enabled periods to the new position, if there is one.
|
||||||
if (positionUs != C.TIME_UNSET) {
|
if (positionUs != C.TIME_UNSET) {
|
||||||
for (int i = 0; i < enabledPeriods.length; i++) {
|
for (MediaPeriod enabledPeriod : enabledPeriods) {
|
||||||
if (enabledPeriods[i] != periods[0]
|
if (enabledPeriod != periods[0]
|
||||||
&& enabledPeriods[i].seekToUs(positionUs) != positionUs) {
|
&& enabledPeriod.seekToUs(positionUs) != positionUs) {
|
||||||
throw new IllegalStateException("Children seeked to different positions");
|
throw new IllegalStateException("Children seeked to different positions");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.mp4.FragmentedMp4Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
|
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
|
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.extractor.ts.TsExtractor;
|
||||||
import com.google.android.exoplayer2.source.BehindLiveWindowException;
|
import com.google.android.exoplayer2.source.BehindLiveWindowException;
|
||||||
import com.google.android.exoplayer2.source.TrackGroup;
|
import com.google.android.exoplayer2.source.TrackGroup;
|
||||||
|
|
@ -384,7 +384,7 @@ import java.util.Locale;
|
||||||
timestampAdjuster = timestampAdjusterProvider.getAdjuster(
|
timestampAdjuster = timestampAdjusterProvider.getAdjuster(
|
||||||
segment.discontinuitySequenceNumber, startTimeUs);
|
segment.discontinuitySequenceNumber, startTimeUs);
|
||||||
// This flag ensures the change of pid between streams does not affect the sample queues.
|
// This flag ensures the change of pid between streams does not affect the sample queues.
|
||||||
@DefaultStreamReaderFactory.Flags
|
@DefaultTsPayloadReaderFactory.Flags
|
||||||
int esReaderFactoryFlags = 0;
|
int esReaderFactoryFlags = 0;
|
||||||
String codecs = variants[newVariantIndex].format.codecs;
|
String codecs = variants[newVariantIndex].format.codecs;
|
||||||
if (!TextUtils.isEmpty(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
|
// exist. If we know from the codec attribute that they don't exist, then we can
|
||||||
// explicitly ignore them even if they're declared.
|
// explicitly ignore them even if they're declared.
|
||||||
if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) {
|
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))) {
|
if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) {
|
||||||
esReaderFactoryFlags |= DefaultStreamReaderFactory.FLAG_IGNORE_H264_STREAM;
|
esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extractor = new TsExtractor(timestampAdjuster,
|
extractor = new TsExtractor(timestampAdjuster,
|
||||||
new DefaultStreamReaderFactory(esReaderFactoryFlags), true);
|
new DefaultTsPayloadReaderFactory(esReaderFactoryFlags), true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// MPEG-2 TS segments, and we need to continue using the same extractor.
|
// MPEG-2 TS segments, and we need to continue using the same extractor.
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,6 @@ import java.util.List;
|
||||||
private int pendingPrepareCount;
|
private int pendingPrepareCount;
|
||||||
private HlsPlaylist playlist;
|
private HlsPlaylist playlist;
|
||||||
private boolean seenFirstTrackSelection;
|
private boolean seenFirstTrackSelection;
|
||||||
private long durationUs;
|
|
||||||
private boolean isLive;
|
private boolean isLive;
|
||||||
private TrackGroupArray trackGroups;
|
private TrackGroupArray trackGroups;
|
||||||
private HlsSampleStreamWrapper[] sampleStreamWrappers;
|
private HlsSampleStreamWrapper[] sampleStreamWrappers;
|
||||||
|
|
@ -280,7 +279,7 @@ import java.util.List;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The wrapper at index 0 is the one of type TRACK_TYPE_DEFAULT.
|
// The wrapper at index 0 is the one of type TRACK_TYPE_DEFAULT.
|
||||||
durationUs = sampleStreamWrappers[0].getDurationUs();
|
long durationUs = sampleStreamWrappers[0].getDurationUs();
|
||||||
isLive = sampleStreamWrappers[0].isLive();
|
isLive = sampleStreamWrappers[0].isLive();
|
||||||
|
|
||||||
int totalTrackGroupCount = 0;
|
int totalTrackGroupCount = 0;
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ package com.google.android.exoplayer2.text;
|
||||||
*/
|
*/
|
||||||
/* package */ final class SimpleSubtitleOutputBuffer extends SubtitleOutputBuffer {
|
/* package */ final class SimpleSubtitleOutputBuffer extends SubtitleOutputBuffer {
|
||||||
|
|
||||||
private SimpleSubtitleDecoder owner;
|
private final SimpleSubtitleDecoder owner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param owner The decoder that owns this buffer.
|
* @param owner The decoder that owns this buffer.
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
public final class Cea608Decoder extends CeaDecoder {
|
public final class Cea608Decoder extends CeaDecoder {
|
||||||
|
|
||||||
private static final int NTSC_CC_FIELD_1 = 0x00;
|
private static final int NTSC_CC_FIELD_1 = 0x00;
|
||||||
|
private static final int CC_TYPE_MASK = 0x03;
|
||||||
private static final int CC_VALID_FLAG = 0x04;
|
private static final int CC_VALID_FLAG = 0x04;
|
||||||
|
|
||||||
private static final int PAYLOAD_TYPE_CC = 4;
|
private static final int PAYLOAD_TYPE_CC = 4;
|
||||||
|
|
@ -223,7 +224,8 @@ public final class Cea608Decoder extends CeaDecoder {
|
||||||
byte ccData2 = (byte) (ccData.readUnsignedByte() & 0x7F);
|
byte ccData2 = (byte) (ccData.readUnsignedByte() & 0x7F);
|
||||||
|
|
||||||
// Only examine valid NTSC_CC_FIELD_1 packets
|
// Only examine valid NTSC_CC_FIELD_1 packets
|
||||||
if (ccTypeAndValid != (CC_VALID_FLAG | NTSC_CC_FIELD_1)) {
|
if ((ccTypeAndValid & CC_VALID_FLAG) == 0
|
||||||
|
|| (ccTypeAndValid & CC_TYPE_MASK) != NTSC_CC_FIELD_1) {
|
||||||
// TODO: Add support for NTSC_CC_FIELD_2 packets
|
// TODO: Add support for NTSC_CC_FIELD_2 packets
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,21 +92,22 @@ import java.util.Map;
|
||||||
builder.setSpan(new AlignmentSpan.Standard(style.getTextAlign()), start, end,
|
builder.setSpan(new AlignmentSpan.Standard(style.getTextAlign()), start, end,
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
}
|
}
|
||||||
if (style.getFontSizeUnit() != TtmlStyle.UNSPECIFIED) {
|
switch (style.getFontSizeUnit()) {
|
||||||
switch (style.getFontSizeUnit()) {
|
case TtmlStyle.FONT_SIZE_UNIT_PIXEL:
|
||||||
case TtmlStyle.FONT_SIZE_UNIT_PIXEL:
|
builder.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end,
|
||||||
builder.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end,
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
break;
|
||||||
break;
|
case TtmlStyle.FONT_SIZE_UNIT_EM:
|
||||||
case TtmlStyle.FONT_SIZE_UNIT_EM:
|
builder.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end,
|
||||||
builder.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end,
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
break;
|
||||||
break;
|
case TtmlStyle.FONT_SIZE_UNIT_PERCENT:
|
||||||
case TtmlStyle.FONT_SIZE_UNIT_PERCENT:
|
builder.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end,
|
||||||
builder.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end,
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
break;
|
||||||
break;
|
case TtmlStyle.UNSPECIFIED:
|
||||||
}
|
// Do nothing.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -413,21 +413,22 @@ import java.util.regex.Pattern;
|
||||||
spannedText.setSpan(new AlignmentSpan.Standard(style.getTextAlign()), start, end,
|
spannedText.setSpan(new AlignmentSpan.Standard(style.getTextAlign()), start, end,
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
}
|
}
|
||||||
if (style.getFontSizeUnit() != WebvttCssStyle.UNSPECIFIED) {
|
switch (style.getFontSizeUnit()) {
|
||||||
switch (style.getFontSizeUnit()) {
|
case WebvttCssStyle.FONT_SIZE_UNIT_PIXEL:
|
||||||
case WebvttCssStyle.FONT_SIZE_UNIT_PIXEL:
|
spannedText.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end,
|
||||||
spannedText.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end,
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
break;
|
||||||
break;
|
case WebvttCssStyle.FONT_SIZE_UNIT_EM:
|
||||||
case WebvttCssStyle.FONT_SIZE_UNIT_EM:
|
spannedText.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end,
|
||||||
spannedText.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end,
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
break;
|
||||||
break;
|
case WebvttCssStyle.FONT_SIZE_UNIT_PERCENT:
|
||||||
case WebvttCssStyle.FONT_SIZE_UNIT_PERCENT:
|
spannedText.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end,
|
||||||
spannedText.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end,
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
break;
|
||||||
break;
|
case WebvttCssStyle.UNSPECIFIED:
|
||||||
}
|
// Do nothing.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,8 @@ public abstract class MappingTrackSelector extends TrackSelector<MappedTrackInfo
|
||||||
* Returns whether this override contains the specified track index.
|
* Returns whether this override contains the specified track index.
|
||||||
*/
|
*/
|
||||||
public boolean containsTrack(int track) {
|
public boolean containsTrack(int track) {
|
||||||
for (int i = 0; i < tracks.length; i++) {
|
for (int overrideTrack : tracks) {
|
||||||
if (tracks[i] == track) {
|
if (overrideTrack == track) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ public final class RawResourceDataSource implements DataSource {
|
||||||
* @param rawResourceId A raw resource identifier (i.e. a constant defined in {@code R.raw}).
|
* @param rawResourceId A raw resource identifier (i.e. a constant defined in {@code R.raw}).
|
||||||
* @return The corresponding {@link Uri}.
|
* @return The corresponding {@link Uri}.
|
||||||
*/
|
*/
|
||||||
public static final Uri buildRawResourceUri(int rawResourceId) {
|
public static Uri buildRawResourceUri(int rawResourceId) {
|
||||||
return Uri.parse(RAW_RESOURCE_SCHEME + ":///" + rawResourceId);
|
return Uri.parse(RAW_RESOURCE_SCHEME + ":///" + rawResourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -211,10 +211,10 @@ public final class MimeTypes {
|
||||||
} else if (isVideo(mimeType)) {
|
} else if (isVideo(mimeType)) {
|
||||||
return C.TRACK_TYPE_VIDEO;
|
return C.TRACK_TYPE_VIDEO;
|
||||||
} else if (isText(mimeType) || APPLICATION_CEA608.equals(mimeType)
|
} else if (isText(mimeType) || APPLICATION_CEA608.equals(mimeType)
|
||||||
|| APPLICATION_SUBRIP.equals(mimeType) || APPLICATION_TTML.equals(mimeType)
|
|| APPLICATION_CEA708.equals(mimeType) || APPLICATION_SUBRIP.equals(mimeType)
|
||||||
|| APPLICATION_TX3G.equals(mimeType) || APPLICATION_MP4VTT.equals(mimeType)
|
|| APPLICATION_TTML.equals(mimeType) || APPLICATION_TX3G.equals(mimeType)
|
||||||
|| APPLICATION_RAWCC.equals(mimeType) || APPLICATION_VOBSUB.equals(mimeType)
|
|| APPLICATION_MP4VTT.equals(mimeType) || APPLICATION_RAWCC.equals(mimeType)
|
||||||
|| APPLICATION_PGS.equals(mimeType)) {
|
|| APPLICATION_VOBSUB.equals(mimeType) || APPLICATION_PGS.equals(mimeType)) {
|
||||||
return C.TRACK_TYPE_TEXT;
|
return C.TRACK_TYPE_TEXT;
|
||||||
} else if (APPLICATION_ID3.equals(mimeType)) {
|
} else if (APPLICATION_ID3.equals(mimeType)) {
|
||||||
return C.TRACK_TYPE_METADATA;
|
return C.TRACK_TYPE_METADATA;
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom"
|
android:layout_gravity="bottom"
|
||||||
|
|
@ -37,6 +38,7 @@
|
||||||
style="@style/ExoMediaButton.Rewind"/>
|
style="@style/ExoMediaButton.Rewind"/>
|
||||||
|
|
||||||
<ImageButton android:id="@+id/play"
|
<ImageButton android:id="@+id/play"
|
||||||
|
tools:ignore="ContentDescription"
|
||||||
style="@style/ExoMediaButton"/>
|
style="@style/ExoMediaButton"/>
|
||||||
|
|
||||||
<ImageButton android:id="@+id/ffwd"
|
<ImageButton android:id="@+id/ffwd"
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.RendererCapabilities;
|
import com.google.android.exoplayer2.RendererCapabilities;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
|
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
||||||
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
||||||
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
|
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
|
||||||
import com.google.android.exoplayer2.drm.UnsupportedDrmException;
|
import com.google.android.exoplayer2.drm.UnsupportedDrmException;
|
||||||
|
|
@ -696,8 +697,9 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
|
||||||
@Override
|
@Override
|
||||||
@TargetApi(18)
|
@TargetApi(18)
|
||||||
@SuppressWarnings("ResourceType")
|
@SuppressWarnings("ResourceType")
|
||||||
protected final StreamingDrmSessionManager buildDrmSessionManager(final String userAgent) {
|
protected final StreamingDrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManager(
|
||||||
StreamingDrmSessionManager drmSessionManager = null;
|
final String userAgent) {
|
||||||
|
StreamingDrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null;
|
||||||
if (isWidevineEncrypted) {
|
if (isWidevineEncrypted) {
|
||||||
try {
|
try {
|
||||||
// Force L3 if secure decoder is not available.
|
// Force L3 if secure decoder is not available.
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
||||||
import com.google.android.exoplayer2.audio.AudioTrack;
|
import com.google.android.exoplayer2.audio.AudioTrack;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
|
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
||||||
import com.google.android.exoplayer2.playbacktests.util.HostActivity.HostedTest;
|
import com.google.android.exoplayer2.playbacktests.util.HostActivity.HostedTest;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.trackselection.AdaptiveVideoTrackSelection;
|
import com.google.android.exoplayer2.trackselection.AdaptiveVideoTrackSelection;
|
||||||
|
|
@ -130,7 +131,7 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
|
||||||
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
||||||
trackSelector = buildTrackSelector(host, bandwidthMeter);
|
trackSelector = buildTrackSelector(host, bandwidthMeter);
|
||||||
String userAgent = "ExoPlayerPlaybackTests";
|
String userAgent = "ExoPlayerPlaybackTests";
|
||||||
DrmSessionManager drmSessionManager = buildDrmSessionManager(userAgent);
|
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = buildDrmSessionManager(userAgent);
|
||||||
player = buildExoPlayer(host, surface, trackSelector, drmSessionManager);
|
player = buildExoPlayer(host, surface, trackSelector, drmSessionManager);
|
||||||
player.prepare(buildSource(host, Util.getUserAgent(host, userAgent), bandwidthMeter));
|
player.prepare(buildSource(host, Util.getUserAgent(host, userAgent), bandwidthMeter));
|
||||||
player.addListener(this);
|
player.addListener(this);
|
||||||
|
|
@ -296,7 +297,7 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
|
||||||
|
|
||||||
// Internal logic
|
// Internal logic
|
||||||
|
|
||||||
protected DrmSessionManager buildDrmSessionManager(String userAgent) {
|
protected DrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManager(String userAgent) {
|
||||||
// Do nothing. Interested subclasses may override.
|
// Do nothing. Interested subclasses may override.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -309,7 +310,8 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
protected SimpleExoPlayer buildExoPlayer(HostActivity host, Surface surface,
|
protected SimpleExoPlayer buildExoPlayer(HostActivity host, Surface surface,
|
||||||
MappingTrackSelector trackSelector, DrmSessionManager drmSessionManager) {
|
MappingTrackSelector trackSelector,
|
||||||
|
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager) {
|
||||||
SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(host, trackSelector,
|
SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(host, trackSelector,
|
||||||
new DefaultLoadControl(), drmSessionManager, false, 0);
|
new DefaultLoadControl(), drmSessionManager, false, 0);
|
||||||
player.setVideoSurface(surface);
|
player.setVideoSurface(surface);
|
||||||
|
|
|
||||||
|
|
@ -232,7 +232,7 @@ public final class HostActivity extends Activity implements SurfaceHolder.Callba
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
private static final int getWifiLockMode() {
|
private static int getWifiLockMode() {
|
||||||
return Util.SDK_INT < 12 ? WifiManager.WIFI_MODE_FULL : WifiManager.WIFI_MODE_FULL_HIGH_PERF;
|
return Util.SDK_INT < 12 ? WifiManager.WIFI_MODE_FULL : WifiManager.WIFI_MODE_FULL_HIGH_PERF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue