blob: 001396527267fed662be687cde83d21f42539c91 [file] [log] [blame]
/*
* Copyright (C) 2012 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.
*/
// This class provides functions to export a FilterGraph.
package androidx.media.filterfw;
import android.content.Context;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
/**
* This class provides functions to export a FilterGraph as a DOT file.
*/
public class GraphExporter {
/**
* Exports the graph as DOT (see http://en.wikipedia.org/wiki/DOT_language).
* Using the exported file, the graph can be visualized e.g. with the command line tool dot.
* Optionally, one may /exclude/ unconnected optional ports (third parameter = false),
* since they can quickly clutter the visualization (and, depending on the purpose, may not
* be interesting).
*
* Example workflow:
* 1. run application on device, make sure it calls exportGraphAsDOT(...);
* 2. adb pull /data/data/<application name>/files/<graph filename>.gv graph.gv
* 3. dot -Tpng graph.gv -o graph.png
* 4. eog graph.png
*/
static public void exportAsDot(FilterGraph graph, String filename,
boolean includeUnconnectedOptionalPorts)
throws java.io.FileNotFoundException, java.io.IOException {
// Initialize, open file stream
Context myAppContext = graph.getContext().getApplicationContext();
Filter[] filters = graph.getAllFilters();
FileOutputStream fOut = myAppContext.openFileOutput(filename, Context.MODE_PRIVATE);
OutputStreamWriter dotFile = new OutputStreamWriter(fOut);
// Write beginning of DOT file
dotFile.write("digraph graphname {\n");
dotFile.write(" node [shape=record];\n");
// N.B. For specification and lots of examples of the DOT language, see
// http://www.graphviz.org/Documentation/dotguide.pdf
// Iterate over all filters of the graph, write corresponding DOT node elements
for(Filter filter : filters) {
dotFile.write(getDotName(" " + filter.getName()) + " [label=\"{");
// Write upper part of element (i.e., input ports)
Set<String> inputPorts = getInputPorts(filter, includeUnconnectedOptionalPorts);
if(inputPorts.size() > 0) {
dotFile.write(" { ");
int counter = 0;
for(String p : inputPorts) {
dotFile.write("<" + getDotName(p) + "_IN>" + p);
if(++counter != inputPorts.size()) dotFile.write(" | ");
}
dotFile.write(" } | ");
}
// Write center part of element (i.e., element label)
dotFile.write(filter.getName());
// Write lower part of element (i.e., output ports)
Set<String> outputPorts = getOutputPorts(filter, includeUnconnectedOptionalPorts);
if(outputPorts.size() > 0) {
dotFile.write(" | { ");
int counter = 0;
for(String p : outputPorts) {
dotFile.write("<" + getDotName(p) + "_OUT>" + p);
if(++counter != outputPorts.size()) dotFile.write(" | ");
}
dotFile.write(" } ");
}
dotFile.write("}\"];\n");
}
dotFile.write("\n");
// Iterate over all filters again to collect connections and find unconnected ports
int dummyNodeCounter = 0;
for(Filter filter : filters) {
Set<String> outputPorts = getOutputPorts(filter, includeUnconnectedOptionalPorts);
for(String portName : outputPorts) {
OutputPort source = filter.getConnectedOutputPort(portName);
if(source != null) {
// Found a connection, draw it
InputPort target = source.getTarget();
dotFile.write(" " +
getDotName(source.getFilter().getName()) + ":" +
getDotName(source.getName()) + "_OUT -> " +
getDotName(target.getFilter().getName()) + ":" +
getDotName(target.getName()) + "_IN;\n" );
} else {
// Found a unconnected output port, add dummy node
String color = filter.getSignature().getOutputPortInfo(portName).isRequired()
? "red" : "blue"; // red for unconnected, required ports
dotFile.write(" " +
"dummy" + (++dummyNodeCounter) +
" [shape=point,label=\"\",color=" + color + "];\n" +
" " + getDotName(filter.getName()) + ":" +
getDotName(portName) + "_OUT -> " +
"dummy" + dummyNodeCounter + " [color=" + color + "];\n");
}
}
Set<String> inputPorts = getInputPorts(filter, includeUnconnectedOptionalPorts);
for(String portName : inputPorts) {
InputPort target = filter.getConnectedInputPort(portName);
if(target != null) {
// Found a connection -- nothing to do, connections have been written out above
} else {
// Found a unconnected input port, add dummy node
String color = filter.getSignature().getInputPortInfo(portName).isRequired()
? "red" : "blue"; // red for unconnected, required ports
dotFile.write(" " +
"dummy" + (++dummyNodeCounter) +
" [shape=point,label=\"\",color=" + color + "];\n" +
" dummy" + dummyNodeCounter + " -> " +
getDotName(filter.getName()) + ":" +
getDotName(portName) + "_IN [color=" + color + "];\n");
}
}
}
// Write end of DOT file, close file stream
dotFile.write("}\n");
dotFile.flush();
dotFile.close();
}
// Internal methods
// From element's name in XML, create DOT-allowed element name
static private String getDotName(String raw) {
return raw.replaceAll("\\.", "___"); // DOT does not allow . in element names
}
// Retrieve all input ports of a filter, including:
// unconnected ports (which can not be retrieved from the filter, only from the signature), and
// additional (connected) ports not listed in the signature (which is allowed by default,
// unless disallowOtherInputs is defined in signature).
// With second parameter = false, *omit* unconnected optional ports.
static private Set<String> getInputPorts(Filter filter, boolean includeUnconnectedOptional) {
// add (connected) ports from filter
Set<String> ports = new HashSet<String>();
ports.addAll(filter.getConnectedInputPortMap().keySet());
// add (unconnected) ports from signature
HashMap<String, Signature.PortInfo> signaturePorts = filter.getSignature().getInputPorts();
if(signaturePorts != null){
for(Entry<String, Signature.PortInfo> e : signaturePorts.entrySet()) {
if(includeUnconnectedOptional || e.getValue().isRequired()) {
ports.add(e.getKey());
}
}
}
return ports;
}
// Retrieve all output ports of a filter (analogous to above function)
static private Set<String> getOutputPorts(Filter filter, boolean includeUnconnectedOptional) {
// add (connected) ports from filter
Set<String> ports = new HashSet<String>();
ports.addAll(filter.getConnectedOutputPortMap().keySet());
// add (unconnected) ports from signature
HashMap<String, Signature.PortInfo> signaturePorts = filter.getSignature().getOutputPorts();
if(signaturePorts != null){
for(Entry<String, Signature.PortInfo> e : signaturePorts.entrySet()) {
if(includeUnconnectedOptional || e.getValue().isRequired()) {
ports.add(e.getKey());
}
}
}
return ports;
}
}