/* * 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 mBackings = new HashSet(); private BackingCache mCache; private Map mFrameSlots = new HashMap(); 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 mQueue; public PriorityBackingCache() { mQueue = new PriorityQueue(4, new Comparator() { @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!"); } } }