| /* |
| * Copyright (C) 2011 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. |
| */ |
| |
| package androidx.media.filterfw; |
| |
| import android.text.TextUtils; |
| |
| import java.io.InputStream; |
| import java.io.IOException; |
| import java.io.StringReader; |
| import java.util.ArrayList; |
| |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.parsers.SAXParser; |
| import javax.xml.parsers.SAXParserFactory; |
| |
| import org.xml.sax.Attributes; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.XMLReader; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| /** |
| * A GraphReader allows obtaining filter graphs from XML graph files or strings. |
| */ |
| public class GraphReader { |
| |
| private static interface Command { |
| public void execute(CommandStack stack); |
| } |
| |
| private static class CommandStack { |
| private ArrayList<Command> mCommands = new ArrayList<Command>(); |
| private FilterGraph.Builder mBuilder; |
| private FilterFactory mFactory; |
| private MffContext mContext; |
| |
| public CommandStack(MffContext context) { |
| mContext = context; |
| mBuilder = new FilterGraph.Builder(mContext); |
| mFactory = new FilterFactory(); |
| } |
| |
| public void execute() { |
| for (Command command : mCommands) { |
| command.execute(this); |
| } |
| } |
| |
| public void append(Command command) { |
| mCommands.add(command); |
| } |
| |
| public FilterFactory getFactory() { |
| return mFactory; |
| } |
| |
| public MffContext getContext() { |
| return mContext; |
| } |
| |
| protected FilterGraph.Builder getBuilder() { |
| return mBuilder; |
| } |
| } |
| |
| private static class ImportPackageCommand implements Command { |
| private String mPackageName; |
| |
| public ImportPackageCommand(String packageName) { |
| mPackageName = packageName; |
| } |
| |
| @Override |
| public void execute(CommandStack stack) { |
| try { |
| stack.getFactory().addPackage(mPackageName); |
| } catch (IllegalArgumentException e) { |
| throw new RuntimeException(e.getMessage()); |
| } |
| } |
| } |
| |
| private static class AddLibraryCommand implements Command { |
| private String mLibraryName; |
| |
| public AddLibraryCommand(String libraryName) { |
| mLibraryName = libraryName; |
| } |
| |
| @Override |
| public void execute(CommandStack stack) { |
| FilterFactory.addFilterLibrary(mLibraryName); |
| } |
| } |
| |
| private static class AllocateFilterCommand implements Command { |
| private String mClassName; |
| private String mFilterName; |
| |
| public AllocateFilterCommand(String className, String filterName) { |
| mClassName = className; |
| mFilterName = filterName; |
| } |
| |
| @Override |
| public void execute(CommandStack stack) { |
| Filter filter = null; |
| try { |
| filter = stack.getFactory().createFilterByClassName(mClassName, |
| mFilterName, |
| stack.getContext()); |
| } catch (IllegalArgumentException e) { |
| throw new RuntimeException("Error creating filter " + mFilterName + "!", e); |
| } |
| stack.getBuilder().addFilter(filter); |
| } |
| } |
| |
| private static class AddSourceSlotCommand implements Command { |
| private String mName; |
| private String mSlotName; |
| |
| public AddSourceSlotCommand(String name, String slotName) { |
| mName = name; |
| mSlotName = slotName; |
| } |
| |
| @Override |
| public void execute(CommandStack stack) { |
| stack.getBuilder().addFrameSlotSource(mName, mSlotName); |
| } |
| } |
| |
| private static class AddTargetSlotCommand implements Command { |
| private String mName; |
| private String mSlotName; |
| |
| public AddTargetSlotCommand(String name, String slotName) { |
| mName = name; |
| mSlotName = slotName; |
| } |
| |
| @Override |
| public void execute(CommandStack stack) { |
| stack.getBuilder().addFrameSlotTarget(mName, mSlotName); |
| } |
| } |
| |
| private static class AddVariableCommand implements Command { |
| private String mName; |
| private Object mValue; |
| |
| public AddVariableCommand(String name, Object value) { |
| mName = name; |
| mValue = value; |
| } |
| |
| @Override |
| public void execute(CommandStack stack) { |
| stack.getBuilder().addVariable(mName, mValue); |
| } |
| } |
| |
| private static class SetFilterInputCommand implements Command { |
| private String mFilterName; |
| private String mFilterInput; |
| private Object mValue; |
| |
| public SetFilterInputCommand(String filterName, String input, Object value) { |
| mFilterName = filterName; |
| mFilterInput = input; |
| mValue = value; |
| } |
| |
| @Override |
| public void execute(CommandStack stack) { |
| if (mValue instanceof Variable) { |
| String varName = ((Variable)mValue).name; |
| stack.getBuilder().assignVariableToFilterInput(varName, mFilterName, mFilterInput); |
| } else { |
| stack.getBuilder().assignValueToFilterInput(mValue, mFilterName, mFilterInput); |
| } |
| } |
| } |
| |
| private static class ConnectCommand implements Command { |
| private String mSourceFilter; |
| private String mSourcePort; |
| private String mTargetFilter; |
| private String mTargetPort; |
| |
| public ConnectCommand(String sourceFilter, |
| String sourcePort, |
| String targetFilter, |
| String targetPort) { |
| mSourceFilter = sourceFilter; |
| mSourcePort = sourcePort; |
| mTargetFilter = targetFilter; |
| mTargetPort = targetPort; |
| } |
| |
| @Override |
| public void execute(CommandStack stack) { |
| stack.getBuilder().connect(mSourceFilter, mSourcePort, mTargetFilter, mTargetPort); |
| } |
| } |
| |
| private static class Variable { |
| public String name; |
| |
| public Variable(String name) { |
| this.name = name; |
| } |
| } |
| |
| private static class XmlGraphReader { |
| |
| private SAXParserFactory mParserFactory; |
| |
| private static class GraphDataHandler extends DefaultHandler { |
| |
| private CommandStack mCommandStack; |
| private boolean mInGraph = false; |
| private String mCurFilterName = null; |
| |
| public GraphDataHandler(CommandStack commandStack) { |
| mCommandStack = commandStack; |
| } |
| |
| @Override |
| public void startElement(String uri, String localName, String qName, Attributes attr) |
| throws SAXException { |
| if (localName.equals("graph")) { |
| beginGraph(); |
| } else { |
| assertInGraph(localName); |
| if (localName.equals("import")) { |
| addImportCommand(attr); |
| } else if (localName.equals("library")) { |
| addLibraryCommand(attr); |
| } else if (localName.equals("connect")) { |
| addConnectCommand(attr); |
| } else if (localName.equals("var")) { |
| addVarCommand(attr); |
| } else if (localName.equals("filter")) { |
| beginFilter(attr); |
| } else if (localName.equals("input")) { |
| addFilterInput(attr); |
| } else { |
| throw new SAXException("Unknown XML element '" + localName + "'!"); |
| } |
| } |
| } |
| |
| @Override |
| public void endElement (String uri, String localName, String qName) { |
| if (localName.equals("graph")) { |
| endGraph(); |
| } else if (localName.equals("filter")) { |
| endFilter(); |
| } |
| } |
| |
| private void addImportCommand(Attributes attributes) throws SAXException { |
| String packageName = getRequiredAttribute(attributes, "package"); |
| mCommandStack.append(new ImportPackageCommand(packageName)); |
| } |
| |
| private void addLibraryCommand(Attributes attributes) throws SAXException { |
| String libraryName = getRequiredAttribute(attributes, "name"); |
| mCommandStack.append(new AddLibraryCommand(libraryName)); |
| } |
| |
| private void addConnectCommand(Attributes attributes) { |
| String sourcePortName = null; |
| String sourceFilterName = null; |
| String targetPortName = null; |
| String targetFilterName = null; |
| |
| // check for shorthand: <connect source="filter:port" target="filter:port"/> |
| String sourceTag = attributes.getValue("source"); |
| if (sourceTag != null) { |
| String[] sourceParts = sourceTag.split(":"); |
| if (sourceParts.length == 2) { |
| sourceFilterName = sourceParts[0]; |
| sourcePortName = sourceParts[1]; |
| } else { |
| throw new RuntimeException( |
| "'source' tag needs to have format \"filter:port\"! " + |
| "Alternatively, you may use the form " + |
| "'sourceFilter=\"filter\" sourcePort=\"port\"'."); |
| } |
| } else { |
| sourceFilterName = attributes.getValue("sourceFilter"); |
| sourcePortName = attributes.getValue("sourcePort"); |
| } |
| |
| String targetTag = attributes.getValue("target"); |
| if (targetTag != null) { |
| String[] targetParts = targetTag.split(":"); |
| if (targetParts.length == 2) { |
| targetFilterName = targetParts[0]; |
| targetPortName = targetParts[1]; |
| } else { |
| throw new RuntimeException( |
| "'target' tag needs to have format \"filter:port\"! " + |
| "Alternatively, you may use the form " + |
| "'targetFilter=\"filter\" targetPort=\"port\"'."); |
| } |
| } else { |
| targetFilterName = attributes.getValue("targetFilter"); |
| targetPortName = attributes.getValue("targetPort"); |
| } |
| |
| String sourceSlotName = attributes.getValue("sourceSlot"); |
| String targetSlotName = attributes.getValue("targetSlot"); |
| if (sourceSlotName != null) { |
| sourceFilterName = "sourceSlot_" + sourceSlotName; |
| mCommandStack.append(new AddSourceSlotCommand(sourceFilterName, |
| sourceSlotName)); |
| sourcePortName = "frame"; |
| } |
| if (targetSlotName != null) { |
| targetFilterName = "targetSlot_" + targetSlotName; |
| mCommandStack.append(new AddTargetSlotCommand(targetFilterName, |
| targetSlotName)); |
| targetPortName = "frame"; |
| } |
| assertValueNotNull("sourceFilter", sourceFilterName); |
| assertValueNotNull("sourcePort", sourcePortName); |
| assertValueNotNull("targetFilter", targetFilterName); |
| assertValueNotNull("targetPort", targetPortName); |
| // TODO: Should slot connections auto-branch? |
| mCommandStack.append(new ConnectCommand(sourceFilterName, |
| sourcePortName, |
| targetFilterName, |
| targetPortName)); |
| } |
| |
| private void addVarCommand(Attributes attributes) throws SAXException { |
| String varName = getRequiredAttribute(attributes, "name"); |
| Object varValue = getAssignmentValue(attributes); |
| mCommandStack.append(new AddVariableCommand(varName, varValue)); |
| } |
| |
| private void beginGraph() throws SAXException { |
| if (mInGraph) { |
| throw new SAXException("Found more than one graph element in XML!"); |
| } |
| mInGraph = true; |
| } |
| |
| private void endGraph() { |
| mInGraph = false; |
| } |
| |
| private void beginFilter(Attributes attributes) throws SAXException { |
| String className = getRequiredAttribute(attributes, "class"); |
| mCurFilterName = getRequiredAttribute(attributes, "name"); |
| mCommandStack.append(new AllocateFilterCommand(className, mCurFilterName)); |
| } |
| |
| private void endFilter() { |
| mCurFilterName = null; |
| } |
| |
| private void addFilterInput(Attributes attributes) throws SAXException { |
| // Make sure we are in a filter element |
| if (mCurFilterName == null) { |
| throw new SAXException("Found 'input' element outside of 'filter' " |
| + "element!"); |
| } |
| |
| // Get input name and value |
| String inputName = getRequiredAttribute(attributes, "name"); |
| Object inputValue = getAssignmentValue(attributes); |
| if (inputValue == null) { |
| throw new SAXException("No value specified for input '" + inputName + "' " |
| + "of filter '" + mCurFilterName + "'!"); |
| } |
| |
| // Push commmand |
| mCommandStack.append(new SetFilterInputCommand(mCurFilterName, |
| inputName, |
| inputValue)); |
| } |
| |
| private void assertInGraph(String localName) throws SAXException { |
| if (!mInGraph) { |
| throw new SAXException("Encountered '" + localName + "' element outside of " |
| + "'graph' element!"); |
| } |
| } |
| |
| private static Object getAssignmentValue(Attributes attributes) { |
| String strValue = null; |
| if ((strValue = attributes.getValue("stringValue")) != null) { |
| return strValue; |
| } else if ((strValue = attributes.getValue("booleanValue")) != null) { |
| return Boolean.parseBoolean(strValue); |
| } else if ((strValue = attributes.getValue("intValue")) != null) { |
| return Integer.parseInt(strValue); |
| } else if ((strValue = attributes.getValue("floatValue")) != null) { |
| return Float.parseFloat(strValue); |
| } else if ((strValue = attributes.getValue("floatsValue")) != null) { |
| String[] floatStrings = TextUtils.split(strValue, ","); |
| float[] result = new float[floatStrings.length]; |
| for (int i = 0; i < floatStrings.length; ++i) { |
| result[i] = Float.parseFloat(floatStrings[i]); |
| } |
| return result; |
| } else if ((strValue = attributes.getValue("varValue")) != null) { |
| return new Variable(strValue); |
| } else { |
| return null; |
| } |
| } |
| |
| private static String getRequiredAttribute(Attributes attributes, String name) |
| throws SAXException { |
| String result = attributes.getValue(name); |
| if (result == null) { |
| throw new SAXException("Required attribute '" + name + "' not found!"); |
| } |
| return result; |
| } |
| |
| private static void assertValueNotNull(String valueName, Object value) { |
| if (value == null) { |
| throw new NullPointerException("Required value '" + value + "' not specified!"); |
| } |
| } |
| |
| } |
| |
| public XmlGraphReader() { |
| mParserFactory = SAXParserFactory.newInstance(); |
| } |
| |
| public void parseString(String graphString, CommandStack commandStack) throws IOException { |
| try { |
| XMLReader reader = getReaderForCommandStack(commandStack); |
| reader.parse(new InputSource(new StringReader(graphString))); |
| } catch (SAXException e) { |
| throw new IOException("XML parse error during graph parsing!", e); |
| } |
| } |
| |
| public void parseInput(InputStream inputStream, CommandStack commandStack) |
| throws IOException { |
| try { |
| XMLReader reader = getReaderForCommandStack(commandStack); |
| reader.parse(new InputSource(inputStream)); |
| } catch (SAXException e) { |
| throw new IOException("XML parse error during graph parsing!", e); |
| } |
| } |
| |
| private XMLReader getReaderForCommandStack(CommandStack commandStack) throws IOException { |
| try { |
| SAXParser parser = mParserFactory.newSAXParser(); |
| XMLReader reader = parser.getXMLReader(); |
| GraphDataHandler graphHandler = new GraphDataHandler(commandStack); |
| reader.setContentHandler(graphHandler); |
| return reader; |
| } catch (ParserConfigurationException e) { |
| throw new IOException("Error creating SAXParser for graph parsing!", e); |
| } catch (SAXException e) { |
| throw new IOException("Error creating XMLReader for graph parsing!", e); |
| } |
| } |
| } |
| |
| /** |
| * Read an XML graph from a String. |
| * |
| * This function automatically checks each filters' signatures and throws a Runtime Exception |
| * if required ports are unconnected. Use the 3-parameter version to avoid this behavior. |
| * |
| * @param context the MffContext into which to load the graph. |
| * @param xmlSource the graph specified in XML. |
| * @return the FilterGraph instance for the XML source. |
| * @throws IOException if there was an error parsing the source. |
| */ |
| public static FilterGraph readXmlGraph(MffContext context, String xmlSource) |
| throws IOException { |
| FilterGraph.Builder builder = getBuilderForXmlString(context, xmlSource); |
| return builder.build(); |
| } |
| |
| /** |
| * Read an XML sub-graph from a String. |
| * |
| * @param context the MffContext into which to load the graph. |
| * @param xmlSource the graph specified in XML. |
| * @param parentGraph the parent graph. |
| * @return the FilterGraph instance for the XML source. |
| * @throws IOException if there was an error parsing the source. |
| */ |
| public static FilterGraph readXmlSubGraph( |
| MffContext context, String xmlSource, FilterGraph parentGraph) |
| throws IOException { |
| FilterGraph.Builder builder = getBuilderForXmlString(context, xmlSource); |
| return builder.buildSubGraph(parentGraph); |
| } |
| |
| /** |
| * Read an XML graph from a resource. |
| * |
| * This function automatically checks each filters' signatures and throws a Runtime Exception |
| * if required ports are unconnected. Use the 3-parameter version to avoid this behavior. |
| * |
| * @param context the MffContext into which to load the graph. |
| * @param resourceId the XML resource ID. |
| * @return the FilterGraph instance for the XML source. |
| * @throws IOException if there was an error reading or parsing the resource. |
| */ |
| public static FilterGraph readXmlGraphResource(MffContext context, int resourceId) |
| throws IOException { |
| FilterGraph.Builder builder = getBuilderForXmlResource(context, resourceId); |
| return builder.build(); |
| } |
| |
| /** |
| * Read an XML graph from a resource. |
| * |
| * This function automatically checks each filters' signatures and throws a Runtime Exception |
| * if required ports are unconnected. Use the 3-parameter version to avoid this behavior. |
| * |
| * @param context the MffContext into which to load the graph. |
| * @param resourceId the XML resource ID. |
| * @return the FilterGraph instance for the XML source. |
| * @throws IOException if there was an error reading or parsing the resource. |
| */ |
| public static FilterGraph readXmlSubGraphResource( |
| MffContext context, int resourceId, FilterGraph parentGraph) |
| throws IOException { |
| FilterGraph.Builder builder = getBuilderForXmlResource(context, resourceId); |
| return builder.buildSubGraph(parentGraph); |
| } |
| |
| private static FilterGraph.Builder getBuilderForXmlString(MffContext context, String source) |
| throws IOException { |
| XmlGraphReader reader = new XmlGraphReader(); |
| CommandStack commands = new CommandStack(context); |
| reader.parseString(source, commands); |
| commands.execute(); |
| return commands.getBuilder(); |
| } |
| |
| private static FilterGraph.Builder getBuilderForXmlResource(MffContext context, int resourceId) |
| throws IOException { |
| InputStream inputStream = context.getApplicationContext().getResources() |
| .openRawResource(resourceId); |
| XmlGraphReader reader = new XmlGraphReader(); |
| CommandStack commands = new CommandStack(context); |
| reader.parseInput(inputStream, commands); |
| commands.execute(); |
| return commands.getBuilder(); |
| } |
| } |
| |