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.
This commit is contained in:
Oliver Woodman 2015-05-01 20:28:49 +01:00
parent 9b112cf94d
commit 54f97c952e
11 changed files with 277 additions and 215 deletions

View file

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

View file

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

View file

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

View file

@ -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.
}

View file

@ -17,6 +17,7 @@ package com.google.android.exoplayer.extractor;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.upstream.Allocation;
import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.util.Assertions;
@ -34,10 +35,10 @@ import java.util.concurrent.LinkedBlockingDeque;
private static final int INITIAL_SCRATCH_SIZE = 32;
private final Allocator allocator;
private final int fragmentLength;
private final int allocationLength;
private final InfoQueue infoQueue;
private final LinkedBlockingDeque<byte[]> dataQueue;
private final LinkedBlockingDeque<Allocation> dataQueue;
private final SampleExtrasHolder extrasHolder;
private final ParsableByteArray scratch;
@ -46,20 +47,20 @@ import java.util.concurrent.LinkedBlockingDeque;
// Accessed only by the loading thread.
private long totalBytesWritten;
private byte[] lastFragment;
private int lastFragmentOffset;
private Allocation lastAllocation;
private int lastAllocationOffset;
/**
* @param allocator An {@link Allocator} from which allocations for sample data can be obtained.
*/
public RollingSampleBuffer(Allocator allocator) {
this.allocator = allocator;
fragmentLength = allocator.getBufferLength();
allocationLength = allocator.getIndividualAllocationLength();
infoQueue = new InfoQueue();
dataQueue = new LinkedBlockingDeque<byte[]>();
dataQueue = new LinkedBlockingDeque<Allocation>();
extrasHolder = new SampleExtrasHolder();
scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE);
lastFragmentOffset = fragmentLength;
lastAllocationOffset = allocationLength;
}
// Called by the consuming thread, but only when there is no loading thread.
@ -70,12 +71,12 @@ import java.util.concurrent.LinkedBlockingDeque;
public void clear() {
infoQueue.clear();
while (!dataQueue.isEmpty()) {
allocator.releaseBuffer(dataQueue.remove());
allocator.release(dataQueue.remove());
}
totalBytesDropped = 0;
totalBytesWritten = 0;
lastFragment = null;
lastFragmentOffset = fragmentLength;
lastAllocation = null;
lastAllocationOffset = allocationLength;
}
/**
@ -97,28 +98,28 @@ import java.util.concurrent.LinkedBlockingDeque;
/**
* Discards data from the write side of the buffer. Data is discarded from the specified absolute
* position. Any fragments that are fully discarded are returned to the allocator.
* position. Any allocations that are fully discarded are returned to the allocator.
*
* @param absolutePosition The absolute position (inclusive) from which to discard data.
*/
private void dropUpstreamFrom(long absolutePosition) {
int relativePosition = (int) (absolutePosition - totalBytesDropped);
// Calculate the index of the fragment containing the position, and the offset within it.
int fragmentIndex = relativePosition / fragmentLength;
int fragmentOffset = relativePosition % fragmentLength;
// We want to discard any fragments after the one at fragmentIndex.
int fragmentDiscardCount = dataQueue.size() - fragmentIndex - 1;
if (fragmentOffset == 0) {
// If the fragment at fragmentIndex is empty, we should discard that one too.
fragmentDiscardCount++;
// Calculate the index of the allocation containing the position, and the offset within it.
int allocationIndex = relativePosition / allocationLength;
int allocationOffset = relativePosition % allocationLength;
// We want to discard any allocations after the one at allocationIdnex.
int allocationDiscardCount = dataQueue.size() - allocationIndex - 1;
if (allocationOffset == 0) {
// If the allocation at allocationIndex is empty, we should discard that one too.
allocationDiscardCount++;
}
// Discard the fragments.
for (int i = 0; i < fragmentDiscardCount; i++) {
allocator.releaseBuffer(dataQueue.removeLast());
// Discard the allocations.
for (int i = 0; i < allocationDiscardCount; i++) {
allocator.release(dataQueue.removeLast());
}
// Update lastFragment and lastFragmentOffset to reflect the new position.
lastFragment = dataQueue.peekLast();
lastFragmentOffset = fragmentOffset == 0 ? fragmentLength : fragmentOffset;
// Update lastAllocation and lastAllocationOffset to reflect the new position.
lastAllocation = dataQueue.peekLast();
lastAllocationOffset = allocationOffset == 0 ? allocationLength : allocationOffset;
}
// Called by the consuming thread.
@ -279,9 +280,10 @@ import java.util.concurrent.LinkedBlockingDeque;
int remaining = length;
while (remaining > 0) {
dropDownstreamTo(absolutePosition);
int positionInFragment = (int) (absolutePosition - totalBytesDropped);
int toCopy = Math.min(remaining, fragmentLength - positionInFragment);
target.put(dataQueue.peek(), positionInFragment, toCopy);
int positionInAllocation = (int) (absolutePosition - totalBytesDropped);
int toCopy = Math.min(remaining, allocationLength - positionInAllocation);
Allocation allocation = dataQueue.peek();
target.put(allocation.data, allocation.translateOffset(positionInAllocation), toCopy);
absolutePosition += toCopy;
remaining -= toCopy;
}
@ -299,26 +301,28 @@ import java.util.concurrent.LinkedBlockingDeque;
int bytesRead = 0;
while (bytesRead < length) {
dropDownstreamTo(absolutePosition);
int positionInFragment = (int) (absolutePosition - totalBytesDropped);
int toCopy = Math.min(length - bytesRead, fragmentLength - positionInFragment);
System.arraycopy(dataQueue.peek(), positionInFragment, target, bytesRead, toCopy);
int positionInAllocation = (int) (absolutePosition - totalBytesDropped);
int toCopy = Math.min(length - bytesRead, allocationLength - positionInAllocation);
Allocation allocation = dataQueue.peek();
System.arraycopy(allocation.data, allocation.translateOffset(positionInAllocation), target,
bytesRead, toCopy);
absolutePosition += toCopy;
bytesRead += toCopy;
}
}
/**
* Discard any fragments that hold data prior to the specified absolute position, returning
* Discard any allocations that hold data prior to the specified absolute position, returning
* them to the allocator.
*
* @param absolutePosition The absolute position up to which fragments can be discarded.
* @param absolutePosition The absolute position up to which allocations can be discarded.
*/
private void dropDownstreamTo(long absolutePosition) {
int relativePosition = (int) (absolutePosition - totalBytesDropped);
int fragmentIndex = relativePosition / fragmentLength;
for (int i = 0; i < fragmentIndex; i++) {
allocator.releaseBuffer(dataQueue.remove());
totalBytesDropped += fragmentLength;
int allocationIndex = relativePosition / allocationLength;
for (int i = 0; i < allocationIndex; i++) {
allocator.release(dataQueue.remove());
totalBytesDropped += allocationLength;
}
}
@ -353,16 +357,17 @@ import java.util.concurrent.LinkedBlockingDeque;
*/
public int appendData(DataSource dataSource, int length) throws IOException {
ensureSpaceForWrite();
int remainingFragmentCapacity = fragmentLength - lastFragmentOffset;
length = length != C.LENGTH_UNBOUNDED ? Math.min(length, remainingFragmentCapacity)
: remainingFragmentCapacity;
int remainingAllocationCapacity = allocationLength - lastAllocationOffset;
length = length != C.LENGTH_UNBOUNDED ? Math.min(length, remainingAllocationCapacity)
: remainingAllocationCapacity;
int bytesRead = dataSource.read(lastFragment, lastFragmentOffset, length);
int bytesRead = dataSource.read(lastAllocation.data,
lastAllocation.translateOffset(lastAllocationOffset), length);
if (bytesRead == C.RESULT_END_OF_INPUT) {
return C.RESULT_END_OF_INPUT;
}
lastFragmentOffset += bytesRead;
lastAllocationOffset += bytesRead;
totalBytesWritten += bytesRead;
return bytesRead;
}
@ -377,9 +382,10 @@ import java.util.concurrent.LinkedBlockingDeque;
*/
public int appendData(ExtractorInput input, int length) throws IOException, InterruptedException {
ensureSpaceForWrite();
int thisWriteLength = Math.min(length, fragmentLength - lastFragmentOffset);
input.readFully(lastFragment, lastFragmentOffset, thisWriteLength);
lastFragmentOffset += thisWriteLength;
int thisWriteLength = Math.min(length, allocationLength - lastAllocationOffset);
input.readFully(lastAllocation.data, lastAllocation.translateOffset(lastAllocationOffset),
thisWriteLength);
lastAllocationOffset += thisWriteLength;
totalBytesWritten += thisWriteLength;
return thisWriteLength;
}
@ -394,9 +400,10 @@ import java.util.concurrent.LinkedBlockingDeque;
int remainingWriteLength = length;
while (remainingWriteLength > 0) {
ensureSpaceForWrite();
int thisWriteLength = Math.min(remainingWriteLength, fragmentLength - lastFragmentOffset);
buffer.readBytes(lastFragment, lastFragmentOffset, thisWriteLength);
lastFragmentOffset += thisWriteLength;
int thisWriteLength = Math.min(remainingWriteLength, allocationLength - lastAllocationOffset);
buffer.readBytes(lastAllocation.data, lastAllocation.translateOffset(lastAllocationOffset),
thisWriteLength);
lastAllocationOffset += thisWriteLength;
remainingWriteLength -= thisWriteLength;
}
totalBytesWritten += length;
@ -417,13 +424,13 @@ import java.util.concurrent.LinkedBlockingDeque;
}
/**
* Ensures at least one byte can be written, allocating a new fragment if necessary.
* Ensures at least one byte can be written, obtaining an additional allocation if necessary.
*/
private void ensureSpaceForWrite() {
if (lastFragmentOffset == fragmentLength) {
lastFragmentOffset = 0;
lastFragment = allocator.allocateBuffer();
dataQueue.add(lastFragment);
if (lastAllocationOffset == allocationLength) {
lastAllocationOffset = 0;
lastAllocation = allocator.allocate();
dataQueue.add(lastAllocation);
}
}

View file

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

View file

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

View file

@ -0,0 +1,53 @@
/*
* 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;
}
}

View file

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

View file

@ -1,96 +0,0 @@
/*
* 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;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Util;
import java.util.Arrays;
/**
* Default implementation of {@link Allocator}.
*/
public final class BufferPool implements Allocator {
private static final int INITIAL_RECYCLED_BUFFERS_CAPACITY = 100;
private final int bufferLength;
private int allocatedCount;
private int recycledCount;
private byte[][] recycledBuffers;
/**
* Constructs an empty pool.
*
* @param bufferLength The length of each buffer in the pool.
*/
public BufferPool(int bufferLength) {
Assertions.checkArgument(bufferLength > 0);
this.bufferLength = bufferLength;
this.recycledBuffers = new byte[INITIAL_RECYCLED_BUFFERS_CAPACITY][];
}
@Override
public synchronized byte[] allocateBuffer() {
allocatedCount++;
return recycledCount > 0 ? recycledBuffers[--recycledCount] : new byte[bufferLength];
}
@Override
public synchronized void releaseBuffer(byte[] buffer) {
// Weak sanity check that the buffer probably originated from this pool.
Assertions.checkArgument(buffer.length == bufferLength);
allocatedCount--;
if (recycledCount == recycledBuffers.length) {
recycledBuffers = Arrays.copyOf(recycledBuffers, recycledBuffers.length * 2);
}
recycledBuffers[recycledCount++] = buffer;
// 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;
}
}
@Override
public synchronized int getAllocatedSize() {
return allocatedCount * bufferLength;
}
@Override
public int getBufferLength() {
return bufferLength;
}
/**
* Blocks execution until the allocated size is not greater than the threshold, or the thread is
* interrupted.
*/
public synchronized void blockWhileAllocatedSizeExceeds(int limit) throws InterruptedException {
while (getAllocatedSize() > limit) {
wait();
}
}
}

View file

@ -0,0 +1,98 @@
/*
* 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;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Util;
import java.util.Arrays;
/**
* Default implementation of {@link Allocator}.
*/
public final class DefaultAllocator implements Allocator {
private static final int INITIAL_RECYCLED_ALLOCATION_CAPACITY = 100;
private final int individualAllocationSize;
private int allocatedCount;
private int recycledCount;
private Allocation[] recycledAllocations;
/**
* Constructs an empty pool.
*
* @param individualAllocationSize The length of each individual allocation.
*/
public DefaultAllocator(int individualAllocationSize) {
Assertions.checkArgument(individualAllocationSize > 0);
this.individualAllocationSize = individualAllocationSize;
this.recycledAllocations = new Allocation[INITIAL_RECYCLED_ALLOCATION_CAPACITY];
}
@Override
public synchronized Allocation allocate() {
allocatedCount++;
return recycledCount > 0 ? recycledAllocations[--recycledCount]
: new Allocation(new byte[individualAllocationSize], 0);
}
@Override
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 == recycledAllocations.length) {
recycledAllocations = Arrays.copyOf(recycledAllocations, recycledAllocations.length * 2);
}
recycledAllocations[recycledCount++] = allocation;
// Wake up threads waiting for the allocated size to drop.
notifyAll();
}
@Override
public synchronized void trim(int targetSize) {
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 getTotalBytesAllocated() {
return allocatedCount * individualAllocationSize;
}
@Override
public int getIndividualAllocationLength() {
return individualAllocationSize;
}
/**
* Blocks execution until the allocated number of bytes allocated is not greater than the
* threshold, or the thread is interrupted.
*/
public synchronized void blockWhileTotalBytesAllocatedExceeds(int limit)
throws InterruptedException {
while (getTotalBytesAllocated() > limit) {
wait();
}
}
}