diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java index 3483ed68d5..400c03d769 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/BaseMediaChunk.java @@ -17,18 +17,18 @@ package com.google.android.exoplayer.chunk; import com.google.android.exoplayer.Format; import com.google.android.exoplayer.drm.DrmInitData; -import com.google.android.exoplayer.extractor.DefaultTrackOutput; +import com.google.android.exoplayer.extractor.RollingSampleBuffer; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSpec; /** * A base implementation of {@link MediaChunk}, for chunks that contain a single track. *

- * Loaded samples are output to a {@link DefaultTrackOutput}. + * Loaded samples are output to a {@link RollingSampleBuffer}. */ public abstract class BaseMediaChunk extends MediaChunk { - private DefaultTrackOutput trackOutput; + private RollingSampleBuffer trackOutput; private int firstSampleIndex; /** @@ -46,19 +46,19 @@ public abstract class BaseMediaChunk extends MediaChunk { } /** - * Initializes the chunk for loading, setting the {@link DefaultTrackOutput} that will receive + * Initializes the chunk for loading, setting the {@link RollingSampleBuffer} that will receive * samples as they are loaded. * * @param trackOutput The output that will receive the loaded samples. */ - public void init(DefaultTrackOutput trackOutput) { + public void init(RollingSampleBuffer trackOutput) { this.trackOutput = trackOutput; this.firstSampleIndex = trackOutput.getWriteIndex(); } /** * Returns the index of the first sample in the output that was passed to - * {@link #init(DefaultTrackOutput)} that will originate from this chunk. + * {@link #init(RollingSampleBuffer)} that will originate from this chunk. */ public final int getFirstSampleIndex() { return firstSampleIndex; @@ -72,9 +72,9 @@ public abstract class BaseMediaChunk extends MediaChunk { public abstract DrmInitData getDrmInitData(); /** - * Returns the track output most recently passed to {@link #init(DefaultTrackOutput)}. + * Returns the track output most recently passed to {@link #init(RollingSampleBuffer)}. */ - protected final DefaultTrackOutput getTrackOutput() { + protected final RollingSampleBuffer getTrackOutput() { return trackOutput; } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java index d79d8cb302..d008d2da98 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java @@ -26,7 +26,7 @@ import com.google.android.exoplayer.TrackGroupArray; import com.google.android.exoplayer.TrackSelection; import com.google.android.exoplayer.TrackStream; import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener.EventDispatcher; -import com.google.android.exoplayer.extractor.DefaultTrackOutput; +import com.google.android.exoplayer.extractor.RollingSampleBuffer; import com.google.android.exoplayer.upstream.Loader; import com.google.android.exoplayer.upstream.Loader.Loadable; import com.google.android.exoplayer.util.Assertions; @@ -56,7 +56,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call private final ChunkHolder nextChunkHolder; private final LinkedList mediaChunks; private final List readOnlyMediaChunks; - private final DefaultTrackOutput sampleQueue; + private final RollingSampleBuffer sampleQueue; private final int bufferSizeContribution; private final EventDispatcher eventDispatcher; @@ -125,7 +125,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call nextChunkHolder = new ChunkHolder(); mediaChunks = new LinkedList<>(); readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks); - sampleQueue = new DefaultTrackOutput(loadControl.getAllocator()); + sampleQueue = new RollingSampleBuffer(loadControl.getAllocator()); pendingResetPositionUs = C.UNSET_TIME_US; } @@ -222,9 +222,9 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call } else if (isPendingReset()) { return pendingResetPositionUs; } else { - long largestParsedTimestampUs = sampleQueue.getLargestParsedTimestampUs(); - return largestParsedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs - : largestParsedTimestampUs; + long largestQueuedTimestampUs = sampleQueue.getLargestQueuedTimestampUs(); + return largestQueuedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs + : largestQueuedTimestampUs; } } @@ -316,7 +316,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call return NOTHING_READ; } - if (sampleQueue.getSample(buffer)) { + if (sampleQueue.readSample(buffer)) { if (buffer.timeUs < lastSeekPositionUs) { buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java index 67d8da8d5f..e6739c8a90 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ContainerMediaChunk.java @@ -19,9 +19,9 @@ import com.google.android.exoplayer.Format; import com.google.android.exoplayer.chunk.ChunkExtractorWrapper.SingleTrackMetadataOutput; import com.google.android.exoplayer.drm.DrmInitData; import com.google.android.exoplayer.extractor.DefaultExtractorInput; -import com.google.android.exoplayer.extractor.DefaultTrackOutput; import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.ExtractorInput; +import com.google.android.exoplayer.extractor.RollingSampleBuffer; import com.google.android.exoplayer.extractor.SeekMap; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSpec; @@ -111,7 +111,7 @@ public class ContainerMediaChunk extends BaseMediaChunk implements SingleTrackMe loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec)); if (bytesLoaded == 0) { // Set the target to ourselves. - DefaultTrackOutput trackOutput = getTrackOutput(); + RollingSampleBuffer trackOutput = getTrackOutput(); trackOutput.formatWithOffset(sampleFormat, sampleOffsetUs); extractorWrapper.init(this, trackOutput); } diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java index 82a3dc8696..241d4c4f3e 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/SingleSampleMediaChunk.java @@ -19,8 +19,8 @@ import com.google.android.exoplayer.C; import com.google.android.exoplayer.Format; import com.google.android.exoplayer.drm.DrmInitData; import com.google.android.exoplayer.extractor.DefaultExtractorInput; -import com.google.android.exoplayer.extractor.DefaultTrackOutput; import com.google.android.exoplayer.extractor.ExtractorInput; +import com.google.android.exoplayer.extractor.RollingSampleBuffer; import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.util.Util; @@ -91,7 +91,7 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk { length += bytesLoaded; } ExtractorInput extractorInput = new DefaultExtractorInput(dataSource, bytesLoaded, length); - DefaultTrackOutput trackOutput = getTrackOutput(); + RollingSampleBuffer trackOutput = getTrackOutput(); trackOutput.formatWithOffset(sampleFormat, 0); // Load the sample data. int result = 0; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java b/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java index c3ef28c4d2..eefccccf38 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java @@ -36,9 +36,6 @@ public final class DefaultTrackOutput implements TrackOutput { private long lastReadTimeUs; private long spliceOutTimeUs; - // Accessed by both the loading and consuming threads. - private volatile long largestParsedTimestampUs; - /** * @param allocator An {@link Allocator} from which allocations for sample data can be obtained. */ @@ -48,7 +45,6 @@ public final class DefaultTrackOutput implements TrackOutput { needKeyframe = true; lastReadTimeUs = Long.MIN_VALUE; spliceOutTimeUs = Long.MIN_VALUE; - largestParsedTimestampUs = Long.MIN_VALUE; } // Called by the consuming thread, but only when there is no loading thread. @@ -61,36 +57,10 @@ public final class DefaultTrackOutput implements TrackOutput { needKeyframe = true; lastReadTimeUs = Long.MIN_VALUE; spliceOutTimeUs = Long.MIN_VALUE; - largestParsedTimestampUs = Long.MIN_VALUE; - } - - /** - * Returns the current absolute write index. - */ - public int getWriteIndex() { - return rollingBuffer.getWriteIndex(); - } - - /** - * Discards samples from the write side of the queue. - * - * @param discardFromIndex The absolute index of the first sample to be discarded. - */ - public void discardUpstreamSamples(int discardFromIndex) { - rollingBuffer.discardUpstreamSamples(discardFromIndex); - largestParsedTimestampUs = rollingBuffer.peekSample(sampleBuffer) ? sampleBuffer.timeUs - : Long.MIN_VALUE; } // Called by the consuming thread. - /** - * Returns the current absolute read index. - */ - public int getReadIndex() { - return rollingBuffer.getReadIndex(); - } - /** * Returns the current upstream {@link Format}. */ @@ -98,19 +68,12 @@ public final class DefaultTrackOutput implements TrackOutput { return rollingBuffer.getUpstreamFormat(); } - /** - * Returns the current downstream {@link Format}. - */ - public Format getDownstreamFormat() { - return rollingBuffer.getDownstreamFormat(); - } - /** * The largest timestamp of any sample received by the output, or {@link Long#MIN_VALUE} if a * sample has yet to be received. */ public long getLargestParsedTimestampUs() { - return largestParsedTimestampUs; + return rollingBuffer.getLargestQueuedTimestampUs(); } /** @@ -148,16 +111,6 @@ public final class DefaultTrackOutput implements TrackOutput { rollingBuffer.skipAllSamples(); } - /** - * Attempts to skip to the keyframe before the specified time, if it's present in the buffer. - * - * @param timeUs The seek time. - * @return True if the skip was successful. False otherwise. - */ - public boolean skipToKeyframeBefore(long timeUs) { - return rollingBuffer.skipToKeyframeBefore(timeUs); - } - /** * Attempts to configure a splice from this queue to the next. * @@ -216,19 +169,6 @@ public final class DefaultTrackOutput implements TrackOutput { // Called by the loading thread. - /** - * Like {@link #format(Format)}, but with an offset that will be added to the timestamps of - * samples subsequently queued to the buffer. The offset is also used to adjust - * {@link Format#subsampleOffsetUs} for both the {@link Format} passed and those subsequently - * passed to {@link #format(Format)}. - * - * @param format The format. - * @param sampleOffsetUs The offset in microseconds. - */ - public void formatWithOffset(Format format, long sampleOffsetUs) { - rollingBuffer.formatWithOffset(format, sampleOffsetUs); - } - @Override public void format(Format format) { rollingBuffer.format(format); @@ -247,7 +187,6 @@ public final class DefaultTrackOutput implements TrackOutput { @Override public void sampleMetadata(long timeUs, int flags, int size, int offset, byte[] encryptionKey) { - largestParsedTimestampUs = Math.max(largestParsedTimestampUs, timeUs); rollingBuffer.sampleMetadata(timeUs, flags, size, offset, encryptionKey); } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java index 378917e5de..03eeb37692 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java @@ -195,7 +195,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu private final ExtractorHolder extractorHolder; private final Allocator allocator; private final int requestedBufferSize; - private final SparseArray sampleQueues; + private final SparseArray sampleQueues; private final int minLoadableRetryCount; private final Uri uri; private final DataSource dataSource; @@ -425,13 +425,13 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu } else if (isPendingReset()) { return pendingResetPositionUs; } else { - long largestParsedTimestampUs = Long.MIN_VALUE; + long largestQueuedTimestampUs = Long.MIN_VALUE; for (int i = 0; i < sampleQueues.size(); i++) { - largestParsedTimestampUs = Math.max(largestParsedTimestampUs, - sampleQueues.valueAt(i).getLargestParsedTimestampUs()); + largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, + sampleQueues.valueAt(i).getLargestQueuedTimestampUs()); } - return largestParsedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs - : largestParsedTimestampUs; + return largestQueuedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs + : largestQueuedTimestampUs; } } @@ -474,7 +474,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu return TrackStream.NOTHING_READ; } - DefaultTrackOutput sampleQueue = sampleQueues.valueAt(track); + RollingSampleBuffer sampleQueue = sampleQueues.valueAt(track); if (pendingMediaFormat[track]) { formatHolder.format = sampleQueue.getUpstreamFormat(); formatHolder.drmInitData = drmInitData; @@ -482,7 +482,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu return TrackStream.FORMAT_READ; } - if (sampleQueue.getSample(buffer)) { + if (sampleQueue.readSample(buffer)) { if (buffer.timeUs < lastSeekPositionUs) { buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); } @@ -538,9 +538,9 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu @Override public TrackOutput track(int id) { - DefaultTrackOutput sampleQueue = sampleQueues.get(id); + RollingSampleBuffer sampleQueue = sampleQueues.get(id); if (sampleQueue == null) { - sampleQueue = new DefaultTrackOutput(allocator); + sampleQueue = new RollingSampleBuffer(allocator); sampleQueues.put(id, sampleQueue); } return sampleQueue; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java b/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java index 2ac38e66d6..372a9a442f 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java @@ -31,7 +31,7 @@ import java.util.concurrent.LinkedBlockingDeque; /** * A rolling buffer of sample data and corresponding sample information. */ -/* package */ final class RollingSampleBuffer implements TrackOutput { +public final class RollingSampleBuffer implements TrackOutput { private static final int INITIAL_SCRATCH_SIZE = 32; @@ -129,6 +129,13 @@ import java.util.concurrent.LinkedBlockingDeque; // Called by the consuming thread. + /** + * Returns whether the buffer is empty. + */ + public boolean isEmpty() { + return infoQueue.isEmpty(); + } + /** * Returns the current absolute read index. */ @@ -151,6 +158,20 @@ import java.util.concurrent.LinkedBlockingDeque; return nextSampleFormat != null ? nextSampleFormat : upstreamFormat; } + /** + * Returns the largest sample timestamp that has been queued since the last {@link #clear()}. + *

+ * Samples that were discarded by calling {@link #discardUpstreamSamples(int)} are not + * considered as having been queued. Samples that were dequeued from the front of the queue are + * considered as having been queued. + * + * @return The largest sample timestamp that has been queued, or {@link Long#MIN_VALUE} if no + * samples have been queued. + */ + public long getLargestQueuedTimestampUs() { + return infoQueue.getLargestQueuedTimestampUs(); + } + /** * Fills {@code buffer} with information about the current sample, but does not write its data. *

@@ -470,6 +491,9 @@ import java.util.concurrent.LinkedBlockingDeque; private int relativeReadIndex; private int relativeWriteIndex; + private long largestDequeuedTimestampUs; + private long largestQueuedTimestampUs; + public InfoQueue() { capacity = SAMPLE_CAPACITY_INCREMENT; offsets = new long[capacity]; @@ -478,6 +502,8 @@ import java.util.concurrent.LinkedBlockingDeque; sizes = new int[capacity]; encryptionKeys = new byte[capacity][]; formats = new Format[capacity]; + largestDequeuedTimestampUs = Long.MIN_VALUE; + largestQueuedTimestampUs = Long.MIN_VALUE; } // Called by the consuming thread, but only when there is no loading thread. @@ -490,6 +516,8 @@ import java.util.concurrent.LinkedBlockingDeque; relativeReadIndex = 0; relativeWriteIndex = 0; queueSize = 0; + largestDequeuedTimestampUs = Long.MIN_VALUE; + largestQueuedTimestampUs = Long.MIN_VALUE; } /** @@ -520,6 +548,16 @@ import java.util.concurrent.LinkedBlockingDeque; queueSize -= discardCount; relativeWriteIndex = (relativeWriteIndex + capacity - discardCount) % capacity; + // Update the largest queued timestamp, assuming that the timestamps prior to a keyframe are + // always less than the timestamp of the keyframe itself, and of subsequent frames. + largestQueuedTimestampUs = Long.MIN_VALUE; + for (int i = queueSize - 1; i >= 0; i--) { + int sampleIndex = (relativeReadIndex + i) % capacity; + largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timesUs[sampleIndex]); + if ((flags[sampleIndex] & C.BUFFER_FLAG_KEY_FRAME) != 0) { + break; + } + } return offsets[relativeWriteIndex]; } @@ -532,6 +570,10 @@ import java.util.concurrent.LinkedBlockingDeque; return absoluteReadIndex; } + public synchronized boolean isEmpty() { + return queueSize == 0; + } + /** * Returns the {@link Format} of the next sample, or null of the queue is empty. */ @@ -542,6 +584,20 @@ import java.util.concurrent.LinkedBlockingDeque; return formats[relativeReadIndex]; } + /** + * Returns the largest sample timestamp that has been queued since the last {@link #clear()}. + *

+ * Samples that were discarded by calling {@link #discardUpstreamSamples(int)} are not + * considered as having been queued. Samples that were dequeued from the front of the queue are + * considered as having been queued. + * + * @return The largest sample timestamp that has been queued, or {@link Long#MIN_VALUE} if no + * samples have been queued. + */ + public synchronized long getLargestQueuedTimestampUs() { + return Math.max(largestDequeuedTimestampUs, largestQueuedTimestampUs); + } + /** * Fills {@code buffer} with information about the current sample, but does not write its data. * The absolute position of the sample's data in the rolling buffer is stored in @@ -575,6 +631,7 @@ import java.util.concurrent.LinkedBlockingDeque; */ public synchronized long moveToNextSample() { queueSize--; + largestDequeuedTimestampUs = Math.max(largestDequeuedTimestampUs, timesUs[relativeReadIndex]); int lastReadIndex = relativeReadIndex++; absoluteReadIndex++; if (relativeReadIndex == capacity) { @@ -658,6 +715,7 @@ import java.util.concurrent.LinkedBlockingDeque; flags[relativeWriteIndex] = sampleFlags; encryptionKeys[relativeWriteIndex] = encryptionKey; formats[relativeWriteIndex] = format; + largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs, timeUs); // Increment the write index. queueSize++; if (queueSize == capacity) {