Fix handling of extended ID3 tags in MPEG-TS/HLS.
Issue #1181 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=117136370
This commit is contained in:
parent
b76db7acd2
commit
028ce2582c
@ -60,11 +60,11 @@ public class AdtsReaderTest extends TestCase {
|
|||||||
|
|
||||||
private static final long ADTS_SAMPLE_DURATION = 23219L;
|
private static final long ADTS_SAMPLE_DURATION = 23219L;
|
||||||
|
|
||||||
private ParsableByteArray data;
|
|
||||||
|
|
||||||
private AdtsReader adtsReader;
|
|
||||||
private FakeTrackOutput adtsOutput;
|
private FakeTrackOutput adtsOutput;
|
||||||
private FakeTrackOutput id3Output;
|
private FakeTrackOutput id3Output;
|
||||||
|
private AdtsReader adtsReader;
|
||||||
|
private ParsableByteArray data;
|
||||||
|
private boolean firstFeed;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
@ -72,6 +72,7 @@ public class AdtsReaderTest extends TestCase {
|
|||||||
id3Output = new FakeTrackOutput();
|
id3Output = new FakeTrackOutput();
|
||||||
adtsReader = new AdtsReader(adtsOutput, id3Output);
|
adtsReader = new AdtsReader(adtsOutput, id3Output);
|
||||||
data = new ParsableByteArray(TEST_DATA);
|
data = new ParsableByteArray(TEST_DATA);
|
||||||
|
firstFeed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSkipToNextSample() throws Exception {
|
public void testSkipToNextSample() throws Exception {
|
||||||
@ -138,7 +139,7 @@ public class AdtsReaderTest extends TestCase {
|
|||||||
public void testMultiPacketConsumed() throws Exception {
|
public void testMultiPacketConsumed() throws Exception {
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
data.setPosition(0);
|
data.setPosition(0);
|
||||||
adtsReader.consume(data, 0, i == 0);
|
feed();
|
||||||
|
|
||||||
long timeUs = ADTS_SAMPLE_DURATION * i;
|
long timeUs = ADTS_SAMPLE_DURATION * i;
|
||||||
int j = i * 2;
|
int j = i * 2;
|
||||||
@ -158,12 +159,21 @@ public class AdtsReaderTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void feedLimited(int limit) {
|
private void feedLimited(int limit) {
|
||||||
|
maybeStartPacket();
|
||||||
data.setLimit(limit);
|
data.setLimit(limit);
|
||||||
feed();
|
feed();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void feed() {
|
private void feed() {
|
||||||
adtsReader.consume(data, 0, true);
|
maybeStartPacket();
|
||||||
|
adtsReader.consume(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeStartPacket() {
|
||||||
|
if (firstFeed) {
|
||||||
|
adtsReader.packetStarted(0, true);
|
||||||
|
firstFeed = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertSampleCounts(int id3SampleCount, int adtsSampleCount) {
|
private void assertSampleCounts(int id3SampleCount, int adtsSampleCount) {
|
||||||
|
@ -74,10 +74,12 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
|
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
||||||
if (startOfPacket) {
|
|
||||||
timeUs = pesTimeUs;
|
timeUs = pesTimeUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void consume(ParsableByteArray data) {
|
||||||
while (data.bytesLeft() > 0) {
|
while (data.bytesLeft() > 0) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_FINDING_SYNC:
|
case STATE_FINDING_SYNC:
|
||||||
|
@ -46,7 +46,7 @@ public final class AdtsExtractor implements Extractor {
|
|||||||
|
|
||||||
// Accessed only by the loading thread.
|
// Accessed only by the loading thread.
|
||||||
private AdtsReader adtsReader;
|
private AdtsReader adtsReader;
|
||||||
private boolean firstPacket;
|
private boolean startedPacket;
|
||||||
|
|
||||||
public AdtsExtractor() {
|
public AdtsExtractor() {
|
||||||
this(0);
|
this(0);
|
||||||
@ -55,7 +55,6 @@ public final class AdtsExtractor implements Extractor {
|
|||||||
public AdtsExtractor(long firstSampleTimestampUs) {
|
public AdtsExtractor(long firstSampleTimestampUs) {
|
||||||
this.firstSampleTimestampUs = firstSampleTimestampUs;
|
this.firstSampleTimestampUs = firstSampleTimestampUs;
|
||||||
packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE);
|
packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE);
|
||||||
firstPacket = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -118,7 +117,7 @@ public final class AdtsExtractor implements Extractor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seek() {
|
public void seek() {
|
||||||
firstPacket = true;
|
startedPacket = false;
|
||||||
adtsReader.seek();
|
adtsReader.seek();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,8 +135,12 @@ public final class AdtsExtractor implements Extractor {
|
|||||||
|
|
||||||
// TODO: Make it possible for adtsReader to consume the dataSource directly, so that it becomes
|
// TODO: Make it possible for adtsReader to consume the dataSource directly, so that it becomes
|
||||||
// unnecessary to copy the data through packetBuffer.
|
// unnecessary to copy the data through packetBuffer.
|
||||||
adtsReader.consume(packetBuffer, firstSampleTimestampUs, firstPacket);
|
if (!startedPacket) {
|
||||||
firstPacket = false;
|
// Pass data to the reader as though it's contained within a single infinitely long packet.
|
||||||
|
adtsReader.packetStarted(firstSampleTimestampUs, true);
|
||||||
|
startedPacket = true;
|
||||||
|
}
|
||||||
|
adtsReader.consume(packetBuffer);
|
||||||
return RESULT_CONTINUE;
|
return RESULT_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,10 +93,12 @@ import java.util.Collections;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
|
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
||||||
if (startOfPacket) {
|
|
||||||
timeUs = pesTimeUs;
|
timeUs = pesTimeUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void consume(ParsableByteArray data) {
|
||||||
while (data.bytesLeft() > 0) {
|
while (data.bytesLeft() > 0) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_FINDING_SAMPLE:
|
case STATE_FINDING_SAMPLE:
|
||||||
|
@ -73,10 +73,12 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
|
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
||||||
if (startOfPacket) {
|
|
||||||
timeUs = pesTimeUs;
|
timeUs = pesTimeUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void consume(ParsableByteArray data) {
|
||||||
while (data.bytesLeft() > 0) {
|
while (data.bytesLeft() > 0) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_FINDING_SYNC:
|
case STATE_FINDING_SYNC:
|
||||||
|
@ -34,27 +34,26 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies the reader that a seek has occurred.
|
* Notifies the reader that a seek has occurred.
|
||||||
* <p>
|
|
||||||
* Following a call to this method, the data passed to the next invocation of
|
|
||||||
* {@link #consume(ParsableByteArray, long, boolean)} will not be a continuation of the data that
|
|
||||||
* was previously passed. Hence the reader should reset any internal state.
|
|
||||||
*/
|
*/
|
||||||
public abstract void seek();
|
public abstract void seek();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consumes (possibly partial) payload data.
|
* Invoked when a packet starts.
|
||||||
*
|
*
|
||||||
* @param data The payload data to consume.
|
* @param pesTimeUs The timestamp associated with the packet.
|
||||||
* @param pesTimeUs The timestamp associated with the payload.
|
* @param dataAlignmentIndicator The data alignment indicator associated with the packet.
|
||||||
* @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);
|
public abstract void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked once all of the payload data for a packet has been passed to
|
* Consumes (possibly partial) data from the current packet.
|
||||||
* {@link #consume(ParsableByteArray, long, boolean)}. The next call to
|
*
|
||||||
* {@link #consume(ParsableByteArray, long, boolean)} will have {@code startOfPacket == true}.
|
* @param data The data to consume.
|
||||||
|
*/
|
||||||
|
public abstract void consume(ParsableByteArray data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked when a packet ends.
|
||||||
*/
|
*/
|
||||||
public abstract void packetFinished();
|
public abstract void packetFinished();
|
||||||
|
|
||||||
|
@ -48,10 +48,13 @@ import java.util.Collections;
|
|||||||
// State that should be reset on seek.
|
// State that should be reset on seek.
|
||||||
private final boolean[] prefixFlags;
|
private final boolean[] prefixFlags;
|
||||||
private final CsdBuffer csdBuffer;
|
private final CsdBuffer csdBuffer;
|
||||||
private boolean foundFirstFrameInPacket;
|
|
||||||
private boolean foundFirstFrameInGroup;
|
private boolean foundFirstFrameInGroup;
|
||||||
private long totalBytesWritten;
|
private long totalBytesWritten;
|
||||||
|
|
||||||
|
// Per packet state that gets reset at the start of each packet.
|
||||||
|
private long pesTimeUs;
|
||||||
|
private boolean foundFirstFrameInPacket;
|
||||||
|
|
||||||
// Per sample state that gets reset at the start of each frame.
|
// Per sample state that gets reset at the start of each frame.
|
||||||
private boolean isKeyframe;
|
private boolean isKeyframe;
|
||||||
private long framePosition;
|
private long framePosition;
|
||||||
@ -73,10 +76,13 @@ import java.util.Collections;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
|
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
||||||
if (startOfPacket) {
|
this.pesTimeUs = pesTimeUs;
|
||||||
foundFirstFrameInPacket = false;
|
foundFirstFrameInPacket = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void consume(ParsableByteArray data) {
|
||||||
while (data.bytesLeft() > 0) {
|
while (data.bytesLeft() > 0) {
|
||||||
int offset = data.getPosition();
|
int offset = data.getPosition();
|
||||||
int limit = data.limit();
|
int limit = data.limit();
|
||||||
|
@ -57,6 +57,9 @@ import java.util.List;
|
|||||||
private boolean foundFirstSample;
|
private boolean foundFirstSample;
|
||||||
private long totalBytesWritten;
|
private long totalBytesWritten;
|
||||||
|
|
||||||
|
// Per packet state that gets reset at the start of each packet.
|
||||||
|
private long pesTimeUs;
|
||||||
|
|
||||||
// Per sample state that gets reset at the start of each sample.
|
// Per sample state that gets reset at the start of each sample.
|
||||||
private boolean isKeyframe;
|
private boolean isKeyframe;
|
||||||
private long samplePosition;
|
private long samplePosition;
|
||||||
@ -78,7 +81,6 @@ import java.util.List;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seek() {
|
public void seek() {
|
||||||
seiReader.seek();
|
|
||||||
NalUnitUtil.clearPrefixFlags(prefixFlags);
|
NalUnitUtil.clearPrefixFlags(prefixFlags);
|
||||||
sps.reset();
|
sps.reset();
|
||||||
pps.reset();
|
pps.reset();
|
||||||
@ -91,7 +93,12 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
|
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
||||||
|
this.pesTimeUs = pesTimeUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void consume(ParsableByteArray data) {
|
||||||
while (data.bytesLeft() > 0) {
|
while (data.bytesLeft() > 0) {
|
||||||
int offset = data.getPosition();
|
int offset = data.getPosition();
|
||||||
int limit = data.limit();
|
int limit = data.limit();
|
||||||
@ -194,7 +201,7 @@ import java.util.List;
|
|||||||
int unescapedLength = NalUnitUtil.unescapeStream(sei.nalData, sei.nalLength);
|
int unescapedLength = NalUnitUtil.unescapeStream(sei.nalData, sei.nalLength);
|
||||||
seiWrapper.reset(sei.nalData, unescapedLength);
|
seiWrapper.reset(sei.nalData, unescapedLength);
|
||||||
seiWrapper.setPosition(4); // NAL prefix and nal_unit() header.
|
seiWrapper.setPosition(4); // NAL prefix and nal_unit() header.
|
||||||
seiReader.consume(seiWrapper, pesTimeUs, true);
|
seiReader.consume(pesTimeUs, seiWrapper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +58,9 @@ import java.util.Collections;
|
|||||||
private final SampleReader sampleReader;
|
private final SampleReader sampleReader;
|
||||||
private long totalBytesWritten;
|
private long totalBytesWritten;
|
||||||
|
|
||||||
|
// Per packet state that gets reset at the start of each packet.
|
||||||
|
private long pesTimeUs;
|
||||||
|
|
||||||
// Scratch variables to avoid allocations.
|
// Scratch variables to avoid allocations.
|
||||||
private final ParsableByteArray seiWrapper;
|
private final ParsableByteArray seiWrapper;
|
||||||
|
|
||||||
@ -76,7 +79,6 @@ import java.util.Collections;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seek() {
|
public void seek() {
|
||||||
seiReader.seek();
|
|
||||||
NalUnitUtil.clearPrefixFlags(prefixFlags);
|
NalUnitUtil.clearPrefixFlags(prefixFlags);
|
||||||
vps.reset();
|
vps.reset();
|
||||||
sps.reset();
|
sps.reset();
|
||||||
@ -88,7 +90,12 @@ import java.util.Collections;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
|
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
||||||
|
this.pesTimeUs = pesTimeUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void consume(ParsableByteArray data) {
|
||||||
while (data.bytesLeft() > 0) {
|
while (data.bytesLeft() > 0) {
|
||||||
int offset = data.getPosition();
|
int offset = data.getPosition();
|
||||||
int limit = data.limit();
|
int limit = data.limit();
|
||||||
@ -179,7 +186,7 @@ import java.util.Collections;
|
|||||||
|
|
||||||
// Skip the NAL prefix and type.
|
// Skip the NAL prefix and type.
|
||||||
seiWrapper.skipBytes(5);
|
seiWrapper.skipBytes(5);
|
||||||
seiReader.consume(seiWrapper, pesTimeUs, true);
|
seiReader.consume(pesTimeUs, seiWrapper);
|
||||||
}
|
}
|
||||||
if (suffixSei.endNalUnit(discardPadding)) {
|
if (suffixSei.endNalUnit(discardPadding)) {
|
||||||
int unescapedLength = NalUnitUtil.unescapeStream(suffixSei.nalData, suffixSei.nalLength);
|
int unescapedLength = NalUnitUtil.unescapeStream(suffixSei.nalData, suffixSei.nalLength);
|
||||||
@ -187,7 +194,7 @@ import java.util.Collections;
|
|||||||
|
|
||||||
// Skip the NAL prefix and type.
|
// Skip the NAL prefix and type.
|
||||||
seiWrapper.skipBytes(5);
|
seiWrapper.skipBytes(5);
|
||||||
seiReader.consume(seiWrapper, pesTimeUs, true);
|
seiReader.consume(pesTimeUs, seiWrapper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,16 +26,22 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
*/
|
*/
|
||||||
/* package */ final class Id3Reader extends ElementaryStreamReader {
|
/* package */ final class Id3Reader extends ElementaryStreamReader {
|
||||||
|
|
||||||
|
private static final int ID3_HEADER_SIZE = 10;
|
||||||
|
|
||||||
|
private final ParsableByteArray id3Header;
|
||||||
|
|
||||||
// State that should be reset on seek.
|
// State that should be reset on seek.
|
||||||
private boolean writingSample;
|
private boolean writingSample;
|
||||||
|
|
||||||
// Per sample state that gets reset at the start of each sample.
|
// Per sample state that gets reset at the start of each sample.
|
||||||
private long sampleTimeUs;
|
private long sampleTimeUs;
|
||||||
private int sampleSize;
|
private int sampleSize;
|
||||||
|
private int sampleBytesRead;
|
||||||
|
|
||||||
public Id3Reader(TrackOutput output) {
|
public Id3Reader(TrackOutput output) {
|
||||||
super(output);
|
super(output);
|
||||||
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, Format.NO_VALUE));
|
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, Format.NO_VALUE));
|
||||||
|
id3Header = new ParsableByteArray(ID3_HEADER_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -44,20 +50,43 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
|
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
||||||
if (startOfPacket) {
|
if (!dataAlignmentIndicator) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
writingSample = true;
|
writingSample = true;
|
||||||
sampleTimeUs = pesTimeUs;
|
sampleTimeUs = pesTimeUs;
|
||||||
sampleSize = 0;
|
sampleSize = 0;
|
||||||
|
sampleBytesRead = 0;
|
||||||
}
|
}
|
||||||
if (writingSample) {
|
|
||||||
sampleSize += data.bytesLeft();
|
@Override
|
||||||
output.sampleData(data, data.bytesLeft());
|
public void consume(ParsableByteArray data) {
|
||||||
|
if (!writingSample) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
int bytesAvailable = data.bytesLeft();
|
||||||
|
if (sampleBytesRead < ID3_HEADER_SIZE) {
|
||||||
|
// We're still reading the ID3 header.
|
||||||
|
int headerBytesAvailable = Math.min(bytesAvailable, ID3_HEADER_SIZE - sampleBytesRead);
|
||||||
|
System.arraycopy(data.data, data.getPosition(), id3Header.data, sampleBytesRead,
|
||||||
|
headerBytesAvailable);
|
||||||
|
if (sampleBytesRead + headerBytesAvailable == ID3_HEADER_SIZE) {
|
||||||
|
// We've finished reading the ID3 header. Extract the sample size.
|
||||||
|
id3Header.setPosition(6); // 'ID3' (3) + version (2) + flags (1)
|
||||||
|
sampleSize = ID3_HEADER_SIZE + id3Header.readSynchSafeInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Write data to the output.
|
||||||
|
output.sampleData(data, bytesAvailable);
|
||||||
|
sampleBytesRead += bytesAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void packetFinished() {
|
public void packetFinished() {
|
||||||
|
if (!writingSample || sampleSize == 0 || sampleBytesRead != sampleSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
output.sampleMetadata(sampleTimeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null);
|
output.sampleMetadata(sampleTimeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null);
|
||||||
writingSample = false;
|
writingSample = false;
|
||||||
}
|
}
|
||||||
|
@ -66,10 +66,12 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
|
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
|
||||||
if (startOfPacket) {
|
|
||||||
timeUs = pesTimeUs;
|
timeUs = pesTimeUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void consume(ParsableByteArray data) {
|
||||||
while (data.bytesLeft() > 0) {
|
while (data.bytesLeft() > 0) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_FINDING_HEADER:
|
case STATE_FINDING_HEADER:
|
||||||
|
@ -23,26 +23,21 @@ import com.google.android.exoplayer.util.MimeTypes;
|
|||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a SEI data from H.264 frames and extracts samples with closed captions data.
|
* Consumes SEI buffers, outputting contained EIA608 messages to a {@link TrackOutput}.
|
||||||
*
|
|
||||||
* TODO: Technically, we shouldn't allow a sample to be read from the queue until we're sure that
|
|
||||||
* a sample with an earlier timestamp won't be added to it.
|
|
||||||
*/
|
*/
|
||||||
/* package */ final class SeiReader extends ElementaryStreamReader {
|
// TODO: Technically, we shouldn't allow a sample to be read from the queue until we're sure that
|
||||||
|
// a sample with an earlier timestamp won't be added to it.
|
||||||
|
/* package */ final class SeiReader {
|
||||||
|
|
||||||
|
private final TrackOutput output;
|
||||||
|
|
||||||
public SeiReader(TrackOutput output) {
|
public SeiReader(TrackOutput output) {
|
||||||
super(output);
|
this.output = output;
|
||||||
output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_EIA608, Format.NO_VALUE,
|
output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_EIA608, Format.NO_VALUE,
|
||||||
null));
|
null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void consume(long pesTimeUs, ParsableByteArray seiBuffer) {
|
||||||
public void seek() {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void consume(ParsableByteArray seiBuffer, long pesTimeUs, boolean startOfPacket) {
|
|
||||||
int b;
|
int b;
|
||||||
while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) {
|
while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) {
|
||||||
// Parse payload type.
|
// Parse payload type.
|
||||||
@ -57,7 +52,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
b = seiBuffer.readUnsignedByte();
|
b = seiBuffer.readUnsignedByte();
|
||||||
payloadSize += b;
|
payloadSize += b;
|
||||||
} while (b == 0xFF);
|
} while (b == 0xFF);
|
||||||
// Process the payload. We only support EIA-608 payloads currently.
|
// Process the payload.
|
||||||
if (Eia608Parser.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) {
|
if (Eia608Parser.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) {
|
||||||
output.sampleData(seiBuffer, payloadSize);
|
output.sampleData(seiBuffer, payloadSize);
|
||||||
output.sampleMetadata(pesTimeUs, C.SAMPLE_FLAG_SYNC, payloadSize, 0, null);
|
output.sampleMetadata(pesTimeUs, C.SAMPLE_FLAG_SYNC, payloadSize, 0, null);
|
||||||
@ -67,9 +62,4 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void packetFinished() {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -439,13 +439,13 @@ public final class TsExtractor implements Extractor {
|
|||||||
|
|
||||||
private int state;
|
private int state;
|
||||||
private int bytesRead;
|
private int bytesRead;
|
||||||
private boolean bodyStarted;
|
|
||||||
|
|
||||||
private boolean ptsFlag;
|
private boolean ptsFlag;
|
||||||
private boolean dtsFlag;
|
private boolean dtsFlag;
|
||||||
private boolean seenFirstDts;
|
private boolean seenFirstDts;
|
||||||
private int extendedHeaderLength;
|
private int extendedHeaderLength;
|
||||||
private int payloadSize;
|
private int payloadSize;
|
||||||
|
private boolean dataAlignmentIndicator;
|
||||||
private long timeUs;
|
private long timeUs;
|
||||||
|
|
||||||
public PesReader(ElementaryStreamReader pesPayloadReader) {
|
public PesReader(ElementaryStreamReader pesPayloadReader) {
|
||||||
@ -458,7 +458,6 @@ public final class TsExtractor implements Extractor {
|
|||||||
public void seek() {
|
public void seek() {
|
||||||
state = STATE_FINDING_HEADER;
|
state = STATE_FINDING_HEADER;
|
||||||
bytesRead = 0;
|
bytesRead = 0;
|
||||||
bodyStarted = false;
|
|
||||||
seenFirstDts = false;
|
seenFirstDts = false;
|
||||||
pesPayloadReader.seek();
|
pesPayloadReader.seek();
|
||||||
}
|
}
|
||||||
@ -483,10 +482,8 @@ public final class TsExtractor implements Extractor {
|
|||||||
if (payloadSize != -1) {
|
if (payloadSize != -1) {
|
||||||
Log.w(TAG, "Unexpected start indicator: expected " + payloadSize + " more bytes");
|
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.
|
// Either way, notify the reader that it has now finished.
|
||||||
if (bodyStarted) {
|
|
||||||
pesPayloadReader.packetFinished();
|
pesPayloadReader.packetFinished();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
setState(STATE_READING_HEADER);
|
setState(STATE_READING_HEADER);
|
||||||
@ -508,7 +505,7 @@ public final class TsExtractor implements Extractor {
|
|||||||
if (continueRead(data, pesScratch.data, readLength)
|
if (continueRead(data, pesScratch.data, readLength)
|
||||||
&& continueRead(data, null, extendedHeaderLength)) {
|
&& continueRead(data, null, extendedHeaderLength)) {
|
||||||
parseHeaderExtension();
|
parseHeaderExtension();
|
||||||
bodyStarted = false;
|
pesPayloadReader.packetStarted(timeUs, dataAlignmentIndicator);
|
||||||
setState(STATE_READING_BODY);
|
setState(STATE_READING_BODY);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -519,8 +516,7 @@ public final class TsExtractor implements Extractor {
|
|||||||
readLength -= padding;
|
readLength -= padding;
|
||||||
data.setLimit(data.getPosition() + readLength);
|
data.setLimit(data.getPosition() + readLength);
|
||||||
}
|
}
|
||||||
pesPayloadReader.consume(data, timeUs, !bodyStarted);
|
pesPayloadReader.consume(data);
|
||||||
bodyStarted = true;
|
|
||||||
if (payloadSize != -1) {
|
if (payloadSize != -1) {
|
||||||
payloadSize -= readLength;
|
payloadSize -= readLength;
|
||||||
if (payloadSize == 0) {
|
if (payloadSize == 0) {
|
||||||
@ -573,9 +569,9 @@ public final class TsExtractor implements Extractor {
|
|||||||
|
|
||||||
pesScratch.skipBits(8); // stream_id.
|
pesScratch.skipBits(8); // stream_id.
|
||||||
int packetLength = pesScratch.readBits(16);
|
int packetLength = pesScratch.readBits(16);
|
||||||
// First 8 bits are skipped: '10' (2), PES_scrambling_control (2), PES_priority (1),
|
pesScratch.skipBits(5); // '10' (2), PES_scrambling_control (2), PES_priority (1)
|
||||||
// data_alignment_indicator (1), copyright (1), original_or_copy (1)
|
dataAlignmentIndicator = pesScratch.readBit();
|
||||||
pesScratch.skipBits(8);
|
pesScratch.skipBits(2); // copyright (1), original_or_copy (1)
|
||||||
ptsFlag = pesScratch.readBit();
|
ptsFlag = pesScratch.readBit();
|
||||||
dtsFlag = pesScratch.readBit();
|
dtsFlag = pesScratch.readBit();
|
||||||
// ESCR_flag (1), ES_rate_flag (1), DSM_trick_mode_flag (1),
|
// ESCR_flag (1), ES_rate_flag (1), DSM_trick_mode_flag (1),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user