diff --git a/build.gradle b/build.gradle index 8e9032be70..9d14c36b1b 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ buildscript { jcenter() } 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' } } diff --git a/extensions/okhttp/build.gradle b/extensions/okhttp/build.gradle index c7555e9ced..442f0f78dc 100644 --- a/extensions/okhttp/build.gradle +++ b/extensions/okhttp/build.gradle @@ -37,7 +37,7 @@ android { dependencies { compile project(':library') - compile('com.squareup.okhttp3:okhttp:+') { + compile('com.squareup.okhttp3:okhttp:3.4.1') { exclude group: 'org.json' } } diff --git a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java index e56072f368..8577d33781 100644 --- a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java +++ b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSource.java @@ -65,7 +65,8 @@ public class OkHttpDataSource implements HttpDataSource { 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 contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * 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 contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * 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 contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the * predicate then a {@link InvalidContentTypeException} is thrown from * {@link #open(DataSpec)}. * @param listener An optional listener. - * @param cacheControl An optional {@link CacheControl} which sets all requests' Cache-Control - * header. For example, you could force the network response for all requests. + * @param cacheControl An optional {@link CacheControl} for setting the Cache-Control header. */ public OkHttpDataSource(Call.Factory callFactory, String userAgent, Predicate contentTypePredicate, TransferListener listener, diff --git a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceFactory.java b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceFactory.java index a4dd10a8d3..33f204a6f3 100644 --- a/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceFactory.java +++ b/extensions/okhttp/src/main/java/com/google/android/exoplayer2/ext/okhttp/OkHttpDataSourceFactory.java @@ -28,25 +28,38 @@ public final class OkHttpDataSourceFactory implements Factory { private final Call.Factory callFactory; private final String userAgent; - private final TransferListener transferListener; + private final TransferListener listener; 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, - TransferListener transferListener) { - this(callFactory, userAgent, transferListener, null); + TransferListener listener) { + 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, - TransferListener transferListener, CacheControl cacheControl) { + TransferListener listener, CacheControl cacheControl) { this.callFactory = callFactory; this.userAgent = userAgent; - this.transferListener = transferListener; + this.listener = listener; this.cacheControl = cacheControl; } @Override public OkHttpDataSource createDataSource() { - return new OkHttpDataSource(callFactory, userAgent, null, transferListener, cacheControl); + return new OkHttpDataSource(callFactory, userAgent, null, listener, cacheControl); } } diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxRenderer.java index a0eccb41a7..d108ae8b4f 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer2/ext/vp9/VpxRenderer.java @@ -73,10 +73,13 @@ import javax.microedition.khronos.opengles.GL10; private final int[] yuvTextures = new int[3]; private final AtomicReference 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 texLocation; private int colorMatrixLocation; - private FloatBuffer textureCoords; private int previousWidth; private int previousStride; diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 04363c87a9..c41838fae2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Sep 01 11:39:15 BST 2016 +#Mon Oct 24 14:40:37 BST 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME 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 diff --git a/library/build.gradle b/library/build.gradle index 42e7cb3bb1..dd1d5c3c87 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -35,9 +35,11 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' } - debug { - testCoverageEnabled = true - } + // Re-enable test coverage when the following issue is fixed: + // https://code.google.com/p/android/issues/detail?id=226070 + // debug { + // testCoverageEnabled = true + // } } lintOptions { @@ -55,7 +57,7 @@ dependencies { androidTestCompile 'com.google.dexmaker:dexmaker:1.2' androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2' 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 -> diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java index cccc619a0e..71edba0612 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerTest.java @@ -27,9 +27,9 @@ import junit.framework.TestCase; */ public final class DefaultOggSeekerTest extends TestCase { - public void testSetupUnboundAudioLength() { + public void testSetupWithUnsetEndPositionFails() { try { - new DefaultOggSeeker(0, C.LENGTH_UNSET, new TestStreamReader()); + new DefaultOggSeeker(0, C.LENGTH_UNSET, new TestStreamReader(), 1, 1); fail(); } catch (IllegalArgumentException e) { // 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); FakeExtractorInput input = new FakeExtractorInput.Builder().setData(testFile.data).build(); 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(); while (true) { diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerUtilMethodsTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerUtilMethodsTest.java index 052f45b8f4..5431d35bdf 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerUtilMethodsTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeekerUtilMethodsTest.java @@ -29,7 +29,7 @@ import junit.framework.TestCase; public class DefaultOggSeekerUtilMethodsTest extends TestCase { private Random random = new Random(0); - + public void testSkipToNextPage() throws Exception { FakeExtractorInput extractorInput = TestData.createInput( TestUtil.joinByteArrays( @@ -75,7 +75,7 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase { private static void skipToNextPage(ExtractorInput extractorInput) throws IOException, InterruptedException { DefaultOggSeeker oggSeeker = new DefaultOggSeeker(0, extractorInput.getLength(), - new FlacReader()); + new FlacReader(), 1, 2); while (true) { try { oggSeeker.skipToNextPage(extractorInput); @@ -143,7 +143,7 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase { private void skipToPageOfGranule(ExtractorInput input, long granule, 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) { try { assertEquals(elapsedSamplesExpected, oggSeeker.skipToPageOfGranule(input, granule, -1)); @@ -193,7 +193,7 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase { private void assertReadGranuleOfLastPage(FakeExtractorInput input, int expected) 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) { try { assertEquals(expected, oggSeeker.readGranuleOfLastPage(input)); diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggTestFile.java b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggTestFile.java index 88f36d35c1..b1294c7a14 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggTestFile.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/OggTestFile.java @@ -33,12 +33,17 @@ import junit.framework.Assert; long lastGranule; int packetCount; 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.lastGranule = lastGranule; this.packetCount = packetCount; this.pageCount = pageCount; + this.firstPayloadPageSize = firstPayloadPageSize; + this.firstPayloadPageGranulePosition = firstPayloadPageGranulePosition; } public static OggTestFile generate(Random random, int pageCount) { @@ -47,6 +52,8 @@ import junit.framework.Assert; long granule = 0; int packetLength = -1; int packetCount = 0; + int firstPayloadPageSize = 0; + long firstPayloadPageGranulePosition = 0; for (int i = 0; i < pageCount; i++) { int headerType = 0x00; @@ -89,6 +96,10 @@ import junit.framework.Assert; byte[] payload = TestUtil.buildTestData(bodySize, random); fileData.add(payload); fileSize += payload.length; + if (i == 0) { + firstPayloadPageSize = header.length + bodySize; + firstPayloadPageGranulePosition = granule; + } } byte[] file = new byte[fileSize]; @@ -97,7 +108,8 @@ import junit.framework.Assert; System.arraycopy(data, 0, file, 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) { diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java index e19de76466..ebb547810b 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/AdtsReaderTest.java @@ -16,7 +16,7 @@ package com.google.android.exoplayer2.extractor.ts; import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.TrackIdGenerator; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.testutil.FakeExtractorOutput; import com.google.android.exoplayer2.testutil.FakeTrackOutput; import com.google.android.exoplayer2.testutil.TestUtil; @@ -52,7 +52,7 @@ public class AdtsReaderTest extends TestCase { public static final byte[] ADTS_CONTENT = TestUtil.createByteArray( 0x20, 0x00, 0x20, 0x00, 0x00, 0x80, 0x0e); - private static final byte TEST_DATA[] = TestUtil.joinByteArrays( + private static final byte[] TEST_DATA = TestUtil.joinByteArrays( ID3_DATA_1, ID3_DATA_2, ADTS_HEADER, @@ -73,7 +73,7 @@ public class AdtsReaderTest extends TestCase { id3Output = fakeExtractorOutput.track(1); adtsReader = new AdtsReader(true); TrackIdGenerator idGenerator = new TrackIdGenerator(0, 1); - adtsReader.init(fakeExtractorOutput, idGenerator); + adtsReader.createTracks(fakeExtractorOutput, idGenerator); data = new ParsableByteArray(TEST_DATA); firstFeed = true; } diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java index 1f08507599..a455a3b841 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/ts/TsExtractorTest.java @@ -22,7 +22,8 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.TimestampAdjuster; import com.google.android.exoplayer2.extractor.TrackOutput; -import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.EsInfo; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.testutil.FakeExtractorInput; import com.google.android.exoplayer2.testutil.FakeExtractorOutput; import com.google.android.exoplayer2.testutil.FakeTrackOutput; @@ -106,7 +107,7 @@ public final class TsExtractorTest extends InstrumentationTestCase { } } - private static final class CustomEsReader extends ElementaryStreamReader { + private static final class CustomEsReader implements ElementaryStreamReader { private final String language; private TrackOutput output; @@ -121,7 +122,7 @@ public final class TsExtractorTest extends InstrumentationTestCase { } @Override - public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { output = extractorOutput.track(idGenerator.getNextId()); output.format(Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0, language, null, 0)); @@ -146,22 +147,22 @@ public final class TsExtractorTest extends InstrumentationTestCase { } - private static final class CustomEsReaderFactory implements ElementaryStreamReader.Factory { + private static final class CustomEsReaderFactory implements TsPayloadReader.Factory { - private final ElementaryStreamReader.Factory defaultFactory; + private final TsPayloadReader.Factory defaultFactory; private CustomEsReader reader; public CustomEsReaderFactory() { - defaultFactory = new DefaultStreamReaderFactory(); + defaultFactory = new DefaultTsPayloadReaderFactory(); } @Override - public ElementaryStreamReader createStreamReader(int streamType, EsInfo esInfo) { + public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) { if (streamType == 3) { reader = new CustomEsReader(esInfo.language); - return reader; + return new PesReader(reader); } else { - return defaultFactory.createStreamReader(streamType, esInfo); + return defaultFactory.createPayloadReader(streamType, esInfo); } } diff --git a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java index 56b862073a..9d6c435635 100644 --- a/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer2/ExoPlayerImplInternal.java @@ -249,6 +249,7 @@ import java.io.IOException; // Handler.Callback implementation. + @SuppressWarnings("unchecked") @Override public boolean handleMessage(Message msg) { try { @@ -335,8 +336,7 @@ import java.io.IOException; } } - private void prepareInternal(MediaSource mediaSource, boolean resetPosition) - throws ExoPlaybackException { + private void prepareInternal(MediaSource mediaSource, boolean resetPosition) { resetInternal(); loadControl.onPrepared(); if (resetPosition) { @@ -884,8 +884,7 @@ import java.io.IOException; } } - private void attemptRestart(Timeline newTimeline, Timeline oldTimeline, - int oldPeriodIndex) throws ExoPlaybackException { + private void attemptRestart(Timeline newTimeline, Timeline oldTimeline, int oldPeriodIndex) { int newPeriodIndex = C.INDEX_UNSET; while (newPeriodIndex == C.INDEX_UNSET && oldPeriodIndex < oldTimeline.getPeriodCount() - 1) { @@ -1260,7 +1259,7 @@ import java.io.IOException; } 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++) { mayRetainStreamFlags[i] = !forceRecreateStreams && Util.areEqual(periodTrackSelections == null ? null : periodTrackSelections.get(i), diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java b/library/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java index 87f6546e1f..1eff48e28d 100644 --- a/library/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java +++ b/library/src/main/java/com/google/android/exoplayer2/audio/AudioTrack.java @@ -23,6 +23,7 @@ import android.os.ConditionVariable; import android.os.SystemClock; import android.util.Log; 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.MimeTypes; import com.google.android.exoplayer2.util.Util; @@ -981,6 +982,9 @@ public final class AudioTrack { case C.ENCODING_PCM_32BIT: resampledSize = size / 2; break; + case C.ENCODING_PCM_16BIT: + case C.ENCODING_INVALID: + case Format.NO_VALUE: default: // Never happens. throw new IllegalStateException(); @@ -1016,6 +1020,9 @@ public final class AudioTrack { resampledBuffer.put(buffer.get(i + 3)); } break; + case C.ENCODING_PCM_16BIT: + case C.ENCODING_INVALID: + case Format.NO_VALUE: default: // Never happens. throw new IllegalStateException(); diff --git a/library/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index 66dd010a6f..5862e7e218 100644 --- a/library/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -76,7 +76,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media * has obtained the keys necessary to decrypt encrypted regions of the media. */ public MediaCodecAudioRenderer(MediaCodecSelector mediaCodecSelector, - DrmSessionManager drmSessionManager, boolean playClearSamplesWithoutKeys) { + DrmSessionManager drmSessionManager, + boolean playClearSamplesWithoutKeys) { this(mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys, null, null); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java index 3d3e677881..a3fde6d455 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/Sniffer.java @@ -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 { long inputLength = input.getLength(); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java index c6e5d46b8e..87e5811a9a 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/DefaultOggSeeker.java @@ -59,13 +59,21 @@ import java.io.IOException; * @param startPosition Start position of the payload (inclusive). * @param endPosition End position of the payload (exclusive). * @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); this.streamReader = streamReader; this.startPosition = startPosition; 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 @@ -77,9 +85,9 @@ import java.io.IOException; positionBeforeSeekToEnd = input.getPosition(); state = STATE_READ_LAST_PAGE; // Seek to the end just before the last page of stream to get the duration. - long lastPagePosition = endPosition - OggPageHeader.MAX_PAGE_SIZE; - if (lastPagePosition > positionBeforeSeekToEnd) { - return lastPagePosition; + long lastPageSearchPosition = endPosition - OggPageHeader.MAX_PAGE_SIZE; + if (lastPageSearchPosition > positionBeforeSeekToEnd) { + return lastPageSearchPosition; } // Fall through. case STATE_READ_LAST_PAGE: diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java index aa3f8e2353..a8b44f8fe9 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/StreamReader.java @@ -144,7 +144,10 @@ import java.io.IOException; } else if (input.getLength() == C.LENGTH_UNSET) { oggSeeker = new UnseekableOggSeeker(); } 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; diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArray.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArray.java index c6c9efc0f7..ae52e80299 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArray.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArray.java @@ -26,7 +26,7 @@ import com.google.android.exoplayer2.util.Assertions; /* package */ final class VorbisBitArray { public final byte[] data; - private int limit; + private final int limit; private int byteOffset; private int bitOffset; diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java index ea9458a657..452d09e132 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java @@ -47,7 +47,6 @@ public final class RawCcExtractor implements Extractor { private final ParsableByteArray dataScratch; - private ExtractorOutput extractorOutput; private TrackOutput trackOutput; private int parserState; @@ -63,10 +62,9 @@ public final class RawCcExtractor implements Extractor { @Override public void init(ExtractorOutput output) { - this.extractorOutput = output; - extractorOutput.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); - trackOutput = extractorOutput.track(0); - extractorOutput.endTracks(); + output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); + trackOutput = output.track(0); + output.endTracks(); trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, null, null)); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java index 7fc8b429a8..dad8214efa 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java @@ -23,7 +23,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; -import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.TrackIdGenerator; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; @@ -119,7 +119,7 @@ public final class Ac3Extractor implements Extractor { @Override public void init(ExtractorOutput output) { reader = new Ac3Reader(); // TODO: Add support for embedded ID3. - reader.init(output, new TrackIdGenerator(0, 1)); + reader.createTracks(output, new TrackIdGenerator(0, 1)); output.endTracks(); output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java index a9d3319f87..52faa8c673 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Reader.java @@ -20,13 +20,14 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.audio.Ac3Util; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; /** * Parses a continuous (E-)AC-3 byte stream and extracts individual samples. */ -/* package */ final class Ac3Reader extends ElementaryStreamReader { +/* package */ final class Ac3Reader implements ElementaryStreamReader { private static final int STATE_FINDING_SYNC = 0; private static final int STATE_READING_HEADER = 1; @@ -82,7 +83,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; } @Override - public void init(ExtractorOutput extractorOutput, TrackIdGenerator generator) { + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) { output = extractorOutput.track(generator.getNextId()); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java index 7a9cbd4bb1..76bc4ce66e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java @@ -22,7 +22,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; -import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.TrackIdGenerator; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; @@ -128,7 +128,7 @@ public final class AdtsExtractor implements Extractor { @Override public void init(ExtractorOutput output) { reader = new AdtsReader(true); - reader.init(output, new TrackIdGenerator(0, 1)); + reader.createTracks(output, new TrackIdGenerator(0, 1)); output.endTracks(); output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java index d0474f7e44..47cb217fc7 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java @@ -22,6 +22,7 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.DummyTrackOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.CodecSpecificDataUtil; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableBitArray; @@ -32,7 +33,7 @@ import java.util.Collections; /** * Parses a continuous ADTS byte stream and extracts individual frames. */ -/* package */ final class AdtsReader extends ElementaryStreamReader { +/* package */ final class AdtsReader implements ElementaryStreamReader { private static final String TAG = "AdtsReader"; @@ -106,7 +107,7 @@ import java.util.Collections; } @Override - public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { output = extractorOutput.track(idGenerator.getNextId()); if (exposeId3) { id3Output = extractorOutput.track(idGenerator.getNextId()); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultStreamReaderFactory.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java similarity index 68% rename from library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultStreamReaderFactory.java rename to library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index 58a0e55f02..5aabc29a5d 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultStreamReaderFactory.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -16,14 +16,14 @@ package com.google.android.exoplayer2.extractor.ts; import android.support.annotation.IntDef; -import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.EsInfo; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** - * Default implementation for {@link ElementaryStreamReader.Factory}. + * Default implementation for {@link TsPayloadReader.Factory}. */ -public final class DefaultStreamReaderFactory implements ElementaryStreamReader.Factory { +public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Factory { /** * Flags controlling elementary stream readers behaviour. @@ -41,39 +41,39 @@ public final class DefaultStreamReaderFactory implements ElementaryStreamReader. @Flags private final int flags; - public DefaultStreamReaderFactory() { + public DefaultTsPayloadReaderFactory() { this(0); } - public DefaultStreamReaderFactory(@Flags int flags) { + public DefaultTsPayloadReaderFactory(@Flags int flags) { this.flags = flags; } @Override - public ElementaryStreamReader createStreamReader(int streamType, EsInfo esInfo) { + public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) { switch (streamType) { case TsExtractor.TS_STREAM_TYPE_MPA: case TsExtractor.TS_STREAM_TYPE_MPA_LSF: - return new MpegAudioReader(esInfo.language); + return new PesReader(new MpegAudioReader(esInfo.language)); case TsExtractor.TS_STREAM_TYPE_AAC: return (flags & FLAG_IGNORE_AAC_STREAM) != 0 ? null - : new AdtsReader(false, esInfo.language); + : new PesReader(new AdtsReader(false, esInfo.language)); case TsExtractor.TS_STREAM_TYPE_AC3: case TsExtractor.TS_STREAM_TYPE_E_AC3: - return new Ac3Reader(esInfo.language); + return new PesReader(new Ac3Reader(esInfo.language)); case TsExtractor.TS_STREAM_TYPE_DTS: case TsExtractor.TS_STREAM_TYPE_HDMV_DTS: - return new DtsReader(esInfo.language); + return new PesReader(new DtsReader(esInfo.language)); case TsExtractor.TS_STREAM_TYPE_H262: - return new H262Reader(); + return new PesReader(new H262Reader()); case TsExtractor.TS_STREAM_TYPE_H264: return (flags & FLAG_IGNORE_H264_STREAM) != 0 ? null - : new H264Reader((flags & FLAG_ALLOW_NON_IDR_KEYFRAMES) != 0, - (flags & FLAG_DETECT_ACCESS_UNITS) != 0); + : new PesReader(new H264Reader((flags & FLAG_ALLOW_NON_IDR_KEYFRAMES) != 0, + (flags & FLAG_DETECT_ACCESS_UNITS) != 0)); case TsExtractor.TS_STREAM_TYPE_H265: - return new H265Reader(); + return new PesReader(new H265Reader()); case TsExtractor.TS_STREAM_TYPE_ID3: - return new Id3Reader(); + return new PesReader(new Id3Reader()); default: return null; } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java index 42223ef285..9707685295 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DtsReader.java @@ -20,12 +20,13 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.audio.DtsUtil; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.ParsableByteArray; /** * Parses a continuous DTS byte stream and extracts individual samples. */ -/* package */ final class DtsReader extends ElementaryStreamReader { +/* package */ final class DtsReader implements ElementaryStreamReader { private static final int STATE_FINDING_SYNC = 0; private static final int STATE_READING_HEADER = 1; @@ -77,7 +78,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; } @Override - public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { output = extractorOutput.track(idGenerator.getNextId()); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java index e2efbebb43..57bcf31fc5 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/ElementaryStreamReader.java @@ -22,82 +22,21 @@ import com.google.android.exoplayer2.util.ParsableByteArray; /** * Extracts individual samples from an elementary media stream, preserving original order. */ -public abstract class ElementaryStreamReader { - - /** - * Factory of {@link ElementaryStreamReader} instances. - */ - public interface Factory { - - /** - * Returns an {@link ElementaryStreamReader} for a given PMT entry. May return null if the - * stream type is not supported or if the stream already has a reader assigned to it. - * - * @param streamType Stream type value as defined in the PMT entry or associated descriptors. - * @param esInfo Information associated to the elementary stream provided in the PMT. - * @return An {@link ElementaryStreamReader} for the elementary streams carried by the provided - * pid. {@code null} if the stream is not supported or if it should be ignored. - */ - ElementaryStreamReader createStreamReader(int streamType, EsInfo esInfo); - - } - - /** - * Holds descriptor information associated with an elementary stream. - */ - public static final class EsInfo { - - public final int streamType; - public String language; - public byte[] descriptorBytes; - - /** - * @param streamType The type of the stream as defined by the - * {@link TsExtractor}{@code .TS_STREAM_TYPE_*}. - * @param language The language of the stream, as defined by ISO/IEC 13818-1, section 2.6.18. - * @param descriptorBytes The descriptor bytes associated to the stream. - */ - public EsInfo(int streamType, String language, byte[] descriptorBytes) { - this.streamType = streamType; - this.language = language; - this.descriptorBytes = descriptorBytes; - } - - } - - /** - * Generates track ids for initializing {@link ElementaryStreamReader}s' {@link TrackOutput}s. - */ - public static final class TrackIdGenerator { - - private final int firstId; - private final int idIncrement; - private int generatedIdCount; - - public TrackIdGenerator(int firstId, int idIncrement) { - this.firstId = firstId; - this.idIncrement = idIncrement; - } - - public int getNextId() { - return firstId + idIncrement * generatedIdCount++; - } - - } +public interface ElementaryStreamReader { /** * Notifies the reader that a seek has occurred. */ - public abstract void seek(); + void seek(); /** * Initializes the reader by providing outputs and ids for the tracks. * * @param extractorOutput The {@link ExtractorOutput} that receives the extracted data. - * @param idGenerator A {@link TrackIdGenerator} that generates unique track ids for the + * @param idGenerator A {@link PesReader.TrackIdGenerator} that generates unique track ids for the * {@link TrackOutput}s. */ - public abstract void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator); + void createTracks(ExtractorOutput extractorOutput, PesReader.TrackIdGenerator idGenerator); /** * Called when a packet starts. @@ -105,18 +44,18 @@ public abstract class ElementaryStreamReader { * @param pesTimeUs The timestamp associated with the packet. * @param dataAlignmentIndicator The data alignment indicator associated with the packet. */ - public abstract void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator); + void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator); /** * Consumes (possibly partial) data from the current packet. * * @param data The data to consume. */ - public abstract void consume(ParsableByteArray data); + void consume(ParsableByteArray data); /** * Called when a packet ends. */ - public abstract void packetFinished(); + void packetFinished(); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java index fbfe7e1209..02ea6d7c4e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H262Reader.java @@ -20,6 +20,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.ParsableByteArray; @@ -29,7 +30,7 @@ import java.util.Collections; /** * Parses a continuous H262 byte stream and extracts individual frames. */ -/* package */ final class H262Reader extends ElementaryStreamReader { +/* package */ final class H262Reader implements ElementaryStreamReader { private static final int START_PICTURE = 0x00; private static final int START_SEQUENCE_HEADER = 0xB3; @@ -76,7 +77,7 @@ import java.util.Collections; } @Override - public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { output = extractorOutput.track(idGenerator.getNextId()); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java index 6fee9ea6d7..ed4682d9b9 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java @@ -20,6 +20,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.NalUnitUtil.SpsData; @@ -32,7 +33,7 @@ import java.util.List; /** * Parses a continuous H264 byte stream and extracts individual frames. */ -/* package */ final class H264Reader extends ElementaryStreamReader { +/* package */ final class H264Reader implements ElementaryStreamReader { private static final int NAL_UNIT_TYPE_SEI = 6; // Supplemental enhancement information private static final int NAL_UNIT_TYPE_SPS = 7; // Sequence parameter set @@ -86,7 +87,7 @@ import java.util.List; } @Override - public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { output = extractorOutput.track(idGenerator.getNextId()); sampleReader = new SampleReader(output, allowNonIdrKeyframes, detectAccessUnits); seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId())); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java index 57d7e77bb7..a78169a054 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java @@ -20,6 +20,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.ParsableByteArray; @@ -29,7 +30,7 @@ import java.util.Collections; /** * Parses a continuous H.265 byte stream and extracts individual frames. */ -/* package */ final class H265Reader extends ElementaryStreamReader { +/* package */ final class H265Reader implements ElementaryStreamReader { private static final String TAG = "H265Reader"; @@ -88,7 +89,7 @@ import java.util.Collections; } @Override - public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { output = extractorOutput.track(idGenerator.getNextId()); sampleReader = new SampleReader(output); seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId())); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java index 2c657d4aca..c19bc9d14e 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Id3Reader.java @@ -15,17 +15,21 @@ */ package com.google.android.exoplayer2.extractor.ts; +import android.util.Log; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; /** * Parses ID3 data and extracts individual text information frames. */ -/* package */ final class Id3Reader extends ElementaryStreamReader { +/* package */ final class Id3Reader implements ElementaryStreamReader { + + private static final String TAG = "Id3Reader"; private static final int ID3_HEADER_SIZE = 10; @@ -51,7 +55,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; } @Override - public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { output = extractorOutput.track(idGenerator.getNextId()); output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null, Format.NO_VALUE, null)); @@ -81,7 +85,14 @@ import com.google.android.exoplayer2.util.ParsableByteArray; headerBytesAvailable); if (sampleBytesRead + headerBytesAvailable == ID3_HEADER_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(); } } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java index d25d0703ae..c67e7ad0ab 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/MpegAudioReader.java @@ -20,12 +20,13 @@ import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.MpegAudioHeader; import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.ParsableByteArray; /** * Parses a continuous MPEG Audio byte stream and extracts individual frames. */ -/* package */ final class MpegAudioReader extends ElementaryStreamReader { +/* package */ final class MpegAudioReader implements ElementaryStreamReader { private static final int STATE_FINDING_HEADER = 0; private static final int STATE_READING_HEADER = 1; @@ -74,7 +75,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; } @Override - public void init(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { output = extractorOutput.track(idGenerator.getNextId()); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java new file mode 100644 index 0000000000..598394a870 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PesReader.java @@ -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); + } + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java index b615a3e8ee..6e80f4c49f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java @@ -24,7 +24,7 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.TimestampAdjuster; -import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.TrackIdGenerator; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; import java.io.IOException; @@ -202,7 +202,7 @@ public final class PsExtractor implements Extractor { } if (elementaryStreamReader != null) { TrackIdGenerator idGenerator = new TrackIdGenerator(streamId, MAX_STREAM_ID_PLUS_ONE); - elementaryStreamReader.init(output, idGenerator); + elementaryStreamReader.createTracks(output, idGenerator); payloadReader = new PesReader(elementaryStreamReader, timestampAdjuster); psPayloadReaders.put(streamId, payloadReader); } @@ -253,8 +253,7 @@ public final class PsExtractor implements Extractor { private int extendedHeaderLength; private long timeUs; - public PesReader(ElementaryStreamReader pesPayloadReader, - TimestampAdjuster timestampAdjuster) { + public PesReader(ElementaryStreamReader pesPayloadReader, TimestampAdjuster timestampAdjuster) { this.pesPayloadReader = pesPayloadReader; this.timestampAdjuster = timestampAdjuster; pesScratch = new ParsableBitArray(new byte[PES_SCRATCH_SIZE]); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionPayloadReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionPayloadReader.java new file mode 100644 index 0000000000..9be41af594 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionPayloadReader.java @@ -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); + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionReader.java new file mode 100644 index 0000000000..ccf00f8d19 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/SectionReader.java @@ -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); + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index bac362d711..b52b5cc047 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -15,7 +15,6 @@ */ package com.google.android.exoplayer2.extractor.ts; -import android.util.Log; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; @@ -28,8 +27,8 @@ import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.extractor.TimestampAdjuster; import com.google.android.exoplayer2.extractor.TrackOutput; -import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.EsInfo; -import com.google.android.exoplayer2.extractor.ts.ElementaryStreamReader.TrackIdGenerator; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableByteArray; @@ -85,14 +84,14 @@ public final class TsExtractor implements Extractor { private final ParsableByteArray tsPacketBuffer; private final ParsableBitArray tsScratch; private final SparseIntArray continuityCounters; - private final ElementaryStreamReader.Factory streamReaderFactory; + private final TsPayloadReader.Factory payloadReaderFactory; private final SparseArray tsPayloadReaders; // Indexed by pid private final SparseBooleanArray trackIds; // Accessed only by the loading thread. private ExtractorOutput output; private boolean tracksEnded; - private ElementaryStreamReader id3Reader; + private TsPayloadReader id3Reader; public TsExtractor() { this(new TimestampAdjuster(0)); @@ -102,19 +101,19 @@ public final class TsExtractor implements Extractor { * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. */ public TsExtractor(TimestampAdjuster timestampAdjuster) { - this(timestampAdjuster, new DefaultStreamReaderFactory(), false); + this(timestampAdjuster, new DefaultTsPayloadReaderFactory(), false); } /** * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. - * @param customReaderFactory Factory for injecting a custom set of elementary stream readers. + * @param payloadReaderFactory Factory for injecting a custom set of payload readers. * @param mapByType True if {@link TrackOutput}s should be mapped by their type, false to map them * by their PID. */ public TsExtractor(TimestampAdjuster timestampAdjuster, - ElementaryStreamReader.Factory customReaderFactory, boolean mapByType) { + TsPayloadReader.Factory payloadReaderFactory, boolean mapByType) { this.timestampAdjuster = timestampAdjuster; - this.streamReaderFactory = Assertions.checkNotNull(customReaderFactory); + this.payloadReaderFactory = Assertions.checkNotNull(payloadReaderFactory); this.mapByType = mapByType; tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE); tsScratch = new ParsableBitArray(new byte[3]); @@ -239,7 +238,7 @@ public final class TsExtractor implements Extractor { payloadReader.seek(); } tsPacketBuffer.setLimit(endOfPacket); - payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator, output); + payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator); Assertions.checkState(tsPacketBuffer.getPosition() <= endOfPacket); tsPacketBuffer.setLimit(limit); } @@ -254,95 +253,29 @@ public final class TsExtractor implements Extractor { private void resetPayloadReaders() { trackIds.clear(); tsPayloadReaders.clear(); - tsPayloadReaders.put(TS_PAT_PID, new PatReader()); + tsPayloadReaders.put(TS_PAT_PID, new SectionReader(new PatReader())); id3Reader = null; } - /** - * Parses TS packet payload data. - */ - private abstract static class TsPayloadReader { - - /** - * Notifies the reader that a seek has occurred. - *

