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))); }