Start cleaning up buffering.

- Always continue loading if we've got less than the minimum
  amount of media in the buffer.
- Use LoadControl in ExtractorSampleSource.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=125679669
This commit is contained in:
olly 2016-06-23 08:27:55 -07:00 committed by Oliver Woodman
parent 5055a4aa91
commit a8883baa69
4 changed files with 57 additions and 69 deletions

View File

@ -58,8 +58,6 @@ public final class DefaultLoadControl implements LoadControl {
public static final int DEFAULT_LOW_WATERMARK_MS = 15000;
public static final int DEFAULT_HIGH_WATERMARK_MS = 30000;
public static final float DEFAULT_LOW_BUFFER_LOAD = 0.2f;
public static final float DEFAULT_HIGH_BUFFER_LOAD = 0.8f;
private static final int ABOVE_HIGH_WATERMARK = 0;
private static final int BETWEEN_WATERMARKS = 1;
@ -73,12 +71,10 @@ public final class DefaultLoadControl implements LoadControl {
private final long lowWatermarkUs;
private final long highWatermarkUs;
private final float lowBufferLoad;
private final float highBufferLoad;
private int targetBufferSize;
private long maxLoadStartPositionUs;
private int bufferState;
private int targetBufferSize;
private boolean targetBufferSizeReached;
private boolean fillingBuffers;
private boolean streamingPrioritySet;
@ -102,7 +98,7 @@ public final class DefaultLoadControl implements LoadControl {
public DefaultLoadControl(DefaultAllocator allocator, Handler eventHandler,
EventListener eventListener) {
this(allocator, eventHandler, eventListener, DEFAULT_LOW_WATERMARK_MS,
DEFAULT_HIGH_WATERMARK_MS, DEFAULT_LOW_BUFFER_LOAD, DEFAULT_HIGH_BUFFER_LOAD);
DEFAULT_HIGH_WATERMARK_MS);
}
/**
@ -117,15 +113,9 @@ public final class DefaultLoadControl implements LoadControl {
* the filling state.
* @param highWatermarkMs The minimum duration of media that can be buffered for the control to
* transition from filling to draining.
* @param lowBufferLoad The minimum fraction of the buffer that must be utilized for the control
* to be in the draining state. If the utilization is lower, then the control will transition
* to the filling state.
* @param highBufferLoad The minimum fraction of the buffer that must be utilized for the control
* to transition from the loading state to the draining state.
*/
public DefaultLoadControl(DefaultAllocator allocator, Handler eventHandler,
EventListener eventListener, int lowWatermarkMs, int highWatermarkMs, float lowBufferLoad,
float highBufferLoad) {
EventListener eventListener, int lowWatermarkMs, int highWatermarkMs) {
this.allocator = allocator;
this.eventHandler = eventHandler;
this.eventListener = eventListener;
@ -133,8 +123,6 @@ public final class DefaultLoadControl implements LoadControl {
this.loaderStates = new HashMap<>();
this.lowWatermarkUs = lowWatermarkMs * 1000L;
this.highWatermarkUs = highWatermarkMs * 1000L;
this.lowBufferLoad = lowBufferLoad;
this.highBufferLoad = highBufferLoad;
}
@Override
@ -151,6 +139,7 @@ public final class DefaultLoadControl implements LoadControl {
LoaderState state = loaderStates.remove(loader);
targetBufferSize -= state.bufferSizeContribution;
allocator.setTargetBufferSize(targetBufferSize);
targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
updateControlState();
}
@ -174,11 +163,10 @@ public final class DefaultLoadControl implements LoadControl {
}
// Update the buffer state.
int currentBufferSize = allocator.getTotalBytesAllocated();
int bufferState = getBufferState(currentBufferSize);
boolean bufferStateChanged = this.bufferState != bufferState;
boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
boolean bufferStateChanged = this.targetBufferSizeReached != targetBufferSizeReached;
if (bufferStateChanged) {
this.bufferState = bufferState;
this.targetBufferSizeReached = targetBufferSizeReached;
}
// If either of the individual states have changed, update the shared control state.
@ -186,8 +174,7 @@ public final class DefaultLoadControl implements LoadControl {
updateControlState();
}
return currentBufferSize < targetBufferSize && nextLoadPositionUs != C.UNSET_TIME_US
&& nextLoadPositionUs <= maxLoadStartPositionUs;
return nextLoadPositionUs != C.UNSET_TIME_US && nextLoadPositionUs <= maxLoadStartPositionUs;
}
private int getLoaderBufferState(long playbackPositionUs, long nextLoadPositionUs) {
@ -201,27 +188,20 @@ public final class DefaultLoadControl implements LoadControl {
}
}
private int getBufferState(int currentBufferSize) {
float bufferLoad = (float) currentBufferSize / targetBufferSize;
return bufferLoad > highBufferLoad ? ABOVE_HIGH_WATERMARK
: bufferLoad < lowBufferLoad ? BELOW_LOW_WATERMARK
: BETWEEN_WATERMARKS;
}
private void updateControlState() {
boolean loading = false;
boolean haveNextLoadPosition = false;
int highestState = bufferState;
int worstLoaderState = ABOVE_HIGH_WATERMARK;
for (int i = 0; i < loaders.size(); i++) {
LoaderState loaderState = loaderStates.get(loaders.get(i));
loading |= loaderState.loading;
haveNextLoadPosition |= loaderState.nextLoadPositionUs != C.UNSET_TIME_US;
highestState = Math.max(highestState, loaderState.bufferState);
worstLoaderState = Math.max(worstLoaderState, loaderState.bufferState);
}
fillingBuffers = !loaders.isEmpty() && (loading || haveNextLoadPosition)
&& (highestState == BELOW_LOW_WATERMARK
|| (highestState == BETWEEN_WATERMARKS && fillingBuffers));
&& (worstLoaderState == BELOW_LOW_WATERMARK
|| (worstLoaderState == BETWEEN_WATERMARKS && fillingBuffers && !targetBufferSizeReached));
if (fillingBuffers && !streamingPrioritySet) {
NetworkLock.instance.add(NetworkLock.STREAMING_PRIORITY);
streamingPrioritySet = true;

View File

@ -17,14 +17,15 @@ package com.google.android.exoplayer.extractor;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.DecoderInputBuffer;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.FormatHolder;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.TrackGroup;
import com.google.android.exoplayer.TrackGroupArray;
import com.google.android.exoplayer.TrackSelection;
import com.google.android.exoplayer.TrackStream;
import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.upstream.BandwidthMeter;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSourceFactory;
@ -36,6 +37,7 @@ import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Util;
import android.net.Uri;
import android.os.ConditionVariable;
import android.os.Handler;
import java.io.EOFException;
@ -125,7 +127,8 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private final Handler eventHandler;
private final EventListener eventListener;
private final DataSource dataSource;
private final Allocator allocator;
private final ConditionVariable loadCondition;
private final LoadControl loadControl;
private final Loader loader;
private final ExtractorHolder extractorHolder;
@ -186,7 +189,8 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
this.eventListener = eventListener;
this.eventHandler = eventHandler;
dataSource = dataSourceFactory.createDataSource(bandwidthMeter);
allocator = new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE);
loadCondition = new ConditionVariable();
loadControl = new DefaultLoadControl(new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE));
loader = new Loader("Loader:ExtractorSampleSource");
extractorHolder = new ExtractorHolder(extractors, this);
pendingResetPositionUs = C.UNSET_TIME_US;
@ -308,6 +312,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
return true;
}
if (seekMap != null && tracksBuilt && haveFormatsForAllTracks()) {
loadCondition.close();
int trackCount = sampleQueues.length;
TrackGroup[] trackArray = new TrackGroup[trackCount];
trackEnabledStates = new boolean[trackCount];
@ -322,6 +327,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
// We're not prepared.
maybeThrowError();
if (!loader.isLoading()) {
loadCondition.open();
startLoading();
}
return false;
@ -341,6 +347,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
public TrackStream[] selectTracks(List<TrackStream> oldStreams,
List<TrackSelection> newSelections, long positionUs) {
Assertions.checkState(prepared);
boolean tracksWereEnabled = enabledTrackCount > 0;
// Unselect old tracks.
for (int i = 0; i < oldStreams.size(); i++) {
int track = ((TrackStreamImpl) oldStreams.get(i)).track;
@ -372,19 +379,33 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
}
// Cancel or start requests as necessary.
if (enabledTrackCount == 0) {
if (tracksWereEnabled) {
loadControl.unregister(this);
loadCondition.close();
}
if (loader.isLoading()) {
loader.cancelLoading();
}
} else if (seenFirstTrackSelection ? newStreams.length > 0 : positionUs != 0) {
} else {
if (!tracksWereEnabled) {
loadControl.register(this, C.DEFAULT_MUXED_BUFFER_SIZE);
loadCondition.open();
}
if (seenFirstTrackSelection ? newStreams.length > 0 : positionUs != 0) {
seekToUs(positionUs);
}
}
seenFirstTrackSelection = true;
return newStreams;
}
@Override
public void continueBuffering(long playbackPositionUs) {
// Do nothing.
if (loadControl.update(this, playbackPositionUs, getBufferedPositionUs(), loader.isLoading())) {
loadCondition.open();
} else {
loadCondition.close();
}
}
@Override
@ -434,6 +455,9 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
for (DefaultTrackOutput sampleQueue : sampleQueues) {
sampleQueue.disable();
}
if (enabledTrackCount > 0) {
loadControl.unregister(this);
}
loader.release();
}
@ -498,7 +522,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
@Override
public TrackOutput track(int id) {
sampleQueues = Arrays.copyOf(sampleQueues, sampleQueues.length + 1);
DefaultTrackOutput sampleQueue = new DefaultTrackOutput(allocator);
DefaultTrackOutput sampleQueue = new DefaultTrackOutput(loadControl.getAllocator());
sampleQueues[sampleQueues.length - 1] = sampleQueue;
return sampleQueue;
}
@ -536,7 +560,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private void startLoading() {
ExtractingLoadable loadable = new ExtractingLoadable(uri, dataSource, extractorHolder,
allocator, C.DEFAULT_MUXED_BUFFER_SIZE);
loadCondition);
if (prepared) {
Assertions.checkState(isPendingReset());
if (durationUs != C.UNSET_TIME_US && pendingResetPositionUs >= durationUs) {
@ -657,8 +681,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private final Uri uri;
private final DataSource dataSource;
private final ExtractorHolder extractorHolder;
private final Allocator allocator;
private final int requestedBufferSize;
private final ConditionVariable loadCondition;
private final PositionHolder positionHolder;
private volatile boolean loadCanceled;
@ -667,12 +690,11 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private long length;
public ExtractingLoadable(Uri uri, DataSource dataSource, ExtractorHolder extractorHolder,
Allocator allocator, int requestedBufferSize) {
ConditionVariable loadCondition) {
this.uri = Assertions.checkNotNull(uri);
this.dataSource = Assertions.checkNotNull(dataSource);
this.extractorHolder = Assertions.checkNotNull(extractorHolder);
this.allocator = Assertions.checkNotNull(allocator);
this.requestedBufferSize = requestedBufferSize;
this.loadCondition = loadCondition;
this.positionHolder = new PositionHolder();
this.pendingExtractorSeek = true;
this.length = C.LENGTH_UNBOUNDED;
@ -711,11 +733,10 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
pendingExtractorSeek = false;
}
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
allocator.blockWhileTotalBytesAllocatedExceeds(requestedBufferSize);
// TODO: Prevent the loader from loading too much data between evaluations of the
// buffering condition.
loadCondition.block();
result = extractor.read(input, positionHolder);
// TODO: Implement throttling to stop us from buffering data too often.
// TODO: Block buffering between the point at which we have sufficient data for
// preparation to complete and the first call to endTrackSelection.
}
} finally {
if (result == Extractor.RESULT_SEEK) {

View File

@ -39,19 +39,10 @@ public interface Allocator {
/**
* Hints to the {@link Allocator} that it should make a best effort to release any memory that it
* has allocated that it no longer requires.
* has allocated beyond the target buffer size.
*/
void trim();
/**
* Blocks execution until the number of bytes allocated is not greater than the limit, or the
* thread is interrupted.
*
* @param limit The limit in bytes.
* @throws InterruptedException If the thread is interrupted.
*/
void blockWhileTotalBytesAllocatedExceeds(int limit) throws InterruptedException;
/**
* Returns the total number of bytes currently allocated.
*/

View File

@ -47,7 +47,7 @@ public final class DefaultAllocator implements Allocator {
/**
* Constructs a pool with some {@link Allocation}s created up front.
* <p>
* Note: Initial {@link Allocation}s will never be discarded by {@link #trim(int)}.
* Note: Initial {@link Allocation}s will never be discarded by {@link #trim()}.
*
* @param individualAllocationSize The length of each individual allocation.
* @param initialAllocationCount The number of allocations to create up front.
@ -70,7 +70,11 @@ public final class DefaultAllocator implements Allocator {
}
public synchronized void setTargetBufferSize(int targetBufferSize) {
boolean targetBufferSizeReduced = targetBufferSize < this.targetBufferSize;
this.targetBufferSize = targetBufferSize;
if (targetBufferSizeReduced) {
trim();
}
}
@Override
@ -147,14 +151,6 @@ public final class DefaultAllocator implements Allocator {
return allocatedCount * individualAllocationSize;
}
@Override
public synchronized void blockWhileTotalBytesAllocatedExceeds(int limit)
throws InterruptedException {
while (getTotalBytesAllocated() > limit) {
wait();
}
}
@Override
public int getIndividualAllocationLength() {
return individualAllocationSize;