/*
* Copyright (C) 2014 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.exoplayer.util;
/**
* 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() {}
/**
* 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);
}
/**
* 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;
}
/**
* Gets the current bit offset.
*
* @return The current bit offset.
*/
public int getPosition() {
return byteOffset * 8 + bitOffset;
}
/**
* 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 bits and moves current reading position forward.
*
* @param n The number of bits to skip.
*/
public void skipBits(int n) {
byteOffset += (n / 8);
bitOffset += (n % 8);
if (bitOffset > 7) {
byteOffset++;
bitOffset -= 8;
}
assertValidOffset();
}
/**
* Reads a single bit.
*
* @return True if the bit is set. False otherwise.
*/
public boolean readBit() {
return readBits(1) == 1;
}
/**
* Reads up to 32 bits.
*
* @param n The number of bits to read.
* @return An integer whose bottom n bits hold the read data.
*/
public int readBits(int n) {
if (n == 0) {
return 0;
}
int returnValue = 0;
// While n >= 8, read whole bytes.
while (n >= 8) {
int byteValue;
if (bitOffset != 0) {
byteValue = ((data[byteOffset] & 0xFF) << bitOffset)
| ((data[byteOffset + 1] & 0xFF) >>> (8 - bitOffset));
} else {
byteValue = data[byteOffset];
}
n -= 8;
returnValue |= (byteValue & 0xFF) << n;
byteOffset++;
}
if (n > 0) {
int nextBit = bitOffset + n;
byte writeMask = (byte) (0xFF >> (8 - n));
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;
}
/**
* Peeks the length of an Exp-Golomb-coded integer (signed or unsigned) starting from the current
* offset, returning the length or -1 if the limit is reached.
*
* @return The length of the Exp-Golob-coded integer, or -1.
*/
public int peekExpGolombCodedNumLength() {
int initialByteOffset = byteOffset;
int initialBitOffset = bitOffset;
int leadingZeros = 0;
while (byteOffset < byteLimit && !readBit()) {
leadingZeros++;
}
boolean hitLimit = byteOffset == byteLimit;
byteOffset = initialByteOffset;
bitOffset = initialBitOffset;
return hitLimit ? -1 : leadingZeros * 2 + 1;
}
/**
* Reads an unsigned Exp-Golomb-coded format integer.
*
* @return The value of the parsed Exp-Golomb-coded integer.
*/
public int readUnsignedExpGolombCodedInt() {
return readExpGolombCodeNum();
}
/**
* Reads an signed Exp-Golomb-coded format integer.
*
* @return The value of the parsed Exp-Golomb-coded integer.
*/
public int readSignedExpGolombCodedInt() {
int codeNum = readExpGolombCodeNum();
return ((codeNum % 2) == 0 ? -1 : 1) * ((codeNum + 1) / 2);
}
private int readExpGolombCodeNum() {
int leadingZeros = 0;
while (!readBit()) {
leadingZeros++;
}
return (1 << leadingZeros) - 1 + (leadingZeros > 0 ? readBits(leadingZeros) : 0);
}
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)));
}
}