From 54f97c952e77fbbda64f6330ac3747806d456718 Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Fri, 1 May 2015 20:28:49 +0100 Subject: [PATCH] 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. --- .../demo/player/DashRendererBuilder.java | 4 +- .../SmoothStreamingRendererBuilder.java | 4 +- .../android/exoplayer/DefaultLoadControl.java | 50 ++++---- .../extractor/ExtractorSampleSource.java | 28 ++--- .../extractor/RollingSampleBuffer.java | 113 ++++++++++-------- .../android/exoplayer/hls/HlsChunkSource.java | 8 +- .../exoplayer/hls/HlsExtractorWrapper.java | 14 +-- .../exoplayer/upstream/Allocation.java | 53 ++++++++ .../android/exoplayer/upstream/Allocator.java | 24 ++-- .../exoplayer/upstream/BufferPool.java | 96 --------------- .../exoplayer/upstream/DefaultAllocator.java | 98 +++++++++++++++ 11 files changed, 277 insertions(+), 215 deletions(-) create mode 100644 library/src/main/java/com/google/android/exoplayer/upstream/Allocation.java delete mode 100644 library/src/main/java/com/google/android/exoplayer/upstream/BufferPool.java create mode 100644 library/src/main/java/com/google/android/exoplayer/upstream/DefaultAllocator.java diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java index 5b8decfbbf..8012114132 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/DashRendererBuilder.java @@ -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; diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java b/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java index f47a0da7ff..fe1fd8b394 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/player/SmoothStreamingRendererBuilder.java @@ -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. diff --git a/library/src/main/java/com/google/android/exoplayer/DefaultLoadControl.java b/library/src/main/java/com/google/android/exoplayer/DefaultLoadControl.java index 9131c4816c..4c9b9f15f5 100644 --- a/library/src/main/java/com/google/android/exoplayer/DefaultLoadControl.java +++ b/library/src/main/java/com/google/android/exoplayer/DefaultLoadControl.java @@ -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(); 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; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java index d57d94f225..59bf9d113c 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java @@ -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 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(); - 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. } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java b/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java index 4ca5b138b1..a2926d275e 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java @@ -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 dataQueue; + private final LinkedBlockingDeque 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(); + dataQueue = new LinkedBlockingDeque(); 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); } } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java index 91fa3a947c..6adea7cb5b 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsChunkSource.java @@ -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 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; } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java index d1fd35b4ab..6e7c9eb556 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java @@ -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 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(); @@ -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; } diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/Allocation.java b/library/src/main/java/com/google/android/exoplayer/upstream/Allocation.java new file mode 100644 index 0000000000..22de92dc13 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/upstream/Allocation.java @@ -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. + *

+ * 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; + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/Allocator.java b/library/src/main/java/com/google/android/exoplayer/upstream/Allocator.java index b9a27e157f..b51217f591 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/Allocator.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/Allocator.java @@ -21,21 +21,21 @@ package com.google.android.exoplayer.upstream; public interface Allocator { /** - * Obtain a buffer from the allocator. + * Obtain an {@link Allocation}. *

- * 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(); } diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/BufferPool.java b/library/src/main/java/com/google/android/exoplayer/upstream/BufferPool.java deleted file mode 100644 index f25d171ab5..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/upstream/BufferPool.java +++ /dev/null @@ -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(); - } - } - -} diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/DefaultAllocator.java b/library/src/main/java/com/google/android/exoplayer/upstream/DefaultAllocator.java new file mode 100644 index 0000000000..1d1b8f4054 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/upstream/DefaultAllocator.java @@ -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(); + } + } + +}