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 
FrameSlot(FrameType type, int flags)67         public FrameSlot(FrameType type, int flags) {
68             mType = type;
69             mFlags = flags;
70         }
71 
getType()72         public FrameType getType() {
73             return mType;
74         }
75 
hasFrame()76         public boolean hasFrame() {
77             return mFrame != null;
78         }
79 
releaseFrame()80         public void releaseFrame() {
81             if (mFrame != null) {
82                 mFrame.release();
83                 mFrame = null;
84             }
85         }
86 
87         // TODO: Type check
assignFrame(Frame frame)88         public void assignFrame(Frame frame) {
89             Frame oldFrame = mFrame;
90             mFrame = frame.retain();
91             if (oldFrame != null) {
92                 oldFrame.release();
93             }
94         }
95 
getFrame()96         public Frame getFrame() {
97             Frame result = mFrame.retain();
98             if ((mFlags & SLOT_FLAG_STICKY) == 0) {
99                 releaseFrame();
100             }
101             return result;
102         }
103 
markWritable()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 
fetchBacking(int mode, int access, int[] dimensions, int elemSize)115         public abstract Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize);
116 
cacheBacking(Backing backing)117         public abstract boolean cacheBacking(Backing backing);
118 
clear()119         public abstract void clear();
120 
getSizeLeft()121         public abstract int getSizeLeft();
122 
setSize(int size)123         public void setSize(int size) {
124             mCacheMaxSize = size;
125         }
126 
getSize()127         public int getSize() {
128             return mCacheMaxSize;
129         }
130     }
131 
132     private static class BackingCacheNone extends BackingCache {
133 
134         @Override
fetchBacking(int mode, int access, int[] dimensions, int elemSize)135         public Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize) {
136             return null;
137         }
138 
139         @Override
cacheBacking(Backing backing)140         public boolean cacheBacking(Backing backing) {
141             return false;
142         }
143 
144         @Override
clear()145         public void clear() {
146         }
147 
148         @Override
getSize()149         public int getSize() {
150             return 0;
151         }
152 
153         @Override
getSizeLeft()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 
PriorityBackingCache()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
fetchBacking(int mode, int access, int[] dimensions, int elemSize)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
cacheBacking(Backing backing)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
clear()202         public void clear() {
203             mQueue.clear();
204             mSize = 0;
205         }
206 
207         @Override
getSizeLeft()208         public int getSizeLeft() {
209             return mCacheMaxSize - mSize;
210         }
211 
onCacheBacking(Backing backing)212         protected abstract void onCacheBacking(Backing backing);
213 
onFetchBacking(Backing backing)214         protected abstract void onFetchBacking(Backing backing);
215 
reserve(int size)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
onCacheBacking(Backing backing)240         protected void onCacheBacking(Backing backing) {
241             backing.cachePriority = 0;
242         }
243 
244         @Override
onFetchBacking(Backing backing)245         protected void onFetchBacking(Backing backing) {
246             ++mTimestamp;
247             backing.cachePriority = mTimestamp;
248         }
249     }
250 
251     private static class BackingCacheLfu extends PriorityBackingCache {
252         @Override
onCacheBacking(Backing backing)253         protected void onCacheBacking(Backing backing) {
254             backing.cachePriority = 0;
255         }
256 
257         @Override
onFetchBacking(Backing backing)258         protected void onFetchBacking(Backing backing) {
259             ++backing.cachePriority;
260         }
261     }
262 
current()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      */
getContext()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      */
getRunner()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      */
setCacheSize(int bytes)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      */
getCacheSize()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      */
importFrame(Frame frame)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      */
addFrameSlot(String name, FrameType type, int flags)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      */
removeFrameSlot(String name)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      */
storeFrame(Frame frame, String slotName)363     public void storeFrame(Frame frame, String slotName) {
364         assertInGraphRun();
365         getSlot(slotName).assignFrame(frame);
366     }
367 
368     /**
369      * TODO: Document!
370      */
fetchFrame(String slotName)371     public Frame fetchFrame(String slotName) {
372         assertInGraphRun();
373         return getSlot(slotName).getFrame();
374     }
375 
376     /**
377      * Clears the Frame cache.
378      */
clearCache()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      */
FrameManager(GraphRunner runner, int cacheType)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 
fetchBacking(int mode, int access, int[] dimensions, int elemSize)409     Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize) {
410         return mCache.fetchBacking(mode, access, dimensions, elemSize);
411     }
412 
onBackingCreated(Backing backing)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 
onBackingAvailable(Backing backing)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      */
destroyBackings()431     void destroyBackings() {
432         for (Backing backing : mBackings) {
433             backing.destroy();
434         }
435         mBackings.clear();
436         mCache.clear();
437     }
438 
getSlot(String name)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 
onBeginRun()447     void onBeginRun() {
448         for (FrameSlot slot : mFrameSlots.values()) {
449             slot.markWritable();
450         }
451     }
452 
453     // Internals ///////////////////////////////////////////////////////////////////////////////////
dimensionsCompatible(int[] dimA, int[] dimB)454     private static boolean dimensionsCompatible(int[] dimA, int[] dimB) {
455         return dimA == null || dimB == null || Arrays.equals(dimA, dimB);
456     }
457 
assertNotRunning()458     private void assertNotRunning() {
459         if (mRunner.isRunning()) {
460             throw new IllegalStateException("Attempting to modify FrameManager while graph is "
461                 + "running!");
462         }
463     }
464 
assertInGraphRun()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