- * Following a call to this method, the data passed to the next invocation of - * {@link #consume(ParsableByteArray, boolean, ExtractorOutput)} will not be a continuation of - * the data that was previously passed. Hence the reader should reset any internal state. - */ - public abstract void seek(); - - /** - * Consumes the payload of a TS packet. - * - * @param data The TS packet. The position will be set to the start of the payload. - * @param payloadUnitStartIndicator Whether payloadUnitStartIndicator was set on the TS packet. - * @param output The output to which parsed data should be written. - */ - public abstract void consume(ParsableByteArray data, boolean payloadUnitStartIndicator, - ExtractorOutput output); - - } - /** * Parses Program Association Table data. */ - private class PatReader extends TsPayloadReader { + private class PatReader implements SectionPayloadReader { - private final ParsableByteArray sectionData; private final ParsableBitArray patScratch; - private int sectionLength; - private int sectionBytesRead; - private int crc; - public PatReader() { - sectionData = new ParsableByteArray(); patScratch = new ParsableBitArray(new byte[4]); } @Override - public void seek() { - // Do nothing. - } - - @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; - } - + public void consume(ParsableByteArray sectionData) { + // table_id(8), section_syntax_indicator(1), '0'(1), reserved(2), section_length(12), // transport_stream_id (16), reserved (2), version_number (5), current_next_indicator (1), // 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++) { sectionData.readBytes(patScratch, 4); int programNumber = patScratch.readBits(16); @@ -351,7 +284,7 @@ public final class TsExtractor implements Extractor { patScratch.skipBits(13); // network_PID (13) } else { 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. */ - 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_ISO639_LANG = 0x0A; @@ -370,60 +303,20 @@ public final class TsExtractor implements Extractor { private static final int TS_PMT_DESC_DTS = 0x7B; private final ParsableBitArray pmtScratch; - private final ParsableByteArray sectionData; private final int pid; - private int sectionLength; - private int sectionBytesRead; - private int crc; - public PmtReader(int pid) { pmtScratch = new ParsableBitArray(new byte[5]); - sectionData = new ParsableByteArray(); this.pid = pid; } @Override - public void seek() { - // Do nothing. - } - - @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; - } - + public void consume(ParsableByteArray sectionData) { + // table_id(8), section_syntax_indicator(1), '0'(1), reserved(2), section_length(12), // program_number (16), reserved (2), version_number (5), current_next_indicator (1), // section_number (8), last_section_number (8), reserved (3), PCR_PID (13) // Skip the rest of the PMT header. - sectionData.skipBytes(7); + sectionData.skipBytes(10); // Read program_info_length. 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 // appears intermittently during playback. See [Internal: b/20261500]. EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, new byte[0]); - id3Reader = streamReaderFactory.createStreamReader(TS_STREAM_TYPE_ID3, dummyEsInfo); - id3Reader.init(output, new TrackIdGenerator(TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE)); + id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo); + id3Reader.init(timestampAdjuster, output, + new TrackIdGenerator(TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE)); } - int remainingEntriesLength = sectionLength - 9 /* Length of fields before descriptors */ - - programInfoLength - 4 /* CRC length */; + int remainingEntriesLength = sectionData.bytesLeft(); while (remainingEntriesLength > 0) { sectionData.readBytes(pmtScratch, 5); int streamType = pmtScratch.readBits(8); @@ -462,16 +355,18 @@ public final class TsExtractor implements Extractor { } trackIds.put(trackId, true); - ElementaryStreamReader pesPayloadReader; + TsPayloadReader reader; if (mapByType && streamType == TS_STREAM_TYPE_ID3) { - pesPayloadReader = id3Reader; + reader = id3Reader; } else { - pesPayloadReader = streamReaderFactory.createStreamReader(streamType, esInfo); - pesPayloadReader.init(output, new TrackIdGenerator(trackId, MAX_PID_PLUS_ONE)); + reader = payloadReaderFactory.createPayloadReader(streamType, esInfo); + if (reader != null) { + reader.init(timestampAdjuster, output, new TrackIdGenerator(trackId, MAX_PID_PLUS_ONE)); + } } - if (pesPayloadReader != null) { - tsPayloadReaders.put(elementaryPid, new PesReader(pesPayloadReader, timestampAdjuster)); + if (reader != null) { + tsPayloadReaders.put(elementaryPid, reader); } } if (mapByType) { @@ -527,213 +422,10 @@ public final class TsExtractor implements Extractor { } data.setPosition(descriptorsEndPosition); 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); - } - } - - } } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java new file mode 100644 index 0000000000..28e9fb9095 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsPayloadReader.java @@ -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. + *

+ * 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); + +} diff --git a/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java b/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java index 2f9524b7f0..4836953f8f 100644 --- a/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java +++ b/library/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecUtil.java @@ -23,6 +23,7 @@ import android.media.MediaCodecList; import android.text.TextUtils; import android.util.Log; import android.util.Pair; +import android.util.SparseIntArray; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; import java.util.ArrayList; @@ -63,8 +64,8 @@ public final class MediaCodecUtil { // Codecs to constant mappings. // AVC. - private static final Map AVC_PROFILE_NUMBER_TO_CONST; - private static final Map AVC_LEVEL_NUMBER_TO_CONST; + private static final SparseIntArray AVC_PROFILE_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_AVC2 = "avc2"; // HEVC. @@ -222,6 +223,7 @@ public final class MediaCodecUtil { && ("CIPAACDecoder".equals(name) || "CIPMP3Decoder".equals(name) || "CIPVorbisDecoder".equals(name) + || "CIPAMRNBDecoder".equals(name) || "AACDecoder".equals(name) || "MP3Decoder".equals(name))) { return false; @@ -364,8 +366,8 @@ public final class MediaCodecUtil { Log.w(TAG, "Ignoring malformed AVC codec string: " + codec); return null; } - Integer profileInteger = null; - Integer levelInteger = null; + Integer profileInteger; + Integer levelInteger; try { if (codecsParts[1].length() == 6) { // Format: avc1.xxccyy, where xx is profile and yy level, both hexadecimal. @@ -555,13 +557,13 @@ public final class MediaCodecUtil { } 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(77, CodecProfileLevel.AVCProfileMain); AVC_PROFILE_NUMBER_TO_CONST.put(88, CodecProfileLevel.AVCProfileExtended); 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); // TODO: Find int for CodecProfileLevel.AVCLevel1b. AVC_LEVEL_NUMBER_TO_CONST.put(11, CodecProfileLevel.AVCLevel11); diff --git a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java index cfab4b14aa..10c56e5576 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/MergingMediaPeriod.java @@ -149,9 +149,9 @@ import java.util.IdentityHashMap; } // It must be possible to seek enabled periods to the new position, if there is one. if (positionUs != C.TIME_UNSET) { - for (int i = 0; i < enabledPeriods.length; i++) { - if (enabledPeriods[i] != periods[0] - && enabledPeriods[i].seekToUs(positionUs) != positionUs) { + for (MediaPeriod enabledPeriod : enabledPeriods) { + if (enabledPeriod != periods[0] + && enabledPeriod.seekToUs(positionUs) != positionUs) { throw new IllegalStateException("Children seeked to different positions"); } } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java index a23ab3bae7..7ef16f361d 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java @@ -26,7 +26,7 @@ import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor; import com.google.android.exoplayer2.extractor.ts.Ac3Extractor; import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; -import com.google.android.exoplayer2.extractor.ts.DefaultStreamReaderFactory; +import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory; import com.google.android.exoplayer2.extractor.ts.TsExtractor; import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.TrackGroup; @@ -384,7 +384,7 @@ import java.util.Locale; timestampAdjuster = timestampAdjusterProvider.getAdjuster( segment.discontinuitySequenceNumber, startTimeUs); // This flag ensures the change of pid between streams does not affect the sample queues. - @DefaultStreamReaderFactory.Flags + @DefaultTsPayloadReaderFactory.Flags int esReaderFactoryFlags = 0; String codecs = variants[newVariantIndex].format.codecs; if (!TextUtils.isEmpty(codecs)) { @@ -392,14 +392,14 @@ import java.util.Locale; // exist. If we know from the codec attribute that they don't exist, then we can // explicitly ignore them even if they're declared. if (!MimeTypes.AUDIO_AAC.equals(MimeTypes.getAudioMediaMimeType(codecs))) { - esReaderFactoryFlags |= DefaultStreamReaderFactory.FLAG_IGNORE_AAC_STREAM; + esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_AAC_STREAM; } if (!MimeTypes.VIDEO_H264.equals(MimeTypes.getVideoMediaMimeType(codecs))) { - esReaderFactoryFlags |= DefaultStreamReaderFactory.FLAG_IGNORE_H264_STREAM; + esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_IGNORE_H264_STREAM; } } extractor = new TsExtractor(timestampAdjuster, - new DefaultStreamReaderFactory(esReaderFactoryFlags), true); + new DefaultTsPayloadReaderFactory(esReaderFactoryFlags), true); } } else { // MPEG-2 TS segments, and we need to continue using the same extractor. diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java index f4c8177f21..598fa9b281 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaPeriod.java @@ -69,7 +69,6 @@ import java.util.List; private int pendingPrepareCount; private HlsPlaylist playlist; private boolean seenFirstTrackSelection; - private long durationUs; private boolean isLive; private TrackGroupArray trackGroups; private HlsSampleStreamWrapper[] sampleStreamWrappers; @@ -280,7 +279,7 @@ import java.util.List; } // 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(); int totalTrackGroupCount = 0; diff --git a/library/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleOutputBuffer.java b/library/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleOutputBuffer.java index 1a69cd7ebd..b2c25631f4 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleOutputBuffer.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/SimpleSubtitleOutputBuffer.java @@ -20,7 +20,7 @@ package com.google.android.exoplayer2.text; */ /* package */ final class SimpleSubtitleOutputBuffer extends SubtitleOutputBuffer { - private SimpleSubtitleDecoder owner; + private final SimpleSubtitleDecoder owner; /** * @param owner The decoder that owns this buffer. diff --git a/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java b/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java index 2715b0cbe0..5ff68c2781 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java @@ -28,6 +28,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray; public final class Cea608Decoder extends CeaDecoder { 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 PAYLOAD_TYPE_CC = 4; @@ -223,7 +224,8 @@ public final class Cea608Decoder extends CeaDecoder { byte ccData2 = (byte) (ccData.readUnsignedByte() & 0x7F); // 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 continue; } diff --git a/library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java b/library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java index bb89b05603..21333081c6 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/ttml/TtmlRenderUtil.java @@ -92,21 +92,22 @@ import java.util.Map; builder.setSpan(new AlignmentSpan.Standard(style.getTextAlign()), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } - if (style.getFontSizeUnit() != TtmlStyle.UNSPECIFIED) { - switch (style.getFontSizeUnit()) { - case TtmlStyle.FONT_SIZE_UNIT_PIXEL: - builder.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - case TtmlStyle.FONT_SIZE_UNIT_EM: - builder.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - case TtmlStyle.FONT_SIZE_UNIT_PERCENT: - builder.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - } + switch (style.getFontSizeUnit()) { + case TtmlStyle.FONT_SIZE_UNIT_PIXEL: + builder.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case TtmlStyle.FONT_SIZE_UNIT_EM: + builder.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case TtmlStyle.FONT_SIZE_UNIT_PERCENT: + builder.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case TtmlStyle.UNSPECIFIED: + // Do nothing. + break; } } diff --git a/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java b/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java index b7345e0b5f..c63004e1cd 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/webvtt/WebvttCueParser.java @@ -413,21 +413,22 @@ import java.util.regex.Pattern; spannedText.setSpan(new AlignmentSpan.Standard(style.getTextAlign()), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } - if (style.getFontSizeUnit() != WebvttCssStyle.UNSPECIFIED) { - switch (style.getFontSizeUnit()) { - case WebvttCssStyle.FONT_SIZE_UNIT_PIXEL: - spannedText.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - case WebvttCssStyle.FONT_SIZE_UNIT_EM: - spannedText.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - case WebvttCssStyle.FONT_SIZE_UNIT_PERCENT: - spannedText.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - break; - } + switch (style.getFontSizeUnit()) { + case WebvttCssStyle.FONT_SIZE_UNIT_PIXEL: + spannedText.setSpan(new AbsoluteSizeSpan((int) style.getFontSize(), true), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case WebvttCssStyle.FONT_SIZE_UNIT_EM: + spannedText.setSpan(new RelativeSizeSpan(style.getFontSize()), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case WebvttCssStyle.FONT_SIZE_UNIT_PERCENT: + spannedText.setSpan(new RelativeSizeSpan(style.getFontSize() / 100), start, end, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + break; + case WebvttCssStyle.UNSPECIFIED: + // Do nothing. + break; } } diff --git a/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java index 3826ee4668..3307fc3baa 100644 --- a/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java +++ b/library/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java @@ -70,8 +70,8 @@ public abstract class MappingTrackSelector extends TrackSelector buildDrmSessionManager( + final String userAgent) { + StreamingDrmSessionManager drmSessionManager = null; if (isWidevineEncrypted) { try { // Force L3 if secure decoder is not available. diff --git a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java index b8ac1eb76c..6d834873ea 100644 --- a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java +++ b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/ExoHostedTest.java @@ -30,6 +30,7 @@ import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.audio.AudioTrack; import com.google.android.exoplayer2.decoder.DecoderCounters; 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.source.MediaSource; import com.google.android.exoplayer2.trackselection.AdaptiveVideoTrackSelection; @@ -130,7 +131,7 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(); trackSelector = buildTrackSelector(host, bandwidthMeter); String userAgent = "ExoPlayerPlaybackTests"; - DrmSessionManager drmSessionManager = buildDrmSessionManager(userAgent); + DrmSessionManager drmSessionManager = buildDrmSessionManager(userAgent); player = buildExoPlayer(host, surface, trackSelector, drmSessionManager); player.prepare(buildSource(host, Util.getUserAgent(host, userAgent), bandwidthMeter)); player.addListener(this); @@ -296,7 +297,7 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen // Internal logic - protected DrmSessionManager buildDrmSessionManager(String userAgent) { + protected DrmSessionManager buildDrmSessionManager(String userAgent) { // Do nothing. Interested subclasses may override. return null; } @@ -309,7 +310,8 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen @SuppressWarnings("unused") protected SimpleExoPlayer buildExoPlayer(HostActivity host, Surface surface, - MappingTrackSelector trackSelector, DrmSessionManager drmSessionManager) { + MappingTrackSelector trackSelector, + DrmSessionManager drmSessionManager) { SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(host, trackSelector, new DefaultLoadControl(), drmSessionManager, false, 0); player.setVideoSurface(surface); diff --git a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/HostActivity.java b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/HostActivity.java index 2a890b7c7f..9c2ced3a8a 100644 --- a/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/HostActivity.java +++ b/playbacktests/src/main/java/com/google/android/exoplayer2/playbacktests/util/HostActivity.java @@ -232,7 +232,7 @@ public final class HostActivity extends Activity implements SurfaceHolder.Callba } @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; }