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