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