Home | History | Annotate | Download | only in filterfw
      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