Home | History | Annotate | Download | only in filterfw
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.media.filterfw;
     18 
     19 import androidx.media.filterfw.BackingStore.Backing;
     20 
     21 import java.util.Arrays;
     22 import java.util.Comparator;
     23 import java.util.HashMap;
     24 import java.util.HashSet;
     25 import java.util.Map;
     26 import java.util.PriorityQueue;
     27 import java.util.Set;
     28 
     29 /**
     30  * The FrameManager tracks, caches, allocates and deallocates frame data.
     31  * All Frame instances are managed by a FrameManager, and belong to exactly one of these. Frames
     32  * cannot be shared across FrameManager instances, however multiple MffContexts may use the same
     33  * FrameManager.
     34  *
     35  * Additionally, frame managers allow attaching Frames under a specified key. This allows decoupling
     36  * filter-graphs by instructing one node to attach a frame under a specific key, and another to
     37  * fetch the frame under the same key.
     38  */
     39 public class FrameManager {
     40 
     41     /** The default max cache size is set to 12 MB */
     42     public final static int DEFAULT_MAX_CACHE_SIZE = 12 * 1024 * 1024;
     43 
     44     /** Frame caching policy: No caching */
     45     public final static int FRAME_CACHE_NONE = 0;
     46     /** Frame caching policy: Drop least recently used frame buffers */
     47     public final static int FRAME_CACHE_LRU = 1;
     48     /** Frame caching policy: Drop least frequently used frame buffers */
     49     public final static int FRAME_CACHE_LFU = 2;
     50 
     51     /** Slot Flag: No flags set */
     52     public final static int SLOT_FLAGS_NONE = 0x00;
     53     /** Slot Flag: Sticky flag set: Frame will remain in slot after fetch. */
     54     public final static int SLOT_FLAG_STICKY = 0x01;
     55 
     56     private GraphRunner mRunner;
     57     private Set<Backing> mBackings = new HashSet<Backing>();
     58     private BackingCache mCache;
     59 
     60     private Map<String, FrameSlot> mFrameSlots = new HashMap<String, FrameSlot>();
     61 
     62     static class FrameSlot {
     63         private FrameType mType;
     64         private int mFlags;
     65         private Frame mFrame = null;
     66 
     67         public FrameSlot(FrameType type, int flags) {
     68             mType = type;
     69             mFlags = flags;
     70         }
     71 
     72         public FrameType getType() {
     73             return mType;
     74         }
     75 
     76         public boolean hasFrame() {
     77             return mFrame != null;
     78         }
     79 
     80         public void releaseFrame() {
     81             if (mFrame != null) {
     82                 mFrame.release();
     83                 mFrame = null;
     84             }
     85         }
     86 
     87         // TODO: Type check
     88         public void assignFrame(Frame frame) {
     89             Frame oldFrame = mFrame;
     90             mFrame = frame.retain();
     91             if (oldFrame != null) {
     92                 oldFrame.release();
     93             }
     94         }
     95 
     96         public Frame getFrame() {
     97             Frame result = mFrame.retain();
     98             if ((mFlags & SLOT_FLAG_STICKY) == 0) {
     99                 releaseFrame();
    100             }
    101             return result;
    102         }
    103 
    104         public void markWritable() {
    105             if (mFrame != null) {
    106                 mFrame.setReadOnly(false);
    107             }
    108         }
    109     }
    110 
    111     private static abstract class BackingCache {
    112 
    113         protected int mCacheMaxSize = DEFAULT_MAX_CACHE_SIZE;
    114 
    115         public abstract Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize);
    116 
    117         public abstract boolean cacheBacking(Backing backing);
    118 
    119         public abstract void clear();
    120 
    121         public abstract int getSizeLeft();
    122 
    123         public void setSize(int size) {
    124             mCacheMaxSize = size;
    125         }
    126 
    127         public int getSize() {
    128             return mCacheMaxSize;
    129         }
    130     }
    131 
    132     private static class BackingCacheNone extends BackingCache {
    133 
    134         @Override
    135         public Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize) {
    136             return null;
    137         }
    138 
    139         @Override
    140         public boolean cacheBacking(Backing backing) {
    141             return false;
    142         }
    143 
    144         @Override
    145         public void clear() {
    146         }
    147 
    148         @Override
    149         public int getSize() {
    150             return 0;
    151         }
    152 
    153         @Override
    154         public int getSizeLeft() {
    155             return 0;
    156         }
    157     }
    158 
    159     private static abstract class PriorityBackingCache extends BackingCache {
    160         private int mSize = 0;
    161         private PriorityQueue<Backing> mQueue;
    162 
    163         public PriorityBackingCache() {
    164             mQueue = new PriorityQueue<Backing>(4, new Comparator<Backing>() {
    165                 @Override
    166                 public int compare(Backing left, Backing right) {
    167                     return left.cachePriority - right.cachePriority;
    168                 }
    169             });
    170         }
    171 
    172         @Override
    173         public Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize) {
    174             for (Backing backing : mQueue) {
    175                 int backingAccess = (mode == Frame.MODE_WRITE)
    176                     ? backing.writeAccess()
    177                     : backing.readAccess();
    178                 if ((backingAccess & access) == access
    179                     && dimensionsCompatible(backing.getDimensions(), dimensions)
    180                     && (elemSize == backing.getElementSize())) {
    181                     mQueue.remove(backing);
    182                     mSize -= backing.getSize();
    183                     onFetchBacking(backing);
    184                     return backing;
    185                 }
    186             }
    187             //Log.w("FrameManager", "Could not find backing for dimensions " + Arrays.toString(dimensions));
    188             return null;
    189         }
    190 
    191         @Override
    192         public boolean cacheBacking(Backing backing) {
    193             if (reserve(backing.getSize())) {
    194                 onCacheBacking(backing);
    195                 mQueue.add(backing);
    196                 return true;
    197             }
    198             return false;
    199         }
    200 
    201         @Override
    202         public void clear() {
    203             mQueue.clear();
    204             mSize = 0;
    205         }
    206 
    207         @Override
    208         public int getSizeLeft() {
    209             return mCacheMaxSize - mSize;
    210         }
    211 
    212         protected abstract void onCacheBacking(Backing backing);
    213 
    214         protected abstract void onFetchBacking(Backing backing);
    215 
    216         private boolean reserve(int size) {
    217             //Log.i("FM", "Reserving " + size + " bytes (max: " + mCacheMaxSize + " bytes).");
    218             //Log.i("FM", "Current size " + mSize);
    219             if (size > mCacheMaxSize) {
    220                 return false;
    221             }
    222             mSize += size;
    223             while (mSize > mCacheMaxSize) {
    224                 Backing dropped = mQueue.poll();
    225                 mSize -= dropped.getSize();
    226                 //Log.i("FM", "Dropping  " + dropped + " with priority "
    227                 //    + dropped.cachePriority + ". New size: " + mSize + "!");
    228                 dropped.destroy();
    229             }
    230             return true;
    231         }
    232 
    233 
    234     }
    235 
    236     private static class BackingCacheLru extends PriorityBackingCache {
    237         private int mTimestamp = 0;
    238 
    239         @Override
    240         protected void onCacheBacking(Backing backing) {
    241             backing.cachePriority = 0;
    242         }
    243 
    244         @Override
    245         protected void onFetchBacking(Backing backing) {
    246             ++mTimestamp;
    247             backing.cachePriority = mTimestamp;
    248         }
    249     }
    250 
    251     private static class BackingCacheLfu extends PriorityBackingCache {
    252         @Override
    253         protected void onCacheBacking(Backing backing) {
    254             backing.cachePriority = 0;
    255         }
    256 
    257         @Override
    258         protected void onFetchBacking(Backing backing) {
    259             ++backing.cachePriority;
    260         }
    261     }
    262 
    263     public static FrameManager current() {
    264         GraphRunner runner = GraphRunner.current();
    265         return runner != null ? runner.getFrameManager() : null;
    266     }
    267 
    268     /**
    269      * Returns the context that the FrameManager is bound to.
    270      *
    271      * @return the MffContext instance that the FrameManager is bound to.
    272      */
    273     public MffContext getContext() {
    274         return mRunner.getContext();
    275     }
    276 
    277     /**
    278      * Returns the GraphRunner that the FrameManager is bound to.
    279      *
    280      * @return the GraphRunner instance that the FrameManager is bound to.
    281      */
    282     public GraphRunner getRunner() {
    283         return mRunner;
    284     }
    285 
    286     /**
    287      * Sets the size of the cache.
    288      *
    289      * Resizes the cache to the specified size in bytes.
    290      *
    291      * @param bytes the new size in bytes.
    292      */
    293     public void setCacheSize(int bytes) {
    294         mCache.setSize(bytes);
    295     }
    296 
    297     /**
    298      * Returns the size of the cache.
    299      *
    300      * @return the size of the cache in bytes.
    301      */
    302     public int getCacheSize() {
    303         return mCache.getSize();
    304     }
    305 
    306     /**
    307      * Imports a frame from another FrameManager.
    308      *
    309      * This will return a frame with the contents of the given frame for use in this FrameManager.
    310      * Note, that there is a substantial cost involved in moving a Frame from one FrameManager to
    311      * another. This may be called from any thread. After the frame has been imported, it may be
    312      * used in the runner that uses this FrameManager. As the new frame may share data with the
    313      * provided frame, that frame must be read-only.
    314      *
    315      * @param frame The frame to import
    316      */
    317     public Frame importFrame(Frame frame) {
    318         if (!frame.isReadOnly()) {
    319             throw new IllegalArgumentException("Frame " + frame + " must be read-only to import "
    320                     + "into another FrameManager!");
    321         }
    322         return frame.makeCpuCopy(this);
    323     }
    324 
    325     /**
    326      * Adds a new frame slot to the frame manager.
    327      * Filters can reference frame slots to pass frames between graphs or runs. If the name
    328      * specified here is already taken the frame slot is overwritten. You can only
    329      * modify frame-slots while no graph of the frame manager is running.
    330      *
    331      * @param name The name of the slot.
    332      * @param type The type of Frame that will be assigned to this slot.
    333      * @param flags A mask of {@code SLOT} flags.
    334      */
    335     public void addFrameSlot(String name, FrameType type, int flags) {
    336         assertNotRunning();
    337         FrameSlot oldSlot = mFrameSlots.get(name);
    338         if (oldSlot != null) {
    339             removeFrameSlot(name);
    340         }
    341         FrameSlot slot = new FrameSlot(type, flags);
    342         mFrameSlots.put(name, slot);
    343     }
    344 
    345     /**
    346      * Removes a frame slot from the frame manager.
    347      * Any frame within the slot is released. You can only modify frame-slots while no graph
    348      * of the frame manager is running.
    349      *
    350      * @param name The name of the slot
    351      * @throws IllegalArgumentException if no such slot exists.
    352      */
    353     public void removeFrameSlot(String name) {
    354         assertNotRunning();
    355         FrameSlot slot = getSlot(name);
    356         slot.releaseFrame();
    357         mFrameSlots.remove(slot);
    358     }
    359 
    360     /**
    361      * TODO: Document!
    362      */
    363     public void storeFrame(Frame frame, String slotName) {
    364         assertInGraphRun();
    365         getSlot(slotName).assignFrame(frame);
    366     }
    367 
    368     /**
    369      * TODO: Document!
    370      */
    371     public Frame fetchFrame(String slotName) {
    372         assertInGraphRun();
    373         return getSlot(slotName).getFrame();
    374     }
    375 
    376     /**
    377      * Clears the Frame cache.
    378      */
    379     public void clearCache() {
    380         mCache.clear();
    381     }
    382 
    383     /**
    384      * Create a new FrameManager instance.
    385      *
    386      * Creates a new FrameManager instance in the specified context and employing a cache with the
    387      * specified cache type (see the cache type constants defined by the FrameManager class).
    388      *
    389      * @param runner the GraphRunner to bind the FrameManager to.
    390      * @param cacheType the type of cache to use.
    391      */
    392     FrameManager(GraphRunner runner, int cacheType) {
    393         mRunner = runner;
    394         switch (cacheType) {
    395             case FRAME_CACHE_NONE:
    396                 mCache = new BackingCacheNone();
    397                 break;
    398             case FRAME_CACHE_LRU:
    399                 mCache = new BackingCacheLru();
    400                 break;
    401             case FRAME_CACHE_LFU:
    402                 mCache = new BackingCacheLfu();
    403                 break;
    404             default:
    405                 throw new IllegalArgumentException("Unknown cache-type " + cacheType + "!");
    406         }
    407     }
    408 
    409     Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize) {
    410         return mCache.fetchBacking(mode, access, dimensions, elemSize);
    411     }
    412 
    413     void onBackingCreated(Backing backing) {
    414         if (backing != null) {
    415             mBackings.add(backing);
    416             // Log.i("FrameManager", "RM: Now have " + mBackings.size() + " backings");
    417         }
    418     }
    419 
    420     void onBackingAvailable(Backing backing) {
    421         if (!backing.shouldCache() || !mCache.cacheBacking(backing)) {
    422             backing.destroy();
    423             mBackings.remove(backing);
    424             //Log.i("FrameManager", "RM: Now have " + mBackings.size() + " backings (" + mCache.getSizeLeft() + ")");
    425         }
    426     }
    427 
    428     /**
    429      * Destroying all references makes any Frames that contain them invalid.
    430      */
    431     void destroyBackings() {
    432         for (Backing backing : mBackings) {
    433             backing.destroy();
    434         }
    435         mBackings.clear();
    436         mCache.clear();
    437     }
    438 
    439     FrameSlot getSlot(String name) {
    440         FrameSlot slot = mFrameSlots.get(name);
    441         if (slot == null) {
    442             throw new IllegalArgumentException("Unknown frame slot '" + name + "'!");
    443         }
    444         return slot;
    445     }
    446 
    447     void onBeginRun() {
    448         for (FrameSlot slot : mFrameSlots.values()) {
    449             slot.markWritable();
    450         }
    451     }
    452 
    453     // Internals ///////////////////////////////////////////////////////////////////////////////////
    454     private static boolean dimensionsCompatible(int[] dimA, int[] dimB) {
    455         return dimA == null || dimB == null || Arrays.equals(dimA, dimB);
    456     }
    457 
    458     private void assertNotRunning() {
    459         if (mRunner.isRunning()) {
    460             throw new IllegalStateException("Attempting to modify FrameManager while graph is "
    461                 + "running!");
    462         }
    463     }
    464 
    465     private void assertInGraphRun() {
    466         if (!mRunner.isRunning() || GraphRunner.current() != mRunner) {
    467             throw new IllegalStateException("Attempting to access FrameManager Frame data "
    468                 + "outside of graph run-loop!");
    469         }
    470     }
    471 
    472 }
    473 
    474