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
...@@ -22,9 +22,10 @@ import com.google.android.exoplayer.mp4.Mp4Util; ...@@ -22,9 +22,10 @@ import com.google.android.exoplayer.mp4.Mp4Util;
import com.google.android.exoplayer.text.eia608.Eia608Parser; import com.google.android.exoplayer.text.eia608.Eia608Parser;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.BitArray;
import com.google.android.exoplayer.util.CodecSpecificDataUtil; import com.google.android.exoplayer.util.CodecSpecificDataUtil;
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 android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.media.MediaExtractor; import android.media.MediaExtractor;
...@@ -58,17 +59,19 @@ public final class TsExtractor { ...@@ -58,17 +59,19 @@ public final class TsExtractor {
private static final long MAX_PTS = 0x1FFFFFFFFL; private static final long MAX_PTS = 0x1FFFFFFFFL;
private final BitArray tsPacketBuffer; private final ParsableByteArray tsPacketBuffer;
private final SparseArray<SampleQueue> sampleQueues; // Indexed by streamType private final SparseArray<SampleQueue> sampleQueues; // Indexed by streamType
private final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid private final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid
private final SamplePool samplePool; private final SamplePool samplePool;
private final boolean shouldSpliceIn; private final boolean shouldSpliceIn;
private final long firstSampleTimestamp; private final long firstSampleTimestamp;
/* package */ final ParsableBitArray scratch;
// Accessed only by the consuming thread. // Accessed only by the consuming thread.
private boolean spliceConfigured; private boolean spliceConfigured;
// Accessed only by the loading thread. // Accessed only by the loading thread.
private int tsPacketBytesRead;
private long timestampOffsetUs; private long timestampOffsetUs;
private long lastPts; private long lastPts;
...@@ -80,7 +83,8 @@ public final class TsExtractor { ...@@ -80,7 +83,8 @@ public final class TsExtractor {
this.firstSampleTimestamp = firstSampleTimestamp; this.firstSampleTimestamp = firstSampleTimestamp;
this.samplePool = samplePool; this.samplePool = samplePool;
this.shouldSpliceIn = shouldSpliceIn; this.shouldSpliceIn = shouldSpliceIn;
tsPacketBuffer = new BitArray(); scratch = new ParsableBitArray(new byte[5]);
tsPacketBuffer = new ParsableByteArray(TS_PACKET_SIZE);
sampleQueues = new SparseArray<SampleQueue>(); sampleQueues = new SparseArray<SampleQueue>();
tsPayloadReaders = new SparseArray<TsPayloadReader>(); tsPayloadReaders = new SparseArray<TsPayloadReader>();
tsPayloadReaders.put(TS_PAT_PID, new PatReader()); tsPayloadReaders.put(TS_PAT_PID, new PatReader());
...@@ -235,42 +239,44 @@ public final class TsExtractor { ...@@ -235,42 +239,44 @@ public final class TsExtractor {
* @return The number of bytes read from the source. * @return The number of bytes read from the source.
*/ */
public int read(DataSource dataSource) throws IOException { public int read(DataSource dataSource) throws IOException {
int read = tsPacketBuffer.append(dataSource, TS_PACKET_SIZE - tsPacketBuffer.bytesLeft()); int bytesRead = dataSource.read(tsPacketBuffer.data, tsPacketBytesRead,
if (read == -1) { TS_PACKET_SIZE - tsPacketBytesRead);
if (bytesRead == -1) {
return -1; return -1;
} }
if (tsPacketBuffer.bytesLeft() != TS_PACKET_SIZE) { tsPacketBytesRead += bytesRead;
return read; if (tsPacketBytesRead < TS_PACKET_SIZE) {
// We haven't read the whole packet yet.
return bytesRead;
} }
// Parse TS header. // Reset before reading the packet.
// Check sync byte. tsPacketBytesRead = 0;
tsPacketBuffer.setPosition(0);
int syncByte = tsPacketBuffer.readUnsignedByte(); int syncByte = tsPacketBuffer.readUnsignedByte();
if (syncByte != TS_SYNC_BYTE) { if (syncByte != TS_SYNC_BYTE) {
return read; return bytesRead;
} }
// Skip transportErrorIndicator. tsPacketBuffer.readBytes(scratch, 3);
tsPacketBuffer.skipBits(1); scratch.skipBits(1); // transport_error_indicator
boolean payloadUnitStartIndicator = tsPacketBuffer.readBit(); boolean payloadUnitStartIndicator = scratch.readBit();
// Skip transportPriority. scratch.skipBits(1); // transport_priority
tsPacketBuffer.skipBits(1); int pid = scratch.readBits(13);
int pid = tsPacketBuffer.readBits(13); scratch.skipBits(2); // transport_scrambling_control
// Skip transport_scrambling_control. boolean adaptationFieldExists = scratch.readBit();
tsPacketBuffer.skipBits(2); boolean payloadExists = scratch.readBit();
boolean adaptationFieldExists = tsPacketBuffer.readBit(); // Last 4 bits of scratch are skipped: continuity_counter
boolean payloadExists = tsPacketBuffer.readBit();
// Skip continuityCounter. // Skip the adaptation field.
tsPacketBuffer.skipBits(4);
// Read the adaptation field.
if (adaptationFieldExists) { if (adaptationFieldExists) {
int adaptationFieldLength = tsPacketBuffer.readBits(8); int adaptationFieldLength = tsPacketBuffer.readUnsignedByte();
tsPacketBuffer.skipBytes(adaptationFieldLength); tsPacketBuffer.skip(adaptationFieldLength);
} }
// Read Payload. // Read the payload.
if (payloadExists) { if (payloadExists) {
TsPayloadReader payloadReader = tsPayloadReaders.get(pid); TsPayloadReader payloadReader = tsPayloadReaders.get(pid);
if (payloadReader != null) { if (payloadReader != null) {
...@@ -282,8 +288,7 @@ public final class TsExtractor { ...@@ -282,8 +288,7 @@ public final class TsExtractor {
prepared = checkPrepared(); prepared = checkPrepared();
} }
tsPacketBuffer.reset(); return bytesRead;
return read;
} }
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
...@@ -331,7 +336,7 @@ public final class TsExtractor { ...@@ -331,7 +336,7 @@ public final class TsExtractor {
*/ */
private abstract static class TsPayloadReader { private abstract static class TsPayloadReader {
public abstract void read(BitArray tsBuffer, boolean payloadUnitStartIndicator); public abstract void read(ParsableByteArray tsBuffer, boolean payloadUnitStartIndicator);
} }
...@@ -341,21 +346,25 @@ public final class TsExtractor { ...@@ -341,21 +346,25 @@ public final class TsExtractor {
private class PatReader extends TsPayloadReader { private class PatReader extends TsPayloadReader {
@Override @Override
public void read(BitArray tsBuffer, boolean payloadUnitStartIndicator) { public void read(ParsableByteArray tsBuffer, boolean payloadUnitStartIndicator) {
// Skip pointer. // Skip pointer.
if (payloadUnitStartIndicator) { if (payloadUnitStartIndicator) {
int pointerField = tsBuffer.readBits(8); int pointerField = tsBuffer.readUnsignedByte();
tsBuffer.skipBytes(pointerField); tsBuffer.skip(pointerField);
} }
tsBuffer.skipBits(12); // 8+1+1+2 tsBuffer.readBytes(scratch, 3);
int sectionLength = tsBuffer.readBits(12); scratch.skipBits(12); // table_id (8), section_syntax_indicator (1), '0' (1), reserved (2)
tsBuffer.skipBits(40); // 16+2+5+1+8+8 int sectionLength = scratch.readBits(12);
// transport_stream_id (16), reserved (2), version_number (5), current_next_indicator (1),
// section_number (8), last_section_number (8)
tsBuffer.skip(5);
int programCount = (sectionLength - 9) / 4; int programCount = (sectionLength - 9) / 4;
for (int i = 0; i < programCount; i++) { for (int i = 0; i < programCount; i++) {
tsBuffer.skipBits(19); tsBuffer.readBytes(scratch, 4);
int pid = tsBuffer.readBits(13); scratch.skipBits(19); // program_number (16), reserved (3)
int pid = scratch.readBits(13);
tsPayloadReaders.put(pid, new PmtReader()); tsPayloadReaders.put(pid, new PmtReader());
} }
...@@ -370,34 +379,41 @@ public final class TsExtractor { ...@@ -370,34 +379,41 @@ public final class TsExtractor {
private class PmtReader extends TsPayloadReader { private class PmtReader extends TsPayloadReader {
@Override @Override
public void read(BitArray tsBuffer, boolean payloadUnitStartIndicator) { public void read(ParsableByteArray tsBuffer, boolean payloadUnitStartIndicator) {
// Skip pointer. // Skip pointer.
if (payloadUnitStartIndicator) { if (payloadUnitStartIndicator) {
int pointerField = tsBuffer.readBits(8); int pointerField = tsBuffer.readUnsignedByte();
tsBuffer.skipBytes(pointerField); tsBuffer.skip(pointerField);
} }
// Skip table_id, section_syntax_indicator, etc. tsBuffer.readBytes(scratch, 3);
tsBuffer.skipBits(12); // 8+1+1+2 scratch.skipBits(12); // table_id (8), section_syntax_indicator (1), '0' (1), reserved (2)
int sectionLength = tsBuffer.readBits(12); int sectionLength = scratch.readBits(12);
// program_number (16), reserved (2), version_number (5), current_next_indicator (1),
// section_number (8), last_section_number (8), reserved (3), PCR_PID (13)
// Skip the rest of the PMT header. // Skip the rest of the PMT header.
tsBuffer.skipBits(60); // 16+2+5+1+8+8+3+13+4 tsBuffer.skip(7);
tsBuffer.readBytes(scratch, 2);
scratch.skipBits(4);
int programInfoLength = scratch.readBits(12);
int programInfoLength = tsBuffer.readBits(12);
// Skip the descriptors. // Skip the descriptors.
tsBuffer.skipBytes(programInfoLength); tsBuffer.skip(programInfoLength);
int entriesSize = sectionLength - 9 /* size of the rest of the fields before descriptors */ int entriesSize = sectionLength - 9 /* Size of the rest of the fields before descriptors */
- programInfoLength - 4 /* CRC size */; - programInfoLength - 4 /* CRC size */;
while (entriesSize > 0) { while (entriesSize > 0) {
int streamType = tsBuffer.readBits(8); tsBuffer.readBytes(scratch, 5);
tsBuffer.skipBits(3); int streamType = scratch.readBits(8);
int elementaryPid = tsBuffer.readBits(13); scratch.skipBits(3); // reserved
tsBuffer.skipBits(4); int elementaryPid = scratch.readBits(13);
scratch.skipBits(4); // reserved
int esInfoLength = scratch.readBits(12);
int esInfoLength = tsBuffer.readBits(12);
// Skip the descriptors. // Skip the descriptors.
tsBuffer.skipBytes(esInfoLength); tsBuffer.skip(esInfoLength);
entriesSize -= esInfoLength + 5; entriesSize -= esInfoLength + 5;
if (sampleQueues.get(streamType) != null) { if (sampleQueues.get(streamType) != null) {
...@@ -436,7 +452,7 @@ public final class TsExtractor { ...@@ -436,7 +452,7 @@ public final class TsExtractor {
private class PesReader extends TsPayloadReader { private class PesReader extends TsPayloadReader {
// Reusable buffer for incomplete PES data. // Reusable buffer for incomplete PES data.
private final BitArray pesBuffer; private final ParsableByteArray pesBuffer;
// Parses PES payload and extracts individual samples. // Parses PES payload and extracts individual samples.
private final PesPayloadReader pesPayloadReader; private final PesPayloadReader pesPayloadReader;
...@@ -445,11 +461,11 @@ public final class TsExtractor { ...@@ -445,11 +461,11 @@ public final class TsExtractor {
public PesReader(PesPayloadReader pesPayloadReader) { public PesReader(PesPayloadReader pesPayloadReader) {
this.pesPayloadReader = pesPayloadReader; this.pesPayloadReader = pesPayloadReader;
this.packetLength = -1; this.packetLength = -1;
pesBuffer = new BitArray(); pesBuffer = new ParsableByteArray();
} }
@Override @Override
public void read(BitArray tsBuffer, boolean payloadUnitStartIndicator) { public void read(ParsableByteArray tsBuffer, boolean payloadUnitStartIndicator) {
if (payloadUnitStartIndicator && !pesBuffer.isEmpty()) { if (payloadUnitStartIndicator && !pesBuffer.isEmpty()) {
if (packetLength == 0) { if (packetLength == 0) {
// The length of the previous packet was unspecified. We've now seen the start of the // The length of the previous packet was unspecified. We've now seen the start of the
...@@ -477,49 +493,50 @@ public final class TsExtractor { ...@@ -477,49 +493,50 @@ public final class TsExtractor {
} }
private void readPacketStart() { private void readPacketStart() {
int startCodePrefix = pesBuffer.readBits(24); pesBuffer.readBytes(scratch, 3);
int startCodePrefix = scratch.readBits(24);
if (startCodePrefix != 0x000001) { if (startCodePrefix != 0x000001) {
Log.w(TAG, "Unexpected start code prefix: " + startCodePrefix); Log.w(TAG, "Unexpected start code prefix: " + startCodePrefix);
pesBuffer.reset(); pesBuffer.reset();
packetLength = -1; packetLength = -1;
} else { } else {
// TODO: Read and use stream_id. pesBuffer.skip(1); // stream_id.
pesBuffer.skipBits(8); // Skip stream_id. packetLength = pesBuffer.readUnsignedShort();
packetLength = pesBuffer.readBits(16);
} }
} }
private void readPacketBody() { private void readPacketBody() {
// Skip some fields/flags. // '10' (2), PES_scrambling_control (2), PES_priority (1), data_alignment_indicator (1),
// TODO: might need to use data_alignment_indicator. // copyright (1), original_or_copy (1)
pesBuffer.skipBits(8); // 2+2+1+1+1+1 pesBuffer.skip(1);
boolean ptsFlag = pesBuffer.readBit();
// Skip DTS flag. pesBuffer.readBytes(scratch, 1);
pesBuffer.skipBits(1); boolean ptsFlag = scratch.readBit();
// Skip some fields/flags. // Last 7 bits of scratch are skipped: DTS_flag (1), ESCR_flag (1), ES_rate_flag (1),
pesBuffer.skipBits(6); // 1+1+1+1+1+1 // DSM_trick_mode_flag (1), additional_copy_info_flag (1), PES_CRC_flag (1),
// PES_extension_flag (1)
int headerDataLength = pesBuffer.readBits(8);
int headerDataLength = pesBuffer.readUnsignedByte();
if (headerDataLength == 0) { if (headerDataLength == 0) {
headerDataLength = pesBuffer.bytesLeft(); headerDataLength = pesBuffer.bytesLeft();
} }
long timeUs = 0; long timeUs = 0;
if (ptsFlag) { if (ptsFlag) {
// Skip prefix. pesBuffer.readBytes(scratch, 5);
pesBuffer.skipBits(4); scratch.skipBits(4); // '0010'
long pts = pesBuffer.readBitsLong(3) << 30; long pts = scratch.readBitsLong(3) << 30;
pesBuffer.skipBits(1); scratch.skipBits(1); // marker_bit
pts |= pesBuffer.readBitsLong(15) << 15; pts |= scratch.readBitsLong(15) << 15;
pesBuffer.skipBits(1); scratch.skipBits(1); // marker_bit
pts |= pesBuffer.readBitsLong(15); pts |= scratch.readBitsLong(15);
pesBuffer.skipBits(1); scratch.skipBits(1); // marker_bit
timeUs = ptsToTimeUs(pts); timeUs = ptsToTimeUs(pts);
// Skip the rest of the header. // Skip the rest of the header.
pesBuffer.skipBytes(headerDataLength - 5); pesBuffer.skip(headerDataLength - 5);
} else { } else {
// Skip the rest of the header. // Skip the rest of the header.
pesBuffer.skipBytes(headerDataLength); pesBuffer.skip(headerDataLength);
} }
int payloadSize; int payloadSize;
...@@ -709,7 +726,7 @@ public final class TsExtractor { ...@@ -709,7 +726,7 @@ public final class TsExtractor {
* @param sampleTimeUs The sample time stamp. * @param sampleTimeUs The sample time stamp.
* @param isKeyframe True if the sample is a keyframe. False otherwise. * @param isKeyframe True if the sample is a keyframe. False otherwise.
*/ */
protected void addSample(int type, BitArray buffer, int sampleSize, long sampleTimeUs, protected void addSample(int type, ParsableByteArray buffer, int sampleSize, long sampleTimeUs,
boolean isKeyframe) { boolean isKeyframe) {
Sample sample = getSample(type); Sample sample = getSample(type);
addToSample(sample, buffer, sampleSize); addToSample(sample, buffer, sampleSize);
...@@ -723,7 +740,7 @@ public final class TsExtractor { ...@@ -723,7 +740,7 @@ public final class TsExtractor {
internalQueueSample(sample); internalQueueSample(sample);
} }
protected void addToSample(Sample sample, BitArray buffer, int size) { protected void addToSample(Sample sample, ParsableByteArray buffer, int size) {
if (sample.data.length - sample.size < size) { if (sample.data.length - sample.size < size) {
sample.expand(size - sample.data.length + sample.size); sample.expand(size - sample.data.length + sample.size);
} }
...@@ -764,7 +781,7 @@ public final class TsExtractor { ...@@ -764,7 +781,7 @@ public final class TsExtractor {
internalQueue.add(sample); internalQueue.add(sample);
} }
public abstract void read(BitArray pesBuffer, int pesPayloadSize, long pesTimeUs); public abstract void read(ParsableByteArray pesBuffer, int pesPayloadSize, long pesTimeUs);
} }
...@@ -798,7 +815,7 @@ public final class TsExtractor { ...@@ -798,7 +815,7 @@ public final class TsExtractor {
} }
@Override @Override
public void read(BitArray pesBuffer, int pesPayloadSize, long pesTimeUs) { public void read(ParsableByteArray pesBuffer, int pesPayloadSize, long pesTimeUs) {
// Read leftover frame data from previous PES packet. // Read leftover frame data from previous PES packet.
pesPayloadSize -= readOneH264Frame(pesBuffer, true); pesPayloadSize -= readOneH264Frame(pesBuffer, true);
...@@ -824,10 +841,10 @@ public final class TsExtractor { ...@@ -824,10 +841,10 @@ public final class TsExtractor {
} }
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
private int readOneH264Frame(BitArray pesBuffer, boolean remainderOnly) { private int readOneH264Frame(ParsableByteArray pesBuffer, boolean remainderOnly) {
byte[] pesData = pesBuffer.getData(); byte[] pesData = pesBuffer.data;
int pesOffset = pesBuffer.getByteOffset(); int pesOffset = pesBuffer.getPosition();
int pesLimit = pesBuffer.limit(); int pesLimit = pesBuffer.length();
int searchOffset = pesOffset + (remainderOnly ? 0 : 3); int searchOffset = pesOffset + (remainderOnly ? 0 : 3);
int audOffset = Mp4Util.findNalUnit(pesData, searchOffset, pesLimit, NAL_UNIT_TYPE_AUD); int audOffset = Mp4Util.findNalUnit(pesData, searchOffset, pesLimit, NAL_UNIT_TYPE_AUD);
...@@ -839,7 +856,7 @@ public final class TsExtractor { ...@@ -839,7 +856,7 @@ public final class TsExtractor {
} }
addToSample(currentSample, pesBuffer, bytesToNextAud); addToSample(currentSample, pesBuffer, bytesToNextAud);
} else { } else {
pesBuffer.skipBytes(bytesToNextAud); pesBuffer.skip(bytesToNextAud);
} }
return bytesToNextAud; return bytesToNextAud;
} }
...@@ -854,8 +871,8 @@ public final class TsExtractor { ...@@ -854,8 +871,8 @@ public final class TsExtractor {
return; return;
} }
// Determine the length of the units, and copy them to build the initialization data. // Determine the length of the units, and copy them to build the initialization data.
int spsLength = Mp4Util.findNextNalUnit(sampleData, spsOffset + 3, sampleSize) - spsOffset; int spsLength = Mp4Util.findNalUnit(sampleData, spsOffset + 3, sampleSize) - spsOffset;
int ppsLength = Mp4Util.findNextNalUnit(sampleData, ppsOffset + 3, sampleSize) - ppsOffset; int ppsLength = Mp4Util.findNalUnit(sampleData, ppsOffset + 3, sampleSize) - ppsOffset;
byte[] spsData = new byte[spsLength]; byte[] spsData = new byte[spsLength];
byte[] ppsData = new byte[ppsLength]; byte[] ppsData = new byte[ppsLength];
System.arraycopy(sampleData, spsOffset, spsData, 0, spsLength); System.arraycopy(sampleData, spsOffset, spsData, 0, spsLength);
...@@ -866,15 +883,11 @@ public final class TsExtractor { ...@@ -866,15 +883,11 @@ public final class TsExtractor {
// Unescape and then parse the SPS unit. // Unescape and then parse the SPS unit.
byte[] unescapedSps = unescapeStream(spsData, 0, spsLength); byte[] unescapedSps = unescapeStream(spsData, 0, spsLength);
BitArray bitArray = new BitArray(unescapedSps, unescapedSps.length); ParsableBitArray bitArray = new ParsableBitArray(unescapedSps);
bitArray.skipBits(32); // NAL header
// Skip the NAL header.
bitArray.skipBytes(4);
int profileIdc = bitArray.readBits(8); int profileIdc = bitArray.readBits(8);
// Skip 6 constraint bits, 2 reserved bits and level_idc. bitArray.skipBits(16); // constraint bits (6), reserved (2) and level_idc (8)
bitArray.skipBytes(2); bitArray.readUnsignedExpGolombCodedInt(); // seq_parameter_set_id
// Skip seq_parameter_set_id.
bitArray.readUnsignedExpGolombCodedInt();
int chromaFormatIdc = 1; // Default is 4:2:0 int chromaFormatIdc = 1; // Default is 4:2:0
if (profileIdc == 100 || profileIdc == 110 || profileIdc == 122 || profileIdc == 244 if (profileIdc == 100 || profileIdc == 110 || profileIdc == 122 || profileIdc == 244
...@@ -882,15 +895,11 @@ public final class TsExtractor { ...@@ -882,15 +895,11 @@ public final class TsExtractor {
|| profileIdc == 128 || profileIdc == 138) { || profileIdc == 128 || profileIdc == 138) {
chromaFormatIdc = bitArray.readUnsignedExpGolombCodedInt(); chromaFormatIdc = bitArray.readUnsignedExpGolombCodedInt();
if (chromaFormatIdc == 3) { if (chromaFormatIdc == 3) {
// Skip separate_colour_plane_flag bitArray.skipBits(1); // separate_colour_plane_flag
bitArray.skipBits(1); }
} bitArray.readUnsignedExpGolombCodedInt(); // bit_depth_luma_minus8
// Skip bit_depth_luma_minus8 bitArray.readUnsignedExpGolombCodedInt(); // bit_depth_chroma_minus8
bitArray.readUnsignedExpGolombCodedInt(); bitArray.skipBits(1); // qpprime_y_zero_transform_bypass_flag
// Skip bit_depth_chroma_minus8
bitArray.readUnsignedExpGolombCodedInt();
// Skip qpprime_y_zero_transform_bypass_flag
bitArray.skipBits(1);
boolean seqScalingMatrixPresentFlag = bitArray.readBit(); boolean seqScalingMatrixPresentFlag = bitArray.readBit();
if (seqScalingMatrixPresentFlag) { if (seqScalingMatrixPresentFlag) {
int limit = (chromaFormatIdc != 3) ? 8 : 12; int limit = (chromaFormatIdc != 3) ? 8 : 12;
...@@ -902,39 +911,32 @@ public final class TsExtractor { ...@@ -902,39 +911,32 @@ public final class TsExtractor {
} }
} }
} }
// Skip log2_max_frame_num_minus4
bitArray.readUnsignedExpGolombCodedInt(); bitArray.readUnsignedExpGolombCodedInt(); // log2_max_frame_num_minus4
long picOrderCntType = bitArray.readUnsignedExpGolombCodedInt(); long picOrderCntType = bitArray.readUnsignedExpGolombCodedInt();
if (picOrderCntType == 0) { if (picOrderCntType == 0) {
// Skip log2_max_pic_order_cnt_lsb_minus4 bitArray.readUnsignedExpGolombCodedInt(); // log2_max_pic_order_cnt_lsb_minus4
bitArray.readUnsignedExpGolombCodedInt();
} else if (picOrderCntType == 1) { } else if (picOrderCntType == 1) {
// Skip delta_pic_order_always_zero_flag bitArray.skipBits(1); // delta_pic_order_always_zero_flag
bitArray.skipBits(1); bitArray.readSignedExpGolombCodedInt(); // offset_for_non_ref_pic
// Skip offset_for_non_ref_pic bitArray.readSignedExpGolombCodedInt(); // offset_for_top_to_bottom_field
bitArray.readSignedExpGolombCodedInt();
// Skip offset_for_top_to_bottom_field
bitArray.readSignedExpGolombCodedInt();
long numRefFramesInPicOrderCntCycle = bitArray.readUnsignedExpGolombCodedInt(); long numRefFramesInPicOrderCntCycle = bitArray.readUnsignedExpGolombCodedInt();
for (int i = 0; i < numRefFramesInPicOrderCntCycle; i++) { for (int i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
// Skip offset_for_ref_frame[i] bitArray.readUnsignedExpGolombCodedInt(); // offset_for_ref_frame[i]
bitArray.readUnsignedExpGolombCodedInt();
} }
} }
// Skip max_num_ref_frames bitArray.readUnsignedExpGolombCodedInt(); // max_num_ref_frames
bitArray.readUnsignedExpGolombCodedInt(); bitArray.skipBits(1); // gaps_in_frame_num_value_allowed_flag
// Skip gaps_in_frame_num_value_allowed_flag
bitArray.skipBits(1);
int picWidthInMbs = bitArray.readUnsignedExpGolombCodedInt() + 1; int picWidthInMbs = bitArray.readUnsignedExpGolombCodedInt() + 1;
int picHeightInMapUnits = bitArray.readUnsignedExpGolombCodedInt() + 1; int picHeightInMapUnits = bitArray.readUnsignedExpGolombCodedInt() + 1;
boolean frameMbsOnlyFlag = bitArray.readBit(); boolean frameMbsOnlyFlag = bitArray.readBit();
int frameHeightInMbs = (2 - (frameMbsOnlyFlag ? 1 : 0)) * picHeightInMapUnits; int frameHeightInMbs = (2 - (frameMbsOnlyFlag ? 1 : 0)) * picHeightInMapUnits;
if (!frameMbsOnlyFlag) { if (!frameMbsOnlyFlag) {
// Skip mb_adaptive_frame_field_flag bitArray.skipBits(1); // mb_adaptive_frame_field_flag
bitArray.skipBits(1);
} }
// Skip direct_8x8_inference_flag
bitArray.skipBits(1); bitArray.skipBits(1); // direct_8x8_inference_flag
int frameWidth = picWidthInMbs * 16; int frameWidth = picWidthInMbs * 16;
int frameHeight = frameHeightInMbs * 16; int frameHeight = frameHeightInMbs * 16;
boolean frameCroppingFlag = bitArray.readBit(); boolean frameCroppingFlag = bitArray.readBit();
...@@ -962,7 +964,7 @@ public final class TsExtractor { ...@@ -962,7 +964,7 @@ public final class TsExtractor {
frameWidth, frameHeight, initializationData)); frameWidth, frameHeight, initializationData));
} }
private void skipScalingList(BitArray bitArray, int size) { private void skipScalingList(ParsableBitArray bitArray, int size) {
int lastScale = 8; int lastScale = 8;
int nextScale = 8; int nextScale = 8;
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
...@@ -1029,13 +1031,13 @@ public final class TsExtractor { ...@@ -1029,13 +1031,13 @@ public final class TsExtractor {
// SEI data, used for Closed Captions. // SEI data, used for Closed Captions.
private static final int NAL_UNIT_TYPE_SEI = 6; private static final int NAL_UNIT_TYPE_SEI = 6;
private final BitArray seiBuffer; private final ParsableByteArray seiBuffer;
private final TreeSet<Sample> internalQueue; private final TreeSet<Sample> internalQueue;
public SeiReader(SamplePool samplePool) { public SeiReader(SamplePool samplePool) {
super(samplePool); super(samplePool);
setMediaFormat(MediaFormat.createEia608Format()); setMediaFormat(MediaFormat.createEia608Format());
seiBuffer = new BitArray(); seiBuffer = new ParsableByteArray();
internalQueue = new TreeSet<Sample>(this); internalQueue = new TreeSet<Sample>(this);
} }
...@@ -1043,12 +1045,12 @@ public final class TsExtractor { ...@@ -1043,12 +1045,12 @@ public final class TsExtractor {
public void read(byte[] data, int length, long pesTimeUs) { public void read(byte[] data, int length, long pesTimeUs) {
seiBuffer.reset(data, length); seiBuffer.reset(data, length);
while (seiBuffer.bytesLeft() > 0) { while (seiBuffer.bytesLeft() > 0) {
int currentOffset = seiBuffer.getByteOffset(); int currentOffset = seiBuffer.getPosition();
int seiOffset = Mp4Util.findNalUnit(data, currentOffset, length, NAL_UNIT_TYPE_SEI); int seiOffset = Mp4Util.findNalUnit(data, currentOffset, length, NAL_UNIT_TYPE_SEI);
if (seiOffset == length) { if (seiOffset == length) {
return; return;
} }
seiBuffer.skipBytes(seiOffset + 4 - currentOffset); seiBuffer.skip(seiOffset + 4 - currentOffset);
int ccDataSize = Eia608Parser.parseHeader(seiBuffer); int ccDataSize = Eia608Parser.parseHeader(seiBuffer);
if (ccDataSize > 0) { if (ccDataSize > 0) {
addSample(Sample.TYPE_MISC, seiBuffer, ccDataSize, pesTimeUs, true); addSample(Sample.TYPE_MISC, seiBuffer, ccDataSize, pesTimeUs, true);
...@@ -1084,17 +1086,17 @@ public final class TsExtractor { ...@@ -1084,17 +1086,17 @@ public final class TsExtractor {
*/ */
private class AdtsReader extends PesPayloadReader { private class AdtsReader extends PesPayloadReader {
private final BitArray adtsBuffer; private final ParsableByteArray adtsBuffer;
private long timeUs; private long timeUs;
private long frameDurationUs; private long frameDurationUs;
public AdtsReader(SamplePool samplePool) { public AdtsReader(SamplePool samplePool) {
super(samplePool); super(samplePool);
adtsBuffer = new BitArray(); adtsBuffer = new ParsableByteArray();
} }
@Override @Override
public void read(BitArray pesBuffer, int pesPayloadSize, long pesTimeUs) { public void read(ParsableByteArray pesBuffer, int pesPayloadSize, long pesTimeUs) {
boolean needToProcessLeftOvers = !adtsBuffer.isEmpty(); boolean needToProcessLeftOvers = !adtsBuffer.isEmpty();
adtsBuffer.append(pesBuffer, pesPayloadSize); adtsBuffer.append(pesBuffer, pesPayloadSize);
// If there are leftovers from previous PES packet, process it with last calculated timeUs. // If there are leftovers from previous PES packet, process it with last calculated timeUs.
...@@ -1114,24 +1116,26 @@ public final class TsExtractor { ...@@ -1114,24 +1116,26 @@ public final class TsExtractor {
} }
int offsetToSyncWord = findOffsetToSyncWord(); int offsetToSyncWord = findOffsetToSyncWord();
adtsBuffer.skipBytes(offsetToSyncWord); adtsBuffer.skip(offsetToSyncWord);
int adtsStartOffset = adtsBuffer.getByteOffset(); int adtsStartOffset = adtsBuffer.getPosition();
if (adtsBuffer.bytesLeft() < 7) { if (adtsBuffer.bytesLeft() < 7) {
adtsBuffer.setByteOffset(adtsStartOffset); adtsBuffer.setPosition(adtsStartOffset);
adtsBuffer.clearReadData(); adtsBuffer.clearReadData();
return false; return false;
} }
adtsBuffer.skipBits(15); adtsBuffer.readBytes(scratch, 2);
boolean hasCRC = !adtsBuffer.readBit(); scratch.skipBits(15);
boolean hasCRC = !scratch.readBit();
adtsBuffer.readBytes(scratch, 5);
if (!hasMediaFormat()) { if (!hasMediaFormat()) {
int audioObjectType = adtsBuffer.readBits(2) + 1; int audioObjectType = scratch.readBits(2) + 1;
int sampleRateIndex = adtsBuffer.readBits(4); int sampleRateIndex = scratch.readBits(4);
adtsBuffer.skipBits(1); scratch.skipBits(1);
int channelConfig = adtsBuffer.readBits(3); int channelConfig = scratch.readBits(3);
byte[] audioSpecificConfig = CodecSpecificDataUtil.buildAudioSpecificConfig( byte[] audioSpecificConfig = CodecSpecificDataUtil.buildAudioSpecificConfig(
audioObjectType, sampleRateIndex, channelConfig); audioObjectType, sampleRateIndex, channelConfig);
...@@ -1144,24 +1148,23 @@ public final class TsExtractor { ...@@ -1144,24 +1148,23 @@ public final class TsExtractor {
frameDurationUs = (C.MICROS_PER_SECOND * 1024L) / mediaFormat.sampleRate; frameDurationUs = (C.MICROS_PER_SECOND * 1024L) / mediaFormat.sampleRate;
setMediaFormat(mediaFormat); setMediaFormat(mediaFormat);
} else { } else {
adtsBuffer.skipBits(10); scratch.skipBits(10);
} }
scratch.skipBits(4);
adtsBuffer.skipBits(4); int frameSize = scratch.readBits(13);
int frameSize = adtsBuffer.readBits(13); scratch.skipBits(13);
adtsBuffer.skipBits(13);
// Decrement frame size by ADTS header size and CRC. // Decrement frame size by ADTS header size and CRC.
if (hasCRC) { if (hasCRC) {
// Skip CRC. // Skip CRC.
adtsBuffer.skipBytes(2); adtsBuffer.skip(2);
frameSize -= 9; frameSize -= 9;
} else { } else {
frameSize -= 7; frameSize -= 7;
} }
if (frameSize > adtsBuffer.bytesLeft()) { if (frameSize > adtsBuffer.bytesLeft()) {
adtsBuffer.setByteOffset(adtsStartOffset); adtsBuffer.setPosition(adtsStartOffset);
adtsBuffer.clearReadData(); adtsBuffer.clearReadData();
return false; return false;
} }
...@@ -1183,9 +1186,9 @@ public final class TsExtractor { ...@@ -1183,9 +1186,9 @@ public final class TsExtractor {
* position of the end of the data is returned. * position of the end of the data is returned.
*/ */
private int findOffsetToSyncWord() { private int findOffsetToSyncWord() {
byte[] adtsData = adtsBuffer.getData(); byte[] adtsData = adtsBuffer.data;
int startOffset = adtsBuffer.getByteOffset(); int startOffset = adtsBuffer.getPosition();
int endOffset = startOffset + adtsBuffer.bytesLeft(); int endOffset = adtsBuffer.length();
for (int i = startOffset; i < endOffset - 1; i++) { for (int i = startOffset; i < endOffset - 1; i++) {
int syncBits = ((adtsData[i] & 0xFF) << 8) | (adtsData[i + 1] & 0xFF); int syncBits = ((adtsData[i] & 0xFF) << 8) | (adtsData[i + 1] & 0xFF);
if ((syncBits & 0xFFF0) == 0xFFF0 && syncBits != 0xFFFF) { if ((syncBits & 0xFFF0) == 0xFFF0 && syncBits != 0xFFFF) {
...@@ -1209,7 +1212,7 @@ public final class TsExtractor { ...@@ -1209,7 +1212,7 @@ public final class TsExtractor {
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
@Override @Override
public void read(BitArray pesBuffer, int pesPayloadSize, long pesTimeUs) { public void read(ParsableByteArray pesBuffer, int pesPayloadSize, long pesTimeUs) {
addSample(Sample.TYPE_MISC, pesBuffer, pesPayloadSize, pesTimeUs, true); addSample(Sample.TYPE_MISC, pesBuffer, pesPayloadSize, pesTimeUs, true);
} }
......
...@@ -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