Merge pull request #3083 from google/dev-v2-r2.4.4

r2.4.4
This commit is contained in:
ojw28 2017-07-19 20:05:45 +01:00 committed by GitHub
commit cbffc14fa1
22 changed files with 364 additions and 347 deletions

View file

@ -1,5 +1,16 @@
# Release notes #
### r2.4.4 ###
* HLS/MPEG-TS: Some initial optimizations of MPEG-TS extractor performance
([#3040](https://github.com/google/ExoPlayer/issues/3040)).
* HLS: Fix propagation of format identifier for CEA-608
([#3033](https://github.com/google/ExoPlayer/issues/3033)).
* HLS: Detect playlist stuck and reset conditions
([#2872](https://github.com/google/ExoPlayer/issues/2872)).
* Video: Fix video dimension reporting on some devices
([#3007](https://github.com/google/ExoPlayer/issues/3007)).
### r2.4.3 ###
* Audio: Workaround custom audio decoders misreporting their maximum supported

View file

@ -48,7 +48,7 @@ allprojects {
releaseRepoName = getBintrayRepo()
releaseUserOrg = 'google'
releaseGroupId = 'com.google.android.exoplayer'
releaseVersion = 'r2.4.3'
releaseVersion = 'r2.4.4'
releaseWebsite = 'https://github.com/google/ExoPlayer'
}
if (it.hasProperty('externalBuildDir')) {

View file

@ -16,8 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.demo"
android:versionCode="2403"
android:versionName="2.4.3">
android:versionCode="2404"
android:versionName="2.4.4">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

View file

@ -136,7 +136,6 @@ import java.util.Arrays;
root.addView(defaultView);
// Per-track views.
boolean haveSupportedTracks = false;
boolean haveAdaptiveTracks = false;
trackViews = new CheckedTextView[trackGroups.length][];
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
@ -159,7 +158,6 @@ import java.util.Arrays;
trackView.setFocusable(true);
trackView.setTag(Pair.create(groupIndex, trackIndex));
trackView.setOnClickListener(this);
haveSupportedTracks = true;
} else {
trackView.setFocusable(false);
trackView.setEnabled(false);
@ -169,10 +167,7 @@ import java.util.Arrays;
}
}
if (!haveSupportedTracks) {
// Indicate that the default selection will be nothing.
defaultView.setText(R.string.selection_default_none);
} else if (haveAdaptiveTracks) {
if (haveAdaptiveTracks) {
// View for using random adaptation.
enableRandomAdaptationView = (CheckedTextView) inflater.inflate(
android.R.layout.simple_list_item_multiple_choice, root, false);

View file

@ -30,8 +30,6 @@
<string name="selection_default">Default</string>
<string name="selection_default_none">Default (none)</string>
<string name="unexpected_intent_action">Unexpected intent action: <xliff:g id="action">%1$s</xliff:g></string>
<string name="enable_random_adaptation">Enable random adaptation</string>

View file

@ -25,36 +25,26 @@ public final class VorbisBitArrayTest extends TestCase {
public void testReadBit() {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0x5c, 0x50));
assertFalse(bitArray.readBit());
assertFalse(bitArray.readBit());
assertTrue(bitArray.readBit());
assertTrue(bitArray.readBit());
assertTrue(bitArray.readBit());
assertFalse(bitArray.readBit());
assertTrue(bitArray.readBit());
assertFalse(bitArray.readBit());
assertFalse(bitArray.readBit());
assertFalse(bitArray.readBit());
assertFalse(bitArray.readBit());
assertFalse(bitArray.readBit());
assertTrue(bitArray.readBit());
assertFalse(bitArray.readBit());
assertTrue(bitArray.readBit());
assertFalse(bitArray.readBit());
try {
assertFalse(bitArray.readBit());
fail();
} catch (IllegalStateException e) {/* ignored */}
}
public void testSkipBits() {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
bitArray.skipBits(10);
assertEquals(10, bitArray.getPosition());
assertTrue(bitArray.readBit());
@ -64,27 +54,10 @@ public final class VorbisBitArrayTest extends TestCase {
assertEquals(14, bitArray.getPosition());
assertFalse(bitArray.readBit());
assertFalse(bitArray.readBit());
try {
bitArray.readBit();
fail();
} catch (IllegalStateException e) {
// ignored
}
}
public void testSkipBitsThrowsErrorIfEOB() {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
try {
bitArray.skipBits(17);
fail();
} catch (IllegalStateException e) {/* ignored */}
}
public void testGetPosition() throws Exception {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
assertEquals(0, bitArray.getPosition());
bitArray.readBit();
assertEquals(1, bitArray.getPosition());
@ -96,35 +69,11 @@ public final class VorbisBitArrayTest extends TestCase {
public void testSetPosition() throws Exception {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
assertEquals(0, bitArray.getPosition());
bitArray.setPosition(4);
assertEquals(4, bitArray.getPosition());
bitArray.setPosition(15);
assertFalse(bitArray.readBit());
try {
bitArray.readBit();
fail();
} catch (IllegalStateException e) {/* ignored */}
}
public void testSetPositionIllegalPositions() throws Exception {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
try {
bitArray.setPosition(16);
fail();
} catch (IllegalArgumentException e) {
assertEquals(0, bitArray.getPosition());
}
try {
bitArray.setPosition(-1);
fail();
} catch (IllegalArgumentException e) {
assertEquals(0, bitArray.getPosition());
}
}
public void testReadInt32() {
@ -136,13 +85,11 @@ public final class VorbisBitArrayTest extends TestCase {
public void testReadBits() throws Exception {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0x03, 0x22));
assertEquals(3, bitArray.readBits(2));
bitArray.skipBits(6);
assertEquals(2, bitArray.readBits(2));
bitArray.skipBits(2);
assertEquals(2, bitArray.readBits(2));
bitArray.reset();
assertEquals(0x2203, bitArray.readBits(16));
}
@ -156,7 +103,6 @@ public final class VorbisBitArrayTest extends TestCase {
public void testReadBitsBeyondByteBoundaries() throws Exception {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xFF, 0x0F, 0xFF, 0x0F));
assertEquals(0x0FFF0FFF, bitArray.readBits(32));
bitArray.reset();
@ -188,83 +134,6 @@ public final class VorbisBitArrayTest extends TestCase {
assertEquals(0, bitArray.getPosition());
bitArray.readBit();
assertEquals(1, bitArray.getPosition());
try {
bitArray.readBits(24);
fail();
} catch (IllegalStateException e) {
assertEquals(1, bitArray.getPosition());
}
}
public void testLimit() {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xc0, 0x02), 1);
try {
bitArray.skipBits(9);
fail();
} catch (IllegalStateException e) {
assertEquals(0, bitArray.getPosition());
}
try {
bitArray.readBits(9);
fail();
} catch (IllegalStateException e) {
assertEquals(0, bitArray.getPosition());
}
int byteValue = bitArray.readBits(8);
assertEquals(0xc0, byteValue);
assertEquals(8, bitArray.getPosition());
try {
bitArray.readBit();
fail();
} catch (IllegalStateException e) {
assertEquals(8, bitArray.getPosition());
}
}
public void testBitsLeft() {
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xc0, 0x02));
assertEquals(16, bitArray.bitsLeft());
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
bitArray.skipBits(1);
assertEquals(15, bitArray.bitsLeft());
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
bitArray.skipBits(3);
assertEquals(12, bitArray.bitsLeft());
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
bitArray.setPosition(6);
assertEquals(10, bitArray.bitsLeft());
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
bitArray.skipBits(1);
assertEquals(9, bitArray.bitsLeft());
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
bitArray.skipBits(1);
assertEquals(8, bitArray.bitsLeft());
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
bitArray.readBits(4);
assertEquals(4, bitArray.bitsLeft());
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
bitArray.readBits(4);
assertEquals(0, bitArray.bitsLeft());
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
try {
bitArray.readBit();
fail();
} catch (IllegalStateException e) {
assertEquals(0, bitArray.bitsLeft());
}
}
}

View file

@ -0,0 +1,139 @@
/*
* Copyright (C) 2017 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.util;
import android.test.MoreAsserts;
import junit.framework.TestCase;
/**
* Tests for {@link ParsableBitArray}.
*/
public final class ParsableBitArrayTest extends TestCase {
private static final byte[] TEST_DATA = new byte[] {0x3C, (byte) 0xD2, (byte) 0x5F, (byte) 0x01,
(byte) 0xFF, (byte) 0x14, (byte) 0x60, (byte) 0x99};
public void testReadAllBytes() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
byte[] bytesRead = new byte[TEST_DATA.length];
testArray.readBytes(bytesRead, 0, TEST_DATA.length);
MoreAsserts.assertEquals(TEST_DATA, bytesRead);
assertEquals(TEST_DATA.length * 8, testArray.getPosition());
assertEquals(TEST_DATA.length, testArray.getBytePosition());
}
public void testReadBit() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
assertReadBitsToEnd(0, testArray);
}
public void testReadBits() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
assertEquals(getTestDataBits(0, 5), testArray.readBits(5));
assertEquals(getTestDataBits(5, 3), testArray.readBits(3));
assertEquals(getTestDataBits(8, 16), testArray.readBits(16));
assertEquals(getTestDataBits(24, 3), testArray.readBits(3));
assertEquals(getTestDataBits(27, 18), testArray.readBits(18));
assertEquals(getTestDataBits(45, 5), testArray.readBits(5));
assertEquals(getTestDataBits(50, 14), testArray.readBits(14));
}
public void testRead32BitsByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
assertEquals(getTestDataBits(0, 32), testArray.readBits(32));
assertEquals(getTestDataBits(32, 32), testArray.readBits(32));
}
public void testRead32BitsNonByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
assertEquals(getTestDataBits(0, 5), testArray.readBits(5));
assertEquals(getTestDataBits(5, 32), testArray.readBits(32));
}
public void testSkipBytes() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
testArray.skipBytes(2);
assertReadBitsToEnd(16, testArray);
}
public void testSkipBitsByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
testArray.skipBits(16);
assertReadBitsToEnd(16, testArray);
}
public void testSkipBitsNonByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
testArray.skipBits(5);
assertReadBitsToEnd(5, testArray);
}
public void testSetPositionByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
testArray.setPosition(16);
assertReadBitsToEnd(16, testArray);
}
public void testSetPositionNonByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
testArray.setPosition(5);
assertReadBitsToEnd(5, testArray);
}
public void testByteAlignFromNonByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
testArray.setPosition(11);
testArray.byteAlign();
assertEquals(2, testArray.getBytePosition());
assertEquals(16, testArray.getPosition());
assertReadBitsToEnd(16, testArray);
}
public void testByteAlignFromByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
testArray.setPosition(16);
testArray.byteAlign(); // Should be a no-op.
assertEquals(2, testArray.getBytePosition());
assertEquals(16, testArray.getPosition());
assertReadBitsToEnd(16, testArray);
}
private static void assertReadBitsToEnd(int expectedStartPosition, ParsableBitArray testArray) {
int position = testArray.getPosition();
assertEquals(expectedStartPosition, position);
for (int i = position; i < TEST_DATA.length * 8; i++) {
assertEquals(getTestDataBit(i), testArray.readBit());
assertEquals(i + 1, testArray.getPosition());
}
}
private static int getTestDataBits(int bitPosition, int length) {
int result = 0;
for (int i = 0; i < length; i++) {
result = result << 1;
if (getTestDataBit(bitPosition++)) {
result |= 0x1;
}
}
return result;
}
private static boolean getTestDataBit(int bitPosition) {
return (TEST_DATA[bitPosition / 8] & (0x80 >>> (bitPosition % 8))) != 0;
}
}

