Commit 8e717e2d by olly Committed by Oliver Woodman

Properly disable sample queues when not selected.

- The main goal of this change is to remove the need for
  discardSamplesForDisabledTracks() in continueBuffering
  in HlsTrackStreamWrapper and ExtractorSampleSource. As
  a result we'll also save one Allocation per disabled
  track.
- Another benefit of this change is that Allocator.trim
  calls can no longer be dropped. This could previously
  happen on player release if the playback thread's
  Looper quit before delivery of onLoadCanceled which
  performed the trim in the case that a load needed to be
  canceled at the time of the release.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=123957434
parent 14cb76a1
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer;
import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.upstream.NetworkLock;
import android.os.Handler;
......@@ -64,7 +65,7 @@ public final class DefaultLoadControl implements LoadControl {
private static final int BETWEEN_WATERMARKS = 1;
private static final int BELOW_LOW_WATERMARK = 2;
private final Allocator allocator;
private final DefaultAllocator allocator;
private final List<Object> loaders;
private final HashMap<Object, LoaderState> loaderStates;
private final Handler eventHandler;
......@@ -84,21 +85,21 @@ public final class DefaultLoadControl implements LoadControl {
/**
* Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class.
*
* @param allocator The {@link Allocator} used by the loader.
* @param allocator The {@link DefaultAllocator} used by the loader.
*/
public DefaultLoadControl(Allocator allocator) {
public DefaultLoadControl(DefaultAllocator allocator) {
this(allocator, null, null);
}
/**
* Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class.
*
* @param allocator The {@link Allocator} used by the loader.
* @param allocator The {@link DefaultAllocator} used by the loader.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
*/
public DefaultLoadControl(Allocator allocator, Handler eventHandler,
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);
......@@ -107,7 +108,7 @@ public final class DefaultLoadControl implements LoadControl {
/**
* Constructs a new instance.
*
* @param allocator The {@link Allocator} used by the loader.
* @param allocator The {@link DefaultAllocator} used by the loader.
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
* null if delivery of events is not required.
* @param eventListener A listener of events. May be null if delivery of events is not required.
......@@ -122,8 +123,9 @@ public final class DefaultLoadControl implements LoadControl {
* @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(Allocator allocator, Handler eventHandler, EventListener eventListener,
int lowWatermarkMs, int highWatermarkMs, float lowBufferLoad, float highBufferLoad) {
public DefaultLoadControl(DefaultAllocator allocator, Handler eventHandler,
EventListener eventListener, int lowWatermarkMs, int highWatermarkMs, float lowBufferLoad,
float highBufferLoad) {
this.allocator = allocator;
this.eventHandler = eventHandler;
this.eventListener = eventListener;
......@@ -140,6 +142,7 @@ public final class DefaultLoadControl implements LoadControl {
loaders.add(loader);
loaderStates.put(loader, new LoaderState(bufferSizeContribution));
targetBufferSize += bufferSizeContribution;
allocator.setTargetBufferSize(targetBufferSize);
}
@Override
......@@ -147,15 +150,11 @@ public final class DefaultLoadControl implements LoadControl {
loaders.remove(loader);
LoaderState state = loaderStates.remove(loader);
targetBufferSize -= state.bufferSizeContribution;
allocator.setTargetBufferSize(targetBufferSize);
updateControlState();
}
@Override
public void trimAllocator() {
allocator.trim(targetBufferSize);
}
@Override
public Allocator getAllocator() {
return allocator;
}
......
......@@ -48,15 +48,6 @@ public interface LoadControl {
Allocator getAllocator();
/**
* Hints to the control that it should consider trimming any unused memory being held in order
* to satisfy allocation requests.
* <p>
* This method is typically invoked by a recently unregistered loader, once it has released all
* of its allocations back to the {@link Allocator}.
*/
void trimAllocator();
/**
* Invoked by a loader to update the control with its current state.
* <p>
* This method must be called by a registered loader whenever its state changes. This is true
......
......@@ -170,16 +170,11 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
* This method should be called when the stream is no longer required.
*/
public void release() {
released = true;
chunkSource.release();
sampleQueue.disable();
loadControl.unregister(this);
if (loader.isLoading()) {
loader.cancelLoading();
} else {
clearState();
loadControl.trimAllocator();
}
loader.release();
released = true;
}
// TrackStream implementation.
......@@ -242,9 +237,6 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
eventDispatcher.loadCanceled(loadable.bytesLoaded());
if (!released) {
restartFrom(pendingResetPositionUs);
} else {
clearState();
loadControl.trimAllocator();
}
}
......@@ -277,19 +269,15 @@ public class ChunkTrackStream<T extends ChunkSource> implements TrackStream,
private void restartFrom(long positionUs) {
pendingResetPositionUs = positionUs;
loadingFinished = false;
mediaChunks.clear();
if (loader.isLoading()) {
loader.cancelLoading();
} else {
clearState();
sampleQueue.reset(true);
maybeStartLoading();
}
}
private void clearState() {
sampleQueue.clear();
mediaChunks.clear();
}
private void maybeStartLoading() {
long now = SystemClock.elapsedRealtime();
if (now - lastPreferredQueueSizeEvaluationTimeMs > 5000) {
......
......@@ -68,7 +68,7 @@ import java.util.List;
* from {@link Extractor#sniff(ExtractorInput)} will be used.
*/
public final class ExtractorSampleSource implements SampleSource, ExtractorOutput,
Loader.Callback<Loadable> {
Loader.Callback<ExtractorSampleSource.ExtractingLoadable> {
/**
* Interface definition for a callback to be notified of {@link ExtractorSampleSource} events.
......@@ -136,11 +136,9 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private boolean[] trackEnabledStates;
private long length;
private long downstreamPositionUs;
private long lastSeekPositionUs;
private long pendingResetPositionUs;
private ExtractingLoadable loadable;
private int extractedSamplesCountAtStartOfLoad;
private boolean loadingFinished;
......@@ -391,6 +389,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
Assertions.checkState(trackEnabledStates[track]);
enabledTrackCount--;
trackEnabledStates[track] = false;
sampleQueues[track].disable();
}
// Select new tracks.
TrackStream[] newStreams = new TrackStream[newSelections.size()];
......@@ -402,20 +401,24 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
Assertions.checkState(!trackEnabledStates[track]);
enabledTrackCount++;
trackEnabledStates[track] = true;
sampleQueues[track].needDownstreamFormat();
newStreams[i] = new TrackStreamImpl(track);
}
// At the time of the first track selection all queues will be enabled, so we need to disable
// any that are no longer required.
if (!seenFirstTrackSelection) {
for (int i = 0; i < sampleQueues.length; i++) {
if (!trackEnabledStates[i]) {
sampleQueues[i].disable();
}
}
}
// Cancel or start requests as necessary.
if (enabledTrackCount == 0) {
downstreamPositionUs = Long.MIN_VALUE;
if (loader.isLoading()) {
loader.cancelLoading();
} else {
clearState();
allocator.trim(0);
}
} else if (seenFirstTrackSelection ? newStreams.length > 0 : positionUs != 0) {
seekToInternal(positionUs);
seekToUs(positionUs);
}
seenFirstTrackSelection = true;
return newStreams;
......@@ -423,8 +426,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
@Override
public void continueBuffering(long playbackPositionUs) {
downstreamPositionUs = playbackPositionUs;
discardSamplesForDisabledTracks();
// Do nothing.
}
@Override
......@@ -448,18 +450,35 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs,
sampleQueue.getLargestQueuedTimestampUs());
}
return largestQueuedTimestampUs == Long.MIN_VALUE ? downstreamPositionUs
return largestQueuedTimestampUs == Long.MIN_VALUE ? lastSeekPositionUs
: largestQueuedTimestampUs;
}
}
@Override
public void seekToUs(long positionUs) {
seekToInternal(positionUs);
// Treat all seeks into non-seekable media as being to t=0.
positionUs = seekMap.isSeekable() ? positionUs : 0;
lastSeekPositionUs = positionUs;
notifyReset = true;
// If we're not pending a reset, see if we can seek within the sample queues.
boolean seekInsideBuffer = !isPendingReset();
for (int i = 0; seekInsideBuffer && i < sampleQueues.length; i++) {
if (trackEnabledStates[i]) {
seekInsideBuffer = sampleQueues[i].skipToKeyframeBefore(positionUs);
}
}
// If we failed to seek within the sample queues, we need to restart.
if (!seekInsideBuffer) {
restartFrom(positionUs);
}
}
@Override
public void release() {
for (DefaultTrackOutput sampleQueue : sampleQueues) {
sampleQueue.disable();
}
enabledTrackCount = 0;
loader.release();
}
......@@ -485,32 +504,29 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
// Loader.Callback implementation.
@Override
public void onLoadCompleted(Loadable loadable, long elapsedMs) {
copyLengthFromLoader();
public void onLoadCompleted(ExtractingLoadable loadable, long elapsedMs) {
copyLengthFromLoader(loadable);
loadingFinished = true;
}
@Override
public void onLoadCanceled(Loadable loadable, long elapsedMs) {
copyLengthFromLoader();
public void onLoadCanceled(ExtractingLoadable loadable, long elapsedMs) {
copyLengthFromLoader(loadable);
if (enabledTrackCount > 0) {
restartFrom(pendingResetPositionUs);
} else {
clearState();
allocator.trim(0);
}
}
@Override
public int onLoadError(Loadable loadable, long elapsedMs, IOException e) {
copyLengthFromLoader();
public int onLoadError(ExtractingLoadable loadable, long elapsedMs, IOException e) {
copyLengthFromLoader(loadable);
notifyLoadError(e);
if (isLoadableExceptionFatal(e)) {
return Loader.DONT_RETRY_FATAL;
}
int extractedSamplesCount = getExtractedSamplesCount();
boolean madeProgress = extractedSamplesCount > extractedSamplesCountAtStartOfLoad;
configureRetry(); // May clear the sample queues.
configureRetry(loadable); // May reset the sample queues.
extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount();
return madeProgress ? Loader.RETRY_RESET_ERROR_COUNT : Loader.RETRY;
}
......@@ -537,45 +553,28 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
// Internal methods.
private void copyLengthFromLoader() {
private void copyLengthFromLoader(ExtractingLoadable loadable) {
if (length == C.LENGTH_UNBOUNDED) {
length = loadable.length;
}
}
private void seekToInternal(long positionUs) {
// Treat all seeks into non-seekable media as being to t=0.
positionUs = seekMap.isSeekable() ? positionUs : 0;
lastSeekPositionUs = positionUs;
downstreamPositionUs = positionUs;
notifyReset = true;
// If we're not pending a reset, see if we can seek within the sample queues.
boolean seekInsideBuffer = !isPendingReset();
for (int i = 0; seekInsideBuffer && i < sampleQueues.length; i++) {
if (trackEnabledStates[i]) {
seekInsideBuffer = sampleQueues[i].skipToKeyframeBefore(positionUs);
}
}
// If we failed to seek within the sample queues, we need to restart.
if (!seekInsideBuffer) {
restartFrom(positionUs);
}
}
private void restartFrom(long positionUs) {
pendingResetPositionUs = positionUs;
loadingFinished = false;
if (loader.isLoading()) {
loader.cancelLoading();
} else {
clearState();
for (int i = 0; i < sampleQueues.length; i++) {
sampleQueues[i].reset(trackEnabledStates[i]);
}
startLoading();
}
}
private void startLoading() {
loadable = new ExtractingLoadable(uri, dataSource, extractorHolder, allocator,
requestedBufferSize);
ExtractingLoadable loadable = new ExtractingLoadable(uri, dataSource, extractorHolder,
allocator, requestedBufferSize);
if (prepared) {
Assertions.checkState(isPendingReset());
if (durationUs != C.UNSET_TIME_US && pendingResetPositionUs >= durationUs) {
......@@ -599,8 +598,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
loader.startLoading(loadable, this, minRetryCount);
}
private void configureRetry() {
Assertions.checkState(loadable != null);
private void configureRetry(ExtractingLoadable loadable) {
if (length != C.LENGTH_UNBOUNDED
|| (seekMap != null && seekMap.getDurationUs() != C.UNSET_TIME_US)) {
// We're playing an on-demand stream. Resume the current loadable, which will
......@@ -612,7 +610,9 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
// previous load finished, so it's necessary to load from the start whenever commencing
// a new load.
notifyReset = prepared;
clearSampleQueues();
for (int i = 0; i < sampleQueues.length; i++) {
sampleQueues[i].reset(trackEnabledStates[i]);
}
loadable.setLoadPosition(0);
}
}
......@@ -634,25 +634,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
return true;
}
private void discardSamplesForDisabledTracks() {
for (int i = 0; i < trackEnabledStates.length; i++) {
if (!trackEnabledStates[i]) {
sampleQueues[i].skipAllSamples();
}
}
}
private void clearState() {
clearSampleQueues();
loadable = null;
}
private void clearSampleQueues() {
for (DefaultTrackOutput sampleQueue : sampleQueues) {
sampleQueue.clear();
}
}
private boolean isPendingReset() {
return pendingResetPositionUs != C.UNSET_TIME_US;
}
......@@ -700,7 +681,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
/**
* Loads the media stream and extracts sample data from it.
*/
private static class ExtractingLoadable implements Loadable {
/* package */ static final class ExtractingLoadable implements Loadable {
private final Uri uri;
private final DataSource dataSource;
......
......@@ -177,6 +177,7 @@ import java.util.List;
for (int i = 0; i < oldStreams.size(); i++) {
int group = ((TrackStreamImpl) oldStreams.get(i)).group;
setTrackGroupEnabledState(group, false);
sampleQueues.valueAt(group).disable();
}
// Select new tracks.
TrackStream[] newStreams = new TrackStream[newSelections.size()];
......@@ -185,25 +186,32 @@ import java.util.List;
int group = selection.group;
int[] tracks = selection.getTracks();
setTrackGroupEnabledState(group, true);
sampleQueues.valueAt(group).needDownstreamFormat();
if (group == primaryTrackGroupIndex) {
chunkSource.selectTracks(tracks, isFirstTrackSelection);
}
newStreams[i] = new TrackStreamImpl(group);
}
// Cancel or start requests as necessary.
// At the time of the first track selection all queues will be enabled, so we need to disable
// any that are no longer required.
if (isFirstTrackSelection) {
int sampleQueueCount = sampleQueues.size();
for (int i = 0; i < sampleQueueCount; i++) {
if (!groupEnabledStates[i]) {
sampleQueues.valueAt(i).disable();
}
}
}
// Cancel requests if necessary.
if (enabledTrackCount == 0) {
chunkSource.reset();
downstreamPositionUs = Long.MIN_VALUE;
downstreamFormat = null;
mediaChunks.clear();
if (tracksWereEnabled) {
loadControl.unregister(this);
}
if (loader.isLoading()) {
loader.cancelLoading();
} else {
clearState();
loadControl.trimAllocator();
}
} else if (!tracksWereEnabled) {
loadControl.register(this, bufferSizeContribution);
......@@ -211,9 +219,26 @@ import java.util.List;
return newStreams;
}
public void restartFrom(long positionUs) {
lastSeekPositionUs = positionUs;
downstreamPositionUs = positionUs;
pendingResetPositionUs = positionUs;
loadingFinished = false;
mediaChunks.clear();
if (loader.isLoading()) {
loader.cancelLoading();
} else {
int sampleQueueCount = sampleQueues.size();
for (int i = 0; i < sampleQueueCount; i++) {
sampleQueues.valueAt(i).reset(groupEnabledStates[i]);
}
maybeStartLoading();
}
}
// TODO[REFACTOR]: Find a way to get rid of this.
public void continueBuffering(long playbackPositionUs) {
downstreamPositionUs = playbackPositionUs;
discardSamplesForDisabledTracks();
if (!loader.isLoading()) {
maybeStartLoading();
}
......@@ -245,23 +270,14 @@ import java.util.List;
}
}
public void restartFrom(long positionUs) {
lastSeekPositionUs = positionUs;
downstreamPositionUs = positionUs;
pendingResetPositionUs = positionUs;
loadingFinished = false;
if (loader.isLoading()) {
loader.cancelLoading();
} else {
clearState();
maybeStartLoading();
}
}
public void release() {
int sampleQueueCount = sampleQueues.size();
for (int i = 0; i < sampleQueueCount; i++) {
sampleQueues.valueAt(i).disable();
}
if (enabledTrackCount > 0) {
loadControl.unregister(this);
enabledTrackCount = 0;
loadControl.unregister(this);
}
loader.release();
}
......@@ -319,9 +335,6 @@ import java.util.List;
eventDispatcher.loadCanceled(loadable.bytesLoaded());
if (enabledTrackCount > 0) {
restartFrom(pendingResetPositionUs);
} else {
clearState();
loadControl.trimAllocator();
}
}
......@@ -501,24 +514,6 @@ import java.util.List;
containerFormat.width, containerFormat.height, 0, containerFormat.language);
}
private void discardSamplesForDisabledTracks() {
if (!prepared) {
return;
}
for (int i = 0; i < groupEnabledStates.length; i++) {
if (!groupEnabledStates[i]) {
sampleQueues.valueAt(i).skipAllSamples();
}
}
}
private void clearState() {
for (int i = 0; i < sampleQueues.size(); i++) {
sampleQueues.valueAt(i).clear();
}
mediaChunks.clear();
}
private void maybeStartLoading() {
boolean shouldStartLoading = !prepared || (enabledTrackCount > 0
&& loadControl.update(this, downstreamPositionUs, getNextLoadPositionUs(), false));
......
......@@ -39,11 +39,9 @@ public interface Allocator {
/**
* Hints to the {@link Allocator} that it should make a best effort to release any memory that it
* has allocated, beyond the specified target number of bytes.
*
* @param targetSize The target size in bytes.
* has allocated that it no longer requires.
*/
void trim(int targetSize);
void trim();
/**
* Blocks execution until the number of bytes allocated is not greater than the limit, or the
......
......@@ -30,6 +30,7 @@ public final class DefaultAllocator implements Allocator {
private final int individualAllocationSize;
private final byte[] initialAllocationBlock;
private int targetBufferSize;
private int allocatedCount;
private int availableCount;
private Allocation[] availableAllocations;
......@@ -68,6 +69,10 @@ public final class DefaultAllocator implements Allocator {
}
}
public synchronized void setTargetBufferSize(int targetBufferSize) {
this.targetBufferSize = targetBufferSize;
}
@Override
public synchronized Allocation allocate() {
allocatedCount++;
......@@ -96,8 +101,8 @@ public final class DefaultAllocator implements Allocator {
}
@Override
public synchronized void trim(int targetSize) {
int targetAllocationCount = Util.ceilDivide(targetSize, individualAllocationSize);
public synchronized void trim() {
int targetAllocationCount = Util.ceilDivide(targetBufferSize, individualAllocationSize);
int targetAvailableCount = Math.max(0, targetAllocationCount - allocatedCount);
if (targetAvailableCount >= availableCount) {
// We're already at or below the target.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment