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 
setState(int newState)62         public synchronized void setState(int newState) {
63             mCurrent = newState;
64         }
65 
check(int state)66         public synchronized boolean check(int state) {
67             return ((mCurrent & state) == state);
68         }
69 
addState(int state)70         public synchronized boolean addState(int state) {
71             if ((mCurrent & state) != state) {
72                 mCurrent |= state;
73                 return true;
74             }
75             return false;
76         }
77 
removeState(int state)78         public synchronized boolean removeState(int state) {
79             boolean result = (mCurrent & state) == state;
80             mCurrent &= (~state);
81             return result;
82         }
83 
current()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 
Event(int code, Object object)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 
loop()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 
GraphRunLoop(boolean allowOpenGL)186         public GraphRunLoop(boolean allowOpenGL) {
187             mAllowOpenGL = allowOpenGL;
188         }
189 
190         @Override
run()191         public void run() {
192             onInit();
193             loop();
194             onDestroy();
195         }
196 
enterSubGraph(FilterGraph graph, SubListener listener)197         public void enterSubGraph(FilterGraph graph, SubListener listener) {
198             if (mState.check(State.RUNNING)) {
199                 onOpenGraph(graph);
200                 mSubListeners.push(listener);
201             }
202         }
203 
pushWakeEvent(Event event)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 
pushEvent(Event event)212         public void pushEvent(Event event) {
213             mEventQueue.offer(event);
214         }
215 
pushEvent(int eventId, Object object)216         public void pushEvent(int eventId, Object object) {
217             mEventQueue.offer(new Event(eventId, object));
218         }
219 
checkState(int state)220         public boolean checkState(int state) {
221             return mState.check(state);
222         }
223 
getStopCondition()224         public ConditionVariable getStopCondition() {
225             return mStopCondition;
226         }
227 
isOpenGLAllowed()228         public boolean isOpenGLAllowed() {
229             // Does not need synchronization as mAllowOpenGL flag is final.
230             return mAllowOpenGL;
231         }
232 
nextEvent()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 
onPause()243         private void onPause() {
244             mState.addState(State.PAUSED);
245         }
246 
onResume()247         private void onResume() {
248             if (mState.removeState(State.PAUSED)) {
249                 if (mState.current() == State.RUNNING) {
250                     pushEvent(STEP_EVENT);
251                 }
252             }
253         }
254 
onHalt()255         private void onHalt() {
256             if (mState.addState(State.HALTED) && mState.check(State.RUNNING)) {
257                 closeAllFilters();
258             }
259         }
260 
onRestart()261         private void onRestart() {
262             if (mState.removeState(State.HALTED)) {
263                 if (mState.current() == State.RUNNING) {
264                     pushEvent(STEP_EVENT);
265                 }
266             }
267         }
268 
onDestroy()269         private void onDestroy() {
270             mFrameManager.destroyBackings();
271             if (mRenderTarget != null) {
272                 mRenderTarget.release();
273                 mRenderTarget = null;
274             }
275         }
276 
onReleaseFrames()277         private void onReleaseFrames() {
278             mFrameManager.destroyBackings();
279         }
280 
onInit()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 
onPrepare(FilterGraph graph)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 
onOpenGraph(FilterGraph graph)297         private void onOpenGraph(FilterGraph graph) {
298             loadFilters(graph);
299             mOpenedGraphs.add(graph);
300             mScheduler.prepare(currentFilters());
301             pushEvent(BEGIN_EVENT);
302         }
303 
onBegin()304         private void onBegin() {
305             if (mState.current() == State.PREPARING) {
306                 mState.setState(State.RUNNING);
307                 pushEvent(STEP_EVENT);
308             }
309         }
310 
onStarve()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 
onStop()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 
cleanUp()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 
onStep()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 
onFlush()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 
onTearDown(FilterGraph graph)386         private void onTearDown(FilterGraph graph) {
387             for (Filter filter : graph.getAllFilters()) {
388                 filter.performTearDown();
389             }
390             graph.wipe();
391         }
392 
loadFilters(FilterGraph graph)393         private void loadFilters(FilterGraph graph) {
394             Filter[] filters = graph.getAllFilters();
395             mFilters.push(filters);
396         }
397 
closeAllFilters()398         private void closeAllFilters() {
399             for (FilterGraph graph : mOpenedGraphs) {
400                 closeFilters(graph);
401             }
402         }
403 
closeFilters(FilterGraph graph)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 
currentFilters()417         private Filter[] currentFilters() {
418             return mFilters.peek();
419         }
420 
scheduleFilter(Filter filter)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 {
prepare(Filter[] filters)439         public void prepare(Filter[] filters);
440 
getStrategy()441         public int getStrategy();
442 
beginStep()443         public void beginStep();
444 
priorityForFilter(Filter filter)445         public long priorityForFilter(Filter filter);
446 
447     }
448 
449     private class LruScheduler implements Scheduler {
450 
451         private long mNow;
452 
453         @Override
prepare(Filter[] filters)454         public void prepare(Filter[] filters) {
455         }
456 
457         @Override
getStrategy()458         public int getStrategy() {
459             return STRATEGY_LRU;
460         }
461 
462         @Override
beginStep()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
priorityForFilter(Filter filter)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
prepare(Filter[] filters)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
getStrategy()496         public int getStrategy() {
497             return STRATEGY_LFU;
498         }
499 
500         @Override
beginStep()501         public void beginStep() {
502         }
503 
504         @Override
priorityForFilter(Filter filter)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
prepare(Filter[] filters)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
getStrategy()525         public int getStrategy() {
526             return STRATEGY_ONESHOT;
527         }
528 
529         @Override
beginStep()530         public void beginStep() {
531         }
532 
533         @Override
priorityForFilter(Filter filter)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          */
onGraphRunnerStopped(GraphRunner runner)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          */
onGraphRunnerError(Exception exception, boolean closedSuccessfully)560         public void onGraphRunnerError(Exception exception, boolean closedSuccessfully);
561     }
562 
563     public interface SubListener {
onSubGraphRunEnded(GraphRunner runner)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      */
GraphRunner(MffContext context)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      */
GraphRunner(MffContext context, Config config)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      */
current()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      */
getRunningGraph()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      */
getContext()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      */
start(FilterGraph graph)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      */
enterSubGraph(FilterGraph graph, SubListener listener)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      */
waitUntilStop()693     public void waitUntilStop() {
694         mRunLoop.getStopCondition().block();
695     }
696 
697     /**
698      * Pauses graph execution.
699      */
pause()700     public void pause() {
701         mRunLoop.pushEvent(PAUSE_EVENT);
702     }
703 
704     /**
705      * Resumes graph execution after pausing.
706      */
resume()707     public void resume() {
708         mRunLoop.pushEvent(RESUME_EVENT);
709     }
710 
711     /**
712      * Stops graph execution.
713      */
stop()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      */
isRunning()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      */
isPaused()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      */
isStopped()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      */
setSchedulerStrategy(int strategy)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      */
getSchedulerStrategy()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      */
setIsVerbose(boolean isVerbose)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      */
isVerbose()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      */
isOpenGLSupported()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      */
setFlushOnClose(boolean flush)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      */
flushOnClose()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      */
setListener(Listener listener)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      */
getListener()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      */
getFrameManager()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      */
tearDown()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      */
releaseFrames()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 ///////////////////////////////////////////////////////////////////////
attachGraph(FilterGraph graph)918     void attachGraph(FilterGraph graph) {
919         synchronized (mGraphs) {
920             mGraphs.add(graph);
921         }
922     }
923 
signalWakeUp()924     void signalWakeUp() {
925         mRunLoop.pushWakeEvent(STEP_EVENT);
926     }
927 
begin()928     void begin() {
929         mRunLoop.pushEvent(BEGIN_EVENT);
930     }
931 
932     /** Like pause(), but closes all filters. Can be resumed using restart(). */
halt()933     void halt() {
934         mRunLoop.pushEvent(HALT_EVENT);
935     }
936 
937     /** Resumes a previously halted runner, and restores it to its non-halted state. */
restart()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      */
tearDownGraph(FilterGraph graph)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      */
flushFrames()969     void flushFrames() {
970         mRunLoop.pushEvent(FLUSH_EVENT);
971     }
972 
973     // Private methods /////////////////////////////////////////////////////////////////////////////
init(Config config)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 
createScheduler(int strategy)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
onRunnerStopped(final Exception exception, final boolean closed)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