mirror of
https://github.com/samsonjs/media.git
synced 2026-03-26 09:35:47 +00:00
commit
cbffc14fa1
22 changed files with 364 additions and 347 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')) {
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue