| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.google.android.exoplayer2.util; |
| |
| import static java.lang.Math.min; |
| |
| import com.google.common.base.Charsets; |
| import java.nio.charset.Charset; |
| |
| /** Wraps a byte array, providing methods that allow it to be read as a bitstream. */ |
| public final class ParsableBitArray { |
| |
| public byte[] data; |
| |
| // The offset within the data, stored as the current byte offset, and the bit offset within that |
| // byte (from 0 to 7). |
| private int byteOffset; |
| private int bitOffset; |
| private int byteLimit; |
| |
| /** Creates a new instance that initially has no backing data. */ |
| public ParsableBitArray() { |
| data = Util.EMPTY_BYTE_ARRAY; |
| } |
| |
| /** |
| * Creates a new instance that wraps an existing array. |
| * |
| * @param data The data to wrap. |
| */ |
| public ParsableBitArray(byte[] data) { |
| this(data, data.length); |
| } |
| |
| /** |
| * Creates a new instance that wraps an existing array. |
| * |
| * @param data The data to wrap. |
| * @param limit The limit in bytes. |
| */ |
| public ParsableBitArray(byte[] data, int limit) { |
| this.data = data; |
| byteLimit = limit; |
| } |
| |
| /** |
| * Updates the instance to wrap {@code data}, and resets the position to zero. |
| * |
| * @param data The array to wrap. |
| */ |
| public void reset(byte[] data) { |
| reset(data, data.length); |
| } |
| |
| /** |
| * Sets this instance's data, position and limit to match the provided {@code parsableByteArray}. |
| * Any modifications to the underlying data array will be visible in both instances |
| * |
| * @param parsableByteArray The {@link ParsableByteArray}. |
| */ |
| public void reset(ParsableByteArray parsableByteArray) { |
| reset(parsableByteArray.getData(), parsableByteArray.limit()); |
| setPosition(parsableByteArray.getPosition() * 8); |
| } |
| |
| /** |
| * Updates the instance to wrap {@code data}, and resets the position to zero. |
| * |
| * @param data The array to wrap. |
| * @param limit The limit in bytes. |
| */ |
| public void reset(byte[] data, int limit) { |
| this.data = data; |
| byteOffset = 0; |
| bitOffset = 0; |
| byteLimit = limit; |
| } |
| |
| /** Returns the number of bits yet to be read. */ |
| public int bitsLeft() { |
| return (byteLimit - byteOffset) * 8 - bitOffset; |
| } |
| |
| /** Returns the current bit offset. */ |
| public int getPosition() { |
| return byteOffset * 8 + bitOffset; |
| } |
| |
| /** |
| * Returns the current byte offset. Must only be called when the position is byte aligned. |
| * |
| * @throws IllegalStateException If the position isn't byte aligned. |
| */ |
| public int getBytePosition() { |
| Assertions.checkState(bitOffset == 0); |
| return byteOffset; |
| } |
| |
| /** |
| * Sets the current bit offset. |
| * |
| * @param position The position to set. |
| */ |
| public void setPosition(int position) { |
| byteOffset = position / 8; |
| bitOffset = position - (byteOffset * 8); |
| assertValidOffset(); |
| } |
| |
| /** Skips a single bit. */ |
| public void skipBit() { |
| if (++bitOffset == 8) { |
| bitOffset = 0; |
| byteOffset++; |
| } |
| assertValidOffset(); |
| } |
| |
| /** |
| * Skips bits and moves current reading position forward. |
| * |
| * @param numBits The number of bits to skip. |
| */ |
| public void skipBits(int numBits) { |
| int numBytes = numBits / 8; |
| byteOffset += numBytes; |
| bitOffset += numBits - (numBytes * 8); |
| if (bitOffset > 7) { |
| byteOffset++; |
| bitOffset -= 8; |
| } |
| assertValidOffset(); |
| } |
| |
| /** |
| * Reads a single bit. |
| * |
| * @return Whether the bit is set. |
| */ |
| public boolean readBit() { |
| boolean returnValue = (data[byteOffset] & (0x80 >> bitOffset)) != 0; |
| skipBit(); |
| return returnValue; |
| } |
| |
| /** |
| * Reads up to 32 bits. |
| * |
| * @param numBits The number of bits to read. |
| * @return An integer whose bottom {@code numBits} bits hold the read data. |
| */ |
| public int readBits(int numBits) { |
| if (numBits == 0) { |
| return 0; |
| } |
| int returnValue = 0; |
| 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++; |
| } |
| assertValidOffset(); |
| return returnValue; |
| } |
| |
| /** |
| * Reads up to 64 bits. |
| * |
| * @param numBits The number of bits to read. |
| * @return A long whose bottom {@code numBits} bits hold the read data. |
| */ |
| public long readBitsToLong(int numBits) { |
| if (numBits <= 32) { |
| return Util.toUnsignedLong(readBits(numBits)); |
| } |
| return Util.toLong(readBits(numBits - 32), readBits(32)); |
| } |
| |
| /** |
| * Reads {@code numBits} bits into {@code buffer}. |
| * |
| * @param buffer The array into which the read data should be written. The trailing {@code numBits |
| * % 8} bits are written into the most significant bits of the last modified {@code buffer} |
| * byte. The remaining ones are unmodified. |
| * @param offset The offset in {@code buffer} at which the read data should be written. |
| * @param numBits The number of bits to read. |
| */ |
| public void readBits(byte[] buffer, int offset, int numBits) { |
| // Whole bytes. |
| int to = offset + (numBits >> 3) /* numBits / 8 */; |
| for (int i = offset; i < to; i++) { |
| buffer[i] = (byte) (data[byteOffset++] << bitOffset); |
| buffer[i] = (byte) (buffer[i] | ((data[byteOffset] & 0xFF) >> (8 - bitOffset))); |
| } |
| // Trailing bits. |
| int bitsLeft = numBits & 7 /* numBits % 8 */; |
| if (bitsLeft == 0) { |
| return; |
| } |
| // Set bits that are going to be overwritten to 0. |
| buffer[to] = (byte) (buffer[to] & (0xFF >> bitsLeft)); |
| if (bitOffset + bitsLeft > 8) { |
| // We read the rest of data[byteOffset] and increase byteOffset. |
| buffer[to] = (byte) (buffer[to] | ((data[byteOffset++] & 0xFF) << bitOffset)); |
| bitOffset -= 8; |
| } |
| bitOffset += bitsLeft; |
| int lastDataByteTrailingBits = (data[byteOffset] & 0xFF) >> (8 - bitOffset); |
| buffer[to] |= (byte) (lastDataByteTrailingBits << (8 - bitsLeft)); |
| if (bitOffset == 8) { |
| bitOffset = 0; |
| byteOffset++; |
| } |
| assertValidOffset(); |
| } |
| |
| /** |
| * Aligns the position to the next byte boundary. Does nothing if the position is already aligned. |
| */ |
| public void byteAlign() { |
| if (bitOffset == 0) { |
| return; |
| } |
| bitOffset = 0; |
| byteOffset++; |
| assertValidOffset(); |
| } |
| |
| /** |
| * Reads the next {@code length} bytes into {@code buffer}. Must only be called when the position |
| * is byte aligned. |
| * |
| * @see System#arraycopy(Object, int, Object, int, int) |
| * @param buffer The array into which the read data should be written. |
| * @param offset The offset in {@code buffer} at which the read data should be written. |
| * @param length The number of bytes to read. |
| * @throws IllegalStateException If the position isn't byte aligned. |
| */ |
| public void readBytes(byte[] buffer, int offset, int length) { |
| Assertions.checkState(bitOffset == 0); |
| System.arraycopy(data, byteOffset, buffer, offset, length); |
| byteOffset += length; |
| assertValidOffset(); |
| } |
| |
| /** |
| * Skips the next {@code length} bytes. Must only be called when the position is byte aligned. |
| * |
| * @param length The number of bytes to read. |
| * @throws IllegalStateException If the position isn't byte aligned. |
| */ |
| public void skipBytes(int length) { |
| Assertions.checkState(bitOffset == 0); |
| byteOffset += length; |
| assertValidOffset(); |
| } |
| |
| /** |
| * Reads the next {@code length} bytes as a UTF-8 string. Must only be called when the position is |
| * byte aligned. |
| * |
| * @param length The number of bytes to read. |
| * @return The string encoded by the bytes in UTF-8. |
| */ |
| public String readBytesAsString(int length) { |
| return readBytesAsString(length, Charsets.UTF_8); |
| } |
| |
| /** |
| * Reads the next {@code length} bytes as a string encoded in {@link Charset}. Must only be called |
| * when the position is byte aligned. |
| * |
| * @param length The number of bytes to read. |
| * @param charset The character set of the encoded characters. |
| * @return The string encoded by the bytes in the specified character set. |
| */ |
| public String readBytesAsString(int length, Charset charset) { |
| byte[] bytes = new byte[length]; |
| readBytes(bytes, 0, length); |
| return new String(bytes, charset); |
| } |
| |
| /** |
| * Overwrites {@code numBits} from this array using the {@code numBits} least significant bits |
| * from {@code value}. Bits are written in order from most significant to least significant. The |
| * read position is advanced by {@code numBits}. |
| * |
| * @param value The integer whose {@code numBits} least significant bits are written into {@link |
| * #data}. |
| * @param numBits The number of bits to write. |
| */ |
| public void putInt(int value, int numBits) { |
| int remainingBitsToRead = numBits; |
| if (numBits < 32) { |
| value &= (1 << numBits) - 1; |
| } |
| int firstByteReadSize = min(8 - bitOffset, numBits); |
| int firstByteRightPaddingSize = 8 - bitOffset - firstByteReadSize; |
| int firstByteBitmask = (0xFF00 >> bitOffset) | ((1 << firstByteRightPaddingSize) - 1); |
| data[byteOffset] = (byte) (data[byteOffset] & firstByteBitmask); |
| int firstByteInputBits = value >>> (numBits - firstByteReadSize); |
| data[byteOffset] = |
| (byte) (data[byteOffset] | (firstByteInputBits << firstByteRightPaddingSize)); |
| remainingBitsToRead -= firstByteReadSize; |
| int currentByteIndex = byteOffset + 1; |
| while (remainingBitsToRead > 8) { |
| data[currentByteIndex++] = (byte) (value >>> (remainingBitsToRead - 8)); |
| remainingBitsToRead -= 8; |
| } |
| int lastByteRightPaddingSize = 8 - remainingBitsToRead; |
| data[currentByteIndex] = |
| (byte) (data[currentByteIndex] & ((1 << lastByteRightPaddingSize) - 1)); |
| int lastByteInput = value & ((1 << remainingBitsToRead) - 1); |
| data[currentByteIndex] = |
| (byte) (data[currentByteIndex] | (lastByteInput << lastByteRightPaddingSize)); |
| skipBits(numBits); |
| assertValidOffset(); |
| } |
| |
| 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))); |
| } |
| } |