Commit 5ca2e0fd by Oliver Woodman

First step toward extractor unification.

- It's probably easiest to think of this as a standalone HLS change, which is splitting out the "loading" and "consuming" sides of HlsExtractor and a good structural change in its own right. To do this, HlsExtractorWrapper becomes a final class implementing the consuming side. HlsExtractor becomes an interface defining the loading side.

- The bigger picture is that, hopefully, HlsExtractor will become a lightweight extractor interface that can be used throughout the library. Because it doesn't need to implement the consuming side, we'll save on having to re-implement the consuming side for every extractor (we'll probably need one consuming side implementation for HLS/DASH/SmoothStreaming, and a second one for everything else, both of which will use SampleQueue). It's expected that the HlsExtractor interface will need to change to accommodate all use cases.

- The next step in unification will be to try and have FragmentedMp4Extractor implement HlsExtractor (which will need renaming). Once this is done, I'll try and move the chunk package over to use the HlsExtractor interface.
parent becc6fca
...@@ -19,6 +19,7 @@ import com.google.android.exoplayer.C; ...@@ -19,6 +19,7 @@ import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.hls.parser.AdtsExtractor; import com.google.android.exoplayer.hls.parser.AdtsExtractor;
import com.google.android.exoplayer.hls.parser.HlsExtractor; import com.google.android.exoplayer.hls.parser.HlsExtractor;
import com.google.android.exoplayer.hls.parser.HlsExtractorWrapper;
import com.google.android.exoplayer.hls.parser.TsExtractor; import com.google.android.exoplayer.hls.parser.TsExtractor;
import com.google.android.exoplayer.upstream.Aes128DataSource; import com.google.android.exoplayer.upstream.Aes128DataSource;
import com.google.android.exoplayer.upstream.BandwidthMeter; import com.google.android.exoplayer.upstream.BandwidthMeter;
...@@ -341,16 +342,17 @@ public class HlsChunkSource { ...@@ -341,16 +342,17 @@ public class HlsChunkSource {
boolean isLastChunk = !mediaPlaylist.live && chunkIndex == mediaPlaylist.segments.size() - 1; boolean isLastChunk = !mediaPlaylist.live && chunkIndex == mediaPlaylist.segments.size() - 1;
// Configure the extractor that will read the chunk. // Configure the extractor that will read the chunk.
HlsExtractor extractor; HlsExtractorWrapper extractorWrapper;
if (previousTsChunk == null || segment.discontinuity || switchingVariant || liveDiscontinuity) { if (previousTsChunk == null || segment.discontinuity || switchingVariant || liveDiscontinuity) {
extractor = chunkUri.getLastPathSegment().endsWith(AAC_FILE_EXTENSION) HlsExtractor extractor = chunkUri.getLastPathSegment().endsWith(AAC_FILE_EXTENSION)
? new AdtsExtractor(switchingVariantSpliced, startTimeUs, bufferPool) ? new AdtsExtractor(startTimeUs)
: new TsExtractor(switchingVariantSpliced, startTimeUs, bufferPool); : new TsExtractor(startTimeUs);
extractorWrapper = new HlsExtractorWrapper(bufferPool, extractor, switchingVariantSpliced);
} else { } else {
extractor = previousTsChunk.extractor; extractorWrapper = previousTsChunk.extractor;
} }
return new TsChunk(dataSource, dataSpec, extractor, enabledVariants[variantIndex].index, return new TsChunk(dataSource, dataSpec, extractorWrapper, enabledVariants[variantIndex].index,
startTimeUs, endTimeUs, chunkMediaSequence, isLastChunk); startTimeUs, endTimeUs, chunkMediaSequence, isLastChunk);
} }
......
...@@ -21,7 +21,7 @@ import com.google.android.exoplayer.SampleHolder; ...@@ -21,7 +21,7 @@ import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.TrackInfo; import com.google.android.exoplayer.TrackInfo;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.hls.parser.HlsExtractor; import com.google.android.exoplayer.hls.parser.HlsExtractorWrapper;
import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.upstream.Loader.Loadable; import com.google.android.exoplayer.upstream.Loader.Loadable;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
...@@ -44,7 +44,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -44,7 +44,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
private static final int NO_RESET_PENDING = -1; private static final int NO_RESET_PENDING = -1;
private final HlsChunkSource chunkSource; private final HlsChunkSource chunkSource;
private final LinkedList<HlsExtractor> extractors; private final LinkedList<HlsExtractorWrapper> extractors;
private final boolean frameAccurateSeeking; private final boolean frameAccurateSeeking;
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
...@@ -83,7 +83,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -83,7 +83,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
this.frameAccurateSeeking = frameAccurateSeeking; this.frameAccurateSeeking = frameAccurateSeeking;
this.remainingReleaseCount = downstreamRendererCount; this.remainingReleaseCount = downstreamRendererCount;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
extractors = new LinkedList<HlsExtractor>(); extractors = new LinkedList<HlsExtractorWrapper>();
} }
@Override @Override
...@@ -96,7 +96,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -96,7 +96,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
} }
continueBufferingInternal(); continueBufferingInternal();
if (!extractors.isEmpty()) { if (!extractors.isEmpty()) {
HlsExtractor extractor = extractors.getFirst(); HlsExtractorWrapper extractor = extractors.getFirst();
if (extractor.isPrepared()) { if (extractor.isPrepared()) {
trackCount = extractor.getTrackCount(); trackCount = extractor.getTrackCount();
trackEnabledStates = new boolean[trackCount]; trackEnabledStates = new boolean[trackCount];
...@@ -195,7 +195,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -195,7 +195,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
return NOTHING_READ; return NOTHING_READ;
} }
HlsExtractor extractor = getCurrentExtractor(); HlsExtractorWrapper extractor = getCurrentExtractor();
if (extractors.size() > 1) { if (extractors.size() > 1) {
// If there's more than one extractor, attempt to configure a seamless splice from the // If there's more than one extractor, attempt to configure a seamless splice from the
// current one to the next one. // current one to the next one.
...@@ -328,8 +328,8 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -328,8 +328,8 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
* *
* @return The current extractor from which samples should be read. Guaranteed to be non-null. * @return The current extractor from which samples should be read. Guaranteed to be non-null.
*/ */
private HlsExtractor getCurrentExtractor() { private HlsExtractorWrapper getCurrentExtractor() {
HlsExtractor extractor = extractors.getFirst(); HlsExtractorWrapper extractor = extractors.getFirst();
while (extractors.size() > 1 && !haveSamplesForEnabledTracks(extractor)) { while (extractors.size() > 1 && !haveSamplesForEnabledTracks(extractor)) {
// We're finished reading from the extractor for all tracks, and so can discard it. // We're finished reading from the extractor for all tracks, and so can discard it.
extractors.removeFirst().release(); extractors.removeFirst().release();
...@@ -338,7 +338,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -338,7 +338,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
return extractor; return extractor;
} }
private void discardSamplesForDisabledTracks(HlsExtractor extractor, long timeUs) { private void discardSamplesForDisabledTracks(HlsExtractorWrapper extractor, long timeUs) {
if (!extractor.isPrepared()) { if (!extractor.isPrepared()) {
return; return;
} }
...@@ -349,7 +349,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { ...@@ -349,7 +349,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback {
} }
} }
private boolean haveSamplesForEnabledTracks(HlsExtractor extractor) { private boolean haveSamplesForEnabledTracks(HlsExtractorWrapper extractor) {
if (!extractor.isPrepared()) { if (!extractor.isPrepared()) {
return false; return false;
} }
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
*/ */
package com.google.android.exoplayer.hls; package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.hls.parser.HlsExtractor; import com.google.android.exoplayer.hls.parser.HlsExtractorWrapper;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.DataSpec;
...@@ -51,7 +51,7 @@ public final class TsChunk extends HlsChunk { ...@@ -51,7 +51,7 @@ public final class TsChunk extends HlsChunk {
/** /**
* The extractor into which this chunk is being consumed. * The extractor into which this chunk is being consumed.
*/ */
public final HlsExtractor extractor; public final HlsExtractorWrapper extractor;
private int loadPosition; private int loadPosition;
private volatile boolean loadFinished; private volatile boolean loadFinished;
...@@ -67,7 +67,7 @@ public final class TsChunk extends HlsChunk { ...@@ -67,7 +67,7 @@ public final class TsChunk extends HlsChunk {
* @param chunkIndex The index of the chunk. * @param chunkIndex The index of the chunk.
* @param isLastChunk True if this is the last chunk in the media. False otherwise. * @param isLastChunk True if this is the last chunk in the media. False otherwise.
*/ */
public TsChunk(DataSource dataSource, DataSpec dataSpec, HlsExtractor extractor, public TsChunk(DataSource dataSource, DataSpec dataSpec, HlsExtractorWrapper extractor,
int variantIndex, long startTimeUs, long endTimeUs, int chunkIndex, boolean isLastChunk) { int variantIndex, long startTimeUs, long endTimeUs, int chunkIndex, boolean isLastChunk) {
super(dataSource, dataSpec); super(dataSource, dataSpec);
this.extractor = extractor; this.extractor = extractor;
......
...@@ -16,10 +16,7 @@ ...@@ -16,10 +16,7 @@
package com.google.android.exoplayer.hls.parser; package com.google.android.exoplayer.hls.parser;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.upstream.BufferPool;
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.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import java.io.IOException; import java.io.IOException;
...@@ -28,73 +25,41 @@ import java.io.IOException; ...@@ -28,73 +25,41 @@ import java.io.IOException;
* Facilitates the extraction of AAC samples from elementary audio files formatted as AAC with ADTS * Facilitates the extraction of AAC samples from elementary audio files formatted as AAC with ADTS
* headers. * headers.
*/ */
public class AdtsExtractor extends HlsExtractor { public class AdtsExtractor implements HlsExtractor {
private static final int MAX_PACKET_SIZE = 200; private static final int MAX_PACKET_SIZE = 200;
private final long firstSampleTimestamp; private final long firstSampleTimestamp;
private final ParsableByteArray packetBuffer; private final ParsableByteArray packetBuffer;
private final AdtsReader adtsReader;
// Accessed only by the loading thread. // Accessed only by the loading thread.
private AdtsReader adtsReader;
private boolean firstPacket; private boolean firstPacket;
// Accessed by both the loading and consuming threads.
private volatile boolean prepared;
public AdtsExtractor(boolean shouldSpliceIn, long firstSampleTimestamp, BufferPool bufferPool) { public AdtsExtractor(long firstSampleTimestamp) {
super(shouldSpliceIn);
this.firstSampleTimestamp = firstSampleTimestamp; this.firstSampleTimestamp = firstSampleTimestamp;
packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE); packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE);
adtsReader = new AdtsReader(bufferPool);
firstPacket = true; firstPacket = true;
} }
@Override @Override
public void init(ExtractorOutput output) {
adtsReader = new AdtsReader(output.getTrackOutput(0));
}
@Override
public int getTrackCount() { public int getTrackCount() {
Assertions.checkState(prepared);
return 1; return 1;
} }
@Override @Override
public MediaFormat getFormat(int track) { public MediaFormat getFormat(int track) {
Assertions.checkState(prepared); return adtsReader.getFormat();
return adtsReader.getMediaFormat();
} }
@Override @Override
public boolean isPrepared() { public boolean isPrepared() {
return prepared; return adtsReader != null && adtsReader.hasFormat();
}
@Override
public void release() {
adtsReader.release();
}
@Override
public long getLargestSampleTimestamp() {
return adtsReader.getLargestParsedTimestampUs();
}
@Override
public boolean getSample(int track, SampleHolder holder) {
Assertions.checkState(prepared);
Assertions.checkState(track == 0);
return adtsReader.getSample(holder);
}
@Override
public void discardUntil(int track, long timeUs) {
Assertions.checkState(prepared);
Assertions.checkState(track == 0);
adtsReader.discardUntil(timeUs);
}
@Override
public boolean hasSamples(int track) {
Assertions.checkState(prepared);
Assertions.checkState(track == 0);
return !adtsReader.isEmpty();
} }
@Override @Override
...@@ -111,16 +76,7 @@ public class AdtsExtractor extends HlsExtractor { ...@@ -111,16 +76,7 @@ public class AdtsExtractor extends HlsExtractor {
// unnecessary to copy the data through packetBuffer. // unnecessary to copy the data through packetBuffer.
adtsReader.consume(packetBuffer, firstSampleTimestamp, firstPacket); adtsReader.consume(packetBuffer, firstSampleTimestamp, firstPacket);
firstPacket = false; firstPacket = false;
if (!prepared) {
prepared = adtsReader.hasMediaFormat();
}
return bytesRead; return bytesRead;
} }
@Override
protected SampleQueue getSampleQueue(int track) {
Assertions.checkState(track == 0);
return adtsReader;
}
} }
...@@ -17,7 +17,7 @@ package com.google.android.exoplayer.hls.parser; ...@@ -17,7 +17,7 @@ package com.google.android.exoplayer.hls.parser;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.upstream.BufferPool; import com.google.android.exoplayer.hls.parser.HlsExtractor.TrackOutput;
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.ParsableBitArray;
...@@ -55,8 +55,8 @@ import java.util.Collections; ...@@ -55,8 +55,8 @@ import java.util.Collections;
// Used when reading the samples. // Used when reading the samples.
private long timeUs; private long timeUs;
public AdtsReader(BufferPool bufferPool) { public AdtsReader(TrackOutput output) {
super(bufferPool); super(output);
adtsScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]); adtsScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]);
state = STATE_FINDING_SYNC; state = STATE_FINDING_SYNC;
} }
...@@ -78,17 +78,17 @@ import java.util.Collections; ...@@ -78,17 +78,17 @@ import java.util.Collections;
int targetLength = hasCrc ? HEADER_SIZE + CRC_SIZE : HEADER_SIZE; int targetLength = hasCrc ? HEADER_SIZE + CRC_SIZE : HEADER_SIZE;
if (continueRead(data, adtsScratch.getData(), targetLength)) { if (continueRead(data, adtsScratch.getData(), targetLength)) {
parseHeader(); parseHeader();
startSample(timeUs); output.startSample(timeUs, 0);
bytesRead = 0; bytesRead = 0;
state = STATE_READING_SAMPLE; state = STATE_READING_SAMPLE;
} }
break; break;
case STATE_READING_SAMPLE: case STATE_READING_SAMPLE:
int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead); int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead);
appendData(data, bytesToRead); output.appendData(data, bytesToRead);
bytesRead += bytesToRead; bytesRead += bytesToRead;
if (bytesRead == sampleSize) { if (bytesRead == sampleSize) {
commitSample(C.SAMPLE_FLAG_SYNC); output.commitSample(C.SAMPLE_FLAG_SYNC, 0, null);
timeUs += frameDurationUs; timeUs += frameDurationUs;
bytesRead = 0; bytesRead = 0;
state = STATE_FINDING_SYNC; state = STATE_FINDING_SYNC;
...@@ -152,7 +152,7 @@ import java.util.Collections; ...@@ -152,7 +152,7 @@ import java.util.Collections;
private void parseHeader() { private void parseHeader() {
adtsScratch.setPosition(0); adtsScratch.setPosition(0);
if (!hasMediaFormat()) { if (!hasFormat()) {
int audioObjectType = adtsScratch.readBits(2) + 1; int audioObjectType = adtsScratch.readBits(2) + 1;
int sampleRateIndex = adtsScratch.readBits(4); int sampleRateIndex = adtsScratch.readBits(4);
adtsScratch.skipBits(1); adtsScratch.skipBits(1);
...@@ -167,7 +167,7 @@ import java.util.Collections; ...@@ -167,7 +167,7 @@ import java.util.Collections;
MediaFormat.NO_VALUE, audioParams.second, audioParams.first, MediaFormat.NO_VALUE, audioParams.second, audioParams.first,
Collections.singletonList(audioSpecificConfig)); Collections.singletonList(audioSpecificConfig));
frameDurationUs = (C.MICROS_PER_SECOND * 1024L) / mediaFormat.sampleRate; frameDurationUs = (C.MICROS_PER_SECOND * 1024L) / mediaFormat.sampleRate;
setMediaFormat(mediaFormat); setFormat(mediaFormat);
} else { } else {
adtsScratch.skipBits(10); adtsScratch.skipBits(10);
} }
......
...@@ -15,16 +15,46 @@ ...@@ -15,16 +15,46 @@
*/ */
package com.google.android.exoplayer.hls.parser; package com.google.android.exoplayer.hls.parser;
import com.google.android.exoplayer.upstream.BufferPool; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.hls.parser.HlsExtractor.TrackOutput;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
/** /**
* Extracts individual samples from an elementary media stream, preserving original order. * Extracts individual samples from an elementary media stream, preserving original order.
*/ */
/* package */ abstract class ElementaryStreamReader extends SampleQueue { /* package */ abstract class ElementaryStreamReader {
protected ElementaryStreamReader(BufferPool bufferPool) { protected final TrackOutput output;
super(bufferPool); private MediaFormat format;
/**
* @param output A {@link TrackOutput} to which samples should be written.
*/
protected ElementaryStreamReader(TrackOutput output) {
this.output = output;
}
/**
* True if the format of the stream is known. False otherwise.
*/
public boolean hasFormat() {
return format != null;
}
/**
* Returns the format of the stream, or {@code null} if {@link #hasFormat()} is false.
*/
public MediaFormat getFormat() {
return format;
}
/**
* Sets the format of the stream.
*
* @param format The format.
*/
protected void setFormat(MediaFormat format) {
this.format = format;
} }
/** /**
......
...@@ -17,8 +17,8 @@ package com.google.android.exoplayer.hls.parser; ...@@ -17,8 +17,8 @@ package com.google.android.exoplayer.hls.parser;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.hls.parser.HlsExtractor.TrackOutput;
import com.google.android.exoplayer.mp4.Mp4Util; import com.google.android.exoplayer.mp4.Mp4Util;
import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
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.ParsableBitArray;
...@@ -44,18 +44,20 @@ import java.util.List; ...@@ -44,18 +44,20 @@ import java.util.List;
private final NalUnitTargetBuffer sps; private final NalUnitTargetBuffer sps;
private final NalUnitTargetBuffer pps; private final NalUnitTargetBuffer pps;
private final NalUnitTargetBuffer sei; private final NalUnitTargetBuffer sei;
private final ParsableByteArray seiWrapper;
private int scratchEscapeCount; private int scratchEscapeCount;
private int[] scratchEscapePositions; private int[] scratchEscapePositions;
private boolean isKeyframe; private boolean isKeyframe;
public H264Reader(BufferPool bufferPool, SeiReader seiReader) { public H264Reader(TrackOutput output, SeiReader seiReader) {
super(bufferPool); super(output);
this.seiReader = seiReader; this.seiReader = seiReader;
prefixFlags = new boolean[3]; prefixFlags = new boolean[3];
sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128); sps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SPS, 128);
pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128); pps = new NalUnitTargetBuffer(NAL_UNIT_TYPE_PPS, 128);
sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128); sei = new NalUnitTargetBuffer(NAL_UNIT_TYPE_SEI, 128);
seiWrapper = new ParsableByteArray();
scratchEscapePositions = new int[10]; scratchEscapePositions = new int[10];
} }
...@@ -67,7 +69,7 @@ import java.util.List; ...@@ -67,7 +69,7 @@ import java.util.List;
byte[] dataArray = data.data; byte[] dataArray = data.data;
// Append the data to the buffer. // Append the data to the buffer.
appendData(data, data.bytesLeft()); output.appendData(data, data.bytesLeft());
// Scan the appended data, processing NAL units as they are encountered // Scan the appended data, processing NAL units as they are encountered
while (offset < limit) { while (offset < limit) {
...@@ -85,13 +87,13 @@ import java.util.List; ...@@ -85,13 +87,13 @@ import java.util.List;
int nalUnitType = Mp4Util.getNalUnitType(dataArray, nextNalUnitOffset); int nalUnitType = Mp4Util.getNalUnitType(dataArray, nextNalUnitOffset);
int nalUnitOffsetInData = nextNalUnitOffset - limit; int nalUnitOffsetInData = nextNalUnitOffset - limit;
if (nalUnitType == NAL_UNIT_TYPE_AUD) { if (nalUnitType == NAL_UNIT_TYPE_AUD) {
if (writingSample()) { if (output.isWritingSample()) {
if (isKeyframe && !hasMediaFormat() && sps.isCompleted() && pps.isCompleted()) { if (isKeyframe && !hasFormat() && sps.isCompleted() && pps.isCompleted()) {
parseMediaFormat(sps, pps); parseMediaFormat(sps, pps);
} }
commitSample(isKeyframe ? C.SAMPLE_FLAG_SYNC : 0, nalUnitOffsetInData); output.commitSample(isKeyframe ? C.SAMPLE_FLAG_SYNC : 0, nalUnitOffsetInData, null);
} }
startSample(pesTimeUs, nalUnitOffsetInData); output.startSample(pesTimeUs, nalUnitOffsetInData);
isKeyframe = false; isKeyframe = false;
} else if (nalUnitType == NAL_UNIT_TYPE_IDR) { } else if (nalUnitType == NAL_UNIT_TYPE_IDR) {
isKeyframe = true; isKeyframe = true;
...@@ -118,7 +120,7 @@ import java.util.List; ...@@ -118,7 +120,7 @@ import java.util.List;
} }
private void feedNalUnitTargetBuffersStart(int nalUnitType) { private void feedNalUnitTargetBuffersStart(int nalUnitType) {
if (!hasMediaFormat()) { if (!hasFormat()) {
sps.startNalUnit(nalUnitType); sps.startNalUnit(nalUnitType);
pps.startNalUnit(nalUnitType); pps.startNalUnit(nalUnitType);
} }
...@@ -126,7 +128,7 @@ import java.util.List; ...@@ -126,7 +128,7 @@ import java.util.List;
} }
private void feedNalUnitTargetBuffersData(byte[] dataArray, int offset, int limit) { private void feedNalUnitTargetBuffersData(byte[] dataArray, int offset, int limit) {
if (!hasMediaFormat()) { if (!hasFormat()) {
sps.appendToNalUnit(dataArray, offset, limit); sps.appendToNalUnit(dataArray, offset, limit);
pps.appendToNalUnit(dataArray, offset, limit); pps.appendToNalUnit(dataArray, offset, limit);
} }
...@@ -138,7 +140,8 @@ import java.util.List; ...@@ -138,7 +140,8 @@ import java.util.List;
pps.endNalUnit(discardPadding); pps.endNalUnit(discardPadding);
if (sei.endNalUnit(discardPadding)) { if (sei.endNalUnit(discardPadding)) {
int unescapedLength = unescapeStream(sei.nalData, sei.nalLength); int unescapedLength = unescapeStream(sei.nalData, sei.nalLength);
seiReader.read(sei.nalData, 0, unescapedLength, pesTimeUs); seiWrapper.reset(sei.nalData, unescapedLength);
seiReader.consume(seiWrapper, pesTimeUs, true);
} }
} }
...@@ -230,7 +233,7 @@ import java.util.List; ...@@ -230,7 +233,7 @@ import java.util.List;
} }
// Set the format. // Set the format.
setMediaFormat(MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE, setFormat(MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE,
frameWidth, frameHeight, initializationData)); frameWidth, frameHeight, initializationData));
} }
......
...@@ -16,120 +16,80 @@ ...@@ -16,120 +16,80 @@
package com.google.android.exoplayer.hls.parser; package com.google.android.exoplayer.hls.parser;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.util.ParsableByteArray;
import java.io.IOException; import java.io.IOException;
/** /**
* Facilitates extraction of media samples for HLS playbacks. * Facilitates extraction of media samples for HLS playbacks.
*/ */
// TODO: Consider consolidating more common logic in this base class. public interface HlsExtractor {
public abstract class HlsExtractor {
private final boolean shouldSpliceIn;
// Accessed only by the consuming thread.
private boolean spliceConfigured;
public HlsExtractor(boolean shouldSpliceIn) {
this.shouldSpliceIn = shouldSpliceIn;
}
/** /**
* Attempts to configure a splice from this extractor to the next. * An object to which extracted data should be output.
* <p>
* The splice is performed such that for each track the samples read from the next extractor
* start with a keyframe, and continue from where the samples read from this extractor finish.
* A successful splice may discard samples from either or both extractors.
* <p>
* Splice configuration may fail if the next extractor is not yet in a state that allows the
* splice to be performed. Calling this method is a noop if the splice has already been
* configured. Hence this method should be called repeatedly during the window within which a
* splice can be performed.
*
* @param nextExtractor The extractor being spliced to.
*/ */
public final void configureSpliceTo(HlsExtractor nextExtractor) { public interface ExtractorOutput {
if (spliceConfigured || !nextExtractor.shouldSpliceIn || !nextExtractor.isPrepared()) {
// The splice is already configured, or the next extractor doesn't want to be spliced in, or
// the next extractor isn't ready to be spliced in.
return;
}
boolean spliceConfigured = true;
int trackCount = getTrackCount();
for (int i = 0; i < trackCount; i++) {
spliceConfigured &= getSampleQueue(i).configureSpliceTo(nextExtractor.getSampleQueue(i));
}
this.spliceConfigured = spliceConfigured;
return;
}
/** /**
* Gets the number of available tracks. * Obtains a {@link TrackOutput} to which extracted data should be output for a given track.
* <p>
* This method should only be called after the extractor has been prepared.
* *
* @return The number of available tracks. * @param trackId A stable track id.
* @return The corresponding {@link TrackOutput}.
*/ */
public abstract int getTrackCount(); TrackOutput getTrackOutput(int trackId);
/** }
* Gets the format of the specified track.
* <p>
* This method must only be called after the extractor has been prepared.
*
* @param track The track index.
* @return The corresponding format.
*/
public abstract MediaFormat getFormat(int track);
/** /**
* Whether the extractor is prepared. * An object to which extracted data belonging to a given track should be output.
*
* @return True if the extractor is prepared. False otherwise.
*/ */
public abstract boolean isPrepared(); public interface TrackOutput {
/** int appendData(DataSource dataSource, int length) throws IOException;
* Releases the extractor, recycling any pending or incomplete samples to the sample pool.
* <p> void appendData(ParsableByteArray data, int length);
* This method should not be called whilst {@link #read(DataSource)} is also being invoked.
*/ void startSample(long timeUs, int offset);
public abstract void release();
void commitSample(int flags, int offset, byte[] encryptionKey);
boolean isWritingSample();
}
/** /**
* Gets the largest timestamp of any sample parsed by the extractor. * Initializes the extractor.
* *
* @return The largest timestamp, or {@link Long#MIN_VALUE} if no samples have been parsed. * @param output An {@link ExtractorOutput} to which extracted data should be output.
*/ */
public abstract long getLargestSampleTimestamp(); void init(ExtractorOutput output);
/** /**
* Gets the next sample for the specified track. * Whether the extractor is prepared.
* *
* @param track The track from which to read. * @return True if the extractor is prepared. False otherwise.
* @param holder A {@link SampleHolder} into which the sample should be read.
* @return True if a sample was read. False otherwise.
*/ */
public abstract boolean getSample(int track, SampleHolder holder); boolean isPrepared();
/** /**
* Discards samples for the specified track up to the specified time. * Gets the number of available tracks.
* <p>
* This method should only be called after the extractor has been prepared.
* *
* @param track The track from which samples should be discarded. * @return The number of available tracks.
* @param timeUs The time up to which samples should be discarded, in microseconds.
*/ */
public abstract void discardUntil(int track, long timeUs); int getTrackCount();
/** /**
* Whether samples are available for reading from {@link #getSample(int, SampleHolder)} for the * Gets the format of the specified track.
* specified track. * <p>
* This method must only be called after the extractor has been prepared.
* *
* @return True if samples are available for reading from {@link #getSample(int, SampleHolder)} * @param track The track index.
* for the specified track. False otherwise. * @return The corresponding format.
*/ */
public abstract boolean hasSamples(int track); MediaFormat getFormat(int track);
/** /**
* Reads up to a single TS packet. * Reads up to a single TS packet.
...@@ -138,14 +98,6 @@ public abstract class HlsExtractor { ...@@ -138,14 +98,6 @@ public abstract class HlsExtractor {
* @throws IOException If an error occurred reading from the source. * @throws IOException If an error occurred reading from the source.
* @return The number of bytes read from the source. * @return The number of bytes read from the source.
*/ */
public abstract int read(DataSource dataSource) throws IOException; int read(DataSource dataSource) throws IOException;
/**
* Gets the {@link SampleQueue} for the specified track.
*
* @param track The track index.
* @return The corresponding sample queue.
*/
protected abstract SampleQueue getSampleQueue(int track);
} }
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.hls.parser;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.hls.parser.HlsExtractor.TrackOutput;
import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.util.Assertions;
import android.util.SparseArray;
import java.io.IOException;
/**
* Wraps a {@link HlsExtractor}, adding functionality to enable reading of the extracted samples.
*/
public final class HlsExtractorWrapper implements HlsExtractor.ExtractorOutput {
private final BufferPool bufferPool;
private final HlsExtractor extractor;
private final boolean shouldSpliceIn;
private SparseArray<SampleQueue> sampleQueues;
// Accessed only by the consuming thread.
private boolean spliceConfigured;
public HlsExtractorWrapper(BufferPool bufferPool, HlsExtractor extractor,
boolean shouldSpliceIn) {
this.bufferPool = bufferPool;
this.extractor = extractor;
this.shouldSpliceIn = shouldSpliceIn;
sampleQueues = new SparseArray<SampleQueue>();
extractor.init(this);
}
/**
* Attempts to configure a splice from this extractor to the next.
* <p>
* The splice is performed such that for each track the samples read from the next extractor
* start with a keyframe, and continue from where the samples read from this extractor finish.
* A successful splice may discard samples from either or both extractors.
* <p>
* Splice configuration may fail if the next extractor is not yet in a state that allows the
* splice to be performed. Calling this method is a noop if the splice has already been
* configured. Hence this method should be called repeatedly during the window within which a
* splice can be performed.
*
* @param nextExtractor The extractor being spliced to.
*/
public final void configureSpliceTo(HlsExtractorWrapper nextExtractor) {
if (spliceConfigured || !nextExtractor.shouldSpliceIn || !nextExtractor.isPrepared()) {
// The splice is already configured, or the next extractor doesn't want to be spliced in, or
// the next extractor isn't ready to be spliced in.
return;
}
boolean spliceConfigured = true;
int trackCount = getTrackCount();
for (int i = 0; i < trackCount; i++) {
SampleQueue currentSampleQueue = sampleQueues.valueAt(i);
SampleQueue nextSampleQueue = nextExtractor.sampleQueues.valueAt(i);
spliceConfigured &= currentSampleQueue.configureSpliceTo(nextSampleQueue);
}
this.spliceConfigured = spliceConfigured;
return;
}
/**
* Gets the number of available tracks.
* <p>
* This method should only be called after the extractor has been prepared.
*
* @return The number of available tracks.
*/
public int getTrackCount() {
return extractor.getTrackCount();
}
/**
* Gets the format of the specified track.
* <p>
* This method must only be called after the extractor has been prepared.
*
* @param track The track index.
* @return The corresponding format.
*/
public MediaFormat getFormat(int track) {
return extractor.getFormat(track);
}
/**
* Whether the extractor is prepared.
*
* @return True if the extractor is prepared. False otherwise.
*/
public boolean isPrepared() {
return extractor.isPrepared();
}
/**
* Releases the extractor, recycling any pending or incomplete samples to the sample pool.
* <p>
* This method should not be called whilst {@link #read(DataSource)} is also being invoked.
*/
public void release() {
for (int i = 0; i < sampleQueues.size(); i++) {
sampleQueues.valueAt(i).release();
}
}
/**
* Gets the largest timestamp of any sample parsed by the extractor.
*
* @return The largest timestamp, or {@link Long#MIN_VALUE} if no samples have been parsed.
*/
public long getLargestSampleTimestamp() {
long largestParsedTimestampUs = Long.MIN_VALUE;
for (int i = 0; i < sampleQueues.size(); i++) {
largestParsedTimestampUs = Math.max(largestParsedTimestampUs,
sampleQueues.valueAt(i).getLargestParsedTimestampUs());
}
return largestParsedTimestampUs;
}
/**
* Gets the next sample for the specified track.
*
* @param track The track from which to read.
* @param holder A {@link SampleHolder} into which the sample should be read.
* @return True if a sample was read. False otherwise.
*/
public boolean getSample(int track, SampleHolder holder) {
Assertions.checkState(isPrepared());
return sampleQueues.valueAt(track).getSample(holder);
}
/**
* Discards samples for the specified track up to the specified time.
*
* @param track The track from which samples should be discarded.
* @param timeUs The time up to which samples should be discarded, in microseconds.
*/
public void discardUntil(int track, long timeUs) {
Assertions.checkState(isPrepared());
sampleQueues.valueAt(track).discardUntil(timeUs);
}
/**
* Whether samples are available for reading from {@link #getSample(int, SampleHolder)} for the
* specified track.
*
* @return True if samples are available for reading from {@link #getSample(int, SampleHolder)}
* for the specified track. False otherwise.
*/
public boolean hasSamples(int track) {
Assertions.checkState(isPrepared());
return !sampleQueues.valueAt(track).isEmpty();
}
/**
* Reads up to a single TS packet.
*
* @param dataSource The {@link DataSource} from which to read.
* @throws IOException If an error occurred reading from the source.
* @return The number of bytes read from the source.
*/
public int read(DataSource dataSource) throws IOException {
return extractor.read(dataSource);
}
// ExtractorOutput implementation.
@Override
public TrackOutput getTrackOutput(int id) {
SampleQueue sampleQueue = sampleQueues.get(id);
if (sampleQueue == null) {
sampleQueue = new SampleQueue(bufferPool);
sampleQueues.put(id, sampleQueue);
}
return sampleQueue;
}
}
...@@ -17,7 +17,7 @@ package com.google.android.exoplayer.hls.parser; ...@@ -17,7 +17,7 @@ package com.google.android.exoplayer.hls.parser;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.upstream.BufferPool; import com.google.android.exoplayer.hls.parser.HlsExtractor.TrackOutput;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
/** /**
...@@ -25,24 +25,24 @@ import com.google.android.exoplayer.util.ParsableByteArray; ...@@ -25,24 +25,24 @@ import com.google.android.exoplayer.util.ParsableByteArray;
*/ */
/* package */ class Id3Reader extends ElementaryStreamReader { /* package */ class Id3Reader extends ElementaryStreamReader {
public Id3Reader(BufferPool bufferPool) { public Id3Reader(TrackOutput output) {
super(bufferPool); super(output);
setMediaFormat(MediaFormat.createId3Format()); setFormat(MediaFormat.createId3Format());
} }
@Override @Override
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) { public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
if (startOfPacket) { if (startOfPacket) {
startSample(pesTimeUs); output.startSample(pesTimeUs, 0);
} }
if (writingSample()) { if (output.isWritingSample()) {
appendData(data, data.bytesLeft()); output.appendData(data, data.bytesLeft());
} }
} }
@Override @Override
public void packetFinished() { public void packetFinished() {
commitSample(C.SAMPLE_FLAG_SYNC); output.commitSample(C.SAMPLE_FLAG_SYNC, 0, null);
} }
} }
...@@ -16,8 +16,8 @@ ...@@ -16,8 +16,8 @@
package com.google.android.exoplayer.hls.parser; package com.google.android.exoplayer.hls.parser;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.hls.parser.HlsExtractor.TrackOutput;
import com.google.android.exoplayer.upstream.BufferPool; import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
...@@ -29,7 +29,7 @@ import java.io.IOException; ...@@ -29,7 +29,7 @@ import java.io.IOException;
* the first sample returned from the queue is a keyframe, allowing splicing to another queue, and * the first sample returned from the queue is a keyframe, allowing splicing to another queue, and
* so on. * so on.
*/ */
/* package */ abstract class SampleQueue { public final class SampleQueue implements TrackOutput {
private final RollingSampleBuffer rollingBuffer; private final RollingSampleBuffer rollingBuffer;
private final SampleHolder sampleInfoHolder; private final SampleHolder sampleInfoHolder;
...@@ -43,10 +43,9 @@ import java.io.IOException; ...@@ -43,10 +43,9 @@ import java.io.IOException;
private boolean writingSample; private boolean writingSample;
// Accessed by both the loading and consuming threads. // Accessed by both the loading and consuming threads.
private volatile MediaFormat mediaFormat;
private volatile long largestParsedTimestampUs; private volatile long largestParsedTimestampUs;
protected SampleQueue(BufferPool bufferPool) { public SampleQueue(BufferPool bufferPool) {
rollingBuffer = new RollingSampleBuffer(bufferPool); rollingBuffer = new RollingSampleBuffer(bufferPool);
sampleInfoHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED); sampleInfoHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED);
needKeyframe = true; needKeyframe = true;
...@@ -65,14 +64,6 @@ import java.io.IOException; ...@@ -65,14 +64,6 @@ import java.io.IOException;
return largestParsedTimestampUs; return largestParsedTimestampUs;
} }
public boolean hasMediaFormat() {
return mediaFormat != null;
}
public MediaFormat getMediaFormat() {
return mediaFormat;
}
public boolean isEmpty() { public boolean isEmpty() {
return !advanceToEligibleSample(); return !advanceToEligibleSample();
} }
...@@ -169,45 +160,34 @@ import java.io.IOException; ...@@ -169,45 +160,34 @@ import java.io.IOException;
return true; return true;
} }
// Called by the loading thread. // TrackOutput implementation. Called by the loading thread.
protected boolean writingSample() { @Override
return writingSample; public int appendData(DataSource dataSource, int length) throws IOException {
} return rollingBuffer.appendData(dataSource, length);
protected void setMediaFormat(MediaFormat mediaFormat) {
this.mediaFormat = mediaFormat;
} }
protected void startSample(long sampleTimeUs) { @Override
startSample(sampleTimeUs, 0); public void appendData(ParsableByteArray buffer, int length) {
rollingBuffer.appendData(buffer, length);
} }
protected void startSample(long sampleTimeUs, int offset) { @Override
public void startSample(long sampleTimeUs, int offset) {
writingSample = true; writingSample = true;
largestParsedTimestampUs = Math.max(largestParsedTimestampUs, sampleTimeUs); largestParsedTimestampUs = Math.max(largestParsedTimestampUs, sampleTimeUs);
rollingBuffer.startSample(sampleTimeUs, offset); rollingBuffer.startSample(sampleTimeUs, offset);
} }
protected int appendData(DataSource dataSource, int length) throws IOException { @Override
return rollingBuffer.appendData(dataSource, length); public void commitSample(int flags, int offset, byte[] encryptionKey) {
}
protected void appendData(ParsableByteArray buffer, int length) {
rollingBuffer.appendData(buffer, length);
}
protected void commitSample(int flags) {
commitSample(flags, 0, null);
}
protected void commitSample(int flags, int offset) {
commitSample(flags, offset, null);
}
protected void commitSample(int flags, int offset, byte[] encryptionKey) {
rollingBuffer.commitSample(flags, offset, encryptionKey); rollingBuffer.commitSample(flags, offset, encryptionKey);
writingSample = false; writingSample = false;
} }
@Override
public boolean isWritingSample() {
return writingSample;
}
} }
...@@ -17,8 +17,8 @@ package com.google.android.exoplayer.hls.parser; ...@@ -17,8 +17,8 @@ package com.google.android.exoplayer.hls.parser;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.hls.parser.HlsExtractor.TrackOutput;
import com.google.android.exoplayer.text.eia608.Eia608Parser; import com.google.android.exoplayer.text.eia608.Eia608Parser;
import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
/** /**
...@@ -27,20 +27,17 @@ import com.google.android.exoplayer.util.ParsableByteArray; ...@@ -27,20 +27,17 @@ import com.google.android.exoplayer.util.ParsableByteArray;
* TODO: Technically, we shouldn't allow a sample to be read from the queue until we're sure that * 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. * a sample with an earlier timestamp won't be added to it.
*/ */
/* package */ class SeiReader extends SampleQueue { /* package */ class SeiReader extends ElementaryStreamReader {
private final ParsableByteArray seiBuffer; public SeiReader(TrackOutput output) {
super(output);
public SeiReader(BufferPool bufferPool) { setFormat(MediaFormat.createEia608Format());
super(bufferPool);
setMediaFormat(MediaFormat.createEia608Format());
seiBuffer = new ParsableByteArray();
} }
public void read(byte[] data, int position, int limit, long pesTimeUs) { @Override
seiBuffer.reset(data, limit); public void consume(ParsableByteArray seiBuffer, long pesTimeUs, boolean startOfPacket) {
// Skip the NAL prefix and type. // Skip the NAL prefix and type.
seiBuffer.setPosition(position + 4); seiBuffer.skip(4);
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 */) {
...@@ -58,13 +55,18 @@ import com.google.android.exoplayer.util.ParsableByteArray; ...@@ -58,13 +55,18 @@ import com.google.android.exoplayer.util.ParsableByteArray;
} while (b == 0xFF); } while (b == 0xFF);
// Process the payload. We only support EIA-608 payloads currently. // Process the payload. We only support EIA-608 payloads currently.
if (Eia608Parser.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) { if (Eia608Parser.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) {
startSample(pesTimeUs); output.startSample(pesTimeUs, 0);
appendData(seiBuffer, payloadSize); output.appendData(seiBuffer, payloadSize);
commitSample(C.SAMPLE_FLAG_SYNC); output.commitSample(C.SAMPLE_FLAG_SYNC, 0, null);
} else { } else {
seiBuffer.skip(payloadSize); seiBuffer.skip(payloadSize);
} }
} }
} }
@Override
public void packetFinished() {
// Do nothing.
}
} }
...@@ -17,8 +17,6 @@ package com.google.android.exoplayer.hls.parser; ...@@ -17,8 +17,6 @@ package com.google.android.exoplayer.hls.parser;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.upstream.BufferPool;
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.ParsableBitArray; import com.google.android.exoplayer.util.ParsableBitArray;
...@@ -32,7 +30,7 @@ import java.io.IOException; ...@@ -32,7 +30,7 @@ import java.io.IOException;
/** /**
* Facilitates the extraction of data from the MPEG-2 TS container format. * Facilitates the extraction of data from the MPEG-2 TS container format.
*/ */
public final class TsExtractor extends HlsExtractor { public final class TsExtractor implements HlsExtractor {
private static final String TAG = "TsExtractor"; private static final String TAG = "TsExtractor";
...@@ -48,13 +46,13 @@ public final class TsExtractor extends HlsExtractor { ...@@ -48,13 +46,13 @@ public final class TsExtractor extends HlsExtractor {
private static final long MAX_PTS = 0x1FFFFFFFFL; private static final long MAX_PTS = 0x1FFFFFFFFL;
private final ParsableByteArray tsPacketBuffer; private final ParsableByteArray tsPacketBuffer;
private final SparseArray<SampleQueue> sampleQueues; // Indexed by streamType private final SparseArray<ElementaryStreamReader> streamReaders; // Indexed by streamType
private final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid private final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid
private final BufferPool bufferPool;
private final long firstSampleTimestamp; private final long firstSampleTimestamp;
private final ParsableBitArray tsScratch; private final ParsableBitArray tsScratch;
// Accessed only by the loading thread. // Accessed only by the loading thread.
private ExtractorOutput output;
private int tsPacketBytesRead; private int tsPacketBytesRead;
private long timestampOffsetUs; private long timestampOffsetUs;
private long lastPts; private long lastPts;
...@@ -62,28 +60,31 @@ public final class TsExtractor extends HlsExtractor { ...@@ -62,28 +60,31 @@ public final class TsExtractor extends HlsExtractor {
// Accessed by both the loading and consuming threads. // Accessed by both the loading and consuming threads.
private volatile boolean prepared; private volatile boolean prepared;
public TsExtractor(boolean shouldSpliceIn, long firstSampleTimestamp, BufferPool bufferPool) { public TsExtractor(long firstSampleTimestamp) {
super(shouldSpliceIn);
this.firstSampleTimestamp = firstSampleTimestamp; this.firstSampleTimestamp = firstSampleTimestamp;
this.bufferPool = bufferPool;
tsScratch = new ParsableBitArray(new byte[3]); tsScratch = new ParsableBitArray(new byte[3]);
tsPacketBuffer = new ParsableByteArray(TS_PACKET_SIZE); tsPacketBuffer = new ParsableByteArray(TS_PACKET_SIZE);
sampleQueues = new SparseArray<SampleQueue>(); streamReaders = new SparseArray<ElementaryStreamReader>();
tsPayloadReaders = new SparseArray<TsPayloadReader>(); tsPayloadReaders = new SparseArray<TsPayloadReader>();
tsPayloadReaders.put(TS_PAT_PID, new PatReader()); tsPayloadReaders.put(TS_PAT_PID, new PatReader());
lastPts = Long.MIN_VALUE; lastPts = Long.MIN_VALUE;
} }
@Override @Override
public void init(ExtractorOutput output) {
this.output = output;
}
@Override
public int getTrackCount() { public int getTrackCount() {
Assertions.checkState(prepared); Assertions.checkState(prepared);
return sampleQueues.size(); return streamReaders.size();
} }
@Override @Override
public MediaFormat getFormat(int track) { public MediaFormat getFormat(int track) {
Assertions.checkState(prepared); Assertions.checkState(prepared);
return sampleQueues.valueAt(track).getMediaFormat(); return streamReaders.valueAt(track).getFormat();
} }
@Override @Override
...@@ -91,48 +92,13 @@ public final class TsExtractor extends HlsExtractor { ...@@ -91,48 +92,13 @@ public final class TsExtractor extends HlsExtractor {
return prepared; return prepared;
} }
@Override
public void release() {
for (int i = 0; i < sampleQueues.size(); i++) {
sampleQueues.valueAt(i).release();
}
}
@Override
public long getLargestSampleTimestamp() {
long largestParsedTimestampUs = Long.MIN_VALUE;
for (int i = 0; i < sampleQueues.size(); i++) {
largestParsedTimestampUs = Math.max(largestParsedTimestampUs,
sampleQueues.valueAt(i).getLargestParsedTimestampUs());
}
return largestParsedTimestampUs;
}
@Override
public boolean getSample(int track, SampleHolder holder) {
Assertions.checkState(prepared);
return sampleQueues.valueAt(track).getSample(holder);
}
@Override
public void discardUntil(int track, long timeUs) {
Assertions.checkState(prepared);
sampleQueues.valueAt(track).discardUntil(timeUs);
}
@Override
public boolean hasSamples(int track) {
Assertions.checkState(prepared);
return !sampleQueues.valueAt(track).isEmpty();
}
private boolean checkPrepared() { private boolean checkPrepared() {
int pesPayloadReaderCount = sampleQueues.size(); int pesPayloadReaderCount = streamReaders.size();
if (pesPayloadReaderCount == 0) { if (pesPayloadReaderCount == 0) {
return false; return false;
} }
for (int i = 0; i < pesPayloadReaderCount; i++) { for (int i = 0; i < pesPayloadReaderCount; i++) {
if (!sampleQueues.valueAt(i).hasMediaFormat()) { if (!streamReaders.valueAt(i).hasFormat()) {
return false; return false;
} }
} }
...@@ -183,7 +149,7 @@ public final class TsExtractor extends HlsExtractor { ...@@ -183,7 +149,7 @@ public final class TsExtractor extends HlsExtractor {
if (payloadExists) { if (payloadExists) {
TsPayloadReader payloadReader = tsPayloadReaders.get(pid); TsPayloadReader payloadReader = tsPayloadReaders.get(pid);
if (payloadReader != null) { if (payloadReader != null) {
payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator); payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator, output);
} }
} }
...@@ -194,11 +160,6 @@ public final class TsExtractor extends HlsExtractor { ...@@ -194,11 +160,6 @@ public final class TsExtractor extends HlsExtractor {
return bytesRead; return bytesRead;
} }
@Override
protected SampleQueue getSampleQueue(int track) {
return sampleQueues.valueAt(track);
}
/** /**
* Adjusts a PTS value to the corresponding time in microseconds, accounting for PTS wraparound. * Adjusts a PTS value to the corresponding time in microseconds, accounting for PTS wraparound.
* *
...@@ -231,7 +192,8 @@ public final class TsExtractor extends HlsExtractor { ...@@ -231,7 +192,8 @@ public final class TsExtractor extends HlsExtractor {
*/ */
private abstract static class TsPayloadReader { private abstract static class TsPayloadReader {
public abstract void consume(ParsableByteArray data, boolean payloadUnitStartIndicator); public abstract void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
ExtractorOutput output);
} }
...@@ -247,7 +209,8 @@ public final class TsExtractor extends HlsExtractor { ...@@ -247,7 +209,8 @@ public final class TsExtractor extends HlsExtractor {
} }
@Override @Override
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) { public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
ExtractorOutput output) {
// Skip pointer. // Skip pointer.
if (payloadUnitStartIndicator) { if (payloadUnitStartIndicator) {
int pointerField = data.readUnsignedByte(); int pointerField = data.readUnsignedByte();
...@@ -286,7 +249,8 @@ public final class TsExtractor extends HlsExtractor { ...@@ -286,7 +249,8 @@ public final class TsExtractor extends HlsExtractor {
} }
@Override @Override
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) { public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
ExtractorOutput output) {
// Skip pointer. // Skip pointer.
if (payloadUnitStartIndicator) { if (payloadUnitStartIndicator) {
int pointerField = data.readUnsignedByte(); int pointerField = data.readUnsignedByte();
...@@ -323,27 +287,28 @@ public final class TsExtractor extends HlsExtractor { ...@@ -323,27 +287,28 @@ public final class TsExtractor extends HlsExtractor {
data.skip(esInfoLength); data.skip(esInfoLength);
entriesSize -= esInfoLength + 5; entriesSize -= esInfoLength + 5;
if (sampleQueues.get(streamType) != null) { if (streamReaders.get(streamType) != null) {
continue; continue;
} }
ElementaryStreamReader pesPayloadReader = null; ElementaryStreamReader pesPayloadReader = null;
switch (streamType) { switch (streamType) {
case TS_STREAM_TYPE_AAC: case TS_STREAM_TYPE_AAC:
pesPayloadReader = new AdtsReader(bufferPool); pesPayloadReader = new AdtsReader(output.getTrackOutput(TS_STREAM_TYPE_AAC));
break; break;
case TS_STREAM_TYPE_H264: case TS_STREAM_TYPE_H264:
SeiReader seiReader = new SeiReader(bufferPool); SeiReader seiReader = new SeiReader(output.getTrackOutput(TS_STREAM_TYPE_EIA608));
sampleQueues.put(TS_STREAM_TYPE_EIA608, seiReader); streamReaders.put(TS_STREAM_TYPE_EIA608, seiReader);
pesPayloadReader = new H264Reader(bufferPool, seiReader); pesPayloadReader = new H264Reader(output.getTrackOutput(TS_STREAM_TYPE_H264),
seiReader);
break; break;
case TS_STREAM_TYPE_ID3: case TS_STREAM_TYPE_ID3:
pesPayloadReader = new Id3Reader(bufferPool); pesPayloadReader = new Id3Reader(output.getTrackOutput(TS_STREAM_TYPE_ID3));
break; break;
} }
if (pesPayloadReader != null) { if (pesPayloadReader != null) {
sampleQueues.put(streamType, pesPayloadReader); streamReaders.put(streamType, pesPayloadReader);
tsPayloadReaders.put(elementaryPid, new PesReader(pesPayloadReader)); tsPayloadReaders.put(elementaryPid, new PesReader(pesPayloadReader));
} }
} }
...@@ -387,7 +352,8 @@ public final class TsExtractor extends HlsExtractor { ...@@ -387,7 +352,8 @@ public final class TsExtractor extends HlsExtractor {
} }
@Override @Override
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) { public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
ExtractorOutput output) {
if (payloadUnitStartIndicator) { if (payloadUnitStartIndicator) {
switch (state) { switch (state) {
case STATE_FINDING_HEADER: case STATE_FINDING_HEADER:
......
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