diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java b/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java index 7817123830..a22e825960 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java @@ -117,9 +117,12 @@ import java.util.Locale; new Sample("Apple master playlist advanced", "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/" + "bipbop_16x9_variant.m3u8", DemoUtil.TYPE_HLS), - new Sample("Apple single media playlist", + new Sample("Apple TS media playlist", "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/" + "prog_index.m3u8", DemoUtil.TYPE_HLS), + new Sample("Apple AAC media playlist", + "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear0/" + + "prog_index.m3u8", DemoUtil.TYPE_HLS), }; public static final Sample[] MISC = new Sample[] { diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index 90bc497478..dc70320f0d 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -17,6 +17,8 @@ package com.google.android.exoplayer.hls; import com.google.android.exoplayer.C; import com.google.android.exoplayer.MediaFormat; +import com.google.android.exoplayer.hls.parser.AdtsExtractor; +import com.google.android.exoplayer.hls.parser.HlsExtractor; import com.google.android.exoplayer.hls.parser.TsExtractor; import com.google.android.exoplayer.upstream.Aes128DataSource; import com.google.android.exoplayer.upstream.BandwidthMeter; @@ -105,6 +107,7 @@ public class HlsChunkSource { public static final long DEFAULT_MAX_BUFFER_TO_SWITCH_DOWN_MS = 20000; private static final String TAG = "HlsChunkSource"; + private static final String AAC_FILE_EXTENSION = ".aac"; private static final float BANDWIDTH_FRACTION = 0.8f; private final BufferPool bufferPool; @@ -332,9 +335,11 @@ public class HlsChunkSource { boolean isLastChunk = !mediaPlaylist.live && chunkIndex == mediaPlaylist.segments.size() - 1; // Configure the extractor that will read the chunk. - TsExtractor extractor; + HlsExtractor extractor; if (previousTsChunk == null || segment.discontinuity || switchingVariant || liveDiscontinuity) { - extractor = new TsExtractor(startTimeUs, switchingVariantSpliced, bufferPool); + extractor = chunkUri.getLastPathSegment().endsWith(AAC_FILE_EXTENSION) + ? new AdtsExtractor(switchingVariantSpliced, startTimeUs, bufferPool) + : new TsExtractor(switchingVariantSpliced, startTimeUs, bufferPool); } else { extractor = previousTsChunk.extractor; } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java index 4603577ff9..b8fffd4c11 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java @@ -21,7 +21,7 @@ import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.TrackInfo; import com.google.android.exoplayer.TrackRenderer; -import com.google.android.exoplayer.hls.parser.TsExtractor; +import com.google.android.exoplayer.hls.parser.HlsExtractor; import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.Loader.Loadable; import com.google.android.exoplayer.util.Assertions; @@ -44,7 +44,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { private static final int NO_RESET_PENDING = -1; private final HlsChunkSource chunkSource; - private final LinkedList extractors; + private final LinkedList extractors; private final boolean frameAccurateSeeking; private final int minLoadableRetryCount; @@ -83,7 +83,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { this.frameAccurateSeeking = frameAccurateSeeking; this.remainingReleaseCount = downstreamRendererCount; this.minLoadableRetryCount = minLoadableRetryCount; - extractors = new LinkedList(); + extractors = new LinkedList(); } @Override @@ -96,7 +96,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { } continueBufferingInternal(); if (!extractors.isEmpty()) { - TsExtractor extractor = extractors.getFirst(); + HlsExtractor extractor = extractors.getFirst(); if (extractor.isPrepared()) { trackCount = extractor.getTrackCount(); trackEnabledStates = new boolean[trackCount]; @@ -195,7 +195,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { return NOTHING_READ; } - TsExtractor extractor = getCurrentExtractor(); + HlsExtractor extractor = getCurrentExtractor(); if (extractors.size() > 1) { // If there's more than one extractor, attempt to configure a seamless splice from the // current one to the next one. @@ -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. */ - private TsExtractor getCurrentExtractor() { - TsExtractor extractor = extractors.getFirst(); + private HlsExtractor getCurrentExtractor() { + HlsExtractor extractor = extractors.getFirst(); while (extractors.size() > 1 && !haveSamplesForEnabledTracks(extractor)) { // We're finished reading from the extractor for all tracks, and so can discard it. extractors.removeFirst().release(); @@ -338,7 +338,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { return extractor; } - private void discardSamplesForDisabledTracks(TsExtractor extractor, long timeUs) { + private void discardSamplesForDisabledTracks(HlsExtractor extractor, long timeUs) { if (!extractor.isPrepared()) { return; } @@ -349,7 +349,7 @@ public class HlsSampleSource implements SampleSource, Loader.Callback { } } - private boolean haveSamplesForEnabledTracks(TsExtractor extractor) { + private boolean haveSamplesForEnabledTracks(HlsExtractor extractor) { if (!extractor.isPrepared()) { return false; } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java b/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java index 36c1e30c8f..a66330bb5c 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/TsChunk.java @@ -15,7 +15,7 @@ */ package com.google.android.exoplayer.hls; -import com.google.android.exoplayer.hls.parser.TsExtractor; +import com.google.android.exoplayer.hls.parser.HlsExtractor; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSpec; @@ -51,7 +51,7 @@ public final class TsChunk extends HlsChunk { /** * The extractor into which this chunk is being consumed. */ - public final TsExtractor extractor; + public final HlsExtractor extractor; private int loadPosition; private volatile boolean loadFinished; @@ -60,16 +60,17 @@ public final class TsChunk extends HlsChunk { /** * @param dataSource A {@link DataSource} for loading the data. * @param dataSpec Defines the data to be loaded. + * @param extractor An extractor to parse samples from the data. * @param variantIndex The index of the variant in the master playlist. * @param startTimeUs The start time of the media contained by the chunk, in microseconds. * @param endTimeUs The end time of the media contained by the chunk, in microseconds. * @param chunkIndex The index of the chunk. * @param isLastChunk True if this is the last chunk in the media. False otherwise. */ - public TsChunk(DataSource dataSource, DataSpec dataSpec, TsExtractor tsExtractor, + public TsChunk(DataSource dataSource, DataSpec dataSpec, HlsExtractor extractor, int variantIndex, long startTimeUs, long endTimeUs, int chunkIndex, boolean isLastChunk) { super(dataSource, dataSpec); - this.extractor = tsExtractor; + this.extractor = extractor; this.variantIndex = variantIndex; this.startTimeUs = startTimeUs; this.endTimeUs = endTimeUs; 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 new file mode 100644 index 0000000000..af164a5f36 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/hls/parser/AdtsExtractor.java @@ -0,0 +1,126 @@ +/* + * 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.upstream.BufferPool; +import com.google.android.exoplayer.upstream.DataSource; +import com.google.android.exoplayer.util.Assertions; +import com.google.android.exoplayer.util.ParsableByteArray; + +import java.io.IOException; + +/** + * Facilitates the extraction of AAC samples from elementary audio files formatted as AAC with ADTS + * headers. + */ +public class AdtsExtractor extends HlsExtractor { + + private static final int MAX_PACKET_SIZE = 200; + + private final long firstSampleTimestamp; + private final ParsableByteArray packetBuffer; + private final AdtsReader adtsReader; + + // Accessed only by the loading thread. + private boolean firstPacket; + // Accessed by both the loading and consuming threads. + private volatile boolean prepared; + + public AdtsExtractor(boolean shouldSpliceIn, long firstSampleTimestamp, BufferPool bufferPool) { + super(shouldSpliceIn); + this.firstSampleTimestamp = firstSampleTimestamp; + packetBuffer = new ParsableByteArray(MAX_PACKET_SIZE); + adtsReader = new AdtsReader(bufferPool); + firstPacket = true; + } + + @Override + public int getTrackCount() { + Assertions.checkState(prepared); + return 1; + } + + @Override + public MediaFormat getFormat(int track) { + Assertions.checkState(prepared); + return adtsReader.getMediaFormat(); + } + + @Override + public boolean isPrepared() { + return prepared; + } + + @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 + public int read(DataSource dataSource) throws IOException { + int bytesRead = dataSource.read(packetBuffer.data, 0, MAX_PACKET_SIZE); + if (bytesRead == -1) { + return -1; + } + + packetBuffer.setPosition(0); + packetBuffer.setLimit(bytesRead); + + // TODO: Make it possible for adtsReader to consume the dataSource directly, so that it becomes + // unnecessary to copy the data through packetBuffer. + adtsReader.consume(packetBuffer, firstSampleTimestamp, firstPacket); + firstPacket = false; + if (!prepared) { + prepared = adtsReader.hasMediaFormat(); + } + return bytesRead; + } + + @Override + protected SampleQueue getSampleQueue(int track) { + Assertions.checkState(track == 0); + return adtsReader; + } + +} 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 14fce5167d..9dec6cc84a 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 @@ -30,7 +30,7 @@ import java.util.Collections; /** * Parses a continuous ADTS byte stream and extracts individual frames. */ -/* package */ class AdtsReader extends PesPayloadReader { +/* package */ class AdtsReader extends ElementaryStreamReader { private static final int STATE_FINDING_SYNC = 0; private static final int STATE_READING_HEADER = 1; diff --git a/library/src/main/java/com/google/android/exoplayer/hls/parser/PesPayloadReader.java b/library/src/main/java/com/google/android/exoplayer/hls/parser/ElementaryStreamReader.java similarity index 87% rename from library/src/main/java/com/google/android/exoplayer/hls/parser/PesPayloadReader.java rename to library/src/main/java/com/google/android/exoplayer/hls/parser/ElementaryStreamReader.java index 2bdce8448a..a8c5c7b562 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/parser/PesPayloadReader.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/parser/ElementaryStreamReader.java @@ -19,11 +19,11 @@ import com.google.android.exoplayer.upstream.BufferPool; import com.google.android.exoplayer.util.ParsableByteArray; /** - * Extracts individual samples from continuous byte stream, preserving original order. + * Extracts individual samples from an elementary media stream, preserving original order. */ -/* package */ abstract class PesPayloadReader extends SampleQueue { +/* package */ abstract class ElementaryStreamReader extends SampleQueue { - protected PesPayloadReader(BufferPool bufferPool) { + protected ElementaryStreamReader(BufferPool bufferPool) { super(bufferPool); } 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 55faeefcf4..5390003a36 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 @@ -30,7 +30,7 @@ import java.util.List; /** * Parses a continuous H264 byte stream and extracts individual frames. */ -/* package */ class H264Reader extends PesPayloadReader { +/* package */ class H264Reader extends ElementaryStreamReader { private static final int NAL_UNIT_TYPE_IDR = 5; private static final int NAL_UNIT_TYPE_SEI = 6; 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 new file mode 100644 index 0000000000..88aef4a0d6 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/hls/parser/HlsExtractor.java @@ -0,0 +1,151 @@ +/* + * 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.upstream.DataSource; + +import java.io.IOException; + +/** + * Facilitates extraction of media samples for HLS playbacks. + */ +// TODO: Consider consolidating more common logic in this base class. +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. + *

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

+ * 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) { + 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. + *

+ * This method should only be called after the extractor has been prepared. + * + * @return The number of available tracks. + */ + public abstract 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. + */ + public abstract MediaFormat getFormat(int track); + + /** + * Whether the extractor is prepared. + * + * @return True if the extractor is prepared. False otherwise. + */ + public abstract boolean isPrepared(); + + /** + * Releases the extractor, recycling any pending or incomplete samples to the sample pool. + *

+ * This method should not be called whilst {@link #read(DataSource)} is also being invoked. + */ + public abstract void 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 abstract long getLargestSampleTimestamp(); + + /** + * 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 abstract boolean getSample(int track, SampleHolder 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 abstract void discardUntil(int track, long 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 abstract boolean hasSamples(int track); + + /** + * 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 abstract 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); + +} 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 609337b664..7de263d6da 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 @@ -22,7 +22,7 @@ import com.google.android.exoplayer.util.ParsableByteArray; /** * Parses ID3 data and extracts individual text information frames. */ -/* package */ class Id3Reader extends PesPayloadReader { +/* package */ class Id3Reader extends ElementaryStreamReader { public Id3Reader(BufferPool bufferPool) { super(bufferPool); 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 d7ad5e7dde..b907042f71 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 @@ -32,7 +32,7 @@ import java.io.IOException; /** * Facilitates the extraction of data from the MPEG-2 TS container format. */ -public final class TsExtractor { +public final class TsExtractor extends HlsExtractor { private static final String TAG = "TsExtractor"; @@ -51,13 +51,9 @@ public final class TsExtractor { private final SparseArray sampleQueues; // Indexed by streamType private final SparseArray tsPayloadReaders; // Indexed by pid private final BufferPool bufferPool; - private final boolean shouldSpliceIn; private final long firstSampleTimestamp; private final ParsableBitArray tsScratch; - // Accessed only by the consuming thread. - private boolean spliceConfigured; - // Accessed only by the loading thread. private int tsPacketBytesRead; private long timestampOffsetUs; @@ -66,9 +62,9 @@ public final class TsExtractor { // Accessed by both the loading and consuming threads. private volatile boolean prepared; - public TsExtractor(long firstSampleTimestamp, boolean shouldSpliceIn, BufferPool bufferPool) { + public TsExtractor(boolean shouldSpliceIn, long firstSampleTimestamp, BufferPool bufferPool) { + super(shouldSpliceIn); this.firstSampleTimestamp = firstSampleTimestamp; - this.shouldSpliceIn = shouldSpliceIn; this.bufferPool = bufferPool; tsScratch = new ParsableBitArray(new byte[3]); tsPacketBuffer = new ParsableByteArray(TS_PACKET_SIZE); @@ -78,86 +74,31 @@ public final class TsExtractor { lastPts = Long.MIN_VALUE; } - /** - * Gets the number of available tracks. - *

- * This method should only be called after the extractor has been prepared. - * - * @return The number of available tracks. - */ + @Override public int getTrackCount() { Assertions.checkState(prepared); return sampleQueues.size(); } - /** - * 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. - */ + @Override public MediaFormat getFormat(int track) { Assertions.checkState(prepared); return sampleQueues.valueAt(track).getMediaFormat(); } - /** - * Whether the extractor is prepared. - * - * @return True if the extractor is prepared. False otherwise. - */ + @Override public boolean isPrepared() { return prepared; } - /** - * Releases the extractor, recycling any pending or incomplete samples to the sample pool. - *

- * This method should not be called whilst {@link #read(DataSource)} is also being invoked. - */ + @Override public void release() { for (int i = 0; i < sampleQueues.size(); i++) { sampleQueues.valueAt(i).release(); } } - /** - * Attempts to configure a splice from this extractor to the next. - *

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

- * 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 void configureSpliceTo(TsExtractor nextExtractor) { - Assertions.checkState(prepared); - 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; - for (int i = 0; i < sampleQueues.size(); i++) { - spliceConfigured &= sampleQueues.valueAt(i).configureSpliceTo( - nextExtractor.sampleQueues.valueAt(i)); - } - this.spliceConfigured = spliceConfigured; - return; - } - - /** - * 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. - */ + @Override public long getLargestSampleTimestamp() { long largestParsedTimestampUs = Long.MIN_VALUE; for (int i = 0; i < sampleQueues.size(); i++) { @@ -167,36 +108,19 @@ public final class TsExtractor { 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. - */ + @Override public boolean getSample(int track, SampleHolder holder) { Assertions.checkState(prepared); 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. - */ + @Override public void discardUntil(int track, long timeUs) { Assertions.checkState(prepared); 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. - */ + @Override public boolean hasSamples(int track) { Assertions.checkState(prepared); return !sampleQueues.valueAt(track).isEmpty(); @@ -215,13 +139,7 @@ public final class TsExtractor { return true; } - /** - * 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. - */ + @Override public int read(DataSource dataSource) throws IOException { int bytesRead = dataSource.read(tsPacketBuffer.data, tsPacketBytesRead, TS_PACKET_SIZE - tsPacketBytesRead); @@ -276,6 +194,12 @@ public final class TsExtractor { return bytesRead; } + @Override + protected SampleQueue getSampleQueue(int track) { + Assertions.checkState(track == 0); + return sampleQueues.valueAt(track); + } + /** * Adjusts a PTS value to the corresponding time in microseconds, accounting for PTS wraparound. * @@ -404,7 +328,7 @@ public final class TsExtractor { continue; } - PesPayloadReader pesPayloadReader = null; + ElementaryStreamReader pesPayloadReader = null; switch (streamType) { case TS_STREAM_TYPE_AAC: pesPayloadReader = new AdtsReader(bufferPool); @@ -444,7 +368,7 @@ public final class TsExtractor { private static final int MAX_HEADER_EXTENSION_SIZE = 5; private final ParsableBitArray pesScratch; - private final PesPayloadReader pesPayloadReader; + private final ElementaryStreamReader pesPayloadReader; private int state; private int bytesRead; @@ -457,7 +381,7 @@ public final class TsExtractor { private long timeUs; - public PesReader(PesPayloadReader pesPayloadReader) { + public PesReader(ElementaryStreamReader pesPayloadReader) { this.pesPayloadReader = pesPayloadReader; pesScratch = new ParsableBitArray(new byte[HEADER_SIZE]); state = STATE_FINDING_HEADER;