mirror of
https://github.com/samsonjs/media.git
synced 2026-04-11 12:15:47 +00:00
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:
parent
9b112cf94d
commit
54f97c952e
11 changed files with 277 additions and 215 deletions
|
|
@ -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.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue