Home | History | Annotate | Download | only in filterfw
      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 package androidx.media.filterfw;
     18 
     19 import android.text.TextUtils;
     20 
     21 import java.io.InputStream;
     22 import java.io.IOException;
     23 import java.io.StringReader;
     24 import java.util.ArrayList;
     25 
     26 import javax.xml.parsers.ParserConfigurationException;
     27 import javax.xml.parsers.SAXParser;
     28 import javax.xml.parsers.SAXParserFactory;
     29 
     30 import org.xml.sax.Attributes;
     31 import org.xml.sax.InputSource;
     32 import org.xml.sax.SAXException;
     33 import org.xml.sax.XMLReader;
     34 import org.xml.sax.helpers.DefaultHandler;
     35 
     36 /**
     37  * A GraphReader allows obtaining filter graphs from XML graph files or strings.
     38  */
     39 public class GraphReader {
     40 
     41     private static interface Command {
     42         public void execute(CommandStack stack);
     43     }
     44 
     45     private static class CommandStack {
     46         private ArrayList<Command> mCommands = new ArrayList<Command>();
     47         private FilterGraph.Builder mBuilder;
     48         private FilterFactory mFactory;
     49         private MffContext mContext;
     50 
     51         public CommandStack(MffContext context) {
     52             mContext = context;
     53             mBuilder = new FilterGraph.Builder(mContext);
     54             mFactory = new FilterFactory();
     55         }
     56 
     57         public void execute() {
     58             for (Command command : mCommands) {
     59                 command.execute(this);
     60             }
     61         }
     62 
     63         public void append(Command command) {
     64             mCommands.add(command);
     65         }
     66 
     67         public FilterFactory getFactory() {
     68             return mFactory;
     69         }
     70 
     71         public MffContext getContext() {
     72             return mContext;
     73         }
     74 
     75         protected FilterGraph.Builder getBuilder() {
     76             return mBuilder;
     77         }
     78     }
     79 
     80     private static class ImportPackageCommand implements Command {
     81         private String mPackageName;
     82 
     83         public ImportPackageCommand(String packageName) {
     84             mPackageName = packageName;
     85         }
     86 
     87         @Override
     88         public void execute(CommandStack stack) {
     89             try {
     90                 stack.getFactory().addPackage(mPackageName);
     91             } catch (IllegalArgumentException e) {
     92                 throw new RuntimeException(e.getMessage());
     93             }
     94         }
     95     }
     96 
     97     private static class AddLibraryCommand implements Command {
     98         private String mLibraryName;
     99 
    100         public AddLibraryCommand(String libraryName) {
    101             mLibraryName = libraryName;
    102         }
    103 
    104         @Override
    105         public void execute(CommandStack stack) {
    106             FilterFactory.addFilterLibrary(mLibraryName);
    107         }
    108     }
    109 
    110     private static class AllocateFilterCommand implements Command {
    111         private String mClassName;
    112         private String mFilterName;
    113 
    114         public AllocateFilterCommand(String className, String filterName) {
    115             mClassName = className;
    116             mFilterName = filterName;
    117         }
    118 
    119         @Override
    120         public void execute(CommandStack stack) {
    121             Filter filter = null;
    122             try {
    123                 filter = stack.getFactory().createFilterByClassName(mClassName,
    124                                                                     mFilterName,
    125                                                                     stack.getContext());
    126             } catch (IllegalArgumentException e) {
    127                 throw new RuntimeException("Error creating filter " + mFilterName + "!", e);
    128             }
    129             stack.getBuilder().addFilter(filter);
    130         }
    131     }
    132 
    133     private static class AddSourceSlotCommand implements Command {
    134         private String mName;
    135         private String mSlotName;
    136 
    137         public AddSourceSlotCommand(String name, String slotName) {
    138             mName = name;
    139             mSlotName = slotName;
    140         }
    141 
    142         @Override
    143         public void execute(CommandStack stack) {
    144             stack.getBuilder().addFrameSlotSource(mName, mSlotName);
    145         }
    146     }
    147 
    148     private static class AddTargetSlotCommand implements Command {
    149         private String mName;
    150         private String mSlotName;
    151 
    152         public AddTargetSlotCommand(String name, String slotName) {
    153             mName = name;
    154             mSlotName = slotName;
    155         }
    156 
    157         @Override
    158         public void execute(CommandStack stack) {
    159             stack.getBuilder().addFrameSlotTarget(mName, mSlotName);
    160         }
    161     }
    162 
    163     private static class AddVariableCommand implements Command {
    164         private String mName;
    165         private Object mValue;
    166 
    167         public AddVariableCommand(String name, Object value) {
    168             mName = name;
    169             mValue = value;
    170         }
    171 
    172         @Override
    173         public void execute(CommandStack stack) {
    174             stack.getBuilder().addVariable(mName, mValue);
    175         }
    176     }
    177 
    178     private static class SetFilterInputCommand implements Command {
    179         private String mFilterName;
    180         private String mFilterInput;
    181         private Object mValue;
    182 
    183         public SetFilterInputCommand(String filterName, String input, Object value) {
    184             mFilterName = filterName;
    185             mFilterInput = input;
    186             mValue = value;
    187         }
    188 
    189         @Override
    190         public void execute(CommandStack stack) {
    191             if (mValue instanceof Variable) {
    192                 String varName = ((Variable)mValue).name;
    193                 stack.getBuilder().assignVariableToFilterInput(varName, mFilterName, mFilterInput);
    194             } else {
    195                 stack.getBuilder().assignValueToFilterInput(mValue, mFilterName, mFilterInput);
    196             }
    197         }
    198     }
    199 
    200     private static class ConnectCommand implements Command {
    201         private String mSourceFilter;
    202         private String mSourcePort;
    203         private String mTargetFilter;
    204         private String mTargetPort;
    205 
    206         public ConnectCommand(String sourceFilter,
    207                               String sourcePort,
    208                               String targetFilter,
    209                               String targetPort) {
    210             mSourceFilter = sourceFilter;
    211             mSourcePort = sourcePort;
    212             mTargetFilter = targetFilter;
    213             mTargetPort = targetPort;
    214         }
    215 
    216         @Override
    217         public void execute(CommandStack stack) {
    218             stack.getBuilder().connect(mSourceFilter, mSourcePort, mTargetFilter, mTargetPort);
    219         }
    220     }
    221 
    222     private static class Variable {
    223         public String name;
    224 
    225         public Variable(String name) {
    226             this.name = name;
    227         }
    228     }
    229 
    230     private static class XmlGraphReader {
    231 
    232         private SAXParserFactory mParserFactory;
    233 
    234         private static class GraphDataHandler extends DefaultHandler {
    235 
    236             private CommandStack mCommandStack;
    237             private boolean mInGraph = false;
    238             private String mCurFilterName = null;
    239 
    240             public GraphDataHandler(CommandStack commandStack) {
    241                 mCommandStack = commandStack;
    242             }
    243 
    244             @Override
    245             public void startElement(String uri, String localName, String qName, Attributes attr)
    246                     throws SAXException {
    247                 if (localName.equals("graph")) {
    248                     beginGraph();
    249                 } else {
    250                     assertInGraph(localName);
    251                     if (localName.equals("import")) {
    252                         addImportCommand(attr);
    253                     } else if (localName.equals("library")) {
    254                         addLibraryCommand(attr);
    255                     } else if (localName.equals("connect")) {
    256                         addConnectCommand(attr);
    257                     } else if (localName.equals("var")) {
    258                         addVarCommand(attr);
    259                     } else if (localName.equals("filter")) {
    260                         beginFilter(attr);
    261                     } else if (localName.equals("input")) {
    262                         addFilterInput(attr);
    263                     } else {
    264                         throw new SAXException("Unknown XML element '" + localName + "'!");
    265                     }
    266                 }
    267             }
    268 
    269             @Override
    270             public void endElement (String uri, String localName, String qName) {
    271                 if (localName.equals("graph")) {
    272                     endGraph();
    273                 } else if (localName.equals("filter")) {
    274                     endFilter();
    275                 }
    276             }
    277 
    278             private void addImportCommand(Attributes attributes) throws SAXException {
    279                 String packageName = getRequiredAttribute(attributes, "package");
    280                 mCommandStack.append(new ImportPackageCommand(packageName));
    281             }
    282 
    283             private void addLibraryCommand(Attributes attributes) throws SAXException {
    284                 String libraryName = getRequiredAttribute(attributes, "name");
    285                 mCommandStack.append(new AddLibraryCommand(libraryName));
    286             }
    287 
    288             private void addConnectCommand(Attributes attributes) {
    289                 String sourcePortName   = null;
    290                 String sourceFilterName = null;
    291                 String targetPortName   = null;
    292                 String targetFilterName = null;
    293 
    294                 // check for shorthand: <connect source="filter:port" target="filter:port"/>
    295                 String sourceTag = attributes.getValue("source");
    296                 if (sourceTag != null) {
    297                     String[] sourceParts = sourceTag.split(":");
    298                     if (sourceParts.length == 2) {
    299                         sourceFilterName = sourceParts[0];
    300                         sourcePortName   = sourceParts[1];
    301                     } else {
    302                         throw new RuntimeException(
    303                             "'source' tag needs to have format \"filter:port\"! " +
    304                             "Alternatively, you may use the form " +
    305                             "'sourceFilter=\"filter\" sourcePort=\"port\"'.");
    306                     }
    307                 } else {
    308                     sourceFilterName = attributes.getValue("sourceFilter");
    309                     sourcePortName   = attributes.getValue("sourcePort");
    310                 }
    311 
    312                 String targetTag = attributes.getValue("target");
    313                 if (targetTag != null) {
    314                     String[] targetParts = targetTag.split(":");
    315                     if (targetParts.length == 2) {
    316                         targetFilterName = targetParts[0];
    317                         targetPortName   = targetParts[1];
    318                     } else {
    319                         throw new RuntimeException(
    320                             "'target' tag needs to have format \"filter:port\"! " +
    321                             "Alternatively, you may use the form " +
    322                             "'targetFilter=\"filter\" targetPort=\"port\"'.");
    323                     }
    324                 } else {
    325                     targetFilterName = attributes.getValue("targetFilter");
    326                     targetPortName   = attributes.getValue("targetPort");
    327                 }
    328 
    329                 String sourceSlotName = attributes.getValue("sourceSlot");
    330                 String targetSlotName = attributes.getValue("targetSlot");
    331                 if (sourceSlotName != null) {
    332                     sourceFilterName = "sourceSlot_" + sourceSlotName;
    333                     mCommandStack.append(new AddSourceSlotCommand(sourceFilterName,
    334                                                                   sourceSlotName));
    335                     sourcePortName = "frame";
    336                 }
    337                 if (targetSlotName != null) {
    338                     targetFilterName = "targetSlot_" + targetSlotName;
    339                     mCommandStack.append(new AddTargetSlotCommand(targetFilterName,
    340                                                                   targetSlotName));
    341                     targetPortName = "frame";
    342                 }
    343                 assertValueNotNull("sourceFilter", sourceFilterName);
    344                 assertValueNotNull("sourcePort", sourcePortName);
    345                 assertValueNotNull("targetFilter", targetFilterName);
    346                 assertValueNotNull("targetPort", targetPortName);
    347                 // TODO: Should slot connections auto-branch?
    348                 mCommandStack.append(new ConnectCommand(sourceFilterName,
    349                                                         sourcePortName,
    350                                                         targetFilterName,
    351                                                         targetPortName));
    352             }
    353 
    354             private void addVarCommand(Attributes attributes) throws SAXException {
    355                 String varName = getRequiredAttribute(attributes, "name");
    356                 Object varValue = getAssignmentValue(attributes);
    357                 mCommandStack.append(new AddVariableCommand(varName, varValue));
    358             }
    359 
    360             private void beginGraph() throws SAXException {
    361                 if (mInGraph) {
    362                     throw new SAXException("Found more than one graph element in XML!");
    363                 }
    364                 mInGraph = true;
    365             }
    366 
    367             private void endGraph() {
    368                 mInGraph = false;
    369             }
    370 
    371             private void beginFilter(Attributes attributes) throws SAXException {
    372                 String className = getRequiredAttribute(attributes, "class");
    373                 mCurFilterName = getRequiredAttribute(attributes, "name");
    374                 mCommandStack.append(new AllocateFilterCommand(className, mCurFilterName));
    375             }
    376 
    377             private void endFilter() {
    378                 mCurFilterName = null;
    379             }
    380 
    381             private void addFilterInput(Attributes attributes) throws SAXException {
    382                 // Make sure we are in a filter element
    383                 if (mCurFilterName == null) {
    384                     throw new SAXException("Found 'input' element outside of 'filter' "
    385                         + "element!");
    386                 }
    387 
    388                 // Get input name and value
    389                 String inputName = getRequiredAttribute(attributes, "name");
    390                 Object inputValue = getAssignmentValue(attributes);
    391                 if (inputValue == null) {
    392                     throw new SAXException("No value specified for input '" + inputName + "' "
    393                         + "of filter '" + mCurFilterName + "'!");
    394                 }
    395 
    396                 // Push commmand
    397                 mCommandStack.append(new SetFilterInputCommand(mCurFilterName,
    398                                                                inputName,
    399                                                                inputValue));
    400             }
    401 
    402             private void assertInGraph(String localName) throws SAXException {
    403                 if (!mInGraph) {
    404                     throw new SAXException("Encountered '" + localName + "' element outside of "
    405                         + "'graph' element!");
    406                 }
    407             }
    408 
    409             private static Object getAssignmentValue(Attributes attributes) {
    410                 String strValue = null;
    411                 if ((strValue = attributes.getValue("stringValue")) != null) {
    412                     return strValue;
    413                 } else if ((strValue = attributes.getValue("booleanValue")) != null) {
    414                     return Boolean.parseBoolean(strValue);
    415                 } else if ((strValue = attributes.getValue("intValue")) != null) {
    416                     return Integer.parseInt(strValue);
    417                 } else if ((strValue = attributes.getValue("floatValue")) != null) {
    418                     return Float.parseFloat(strValue);
    419                 } else if ((strValue = attributes.getValue("floatsValue")) != null) {
    420                     String[] floatStrings = TextUtils.split(strValue, ",");
    421                     float[] result = new float[floatStrings.length];
    422                     for (int i = 0; i < floatStrings.length; ++i) {
    423                         result[i] = Float.parseFloat(floatStrings[i]);
    424                     }
    425                     return result;
    426                 } else if ((strValue = attributes.getValue("varValue")) != null) {
    427                     return new Variable(strValue);
    428                 } else {
    429                     return null;
    430                 }
    431             }
    432 
    433             private static String getRequiredAttribute(Attributes attributes, String name)
    434                     throws SAXException {
    435                 String result = attributes.getValue(name);
    436                 if (result == null) {
    437                     throw new SAXException("Required attribute '" + name + "' not found!");
    438                 }
    439                 return result;
    440             }
    441 
    442             private static void assertValueNotNull(String valueName, Object value) {
    443                 if (value == null) {
    444                     throw new NullPointerException("Required value '" + value + "' not specified!");
    445                 }
    446             }
    447 
    448         }
    449 
    450         public XmlGraphReader() {
    451             mParserFactory = SAXParserFactory.newInstance();
    452         }
    453 
    454         public void parseString(String graphString, CommandStack commandStack) throws IOException {
    455             try {
    456                 XMLReader reader = getReaderForCommandStack(commandStack);
    457                 reader.parse(new InputSource(new StringReader(graphString)));
    458             } catch (SAXException e) {
    459                 throw new IOException("XML parse error during graph parsing!", e);
    460             }
    461         }
    462 
    463         public void parseInput(InputStream inputStream, CommandStack commandStack)
    464                 throws IOException {
    465             try {
    466                 XMLReader reader = getReaderForCommandStack(commandStack);
    467                 reader.parse(new InputSource(inputStream));
    468             } catch (SAXException e) {
    469                 throw new IOException("XML parse error during graph parsing!", e);
    470             }
    471         }
    472 
    473         private XMLReader getReaderForCommandStack(CommandStack commandStack) throws IOException {
    474             try {
    475                 SAXParser parser = mParserFactory.newSAXParser();
    476                 XMLReader reader = parser.getXMLReader();
    477                 GraphDataHandler graphHandler = new GraphDataHandler(commandStack);
    478                 reader.setContentHandler(graphHandler);
    479                 return reader;
    480             } catch (ParserConfigurationException e) {
    481                 throw new IOException("Error creating SAXParser for graph parsing!", e);
    482             } catch (SAXException e) {
    483                 throw new IOException("Error creating XMLReader for graph parsing!", e);
    484             }
    485         }
    486     }
    487 
    488     /**
    489      * Read an XML graph from a String.
    490      *
    491      * This function automatically checks each filters' signatures and throws a Runtime Exception
    492      * if required ports are unconnected. Use the 3-parameter version to avoid this behavior.
    493      *
    494      * @param context the MffContext into which to load the graph.
    495      * @param xmlSource the graph specified in XML.
    496      * @return the FilterGraph instance for the XML source.
    497      * @throws IOException if there was an error parsing the source.
    498      */
    499     public static FilterGraph readXmlGraph(MffContext context, String xmlSource)
    500             throws IOException {
    501         FilterGraph.Builder builder = getBuilderForXmlString(context, xmlSource);
    502         return builder.build();
    503     }
    504 
    505     /**
    506      * Read an XML sub-graph from a String.
    507      *
    508      * @param context the MffContext into which to load the graph.
    509      * @param xmlSource the graph specified in XML.
    510      * @param parentGraph the parent graph.
    511      * @return the FilterGraph instance for the XML source.
    512      * @throws IOException if there was an error parsing the source.
    513      */
    514     public static FilterGraph readXmlSubGraph(
    515             MffContext context, String xmlSource, FilterGraph parentGraph)
    516             throws IOException {
    517         FilterGraph.Builder builder = getBuilderForXmlString(context, xmlSource);
    518         return builder.buildSubGraph(parentGraph);
    519     }
    520 
    521     /**
    522      * Read an XML graph from a resource.
    523      *
    524      * This function automatically checks each filters' signatures and throws a Runtime Exception
    525      * if required ports are unconnected. Use the 3-parameter version to avoid this behavior.
    526      *
    527      * @param context the MffContext into which to load the graph.
    528      * @param resourceId the XML resource ID.
    529      * @return the FilterGraph instance for the XML source.
    530      * @throws IOException if there was an error reading or parsing the resource.
    531      */
    532     public static FilterGraph readXmlGraphResource(MffContext context, int resourceId)
    533             throws IOException {
    534         FilterGraph.Builder builder = getBuilderForXmlResource(context, resourceId);
    535         return builder.build();
    536     }
    537 
    538     /**
    539      * Read an XML graph from a resource.
    540      *
    541      * This function automatically checks each filters' signatures and throws a Runtime Exception
    542      * if required ports are unconnected. Use the 3-parameter version to avoid this behavior.
    543      *
    544      * @param context the MffContext into which to load the graph.
    545      * @param resourceId the XML resource ID.
    546      * @return the FilterGraph instance for the XML source.
    547      * @throws IOException if there was an error reading or parsing the resource.
    548      */
    549     public static FilterGraph readXmlSubGraphResource(
    550             MffContext context, int resourceId, FilterGraph parentGraph)
    551             throws IOException {
    552         FilterGraph.Builder builder = getBuilderForXmlResource(context, resourceId);
    553         return builder.buildSubGraph(parentGraph);
    554     }
    555 
    556     private static FilterGraph.Builder getBuilderForXmlString(MffContext context, String source)
    557             throws IOException {
    558         XmlGraphReader reader = new XmlGraphReader();
    559         CommandStack commands = new CommandStack(context);
    560         reader.parseString(source, commands);
    561         commands.execute();
    562         return commands.getBuilder();
    563     }
    564 
    565     private static FilterGraph.Builder getBuilderForXmlResource(MffContext context, int resourceId)
    566             throws IOException {
    567         InputStream inputStream = context.getApplicationContext().getResources()
    568                 .openRawResource(resourceId);
    569         XmlGraphReader reader = new XmlGraphReader();
    570         CommandStack commands = new CommandStack(context);
    571         reader.parseInput(inputStream, commands);
    572         commands.execute();
    573         return commands.getBuilder();
    574     }
    575 }
    576 
    577