Remove intermediate copy steps in TsExtractor.

1. AdtsReader would previously copy all data through an intermediate
adtsBuffer. This change eliminates the additional copy step, and
instead copies directly into Sample objects.

2. PesReader would previously accumulate a whole packet by copying
multiple TS packets into an intermediate buffer. This change
eliminates this copy step. After the change, TS packet buffers
are propagated directly to PesPayloadReaders, which are required
to handle partial payload data correctly. The copy steps in the
extractor are simplified from:

DataSource->Ts_BitArray->Pes_BitArray->Sample->SampleHolder

To:

DataSource->Ts_BitArray->Sample->SampleHolder

Issue: #278
This commit is contained in:
Oliver Woodman 2015-02-11 14:57:07 +00:00
parent 797fa7f872
commit 92f085bc58
2 changed files with 425 additions and 267 deletions

View file

@ -65,7 +65,7 @@ public final class TsExtractor {
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; private final ParsableBitArray tsScratch;
// Accessed only by the consuming thread. // Accessed only by the consuming thread.
private boolean spliceConfigured; private boolean spliceConfigured;
@ -83,7 +83,7 @@ public final class TsExtractor {
this.firstSampleTimestamp = firstSampleTimestamp; this.firstSampleTimestamp = firstSampleTimestamp;
this.samplePool = samplePool; this.samplePool = samplePool;
this.shouldSpliceIn = shouldSpliceIn; this.shouldSpliceIn = shouldSpliceIn;
scratch = new ParsableBitArray(new byte[5]); tsScratch = new ParsableBitArray(new byte[3]);
tsPacketBuffer = new ParsableByteArray(TS_PACKET_SIZE); tsPacketBuffer = new ParsableByteArray(TS_PACKET_SIZE);
sampleQueues = new SparseArray<SampleQueue>(); sampleQueues = new SparseArray<SampleQueue>();
tsPayloadReaders = new SparseArray<TsPayloadReader>(); tsPayloadReaders = new SparseArray<TsPayloadReader>();
@ -254,20 +254,21 @@ public final class TsExtractor {
// Reset before reading the packet. // Reset before reading the packet.
tsPacketBytesRead = 0; tsPacketBytesRead = 0;
tsPacketBuffer.setPosition(0); tsPacketBuffer.setPosition(0);
tsPacketBuffer.setLimit(TS_PACKET_SIZE);
int syncByte = tsPacketBuffer.readUnsignedByte(); int syncByte = tsPacketBuffer.readUnsignedByte();
if (syncByte != TS_SYNC_BYTE) { if (syncByte != TS_SYNC_BYTE) {
return bytesRead; return bytesRead;
} }
tsPacketBuffer.readBytes(scratch, 3); tsPacketBuffer.readBytes(tsScratch, 3);
scratch.skipBits(1); // transport_error_indicator tsScratch.skipBits(1); // transport_error_indicator
boolean payloadUnitStartIndicator = scratch.readBit(); boolean payloadUnitStartIndicator = tsScratch.readBit();
scratch.skipBits(1); // transport_priority tsScratch.skipBits(1); // transport_priority
int pid = scratch.readBits(13); int pid = tsScratch.readBits(13);
scratch.skipBits(2); // transport_scrambling_control tsScratch.skipBits(2); // transport_scrambling_control
boolean adaptationFieldExists = scratch.readBit(); boolean adaptationFieldExists = tsScratch.readBit();
boolean payloadExists = scratch.readBit(); boolean payloadExists = tsScratch.readBit();
// Last 4 bits of scratch are skipped: continuity_counter // Last 4 bits of scratch are skipped: continuity_counter
// Skip the adaptation field. // Skip the adaptation field.
@ -280,7 +281,7 @@ public final class TsExtractor {
if (payloadExists) { if (payloadExists) {
TsPayloadReader payloadReader = tsPayloadReaders.get(pid); TsPayloadReader payloadReader = tsPayloadReaders.get(pid);
if (payloadReader != null) { if (payloadReader != null) {
payloadReader.read(tsPacketBuffer, payloadUnitStartIndicator); payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator);
} }
} }
@ -332,11 +333,11 @@ public final class TsExtractor {
} }
/** /**
* Parses payload data. * Parses TS packet payload data.
*/ */
private abstract static class TsPayloadReader { private abstract static class TsPayloadReader {
public abstract void read(ParsableByteArray tsBuffer, boolean payloadUnitStartIndicator); public abstract void consume(ParsableByteArray data, boolean payloadUnitStartIndicator);
} }
@ -345,26 +346,32 @@ public final class TsExtractor {
*/ */
private class PatReader extends TsPayloadReader { private class PatReader extends TsPayloadReader {
private final ParsableBitArray patScratch;
public PatReader() {
patScratch = new ParsableBitArray(new byte[4]);
}
@Override @Override
public void read(ParsableByteArray tsBuffer, boolean payloadUnitStartIndicator) { public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) {
// Skip pointer. // Skip pointer.
if (payloadUnitStartIndicator) { if (payloadUnitStartIndicator) {
int pointerField = tsBuffer.readUnsignedByte(); int pointerField = data.readUnsignedByte();
tsBuffer.skip(pointerField); data.skip(pointerField);
} }
tsBuffer.readBytes(scratch, 3); data.readBytes(patScratch, 3);
scratch.skipBits(12); // table_id (8), section_syntax_indicator (1), '0' (1), reserved (2) patScratch.skipBits(12); // table_id (8), section_syntax_indicator (1), '0' (1), reserved (2)
int sectionLength = scratch.readBits(12); int sectionLength = patScratch.readBits(12);
// transport_stream_id (16), reserved (2), version_number (5), current_next_indicator (1), // transport_stream_id (16), reserved (2), version_number (5), current_next_indicator (1),
// section_number (8), last_section_number (8) // section_number (8), last_section_number (8)
tsBuffer.skip(5); data.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.readBytes(scratch, 4); data.readBytes(patScratch, 4);
scratch.skipBits(19); // program_number (16), reserved (3) patScratch.skipBits(19); // program_number (16), reserved (3)
int pid = scratch.readBits(13); int pid = patScratch.readBits(13);
tsPayloadReaders.put(pid, new PmtReader()); tsPayloadReaders.put(pid, new PmtReader());
} }
@ -378,42 +385,48 @@ public final class TsExtractor {
*/ */
private class PmtReader extends TsPayloadReader { private class PmtReader extends TsPayloadReader {
private final ParsableBitArray pmtScratch;
public PmtReader() {
pmtScratch = new ParsableBitArray(new byte[5]);
}
@Override @Override
public void read(ParsableByteArray tsBuffer, boolean payloadUnitStartIndicator) { public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) {
// Skip pointer. // Skip pointer.
if (payloadUnitStartIndicator) { if (payloadUnitStartIndicator) {
int pointerField = tsBuffer.readUnsignedByte(); int pointerField = data.readUnsignedByte();
tsBuffer.skip(pointerField); data.skip(pointerField);
} }
tsBuffer.readBytes(scratch, 3); data.readBytes(pmtScratch, 3);
scratch.skipBits(12); // table_id (8), section_syntax_indicator (1), '0' (1), reserved (2) pmtScratch.skipBits(12); // table_id (8), section_syntax_indicator (1), '0' (1), reserved (2)
int sectionLength = scratch.readBits(12); int sectionLength = pmtScratch.readBits(12);
// program_number (16), reserved (2), version_number (5), current_next_indicator (1), // program_number (16), reserved (2), version_number (5), current_next_indicator (1),
// section_number (8), last_section_number (8), reserved (3), PCR_PID (13) // 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.skip(7); data.skip(7);
tsBuffer.readBytes(scratch, 2); data.readBytes(pmtScratch, 2);
scratch.skipBits(4); pmtScratch.skipBits(4);
int programInfoLength = scratch.readBits(12); int programInfoLength = pmtScratch.readBits(12);
// Skip the descriptors. // Skip the descriptors.
tsBuffer.skip(programInfoLength); data.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) {
tsBuffer.readBytes(scratch, 5); data.readBytes(pmtScratch, 5);
int streamType = scratch.readBits(8); int streamType = pmtScratch.readBits(8);
scratch.skipBits(3); // reserved pmtScratch.skipBits(3); // reserved
int elementaryPid = scratch.readBits(13); int elementaryPid = pmtScratch.readBits(13);
scratch.skipBits(4); // reserved pmtScratch.skipBits(4); // reserved
int esInfoLength = scratch.readBits(12); int esInfoLength = pmtScratch.readBits(12);
// Skip the descriptors. // Skip the descriptors.
tsBuffer.skip(esInfoLength); data.skip(esInfoLength);
entriesSize -= esInfoLength + 5; entriesSize -= esInfoLength + 5;
if (sampleQueues.get(streamType) != null) { if (sampleQueues.get(streamType) != null) {
@ -451,105 +464,172 @@ public final class TsExtractor {
*/ */
private class PesReader extends TsPayloadReader { private class PesReader extends TsPayloadReader {
// Reusable buffer for incomplete PES data. private static final int STATE_FINDING_HEADER = 0;
private final ParsableByteArray pesBuffer; private static final int STATE_READING_HEADER = 1;
// Parses PES payload and extracts individual samples. private static final int STATE_READING_HEADER_EXTENSION = 2;
private static final int STATE_READING_BODY = 3;
private static final int HEADER_SIZE = 9;
private static final int MAX_HEADER_EXTENSION_SIZE = 5;
private final ParsableBitArray pesScratch;
private final PesPayloadReader pesPayloadReader; private final PesPayloadReader pesPayloadReader;
private int packetLength; private int state;
private int bytesRead;
private boolean bodyStarted;
private boolean ptsFlag;
private int extendedHeaderLength;
private int payloadSize;
private long timeUs;
public PesReader(PesPayloadReader pesPayloadReader) { public PesReader(PesPayloadReader pesPayloadReader) {
this.pesPayloadReader = pesPayloadReader; this.pesPayloadReader = pesPayloadReader;
this.packetLength = -1; pesScratch = new ParsableBitArray(new byte[HEADER_SIZE]);
pesBuffer = new ParsableByteArray(); state = STATE_FINDING_HEADER;
} }
@Override @Override
public void read(ParsableByteArray tsBuffer, boolean payloadUnitStartIndicator) { public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) {
if (payloadUnitStartIndicator && !pesBuffer.isEmpty()) { if (payloadUnitStartIndicator) {
if (packetLength == 0) { switch (state) {
// The length of the previous packet was unspecified. We've now seen the start of the case STATE_FINDING_HEADER:
// next one, so consume the previous packet's body. case STATE_READING_HEADER:
readPacketBody(); // Expected.
} else { break;
// Either we didn't have enough data to read the length of the previous packet, or we case STATE_READING_HEADER_EXTENSION:
// did read the length but didn't receive that amount of data. Neither case is expected. Log.w(TAG, "Unexpected start indicator reading extended header");
Log.w(TAG, "Unexpected packet fragment of length " + pesBuffer.bytesLeft()); break;
pesBuffer.reset(); case STATE_READING_BODY:
packetLength = -1; // If payloadSize == -1 then the length of the previous packet was unspecified, and so
// we only know that it's finished now that we've seen the start of the next one. This
// is expected. If payloadSize != -1, then the length of the previous packet was known,
// but we didn't receive that amount of data. This is not expected.
if (payloadSize != -1) {
Log.w(TAG, "Unexpected start indicator: expected " + payloadSize + " more bytes");
}
// Either way, if the body was started, notify the reader that it has now finished.
if (bodyStarted) {
pesPayloadReader.packetFinished();
}
break;
}
setState(STATE_READING_HEADER);
}
while (data.bytesLeft() > 0) {
switch (state) {
case STATE_FINDING_HEADER:
data.skip(data.bytesLeft());
break;
case STATE_READING_HEADER:
if (continueRead(data, pesScratch.getData(), HEADER_SIZE)) {
setState(parseHeader() ? STATE_READING_HEADER_EXTENSION : STATE_FINDING_HEADER);
}
break;
case STATE_READING_HEADER_EXTENSION:
int readLength = Math.min(MAX_HEADER_EXTENSION_SIZE, extendedHeaderLength);
// Read as much of the extended header as we're interested in, and skip the rest.
if (continueRead(data, pesScratch.getData(), readLength)
&& continueRead(data, null, extendedHeaderLength)) {
parseHeaderExtension();
bodyStarted = false;
setState(STATE_READING_BODY);
}
break;
case STATE_READING_BODY:
readLength = data.bytesLeft();
int padding = payloadSize == -1 ? 0 : readLength - payloadSize;
if (padding > 0) {
readLength -= padding;
data.setLimit(data.getPosition() + readLength);
}
pesPayloadReader.consume(data, timeUs, !bodyStarted);
bodyStarted = true;
if (payloadSize != -1) {
payloadSize -= readLength;
if (payloadSize == 0) {
pesPayloadReader.packetFinished();
setState(STATE_READING_HEADER);
}
}
break;
} }
} }
pesBuffer.append(tsBuffer, tsBuffer.bytesLeft());
if (packetLength == -1 && pesBuffer.bytesLeft() >= 6) {
// We haven't read the start of the packet, but have enough data to do so.
readPacketStart();
}
if (packetLength > 0 && pesBuffer.bytesLeft() >= packetLength) {
// The packet length was specified and we now have the whole packet. Read it.
readPacketBody();
}
} }
private void readPacketStart() { private void setState(int state) {
pesBuffer.readBytes(scratch, 3); this.state = state;
int startCodePrefix = scratch.readBits(24); bytesRead = 0;
}
/**
* Continues a read from the provided {@code source} into a given {@code target}. It's assumed
* that the data should be written into {@code target} starting from an offset of zero.
*
* @param source The source from which to read.
* @param target The target into which data is to be read, or {@code null} to skip.
* @param targetLength The target length of the read.
* @return Whether the target length has been reached.
*/
private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) {
int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead);
if (bytesToRead <= 0) {
return true;
} else if (target == null) {
source.skip(bytesToRead);
} else {
source.readBytes(target, bytesRead, bytesToRead);
}
bytesRead += bytesToRead;
return bytesRead == targetLength;
}
private boolean parseHeader() {
pesScratch.setPosition(0);
int startCodePrefix = pesScratch.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(); payloadSize = -1;
packetLength = -1; return false;
} else {
pesBuffer.skip(1); // stream_id.
packetLength = pesBuffer.readUnsignedShort();
} }
pesScratch.skipBits(8); // stream_id.
int packetLength = pesScratch.readBits(16);
// First 8 bits are skipped: '10' (2), PES_scrambling_control (2), PES_priority (1),
// data_alignment_indicator (1), copyright (1), original_or_copy (1)
pesScratch.skipBits(8);
ptsFlag = pesScratch.readBit();
// DTS_flag (1), ESCR_flag (1), ES_rate_flag (1), DSM_trick_mode_flag (1),
// additional_copy_info_flag (1), PES_CRC_flag (1), PES_extension_flag (1)
pesScratch.skipBits(7);
extendedHeaderLength = pesScratch.readBits(8);
if (packetLength == 0) {
payloadSize = -1;
} else {
payloadSize = packetLength + 6 /* packetLength does not include the first 6 bytes */
- HEADER_SIZE - extendedHeaderLength;
}
return true;
} }
private void readPacketBody() { private void parseHeaderExtension() {
// '10' (2), PES_scrambling_control (2), PES_priority (1), data_alignment_indicator (1), pesScratch.setPosition(0);
// copyright (1), original_or_copy (1) timeUs = 0;
pesBuffer.skip(1);
pesBuffer.readBytes(scratch, 1);
boolean ptsFlag = scratch.readBit();
// Last 7 bits of scratch are skipped: DTS_flag (1), ESCR_flag (1), ES_rate_flag (1),
// DSM_trick_mode_flag (1), additional_copy_info_flag (1), PES_CRC_flag (1),
// PES_extension_flag (1)
int headerDataLength = pesBuffer.readUnsignedByte();
if (headerDataLength == 0) {
headerDataLength = pesBuffer.bytesLeft();
}
long timeUs = 0;
if (ptsFlag) { if (ptsFlag) {
pesBuffer.readBytes(scratch, 5); pesScratch.skipBits(4); // '0010'
scratch.skipBits(4); // '0010' long pts = pesScratch.readBitsLong(3) << 30;
long pts = scratch.readBitsLong(3) << 30; pesScratch.skipBits(1); // marker_bit
scratch.skipBits(1); // marker_bit pts |= pesScratch.readBitsLong(15) << 15;
pts |= scratch.readBitsLong(15) << 15; pesScratch.skipBits(1); // marker_bit
scratch.skipBits(1); // marker_bit pts |= pesScratch.readBitsLong(15);
pts |= scratch.readBitsLong(15); pesScratch.skipBits(1); // marker_bit
scratch.skipBits(1); // marker_bit
timeUs = ptsToTimeUs(pts); timeUs = ptsToTimeUs(pts);
// Skip the rest of the header.
pesBuffer.skip(headerDataLength - 5);
} else {
// Skip the rest of the header.
pesBuffer.skip(headerDataLength);
} }
int payloadSize;
if (packetLength == 0) {
// If pesPacketLength is not specified read all available data.
payloadSize = pesBuffer.bytesLeft();
} else {
payloadSize = packetLength - headerDataLength - 3;
}
pesPayloadReader.read(pesBuffer, payloadSize, timeUs);
pesBuffer.reset();
packetLength = -1;
} }
} }
@ -781,7 +861,22 @@ public final class TsExtractor {
internalQueue.add(sample); internalQueue.add(sample);
} }
public abstract void read(ParsableByteArray pesBuffer, int pesPayloadSize, long pesTimeUs); /**
* Consumes (possibly partial) payload data.
*
* @param data The payload data to consume.
* @param pesTimeUs The timestamp associated with the payload.
* @param startOfPacket True if this is the first time this method is being called for the
* current packet. False otherwise.
*/
public abstract void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket);
/**
* Invoked once all of the payload data for a packet has been passed to
* {@link #consume(ParsableByteArray, long, boolean)}. The next call to
* {@link #consume(ParsableByteArray, long, boolean)} will have {@code startOfPacket == true}.
*/
public abstract void packetFinished();
} }
@ -795,9 +890,8 @@ public final class TsExtractor {
private static final int NAL_UNIT_TYPE_PPS = 8; private static final int NAL_UNIT_TYPE_PPS = 8;
private static final int NAL_UNIT_TYPE_AUD = 9; private static final int NAL_UNIT_TYPE_AUD = 9;
public final SeiReader seiReader; private final SeiReader seiReader;
// Used to store uncompleted sample data.
private Sample currentSample; private Sample currentSample;
public H264Reader(SamplePool samplePool, SeiReader seiReader) { public H264Reader(SamplePool samplePool, SeiReader seiReader) {
@ -805,6 +899,27 @@ public final class TsExtractor {
this.seiReader = seiReader; this.seiReader = seiReader;
} }
@Override
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
while (data.bytesLeft() > 0) {
if (readToNextAudUnit(data, pesTimeUs)) {
currentSample.isKeyframe = currentSample.size
> Mp4Util.findNalUnit(currentSample.data, 0, currentSample.size, NAL_UNIT_TYPE_IDR);
if (!hasMediaFormat() && currentSample.isKeyframe) {
parseMediaFormat(currentSample);
}
seiReader.read(currentSample.data, currentSample.size, currentSample.timeUs);
addSample(currentSample);
currentSample = null;
}
}
}
@Override
public void packetFinished() {
// Do nothing.
}
@Override @Override
public void release() { public void release() {
super.release(); super.release();
@ -814,51 +929,37 @@ public final class TsExtractor {
} }
} }
@Override /**
public void read(ParsableByteArray pesBuffer, int pesPayloadSize, long pesTimeUs) { * Reads data up to (but not including) the start of the next AUD unit.
// Read leftover frame data from previous PES packet. *
pesPayloadSize -= readOneH264Frame(pesBuffer, true); * @param data The data to consume.
* @param pesTimeUs The corresponding time.
* @return True if the current sample is now complete. False otherwise.
*/
private boolean readToNextAudUnit(ParsableByteArray data, long pesTimeUs) {
int pesOffset = data.getPosition();
int pesLimit = data.length();
if (pesBuffer.bytesLeft() <= 0 || pesPayloadSize <= 0) { // TODO: We probably need to handle the case where the AUD start code was split across the
return; // previous and current data buffers.
} int audOffset = Mp4Util.findNalUnit(data.data, pesOffset, pesLimit, NAL_UNIT_TYPE_AUD);
// Single PES packet should contain only one new H.264 frame.
if (currentSample != null) {
if (!hasMediaFormat() && currentSample.isKeyframe) {
parseMediaFormat(currentSample);
}
seiReader.read(currentSample.data, currentSample.size, currentSample.timeUs);
addSample(currentSample);
}
currentSample = getSample(Sample.TYPE_VIDEO);
pesPayloadSize -= readOneH264Frame(pesBuffer, false);
currentSample.timeUs = pesTimeUs;
if (pesPayloadSize > 0) {
Log.e(TAG, "PES packet contains more frame data than expected");
}
}
@SuppressLint("InlinedApi")
private int readOneH264Frame(ParsableByteArray pesBuffer, boolean remainderOnly) {
byte[] pesData = pesBuffer.data;
int pesOffset = pesBuffer.getPosition();
int pesLimit = pesBuffer.length();
int searchOffset = pesOffset + (remainderOnly ? 0 : 3);
int audOffset = Mp4Util.findNalUnit(pesData, searchOffset, pesLimit, NAL_UNIT_TYPE_AUD);
int bytesToNextAud = audOffset - pesOffset; int bytesToNextAud = audOffset - pesOffset;
if (currentSample != null) { if (bytesToNextAud == 0) {
int idrOffset = Mp4Util.findNalUnit(pesData, searchOffset, pesLimit, NAL_UNIT_TYPE_IDR); if (currentSample == null) {
if (idrOffset < audOffset) { currentSample = getSample(Sample.TYPE_VIDEO);
currentSample.isKeyframe = true; currentSample.timeUs = pesTimeUs;
addToSample(currentSample, data, 4);
return false;
} else {
return true;
} }
addToSample(currentSample, pesBuffer, bytesToNextAud); } else if (currentSample != null) {
addToSample(currentSample, data, bytesToNextAud);
return data.bytesLeft() > 0;
} else { } else {
pesBuffer.skip(bytesToNextAud); data.skip(bytesToNextAud);
return false;
} }
return bytesToNextAud;
} }
private void parseMediaFormat(Sample sample) { private void parseMediaFormat(Sample sample) {
@ -1086,56 +1187,142 @@ public final class TsExtractor {
*/ */
private class AdtsReader extends PesPayloadReader { private class AdtsReader extends PesPayloadReader {
private final ParsableByteArray adtsBuffer; private static final int STATE_FINDING_SYNC = 0;
private long timeUs; private static final int STATE_READING_HEADER = 1;
private static final int STATE_READING_SAMPLE = 2;
private static final int HEADER_SIZE = 5;
private static final int CRC_SIZE = 2;
private final ParsableBitArray adtsScratch;
private int state;
private int bytesRead;
// Used to find the header.
private boolean lastByteWasOxFF;
private boolean hasCrc;
// Parsed from the header.
private long frameDurationUs; private long frameDurationUs;
private int sampleSize;
// Used when reading the samples.
private long timeUs;
private Sample currentSample;
public AdtsReader(SamplePool samplePool) { public AdtsReader(SamplePool samplePool) {
super(samplePool); super(samplePool);
adtsBuffer = new ParsableByteArray(); adtsScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]);
state = STATE_FINDING_SYNC;
} }
@Override @Override
public void read(ParsableByteArray pesBuffer, int pesPayloadSize, long pesTimeUs) { public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
boolean needToProcessLeftOvers = !adtsBuffer.isEmpty(); if (startOfPacket) {
adtsBuffer.append(pesBuffer, pesPayloadSize); timeUs = pesTimeUs;
// If there are leftovers from previous PES packet, process it with last calculated timeUs. }
if (needToProcessLeftOvers && !readOneAacFrame(timeUs)) { while (data.bytesLeft() > 0) {
return; switch (state) {
case STATE_FINDING_SYNC:
if (skipToNextSync(data)) {
bytesRead = 0;
state = STATE_READING_HEADER;
}
break;
case STATE_READING_HEADER:
int targetLength = hasCrc ? HEADER_SIZE + CRC_SIZE : HEADER_SIZE;
if (continueRead(data, adtsScratch.getData(), targetLength)) {
parseHeader();
currentSample = getSample(Sample.TYPE_AUDIO);
currentSample.timeUs = timeUs;
currentSample.isKeyframe = true;
bytesRead = 0;
state = STATE_READING_SAMPLE;
}
break;
case STATE_READING_SAMPLE:
int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead);
addToSample(currentSample, data, bytesToRead);
bytesRead += bytesToRead;
if (bytesRead == sampleSize) {
addSample(currentSample);
currentSample = null;
timeUs += frameDurationUs;
bytesRead = 0;
state = STATE_FINDING_SYNC;
}
break;
}
} }
int frameIndex = 0;
do {
timeUs = pesTimeUs + (frameDurationUs * frameIndex++);
} while(readOneAacFrame(timeUs));
} }
@SuppressLint("InlinedApi") @Override
private boolean readOneAacFrame(long timeUs) { public void packetFinished() {
if (adtsBuffer.isEmpty()) { // Do nothing.
return false; }
@Override
public void release() {
super.release();
if (currentSample != null) {
recycle(currentSample);
currentSample = null;
} }
}
int offsetToSyncWord = findOffsetToSyncWord(); /**
adtsBuffer.skip(offsetToSyncWord); * Continues a read from the provided {@code source} into a given {@code target}. It's assumed
* that the data should be written into {@code target} starting from an offset of zero.
*
* @param source The source from which to read.
* @param target The target into which data is to be read.
* @param targetLength The target length of the read.
* @return Whether the target length was reached.
*/
private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) {
int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead);
source.readBytes(target, bytesRead, bytesToRead);
bytesRead += bytesToRead;
return bytesRead == targetLength;
}
int adtsStartOffset = adtsBuffer.getPosition(); /**
* Locates the next sync word, advancing the position to the byte that immediately follows it.
if (adtsBuffer.bytesLeft() < 7) { * If a sync word was not located, the position is advanced to the limit.
adtsBuffer.setPosition(adtsStartOffset); *
adtsBuffer.clearReadData(); * @param pesBuffer The buffer whose position should be advanced.
return false; * @return True if a sync word position was found. False otherwise.
*/
private boolean skipToNextSync(ParsableByteArray pesBuffer) {
byte[] adtsData = pesBuffer.data;
int startOffset = pesBuffer.getPosition();
int endOffset = pesBuffer.length();
for (int i = startOffset; i < endOffset; i++) {
boolean byteIsOxFF = (adtsData[i] & 0xFF) == 0xFF;
boolean found = lastByteWasOxFF && !byteIsOxFF && (adtsData[i] & 0xF0) == 0xF0;
lastByteWasOxFF = byteIsOxFF;
if (found) {
hasCrc = (adtsData[i] & 0x1) == 0;
pesBuffer.setPosition(i + 1);
return true;
}
} }
pesBuffer.setPosition(endOffset);
return false;
}
adtsBuffer.readBytes(scratch, 2); /**
scratch.skipBits(15); * Parses the sample header.
boolean hasCRC = !scratch.readBit(); */
private void parseHeader() {
adtsScratch.setPosition(0);
adtsBuffer.readBytes(scratch, 5);
if (!hasMediaFormat()) { if (!hasMediaFormat()) {
int audioObjectType = scratch.readBits(2) + 1; int audioObjectType = adtsScratch.readBits(2) + 1;
int sampleRateIndex = scratch.readBits(4); int sampleRateIndex = adtsScratch.readBits(4);
scratch.skipBits(1); adtsScratch.skipBits(1);
int channelConfig = scratch.readBits(3); int channelConfig = adtsScratch.readBits(3);
byte[] audioSpecificConfig = CodecSpecificDataUtil.buildAudioSpecificConfig( byte[] audioSpecificConfig = CodecSpecificDataUtil.buildAudioSpecificConfig(
audioObjectType, sampleRateIndex, channelConfig); audioObjectType, sampleRateIndex, channelConfig);
@ -1148,54 +1335,14 @@ 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 {
scratch.skipBits(10); adtsScratch.skipBits(10);
}
scratch.skipBits(4);
int frameSize = scratch.readBits(13);
scratch.skipBits(13);
// Decrement frame size by ADTS header size and CRC.
if (hasCRC) {
// Skip CRC.
adtsBuffer.skip(2);
frameSize -= 9;
} else {
frameSize -= 7;
} }
if (frameSize > adtsBuffer.bytesLeft()) { adtsScratch.skipBits(4);
adtsBuffer.setPosition(adtsStartOffset); sampleSize = adtsScratch.readBits(13) - 2 /* the sync word */ - HEADER_SIZE;
adtsBuffer.clearReadData(); if (hasCrc) {
return false; sampleSize -= CRC_SIZE;
} }
addSample(Sample.TYPE_AUDIO, adtsBuffer, frameSize, timeUs, true);
return true;
}
@Override
public void release() {
super.release();
adtsBuffer.reset();
}
/**
* Finds the offset to the next Adts sync word.
*
* @return The position of the next Adts sync word. If an Adts sync word is not found, then the
* position of the end of the data is returned.
*/
private int findOffsetToSyncWord() {
byte[] adtsData = adtsBuffer.data;
int startOffset = adtsBuffer.getPosition();
int endOffset = adtsBuffer.length();
for (int i = startOffset; i < endOffset - 1; i++) {
int syncBits = ((adtsData[i] & 0xFF) << 8) | (adtsData[i + 1] & 0xFF);
if ((syncBits & 0xFFF0) == 0xFFF0 && syncBits != 0xFFFF) {
return i - startOffset;
}
}
return endOffset - startOffset;
} }
} }
@ -1205,6 +1352,8 @@ public final class TsExtractor {
*/ */
private class Id3Reader extends PesPayloadReader { private class Id3Reader extends PesPayloadReader {
private Sample currentSample;
public Id3Reader(SamplePool samplePool) { public Id3Reader(SamplePool samplePool) {
super(samplePool); super(samplePool);
setMediaFormat(MediaFormat.createId3Format()); setMediaFormat(MediaFormat.createId3Format());
@ -1212,8 +1361,30 @@ public final class TsExtractor {
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
@Override @Override
public void read(ParsableByteArray pesBuffer, int pesPayloadSize, long pesTimeUs) { public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
addSample(Sample.TYPE_MISC, pesBuffer, pesPayloadSize, pesTimeUs, true); if (startOfPacket) {
currentSample = getSample(Sample.TYPE_MISC);
currentSample.timeUs = pesTimeUs;
currentSample.isKeyframe = true;
}
if (currentSample != null) {
addToSample(currentSample, data, data.bytesLeft());
}
}
@Override
public void packetFinished() {
addSample(currentSample);
currentSample = null;
}
@Override
public void release() {
super.release();
if (currentSample != null) {
recycle(currentSample);
currentSample = null;
}
} }
} }

View file

@ -16,7 +16,6 @@
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
@ -69,39 +68,27 @@ public final class ParsableByteArray {
limit = 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. */ /** Returns the number of bytes yet to be read. */
public int bytesLeft() { public int bytesLeft() {
return limit - position; return limit - position;
} }
/** Returns the number of bytes in the array. */ /** Returns the number of bytes in the array. */
// TODO: Rename to limit.
public int length() { public int length() {
return limit; return limit;
} }
/**
* Sets the limit.
*
* @param limit The limit to set.
*/
public void setLimit(int limit) {
Assertions.checkArgument(limit >= 0 && limit <= data.length);
this.limit = limit;
}
/** Returns the current offset in the array, in bytes. */ /** Returns the current offset in the array, in bytes. */
public int getPosition() { public int getPosition() {
return position; return position;