Commit 54f97c95 by Oliver Woodman

Reintroduce Allocation abstraction.

Play movies has an Allocator that attempts to allocate a single
huge byte[] up front to minimize the risk of GC pauses. This abstraction
will be required to keep that when updating them to the new Exo.
parent 9b112cf9
......@@ -48,8 +48,8 @@ import com.google.android.exoplayer.drm.StreamingDrmSessionManager;
import com.google.android.exoplayer.text.TextTrackRenderer;
import com.google.android.exoplayer.text.ttml.TtmlParser;
import com.google.android.exoplayer.text.webvtt.WebvttParser;
import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer.upstream.DefaultUriDataSource;
......@@ -170,7 +170,7 @@ public class DashRendererBuilder implements RendererBuilder,
private void buildRenderers() {
Period period = manifest.periods.get(0);
Handler mainHandler = player.getMainHandler();
LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE));
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, player);
boolean hasContentProtection = false;
......
......@@ -40,8 +40,8 @@ import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.Trac
import com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifestParser;
import com.google.android.exoplayer.text.TextTrackRenderer;
import com.google.android.exoplayer.text.ttml.TtmlParser;
import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer.upstream.DefaultUriDataSource;
......@@ -107,7 +107,7 @@ public class SmoothStreamingRendererBuilder implements RendererBuilder,
@Override
public void onSingleManifest(SmoothStreamingManifest manifest) {
Handler mainHandler = player.getMainHandler();
LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE));
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(mainHandler, player);
// Check drm support if necessary.
......
......@@ -57,8 +57,8 @@ public 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_POOL_LOAD = 0.2f;
public static final float DEFAULT_HIGH_POOL_LOAD = 0.8f;
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;
......@@ -72,12 +72,12 @@ public class DefaultLoadControl implements LoadControl {
private final long lowWatermarkUs;
private final long highWatermarkUs;
private final float lowPoolLoad;
private final float highPoolLoad;
private final float lowBufferLoad;
private final float highBufferLoad;
private int targetBufferSize;
private long maxLoadStartPositionUs;
private int bufferPoolState;
private int bufferState;
private boolean fillingBuffers;
private boolean streamingPrioritySet;
......@@ -101,7 +101,7 @@ public class DefaultLoadControl implements LoadControl {
public DefaultLoadControl(Allocator allocator, Handler eventHandler,
EventListener eventListener) {
this(allocator, eventHandler, eventListener, DEFAULT_LOW_WATERMARK_MS,
DEFAULT_HIGH_WATERMARK_MS, DEFAULT_LOW_POOL_LOAD, DEFAULT_HIGH_POOL_LOAD);
DEFAULT_HIGH_WATERMARK_MS, DEFAULT_LOW_BUFFER_LOAD, DEFAULT_HIGH_BUFFER_LOAD);
}
/**
......@@ -116,14 +116,14 @@ public 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 lowPoolLoad The minimum fraction of the buffer that must be utilized for the control
* @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 highPoolLoad The minimum fraction of the buffer that must be utilized for the control
* @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 lowPoolLoad, float highPoolLoad) {
int lowWatermarkMs, int highWatermarkMs, float lowBufferLoad, float highBufferLoad) {
this.allocator = allocator;
this.eventHandler = eventHandler;
this.eventListener = eventListener;
......@@ -131,8 +131,8 @@ public class DefaultLoadControl implements LoadControl {
this.loaderStates = new HashMap<Object, LoaderState>();
this.lowWatermarkUs = lowWatermarkMs * 1000L;
this.highWatermarkUs = highWatermarkMs * 1000L;
this.lowPoolLoad = lowPoolLoad;
this.highPoolLoad = highPoolLoad;
this.lowBufferLoad = lowBufferLoad;
this.highBufferLoad = highBufferLoad;
}
@Override
......@@ -176,20 +176,20 @@ public class DefaultLoadControl implements LoadControl {
loaderState.failed = failed;
}
// Update the buffer pool state.
int allocatedSize = allocator.getAllocatedSize();
int bufferPoolState = getBufferPoolState(allocatedSize);
boolean bufferPoolStateChanged = this.bufferPoolState != bufferPoolState;
if (bufferPoolStateChanged) {
this.bufferPoolState = bufferPoolState;
// Update the buffer state.
int currentBufferSize = allocator.getTotalBytesAllocated();
int bufferState = getBufferState(currentBufferSize);
boolean bufferStateChanged = this.bufferState != bufferState;
if (bufferStateChanged) {
this.bufferState = bufferState;
}
// If either of the individual states have changed, update the shared control state.
if (loaderStateChanged || bufferPoolStateChanged) {
if (loaderStateChanged || bufferStateChanged) {
updateControlState();
}
return allocatedSize < targetBufferSize && nextLoadPositionUs != -1
return currentBufferSize < targetBufferSize && nextLoadPositionUs != -1
&& nextLoadPositionUs <= maxLoadStartPositionUs;
}
......@@ -204,18 +204,18 @@ public class DefaultLoadControl implements LoadControl {
}
}
private int getBufferPoolState(int allocatedSize) {
float bufferPoolLoad = (float) allocatedSize / targetBufferSize;
return bufferPoolLoad > highPoolLoad ? ABOVE_HIGH_WATERMARK :
bufferPoolLoad < lowPoolLoad ? BELOW_LOW_WATERMARK :
BETWEEN_WATERMARKS;
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 failed = false;
boolean haveNextLoadPosition = false;
int highestState = bufferPoolState;
int highestState = bufferState;
for (int i = 0; i < loaders.size(); i++) {
LoaderState loaderState = loaderStates.get(loaders.get(i));
loading |= loaderState.loading;
......
......@@ -24,9 +24,9 @@ import com.google.android.exoplayer.TrackInfo;
import com.google.android.exoplayer.TrackRenderer;
import com.google.android.exoplayer.drm.DrmInitData;
import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.upstream.Loader;
import com.google.android.exoplayer.upstream.Loader.Loadable;
import com.google.android.exoplayer.util.Assertions;
......@@ -57,7 +57,7 @@ public class ExtractorSampleSource implements SampleSource, ExtractorOutput, Loa
private static final int NO_RESET_PENDING = -1;
private final Extractor extractor;
private final BufferPool bufferPool;
private final DefaultAllocator allocator;
private final int requestedBufferSize;
private final SparseArray<InternalTrackOutput> sampleQueues;
private final int minLoadableRetryCount;
......@@ -130,7 +130,7 @@ public class ExtractorSampleSource implements SampleSource, ExtractorOutput, Loa
this.requestedBufferSize = requestedBufferSize;
this.minLoadableRetryCount = minLoadableRetryCount;
sampleQueues = new SparseArray<InternalTrackOutput>();
bufferPool = new BufferPool(BUFFER_FRAGMENT_LENGTH);
allocator = new DefaultAllocator(BUFFER_FRAGMENT_LENGTH);
pendingResetPositionUs = NO_RESET_PENDING;
frameAccurateSeeking = true;
extractor.init(this);
......@@ -203,7 +203,7 @@ public class ExtractorSampleSource implements SampleSource, ExtractorOutput, Loa
loader.cancelLoading();
} else {
clearState();
bufferPool.trim(0);
allocator.trim(0);
}
}
}
......@@ -332,7 +332,7 @@ public class ExtractorSampleSource implements SampleSource, ExtractorOutput, Loa
restartFrom(pendingResetPositionUs);
} else {
clearState();
bufferPool.trim(0);
allocator.trim(0);
}
}
......@@ -351,7 +351,7 @@ public class ExtractorSampleSource implements SampleSource, ExtractorOutput, Loa
public TrackOutput track(int id) {
InternalTrackOutput sampleQueue = sampleQueues.get(id);
if (sampleQueue == null) {
sampleQueue = new InternalTrackOutput(bufferPool);
sampleQueue = new InternalTrackOutput(allocator);
sampleQueues.put(id, sampleQueue);
}
return sampleQueue;
......@@ -476,11 +476,11 @@ public class ExtractorSampleSource implements SampleSource, ExtractorOutput, Loa
}
private ExtractingLoadable createLoadableFromStart() {
return new ExtractingLoadable(uri, dataSource, extractor, bufferPool, requestedBufferSize, 0);
return new ExtractingLoadable(uri, dataSource, extractor, allocator, requestedBufferSize, 0);
}
private ExtractingLoadable createLoadableFromPositionUs(long positionUs) {
return new ExtractingLoadable(uri, dataSource, extractor, bufferPool, requestedBufferSize,
return new ExtractingLoadable(uri, dataSource, extractor, allocator, requestedBufferSize,
seekMap.getPosition(positionUs));
}
......@@ -554,8 +554,8 @@ public class ExtractorSampleSource implements SampleSource, ExtractorOutput, Loa
private final Uri uri;
private final DataSource dataSource;
private final Extractor extractor;
private final BufferPool bufferPool;
private final int bufferPoolSizeLimit;
private final DefaultAllocator allocator;
private final int requestedBufferSize;
private final PositionHolder positionHolder;
private volatile boolean loadCanceled;
......@@ -563,12 +563,12 @@ public class ExtractorSampleSource implements SampleSource, ExtractorOutput, Loa
private boolean pendingExtractorSeek;
public ExtractingLoadable(Uri uri, DataSource dataSource, Extractor extractor,
BufferPool bufferPool, int bufferPoolSizeLimit, long position) {
DefaultAllocator allocator, int requestedBufferSize, long position) {
this.uri = Assertions.checkNotNull(uri);
this.dataSource = Assertions.checkNotNull(dataSource);
this.extractor = Assertions.checkNotNull(extractor);
this.bufferPool = Assertions.checkNotNull(bufferPool);
this.bufferPoolSizeLimit = bufferPoolSizeLimit;
this.allocator = Assertions.checkNotNull(allocator);
this.requestedBufferSize = requestedBufferSize;
positionHolder = new PositionHolder();
positionHolder.position = position;
pendingExtractorSeek = true;
......@@ -601,7 +601,7 @@ public class ExtractorSampleSource implements SampleSource, ExtractorOutput, Loa
}
input = new DefaultExtractorInput(dataSource, position, length);
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
bufferPool.blockWhileAllocatedSizeExceeds(bufferPoolSizeLimit);
allocator.blockWhileTotalBytesAllocatedExceeds(requestedBufferSize);
result = extractor.read(input, positionHolder);
// TODO: Implement throttling to stop us from buffering data too often.
}
......
......@@ -25,9 +25,9 @@ import com.google.android.exoplayer.extractor.Extractor;
import com.google.android.exoplayer.extractor.ts.AdtsExtractor;
import com.google.android.exoplayer.extractor.ts.TsExtractor;
import com.google.android.exoplayer.upstream.BandwidthMeter;
import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.upstream.HttpDataSource.InvalidResponseCodeException;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.MimeTypes;
......@@ -126,7 +126,7 @@ public class HlsChunkSource {
private static final String AAC_FILE_EXTENSION = ".aac";
private static final float BANDWIDTH_FRACTION = 0.8f;
private final BufferPool bufferPool;
private final DefaultAllocator bufferPool;
private final DataSource dataSource;
private final HlsPlaylistParser playlistParser;
private final List<Variant> variants;
......@@ -193,7 +193,7 @@ public class HlsChunkSource {
maxBufferDurationToSwitchDownUs = maxBufferDurationToSwitchDownMs * 1000;
baseUri = playlist.baseUri;
playlistParser = new HlsPlaylistParser();
bufferPool = new BufferPool(256 * 1024);
bufferPool = new DefaultAllocator(256 * 1024);
if (playlist.type == HlsPlaylist.TYPE_MEDIA) {
variants = Collections.singletonList(new Variant(playlistUrl, 0, null, -1, -1));
......@@ -258,7 +258,7 @@ public class HlsChunkSource {
long playbackPositionUs) {
if (previousTsChunk != null && (previousTsChunk.isLastChunk
|| previousTsChunk.endTimeUs - playbackPositionUs >= targetBufferDurationUs)
|| bufferPool.getAllocatedSize() >= targetBufferSize) {
|| bufferPool.getTotalBytesAllocated() >= targetBufferSize) {
// We're either finished, or we have the target amount of data or time buffered.
return null;
}
......
......@@ -25,7 +25,7 @@ import com.google.android.exoplayer.extractor.ExtractorInput;
import com.google.android.exoplayer.extractor.ExtractorOutput;
import com.google.android.exoplayer.extractor.SeekMap;
import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.upstream.DefaultAllocator;
import com.google.android.exoplayer.util.Assertions;
import android.util.SparseArray;
......@@ -41,7 +41,7 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
public final Format format;
public final long startTimeUs;
private final BufferPool bufferPool;
private final DefaultAllocator allocator;
private final Extractor extractor;
private final SparseArray<DefaultTrackOutput> sampleQueues;
private final boolean shouldSpliceIn;
......@@ -52,12 +52,12 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
private boolean prepared;
private boolean spliceConfigured;
public HlsExtractorWrapper(int trigger, Format format, long startTimeUs, BufferPool bufferPool,
Extractor extractor, boolean shouldSpliceIn) {
public HlsExtractorWrapper(int trigger, Format format, long startTimeUs,
DefaultAllocator allocator, Extractor extractor, boolean shouldSpliceIn) {
this.trigger = trigger;
this.format = format;
this.startTimeUs = startTimeUs;
this.bufferPool = bufferPool;
this.allocator = allocator;
this.extractor = extractor;
this.shouldSpliceIn = shouldSpliceIn;
sampleQueues = new SparseArray<DefaultTrackOutput>();
......@@ -136,7 +136,7 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
}
/**
* Clears queues for all tracks, returning all allocations to the buffer pool.
* Clears queues for all tracks, returning all allocations to the allocator.
*/
public void clear() {
for (int i = 0; i < sampleQueues.size(); i++) {
......@@ -211,7 +211,7 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
@Override
public TrackOutput track(int id) {
DefaultTrackOutput sampleQueue = new DefaultTrackOutput(bufferPool);
DefaultTrackOutput sampleQueue = new DefaultTrackOutput(allocator);
sampleQueues.put(id, sampleQueue);
return sampleQueue;
}
......
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer.upstream;
/**
* An allocation within a byte array.
* <p>
* The allocation's length is obtained by calling {@link Allocator#getIndividualAllocationLength()}
* on the {@link Allocator} from which it was obtained.
*/
public final class Allocation {
/**
* The array containing the allocated space. The allocated space may not be at the start of the
* array, and so {@link #translateOffset(int)} method must be used when indexing into it.
*/
public final byte[] data;
private final int offset;
/**
* @param data The array containing the allocated space.
* @param offset The offset of the allocated space within the array.
*/
public Allocation(byte[] data, int offset) {
this.data = data;
this.offset = offset;
}
/**
* Translates a zero-based offset into the allocation to the corresponding {@link #data} offset.
*
* @param offset The zero-based offset to translate.
* @return The corresponding offset in {@link #data}.
*/
public int translateOffset(int offset) {
return this.offset + offset;
}
}
......@@ -21,21 +21,21 @@ package com.google.android.exoplayer.upstream;
public interface Allocator {
/**
* Obtain a buffer from the allocator.
* Obtain an {@link Allocation}.
* <p>
* When the caller has finished with the buffer, it should be returned by calling
* {@link #releaseBuffer(byte[])}.
* When the caller has finished with the {@link Allocation}, it should be returned by calling
* {@link #release(Allocation)}.
*
* @return The allocated buffer.
* @return The {@link Allocation}.
*/
byte[] allocateBuffer();
Allocation allocate();
/**
* Return a buffer to the allocator.
* Return an {@link Allocation}.
*
* @param buffer The buffer being returned.
* @param allocation The {@link Allocation} being returned.
*/
void releaseBuffer(byte[] buffer);
void release(Allocation allocation);
/**
* Hints to the {@link Allocator} that it should make a best effort to release any memory that it
......@@ -46,13 +46,13 @@ public interface Allocator {
void trim(int targetSize);
/**
* Returns the total size of all allocated buffers.
* Returns the total number of bytes currently allocated.
*/
int getAllocatedSize();
int getTotalBytesAllocated();
/**
* Returns the length of each buffer provided by the allocator.
* Returns the length of each individual {@link Allocation}.
*/
int getBufferLength();
int getIndividualAllocationLength();
}
......@@ -23,72 +23,74 @@ import java.util.Arrays;
/**
* Default implementation of {@link Allocator}.
*/
public final class BufferPool implements Allocator {
public final class DefaultAllocator implements Allocator {
private static final int INITIAL_RECYCLED_BUFFERS_CAPACITY = 100;
private static final int INITIAL_RECYCLED_ALLOCATION_CAPACITY = 100;
private final int bufferLength;
private final int individualAllocationSize;
private int allocatedCount;
private int recycledCount;
private byte[][] recycledBuffers;
private Allocation[] recycledAllocations;
/**
* Constructs an empty pool.
*
* @param bufferLength The length of each buffer in the pool.
* @param individualAllocationSize The length of each individual allocation.
*/
public BufferPool(int bufferLength) {
Assertions.checkArgument(bufferLength > 0);
this.bufferLength = bufferLength;
this.recycledBuffers = new byte[INITIAL_RECYCLED_BUFFERS_CAPACITY][];
public DefaultAllocator(int individualAllocationSize) {
Assertions.checkArgument(individualAllocationSize > 0);
this.individualAllocationSize = individualAllocationSize;
this.recycledAllocations = new Allocation[INITIAL_RECYCLED_ALLOCATION_CAPACITY];
}
@Override
public synchronized byte[] allocateBuffer() {
public synchronized Allocation allocate() {
allocatedCount++;
return recycledCount > 0 ? recycledBuffers[--recycledCount] : new byte[bufferLength];
return recycledCount > 0 ? recycledAllocations[--recycledCount]
: new Allocation(new byte[individualAllocationSize], 0);
}
@Override
public synchronized void releaseBuffer(byte[] buffer) {
// Weak sanity check that the buffer probably originated from this pool.
Assertions.checkArgument(buffer.length == bufferLength);
public synchronized void release(Allocation allocation) {
// Weak sanity check that the allocation probably originated from this pool.
Assertions.checkArgument(allocation.data.length == individualAllocationSize);
allocatedCount--;
if (recycledCount == recycledBuffers.length) {
recycledBuffers = Arrays.copyOf(recycledBuffers, recycledBuffers.length * 2);
if (recycledCount == recycledAllocations.length) {
recycledAllocations = Arrays.copyOf(recycledAllocations, recycledAllocations.length * 2);
}
recycledBuffers[recycledCount++] = buffer;
recycledAllocations[recycledCount++] = allocation;
// Wake up threads waiting for the allocated size to drop.
notifyAll();
}
@Override
public synchronized void trim(int targetSize) {
int targetBufferCount = Util.ceilDivide(targetSize, bufferLength);
int targetRecycledBufferCount = Math.max(0, targetBufferCount - allocatedCount);
if (targetRecycledBufferCount < recycledCount) {
Arrays.fill(recycledBuffers, targetRecycledBufferCount, recycledCount, null);
recycledCount = targetRecycledBufferCount;
int targetAllocationCount = Util.ceilDivide(targetSize, individualAllocationSize);
int targetRecycledAllocationCount = Math.max(0, targetAllocationCount - allocatedCount);
if (targetRecycledAllocationCount < recycledCount) {
Arrays.fill(recycledAllocations, targetRecycledAllocationCount, recycledCount, null);
recycledCount = targetRecycledAllocationCount;
}
}
@Override
public synchronized int getAllocatedSize() {
return allocatedCount * bufferLength;
public synchronized int getTotalBytesAllocated() {
return allocatedCount * individualAllocationSize;
}
@Override
public int getBufferLength() {
return bufferLength;
public int getIndividualAllocationLength() {
return individualAllocationSize;
}
/**
* Blocks execution until the allocated size is not greater than the threshold, or the thread is
* interrupted.
* Blocks execution until the allocated number of bytes allocated is not greater than the
* threshold, or the thread is interrupted.
*/
public synchronized void blockWhileAllocatedSizeExceeds(int limit) throws InterruptedException {
while (getAllocatedSize() > limit) {
public synchronized void blockWhileTotalBytesAllocatedExceeds(int limit)
throws InterruptedException {
while (getTotalBytesAllocated() > limit) {
wait();
}
}
......
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