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 android.compat.annotation.UnsupportedAppUsage; 21 import android.filterpacks.base.FrameBranch; 22 import android.filterpacks.base.NullFilter; 23 import android.util.Log; 24 25 import java.util.HashMap; 26 import java.util.HashSet; 27 import java.util.Iterator; 28 import java.util.LinkedList; 29 import java.util.Map.Entry; 30 import java.util.Set; 31 import java.util.Stack; 32 33 /** 34 * @hide 35 */ 36 public class FilterGraph { 37 38 private HashSet<Filter> mFilters = new HashSet<Filter>(); 39 private HashMap<String, Filter> mNameMap = new HashMap<String, Filter>(); 40 private HashMap<OutputPort, LinkedList<InputPort>> mPreconnections = new 41 HashMap<OutputPort, LinkedList<InputPort>>(); 42 43 public static final int AUTOBRANCH_OFF = 0; 44 public static final int AUTOBRANCH_SYNCED = 1; 45 public static final int AUTOBRANCH_UNSYNCED = 2; 46 47 public static final int TYPECHECK_OFF = 0; 48 public static final int TYPECHECK_DYNAMIC = 1; 49 public static final int TYPECHECK_STRICT = 2; 50 51 private boolean mIsReady = false; 52 private int mAutoBranchMode = AUTOBRANCH_OFF; 53 private int mTypeCheckMode = TYPECHECK_STRICT; 54 private boolean mDiscardUnconnectedOutputs = false; 55 56 private boolean mLogVerbose; 57 private String TAG = "FilterGraph"; 58 FilterGraph()59 public FilterGraph() { 60 mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); 61 } 62 addFilter(Filter filter)63 public boolean addFilter(Filter filter) { 64 if (!containsFilter(filter)) { 65 mFilters.add(filter); 66 mNameMap.put(filter.getName(), filter); 67 return true; 68 } 69 return false; 70 } 71 containsFilter(Filter filter)72 public boolean containsFilter(Filter filter) { 73 return mFilters.contains(filter); 74 } 75 76 @UnsupportedAppUsage getFilter(String name)77 public Filter getFilter(String name) { 78 return mNameMap.get(name); 79 } 80 connect(Filter source, String outputName, Filter target, String inputName)81 public void connect(Filter source, 82 String outputName, 83 Filter target, 84 String inputName) { 85 if (source == null || target == null) { 86 throw new IllegalArgumentException("Passing null Filter in connect()!"); 87 } else if (!containsFilter(source) || !containsFilter(target)) { 88 throw new RuntimeException("Attempting to connect filter not in graph!"); 89 } 90 91 OutputPort outPort = source.getOutputPort(outputName); 92 InputPort inPort = target.getInputPort(inputName); 93 if (outPort == null) { 94 throw new RuntimeException("Unknown output port '" + outputName + "' on Filter " + 95 source + "!"); 96 } else if (inPort == null) { 97 throw new RuntimeException("Unknown input port '" + inputName + "' on Filter " + 98 target + "!"); 99 } 100 101 preconnect(outPort, inPort); 102 } 103 connect(String sourceName, String outputName, String targetName, String inputName)104 public void connect(String sourceName, 105 String outputName, 106 String targetName, 107 String inputName) { 108 Filter source = getFilter(sourceName); 109 Filter target = getFilter(targetName); 110 if (source == null) { 111 throw new RuntimeException( 112 "Attempting to connect unknown source filter '" + sourceName + "'!"); 113 } else if (target == null) { 114 throw new RuntimeException( 115 "Attempting to connect unknown target filter '" + targetName + "'!"); 116 } 117 connect(source, outputName, target, inputName); 118 } 119 getFilters()120 public Set<Filter> getFilters() { 121 return mFilters; 122 } 123 beginProcessing()124 public void beginProcessing() { 125 if (mLogVerbose) Log.v(TAG, "Opening all filter connections..."); 126 for (Filter filter : mFilters) { 127 filter.openOutputs(); 128 } 129 mIsReady = true; 130 } 131 flushFrames()132 public void flushFrames() { 133 for (Filter filter : mFilters) { 134 filter.clearOutputs(); 135 } 136 } 137 closeFilters(FilterContext context)138 public void closeFilters(FilterContext context) { 139 if (mLogVerbose) Log.v(TAG, "Closing all filters..."); 140 for (Filter filter : mFilters) { 141 filter.performClose(context); 142 } 143 mIsReady = false; 144 } 145 isReady()146 public boolean isReady() { 147 return mIsReady; 148 } 149 setAutoBranchMode(int autoBranchMode)150 public void setAutoBranchMode(int autoBranchMode) { 151 mAutoBranchMode = autoBranchMode; 152 } 153 setDiscardUnconnectedOutputs(boolean discard)154 public void setDiscardUnconnectedOutputs(boolean discard) { 155 mDiscardUnconnectedOutputs = discard; 156 } 157 setTypeCheckMode(int typeCheckMode)158 public void setTypeCheckMode(int typeCheckMode) { 159 mTypeCheckMode = typeCheckMode; 160 } 161 162 @UnsupportedAppUsage 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