/*
* 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 java.util.Arrays;
/**
* An {@link Allocator} that maintains a pool of fixed length byte arrays (buffers).
* <p>
* An {@link Allocation} obtained from a {@link BufferPool} consists of the whole number of these
* buffers. When an {@link Allocation} is released, the underlying buffers are returned to the pool
* for re-use.
*/
public final class BufferPool implements Allocator {
private static final int INITIAL_RECYCLED_BUFFERS_CAPACITY = 100;
/**
* The length in bytes of each individual buffer in the pool.
*/
public final int bufferLength;
private int allocatedBufferCount;
private int recycledBufferCount;
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 int getAllocatedSize() {
return allocatedBufferCount * bufferLength;
}
@Override
public synchronized void trim(int targetSize) {
int targetBufferCount = (targetSize + bufferLength - 1) / bufferLength;
int targetRecycledBufferCount = Math.max(0, targetBufferCount - allocatedBufferCount);
if (targetRecycledBufferCount < recycledBufferCount) {
Arrays.fill(recycledBuffers, targetRecycledBufferCount, recycledBufferCount, null);
recycledBufferCount = targetRecycledBufferCount;
}
}
@Override
public synchronized Allocation allocate(int size) {
return new AllocationImpl(allocate(size, null));
}
/**
* Allocates byte arrays whose combined length is at least {@code size}.
* <p>
* An existing array of byte arrays may be provided to form the start of the allocation.
*
* @param size The total size required, in bytes.
* @param existing Existing byte arrays to use as the start of the allocation. May be null.
* @return The allocated byte arrays.
*/
/* package */ synchronized byte[][] allocate(int size, byte[][] existing) {
int requiredBufferCount = requiredBufferCount(size);
if (existing != null && requiredBufferCount <= existing.length) {
// The existing buffers are sufficient.
return existing;
}
// We need to allocate additional buffers.
byte[][] buffers = new byte[requiredBufferCount][];
int firstNewBufferIndex = 0;
if (existing != null) {
firstNewBufferIndex = existing.length;
System.arraycopy(existing, 0, buffers, 0, firstNewBufferIndex);
}
// Allocate the new buffers
allocatedBufferCount += requiredBufferCount - firstNewBufferIndex;
for (int i = firstNewBufferIndex; i < requiredBufferCount; i++) {
// Use a recycled buffer if one is available. Else instantiate a new one.
buffers[i] = recycledBufferCount > 0 ? recycledBuffers[--recycledBufferCount] :
new byte[bufferLength];
}
return buffers;
}
/**
* Returns the buffers belonging to an allocation to the pool.
*
* @param allocation The allocation to return.
*/
/* package */ synchronized void release(AllocationImpl allocation) {
byte[][] buffers = allocation.getBuffers();
allocatedBufferCount -= buffers.length;
int newRecycledBufferCount = recycledBufferCount + buffers.length;
if (recycledBuffers.length < newRecycledBufferCount) {
// Expand the capacity of the recycled buffers array.
byte[][] newRecycledBuffers = new byte[newRecycledBufferCount * 2][];
if (recycledBufferCount > 0) {
System.arraycopy(recycledBuffers, 0, newRecycledBuffers, 0, recycledBufferCount);
}
recycledBuffers = newRecycledBuffers;
}
System.arraycopy(buffers, 0, recycledBuffers, recycledBufferCount, buffers.length);
recycledBufferCount = newRecycledBufferCount;
}
private int requiredBufferCount(long size) {
return (int) ((size + bufferLength - 1) / bufferLength);
}
private class AllocationImpl implements Allocation {
private byte[][] buffers;
public AllocationImpl(byte[][] buffers) {
this.buffers = buffers;
}
@Override
public void ensureCapacity(int size) {
buffers = allocate(size, buffers);
}
@Override
public int capacity() {
return bufferLength * buffers.length;
}
@Override
public byte[][] getBuffers() {
return buffers;
}
@Override
public int getFragmentOffset(int index) {
return 0;
}
@Override
public int getFragmentLength(int index) {
return bufferLength;
}
@Override
public void release() {
if (buffers != null) {
BufferPool.this.release(this);
buffers = null;
}
}
}
}