• 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 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 {
execute(CommandStack stack)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 
CommandStack(MffContext context)51         public CommandStack(MffContext context) {
52             mContext = context;
53             mBuilder = new FilterGraph.Builder(mContext);
54             mFactory = new FilterFactory();
55         }
56 
execute()57         public void execute() {
58             for (Command command : mCommands) {
59                 command.execute(this);
60             }
61         }
62 
append(Command command)63         public void append(Command command) {
64             mCommands.add(command);
65         }
66 
getFactory()67         public FilterFactory getFactory() {
68             return mFactory;
69         }
70 
getContext()71         public MffContext getContext() {
72             return mContext;
73         }
74 
getBuilder()75         protected FilterGraph.Builder getBuilder() {
76             return mBuilder;
77         }
78     }
79 
80     private static class ImportPackageCommand implements Command {
81         private String mPackageName;
82 
ImportPackageCommand(String packageName)83         public ImportPackageCommand(String packageName) {
84             mPackageName = packageName;
85         }
86 
87         @Override
execute(CommandStack stack)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 
AddLibraryCommand(String libraryName)100         public AddLibraryCommand(String libraryName) {
101             mLibraryName = libraryName;
102         }
103 
104         @Override
execute(CommandStack stack)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 
AllocateFilterCommand(String className, String filterName)114         public AllocateFilterCommand(String className, String filterName) {
115             mClassName = className;
116             mFilterName = filterName;
117         }
118 
119         @Override
execute(CommandStack stack)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 
AddSourceSlotCommand(String name, String slotName)137         public AddSourceSlotCommand(String name, String slotName) {
138             mName = name;
139             mSlotName = slotName;
140         }
141 
142         @Override
execute(CommandStack stack)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 
AddTargetSlotCommand(String name, String slotName)152         public AddTargetSlotCommand(String name, String slotName) {
153             mName = name;
154             mSlotName = slotName;
155         }
156 
157         @Override
execute(CommandStack stack)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 
AddVariableCommand(String name, Object value)167         public AddVariableCommand(String name, Object value) {
168             mName = name;
169             mValue = value;
170         }
171 
172         @Override
execute(CommandStack stack)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 
SetFilterInputCommand(String filterName, String input, Object value)183         public SetFilterInputCommand(String filterName, String input, Object value) {
184             mFilterName = filterName;
185             mFilterInput = input;
186             mValue = value;
187         }
188 
189         @Override
execute(CommandStack stack)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 
ConnectCommand(String sourceFilter, String sourcePort, String targetFilter, String targetPort)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
execute(CommandStack stack)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 
Variable(String name)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 
GraphDataHandler(CommandStack commandStack)240             public GraphDataHandler(CommandStack commandStack) {
241                 mCommandStack = commandStack;
242             }
243 
244             @Override
startElement(String uri, String localName, String qName, Attributes attr)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
endElement(String uri, String localName, String qName)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 
addImportCommand(Attributes attributes)278             private void addImportCommand(Attributes attributes) throws SAXException {
279                 String packageName = getRequiredAttribute(attributes, "package");
280                 mCommandStack.append(new ImportPackageCommand(packageName));
281             }
282 
addLibraryCommand(Attributes attributes)283             private void addLibraryCommand(Attributes attributes) throws SAXException {
284                 String libraryName = getRequiredAttribute(attributes, "name");
285                 mCommandStack.append(new AddLibraryCommand(libraryName));
286             }
287 
addConnectCommand(Attributes attributes)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 
addVarCommand(Attributes attributes)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 
beginGraph()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 
endGraph()367             private void endGraph() {
368                 mInGraph = false;
369             }
370 
beginFilter(Attributes attributes)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 
endFilter()377             private void endFilter() {
378                 mCurFilterName = null;
379             }
380 
addFilterInput(Attributes attributes)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 
assertInGraph(String localName)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 
getAssignmentValue(Attributes attributes)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 
getRequiredAttribute(Attributes attributes, String name)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 
assertValueNotNull(String valueName, Object value)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 
XmlGraphReader()450         public XmlGraphReader() {
451             mParserFactory = SAXParserFactory.newInstance();
452         }
453 
parseString(String graphString, CommandStack commandStack)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 
parseInput(InputStream inputStream, CommandStack commandStack)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 
getReaderForCommandStack(CommandStack commandStack)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      */
readXmlGraph(MffContext context, String xmlSource)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      */
readXmlSubGraph( MffContext context, String xmlSource, FilterGraph parentGraph)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      */
readXmlGraphResource(MffContext context, int resourceId)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      */
readXmlSubGraphResource( MffContext context, int resourceId, FilterGraph parentGraph)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 
getBuilderForXmlString(MffContext context, String source)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 
getBuilderForXmlResource(MffContext context, int resourceId)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