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