1 /* 2 * Copyright (C) 2012 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 // This class provides functions to export a FilterGraph. 18 19 package androidx.media.filterfw; 20 21 import android.content.Context; 22 23 import java.io.FileOutputStream; 24 import java.io.OutputStreamWriter; 25 import java.util.HashMap; 26 import java.util.HashSet; 27 import java.util.Map.Entry; 28 import java.util.Set; 29 30 /** 31 * This class provides functions to export a FilterGraph as a DOT file. 32 */ 33 public class GraphExporter { 34 35 /** 36 * Exports the graph as DOT (see http://en.wikipedia.org/wiki/DOT_language). 37 * Using the exported file, the graph can be visualized e.g. with the command line tool dot. 38 * Optionally, one may /exclude/ unconnected optional ports (third parameter = false), 39 * since they can quickly clutter the visualization (and, depending on the purpose, may not 40 * be interesting). 41 * 42 * Example workflow: 43 * 1. run application on device, make sure it calls exportGraphAsDOT(...); 44 * 2. adb pull /data/data/<application name>/files/<graph filename>.gv graph.gv 45 * 3. dot -Tpng graph.gv -o graph.png 46 * 4. eog graph.png 47 */ 48 static public void exportAsDot(FilterGraph graph, String filename, 49 boolean includeUnconnectedOptionalPorts) 50 throws java.io.FileNotFoundException, java.io.IOException { 51 // Initialize, open file stream 52 Context myAppContext = graph.getContext().getApplicationContext(); 53 Filter[] filters = graph.getAllFilters(); 54 FileOutputStream fOut = myAppContext.openFileOutput(filename, Context.MODE_PRIVATE); 55 OutputStreamWriter dotFile = new OutputStreamWriter(fOut); 56 57 // Write beginning of DOT file 58 dotFile.write("digraph graphname {\n"); 59 dotFile.write(" node [shape=record];\n"); 60 61 // N.B. For specification and lots of examples of the DOT language, see 62 // http://www.graphviz.org/Documentation/dotguide.pdf 63 64 // Iterate over all filters of the graph, write corresponding DOT node elements 65 66 for(Filter filter : filters) { 67 dotFile.write(getDotName(" " + filter.getName()) + " [label=\"{"); 68 69 // Write upper part of element (i.e., input ports) 70 Set<String> inputPorts = getInputPorts(filter, includeUnconnectedOptionalPorts); 71 if(inputPorts.size() > 0) { 72 dotFile.write(" { "); 73 int counter = 0; 74 for(String p : inputPorts) { 75 dotFile.write("<" + getDotName(p) + "_IN>" + p); 76 if(++counter != inputPorts.size()) dotFile.write(" | "); 77 } 78 dotFile.write(" } | "); 79 } 80 81 // Write center part of element (i.e., element label) 82 dotFile.write(filter.getName()); 83 84 // Write lower part of element (i.e., output ports) 85 Set<String> outputPorts = getOutputPorts(filter, includeUnconnectedOptionalPorts); 86 if(outputPorts.size() > 0) { 87 dotFile.write(" | { "); 88 int counter = 0; 89 for(String p : outputPorts) { 90 dotFile.write("<" + getDotName(p) + "_OUT>" + p); 91 if(++counter != outputPorts.size()) dotFile.write(" | "); 92 } 93 dotFile.write(" } "); 94 } 95 96 dotFile.write("}\"];\n"); 97 } 98 dotFile.write("\n"); 99 100 // Iterate over all filters again to collect connections and find unconnected ports 101 102 int dummyNodeCounter = 0; 103 for(Filter filter : filters) { 104 Set<String> outputPorts = getOutputPorts(filter, includeUnconnectedOptionalPorts); 105 for(String portName : outputPorts) { 106 OutputPort source = filter.getConnectedOutputPort(portName); 107 if(source != null) { 108 // Found a connection, draw it 109 InputPort target = source.getTarget(); 110 dotFile.write(" " + 111 getDotName(source.getFilter().getName()) + ":" + 112 getDotName(source.getName()) + "_OUT -> " + 113 getDotName(target.getFilter().getName()) + ":" + 114 getDotName(target.getName()) + "_IN;\n" ); 115 } else { 116 // Found a unconnected output port, add dummy node 117 String color = filter.getSignature().getOutputPortInfo(portName).isRequired() 118 ? "red" : "blue"; // red for unconnected, required ports 119 dotFile.write(" " + 120 "dummy" + (++dummyNodeCounter) + 121 " [shape=point,label=\"\",color=" + color + "];\n" + 122 " " + getDotName(filter.getName()) + ":" + 123 getDotName(portName) + "_OUT -> " + 124 "dummy" + dummyNodeCounter + " [color=" + color + "];\n"); 125 } 126 } 127 128 Set<String> inputPorts = getInputPorts(filter, includeUnconnectedOptionalPorts); 129 for(String portName : inputPorts) { 130 InputPort target = filter.getConnectedInputPort(portName); 131 if(target != null) { 132 // Found a connection -- nothing to do, connections have been written out above 133 } else { 134 // Found a unconnected input port, add dummy node 135 String color = filter.getSignature().getInputPortInfo(portName).isRequired() 136 ? "red" : "blue"; // red for unconnected, required ports 137 dotFile.write(" " + 138 "dummy" + (++dummyNodeCounter) + 139 " [shape=point,label=\"\",color=" + color + "];\n" + 140 " dummy" + dummyNodeCounter + " -> " + 141 getDotName(filter.getName()) + ":" + 142 getDotName(portName) + "_IN [color=" + color + "];\n"); 143 } 144 } 145 } 146 147 // Write end of DOT file, close file stream 148 dotFile.write("}\n"); 149 dotFile.flush(); 150 dotFile.close(); 151 } 152 153 // Internal methods 154 155 // From element's name in XML, create DOT-allowed element name 156 static private String getDotName(String raw) { 157 return raw.replaceAll("\\.", "___"); // DOT does not allow . in element names 158 } 159 160 // Retrieve all input ports of a filter, including: 161 // unconnected ports (which can not be retrieved from the filter, only from the signature), and 162 // additional (connected) ports not listed in the signature (which is allowed by default, 163 // unless disallowOtherInputs is defined in signature). 164 // With second parameter = false, *omit* unconnected optional ports. 165 static private Set<String> getInputPorts(Filter filter, boolean includeUnconnectedOptional) { 166 // add (connected) ports from filter 167 Set<String> ports = new HashSet<String>(); 168 ports.addAll(filter.getConnectedInputPortMap().keySet()); 169 170 // add (unconnected) ports from signature 171 HashMap<String, Signature.PortInfo> signaturePorts = filter.getSignature().getInputPorts(); 172 if(signaturePorts != null){ 173 for(Entry<String, Signature.PortInfo> e : signaturePorts.entrySet()) { 174 if(includeUnconnectedOptional || e.getValue().isRequired()) { 175 ports.add(e.getKey()); 176 } 177 } 178 } 179 return ports; 180 } 181 182 // Retrieve all output ports of a filter (analogous to above function) 183 static private Set<String> getOutputPorts(Filter filter, boolean includeUnconnectedOptional) { 184 // add (connected) ports from filter 185 Set<String> ports = new HashSet<String>(); 186 ports.addAll(filter.getConnectedOutputPortMap().keySet()); 187 188 // add (unconnected) ports from signature 189 HashMap<String, Signature.PortInfo> signaturePorts = filter.getSignature().getOutputPorts(); 190 if(signaturePorts != null){ 191 for(Entry<String, Signature.PortInfo> e : signaturePorts.entrySet()) { 192 if(includeUnconnectedOptional || e.getValue().isRequired()) { 193 ports.add(e.getKey()); 194 } 195 } 196 } 197 return ports; 198 } 199 } 200