Home | History | Annotate | Download | only in core
      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 
     18 package android.filterfw.core;
     19 
     20 import java.util.HashMap;
     21 import java.util.HashSet;
     22 import java.util.Iterator;
     23 import java.util.LinkedList;
     24 import java.util.Map.Entry;
     25 import java.util.Set;
     26 import java.util.Stack;
     27 
     28 import android.filterfw.core.FilterContext;
     29 import android.filterfw.core.KeyValueMap;
     30 import android.filterpacks.base.FrameBranch;
     31 import android.filterpacks.base.NullFilter;
     32 
     33 import android.util.Log;
     34 
     35 /**
     36  * @hide
     37  */
     38 public class FilterGraph {
     39 
     40     private HashSet<Filter> mFilters = new HashSet<Filter>();
     41     private HashMap<String, Filter> mNameMap = new HashMap<String, Filter>();
     42     private HashMap<OutputPort, LinkedList<InputPort>> mPreconnections = new
     43             HashMap<OutputPort, LinkedList<InputPort>>();
     44 
     45     public static final int AUTOBRANCH_OFF      = 0;
     46     public static final int AUTOBRANCH_SYNCED   = 1;
     47     public static final int AUTOBRANCH_UNSYNCED = 2;
     48 
     49     public static final int TYPECHECK_OFF       = 0;
     50     public static final int TYPECHECK_DYNAMIC   = 1;
     51     public static final int TYPECHECK_STRICT    = 2;
     52 
     53     private boolean mIsReady = false;
     54     private int mAutoBranchMode = AUTOBRANCH_OFF;
     55     private int mTypeCheckMode = TYPECHECK_STRICT;
     56     private boolean mDiscardUnconnectedOutputs = false;
     57 
     58     private boolean mLogVerbose;
     59     private String TAG = "FilterGraph";
     60 
     61     public FilterGraph() {
     62         mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
     63     }
     64 
     65     public boolean addFilter(Filter filter) {
     66         if (!containsFilter(filter)) {
     67             mFilters.add(filter);
     68             mNameMap.put(filter.getName(), filter);
     69             return true;
     70         }
     71         return false;
     72     }
     73 
     74     public boolean containsFilter(Filter filter) {
     75         return mFilters.contains(filter);
     76     }
     77 
     78     public Filter getFilter(String name) {
     79         return mNameMap.get(name);
     80     }
     81 
     82     public void connect(Filter source,
     83                         String outputName,
     84                         Filter target,
     85                         String inputName) {
     86         if (source == null || target == null) {
     87             throw new IllegalArgumentException("Passing null Filter in connect()!");
     88         } else if (!containsFilter(source) || !containsFilter(target)) {
     89             throw new RuntimeException("Attempting to connect filter not in graph!");
     90         }
     91 
     92         OutputPort outPort = source.getOutputPort(outputName);
     93         InputPort inPort = target.getInputPort(inputName);
     94         if (outPort == null) {
     95             throw new RuntimeException("Unknown output port '" + outputName + "' on Filter " +
     96                                        source + "!");
     97         } else if (inPort == null) {
     98             throw new RuntimeException("Unknown input port '" + inputName + "' on Filter " +
     99                                        target + "!");
    100         }
    101 
    102         preconnect(outPort, inPort);
    103     }
    104 
    105     public void connect(String sourceName,
    106                         String outputName,
    107                         String targetName,
    108                         String inputName) {
    109         Filter source = getFilter(sourceName);
    110         Filter target = getFilter(targetName);
    111         if (source == null) {
    112             throw new RuntimeException(
    113                 "Attempting to connect unknown source filter '" + sourceName + "'!");
    114         } else if (target == null) {
    115             throw new RuntimeException(
    116                 "Attempting to connect unknown target filter '" + targetName + "'!");
    117         }
    118         connect(source, outputName, target, inputName);
    119     }
    120 
    121     public Set<Filter> getFilters() {
    122         return mFilters;
    123     }
    124 
    125     public void beginProcessing() {
    126         if (mLogVerbose) Log.v(TAG, "Opening all filter connections...");
    127         for (Filter filter : mFilters) {
    128             filter.openOutputs();
    129         }
    130         mIsReady = true;
    131     }
    132 
    133     public void flushFrames() {
    134         for (Filter filter : mFilters) {
    135             filter.clearOutputs();
    136         }
    137     }
    138 
    139     public void closeFilters(FilterContext context) {
    140         if (mLogVerbose) Log.v(TAG, "Closing all filters...");
    141         for (Filter filter : mFilters) {
    142             filter.performClose(context);
    143         }
    144         mIsReady = false;
    145     }
    146 
    147     public boolean isReady() {
    148         return mIsReady;
    149     }
    150 
    151     public void setAutoBranchMode(int autoBranchMode) {
    152         mAutoBranchMode = autoBranchMode;
    153     }
    154 
    155     public void setDiscardUnconnectedOutputs(boolean discard) {
    156         mDiscardUnconnectedOutputs = discard;
    157     }
    158 
    159     public void setTypeCheckMode(int typeCheckMode) {
    160         mTypeCheckMode = typeCheckMode;
    161     }
    162 
    163     public void tearDown(FilterContext context) {
    164         if (!mFilters.isEmpty()) {
    165             flushFrames();
    166             for (Filter filter : mFilters) {
    167                 filter.performTearDown(context);
    168             }
    169             mFilters.clear();
    170             mNameMap.clear();
    171             mIsReady = false;
    172         }
    173     }
    174 
    175     private boolean readyForProcessing(Filter filter, Set<Filter> processed) {
    176         // Check if this has been already processed
    177         if (processed.contains(filter)) {
    178             return false;
    179         }
    180 
    181         // Check if all dependencies have been processed
    182         for (InputPort port : filter.getInputPorts()) {
    183             Filter dependency = port.getSourceFilter();
    184             if (dependency != null && !processed.contains(dependency)) {
    185                 return false;
    186             }
    187         }
    188         return true;
    189     }
    190 
    191     private void runTypeCheck() {
    192         Stack<Filter> filterStack = new Stack<Filter>();
    193         Set<Filter> processedFilters = new HashSet<Filter>();
    194         filterStack.addAll(getSourceFilters());
    195 
    196         while (!filterStack.empty()) {
    197             // Get current filter and mark as processed
    198             Filter filter = filterStack.pop();
    199             processedFilters.add(filter);
    200 
    201             // Anchor output formats
    202             updateOutputs(filter);
    203 
    204             // Perform type check
    205             if (mLogVerbose) Log.v(TAG, "Running type check on " + filter + "...");
    206             runTypeCheckOn(filter);
    207 
    208             // Push connected filters onto stack
    209             for (OutputPort port : filter.getOutputPorts()) {
    210                 Filter target = port.getTargetFilter();
    211                 if (target != null && readyForProcessing(target, processedFilters)) {
    212                     filterStack.push(target);
    213                 }
    214             }
    215         }
    216 
    217         // Make sure all ports were setup
    218         if (processedFilters.size() != getFilters().size()) {
    219             throw new RuntimeException("Could not schedule all filters! Is your graph malformed?");
    220         }
    221     }
    222 
    223     private void updateOutputs(Filter filter) {
    224         for (OutputPort outputPort : filter.getOutputPorts()) {
    225             InputPort inputPort = outputPort.getBasePort();
    226             if (inputPort != null) {
    227                 FrameFormat inputFormat = inputPort.getSourceFormat();
    228                 FrameFormat outputFormat = filter.getOutputFormat(outputPort.getName(),
    229                                                                   inputFormat);
    230                 if (outputFormat == null) {
    231                     throw new RuntimeException("Filter did not return an output format for "
    232                         + outputPort + "!");
    233                 }
    234                 outputPort.setPortFormat(outputFormat);
    235             }
    236         }
    237     }
    238 
    239     private void runTypeCheckOn(Filter filter) {
    240         for (InputPort inputPort : filter.getInputPorts()) {
    241             if (mLogVerbose) Log.v(TAG, "Type checking port " + inputPort);
    242             FrameFormat sourceFormat = inputPort.getSourceFormat();
    243             FrameFormat targetFormat = inputPort.getPortFormat();
    244             if (sourceFormat != null && targetFormat != null) {
    245                 if (mLogVerbose) Log.v(TAG, "Checking " + sourceFormat + " against " + targetFormat + ".");
    246 
    247                 boolean compatible = true;
    248                 switch (mTypeCheckMode) {
    249                     case TYPECHECK_OFF:
    250                         inputPort.setChecksType(false);
    251                         break;
    252                     case TYPECHECK_DYNAMIC:
    253                         compatible = sourceFormat.mayBeCompatibleWith(targetFormat);
    254                         inputPort.setChecksType(true);
    255                         break;
    256                     case TYPECHECK_STRICT:
    257                         compatible = sourceFormat.isCompatibleWith(targetFormat);
    258                         inputPort.setChecksType(false);
    259                         break;
    260                 }
    261 
    262                 if (!compatible) {
    263                     throw new RuntimeException("Type mismatch: Filter " + filter + " expects a "
    264                         + "format of type " + targetFormat + " but got a format of type "
    265                         + sourceFormat + "!");
    266                 }
    267             }
    268         }
    269     }
    270 
    271     private void checkConnections() {
    272         // TODO
    273     }
    274 
    275     private void discardUnconnectedOutputs() {
    276         // Connect unconnected ports to Null filters
    277         LinkedList<Filter> addedFilters = new LinkedList<Filter>();
    278         for (Filter filter : mFilters) {
    279             int id = 0;
    280             for (OutputPort port : filter.getOutputPorts()) {
    281                 if (!port.isConnected()) {
    282                     if (mLogVerbose) Log.v(TAG, "Autoconnecting unconnected " + port + " to Null filter.");
    283                     NullFilter nullFilter = new NullFilter(filter.getName() + "ToNull" + id);
    284                     nullFilter.init();
    285                     addedFilters.add(nullFilter);
    286                     port.connectTo(nullFilter.getInputPort("frame"));
    287                     ++id;
    288                 }
    289             }
    290         }
    291         // Add all added filters to this graph
    292         for (Filter filter : addedFilters) {
    293             addFilter(filter);
    294         }
    295     }
    296 
    297     private void removeFilter(Filter filter) {
    298         mFilters.remove(filter);
    299         mNameMap.remove(filter.getName());
    300     }
    301 
    302     private void preconnect(OutputPort outPort, InputPort inPort) {
    303         LinkedList<InputPort> targets;
    304         targets = mPreconnections.get(outPort);
    305         if (targets == null) {
    306             targets = new LinkedList<InputPort>();
    307             mPreconnections.put(outPort, targets);
    308         }
    309         targets.add(inPort);
    310     }
    311 
    312     private void connectPorts() {
    313         int branchId = 1;
    314         for (Entry<OutputPort, LinkedList<InputPort>> connection : mPreconnections.entrySet()) {
    315             OutputPort outputPort = connection.getKey();
    316             LinkedList<InputPort> inputPorts = connection.getValue();
    317             if (inputPorts.size() == 1) {
    318                 outputPort.connectTo(inputPorts.get(0));
    319             } else if (mAutoBranchMode == AUTOBRANCH_OFF) {
    320                 throw new RuntimeException("Attempting to connect " + outputPort + " to multiple "
    321                                          + "filter ports! Enable auto-branching to allow this.");
    322             } else {
    323                 if (mLogVerbose) Log.v(TAG, "Creating branch for " + outputPort + "!");
    324                 FrameBranch branch = null;
    325                 if (mAutoBranchMode == AUTOBRANCH_SYNCED) {
    326                     branch = new FrameBranch("branch" + branchId++);
    327                 } else {
    328                     throw new RuntimeException("TODO: Unsynced branches not implemented yet!");
    329                 }
    330                 KeyValueMap branchParams = new KeyValueMap();
    331                 branch.initWithAssignmentList("outputs", inputPorts.size());
    332                 addFilter(branch);
    333                 outputPort.connectTo(branch.getInputPort("in"));
    334                 Iterator<InputPort> inputPortIter = inputPorts.iterator();
    335                 for (OutputPort branchOutPort : ((Filter)branch).getOutputPorts()) {
    336                     branchOutPort.connectTo(inputPortIter.next());
    337                 }
    338             }
    339         }
    340         mPreconnections.clear();
    341     }
    342 
    343     private HashSet<Filter> getSourceFilters() {
    344         HashSet<Filter> sourceFilters = new HashSet<Filter>();
    345         for (Filter filter : getFilters()) {
    346             if (filter.getNumberOfConnectedInputs() == 0) {
    347                 if (mLogVerbose) Log.v(TAG, "Found source filter: " + filter);
    348                 sourceFilters.add(filter);
    349             }
    350         }
    351         return sourceFilters;
    352     }
    353 
    354     // Core internal methods /////////////////////////////////////////////////////////////////////////
    355     void setupFilters() {
    356         if (mDiscardUnconnectedOutputs) {
    357             discardUnconnectedOutputs();
    358         }
    359         connectPorts();
    360         checkConnections();
    361         runTypeCheck();
    362     }
    363 }
    364