| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| |
| package android.filterfw.core; |
| |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.Stack; |
| |
| import android.filterfw.core.FilterContext; |
| import android.filterfw.core.KeyValueMap; |
| import android.filterpacks.base.FrameBranch; |
| import android.filterpacks.base.NullFilter; |
| |
| import android.util.Log; |
| |
| /** |
| * @hide |
| */ |
| public class FilterGraph { |
| |
| private HashSet<Filter> mFilters = new HashSet<Filter>(); |
| private HashMap<String, Filter> mNameMap = new HashMap<String, Filter>(); |
| private HashMap<OutputPort, LinkedList<InputPort>> mPreconnections = new |
| HashMap<OutputPort, LinkedList<InputPort>>(); |
| |
| public static final int AUTOBRANCH_OFF = 0; |
| public static final int AUTOBRANCH_SYNCED = 1; |
| public static final int AUTOBRANCH_UNSYNCED = 2; |
| |
| public static final int TYPECHECK_OFF = 0; |
| public static final int TYPECHECK_DYNAMIC = 1; |
| public static final int TYPECHECK_STRICT = 2; |
| |
| private boolean mIsReady = false; |
| private int mAutoBranchMode = AUTOBRANCH_OFF; |
| private int mTypeCheckMode = TYPECHECK_STRICT; |
| private boolean mDiscardUnconnectedOutputs = false; |
| |
| private boolean mLogVerbose; |
| private String TAG = "FilterGraph"; |
| |
| public FilterGraph() { |
| mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); |
| } |
| |
| public boolean addFilter(Filter filter) { |
| if (!containsFilter(filter)) { |
| mFilters.add(filter); |
| mNameMap.put(filter.getName(), filter); |
| return true; |
| } |
| return false; |
| } |
| |
| public boolean containsFilter(Filter filter) { |
| return mFilters.contains(filter); |
| } |
| |
| public Filter getFilter(String name) { |
| return mNameMap.get(name); |
| } |
| |
| public void connect(Filter source, |
| String outputName, |
| Filter target, |
| String inputName) { |
| if (source == null || target == null) { |
| throw new IllegalArgumentException("Passing null Filter in connect()!"); |
| } else if (!containsFilter(source) || !containsFilter(target)) { |
| throw new RuntimeException("Attempting to connect filter not in graph!"); |
| } |
| |
| OutputPort outPort = source.getOutputPort(outputName); |
| InputPort inPort = target.getInputPort(inputName); |
| if (outPort == null) { |
| throw new RuntimeException("Unknown output port '" + outputName + "' on Filter " + |
| source + "!"); |
| } else if (inPort == null) { |
| throw new RuntimeException("Unknown input port '" + inputName + "' on Filter " + |
| target + "!"); |
| } |
| |
| preconnect(outPort, inPort); |
| } |
| |
| public void connect(String sourceName, |
| String outputName, |
| String targetName, |
| String inputName) { |
| Filter source = getFilter(sourceName); |
| Filter target = getFilter(targetName); |
| if (source == null) { |
| throw new RuntimeException( |
| "Attempting to connect unknown source filter '" + sourceName + "'!"); |
| } else if (target == null) { |
| throw new RuntimeException( |
| "Attempting to connect unknown target filter '" + targetName + "'!"); |
| } |
| connect(source, outputName, target, inputName); |
| } |
| |
| public Set<Filter> getFilters() { |
| return mFilters; |
| } |
| |
| public void beginProcessing() { |
| if (mLogVerbose) Log.v(TAG, "Opening all filter connections..."); |
| for (Filter filter : mFilters) { |
| filter.openOutputs(); |
| } |
| mIsReady = true; |
| } |
| |
| public void flushFrames() { |
| for (Filter filter : mFilters) { |
| filter.clearOutputs(); |
| } |
| } |
| |
| public void closeFilters(FilterContext context) { |
| if (mLogVerbose) Log.v(TAG, "Closing all filters..."); |
| for (Filter filter : mFilters) { |
| filter.performClose(context); |
| } |
| mIsReady = false; |
| } |
| |
| public boolean isReady() { |
| return mIsReady; |
| } |
| |
| public void setAutoBranchMode(int autoBranchMode) { |
| mAutoBranchMode = autoBranchMode; |
| } |
| |
| public void setDiscardUnconnectedOutputs(boolean discard) { |
| mDiscardUnconnectedOutputs = discard; |
| } |
| |
| public void setTypeCheckMode(int typeCheckMode) { |
| mTypeCheckMode = typeCheckMode; |
| } |
| |
| public void tearDown(FilterContext context) { |
| if (!mFilters.isEmpty()) { |
| flushFrames(); |
| for (Filter filter : mFilters) { |
| filter.performTearDown(context); |
| } |
| mFilters.clear(); |
| mNameMap.clear(); |
| mIsReady = false; |
| } |
| } |
| |
| private boolean readyForProcessing(Filter filter, Set<Filter> processed) { |
| // Check if this has been already processed |
| if (processed.contains(filter)) { |
| return false; |
| } |
| |
| // Check if all dependencies have been processed |
| for (InputPort port : filter.getInputPorts()) { |
| Filter dependency = port.getSourceFilter(); |
| if (dependency != null && !processed.contains(dependency)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private void runTypeCheck() { |
| Stack<Filter> filterStack = new Stack<Filter>(); |
| Set<Filter> processedFilters = new HashSet<Filter>(); |
| filterStack.addAll(getSourceFilters()); |
| |
| while (!filterStack.empty()) { |
| // Get current filter and mark as processed |
| Filter filter = filterStack.pop(); |
| processedFilters.add(filter); |
| |
| // Anchor output formats |
| updateOutputs(filter); |
| |
| // Perform type check |
| if (mLogVerbose) Log.v(TAG, "Running type check on " + filter + "..."); |
| runTypeCheckOn(filter); |
| |
| // Push connected filters onto stack |
| for (OutputPort port : filter.getOutputPorts()) { |
| Filter target = port.getTargetFilter(); |
| if (target != null && readyForProcessing(target, processedFilters)) { |
| filterStack.push(target); |
| } |
| } |
| } |
| |
| // Make sure all ports were setup |
| if (processedFilters.size() != getFilters().size()) { |
| throw new RuntimeException("Could not schedule all filters! Is your graph malformed?"); |
| } |
| } |
| |
| private void updateOutputs(Filter filter) { |
| for (OutputPort outputPort : filter.getOutputPorts()) { |
| InputPort inputPort = outputPort.getBasePort(); |
| if (inputPort != null) { |
| FrameFormat inputFormat = inputPort.getSourceFormat(); |
| FrameFormat outputFormat = filter.getOutputFormat(outputPort.getName(), |
| inputFormat); |
| if (outputFormat == null) { |
| throw new RuntimeException("Filter did not return an output format for " |
| + outputPort + "!"); |
| } |
| outputPort.setPortFormat(outputFormat); |
| } |
| } |
| } |
| |
| private void runTypeCheckOn(Filter filter) { |
| for (InputPort inputPort : filter.getInputPorts()) { |
| if (mLogVerbose) Log.v(TAG, "Type checking port " + inputPort); |
| FrameFormat sourceFormat = inputPort.getSourceFormat(); |
| FrameFormat targetFormat = inputPort.getPortFormat(); |
| if (sourceFormat != null && targetFormat != null) { |
| if (mLogVerbose) Log.v(TAG, "Checking " + sourceFormat + " against " + targetFormat + "."); |
| |
| boolean compatible = true; |
| switch (mTypeCheckMode) { |
| case TYPECHECK_OFF: |
| inputPort.setChecksType(false); |
| break; |
| case TYPECHECK_DYNAMIC: |
| compatible = sourceFormat.mayBeCompatibleWith(targetFormat); |
| inputPort.setChecksType(true); |
| break; |
| case TYPECHECK_STRICT: |
| compatible = sourceFormat.isCompatibleWith(targetFormat); |
| inputPort.setChecksType(false); |
| break; |
| } |
| |
| if (!compatible) { |
| throw new RuntimeException("Type mismatch: Filter " + filter + " expects a " |
| + "format of type " + targetFormat + " but got a format of type " |
| + sourceFormat + "!"); |
| } |
| } |
| } |
| } |
| |
| private void checkConnections() { |
| // TODO |
| } |
| |
| private void discardUnconnectedOutputs() { |
| // Connect unconnected ports to Null filters |
| LinkedList<Filter> addedFilters = new LinkedList<Filter>(); |
| for (Filter filter : mFilters) { |
| int id = 0; |
| for (OutputPort port : filter.getOutputPorts()) { |
| if (!port.isConnected()) { |
| if (mLogVerbose) Log.v(TAG, "Autoconnecting unconnected " + port + " to Null filter."); |
| NullFilter nullFilter = new NullFilter(filter.getName() + "ToNull" + id); |
| nullFilter.init(); |
| addedFilters.add(nullFilter); |
| port.connectTo(nullFilter.getInputPort("frame")); |
| ++id; |
| } |
| } |
| } |
| // Add all added filters to this graph |
| for (Filter filter : addedFilters) { |
| addFilter(filter); |
| } |
| } |
| |
| private void removeFilter(Filter filter) { |
| mFilters.remove(filter); |
| mNameMap.remove(filter.getName()); |
| } |
| |
| private void preconnect(OutputPort outPort, InputPort inPort) { |
| LinkedList<InputPort> targets; |
| targets = mPreconnections.get(outPort); |
| if (targets == null) { |
| targets = new LinkedList<InputPort>(); |
| mPreconnections.put(outPort, targets); |
| } |
| targets.add(inPort); |
| } |
| |
| private void connectPorts() { |
| int branchId = 1; |
| for (Entry<OutputPort, LinkedList<InputPort>> connection : mPreconnections.entrySet()) { |
| OutputPort outputPort = connection.getKey(); |
| LinkedList<InputPort> inputPorts = connection.getValue(); |
| if (inputPorts.size() == 1) { |
| outputPort.connectTo(inputPorts.get(0)); |
| } else if (mAutoBranchMode == AUTOBRANCH_OFF) { |
| throw new RuntimeException("Attempting to connect " + outputPort + " to multiple " |
| + "filter ports! Enable auto-branching to allow this."); |
| } else { |
| if (mLogVerbose) Log.v(TAG, "Creating branch for " + outputPort + "!"); |
| FrameBranch branch = null; |
| if (mAutoBranchMode == AUTOBRANCH_SYNCED) { |
| branch = new FrameBranch("branch" + branchId++); |
| } else { |
| throw new RuntimeException("TODO: Unsynced branches not implemented yet!"); |
| } |
| KeyValueMap branchParams = new KeyValueMap(); |
| branch.initWithAssignmentList("outputs", inputPorts.size()); |
| addFilter(branch); |
| outputPort.connectTo(branch.getInputPort("in")); |
| Iterator<InputPort> inputPortIter = inputPorts.iterator(); |
| for (OutputPort branchOutPort : ((Filter)branch).getOutputPorts()) { |
| branchOutPort.connectTo(inputPortIter.next()); |
| } |
| } |
| } |
| mPreconnections.clear(); |
| } |
| |
| private HashSet<Filter> getSourceFilters() { |
| HashSet<Filter> sourceFilters = new HashSet<Filter>(); |
| for (Filter filter : getFilters()) { |
| if (filter.getNumberOfConnectedInputs() == 0) { |
| if (mLogVerbose) Log.v(TAG, "Found source filter: " + filter); |
| sourceFilters.add(filter); |
| } |
| } |
| return sourceFilters; |
| } |
| |
| // Core internal methods ///////////////////////////////////////////////////////////////////////// |
| void setupFilters() { |
| if (mDiscardUnconnectedOutputs) { |
| discardUnconnectedOutputs(); |
| } |
| connectPorts(); |
| checkConnections(); |
| runTypeCheck(); |
| } |
| } |