View file

@ -95,7 +95,7 @@ public final class ParsableNalUnitBitArrayTest extends TestCase {
ParsableNalUnitBitArray array =
new ParsableNalUnitBitArray(createByteArray(0, 0, 3, 128, 0), 0, 5);
assertFalse(array.canReadExpGolombCodedNum());
array.skipBits(1);
array.skipBit();
assertTrue(array.canReadExpGolombCodedNum());
assertEquals(32767, array.readUnsignedExpGolombCodedInt());
assertFalse(array.canReadBits(1));

View file

@ -24,13 +24,13 @@ public interface ExoPlayerLibraryInfo {
* The version of the library expressed as a string, for example "1.2.3".
*/
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
String VERSION = "2.4.3";
String VERSION = "2.4.4";
/**
* The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}.
*/
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
String VERSION_SLASHY = "ExoPlayerLib/2.4.3";
String VERSION_SLASHY = "ExoPlayerLib/2.4.4";
/**
* The version of the library expressed as an integer, for example 1002003.
@ -40,7 +40,7 @@ public interface ExoPlayerLibraryInfo {
* integer version 123045006 (123-045-006).
*/
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
int VERSION_INT = 2004003;
int VERSION_INT = 2004004;
/**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}

View file

@ -25,8 +25,9 @@ import com.google.android.exoplayer2.util.Assertions;
*/
/* package */ final class VorbisBitArray {
public final byte[] data;
private final int limit;
private final byte[] data;
private final int byteLimit;
private int byteOffset;
private int bitOffset;
@ -36,18 +37,8 @@ import com.google.android.exoplayer2.util.Assertions;
* @param data the array to wrap.
*/
public VorbisBitArray(byte[] data) {
this(data, data.length);
}
/**
* Creates a new instance that wraps an existing array.
*
* @param data the array to wrap.
* @param limit the limit in bytes.
*/
public VorbisBitArray(byte[] data, int limit) {
this.data = data;
this.limit = limit * 8;
byteLimit = data.length;
}
/**
@ -64,7 +55,9 @@ import com.google.android.exoplayer2.util.Assertions;
* @return {@code true} if the bit is set, {@code false} otherwise.
*/
public boolean readBit() {
return readBits(1) == 1;
boolean returnValue = (((data[byteOffset] & 0xFF) >> bitOffset) & 0x01) == 1;
skipBits(1);
return returnValue;
}
/**
@ -74,53 +67,32 @@ import com.google.android.exoplayer2.util.Assertions;
* @return An integer whose bottom {@code numBits} bits hold the read data.
*/
public int readBits(int numBits) {
Assertions.checkState(getPosition() + numBits <= limit);
if (numBits == 0) {
return 0;
int tempByteOffset = byteOffset;
int bitsRead = Math.min(numBits, 8 - bitOffset);
int returnValue = ((data[tempByteOffset++] & 0xFF) >> bitOffset) & (0xFF >> (8 - bitsRead));
while (bitsRead < numBits) {
returnValue |= (data[tempByteOffset++] & 0xFF) << bitsRead;
bitsRead += 8;
}
int result = 0;
int bitCount = 0;
if (bitOffset != 0) {
bitCount = Math.min(numBits, 8 - bitOffset);
int mask = 0xFF >>> (8 - bitCount);
result = (data[byteOffset] >>> bitOffset) & mask;
bitOffset += bitCount;
if (bitOffset == 8) {
byteOffset++;
bitOffset = 0;
}
}
if (numBits - bitCount > 7) {
int numBytes = (numBits - bitCount) / 8;
for (int i = 0; i < numBytes; i++) {
result |= (data[byteOffset++] & 0xFFL) << bitCount;
bitCount += 8;
}
}
if (numBits > bitCount) {
int bitsOnNextByte = numBits - bitCount;
int mask = 0xFF >>> (8 - bitsOnNextByte);
result |= (data[byteOffset] & mask) << bitCount;
bitOffset += bitsOnNextByte;
}
return result;
returnValue &= 0xFFFFFFFF >>> (32 - numBits);
skipBits(numBits);
return returnValue;
}
/**
* Skips {@code numberOfBits} bits.
*
* @param numberOfBits The number of bits to skip.
* @param numBits The number of bits to skip.
*/
public void skipBits(int numberOfBits) {
Assertions.checkState(getPosition() + numberOfBits <= limit);
byteOffset += numberOfBits / 8;
bitOffset += numberOfBits % 8;
public void skipBits(int numBits) {
int numBytes = numBits / 8;
byteOffset += numBytes;
bitOffset += numBits - (numBytes * 8);
if (bitOffset > 7) {
byteOffset++;
bitOffset -= 8;
}
assertValidOffset();
}
/**
@ -136,23 +108,22 @@ import com.google.android.exoplayer2.util.Assertions;
* @param position The new reading position in bits.
*/
public void setPosition(int position) {
Assertions.checkArgument(position < limit && position >= 0);
byteOffset = position / 8;
bitOffset = position - (byteOffset * 8);
assertValidOffset();
}
/**
* Returns the number of remaining bits.
*/
public int bitsLeft() {
return limit - getPosition();
return (byteLimit - byteOffset) * 8 - bitOffset;
}
/**
* Returns the limit in bits.
**/
public int limit() {
return limit;
private void assertValidOffset() {
// It is fine for position to be at the end of the array, but no further.
Assertions.checkState(byteOffset >= 0
&& (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0)));
}
}

View file

@ -316,7 +316,7 @@ public final class H264Reader implements ElementaryStreamReader {
if (!bitArray.canReadBits(8)) {
return;
}
bitArray.skipBits(1); // forbidden_zero_bit
bitArray.skipBit(); // forbidden_zero_bit
int nalRefIdc = bitArray.readBits(2);
bitArray.skipBits(5); // nal_unit_type

View file

@ -225,7 +225,7 @@ public final class H265Reader implements ElementaryStreamReader {
ParsableNalUnitBitArray bitArray = new ParsableNalUnitBitArray(sps.nalData, 0, sps.nalLength);
bitArray.skipBits(40 + 4); // NAL header, sps_video_parameter_set_id
int maxSubLayersMinus1 = bitArray.readBits(3);
bitArray.skipBits(1); // sps_temporal_id_nesting_flag
bitArray.skipBit(); // sps_temporal_id_nesting_flag
// profile_tier_level(1, sps_max_sub_layers_minus1)
bitArray.skipBits(88); // if (profilePresentFlag) {...}
@ -247,7 +247,7 @@ public final class H265Reader implements ElementaryStreamReader {
bitArray.readUnsignedExpGolombCodedInt(); // sps_seq_parameter_set_id
int chromaFormatIdc = bitArray.readUnsignedExpGolombCodedInt();
if (chromaFormatIdc == 3) {
bitArray.skipBits(1); // separate_colour_plane_flag
bitArray.skipBit(); // separate_colour_plane_flag
}
int picWidthInLumaSamples = bitArray.readUnsignedExpGolombCodedInt();
int picHeightInLumaSamples = bitArray.readUnsignedExpGolombCodedInt();
@ -288,7 +288,7 @@ public final class H265Reader implements ElementaryStreamReader {
bitArray.skipBits(8);
bitArray.readUnsignedExpGolombCodedInt(); // log2_min_pcm_luma_coding_block_size_minus3
bitArray.readUnsignedExpGolombCodedInt(); // log2_diff_max_min_pcm_luma_coding_block_size
bitArray.skipBits(1); // pcm_loop_filter_disabled_flag
bitArray.skipBit(); // pcm_loop_filter_disabled_flag
}
// Skips all short term reference picture sets.
skipShortTermRefPicSets(bitArray);
@ -365,11 +365,11 @@ public final class H265Reader implements ElementaryStreamReader {
interRefPicSetPredictionFlag = bitArray.readBit();
}
if (interRefPicSetPredictionFlag) {
bitArray.skipBits(1); // delta_rps_sign
bitArray.skipBit(); // delta_rps_sign
bitArray.readUnsignedExpGolombCodedInt(); // abs_delta_rps_minus1
for (int j = 0; j <= previousNumDeltaPocs; j++) {
if (bitArray.readBit()) { // used_by_curr_pic_flag[j]
bitArray.skipBits(1); // use_delta_flag[j]
bitArray.skipBit(); // use_delta_flag[j]
}
}
} else {
@ -378,11 +378,11 @@ public final class H265Reader implements ElementaryStreamReader {
previousNumDeltaPocs = numNegativePics + numPositivePics;
for (int i = 0; i < numNegativePics; i++) {
bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s0_minus1[i]
bitArray.skipBits(1); // used_by_curr_pic_s0_flag[i]
bitArray.skipBit(); // used_by_curr_pic_s0_flag[i]
}
for (int i = 0; i < numPositivePics; i++) {
bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s1_minus1[i]
bitArray.skipBits(1); // used_by_curr_pic_s1_flag[i]
bitArray.skipBit(); // used_by_curr_pic_s1_flag[i]
}
}
}

View file

@ -51,9 +51,10 @@ import java.util.List;
Assertions.checkArgument(MimeTypes.APPLICATION_CEA608.equals(channelMimeType)
|| MimeTypes.APPLICATION_CEA708.equals(channelMimeType),
"Invalid closed caption mime type provided: " + channelMimeType);
output.format(Format.createTextSampleFormat(idGenerator.getFormatId(), channelMimeType, null,
Format.NO_VALUE, channelFormat.selectionFlags, channelFormat.language,
channelFormat.accessibilityChannel, null));
String formatId = channelFormat.id != null ? channelFormat.id : idGenerator.getFormatId();
output.format(Format.createTextSampleFormat(formatId, channelMimeType, null, Format.NO_VALUE,
channelFormat.selectionFlags, channelFormat.language, channelFormat.accessibilityChannel,
null));
outputs[i] = output;
}
}

View file

@ -111,7 +111,6 @@ public final class TsExtractor implements Extractor {
@Mode private final int mode;
private final List<TimestampAdjuster> timestampAdjusters;
private final ParsableByteArray tsPacketBuffer;
private final ParsableBitArray tsScratch;
private final SparseIntArray continuityCounters;
private final TsPayloadReader.Factory payloadReaderFactory;
private final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid
@ -164,7 +163,6 @@ public final class TsExtractor implements Extractor {
timestampAdjusters.add(timestampAdjuster);
}
tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE);
tsScratch = new ParsableBitArray(new byte[3]);
trackIds = new SparseBooleanArray();
tsPayloadReaders = new SparseArray<>();
continuityCounters = new SparseIntArray();
@ -250,24 +248,23 @@ public final class TsExtractor implements Extractor {
return RESULT_CONTINUE;
}
tsPacketBuffer.skipBytes(1);
tsPacketBuffer.readBytes(tsScratch, 3);
if (tsScratch.readBit()) { // transport_error_indicator
int tsPacketHeader = tsPacketBuffer.readInt();
if ((tsPacketHeader & 0x800000) != 0) { // transport_error_indicator
// There are uncorrectable errors in this packet.
tsPacketBuffer.setPosition(endOfPacket);
return RESULT_CONTINUE;
}
boolean payloadUnitStartIndicator = tsScratch.readBit();
tsScratch.skipBits(1); // transport_priority
int pid = tsScratch.readBits(13);
tsScratch.skipBits(2); // transport_scrambling_control
boolean adaptationFieldExists = tsScratch.readBit();
boolean payloadExists = tsScratch.readBit();
boolean payloadUnitStartIndicator = (tsPacketHeader & 0x400000) != 0;
// Ignoring transport_priority (tsPacketHeader & 0x200000)
int pid = (tsPacketHeader & 0x1FFF00) >> 8;
// Ignoring transport_scrambling_control (tsPacketHeader & 0xC0)
boolean adaptationFieldExists = (tsPacketHeader & 0x20) != 0;
boolean payloadExists = (tsPacketHeader & 0x10) != 0;
// Discontinuity check.
boolean discontinuityFound = false;
int continuityCounter = tsScratch.readBits(4);
if (mode != MODE_HLS) {
int continuityCounter = tsPacketHeader & 0xF;
int previousCounter = continuityCounters.get(pid, continuityCounter - 1);
continuityCounters.put(pid, continuityCounter);
if (previousCounter == continuityCounter) {
@ -276,7 +273,7 @@ public final class TsExtractor implements Extractor {
tsPacketBuffer.setPosition(endOfPacket);
return RESULT_CONTINUE;
}
} else if (continuityCounter != (previousCounter + 1) % 16) {
} else if (continuityCounter != ((previousCounter + 1) & 0xF)) {
discontinuityFound = true;
}
}
@ -296,7 +293,6 @@ public final class TsExtractor implements Extractor {
}
tsPacketBuffer.setLimit(endOfPacket);
payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator);
Assertions.checkState(tsPacketBuffer.getPosition() <= endOfPacket);
tsPacketBuffer.setLimit(limit);
}
}

View file

@ -47,7 +47,8 @@ public final class FlacStreamInfo {
this.sampleRate = scratch.readBits(20);
this.channels = scratch.readBits(3) + 1;
this.bitsPerSample = scratch.readBits(5) + 1;
this.totalSamples = scratch.readBits(36);
this.totalSamples = ((scratch.readBits(4) & 0xFL) << 32)
| (scratch.readBits(32) & 0xFFFFFFFFL);
// Remaining 16 bytes is md5 value
}

View file

@ -265,7 +265,7 @@ public final class NalUnitUtil {
}
data.readUnsignedExpGolombCodedInt(); // bit_depth_luma_minus8
data.readUnsignedExpGolombCodedInt(); // bit_depth_chroma_minus8
data.skipBits(1); // qpprime_y_zero_transform_bypass_flag
data.skipBit(); // qpprime_y_zero_transform_bypass_flag
boolean seqScalingMatrixPresentFlag = data.readBit();
if (seqScalingMatrixPresentFlag) {
int limit = (chromaFormatIdc != 3) ? 8 : 12;
@ -295,17 +295,17 @@ public final class NalUnitUtil {
}
}
data.readUnsignedExpGolombCodedInt(); // max_num_ref_frames
data.skipBits(1); // gaps_in_frame_num_value_allowed_flag
data.skipBit(); // gaps_in_frame_num_value_allowed_flag
int picWidthInMbs = data.readUnsignedExpGolombCodedInt() + 1;
int picHeightInMapUnits = data.readUnsignedExpGolombCodedInt() + 1;
boolean frameMbsOnlyFlag = data.readBit();
int frameHeightInMbs = (2 - (frameMbsOnlyFlag ? 1 : 0)) * picHeightInMapUnits;
if (!frameMbsOnlyFlag) {
data.skipBits(1); // mb_adaptive_frame_field_flag
data.skipBit(); // mb_adaptive_frame_field_flag
}
data.skipBits(1); // direct_8x8_inference_flag
data.skipBit(); // direct_8x8_inference_flag
int frameWidth = picWidthInMbs * 16;
int frameHeight = frameHeightInMbs * 16;
boolean frameCroppingFlag = data.readBit();
@ -368,7 +368,7 @@ public final class NalUnitUtil {
data.skipBits(8); // nal_unit
int picParameterSetId = data.readUnsignedExpGolombCodedInt();
int seqParameterSetId = data.readUnsignedExpGolombCodedInt();
data.skipBits(1); // entropy_coding_mode_flag
data.skipBit(); // entropy_coding_mode_flag
boolean bottomFieldPicOrderInFramePresentFlag = data.readBit();
return new PpsData(picParameterSetId, seqParameterSetId, bottomFieldPicOrderInFramePresentFlag);
}

View file

@ -110,14 +110,26 @@ public final class ParsableBitArray {
assertValidOffset();
}
/**
* Skips a single bit.
*/
public void skipBit() {
if (++bitOffset == 8) {
bitOffset = 0;
byteOffset++;
}
assertValidOffset();
}
/**
* Skips bits and moves current reading position forward.
*
* @param n The number of bits to skip.
* @param numBits The number of bits to skip.
*/
public void skipBits(int n) {
byteOffset += (n / 8);
bitOffset += (n % 8);
public void skipBits(int numBits) {
int numBytes = numBits / 8;
byteOffset += numBytes;
bitOffset += numBits - (numBytes * 8);
if (bitOffset > 7) {
byteOffset++;
bitOffset -= 8;
@ -131,7 +143,9 @@ public final class ParsableBitArray {
* @return Whether the bit is set.
*/
public boolean readBit() {
return readBits(1) == 1;
boolean returnValue = (data[byteOffset] & (0x80 >> bitOffset)) != 0;
skipBit();
return returnValue;
}
/**
@ -141,48 +155,18 @@ public final class ParsableBitArray {
* @return An integer whose bottom n bits hold the read data.
*/
public int readBits(int numBits) {
if (numBits == 0) {
return 0;
}
int returnValue = 0;
// Read as many whole bytes as we can.
int wholeBytes = (numBits / 8);
for (int i = 0; i < wholeBytes; i++) {
int byteValue;
if (bitOffset != 0) {
byteValue = ((data[byteOffset] & 0xFF) << bitOffset)
| ((data[byteOffset + 1] & 0xFF) >>> (8 - bitOffset));
} else {
byteValue = data[byteOffset];
}
numBits -= 8;
returnValue |= (byteValue & 0xFF) << numBits;
bitOffset += numBits;
while (bitOffset > 8) {
bitOffset -= 8;
returnValue |= (data[byteOffset++] & 0xFF) << bitOffset;
}
returnValue |= (data[byteOffset] & 0xFF) >> 8 - bitOffset;
returnValue &= 0xFFFFFFFF >>> (32 - numBits);
if (bitOffset == 8) {
bitOffset = 0;
byteOffset++;
}
// Read any remaining bits.
if (numBits > 0) {
int nextBit = bitOffset + numBits;
byte writeMask = (byte) (0xFF >> (8 - numBits));
if (nextBit > 8) {
// Combine bits from current byte and next byte.
returnValue |= ((((data[byteOffset] & 0xFF) << (nextBit - 8)
| ((data[byteOffset + 1] & 0xFF) >> (16 - nextBit))) & writeMask));
byteOffset++;
} else {
// Bits to be read only within current byte.
returnValue |= (((data[byteOffset] & 0xFF) >> (8 - nextBit)) & writeMask);
if (nextBit == 8) {
byteOffset++;
}
}
bitOffset = nextBit % 8;
}
assertValidOffset();
return returnValue;
}
@ -231,7 +215,6 @@ public final class ParsableBitArray {
private void assertValidOffset() {
// It is fine for position to be at the end of the array, but no further.
Assertions.checkState(byteOffset >= 0
&& (bitOffset >= 0 && bitOffset < 8)
&& (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0)));
}

View file

@ -54,15 +54,27 @@ public final class ParsableNalUnitBitArray {
assertValidOffset();
}
/**
* Skips a single bit.
*/
public void skipBit() {
if (++bitOffset == 8) {
bitOffset = 0;
byteOffset += shouldSkipByte(byteOffset + 1) ? 2 : 1;
}
assertValidOffset();
}
/**
* Skips bits and moves current reading position forward.
*
* @param n The number of bits to skip.
* @param numBits The number of bits to skip.
*/
public void skipBits(int n) {
public void skipBits(int numBits) {
int oldByteOffset = byteOffset;
byteOffset += (n / 8);
bitOffset += (n % 8);
int numBytes = numBits / 8;
byteOffset += numBytes;
bitOffset += numBits - (numBytes * 8);
if (bitOffset > 7) {
byteOffset++;
bitOffset -= 8;
@ -81,13 +93,14 @@ public final class ParsableNalUnitBitArray {
* Returns whether it's possible to read {@code n} bits starting from the current offset. The
* offset is not modified.
*
* @param n The number of bits.
* @param numBits The number of bits.
* @return Whether it is possible to read {@code n} bits.
*/
public boolean canReadBits(int n) {
public boolean canReadBits(int numBits) {
int oldByteOffset = byteOffset;
int newByteOffset = byteOffset + (n / 8);
int newBitOffset = bitOffset + (n % 8);
int numBytes = numBits / 8;
int newByteOffset = byteOffset + numBytes;
int newBitOffset = bitOffset + numBits - (numBytes * 8);
if (newBitOffset > 7) {
newByteOffset++;
newBitOffset -= 8;
@ -108,7 +121,9 @@ public final class ParsableNalUnitBitArray {
* @return Whether the bit is set.
*/
public boolean readBit() {
return readBits(1) == 1;
boolean returnValue = (data[byteOffset] & (0x80 >> bitOffset)) != 0;
skipBit();
return returnValue;
}
/**
@ -118,50 +133,19 @@ public final class ParsableNalUnitBitArray {
* @return An integer whose bottom n bits hold the read data.
*/
public int readBits(int numBits) {
if (numBits == 0) {
return 0;
}
int returnValue = 0;
// Read as many whole bytes as we can.
int wholeBytes = (numBits / 8);
for (int i = 0; i < wholeBytes; i++) {
int nextByteOffset = shouldSkipByte(byteOffset + 1) ? byteOffset + 2 : byteOffset + 1;
int byteValue;
if (bitOffset != 0) {
byteValue = ((data[byteOffset] & 0xFF) << bitOffset)
| ((data[nextByteOffset] & 0xFF) >>> (8 - bitOffset));
} else {
byteValue = data[byteOffset];
}
numBits -= 8;
returnValue |= (byteValue & 0xFF) << numBits;
byteOffset = nextByteOffset;
bitOffset += numBits;
while (bitOffset > 8) {
bitOffset -= 8;
returnValue |= (data[byteOffset] & 0xFF) << bitOffset;
byteOffset += shouldSkipByte(byteOffset + 1) ? 2 : 1;
}
// Read any remaining bits.
if (numBits > 0) {
int nextBit = bitOffset + numBits;
byte writeMask = (byte) (0xFF >> (8 - numBits));
int nextByteOffset = shouldSkipByte(byteOffset + 1) ? byteOffset + 2 : byteOffset + 1;
if (nextBit > 8) {
// Combine bits from current byte and next byte.
returnValue |= ((((data[byteOffset] & 0xFF) << (nextBit - 8)
| ((data[nextByteOffset] & 0xFF) >> (16 - nextBit))) & writeMask));
byteOffset = nextByteOffset;
} else {
// Bits to be read only within current byte.
returnValue |= (((data[byteOffset] & 0xFF) >> (8 - nextBit)) & writeMask);
if (nextBit == 8) {
byteOffset = nextByteOffset;
}
}
bitOffset = nextBit % 8;
returnValue |= (data[byteOffset] & 0xFF) >> 8 - bitOffset;
returnValue &= 0xFFFFFFFF >>> (32 - numBits);
if (bitOffset == 8) {
bitOffset = 0;
byteOffset += shouldSkipByte(byteOffset + 1) ? 2 : 1;
}
assertValidOffset();
return returnValue;
}
@ -220,7 +204,6 @@ public final class ParsableNalUnitBitArray {
private void assertValidOffset() {
// It is fine for position to be at the end of the array, but no further.
Assertions.checkState(byteOffset >= 0
&& (bitOffset >= 0 && bitOffset < 8)
&& (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0)));
}

View file

@ -578,9 +578,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
private void maybeNotifyVideoSizeChanged() {
if (reportedWidth != currentWidth || reportedHeight != currentHeight
if ((currentWidth != Format.NO_VALUE || currentHeight != Format.NO_VALUE)
&& (reportedWidth != currentWidth || reportedHeight != currentHeight
|| reportedUnappliedRotationDegrees != currentUnappliedRotationDegrees
|| reportedPixelWidthHeightRatio != currentPixelWidthHeightRatio) {
|| reportedPixelWidthHeightRatio != currentPixelWidthHeightRatio)) {
eventDispatcher.videoSizeChanged(currentWidth, currentHeight, currentUnappliedRotationDegrees,
currentPixelWidthHeightRatio);
reportedWidth = currentWidth;
@ -592,8 +593,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
private void maybeRenotifyVideoSizeChanged() {
if (reportedWidth != Format.NO_VALUE || reportedHeight != Format.NO_VALUE) {
eventDispatcher.videoSizeChanged(currentWidth, currentHeight, currentUnappliedRotationDegrees,
currentPixelWidthHeightRatio);
eventDispatcher.videoSizeChanged(reportedWidth, reportedHeight,
reportedUnappliedRotationDegrees, reportedPixelWidthHeightRatio);
}
}

View file

@ -40,6 +40,38 @@ import java.util.List;
*/
public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable<HlsPlaylist>> {
/**
* Thrown when a playlist is considered to be stuck due to a server side error.
*/
public static final class PlaylistStuckException extends IOException {
/**
* The url of the stuck playlist.
*/
public final String url;
private PlaylistStuckException(String url) {
this.url = url;
}
}
/**
* Thrown when the media sequence of a new snapshot indicates the server has reset.
*/
public static final class PlaylistResetException extends IOException {
/**
* The url of the reset playlist.
*/
public final String url;
private PlaylistResetException(String url) {
this.url = url;
}
}
/**
* Listener for primary playlist changes.
*/
@ -75,6 +107,11 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
}
/**
* Coefficient applied on the target duration of a playlist to determine the amount of time after
* which an unchanging playlist is considered stuck.
*/
private static final double PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT = 3.5;
/**
* The minimum number of milliseconds that a url is kept as primary url, if no
* {@link #getPlaylistSnapshot} call is made for that url.
@ -213,14 +250,14 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
}
/**
* If the playlist is having trouble loading the playlist referenced by the given {@link HlsUrl},
* this method throws the underlying error.
* If the playlist is having trouble refreshing the playlist referenced by the given
* {@link HlsUrl}, this method throws the underlying error.
*
* @param url The {@link HlsUrl}.
* @throws IOException The underyling error.
*/
public void maybeThrowPlaylistRefreshError(HlsUrl url) throws IOException {
playlistBundles.get(url).mediaPlaylistLoader.maybeThrowError();
playlistBundles.get(url).maybeThrowPlaylistRefreshError();
}
/**
@ -441,9 +478,11 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
private HlsMediaPlaylist playlistSnapshot;
private long lastSnapshotLoadMs;
private long lastSnapshotChangeMs;
private long lastSnapshotAccessTimeMs;
private long blacklistUntilMs;
private boolean pendingRefresh;
private IOException playlistError;
public MediaPlaylistBundle(HlsUrl playlistUrl, long initialLastSnapshotAccessTimeMs) {
this.playlistUrl = playlistUrl;
@ -483,6 +522,13 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
}
}
public void maybeThrowPlaylistRefreshError() throws IOException {
mediaPlaylistLoader.maybeThrowError();
if (playlistError != null) {
throw playlistError;
}
}
// Loader.Callback implementation.
@Override
@ -494,8 +540,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
eventDispatcher.loadCompleted(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs,
loadDurationMs, loadable.bytesLoaded());
} else {
onLoadError(loadable, elapsedRealtimeMs, loadDurationMs,
new ParserException("Loaded playlist has unexpected type."));
playlistError = new ParserException("Loaded playlist has unexpected type.");
}
}
@ -517,10 +562,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
}
boolean shouldRetry = true;
if (ChunkedTrackBlacklistUtil.shouldBlacklist(error)) {
blacklistUntilMs =
SystemClock.elapsedRealtime() + ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS;
notifyPlaylistBlacklisting(playlistUrl,
ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS);
blacklistPlaylist();
shouldRetry = primaryHlsUrl == playlistUrl && !maybeSelectNewPrimaryUrl();
}
return shouldRetry ? Loader.RETRY : Loader.DONT_RETRY;
@ -538,14 +580,28 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
private void processLoadedPlaylist(HlsMediaPlaylist loadedPlaylist) {
HlsMediaPlaylist oldPlaylist = playlistSnapshot;
lastSnapshotLoadMs = SystemClock.elapsedRealtime();
long currentTimeMs = SystemClock.elapsedRealtime();
lastSnapshotLoadMs = currentTimeMs;
playlistSnapshot = getLatestPlaylistSnapshot(oldPlaylist, loadedPlaylist);
long refreshDelayUs = C.TIME_UNSET;
if (playlistSnapshot != oldPlaylist) {
playlistError = null;
lastSnapshotChangeMs = currentTimeMs;
if (onPlaylistUpdated(playlistUrl, playlistSnapshot)) {
refreshDelayUs = playlistSnapshot.targetDurationUs;
}
} else if (!playlistSnapshot.hasEndTag) {
if (currentTimeMs - lastSnapshotChangeMs
> C.usToMs(playlistSnapshot.targetDurationUs)
* PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT) {
// The playlist seems to be stuck, we blacklist it.
playlistError = new PlaylistStuckException(playlistUrl.url);
blacklistPlaylist();
} else if (loadedPlaylist.mediaSequence + loadedPlaylist.segments.size()
< playlistSnapshot.mediaSequence) {
// The media sequence has jumped backwards. The server has likely reset.
playlistError = new PlaylistResetException(playlistUrl.url);
}
refreshDelayUs = playlistSnapshot.targetDurationUs / 2;
}
if (refreshDelayUs != C.TIME_UNSET) {
@ -554,6 +610,12 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
}
}
private void blacklistPlaylist() {
blacklistUntilMs = SystemClock.elapsedRealtime()
+ ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS;
notifyPlaylistBlacklisting(playlistUrl, ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS);
}
}
}

View file

@ -712,7 +712,12 @@ public class PlaybackControlView extends FrameLayout {
if (fastForwardMs <= 0) {
return;
}
seekTo(Math.min(player.getCurrentPosition() + fastForwardMs, player.getDuration()));
long durationMs = player.getDuration();
long seekPositionMs = player.getCurrentPosition() + fastForwardMs;
if (durationMs != C.TIME_UNSET) {
seekPositionMs = Math.min(seekPositionMs, durationMs);
}
seekTo(seekPositionMs);
}
private void seekTo(long positionMs) {

View file

@ -574,7 +574,8 @@ public final class SimpleExoPlayerView extends FrameLayout {
/**
* Sets the rewind increment in milliseconds.
*
* @param rewindMs The rewind increment in milliseconds.
* @param rewindMs The rewind increment in milliseconds. A non-positive value will cause the
* rewind button to be disabled.
*/
public void setRewindIncrementMs(int rewindMs) {
Assertions.checkState(controller != null);
@ -584,7 +585,8 @@ public final class SimpleExoPlayerView extends FrameLayout {
/**
* Sets the fast forward increment in milliseconds.
*
* @param fastForwardMs The fast forward increment in milliseconds.
* @param fastForwardMs The fast forward increment in milliseconds. A non-positive value will
* cause the fast forward button to be disabled.
*/
public void setFastForwardIncrementMs(int fastForwardMs) {
Assertions.checkState(controller != null);