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_LOW_WATERMARK_MS = 15000;
public static final int DEFAULT_HIGH_WATERMARK_MS = 30000; 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 ABOVE_HIGH_WATERMARK = 0;
private static final int BETWEEN_WATERMARKS = 1; private static final int BETWEEN_WATERMARKS = 1;
@ -73,12 +71,10 @@ public final class DefaultLoadControl implements LoadControl {
private final long lowWatermarkUs; private final long lowWatermarkUs;
private final long highWatermarkUs; private final long highWatermarkUs;
private final float lowBufferLoad;
private final float highBufferLoad;
private int targetBufferSize;
private long maxLoadStartPositionUs; private long maxLoadStartPositionUs;
private int bufferState; private int targetBufferSize;
private boolean targetBufferSizeReached;
private boolean fillingBuffers; private boolean fillingBuffers;
private boolean streamingPrioritySet; private boolean streamingPrioritySet;
@ -102,7 +98,7 @@ public final class DefaultLoadControl implements LoadControl {
public DefaultLoadControl(DefaultAllocator allocator, Handler eventHandler, public DefaultLoadControl(DefaultAllocator allocator, Handler eventHandler,
EventListener eventListener) { EventListener eventListener) {
this(allocator, eventHandler, eventListener, DEFAULT_LOW_WATERMARK_MS, 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. * the filling state.
* @param highWatermarkMs The minimum duration of media that can be buffered for the control to * @param highWatermarkMs The minimum duration of media that can be buffered for the control to
* transition from filling to draining. * 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, public DefaultLoadControl(DefaultAllocator allocator, Handler eventHandler,
EventListener eventListener, int lowWatermarkMs, int highWatermarkMs, float lowBufferLoad, EventListener eventListener, int lowWatermarkMs, int highWatermarkMs) {
float highBufferLoad) {
this.allocator = allocator; this.allocator = allocator;
this.eventHandler = eventHandler; this.eventHandler = eventHandler;
this.eventListener = eventListener; this.eventListener = eventListener;
@ -133,8 +123,6 @@ public final class DefaultLoadControl implements LoadControl {
this.loaderStates = new HashMap<>(); this.loaderStates = new HashMap<>();
this.lowWatermarkUs = lowWatermarkMs * 1000L; this.lowWatermarkUs = lowWatermarkMs * 1000L;
this.highWatermarkUs = highWatermarkMs * 1000L; this.highWatermarkUs = highWatermarkMs * 1000L;
this.lowBufferLoad = lowBufferLoad;
this.highBufferLoad = highBufferLoad;
} }
@Override @Override
@ -151,6 +139,7 @@ public final class DefaultLoadControl implements LoadControl {
LoaderState state = loaderStates.remove(loader); LoaderState state = loaderStates.remove(loader);
targetBufferSize -= state.bufferSizeContribution; targetBufferSize -= state.bufferSizeContribution;
allocator.setTargetBufferSize(targetBufferSize); allocator.setTargetBufferSize(targetBufferSize);
targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
updateControlState(); updateControlState();
} }
@ -174,11 +163,10 @@ public final class DefaultLoadControl implements LoadControl {
} }
// Update the buffer state. // Update the buffer state.
int currentBufferSize = allocator.getTotalBytesAllocated(); boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
int bufferState = getBufferState(currentBufferSize); boolean bufferStateChanged = this.targetBufferSizeReached != targetBufferSizeReached;
boolean bufferStateChanged = this.bufferState != bufferState;
if (bufferStateChanged) { if (bufferStateChanged) {
this.bufferState = bufferState; this.targetBufferSizeReached = targetBufferSizeReached;
} }
// If either of the individual states have changed, update the shared control state. // If either of the individual states have changed, update the shared control state.
@ -186,8 +174,7 @@ public final class DefaultLoadControl implements LoadControl {
updateControlState(); updateControlState();
} }
return currentBufferSize < targetBufferSize && nextLoadPositionUs != C.UNSET_TIME_US return nextLoadPositionUs != C.UNSET_TIME_US && nextLoadPositionUs <= maxLoadStartPositionUs;
&& nextLoadPositionUs <= maxLoadStartPositionUs;
} }
private int getLoaderBufferState(long playbackPositionUs, long nextLoadPositionUs) { 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() { private void updateControlState() {
boolean loading = false; boolean loading = false;
boolean haveNextLoadPosition = false; boolean haveNextLoadPosition = false;
int highestState = bufferState; int worstLoaderState = ABOVE_HIGH_WATERMARK;
for (int i = 0; i < loaders.size(); i++) { for (int i = 0; i < loaders.size(); i++) {
LoaderState loaderState = loaderStates.get(loaders.get(i)); LoaderState loaderState = loaderStates.get(loaders.get(i));
loading |= loaderState.loading; loading |= loaderState.loading;
haveNextLoadPosition |= loaderState.nextLoadPositionUs != C.UNSET_TIME_US; haveNextLoadPosition |= loaderState.nextLoadPositionUs != C.UNSET_TIME_US;
highestState = Math.max(highestState, loaderState.bufferState); worstLoaderState = Math.max(worstLoaderState, loaderState.bufferState);
} }
fillingBuffers = !loaders.isEmpty() && (loading || haveNextLoadPosition) fillingBuffers = !loaders.isEmpty() && (loading || haveNextLoadPosition)
&& (highestState == BELOW_LOW_WATERMARK && (worstLoaderState == BELOW_LOW_WATERMARK
|| (highestState == BETWEEN_WATERMARKS && fillingBuffers)); || (worstLoaderState == BETWEEN_WATERMARKS && fillingBuffers && !targetBufferSizeReached));
if (fillingBuffers && !streamingPrioritySet) { if (fillingBuffers && !streamingPrioritySet) {
NetworkLock.instance.add(NetworkLock.STREAMING_PRIORITY); NetworkLock.instance.add(NetworkLock.STREAMING_PRIORITY);
streamingPrioritySet = true; 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.C;
import com.google.android.exoplayer.DecoderInputBuffer; import com.google.android.exoplayer.DecoderInputBuffer;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.FormatHolder; import com.google.android.exoplayer.FormatHolder;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleSource; import com.google.android.exoplayer.SampleSource;
import com.google.android.exoplayer.TrackGroup; import com.google.android.exoplayer.TrackGroup;
import com.google.android.exoplayer.TrackGroupArray; import com.google.android.exoplayer.TrackGroupArray;
import com.google.android.exoplayer.TrackSelection; import com.google.android.exoplayer.TrackSelection;
import com.google.android.exoplayer.TrackStream; 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.BandwidthMeter;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSourceFactory; 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 com.google.android.exoplayer.util.Util;
import android.net.Uri; import android.net.Uri;
import android.os.ConditionVariable;
import android.os.Handler; import android.os.Handler;
import java.io.EOFException; import java.io.EOFException;
@ -125,7 +127,8 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private final Handler eventHandler; private final Handler eventHandler;
private final EventListener eventListener; private final EventListener eventListener;
private final DataSource dataSource; private final DataSource dataSource;
private final Allocator allocator; private final ConditionVariable loadCondition;
private final LoadControl loadControl;
private final Loader loader; private final Loader loader;
private final ExtractorHolder extractorHolder; private final ExtractorHolder extractorHolder;
@ -186,7 +189,8 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
this.eventListener = eventListener; this.eventListener = eventListener;
this.eventHandler = eventHandler; this.eventHandler = eventHandler;
dataSource = dataSourceFactory.createDataSource(bandwidthMeter); 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"); loader = new Loader("Loader:ExtractorSampleSource");
extractorHolder = new ExtractorHolder(extractors, this); extractorHolder = new ExtractorHolder(extractors, this);
pendingResetPositionUs = C.UNSET_TIME_US; pendingResetPositionUs = C.UNSET_TIME_US;
@ -308,6 +312,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
return true; return true;
} }
if (seekMap != null && tracksBuilt && haveFormatsForAllTracks()) { if (seekMap != null && tracksBuilt && haveFormatsForAllTracks()) {
loadCondition.close();
int trackCount = sampleQueues.length; int trackCount = sampleQueues.length;
TrackGroup[] trackArray = new TrackGroup[trackCount]; TrackGroup[] trackArray = new TrackGroup[trackCount];
trackEnabledStates = new boolean[trackCount]; trackEnabledStates = new boolean[trackCount];
@ -322,6 +327,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
// We're not prepared. // We're not prepared.
maybeThrowError(); maybeThrowError();
if (!loader.isLoading()) { if (!loader.isLoading()) {
loadCondition.open();
startLoading(); startLoading();
} }
return false; return false;
@ -341,6 +347,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
public TrackStream[] selectTracks(List<TrackStream> oldStreams, public TrackStream[] selectTracks(List<TrackStream> oldStreams,
List<TrackSelection> newSelections, long positionUs) { List<TrackSelection> newSelections, long positionUs) {
Assertions.checkState(prepared); Assertions.checkState(prepared);
boolean tracksWereEnabled = enabledTrackCount > 0;
// Unselect old tracks. // Unselect old tracks.
for (int i = 0; i < oldStreams.size(); i++) { for (int i = 0; i < oldStreams.size(); i++) {
int track = ((TrackStreamImpl) oldStreams.get(i)).track; int track = ((TrackStreamImpl) oldStreams.get(i)).track;
@ -372,19 +379,33 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
} }
// Cancel or start requests as necessary. // Cancel or start requests as necessary.
if (enabledTrackCount == 0) { if (enabledTrackCount == 0) {
if (tracksWereEnabled) {
loadControl.unregister(this);
loadCondition.close();
}
if (loader.isLoading()) { if (loader.isLoading()) {
loader.cancelLoading(); 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); seekToUs(positionUs);
} }
}
seenFirstTrackSelection = true; seenFirstTrackSelection = true;
return newStreams; return newStreams;
} }
@Override @Override
public void continueBuffering(long playbackPositionUs) { public void continueBuffering(long playbackPositionUs) {
// Do nothing. if (loadControl.update(this, playbackPositionUs, getBufferedPositionUs(), loader.isLoading())) {
loadCondition.open();
} else {
loadCondition.close();
}
} }
@Override @Override
@ -434,6 +455,9 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
for (DefaultTrackOutput sampleQueue : sampleQueues) { for (DefaultTrackOutput sampleQueue : sampleQueues) {
sampleQueue.disable(); sampleQueue.disable();
} }
if (enabledTrackCount > 0) {
loadControl.unregister(this);
}
loader.release(); loader.release();
} }
@ -498,7 +522,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
@Override @Override
public TrackOutput track(int id) { public TrackOutput track(int id) {
sampleQueues = Arrays.copyOf(sampleQueues, sampleQueues.length + 1); sampleQueues = Arrays.copyOf(sampleQueues, sampleQueues.length + 1);
DefaultTrackOutput sampleQueue = new DefaultTrackOutput(allocator); DefaultTrackOutput sampleQueue = new DefaultTrackOutput(loadControl.getAllocator());
sampleQueues[sampleQueues.length - 1] = sampleQueue; sampleQueues[sampleQueues.length - 1] = sampleQueue;
return sampleQueue; return sampleQueue;
} }
@ -536,7 +560,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private void startLoading() { private void startLoading() {
ExtractingLoadable loadable = new ExtractingLoadable(uri, dataSource, extractorHolder, ExtractingLoadable loadable = new ExtractingLoadable(uri, dataSource, extractorHolder,
allocator, C.DEFAULT_MUXED_BUFFER_SIZE); loadCondition);
if (prepared) { if (prepared) {
Assertions.checkState(isPendingReset()); Assertions.checkState(isPendingReset());
if (durationUs != C.UNSET_TIME_US && pendingResetPositionUs >= durationUs) { 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 Uri uri;
private final DataSource dataSource; private final DataSource dataSource;
private final ExtractorHolder extractorHolder; private final ExtractorHolder extractorHolder;
private final Allocator allocator; private final ConditionVariable loadCondition;
private final int requestedBufferSize;
private final PositionHolder positionHolder; private final PositionHolder positionHolder;
private volatile boolean loadCanceled; private volatile boolean loadCanceled;
@ -667,12 +690,11 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private long length; private long length;
public ExtractingLoadable(Uri uri, DataSource dataSource, ExtractorHolder extractorHolder, public ExtractingLoadable(Uri uri, DataSource dataSource, ExtractorHolder extractorHolder,
Allocator allocator, int requestedBufferSize) { ConditionVariable loadCondition) {
this.uri = Assertions.checkNotNull(uri); this.uri = Assertions.checkNotNull(uri);
this.dataSource = Assertions.checkNotNull(dataSource); this.dataSource = Assertions.checkNotNull(dataSource);
this.extractorHolder = Assertions.checkNotNull(extractorHolder); this.extractorHolder = Assertions.checkNotNull(extractorHolder);
this.allocator = Assertions.checkNotNull(allocator); this.loadCondition = loadCondition;
this.requestedBufferSize = requestedBufferSize;
this.positionHolder = new PositionHolder(); this.positionHolder = new PositionHolder();
this.pendingExtractorSeek = true; this.pendingExtractorSeek = true;
this.length = C.LENGTH_UNBOUNDED; this.length = C.LENGTH_UNBOUNDED;
@ -711,11 +733,10 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
pendingExtractorSeek = false; pendingExtractorSeek = false;
} }
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { 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); 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 { } finally {
if (result == Extractor.RESULT_SEEK) { 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 * 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(); 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. * 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. * Constructs a pool with some {@link Allocation}s created up front.
* <p> * <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 individualAllocationSize The length of each individual allocation.
* @param initialAllocationCount The number of allocations to create up front. * @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) { public synchronized void setTargetBufferSize(int targetBufferSize) {
boolean targetBufferSizeReduced = targetBufferSize < this.targetBufferSize;
this.targetBufferSize = targetBufferSize; this.targetBufferSize = targetBufferSize;
if (targetBufferSizeReduced) {
trim();
}
} }
@Override @Override
@ -147,14 +151,6 @@ public final class DefaultAllocator implements Allocator {
return allocatedCount * individualAllocationSize; return allocatedCount * individualAllocationSize;
} }
@Override
public synchronized void blockWhileTotalBytesAllocatedExceeds(int limit)
throws InterruptedException {
while (getTotalBytesAllocated() > limit) {
wait();
}
}
@Override @Override
public int getIndividualAllocationLength() { public int getIndividualAllocationLength() {
return individualAllocationSize; return individualAllocationSize;