From f94db63366f8649fab29978d008fea35c761b837 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 4 Jul 2017 03:32:23 -0700 Subject: [PATCH 01/11] Fix reporting of width/height 1. maybeRenotifyVideoSizeChanged should report reported* variables 2. Add check into maybeNotifyVideoSizeChanged to suppress reporting in the case that the width and height are still unknown. Issue: #3007 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=160879625 --- .../exoplayer2/video/MediaCodecVideoRenderer.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 990a29dc4e..fbbcd9a99a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -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); } } From 4233f81ed7d95d3f40d277f93820b03f6c50890d Mon Sep 17 00:00:00 2001 From: olly Date: Mon, 10 Jul 2017 16:00:12 -0700 Subject: [PATCH 02/11] Misc tweaks to UI components ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=161454491 --- .../google/android/exoplayer2/ui/PlaybackControlView.java | 7 ++++++- .../google/android/exoplayer2/ui/SimpleExoPlayerView.java | 6 ++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java index 5f88f3a241..250c237772 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlaybackControlView.java @@ -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) { diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java index fce05f5bc4..245999f8b5 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/SimpleExoPlayerView.java @@ -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); From 2665e42f85b5c64dd38439b49247bfbfa983cda2 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 11 Jul 2017 04:36:06 -0700 Subject: [PATCH 03/11] Correctly propagate format identifier for CEA-608 in HLS Issue: #3033 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=161512537 --- .../google/android/exoplayer2/extractor/ts/SeiReader.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/SeiReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/SeiReader.java index 1e5d480ea1..907419f8fc 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/SeiReader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/SeiReader.java @@ -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; } } From 4cb5b349777ed6a71ab704200e7ccc6a8d1d3fe2 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 12 Jul 2017 04:47:31 -0700 Subject: [PATCH 04/11] Don't use ParsableBitArray to parse TS packet headers Really low hanging fruit optimization for TS extraction. ParsableBitArray is quite expensive. In particular readBits contains at least 2 if blocks and a for loop, and was being called 5 times per 188 byte packet (4 times via readBit). A separate change will follow that optimizes readBit, but for this particular case there's no real value to using a ParsableBitArray anyway; use of ParsableBitArray IMO only really becomes useful when you need to parse a bitstream more than 4 bytes long, or where parsing the bitstream requires some control flow (if/for) to parse. There are probably other places where we're using ParsableBitArray over-zealously. I'll roll that into a tracking bug for looking in more detail at all extractors. Issue: #3040 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=161650940 --- .../exoplayer2/extractor/ts/TsExtractor.java | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index 7b63ce813c..1149856649 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -111,7 +111,6 @@ public final class TsExtractor implements Extractor { @Mode private final int mode; private final List timestampAdjusters; private final ParsableByteArray tsPacketBuffer; - private final ParsableBitArray tsScratch; private final SparseIntArray continuityCounters; private final TsPayloadReader.Factory payloadReaderFactory; private final SparseArray 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); } } From fba0546774133c0d7b78b575e96ba8208a88cab5 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 12 Jul 2017 08:12:11 -0700 Subject: [PATCH 05/11] Optimize ParsableBitArray ParsableBitArray.readBit in particular was doing an excessive amount of work. The new implementation is ~20% faster on desktop. Issue: #3040 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=161666420 --- .../exoplayer2/util/ParsableBitArrayTest.java | 139 ++++++++++++++++++ .../exoplayer2/util/ParsableBitArray.java | 73 ++++----- 2 files changed, 167 insertions(+), 45 deletions(-) create mode 100644 library/core/src/androidTest/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java new file mode 100644 index 0000000000..cfb9cd78be --- /dev/null +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java @@ -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; + } + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java index df9f04f067..0456bcb879 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java @@ -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))); } From 17e73bdc7859852cab8821501da6c61bbcb7dc29 Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 12 Jul 2017 09:25:08 -0700 Subject: [PATCH 06/11] Optimize ParsableNalUnitBitArray Apply the same learnings as in ParsableBitArray. Issue: #3040 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=161674119 --- .../util/ParsableNalUnitBitArrayTest.java | 2 +- .../exoplayer2/extractor/ts/H264Reader.java | 2 +- .../exoplayer2/extractor/ts/H265Reader.java | 14 +-- .../android/exoplayer2/util/NalUnitUtil.java | 10 +-- .../util/ParsableNalUnitBitArray.java | 85 ++++++++----------- 5 files changed, 48 insertions(+), 65 deletions(-) diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java index b62aff46f5..294d3d352a 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArrayTest.java @@ -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)); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java index 8206ed7d6d..3cde946ce3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H264Reader.java @@ -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 diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java index 712ca8d69c..f6ae80ba56 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/H265Reader.java @@ -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] } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java index ab2fec0db7..c4ed20546d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/NalUnitUtil.java @@ -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); } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java index 05d7a9929d..443c69909c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableNalUnitBitArray.java @@ -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))); } From a39ab8161e035174c862fb42b0c522baf42f22fb Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 12 Jul 2017 09:53:20 -0700 Subject: [PATCH 07/11] Fix FlacStreamInfo to not call readBits with a >32 value ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=161677399 --- .../com/google/android/exoplayer2/util/FlacStreamInfo.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/FlacStreamInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/util/FlacStreamInfo.java index 8a69a6d095..6382f1130e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/FlacStreamInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/FlacStreamInfo.java @@ -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 } From 977fb225e3ba58965b16bda99483f5e5eef9313e Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 12 Jul 2017 11:55:49 -0700 Subject: [PATCH 08/11] Minor tweak to demo app "Default (none)" is sometimes just wrong, since the track selector may attempt to select a track even if it exceeds the renderer's capabilities. Just "Default", as it used to be, was more accurate. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=161695241 --- .../android/exoplayer2/demo/TrackSelectionHelper.java | 7 +------ demo/src/main/res/values/strings.xml | 2 -- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/demo/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java b/demo/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java index 033b515767..fb7217f8fd 100644 --- a/demo/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java +++ b/demo/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java @@ -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); diff --git a/demo/src/main/res/values/strings.xml b/demo/src/main/res/values/strings.xml index ac17ad4443..4eb2b89324 100644 --- a/demo/src/main/res/values/strings.xml +++ b/demo/src/main/res/values/strings.xml @@ -30,8 +30,6 @@ Default - Default (none) - Unexpected intent action: %1$s Enable random adaptation From 1855423734287861163ccb82258779555159dad8 Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 13 Jul 2017 05:59:15 -0700 Subject: [PATCH 09/11] Simplify + optimize VorbisBitArray ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=161796758 --- .../extractor/ogg/VorbisBitArrayTest.java | 131 ------------------ .../extractor/ogg/VorbisBitArray.java | 85 ++++-------- 2 files changed, 28 insertions(+), 188 deletions(-) diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArrayTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArrayTest.java index 9a65cad6a5..a24cb1599b 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArrayTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArrayTest.java @@ -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()); - } } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArray.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArray.java index ae52e80299..958a2ef955 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArray.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ogg/VorbisBitArray.java @@ -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))); } } From 055abc759269e48dd4a9c7a1b315012d3abf0ac1 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Tue, 4 Jul 2017 09:38:57 -0700 Subject: [PATCH 10/11] Detect playlist stuck and playlist reset conditions in HLS This CL aims that the player fails upon: - Playlist that don't change in a suspiciously long time, which might mean there are server side issues. - Playlist with a media sequence lower that its last snapshot and no overlapping segments. This two error conditions are propagated through the renderer, but not through MediaSource#maybeThrowSourceInfoRefreshError. Issue:#2872 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=160899995 --- .../hls/playlist/HlsPlaylistTracker.java | 82 ++++++++++++++++--- 1 file changed, 72 insertions(+), 10 deletions(-) diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java index 62b77a0575..567dbd4af6 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistTracker.java @@ -40,6 +40,38 @@ import java.util.List; */ public final class HlsPlaylistTracker implements Loader.Callback> { + /** + * 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 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 Date: Wed, 19 Jul 2017 11:52:12 -0700 Subject: [PATCH 11/11] Update release notes + bump version number ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=162514848 --- RELEASENOTES.md | 11 +++++++++++ build.gradle | 2 +- demo/src/main/AndroidManifest.xml | 4 ++-- .../android/exoplayer2/ExoPlayerLibraryInfo.java | 6 +++--- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f9f6b02c19..ff1bd42fde 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -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 diff --git a/build.gradle b/build.gradle index 01d8b6616c..a4ae1f175e 100644 --- a/build.gradle +++ b/build.gradle @@ -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')) { diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index addce60cad..afcddccac9 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -16,8 +16,8 @@ + android:versionCode="2404" + android:versionName="2.4.4"> diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java index 650ce727cd..73d91f293e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerLibraryInfo.java @@ -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}