Use RollingSampleBuffer directly for non-HLS.

This also fixes the largest queued timestamp to be the
correct value if upstream samples are discarded.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=120207054
This commit is contained in:
olly 2016-04-19 01:55:12 -07:00 committed by Oliver Woodman
parent 5ce210e374
commit a760c9bfd9
7 changed files with 89 additions and 92 deletions

View File

@ -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.
* <p>
* 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;
}

View File

@ -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<BaseMediaChunk> mediaChunks;
private final List<BaseMediaChunk> 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);
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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<DefaultTrackOutput> sampleQueues;
private final SparseArray<RollingSampleBuffer> 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;

View File

@ -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()}.
* <p>
* 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.
* <p>
@ -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()}.
* <p>
* 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) {