Commit 797fa7f8 by Oliver Woodman

Make TsExtractor use ParsableByteArray where possible.

- TsExtractor is now based on ParsableByteArray rather than BitArray.
  This makes is much clearer that, for the most part, data is byte
  aligned. It will allow us to optimize TsExtractor without worrying
  about arbitrary bit offsets.
- BitArray is renamed ParsableBitArray for consistency, and is now
  exclusively for bit-stream level reading.
- There are some temporary methods in ParsableByteArray that should be
  cleared up once the optimizations are in place.

Issue: #278
parent 7c66b6ed
...@@ -38,7 +38,7 @@ public class Id3Parser implements MetadataParser<Map<String, Object>> { ...@@ -38,7 +38,7 @@ public class Id3Parser implements MetadataParser<Map<String, Object>> {
public Map<String, Object> parse(byte[] data, int size) public Map<String, Object> parse(byte[] data, int size)
throws UnsupportedEncodingException, ParserException { throws UnsupportedEncodingException, ParserException {
Map<String, Object> metadata = new HashMap<String, Object>(); Map<String, Object> metadata = new HashMap<String, Object>();
ParsableByteArray id3Data = new ParsableByteArray(data); ParsableByteArray id3Data = new ParsableByteArray(data, size);
int id3Size = parseId3Header(id3Data); int id3Size = parseId3Header(id3Data);
while (id3Size > 0) { while (id3Size > 0) {
......
...@@ -106,7 +106,7 @@ public final class Mp4Util { ...@@ -106,7 +106,7 @@ public final class Mp4Util {
* @param endOffset The offset (exclusive) in the data to end the search. * @param endOffset The offset (exclusive) in the data to end the search.
* @return The offset of the NAL unit, or {@code endOffset} if a NAL unit was not found. * @return The offset of the NAL unit, or {@code endOffset} if a NAL unit was not found.
*/ */
public static int findNextNalUnit(byte[] data, int startOffset, int endOffset) { public static int findNalUnit(byte[] data, int startOffset, int endOffset) {
return findNalUnit(data, startOffset, endOffset, -1); return findNalUnit(data, startOffset, endOffset, -1);
} }
......
...@@ -15,8 +15,9 @@ ...@@ -15,8 +15,9 @@
*/ */
package com.google.android.exoplayer.text.eia608; package com.google.android.exoplayer.text.eia608;
import com.google.android.exoplayer.util.BitArray;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.ParsableBitArray;
import com.google.android.exoplayer.util.ParsableByteArray;
import java.util.List; import java.util.List;
...@@ -80,11 +81,11 @@ public class Eia608Parser { ...@@ -80,11 +81,11 @@ public class Eia608Parser {
0xFB // 3F: 251 'û' "Latin small letter U with circumflex" 0xFB // 3F: 251 'û' "Latin small letter U with circumflex"
}; };
private final BitArray seiBuffer; private final ParsableBitArray seiBuffer;
private final StringBuilder stringBuilder; private final StringBuilder stringBuilder;
/* package */ Eia608Parser() { /* package */ Eia608Parser() {
seiBuffer = new BitArray(); seiBuffer = new ParsableBitArray();
stringBuilder = new StringBuilder(); stringBuilder = new StringBuilder();
} }
...@@ -98,10 +99,10 @@ public class Eia608Parser { ...@@ -98,10 +99,10 @@ public class Eia608Parser {
} }
stringBuilder.setLength(0); stringBuilder.setLength(0);
seiBuffer.reset(data, size); seiBuffer.reset(data);
seiBuffer.skipBits(3); // reserved + process_cc_data_flag + zero_bit seiBuffer.skipBits(3); // reserved + process_cc_data_flag + zero_bit
int ccCount = seiBuffer.readBits(5); int ccCount = seiBuffer.readBits(5);
seiBuffer.skipBytes(1); seiBuffer.skipBits(8);
for (int i = 0; i < ccCount; i++) { for (int i = 0; i < ccCount; i++) {
seiBuffer.skipBits(5); // one_bit + reserved seiBuffer.skipBits(5); // one_bit + reserved
...@@ -170,7 +171,7 @@ public class Eia608Parser { ...@@ -170,7 +171,7 @@ public class Eia608Parser {
* @param seiBuffer The buffer to read from. * @param seiBuffer The buffer to read from.
* @return The size of closed captions data. * @return The size of closed captions data.
*/ */
public static int parseHeader(BitArray seiBuffer) { public static int parseHeader(ParsableByteArray seiBuffer) {
int b = 0; int b = 0;
int payloadType = 0; int payloadType = 0;
...@@ -197,11 +198,11 @@ public class Eia608Parser { ...@@ -197,11 +198,11 @@ public class Eia608Parser {
if (countryCode != COUNTRY_CODE) { if (countryCode != COUNTRY_CODE) {
return 0; return 0;
} }
int providerCode = seiBuffer.readBits(16); int providerCode = seiBuffer.readUnsignedShort();
if (providerCode != PROVIDER_CODE) { if (providerCode != PROVIDER_CODE) {
return 0; return 0;
} }
int userIdentifier = seiBuffer.readBits(32); int userIdentifier = seiBuffer.readInt();
if (userIdentifier != USER_ID) { if (userIdentifier != USER_ID) {
return 0; return 0;
} }
......
...@@ -15,51 +15,37 @@ ...@@ -15,51 +15,37 @@
*/ */
package com.google.android.exoplayer.util; package com.google.android.exoplayer.util;
import com.google.android.exoplayer.upstream.DataSource;
import java.io.IOException;
/** /**
* Wraps a byte array, providing methods that allow it to be read as a bitstream. * Wraps a byte array, providing methods that allow it to be read as a bitstream.
*/ */
public final class BitArray { public final class ParsableBitArray {
private byte[] data; private byte[] data;
// The length of the valid data.
private int limit;
// The offset within the data, stored as the current byte offset, and the bit offset within that // The offset within the data, stored as the current byte offset, and the bit offset within that
// byte (from 0 to 7). // byte (from 0 to 7).
private int byteOffset; private int byteOffset;
private int bitOffset; private int bitOffset;
public BitArray() { /** Creates a new instance that initially has no backing data. */
} public ParsableBitArray() {}
public BitArray(byte[] data, int limit) {
this.data = data;
this.limit = limit;
}
/** /**
* Clears all data, setting the offset and limit to zero. * Creates a new instance that wraps an existing array.
*
* @param data The data to wrap.
*/ */
public void reset() { public ParsableBitArray(byte[] data) {
byteOffset = 0; this.data = data;
bitOffset = 0;
limit = 0;
} }
/** /**
* Resets to wrap the specified data, setting the offset to zero. * Updates the instance to wrap {@code data}, and resets the position to zero.
* *
* @param data The data to wrap. * @param data The array to wrap.
* @param limit The limit to set.
*/ */
public void reset(byte[] data, int limit) { public void reset(byte[] data) {
this.data = data; this.data = data;
this.limit = limit;
byteOffset = 0; byteOffset = 0;
bitOffset = 0; bitOffset = 0;
} }
...@@ -74,89 +60,36 @@ public final class BitArray { ...@@ -74,89 +60,36 @@ public final class BitArray {
} }
/** /**
* Gets the current byte offset. * Gets the current bit offset.
* *
* @return The current byte offset. * @return The current bit offset.
*/ */
public int getByteOffset() { public int getPosition() {
return byteOffset; return byteOffset * 8 + bitOffset;
} }
/** /**
* Sets the current byte offset. * Sets the current bit offset.
* *
* @param byteOffset The byte offset to set. * @param position The position to set.
*/
public void setByteOffset(int byteOffset) {
this.byteOffset = byteOffset;
}
/**
* Appends data from a {@link DataSource}.
*
* @param dataSource The {@link DataSource} from which to read.
* @param length The maximum number of bytes to read and append.
* @return The number of bytes that were read and appended, or -1 if no more data is available.
* @throws IOException If an error occurs reading from the source.
*/
public int append(DataSource dataSource, int length) throws IOException {
expand(length);
int bytesRead = dataSource.read(data, limit, length);
if (bytesRead == -1) {
return -1;
}
limit += bytesRead;
return bytesRead;
}
/**
* Appends data from another {@link BitArray}.
*
* @param bitsArray The {@link BitArray} whose data should be appended.
* @param length The number of bytes to read and append.
*/
public void append(BitArray bitsArray, int length) {
expand(length);
bitsArray.readBytes(data, limit, length);
limit += length;
}
private void expand(int length) {
if (data == null) {
data = new byte[length];
return;
}
if (data.length - limit < length) {
byte[] newBuffer = new byte[limit + length];
System.arraycopy(data, 0, newBuffer, 0, limit);
data = newBuffer;
}
}
/**
* Clears data that has already been read, moving the remaining data to the start of the buffer.
*/ */
public void clearReadData() { public void setPosition(int position) {
System.arraycopy(data, byteOffset, data, 0, limit - byteOffset); byteOffset = position / 8;
limit -= byteOffset; bitOffset = position - (byteOffset * 8);
byteOffset = 0;
} }
/** /**
* Reads a single unsigned byte. * Skips bits and moves current reading position forward.
* *
* @return The value of the parsed byte. * @param n The number of bits to skip.
*/ */
public int readUnsignedByte() { public void skipBits(int n) {
int value; byteOffset += (n / 8);
if (bitOffset != 0) { bitOffset += (n % 8);
value = ((data[byteOffset] & 0xFF) << bitOffset) if (bitOffset > 7) {
| ((data[byteOffset + 1] & 0xFF) >>> (8 - bitOffset));
} else {
value = data[byteOffset];
}
byteOffset++; byteOffset++;
return value & 0xFF; bitOffset -= 8;
}
} }
/** /**
...@@ -220,71 +153,6 @@ public final class BitArray { ...@@ -220,71 +153,6 @@ public final class BitArray {
return retval; return retval;
} }
private int getUnsignedByte(int offset) {
return data[offset] & 0xFF;
}
/**
* 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;
}
}
/**
* Skips bytes and moves current reading position forward.
*
* @param n The number of bytes to skip.
*/
public void skipBytes(int n) {
byteOffset += n;
}
/**
* Reads multiple bytes and copies them into provided byte array.
* <p>
* The read position must be at a whole byte boundary for this method to be called.
*
* @param out The byte array to copy read data.
* @param offset The offset in the out byte array.
* @param length The length of the data to read
* @throws IllegalStateException If the method is called with the read position not at a whole
* byte boundary.
*/
public void readBytes(byte[] out, int offset, int length) {
Assertions.checkState(bitOffset == 0);
System.arraycopy(data, byteOffset, out, offset, length);
byteOffset += length;
}
/**
* @return The number of whole bytes that are available to read.
*/
public int bytesLeft() {
return limit - byteOffset;
}
/**
* @return The limit of the data, specified in whole bytes.
*/
public int limit() {
return limit;
}
/**
* @return Whether or not there is any data available.
*/
public boolean isEmpty() {
return limit == 0;
}
/** /**
* Reads an unsigned Exp-Golomb-coded format integer. * Reads an unsigned Exp-Golomb-coded format integer.
* *
...@@ -304,6 +172,22 @@ public final class BitArray { ...@@ -304,6 +172,22 @@ public final class BitArray {
return ((codeNum % 2) == 0 ? -1 : 1) * ((codeNum + 1) / 2); return ((codeNum % 2) == 0 ? -1 : 1) * ((codeNum + 1) / 2);
} }
private int readUnsignedByte() {
int value;
if (bitOffset != 0) {
value = ((data[byteOffset] & 0xFF) << bitOffset)
| ((data[byteOffset + 1] & 0xFF) >>> (8 - bitOffset));
} else {
value = data[byteOffset];
}
byteOffset++;
return value & 0xFF;
}
private int getUnsignedByte(int offset) {
return data[offset] & 0xFF;
}
private int readExpGolombCodeNum() { private int readExpGolombCodeNum() {
int leadingZeros = 0; int leadingZeros = 0;
while (!readBit()) { while (!readBit()) {
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer.util; package com.google.android.exoplayer.util;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays;
/** /**
* Wraps a byte array, providing a set of methods for parsing data from it. Numerical values are * Wraps a byte array, providing a set of methods for parsing data from it. Numerical values are
...@@ -23,27 +24,82 @@ import java.nio.ByteBuffer; ...@@ -23,27 +24,82 @@ import java.nio.ByteBuffer;
*/ */
public final class ParsableByteArray { public final class ParsableByteArray {
public final byte[] data; public byte[] data;
private int position; private int position;
private int limit;
/** Creates a new instance that initially has no backing data. */
public ParsableByteArray() {}
/** Creates a new instance with {@code length} bytes. */ /** Creates a new instance with {@code length} bytes. */
public ParsableByteArray(int length) { public ParsableByteArray(int length) {
this.data = new byte[length]; this.data = new byte[length];
limit = data.length;
} }
/** /**
* Creates a new instance that wraps an existing array. * Creates a new instance that wraps an existing array.
* *
* @param data The data to wrap. * @param data The data to wrap.
* @param limit The limit.
*/
public ParsableByteArray(byte[] data, int limit) {
this.data = data;
this.limit = limit;
}
/**
* Updates the instance to wrap {@code data}, and resets the position to zero.
*
* @param data The array to wrap.
* @param limit The limit.
*/ */
public ParsableByteArray(byte[] data) { public void reset(byte[] data, int limit) {
this.data = data; this.data = data;
this.limit = limit;
position = 0;
}
/**
* Sets the position and limit to zero.
*/
public void reset() {
position = 0;
limit = 0;
}
// TODO: This method is temporary
public void append(ParsableByteArray buffer, int length) {
if (data == null) {
data = new byte[length];
} else if (data.length < limit + length) {
data = Arrays.copyOf(data, limit + length);
}
buffer.readBytes(data, limit, length);
limit += length;
}
// TODO: This method is temporary
public void clearReadData() {
System.arraycopy(data, position, data, 0, limit - position);
limit -= position;
position = 0;
}
// TODO: This method is temporary
public boolean isEmpty() {
return limit == 0;
}
/** Returns the number of bytes yet to be read. */
public int bytesLeft() {
return limit - position;
} }
/** Returns the number of bytes in the array. */ /** Returns the number of bytes in the array. */
public int length() { public int length() {
return data.length; return limit;
} }
/** Returns the current offset in the array, in bytes. */ /** Returns the current offset in the array, in bytes. */
...@@ -60,7 +116,7 @@ public final class ParsableByteArray { ...@@ -60,7 +116,7 @@ public final class ParsableByteArray {
*/ */
public void setPosition(int position) { public void setPosition(int position) {
// It is fine for position to be at the end of the array. // It is fine for position to be at the end of the array.
Assertions.checkArgument(position >= 0 && position <= data.length); Assertions.checkArgument(position >= 0 && position <= limit);
this.position = position; this.position = position;
} }
...@@ -70,11 +126,27 @@ public final class ParsableByteArray { ...@@ -70,11 +126,27 @@ public final class ParsableByteArray {
* @throws IllegalArgumentException Thrown if the new position is neither in nor at the end of the * @throws IllegalArgumentException Thrown if the new position is neither in nor at the end of the
* array. * array.
*/ */
// TODO: Rename to skipBytes so that it's clearer how much data is being skipped in code where
// both ParsableBitArray and ParsableByteArray are in use.
public void skip(int bytes) { public void skip(int bytes) {
setPosition(position + bytes); setPosition(position + bytes);
} }
/** /**
* Reads the next {@code length} bytes into {@code bitArray}, and resets the position of
* {@code bitArray} to zero.
*
* @param bitArray The {@link ParsableBitArray} into which the bytes should be read.
* @param length The number of bytes to write.
*/
// TODO: It's possible to have bitArray directly index into the same array as is being wrapped
// by this instance. Decide whether it's worth doing this.
public void readBytes(ParsableBitArray bitArray, int length) {
readBytes(bitArray.getData(), 0, length);
bitArray.setPosition(0);
}
/**
* Reads the next {@code length} bytes into {@code buffer} at {@code offset}. * Reads the next {@code length} bytes into {@code buffer} at {@code offset}.
* *
* @see System#arraycopy * @see System#arraycopy
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment