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 FilterGraph()61 public FilterGraph() { 62 mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); 63 } 64 addFilter(Filter filter)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 containsFilter(Filter filter)74 public boolean containsFilter(Filter filter) { 75 return mFilters.contains(filter); 76 } 77 getFilter(String name)78 public Filter getFilter(String name) { 79 return mNameMap.get(name); 80 } 81 connect(Filter source, String outputName, Filter target, String inputName)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 connect(String sourceName, String outputName, String targetName, String inputName)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 getFilters()121 public Set<Filter> getFilters() { 122 return mFilters; 123 } 124 beginProcessing()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 flushFrames()133 public void flushFrames() { 134 for (Filter filter : mFilters) { 135 filter.clearOutputs(); 136 } 137 } 138 closeFilters(FilterContext context)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 isReady()147 public boolean isReady() { 148 return mIsReady; 149 } 150 setAutoBranchMode(int autoBranchMode)151 public void setAutoBranchMode(int autoBranchMode) { 152 mAutoBranchMode = autoBranchMode; 153 } 154 setDiscardUnconnectedOutputs(boolean discard)155 public void setDiscardUnconnectedOutputs(boolean discard) { 156 mDiscardUnconnectedOutputs = discard; 157 } 158 setTypeCheckMode(int typeCheckMode)159 public void setTypeCheckMode(int typeCheckMode) { 160 mTypeCheckMode = typeCheckMode; 161 } 162 tearDown(FilterContext context)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 readyForProcessing(Filter filter, Set<Filter> processed)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 runTypeCheck()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 updateOutputs(Filter filter)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 runTypeCheckOn(Filter filter)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 checkConnections()271 private void checkConnections() { 272 // TODO 273 } 274 discardUnconnectedOutputs()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 removeFilter(Filter filter)297 private void removeFilter(Filter filter) { 298 mFilters.remove(filter); 299 mNameMap.remove(filter.getName()); 300 } 301 preconnect(OutputPort outPort, InputPort inPort)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 connectPorts()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 getSourceFilters()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 ///////////////////////////////////////////////////////////////////////// setupFilters()355 void setupFilters() { 356 if (mDiscardUnconnectedOutputs) { 357 discardUnconnectedOutputs(); 358 } 359 connectPorts(); 360 checkConnections(); 361 runTypeCheck(); 362 } 363 } 364