diff --git a/library/src/main/java/com/google/android/exoplayer/hls/parser/AdtsExtractor.java b/library/src/main/java/com/google/android/exoplayer/hls/parser/AdtsExtractor.java index a22e17ce85..5915ea6885 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/parser/AdtsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/parser/AdtsExtractor.java @@ -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 diff --git a/library/src/main/java/com/google/android/exoplayer/hls/parser/AdtsReader.java b/library/src/main/java/com/google/android/exoplayer/hls/parser/AdtsReader.java index 81c643c7a8..fc7417a60a 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/parser/AdtsReader.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/parser/AdtsReader.java @@ -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); } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/parser/ElementaryStreamReader.java b/library/src/main/java/com/google/android/exoplayer/hls/parser/ElementaryStreamReader.java index 8522cbeb18..a441ff35b9 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/parser/ElementaryStreamReader.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/parser/ElementaryStreamReader.java @@ -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. * diff --git a/library/src/main/java/com/google/android/exoplayer/hls/parser/H264Reader.java b/library/src/main/java/com/google/android/exoplayer/hls/parser/H264Reader.java index ae9d0fe7a9..40b3b1ca32 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/parser/H264Reader.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/parser/H264Reader.java @@ -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)); } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/parser/HlsExtractor.java b/library/src/main/java/com/google/android/exoplayer/hls/parser/HlsExtractor.java index 92a4ac6717..e3b3e5468f 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/parser/HlsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/parser/HlsExtractor.java @@ -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. - *

- * 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. - *

- * 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. diff --git a/library/src/main/java/com/google/android/exoplayer/hls/parser/HlsExtractorWrapper.java b/library/src/main/java/com/google/android/exoplayer/hls/parser/HlsExtractorWrapper.java index b44626722f..40bc8f7280 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/parser/HlsExtractorWrapper.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/parser/HlsExtractorWrapper.java @@ -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 sampleQueues; private final boolean shouldSpliceIn; - private SparseArray 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; + } + } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/parser/Id3Reader.java b/library/src/main/java/com/google/android/exoplayer/hls/parser/Id3Reader.java index a76bcdbaa4..d48aa6e56d 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/parser/Id3Reader.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/parser/Id3Reader.java @@ -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 diff --git a/library/src/main/java/com/google/android/exoplayer/hls/parser/SampleQueue.java b/library/src/main/java/com/google/android/exoplayer/hls/parser/SampleQueue.java index 5e9bd7950f..4f78f494bb 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/parser/SampleQueue.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/parser/SampleQueue.java @@ -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); diff --git a/library/src/main/java/com/google/android/exoplayer/hls/parser/SeiReader.java b/library/src/main/java/com/google/android/exoplayer/hls/parser/SeiReader.java index 2bf48be730..979bc44d5f 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/parser/SeiReader.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/parser/SeiReader.java @@ -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 diff --git a/library/src/main/java/com/google/android/exoplayer/hls/parser/TsExtractor.java b/library/src/main/java/com/google/android/exoplayer/hls/parser/TsExtractor.java index 15eae10354..4a4417270a 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/parser/TsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/parser/TsExtractor.java @@ -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: