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"); you may not use this file except
      5  * in compliance with the License. You may obtain a copy of the License at
      6  *
      7  * http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the License
     10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
     11  * or implied. See the License for the specific language governing permissions and limitations under
     12  * the License.
     13  */
     14 
     15 package androidx.media.filterfw;
     16 
     17 import android.os.ConditionVariable;
     18 import android.os.SystemClock;
     19 import android.util.Log;
     20 
     21 import java.util.HashSet;
     22 import java.util.Set;
     23 import java.util.Stack;
     24 import java.util.concurrent.LinkedBlockingQueue;
     25 
     26 /**
     27  * A GraphRunner schedules and executes the filter nodes of a graph.
     28  *
     29  * Typically, you create a GraphRunner given a FilterGraph instance, and execute it by calling
     30  * {@link #start(FilterGraph)}.
     31  *
     32  * The scheduling strategy determines how the filter nodes are selected
     33  * for scheduling. More precisely, given the set of nodes that can be scheduled, the scheduling
     34  * strategy determines which node of this set to select for execution. For instance, an LFU
     35  * scheduler (the default) chooses the node that has been executed the least amount of times.
     36  */
     37 public final class GraphRunner {
     38 
     39     private static int PRIORITY_SLEEP = -1;
     40     private static int PRIORITY_STOP = -2;
     41 
     42     private static final Event BEGIN_EVENT = new Event(Event.BEGIN, null);
     43     private static final Event FLUSH_EVENT = new Event(Event.FLUSH, null);
     44     private static final Event HALT_EVENT = new Event(Event.HALT, null);
     45     private static final Event KILL_EVENT = new Event(Event.KILL, null);
     46     private static final Event PAUSE_EVENT = new Event(Event.PAUSE, null);
     47     private static final Event RELEASE_FRAMES_EVENT = new Event(Event.RELEASE_FRAMES, null);
     48     private static final Event RESTART_EVENT = new Event(Event.RESTART, null);
     49     private static final Event RESUME_EVENT = new Event(Event.RESUME, null);
     50     private static final Event STEP_EVENT = new Event(Event.STEP, null);
     51     private static final Event STOP_EVENT = new Event(Event.STOP, null);
     52 
     53     private static class State {
     54         public static final int STOPPED = 1;
     55         public static final int PREPARING = 2;
     56         public static final int RUNNING = 4;
     57         public static final int PAUSED = 8;
     58         public static final int HALTED = 16;
     59 
     60         private int mCurrent = STOPPED;
     61 
     62         public synchronized void setState(int newState) {
     63             mCurrent = newState;
     64         }
     65 
     66         public synchronized boolean check(int state) {
     67             return ((mCurrent & state) == state);
     68         }
     69 
     70         public synchronized boolean addState(int state) {
     71             if ((mCurrent & state) != state) {
     72                 mCurrent |= state;
     73                 return true;
     74             }
     75             return false;
     76         }
     77 
     78         public synchronized boolean removeState(int state) {
     79             boolean result = (mCurrent & state) == state;
     80             mCurrent &= (~state);
     81             return result;
     82         }
     83 
     84         public synchronized int current() {
     85             return mCurrent;
     86         }
     87     }
     88 
     89     private static class Event {
     90         public static final int PREPARE = 1;
     91         public static final int BEGIN = 2;
     92         public static final int STEP = 3;
     93         public static final int STOP = 4;
     94         public static final int PAUSE = 6;
     95         public static final int HALT = 7;
     96         public static final int RESUME = 8;
     97         public static final int RESTART = 9;
     98         public static final int FLUSH = 10;
     99         public static final int TEARDOWN = 11;
    100         public static final int KILL = 12;
    101         public static final int RELEASE_FRAMES = 13;
    102 
    103         public int code;
    104         public Object object;
    105 
    106         public Event(int code, Object object) {
    107             this.code = code;
    108             this.object = object;
    109         }
    110     }
    111 
    112     private final class GraphRunLoop implements Runnable {
    113 
    114         private State mState = new State();
    115         private final boolean mAllowOpenGL;
    116         private RenderTarget mRenderTarget = null;
    117         private LinkedBlockingQueue<Event> mEventQueue = new LinkedBlockingQueue<Event>();
    118         private Exception mCaughtException = null;
    119         private boolean mClosedSuccessfully = true;
    120         private Stack<Filter[]> mFilters = new Stack<Filter[]>();
    121         private Stack<SubListener> mSubListeners = new Stack<SubListener>();
    122         private Set<FilterGraph> mOpenedGraphs = new HashSet<FilterGraph>();
    123         public ConditionVariable mStopCondition = new ConditionVariable(true);
    124 
    125         private void loop() {
    126             boolean killed = false;
    127             while (!killed) {
    128                 try {
    129                     Event event = nextEvent();
    130                     if (event == null) continue;
    131                     switch (event.code) {
    132                         case Event.PREPARE:
    133                             onPrepare((FilterGraph)event.object);
    134                             break;
    135                         case Event.BEGIN:
    136                             onBegin();
    137                             break;
    138                         case Event.STEP:
    139                             onStep();
    140                             break;
    141                         case Event.STOP:
    142                             onStop();
    143                             break;
    144                         case Event.PAUSE:
    145                             onPause();
    146                             break;
    147                         case Event.HALT:
    148                             onHalt();
    149                             break;
    150                         case Event.RESUME:
    151                             onResume();
    152                             break;
    153                         case Event.RESTART:
    154                             onRestart();
    155                             break;
    156                         case Event.FLUSH:
    157                             onFlush();
    158                             break;
    159                         case Event.TEARDOWN:
    160                             onTearDown((FilterGraph)event.object);
    161                             break;
    162                         case Event.KILL:
    163                             killed = true;
    164                             break;
    165                         case Event.RELEASE_FRAMES:
    166                             onReleaseFrames();
    167                             break;
    168                     }
    169                 } catch (Exception e) {
    170                     if (mCaughtException == null) {
    171                         mCaughtException = e;
    172                         mClosedSuccessfully = true;
    173                         e.printStackTrace();
    174                         pushEvent(STOP_EVENT);
    175                     } else {
    176                         // Exception during exception recovery? Abort all processing. Do not
    177                         // overwrite the original exception.
    178                         mClosedSuccessfully = false;
    179                         mEventQueue.clear();
    180                         cleanUp();
    181                     }
    182                 }
    183             }
    184         }
    185 
    186         public GraphRunLoop(boolean allowOpenGL) {
    187             mAllowOpenGL = allowOpenGL;
    188         }
    189 
    190         @Override
    191         public void run() {
    192             onInit();
    193             loop();
    194             onDestroy();
    195         }
    196 
    197         public void enterSubGraph(FilterGraph graph, SubListener listener) {
    198             if (mState.check(State.RUNNING)) {
    199                 onOpenGraph(graph);
    200                 mSubListeners.push(listener);
    201             }
    202         }
    203 
    204         public void pushWakeEvent(Event event) {
    205             // This is of course not race-condition proof. The worst case is that the event
    206             // is pushed even though the queue was not empty, which is acceptible for our cases.
    207             if (mEventQueue.isEmpty()) {
    208                 pushEvent(event);
    209             }
    210         }
    211 
    212         public void pushEvent(Event event) {
    213             mEventQueue.offer(event);
    214         }
    215 
    216         public void pushEvent(int eventId, Object object) {
    217             mEventQueue.offer(new Event(eventId, object));
    218         }
    219 
    220         public boolean checkState(int state) {
    221             return mState.check(state);
    222         }
    223 
    224         public ConditionVariable getStopCondition() {
    225             return mStopCondition;
    226         }
    227 
    228         public boolean isOpenGLAllowed() {
    229             // Does not need synchronization as mAllowOpenGL flag is final.
    230             return mAllowOpenGL;
    231         }
    232 
    233         private Event nextEvent() {
    234             try {
    235                 return mEventQueue.take();
    236             } catch (InterruptedException e) {
    237                 // Ignore and keep going.
    238                 Log.w("GraphRunner", "Event queue processing was interrupted.");
    239                 return null;
    240             }
    241         }
    242 
    243         private void onPause() {
    244             mState.addState(State.PAUSED);
    245         }
    246 
    247         private void onResume() {
    248             if (mState.removeState(State.PAUSED)) {
    249                 if (mState.current() == State.RUNNING) {
    250                     pushEvent(STEP_EVENT);
    251                 }
    252             }
    253         }
    254 
    255         private void onHalt() {
    256             if (mState.addState(State.HALTED) && mState.check(State.RUNNING)) {
    257                 closeAllFilters();
    258             }
    259         }
    260 
    261         private void onRestart() {
    262             if (mState.removeState(State.HALTED)) {
    263                 if (mState.current() == State.RUNNING) {
    264                     pushEvent(STEP_EVENT);
    265                 }
    266             }
    267         }
    268 
    269         private void onDestroy() {
    270             mFrameManager.destroyBackings();
    271             if (mRenderTarget != null) {
    272                 mRenderTarget.release();
    273                 mRenderTarget = null;
    274             }
    275         }
    276 
    277         private void onReleaseFrames() {
    278             mFrameManager.destroyBackings();
    279         }
    280 
    281         private void onInit() {
    282             mThreadRunner.set(GraphRunner.this);
    283             if (getContext().isOpenGLSupported()) {
    284                 mRenderTarget = RenderTarget.newTarget(1, 1);
    285                 mRenderTarget.focus();
    286             }
    287         }
    288 
    289         private void onPrepare(FilterGraph graph) {
    290             if (mState.current() == State.STOPPED) {
    291                 mState.setState(State.PREPARING);
    292                 mCaughtException = null;
    293                 onOpenGraph(graph);
    294             }
    295         }
    296 
    297         private void onOpenGraph(FilterGraph graph) {
    298             loadFilters(graph);
    299             mOpenedGraphs.add(graph);
    300             mScheduler.prepare(currentFilters());
    301             pushEvent(BEGIN_EVENT);
    302         }
    303 
    304         private void onBegin() {
    305             if (mState.current() == State.PREPARING) {
    306                 mState.setState(State.RUNNING);
    307                 pushEvent(STEP_EVENT);
    308             }
    309         }
    310 
    311         private void onStarve() {
    312             mFilters.pop();
    313             if (mFilters.empty()) {
    314                 onStop();
    315             } else {
    316                 SubListener listener = mSubListeners.pop();
    317                 if (listener != null) {
    318                     listener.onSubGraphRunEnded(GraphRunner.this);
    319                 }
    320                 mScheduler.prepare(currentFilters());
    321                 pushEvent(STEP_EVENT);
    322             }
    323         }
    324 
    325         private void onStop() {
    326             if (mState.check(State.RUNNING)) {
    327                 // Close filters if not already halted (and already closed)
    328                 if (!mState.check(State.HALTED)) {
    329                     closeAllFilters();
    330                 }
    331                 cleanUp();
    332             }
    333         }
    334 
    335         private void cleanUp() {
    336             mState.setState(State.STOPPED);
    337             if (flushOnClose()) {
    338                 onFlush();
    339             }
    340             mOpenedGraphs.clear();
    341             mFilters.clear();
    342             onRunnerStopped(mCaughtException, mClosedSuccessfully);
    343             mStopCondition.open();
    344         }
    345 
    346         private void onStep() {
    347             if (mState.current() == State.RUNNING) {
    348                 Filter bestFilter = null;
    349                 long maxPriority = PRIORITY_STOP;
    350                 mScheduler.beginStep();
    351                 Filter[] filters = currentFilters();
    352                 for (int i = 0; i < filters.length; ++i) {
    353                     Filter filter = filters[i];
    354                     long priority = mScheduler.priorityForFilter(filter);
    355                     if (priority > maxPriority) {
    356                         maxPriority = priority;
    357                         bestFilter = filter;
    358                     }
    359                 }
    360                 if (maxPriority == PRIORITY_SLEEP) {
    361                     // NOOP: When going into sleep mode, we simply do not schedule another node.
    362                     // If some other event (such as a resume()) does schedule, then we may schedule
    363                     // during sleeping. This is an edge case an irrelevant. (On the other hand,
    364                     // going into a dedicated "sleep state" requires highly complex synchronization
    365                     // to not "miss" a wake-up event. Thus we choose the more defensive approach
    366                     // here).
    367                 } else if (maxPriority == PRIORITY_STOP) {
    368                     onStarve();
    369                 } else {
    370                     scheduleFilter(bestFilter);
    371                     pushEvent(STEP_EVENT);
    372                 }
    373             } else {
    374                 Log.w("GraphRunner", "State is not running! (" + mState.current() + ")");
    375             }
    376         }
    377 
    378         private void onFlush() {
    379            if (mState.check(State.HALTED) || mState.check(State.STOPPED)) {
    380                for (FilterGraph graph : mOpenedGraphs) {
    381                    graph.flushFrames();
    382                }
    383            }
    384         }
    385 
    386         private void onTearDown(FilterGraph graph) {
    387             for (Filter filter : graph.getAllFilters()) {
    388                 filter.performTearDown();
    389             }
    390             graph.wipe();
    391         }
    392 
    393         private void loadFilters(FilterGraph graph) {
    394             Filter[] filters = graph.getAllFilters();
    395             mFilters.push(filters);
    396         }
    397 
    398         private void closeAllFilters() {
    399             for (FilterGraph graph : mOpenedGraphs) {
    400                 closeFilters(graph);
    401             }
    402         }
    403 
    404         private void closeFilters(FilterGraph graph) {
    405             // [Non-iterator looping]
    406             Log.v("GraphRunner", "CLOSING FILTERS");
    407             Filter[] filters = graph.getAllFilters();
    408             boolean isVerbose = isVerbose();
    409             for (int i = 0; i < filters.length; ++i) {
    410                 if (isVerbose) {
    411                     Log.i("GraphRunner", "Closing Filter " + filters[i] + "!");
    412                 }
    413                 filters[i].softReset();
    414             }
    415         }
    416 
    417         private Filter[] currentFilters() {
    418             return mFilters.peek();
    419         }
    420 
    421         private void scheduleFilter(Filter filter) {
    422             long scheduleTime = 0;
    423             if (isVerbose()) {
    424                 scheduleTime = SystemClock.elapsedRealtime();
    425                 Log.i("GraphRunner", scheduleTime + ": Scheduling " + filter + "!");
    426             }
    427             filter.execute();
    428             if (isVerbose()) {
    429                 long nowTime = SystemClock.elapsedRealtime();
    430                 Log.i("GraphRunner",
    431                         "-> Schedule time (" + filter + ") = " + (nowTime - scheduleTime) + " ms.");
    432             }
    433         }
    434 
    435     }
    436 
    437     // GraphRunner.Scheduler classes ///////////////////////////////////////////////////////////////
    438     private interface Scheduler {
    439         public void prepare(Filter[] filters);
    440 
    441         public int getStrategy();
    442 
    443         public void beginStep();
    444 
    445         public long priorityForFilter(Filter filter);
    446 
    447     }
    448 
    449     private class LruScheduler implements Scheduler {
    450 
    451         private long mNow;
    452 
    453         @Override
    454         public void prepare(Filter[] filters) {
    455         }
    456 
    457         @Override
    458         public int getStrategy() {
    459             return STRATEGY_LRU;
    460         }
    461 
    462         @Override
    463         public void beginStep() {
    464             // TODO(renn): We could probably do this with a simple GraphRunner counter that would
    465             // represent GraphRunner local time. This would allow us to use integers instead of
    466             // longs, and save us calls to the system clock.
    467             mNow = SystemClock.elapsedRealtime();
    468         }
    469 
    470         @Override
    471         public long priorityForFilter(Filter filter) {
    472             if (filter.isSleeping()) {
    473                 return PRIORITY_SLEEP;
    474             } else if (filter.canSchedule()) {
    475                 return mNow - filter.getLastScheduleTime();
    476             } else {
    477                 return PRIORITY_STOP;
    478             }
    479         }
    480 
    481     }
    482 
    483     private class LfuScheduler implements Scheduler {
    484 
    485         private final int MAX_PRIORITY = Integer.MAX_VALUE;
    486 
    487         @Override
    488         public void prepare(Filter[] filters) {
    489             // [Non-iterator looping]
    490             for (int i = 0; i < filters.length; ++i) {
    491                 filters[i].resetScheduleCount();
    492             }
    493         }
    494 
    495         @Override
    496         public int getStrategy() {
    497             return STRATEGY_LFU;
    498         }
    499 
    500         @Override
    501         public void beginStep() {
    502         }
    503 
    504         @Override
    505         public long priorityForFilter(Filter filter) {
    506             return filter.isSleeping() ? PRIORITY_SLEEP
    507                     : (filter.canSchedule() ? (MAX_PRIORITY - filter.getScheduleCount())
    508                             : PRIORITY_STOP);
    509         }
    510 
    511     }
    512 
    513     private class OneShotScheduler extends LfuScheduler {
    514         private int mCurCount = 1;
    515 
    516         @Override
    517         public void prepare(Filter[] filters) {
    518             // [Non-iterator looping]
    519             for (int i = 0; i < filters.length; ++i) {
    520                 filters[i].resetScheduleCount();
    521             }
    522         }
    523 
    524         @Override
    525         public int getStrategy() {
    526             return STRATEGY_ONESHOT;
    527         }
    528 
    529         @Override
    530         public void beginStep() {
    531         }
    532 
    533         @Override
    534         public long priorityForFilter(Filter filter) {
    535             return filter.getScheduleCount() < mCurCount ? super.priorityForFilter(filter)
    536                     : PRIORITY_STOP;
    537         }
    538 
    539     }
    540 
    541     // GraphRunner.Listener callback class /////////////////////////////////////////////////////////
    542     public interface Listener {
    543         /**
    544          * Callback method that is called when the runner completes a run. This method is called
    545          * only if the graph completed without an error.
    546          */
    547         public void onGraphRunnerStopped(GraphRunner runner);
    548 
    549         /**
    550          * Callback method that is called when runner encounters an error.
    551          *
    552          *  Any exceptions thrown in the GraphRunner's thread will cause the run to abort. The
    553          * thrown exception is passed to the listener in this method. If no listener is set, the
    554          * exception message is logged to the error stream. You will not receive an
    555          * {@link #onGraphRunnerStopped(GraphRunner)} callback in case of an error.
    556          *
    557          * @param exception the exception that was thrown.
    558          * @param closedSuccessfully true, if the graph was closed successfully after the error.
    559          */
    560         public void onGraphRunnerError(Exception exception, boolean closedSuccessfully);
    561     }
    562 
    563     public interface SubListener {
    564         public void onSubGraphRunEnded(GraphRunner runner);
    565     }
    566 
    567     /**
    568      * Config class to setup a GraphRunner with a custom configuration.
    569      *
    570      * The configuration object is passed to the constructor. Any changes to it will not affect
    571      * the created GraphRunner instance.
    572      */
    573     public static class Config {
    574         /** The runner's thread priority. */
    575         public int threadPriority = Thread.NORM_PRIORITY;
    576         /** Whether to allow filters to use OpenGL or not. */
    577         public boolean allowOpenGL = true;
    578     }
    579 
    580     /** Parameters shared between run-thread and GraphRunner frontend. */
    581     private class RunParameters {
    582         public Listener listener = null;
    583         public boolean isVerbose = false;
    584         public boolean flushOnClose = true;
    585     }
    586 
    587     // GraphRunner implementation //////////////////////////////////////////////////////////////////
    588     /** Schedule strategy: From set of candidates, pick a random one. */
    589     public static final int STRATEGY_RANDOM = 1;
    590     /** Schedule strategy: From set of candidates, pick node executed least recently executed. */
    591     public static final int STRATEGY_LRU = 2;
    592     /** Schedule strategy: From set of candidates, pick node executed least number of times. */
    593     public static final int STRATEGY_LFU = 3;
    594     /** Schedule strategy: Schedules no node more than once. */
    595     public static final int STRATEGY_ONESHOT = 4;
    596 
    597     private final MffContext mContext;
    598 
    599     private FilterGraph mRunningGraph = null;
    600     private Set<FilterGraph> mGraphs = new HashSet<FilterGraph>();
    601 
    602     private Scheduler mScheduler;
    603 
    604     private GraphRunLoop mRunLoop;
    605 
    606     private Thread mRunThread = null;
    607 
    608     private FrameManager mFrameManager = null;
    609 
    610     private static ThreadLocal<GraphRunner> mThreadRunner = new ThreadLocal<GraphRunner>();
    611 
    612     private RunParameters mParams = new RunParameters();
    613 
    614     /**
    615      * Creates a new GraphRunner with the default configuration. You must attach FilterGraph
    616      * instances to this runner before you can execute any of these graphs.
    617      *
    618      * @param context The MffContext instance for this runner.
    619      */
    620     public GraphRunner(MffContext context) {
    621         mContext = context;
    622         init(new Config());
    623     }
    624 
    625     /**
    626      * Creates a new GraphRunner with the specified configuration. You must attach FilterGraph
    627      * instances to this runner before you can execute any of these graphs.
    628      *
    629      * @param context The MffContext instance for this runner.
    630      * @param config A Config instance with the configuration of this runner.
    631      */
    632     public GraphRunner(MffContext context, Config config) {
    633         mContext = context;
    634         init(config);
    635     }
    636 
    637     /**
    638      * Returns the currently running graph-runner.
    639      * @return The currently running graph-runner.
    640      */
    641     public static GraphRunner current() {
    642         return mThreadRunner.get();
    643     }
    644 
    645     /**
    646      * Returns the graph that this runner is currently executing. Returns null if no graph is
    647      * currently being executed by this runner.
    648      *
    649      * @return the FilterGraph instance that this GraphRunner is executing.
    650      */
    651     public synchronized FilterGraph getRunningGraph() {
    652         return mRunningGraph;
    653     }
    654 
    655     /**
    656      * Returns the context that this runner is bound to.
    657      *
    658      * @return the MffContext instance that this runner is bound to.
    659      */
    660     public MffContext getContext() {
    661         return mContext;
    662     }
    663 
    664     /**
    665      * Begins graph execution. The graph filters are scheduled and executed until processing
    666      * finishes or is stopped.
    667      */
    668     public synchronized void start(FilterGraph graph) {
    669         if (graph.mRunner != this) {
    670             throw new IllegalArgumentException("Graph must be attached to runner!");
    671         }
    672         mRunningGraph = graph;
    673         mRunLoop.getStopCondition().close();
    674         mRunLoop.pushEvent(Event.PREPARE, graph);
    675     }
    676 
    677     /**
    678      * Begin executing a sub-graph. This only succeeds if the current runner is already
    679      * executing.
    680      */
    681     public void enterSubGraph(FilterGraph graph, SubListener listener) {
    682         if (Thread.currentThread() != mRunThread) {
    683             throw new RuntimeException("enterSubGraph must be called from the runner's thread!");
    684         }
    685         mRunLoop.enterSubGraph(graph, listener);
    686     }
    687 
    688     /**
    689      * Waits until graph execution has finished or stopped with an error.
    690      * Care must be taken when using this method to not block the UI thread. This is typically
    691      * used when a graph is run in one-shot mode to compute a result.
    692      */
    693     public void waitUntilStop() {
    694         mRunLoop.getStopCondition().block();
    695     }
    696 
    697     /**
    698      * Pauses graph execution.
    699      */
    700     public void pause() {
    701         mRunLoop.pushEvent(PAUSE_EVENT);
    702     }
    703 
    704     /**
    705      * Resumes graph execution after pausing.
    706      */
    707     public void resume() {
    708         mRunLoop.pushEvent(RESUME_EVENT);
    709     }
    710 
    711     /**
    712      * Stops graph execution.
    713      */
    714     public void stop() {
    715         mRunLoop.pushEvent(STOP_EVENT);
    716     }
    717 
    718     /**
    719      * Returns whether the graph is currently being executed. A graph is considered to be running,
    720      * even if it is paused or in the process of being stopped.
    721      *
    722      * @return true, if the graph is currently being executed.
    723      */
    724     public boolean isRunning() {
    725         return !mRunLoop.checkState(State.STOPPED);
    726     }
    727 
    728     /**
    729      * Returns whether the graph is currently paused.
    730      *
    731      * @return true, if the graph is currently paused.
    732      */
    733     public boolean isPaused() {
    734         return mRunLoop.checkState(State.PAUSED);
    735     }
    736 
    737     /**
    738      * Returns whether the graph is currently stopped.
    739      *
    740      * @return true, if the graph is currently stopped.
    741      */
    742     public boolean isStopped() {
    743         return mRunLoop.checkState(State.STOPPED);
    744     }
    745 
    746     /**
    747      * Sets the filter scheduling strategy. This method can not be called when the GraphRunner is
    748      * running.
    749      *
    750      * @param strategy a constant specifying which scheduler strategy to use.
    751      * @throws RuntimeException if the GraphRunner is running.
    752      * @throws IllegalArgumentException if invalid strategy is specified.
    753      * @see #getSchedulerStrategy()
    754      */
    755     public void setSchedulerStrategy(int strategy) {
    756         if (isRunning()) {
    757             throw new RuntimeException(
    758                     "Attempting to change scheduling strategy on running " + "GraphRunner!");
    759         }
    760         createScheduler(strategy);
    761     }
    762 
    763     /**
    764      * Returns the current scheduling strategy.
    765      *
    766      * @return the scheduling strategy used by this GraphRunner.
    767      * @see #setSchedulerStrategy(int)
    768      */
    769     public int getSchedulerStrategy() {
    770         return mScheduler.getStrategy();
    771     }
    772 
    773     /**
    774      * Set whether or not the runner is verbose. When set to true, the runner will output individual
    775      * scheduling steps that may help identify and debug problems in the graph structure. The
    776      * default is false.
    777      *
    778      * @param isVerbose true, if the GraphRunner should log scheduling details.
    779      * @see #isVerbose()
    780      */
    781     public void setIsVerbose(boolean isVerbose) {
    782         synchronized (mParams) {
    783             mParams.isVerbose = isVerbose;
    784         }
    785     }
    786 
    787     /**
    788      * Returns whether the GraphRunner is verbose.
    789      *
    790      * @return true, if the GraphRunner logs scheduling details.
    791      * @see #setIsVerbose(boolean)
    792      */
    793     public boolean isVerbose() {
    794         synchronized (mParams) {
    795             return mParams.isVerbose;
    796         }
    797     }
    798 
    799     /**
    800      * Returns whether Filters of this GraphRunner can use OpenGL.
    801      *
    802      * Filters may use OpenGL if the MffContext supports OpenGL and the GraphRunner allows it.
    803      *
    804      * @return true, if Filters are allowed to use OpenGL.
    805      */
    806     public boolean isOpenGLSupported() {
    807         return mRunLoop.isOpenGLAllowed() && mContext.isOpenGLSupported();
    808     }
    809 
    810     /**
    811      * Enable flushing all frames from the graph when running completes.
    812      *
    813      * If this is set to false, then frames may remain in the pipeline even after running completes.
    814      * The default value is true.
    815      *
    816      * @param flush true, if the GraphRunner should flush the graph when running completes.
    817      * @see #flushOnClose()
    818      */
    819     public void setFlushOnClose(boolean flush) {
    820         synchronized (mParams) {
    821             mParams.flushOnClose = flush;
    822         }
    823     }
    824 
    825     /**
    826      * Returns whether the GraphRunner flushes frames when running completes.
    827      *
    828      * @return true, if the GraphRunner flushes frames when running completes.
    829      * @see #setFlushOnClose(boolean)
    830      */
    831     public boolean flushOnClose() {
    832         synchronized (mParams) {
    833             return mParams.flushOnClose;
    834         }
    835     }
    836 
    837     /**
    838      * Sets the listener for receiving runtime events. A GraphRunner.Listener instance can be used
    839      * to determine when certain events occur during graph execution (and react on them). See the
    840      * {@link GraphRunner.Listener} class for details.
    841      *
    842      * @param listener the GraphRunner.Listener instance to set.
    843      * @see #getListener()
    844      */
    845     public void setListener(Listener listener) {
    846         synchronized (mParams) {
    847             mParams.listener = listener;
    848         }
    849     }
    850 
    851     /**
    852      * Returns the currently assigned GraphRunner.Listener.
    853      *
    854      * @return the currently assigned GraphRunner.Listener instance.
    855      * @see #setListener(Listener)
    856      */
    857     public Listener getListener() {
    858         synchronized (mParams) {
    859             return mParams.listener;
    860         }
    861     }
    862 
    863     /**
    864      * Returns the FrameManager that manages the runner's frames.
    865      *
    866      * @return the FrameManager instance that manages the runner's frames.
    867      */
    868     public FrameManager getFrameManager() {
    869         return mFrameManager;
    870     }
    871 
    872     /**
    873      * Tear down a GraphRunner and all its resources.
    874      * <p>
    875      * You must make sure that before calling this, no more graphs are attached to this runner.
    876      * Typically, graphs are removed from runners when they are torn down.
    877      *
    878      * @throws IllegalStateException if there are still graphs attached to this runner.
    879      */
    880     public void tearDown() {
    881         synchronized (mGraphs) {
    882             if (!mGraphs.isEmpty()) {
    883                 throw new IllegalStateException("Attempting to tear down runner with "
    884                         + mGraphs.size() + " graphs still attached!");
    885             }
    886         }
    887         mRunLoop.pushEvent(KILL_EVENT);
    888         // Wait for thread to complete, so that everything is torn down by the time we return.
    889         try {
    890             mRunThread.join();
    891         } catch (InterruptedException e) {
    892             Log.e("GraphRunner", "Error waiting for runner thread to finish!");
    893         }
    894     }
    895 
    896     /**
    897      * Release all frames managed by this runner.
    898      * <p>
    899      * Note, that you must make sure no graphs are attached to this runner before calling this
    900      * method, as otherwise Filters in the graph may reference frames that are now released.
    901      *
    902      * TODO: Eventually, this method should be removed. Instead we should have better analysis
    903      * that catches leaking frames from filters.
    904      *
    905      * @throws IllegalStateException if there are still graphs attached to this runner.
    906      */
    907     public void releaseFrames() {
    908         synchronized (mGraphs) {
    909             if (!mGraphs.isEmpty()) {
    910                 throw new IllegalStateException("Attempting to release frames with "
    911                         + mGraphs.size() + " graphs still attached!");
    912             }
    913         }
    914         mRunLoop.pushEvent(RELEASE_FRAMES_EVENT);
    915     }
    916 
    917     // Core internal methods ///////////////////////////////////////////////////////////////////////
    918     void attachGraph(FilterGraph graph) {
    919         synchronized (mGraphs) {
    920             mGraphs.add(graph);
    921         }
    922     }
    923 
    924     void signalWakeUp() {
    925         mRunLoop.pushWakeEvent(STEP_EVENT);
    926     }
    927 
    928     void begin() {
    929         mRunLoop.pushEvent(BEGIN_EVENT);
    930     }
    931 
    932     /** Like pause(), but closes all filters. Can be resumed using restart(). */
    933     void halt() {
    934         mRunLoop.pushEvent(HALT_EVENT);
    935     }
    936 
    937     /** Resumes a previously halted runner, and restores it to its non-halted state. */
    938     void restart() {
    939         mRunLoop.pushEvent(RESTART_EVENT);
    940     }
    941 
    942     /**
    943      * Tears down the specified graph.
    944      *
    945      * The graph must be attached to this runner.
    946      */
    947     void tearDownGraph(FilterGraph graph) {
    948         if (graph.getRunner() != this) {
    949             throw new IllegalArgumentException("Attempting to tear down graph with foreign "
    950                     + "GraphRunner!");
    951         }
    952         mRunLoop.pushEvent(Event.TEARDOWN, graph);
    953         synchronized (mGraphs) {
    954             mGraphs.remove(graph);
    955         }
    956     }
    957 
    958     /**
    959      * Remove all frames that are waiting to be processed.
    960      *
    961      * Removes and releases frames that are waiting in the graph connections of the currently
    962      * halted graphs, i.e. frames that are waiting to be processed. This does not include frames
    963      * that may be held or cached by filters themselves.
    964      *
    965      * TODO: With the new sub-graph architecture, this can now be simplified and made public.
    966      * It can then no longer rely on opened graphs, and instead flush a graph and all its
    967      * sub-graphs.
    968      */
    969     void flushFrames() {
    970         mRunLoop.pushEvent(FLUSH_EVENT);
    971     }
    972 
    973     // Private methods /////////////////////////////////////////////////////////////////////////////
    974     private void init(Config config) {
    975         mFrameManager = new FrameManager(this, FrameManager.FRAME_CACHE_LRU);
    976         createScheduler(STRATEGY_LRU);
    977         mRunLoop = new GraphRunLoop(config.allowOpenGL);
    978         mRunThread = new Thread(mRunLoop);
    979         mRunThread.setPriority(config.threadPriority);
    980         mRunThread.start();
    981         mContext.addRunner(this);
    982     }
    983 
    984     private void createScheduler(int strategy) {
    985         switch (strategy) {
    986             case STRATEGY_LRU:
    987                 mScheduler = new LruScheduler();
    988                 break;
    989             case STRATEGY_LFU:
    990                 mScheduler = new LfuScheduler();
    991                 break;
    992             case STRATEGY_ONESHOT:
    993                 mScheduler = new OneShotScheduler();
    994                 break;
    995             default:
    996                 throw new IllegalArgumentException(
    997                         "Unknown schedule-strategy constant " + strategy + "!");
    998         }
    999     }
   1000 
   1001     // Called within the runner's thread
   1002     private void onRunnerStopped(final Exception exception, final boolean closed) {
   1003         mRunningGraph = null;
   1004         synchronized (mParams) {
   1005             if (mParams.listener != null) {
   1006                 getContext().postRunnable(new Runnable() {
   1007                     @Override
   1008                     public void run() {
   1009                         if (exception == null) {
   1010                             mParams.listener.onGraphRunnerStopped(GraphRunner.this);
   1011                         } else {
   1012                             mParams.listener.onGraphRunnerError(exception, closed);
   1013                         }
   1014                     }
   1015                 });
   1016             } else if (exception != null) {
   1017                 Log.e("GraphRunner",
   1018                         "Uncaught exception during graph execution! Stack Trace: ");
   1019                 exception.printStackTrace();
   1020             }
   1021         }
   1022     }
   1023 }
   1024