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");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.media.filterfw;
     18 
     19 import android.util.Log;
     20 import android.view.View;
     21 import androidx.media.filterpacks.base.BranchFilter;
     22 import androidx.media.filterpacks.base.FrameSlotSource;
     23 import androidx.media.filterpacks.base.FrameSlotTarget;
     24 import androidx.media.filterpacks.base.GraphInputSource;
     25 import androidx.media.filterpacks.base.GraphOutputTarget;
     26 import androidx.media.filterpacks.base.ValueTarget;
     27 import androidx.media.filterpacks.base.ValueTarget.ValueListener;
     28 import androidx.media.filterpacks.base.VariableSource;
     29 
     30 import java.util.Collection;
     31 import java.util.HashMap;
     32 import java.util.HashSet;
     33 import java.util.Map.Entry;
     34 import java.util.Set;
     35 
     36 /**
     37  * A graph of Filter nodes.
     38  *
     39  * A FilterGraph instance contains a set of Filter instances connected by their output and input
     40  * ports. Every filter belongs to exactly one graph and cannot be moved to another graph.
     41  *
     42  * FilterGraphs may contain sub-graphs that are dependent on the parent graph. These are typically
     43  * used when inserting sub-graphs into MetaFilters. When a parent graph is torn down so are its
     44  * sub-graphs. The same applies to flushing frames of a graph.
     45  */
     46 public class FilterGraph {
     47 
     48     private final static boolean DEBUG = false;
     49 
     50     /** The context that this graph lives in */
     51     private MffContext mContext;
     52 
     53     /** Map from name of filter to the filter instance */
     54     private HashMap<String, Filter> mFilterMap = new HashMap<String, Filter>();
     55 
     56     /** Allows quick access to array of all filters. */
     57     private Filter[] mAllFilters = null;
     58 
     59     /** The GraphRunner currently attached to this graph */
     60     GraphRunner mRunner;
     61 
     62     /** The set of sub-graphs of this graph */
     63     HashSet<FilterGraph> mSubGraphs = new HashSet<FilterGraph>();
     64 
     65     /** The parent graph of this graph, or null it this graph is a root graph. */
     66     private FilterGraph mParentGraph;
     67 
     68     public static class Builder {
     69 
     70         /** The context that this builder lives in */
     71         private MffContext mContext;
     72 
     73         /** Map from name of filter to the filter instance */
     74         private HashMap<String, Filter> mFilterMap = new HashMap<String, Filter>();
     75 
     76         /**
     77          * Creates a new builder for specifying a graph structure.
     78          * @param context The context the graph will live in.
     79          */
     80         public Builder(MffContext context) {
     81             mContext = context;
     82         }
     83 
     84         /**
     85          * Add a filter to the graph.
     86          *
     87          * Adds the specified filter to the set of filters of this graph. The filter must not be in
     88          * the graph already, and the filter's name must be unique within the graph.
     89          *
     90          * @param filter the filter to add to the graph.
     91          * @throws IllegalArgumentException if the filter is in the graph already, or its name is
     92          *                                  is already taken.
     93          */
     94         public void addFilter(Filter filter) {
     95             if (mFilterMap.values().contains(filter)) {
     96                 throw new IllegalArgumentException("Attempting to add filter " + filter + " that "
     97                     + "is in the graph already!");
     98             } else if (mFilterMap.containsKey(filter.getName())) {
     99                 throw new IllegalArgumentException("Graph contains filter with name '"
    100                     + filter.getName() + "' already!");
    101             } else {
    102                 mFilterMap.put(filter.getName(), filter);
    103             }
    104         }
    105 
    106         /**
    107          * Adds a variable to the graph.
    108          *
    109          * TODO: More documentation.
    110          *
    111          * @param name the name of the variable.
    112          * @param value the value of the variable or null if no value is to be set yet.
    113          * @return the VariableSource filter that holds the value of this variable.
    114          */
    115         public VariableSource addVariable(String name, Object value) {
    116             if (getFilter(name) != null) {
    117                 throw new IllegalArgumentException("Filter named '" + name + "' exists already!");
    118             }
    119             VariableSource valueSource = new VariableSource(mContext, name);
    120             addFilter(valueSource);
    121             if (value != null) {
    122                 valueSource.setValue(value);
    123             }
    124             return valueSource;
    125         }
    126 
    127         public FrameSlotSource addFrameSlotSource(String name, String slotName) {
    128             FrameSlotSource filter = new FrameSlotSource(mContext, name, slotName);
    129             addFilter(filter);
    130             return filter;
    131         }
    132 
    133         public FrameSlotTarget addFrameSlotTarget(String name, String slotName) {
    134             FrameSlotTarget filter = new FrameSlotTarget(mContext, name, slotName);
    135             addFilter(filter);
    136             return filter;
    137         }
    138 
    139         /**
    140          * Connect two filters by their ports.
    141          * The filters specified must have been previously added to the graph builder.
    142          *
    143          * @param sourceFilterName The name of the source filter.
    144          * @param sourcePort The name of the source port.
    145          * @param targetFilterName The name of the target filter.
    146          * @param targetPort The name of the target port.
    147          */
    148         public void connect(String sourceFilterName, String sourcePort,
    149                             String targetFilterName, String targetPort) {
    150             Filter sourceFilter = getFilter(sourceFilterName);
    151             Filter targetFilter = getFilter(targetFilterName);
    152             if (sourceFilter == null) {
    153                 throw new IllegalArgumentException("Unknown filter '" + sourceFilterName + "'!");
    154             } else if (targetFilter == null) {
    155                 throw new IllegalArgumentException("Unknown filter '" + targetFilterName + "'!");
    156             }
    157             connect(sourceFilter, sourcePort, targetFilter, targetPort);
    158         }
    159 
    160         /**
    161          * Connect two filters by their ports.
    162          * The filters specified must have been previously added to the graph builder.
    163          *
    164          * @param sourceFilter The source filter.
    165          * @param sourcePort The name of the source port.
    166          * @param targetFilter The target filter.
    167          * @param targetPort The name of the target port.
    168          */
    169         public void connect(Filter sourceFilter, String sourcePort,
    170                             Filter targetFilter, String targetPort) {
    171             sourceFilter.connect(sourcePort, targetFilter, targetPort);
    172         }
    173 
    174         /**
    175          * Returns the filter with the specified name.
    176          *
    177          * @return the filter with the specified name, or null if no such filter exists.
    178          */
    179         public Filter getFilter(String name) {
    180             return mFilterMap.get(name);
    181         }
    182 
    183         /**
    184          * Builds the graph and checks signatures.
    185          *
    186          * @return The new graph instance.
    187          */
    188         public FilterGraph build() {
    189             checkSignatures();
    190             return buildWithParent(null);
    191         }
    192 
    193         /**
    194          * Builds the sub-graph and checks signatures.
    195          *
    196          * @param parentGraph the parent graph of the built sub-graph.
    197          * @return The new graph instance.
    198          */
    199         public FilterGraph buildSubGraph(FilterGraph parentGraph) {
    200             if (parentGraph == null) {
    201                 throw new NullPointerException("Parent graph must be non-null!");
    202             }
    203             checkSignatures();
    204             return buildWithParent(parentGraph);
    205         }
    206 
    207         VariableSource assignValueToFilterInput(Object value, String filterName, String inputName) {
    208             // Get filter to connect to
    209             Filter filter = getFilter(filterName);
    210             if (filter == null) {
    211                 throw new IllegalArgumentException("Unknown filter '" + filterName + "'!");
    212             }
    213 
    214             // Construct a name for our value source and make sure it does not exist already
    215             String valueSourceName = filterName + "." + inputName;
    216             if (getFilter(valueSourceName) != null) {
    217                 throw new IllegalArgumentException("VariableSource for '" + filterName + "' and "
    218                     + "input '" + inputName + "' exists already!");
    219             }
    220 
    221             // Create new VariableSource and connect it to the target filter and port
    222             VariableSource valueSource = new VariableSource(mContext, valueSourceName);
    223             addFilter(valueSource);
    224             try {
    225                 ((Filter)valueSource).connect("value", filter, inputName);
    226             } catch (RuntimeException e) {
    227                 throw new RuntimeException("Could not connect VariableSource to input '" + inputName
    228                     + "' of filter '" + filterName + "'!", e);
    229             }
    230 
    231             // Assign the value to the VariableSource
    232             if (value != null) {
    233                 valueSource.setValue(value);
    234             }
    235 
    236             return valueSource;
    237         }
    238 
    239         VariableSource assignVariableToFilterInput(String varName,
    240                                                    String filterName,
    241                                                    String inputName) {
    242             // Get filter to connect to
    243             Filter filter = getFilter(filterName);
    244             if (filter == null) {
    245                 throw new IllegalArgumentException("Unknown filter '" + filterName + "'!");
    246             }
    247 
    248             // Get variable
    249             Filter variable = getFilter(varName);
    250             if (variable == null || !(variable instanceof VariableSource)) {
    251                 throw new IllegalArgumentException("Unknown variable '" + varName + "'!");
    252             }
    253 
    254             // Connect variable (and possibly branch) variable to filter
    255             try {
    256                 connectAndBranch(variable, "value", filter, inputName);
    257             } catch (RuntimeException e) {
    258                 throw new RuntimeException("Could not connect VariableSource to input '" + inputName
    259                     + "' of filter '" + filterName + "'!", e);
    260             }
    261 
    262             return (VariableSource)variable;
    263         }
    264 
    265         /**
    266          * Builds the graph without checking signatures.
    267          * If parent is non-null, build a sub-graph of the specified parent.
    268          *
    269          * @return The new graph instance.
    270          */
    271         private FilterGraph buildWithParent(FilterGraph parent) {
    272             FilterGraph graph = new FilterGraph(mContext, parent);
    273             graph.mFilterMap = mFilterMap;
    274             graph.mAllFilters = mFilterMap.values().toArray(new Filter[0]);
    275             for (Entry<String, Filter> filterEntry : mFilterMap.entrySet()) {
    276                 filterEntry.getValue().insertIntoFilterGraph(graph);
    277             }
    278             return graph;
    279         }
    280 
    281         private void checkSignatures() {
    282             checkSignaturesForFilters(mFilterMap.values());
    283         }
    284 
    285         // TODO: Currently this always branches even if the connection is a 1:1 connection. Later
    286         // we may optimize to pass through directly in the 1:1 case (may require disconnecting
    287         // ports).
    288         private void connectAndBranch(Filter sourceFilter,
    289                                       String sourcePort,
    290                                       Filter targetFilter,
    291                                       String targetPort) {
    292             String branchName = "__" + sourceFilter.getName() + "_" + sourcePort + "Branch";
    293             Filter branch = getFilter(branchName);
    294             if (branch == null) {
    295                 branch = new BranchFilter(mContext, branchName, false);
    296                 addFilter(branch);
    297                 sourceFilter.connect(sourcePort, branch, "input");
    298             }
    299             String portName = "to" + targetFilter.getName() + "_" + targetPort;
    300             branch.connect(portName, targetFilter, targetPort);
    301         }
    302 
    303     }
    304 
    305     /**
    306      * Attach the graph and its subgraphs to a custom GraphRunner.
    307      *
    308      * Call this if you want the graph to be executed by a specific GraphRunner. You must call
    309      * this before any other runner is set. Note that calls to {@code getRunner()} and
    310      * {@code run()} auto-create a GraphRunner.
    311      *
    312      * @param runner The GraphRunner instance that should execute this graph.
    313      * @see #getRunner()
    314      * @see #run()
    315      */
    316     public void attachToRunner(GraphRunner runner) {
    317         if (mRunner == null) {
    318             for (FilterGraph subGraph : mSubGraphs) {
    319                 subGraph.attachToRunner(runner);
    320             }
    321             runner.attachGraph(this);
    322             mRunner = runner;
    323         } else if (mRunner != runner) {
    324             throw new RuntimeException("Cannot attach FilterGraph to GraphRunner that is already "
    325                 + "attached to another GraphRunner!");
    326         }
    327     }
    328 
    329     /**
    330      * Forcibly tear down a filter graph.
    331      *
    332      * Call this to release any resources associated with the filter graph, its filters and any of
    333      * its sub-graphs. This method must not be called if the graph (or any sub-graph) is running.
    334      *
    335      * You may no longer access this graph instance or any of its subgraphs after calling this
    336      * method.
    337      *
    338      * Tearing down of sub-graphs is not supported. You must tear down the root graph, which will
    339      * tear down all of its sub-graphs.
    340      *
    341      * @throws IllegalStateException if the graph is still running.
    342      * @throws RuntimeException if you attempt to tear down a sub-graph.
    343      */
    344     public void tearDown() {
    345         assertNotRunning();
    346         if (mParentGraph != null) {
    347             throw new RuntimeException("Attempting to tear down sub-graph!");
    348         }
    349         if (mRunner != null) {
    350             mRunner.tearDownGraph(this);
    351         }
    352         for (FilterGraph subGraph : mSubGraphs) {
    353             subGraph.mParentGraph = null;
    354             subGraph.tearDown();
    355         }
    356         mSubGraphs.clear();
    357     }
    358 
    359     /**
    360      * Returns the context of the graph.
    361      *
    362      * @return the MffContext instance that this graph is bound to.
    363      */
    364     public MffContext getContext() {
    365         return mContext;
    366     }
    367 
    368     /**
    369      * Returns the filter with the specified name.
    370      *
    371      * @return the filter with the specified name, or null if no such filter exists.
    372      */
    373     public Filter getFilter(String name) {
    374         return mFilterMap.get(name);
    375     }
    376 
    377     /**
    378      * Returns the VariableSource for the specified variable.
    379      *
    380      * TODO: More documentation.
    381      * TODO: More specialized error handling.
    382      *
    383      * @param name The name of the VariableSource.
    384      * @return The VariableSource filter instance with the specified name.
    385      */
    386     public VariableSource getVariable(String name) {
    387         Filter result = mFilterMap.get(name);
    388         if (result != null && result instanceof VariableSource) {
    389             return (VariableSource)result;
    390         } else {
    391             throw new IllegalArgumentException("Unknown variable '" + name + "' specified!");
    392         }
    393     }
    394 
    395     /**
    396      * Returns the GraphOutputTarget with the specified name.
    397      *
    398      * @param name The name of the target.
    399      * @return The GraphOutputTarget instance with the specified name.
    400      */
    401     public GraphOutputTarget getGraphOutput(String name) {
    402         Filter result = mFilterMap.get(name);
    403         if (result != null && result instanceof GraphOutputTarget) {
    404             return (GraphOutputTarget)result;
    405         } else {
    406             throw new IllegalArgumentException("Unknown target '" + name + "' specified!");
    407         }
    408     }
    409 
    410     /**
    411      * Returns the GraphInputSource with the specified name.
    412      *
    413      * @param name The name of the source.
    414      * @return The GraphInputSource instance with the specified name.
    415      */
    416     public GraphInputSource getGraphInput(String name) {
    417         Filter result = mFilterMap.get(name);
    418         if (result != null && result instanceof GraphInputSource) {
    419             return (GraphInputSource)result;
    420         } else {
    421             throw new IllegalArgumentException("Unknown source '" + name + "' specified!");
    422         }
    423     }
    424 
    425     /**
    426      * Binds a filter to a view.
    427      *
    428      * ViewFilter instances support visualizing their data to a view. See the specific filter
    429      * documentation for details. Views may be bound only if the graph is not running.
    430      *
    431      * @param filterName the name of the filter to bind.
    432      * @param view the view to bind to.
    433      * @throws IllegalStateException if the filter is in an illegal state.
    434      * @throws IllegalArgumentException if no such view-filter exists.
    435      */
    436     public void bindFilterToView(String filterName, View view) {
    437         Filter filter = mFilterMap.get(filterName);
    438         if (filter != null && filter instanceof ViewFilter) {
    439             ((ViewFilter)filter).bindToView(view);
    440         } else {
    441             throw new IllegalArgumentException("Unknown view filter '" + filterName + "'!");
    442         }
    443     }
    444 
    445     /**
    446      * TODO: Documentation.
    447      */
    448     public void bindValueTarget(String filterName, ValueListener listener, boolean onCallerThread) {
    449         Filter filter = mFilterMap.get(filterName);
    450         if (filter != null && filter instanceof ValueTarget) {
    451             ((ValueTarget)filter).setListener(listener, onCallerThread);
    452         } else {
    453             throw new IllegalArgumentException("Unknown ValueTarget filter '" + filterName + "'!");
    454         }
    455     }
    456 
    457     // Running Graphs //////////////////////////////////////////////////////////////////////////////
    458     /**
    459      * Convenience method to run the graph.
    460      *
    461      * Creates a new runner for this graph in the specified mode and executes it. Returns the
    462      * runner to allow control of execution.
    463      *
    464      * @throws IllegalStateException if the graph is already running.
    465      * @return the GraphRunner instance that was used for execution.
    466      */
    467     public GraphRunner run() {
    468         GraphRunner runner = getRunner();
    469         runner.setIsVerbose(false);
    470         runner.start(this);
    471         return runner;
    472     }
    473 
    474     /**
    475      * Returns the GraphRunner for this graph.
    476      *
    477      * Every FilterGraph instance has a GraphRunner instance associated with it for executing the
    478      * graph.
    479      *
    480      * @return the GraphRunner instance for this graph.
    481      */
    482     public GraphRunner getRunner() {
    483         if (mRunner == null) {
    484             GraphRunner runner = new GraphRunner(mContext);
    485             attachToRunner(runner);
    486         }
    487         return mRunner;
    488     }
    489 
    490     /**
    491      * Returns whether the graph is currently running.
    492      *
    493      * @return true if the graph is currently running.
    494      */
    495     public boolean isRunning() {
    496         return mRunner != null && mRunner.isRunning();
    497     }
    498 
    499     /**
    500      * Check each filter's signatures if all requirements are fulfilled.
    501      *
    502      * This will throw a RuntimeException if any unfulfilled requirements are found.
    503      * Note that FilterGraph.Builder also has a function checkSignatures(), which allows
    504      * to do the same /before/ the FilterGraph is built.
    505      */
    506     public void checkSignatures() {
    507         checkSignaturesForFilters(mFilterMap.values());
    508     }
    509 
    510     // MFF Internal Methods ////////////////////////////////////////////////////////////////////////
    511     Filter[] getAllFilters() {
    512         return mAllFilters;
    513     }
    514 
    515     static void checkSignaturesForFilters(Collection<Filter> filters) {
    516         for (Filter filter : filters) {
    517             if (DEBUG) {
    518                 Log.d("FilterGraph", "Checking filter " + filter.getName() + "...");
    519             }
    520             Signature signature = filter.getSignature();
    521             signature.checkInputPortsConform(filter);
    522             signature.checkOutputPortsConform(filter);
    523         }
    524     }
    525 
    526     /**
    527      * Wipes the filter references in this graph, so that they may be collected.
    528      *
    529      * This must be called only after a tearDown as this will make the FilterGraph invalid.
    530      */
    531     void wipe() {
    532         mAllFilters = null;
    533         mFilterMap = null;
    534     }
    535 
    536     void flushFrames() {
    537         for (Filter filter : mFilterMap.values()) {
    538             for (InputPort inputPort : filter.getConnectedInputPorts()) {
    539                 inputPort.clear();
    540             }
    541             for (OutputPort outputPort : filter.getConnectedOutputPorts()) {
    542                 outputPort.clear();
    543             }
    544         }
    545     }
    546 
    547     Set<FilterGraph> getSubGraphs() {
    548         return mSubGraphs;
    549     }
    550 
    551     // Internal Methods ////////////////////////////////////////////////////////////////////////////
    552     private FilterGraph(MffContext context, FilterGraph parentGraph) {
    553         mContext = context;
    554         mContext.addGraph(this);
    555         if (parentGraph != null) {
    556             mParentGraph = parentGraph;
    557             mParentGraph.mSubGraphs.add(this);
    558         }
    559     }
    560 
    561     private void assertNotRunning() {
    562         if (isRunning()) {
    563             throw new IllegalStateException("Attempting to modify running graph!");
    564         }
    565     }
    566 }
    567 
    568