Further simplify HlsExtractor interface.

- Move to builder naming.
- Propagate formats to the TrackOutput instances, rather than having
  them be read through the Extractor. There was actually some weird
  indexing going on here before (which happened to work, but wasn't
  well defined).
This commit is contained in:
Oliver Woodman 2015-03-13 11:44:27 +00:00
parent 04cead415a
commit 1111dd73a0
10 changed files with 78 additions and 145 deletions

View File

@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer.hls.parser;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.util.ParsableByteArray;
@ -43,23 +42,9 @@ public class AdtsExtractor implements HlsExtractor {
}
@Override
public void init(ExtractorOutput output) {
adtsReader = new AdtsReader(output.getTrackOutput(0));
}
@Override
public int getTrackCount() {
return 1;
}
@Override
public MediaFormat getFormat(int track) {
return adtsReader.getFormat();
}
@Override
public boolean isPrepared() {
return adtsReader != null && adtsReader.hasFormat();
public void init(TrackOutputBuilder output) {
adtsReader = new AdtsReader(output.buildOutput(0));
output.allOutputsBuilt();
}
@Override

View File

@ -152,7 +152,7 @@ import java.util.Collections;
private void parseHeader() {
adtsScratch.setPosition(0);
if (!hasFormat()) {
if (!output.hasFormat()) {
int audioObjectType = adtsScratch.readBits(2) + 1;
int sampleRateIndex = adtsScratch.readBits(4);
adtsScratch.skipBits(1);
@ -167,7 +167,7 @@ import java.util.Collections;
MediaFormat.NO_VALUE, audioParams.second, audioParams.first,
Collections.singletonList(audioSpecificConfig));
frameDurationUs = (C.MICROS_PER_SECOND * 1024L) / mediaFormat.sampleRate;
setFormat(mediaFormat);
output.setFormat(mediaFormat);
} else {
adtsScratch.skipBits(10);
}

View File

@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer.hls.parser;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.hls.parser.HlsExtractor.TrackOutput;
import com.google.android.exoplayer.util.ParsableByteArray;
@ -25,7 +24,6 @@ import com.google.android.exoplayer.util.ParsableByteArray;
/* package */ abstract class ElementaryStreamReader {
protected final TrackOutput output;
private MediaFormat format;
/**
* @param output A {@link TrackOutput} to which samples should be written.
@ -34,29 +32,6 @@ import com.google.android.exoplayer.util.ParsableByteArray;
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;
}
/**
* Consumes (possibly partial) payload data.
*

View File

@ -88,7 +88,7 @@ import java.util.List;
int nalUnitOffsetInData = nextNalUnitOffset - limit;
if (nalUnitType == NAL_UNIT_TYPE_AUD) {
if (output.isWritingSample()) {
if (isKeyframe && !hasFormat() && sps.isCompleted() && pps.isCompleted()) {
if (isKeyframe && !output.hasFormat() && sps.isCompleted() && pps.isCompleted()) {
parseMediaFormat(sps, pps);
}
output.commitSample(isKeyframe ? C.SAMPLE_FLAG_SYNC : 0, nalUnitOffsetInData, null);
@ -120,7 +120,7 @@ import java.util.List;
}
private void feedNalUnitTargetBuffersStart(int nalUnitType) {
if (!hasFormat()) {
if (!output.hasFormat()) {
sps.startNalUnit(nalUnitType);
pps.startNalUnit(nalUnitType);
}
@ -128,7 +128,7 @@ import java.util.List;
}
private void feedNalUnitTargetBuffersData(byte[] dataArray, int offset, int limit) {
if (!hasFormat()) {
if (!output.hasFormat()) {
sps.appendToNalUnit(dataArray, offset, limit);
pps.appendToNalUnit(dataArray, offset, limit);
}
@ -233,7 +233,7 @@ import java.util.List;
}
// Set the format.
setFormat(MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE,
output.setFormat(MediaFormat.createVideoFormat(MimeTypes.VIDEO_H264, MediaFormat.NO_VALUE,
frameWidth, frameHeight, initializationData));
}

View File

@ -29,15 +29,21 @@ public interface HlsExtractor {
/**
* An object to which extracted data should be output.
*/
public interface ExtractorOutput {
public interface TrackOutputBuilder {
/**
* Obtains a {@link TrackOutput} to which extracted data should be output for a given track.
* Invoked to build a {@link TrackOutput} to which data should be output for a given track.
*
* @param trackId A stable track id.
* @return The corresponding {@link TrackOutput}.
*/
TrackOutput getTrackOutput(int trackId);
TrackOutput buildOutput(int trackId);
/**
* Invoked when all {@link TrackOutput}s have been built, meaning {@link #buildOutput(int)}
* will not be invoked again.
*/
void allOutputsBuilt();
}
@ -46,6 +52,12 @@ public interface HlsExtractor {
*/
public interface TrackOutput {
boolean hasFormat();
void setFormat(MediaFormat format);
boolean isWritingSample();
int appendData(DataSource dataSource, int length) throws IOException;
void appendData(ParsableByteArray data, int length);
@ -54,42 +66,14 @@ public interface HlsExtractor {
void commitSample(int flags, int offset, byte[] encryptionKey);
boolean isWritingSample();
}
/**
* Initializes the extractor.
*
* @param output An {@link ExtractorOutput} to which extracted data should be output.
* @param output A {@link TrackOutputBuilder} to which extracted data should be output.
*/
void init(ExtractorOutput output);
/**
* Whether the extractor is prepared.
*
* @return True if the extractor is prepared. False otherwise.
*/
boolean isPrepared();
/**
* 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.
*/
int 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.
*/
MediaFormat getFormat(int track);
void init(TrackOutputBuilder output);
/**
* Reads up to a single TS packet.

View File

@ -29,15 +29,17 @@ import java.io.IOException;
/**
* Wraps a {@link HlsExtractor}, adding functionality to enable reading of the extracted samples.
*/
public final class HlsExtractorWrapper implements HlsExtractor.ExtractorOutput {
public final class HlsExtractorWrapper implements HlsExtractor.TrackOutputBuilder {
private final BufferPool bufferPool;
private final HlsExtractor extractor;
private final SparseArray<SampleQueue> sampleQueues;
private final boolean shouldSpliceIn;
private SparseArray<SampleQueue> sampleQueues;
private volatile boolean outputsBuilt;
// Accessed only by the consuming thread.
private boolean prepared;
private boolean spliceConfigured;
public HlsExtractorWrapper(BufferPool bufferPool, HlsExtractor extractor,
@ -88,7 +90,7 @@ public final class HlsExtractorWrapper implements HlsExtractor.ExtractorOutput {
* @return The number of available tracks.
*/
public int getTrackCount() {
return extractor.getTrackCount();
return sampleQueues.size();
}
/**
@ -100,7 +102,7 @@ public final class HlsExtractorWrapper implements HlsExtractor.ExtractorOutput {
* @return The corresponding format.
*/
public MediaFormat getFormat(int track) {
return extractor.getFormat(track);
return sampleQueues.valueAt(track).getFormat();
}
/**
@ -109,7 +111,15 @@ public final class HlsExtractorWrapper implements HlsExtractor.ExtractorOutput {
* @return True if the extractor is prepared. False otherwise.
*/
public boolean isPrepared() {
return extractor.isPrepared();
if (!prepared && outputsBuilt) {
for (int i = 0; i < sampleQueues.size(); i++) {
if (!sampleQueues.valueAt(i).hasFormat()) {
return false;
}
}
prepared = true;
}
return prepared;
}
/**
@ -186,13 +196,15 @@ public final class HlsExtractorWrapper implements HlsExtractor.ExtractorOutput {
// ExtractorOutput implementation.
@Override
public TrackOutput getTrackOutput(int id) {
SampleQueue sampleQueue = sampleQueues.get(id);
if (sampleQueue == null) {
sampleQueue = new SampleQueue(bufferPool);
sampleQueues.put(id, sampleQueue);
}
public TrackOutput buildOutput(int id) {
SampleQueue sampleQueue = new SampleQueue(bufferPool);
sampleQueues.put(id, sampleQueue);
return sampleQueue;
}
@Override
public void allOutputsBuilt() {
this.outputsBuilt = true;
}
}

View File

@ -27,7 +27,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
public Id3Reader(TrackOutput output) {
super(output);
setFormat(MediaFormat.createId3Format());
output.setFormat(MediaFormat.createId3Format());
}
@Override

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer.hls.parser;
import com.google.android.exoplayer.C;
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;
@ -44,6 +45,7 @@ public final class SampleQueue implements TrackOutput {
// Accessed by both the loading and consuming threads.
private volatile long largestParsedTimestampUs;
private volatile MediaFormat format;
public SampleQueue(BufferPool bufferPool) {
rollingBuffer = new RollingSampleBuffer(bufferPool);
@ -60,6 +62,10 @@ public final class SampleQueue implements TrackOutput {
// Called by the consuming thread.
public MediaFormat getFormat() {
return format;
}
public long getLargestParsedTimestampUs() {
return largestParsedTimestampUs;
}
@ -162,6 +168,16 @@ public final class SampleQueue implements TrackOutput {
// TrackOutput implementation. Called by the loading thread.
@Override
public boolean hasFormat() {
return format != null;
}
@Override
public void setFormat(MediaFormat format) {
this.format = format;
}
@Override
public int appendData(DataSource dataSource, int length) throws IOException {
return rollingBuffer.appendData(dataSource, length);

View File

@ -31,7 +31,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
public SeiReader(TrackOutput output) {
super(output);
setFormat(MediaFormat.createEia608Format());
output.setFormat(MediaFormat.createEia608Format());
}
@Override

View File

@ -16,9 +16,7 @@
package com.google.android.exoplayer.hls.parser;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.ParsableBitArray;
import com.google.android.exoplayer.util.ParsableByteArray;
@ -52,14 +50,11 @@ public final class TsExtractor implements HlsExtractor {
private final ParsableBitArray tsScratch;
// Accessed only by the loading thread.
private ExtractorOutput output;
private TrackOutputBuilder output;
private int tsPacketBytesRead;
private long timestampOffsetUs;
private long lastPts;
// Accessed by both the loading and consuming threads.
private volatile boolean prepared;
public TsExtractor(long firstSampleTimestamp) {
this.firstSampleTimestamp = firstSampleTimestamp;
tsScratch = new ParsableBitArray(new byte[3]);
@ -71,40 +66,10 @@ public final class TsExtractor implements HlsExtractor {
}
@Override
public void init(ExtractorOutput output) {
public void init(TrackOutputBuilder output) {
this.output = output;
}
@Override
public int getTrackCount() {
Assertions.checkState(prepared);
return streamReaders.size();
}
@Override
public MediaFormat getFormat(int track) {
Assertions.checkState(prepared);
return streamReaders.valueAt(track).getFormat();
}
@Override
public boolean isPrepared() {
return prepared;
}
private boolean checkPrepared() {
int pesPayloadReaderCount = streamReaders.size();
if (pesPayloadReaderCount == 0) {
return false;
}
for (int i = 0; i < pesPayloadReaderCount; i++) {
if (!streamReaders.valueAt(i).hasFormat()) {
return false;
}
}
return true;
}
@Override
public int read(DataSource dataSource) throws IOException {
int bytesRead = dataSource.read(tsPacketBuffer.data, tsPacketBytesRead,
@ -153,10 +118,6 @@ public final class TsExtractor implements HlsExtractor {
}
}
if (!prepared) {
prepared = checkPrepared();
}
return bytesRead;
}
@ -193,7 +154,7 @@ public final class TsExtractor implements HlsExtractor {
private abstract static class TsPayloadReader {
public abstract void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
ExtractorOutput output);
TrackOutputBuilder output);
}
@ -210,7 +171,7 @@ public final class TsExtractor implements HlsExtractor {
@Override
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
ExtractorOutput output) {
TrackOutputBuilder output) {
// Skip pointer.
if (payloadUnitStartIndicator) {
int pointerField = data.readUnsignedByte();
@ -250,7 +211,7 @@ public final class TsExtractor implements HlsExtractor {
@Override
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
ExtractorOutput output) {
TrackOutputBuilder output) {
// Skip pointer.
if (payloadUnitStartIndicator) {
int pointerField = data.readUnsignedByte();
@ -294,16 +255,16 @@ public final class TsExtractor implements HlsExtractor {
ElementaryStreamReader pesPayloadReader = null;
switch (streamType) {
case TS_STREAM_TYPE_AAC:
pesPayloadReader = new AdtsReader(output.getTrackOutput(TS_STREAM_TYPE_AAC));
pesPayloadReader = new AdtsReader(output.buildOutput(TS_STREAM_TYPE_AAC));
break;
case TS_STREAM_TYPE_H264:
SeiReader seiReader = new SeiReader(output.getTrackOutput(TS_STREAM_TYPE_EIA608));
SeiReader seiReader = new SeiReader(output.buildOutput(TS_STREAM_TYPE_EIA608));
streamReaders.put(TS_STREAM_TYPE_EIA608, seiReader);
pesPayloadReader = new H264Reader(output.getTrackOutput(TS_STREAM_TYPE_H264),
pesPayloadReader = new H264Reader(output.buildOutput(TS_STREAM_TYPE_H264),
seiReader);
break;
case TS_STREAM_TYPE_ID3:
pesPayloadReader = new Id3Reader(output.getTrackOutput(TS_STREAM_TYPE_ID3));
pesPayloadReader = new Id3Reader(output.buildOutput(TS_STREAM_TYPE_ID3));
break;
}
@ -313,7 +274,7 @@ public final class TsExtractor implements HlsExtractor {
}
}
// Skip CRC_32.
output.allOutputsBuilt();
}
}
@ -353,7 +314,7 @@ public final class TsExtractor implements HlsExtractor {
@Override
public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator,
ExtractorOutput output) {
TrackOutputBuilder output) {
if (payloadUnitStartIndicator) {
switch (state) {
case STATE_FINDING_HEADER: