• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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