blob: 55ed277a349ee983335053f54a14206be402b580 [file] [log] [blame]
/*
* Copyright (C) 2011 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 androidx.media.filterfw;
import androidx.media.filterfw.BackingStore.Backing;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;
/**
* The FrameManager tracks, caches, allocates and deallocates frame data.
* All Frame instances are managed by a FrameManager, and belong to exactly one of these. Frames
* cannot be shared across FrameManager instances, however multiple MffContexts may use the same
* FrameManager.
*
* Additionally, frame managers allow attaching Frames under a specified key. This allows decoupling
* filter-graphs by instructing one node to attach a frame under a specific key, and another to
* fetch the frame under the same key.
*/
public class FrameManager {
/** The default max cache size is set to 12 MB */
public final static int DEFAULT_MAX_CACHE_SIZE = 12 * 1024 * 1024;
/** Frame caching policy: No caching */
public final static int FRAME_CACHE_NONE = 0;
/** Frame caching policy: Drop least recently used frame buffers */
public final static int FRAME_CACHE_LRU = 1;
/** Frame caching policy: Drop least frequently used frame buffers */
public final static int FRAME_CACHE_LFU = 2;
/** Slot Flag: No flags set */
public final static int SLOT_FLAGS_NONE = 0x00;
/** Slot Flag: Sticky flag set: Frame will remain in slot after fetch. */
public final static int SLOT_FLAG_STICKY = 0x01;
private GraphRunner mRunner;
private Set<Backing> mBackings = new HashSet<Backing>();
private BackingCache mCache;
private Map<String, FrameSlot> mFrameSlots = new HashMap<String, FrameSlot>();
static class FrameSlot {
private FrameType mType;
private int mFlags;
private Frame mFrame = null;
public FrameSlot(FrameType type, int flags) {
mType = type;
mFlags = flags;
}
public FrameType getType() {
return mType;
}
public boolean hasFrame() {
return mFrame != null;
}
public void releaseFrame() {
if (mFrame != null) {
mFrame.release();
mFrame = null;
}
}
// TODO: Type check
public void assignFrame(Frame frame) {
Frame oldFrame = mFrame;
mFrame = frame.retain();
if (oldFrame != null) {
oldFrame.release();
}
}
public Frame getFrame() {
Frame result = mFrame.retain();
if ((mFlags & SLOT_FLAG_STICKY) == 0) {
releaseFrame();
}
return result;
}
public void markWritable() {
if (mFrame != null) {
mFrame.setReadOnly(false);
}
}
}
private static abstract class BackingCache {
protected int mCacheMaxSize = DEFAULT_MAX_CACHE_SIZE;
public abstract Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize);
public abstract boolean cacheBacking(Backing backing);
public abstract void clear();
public abstract int getSizeLeft();
public void setSize(int size) {
mCacheMaxSize = size;
}
public int getSize() {
return mCacheMaxSize;
}
}
private static class BackingCacheNone extends BackingCache {
@Override
public Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize) {
return null;
}
@Override
public boolean cacheBacking(Backing backing) {
return false;
}
@Override
public void clear() {
}
@Override
public int getSize() {
return 0;
}
@Override
public int getSizeLeft() {
return 0;
}
}
private static abstract class PriorityBackingCache extends BackingCache {
private int mSize = 0;
private PriorityQueue<Backing> mQueue;
public PriorityBackingCache() {
mQueue = new PriorityQueue<Backing>(4, new Comparator<Backing>() {
@Override
public int compare(Backing left, Backing right) {
return left.cachePriority - right.cachePriority;
}
});
}
@Override
public Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize) {
for (Backing backing : mQueue) {
int backingAccess = (mode == Frame.MODE_WRITE)
? backing.writeAccess()
: backing.readAccess();
if ((backingAccess & access) == access
&& dimensionsCompatible(backing.getDimensions(), dimensions)
&& (elemSize == backing.getElementSize())) {
mQueue.remove(backing);
mSize -= backing.getSize();
onFetchBacking(backing);
return backing;
}
}
//Log.w("FrameManager", "Could not find backing for dimensions " + Arrays.toString(dimensions));
return null;
}
@Override
public boolean cacheBacking(Backing backing) {
if (reserve(backing.getSize())) {
onCacheBacking(backing);
mQueue.add(backing);
return true;
}
return false;
}
@Override
public void clear() {
mQueue.clear();
mSize = 0;
}
@Override
public int getSizeLeft() {
return mCacheMaxSize - mSize;
}
protected abstract void onCacheBacking(Backing backing);
protected abstract void onFetchBacking(Backing backing);
private boolean reserve(int size) {
//Log.i("FM", "Reserving " + size + " bytes (max: " + mCacheMaxSize + " bytes).");
//Log.i("FM", "Current size " + mSize);
if (size > mCacheMaxSize) {
return false;
}
mSize += size;
while (mSize > mCacheMaxSize) {
Backing dropped = mQueue.poll();
mSize -= dropped.getSize();
//Log.i("FM", "Dropping " + dropped + " with priority "
// + dropped.cachePriority + ". New size: " + mSize + "!");
dropped.destroy();
}
return true;
}
}
private static class BackingCacheLru extends PriorityBackingCache {
private int mTimestamp = 0;
@Override
protected void onCacheBacking(Backing backing) {
backing.cachePriority = 0;
}
@Override
protected void onFetchBacking(Backing backing) {
++mTimestamp;
backing.cachePriority = mTimestamp;
}
}
private static class BackingCacheLfu extends PriorityBackingCache {
@Override
protected void onCacheBacking(Backing backing) {
backing.cachePriority = 0;
}
@Override
protected void onFetchBacking(Backing backing) {
++backing.cachePriority;
}
}
public static FrameManager current() {
GraphRunner runner = GraphRunner.current();
return runner != null ? runner.getFrameManager() : null;
}
/**
* Returns the context that the FrameManager is bound to.
*
* @return the MffContext instance that the FrameManager is bound to.
*/
public MffContext getContext() {
return mRunner.getContext();
}
/**
* Returns the GraphRunner that the FrameManager is bound to.
*
* @return the GraphRunner instance that the FrameManager is bound to.
*/
public GraphRunner getRunner() {
return mRunner;
}
/**
* Sets the size of the cache.
*
* Resizes the cache to the specified size in bytes.
*
* @param bytes the new size in bytes.
*/
public void setCacheSize(int bytes) {
mCache.setSize(bytes);
}
/**
* Returns the size of the cache.
*
* @return the size of the cache in bytes.
*/
public int getCacheSize() {
return mCache.getSize();
}
/**
* Imports a frame from another FrameManager.
*
* This will return a frame with the contents of the given frame for use in this FrameManager.
* Note, that there is a substantial cost involved in moving a Frame from one FrameManager to
* another. This may be called from any thread. After the frame has been imported, it may be
* used in the runner that uses this FrameManager. As the new frame may share data with the
* provided frame, that frame must be read-only.
*
* @param frame The frame to import
*/
public Frame importFrame(Frame frame) {
if (!frame.isReadOnly()) {
throw new IllegalArgumentException("Frame " + frame + " must be read-only to import "
+ "into another FrameManager!");
}
return frame.makeCpuCopy(this);
}
/**
* Adds a new frame slot to the frame manager.
* Filters can reference frame slots to pass frames between graphs or runs. If the name
* specified here is already taken the frame slot is overwritten. You can only
* modify frame-slots while no graph of the frame manager is running.
*
* @param name The name of the slot.
* @param type The type of Frame that will be assigned to this slot.
* @param flags A mask of {@code SLOT} flags.
*/
public void addFrameSlot(String name, FrameType type, int flags) {
assertNotRunning();
FrameSlot oldSlot = mFrameSlots.get(name);
if (oldSlot != null) {
removeFrameSlot(name);
}
FrameSlot slot = new FrameSlot(type, flags);
mFrameSlots.put(name, slot);
}
/**
* Removes a frame slot from the frame manager.
* Any frame within the slot is released. You can only modify frame-slots while no graph
* of the frame manager is running.
*
* @param name The name of the slot
* @throws IllegalArgumentException if no such slot exists.
*/
public void removeFrameSlot(String name) {
assertNotRunning();
FrameSlot slot = getSlot(name);
slot.releaseFrame();
mFrameSlots.remove(slot);
}
/**
* TODO: Document!
*/
public void storeFrame(Frame frame, String slotName) {
assertInGraphRun();
getSlot(slotName).assignFrame(frame);
}
/**
* TODO: Document!
*/
public Frame fetchFrame(String slotName) {
assertInGraphRun();
return getSlot(slotName).getFrame();
}
/**
* Clears the Frame cache.
*/
public void clearCache() {
mCache.clear();
}
/**
* Create a new FrameManager instance.
*
* Creates a new FrameManager instance in the specified context and employing a cache with the
* specified cache type (see the cache type constants defined by the FrameManager class).
*
* @param runner the GraphRunner to bind the FrameManager to.
* @param cacheType the type of cache to use.
*/
FrameManager(GraphRunner runner, int cacheType) {
mRunner = runner;
switch (cacheType) {
case FRAME_CACHE_NONE:
mCache = new BackingCacheNone();
break;
case FRAME_CACHE_LRU:
mCache = new BackingCacheLru();
break;
case FRAME_CACHE_LFU:
mCache = new BackingCacheLfu();
break;
default:
throw new IllegalArgumentException("Unknown cache-type " + cacheType + "!");
}
}
Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize) {
return mCache.fetchBacking(mode, access, dimensions, elemSize);
}
void onBackingCreated(Backing backing) {
if (backing != null) {
mBackings.add(backing);
// Log.i("FrameManager", "RM: Now have " + mBackings.size() + " backings");
}
}
void onBackingAvailable(Backing backing) {
if (!backing.shouldCache() || !mCache.cacheBacking(backing)) {
backing.destroy();
mBackings.remove(backing);
//Log.i("FrameManager", "RM: Now have " + mBackings.size() + " backings (" + mCache.getSizeLeft() + ")");
}
}
/**
* Destroying all references makes any Frames that contain them invalid.
*/
void destroyBackings() {
for (Backing backing : mBackings) {
backing.destroy();
}
mBackings.clear();
mCache.clear();
}
FrameSlot getSlot(String name) {
FrameSlot slot = mFrameSlots.get(name);
if (slot == null) {
throw new IllegalArgumentException("Unknown frame slot '" + name + "'!");
}
return slot;
}
void onBeginRun() {
for (FrameSlot slot : mFrameSlots.values()) {
slot.markWritable();
}
}
// Internals ///////////////////////////////////////////////////////////////////////////////////
private static boolean dimensionsCompatible(int[] dimA, int[] dimB) {
return dimA == null || dimB == null || Arrays.equals(dimA, dimB);
}
private void assertNotRunning() {
if (mRunner.isRunning()) {
throw new IllegalStateException("Attempting to modify FrameManager while graph is "
+ "running!");
}
}
private void assertInGraphRun() {
if (!mRunner.isRunning() || GraphRunner.current() != mRunner) {
throw new IllegalStateException("Attempting to access FrameManager Frame data "
+ "outside of graph run-loop!");
}
}
}