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