1 /* 2 * Copyright (C) 2013 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 package androidx.media.filterfw; 17 18 import androidx.media.filterfw.GraphRunner.Listener; 19 import androidx.media.filterfw.Signature.PortInfo; 20 21 import com.google.common.util.concurrent.SettableFuture; 22 23 import junit.framework.TestCase; 24 25 import java.util.HashMap; 26 import java.util.HashSet; 27 import java.util.Map; 28 import java.util.Map.Entry; 29 import java.util.Set; 30 import java.util.concurrent.ExecutionException; 31 import java.util.concurrent.TimeUnit; 32 import java.util.concurrent.TimeoutException; 33 34 /** 35 * A {@link TestCase} for testing single MFF filter runs. Implementers should extend this class and 36 * implement the {@link #createFilter(MffContext)} method to create the filter under test. Inside 37 * each test method, the implementer should supply one or more frames for all the filter inputs 38 * (calling {@link #injectInputFrame(String, Frame)}) and then invoke {@link #process()}. Once the 39 * processing finishes, one should call {@link #getOutputFrame(String)} to get and inspect the 40 * output frames. 41 * 42 * TODO: extend this to deal with filters that push multiple output frames. 43 * TODO: relax the requirement that all output ports should be pushed (the implementer should be 44 * able to tell which ports to wait for before process() returns). 45 * TODO: handle undeclared inputs and outputs. 46 */ 47 public abstract class MffFilterTestCase extends MffTestCase { 48 49 private static final long DEFAULT_TIMEOUT_MS = 1000; 50 51 private FilterGraph mGraph; 52 private GraphRunner mRunner; 53 private Map<String, Frame> mOutputFrames; 54 private Set<String> mEmptyOutputPorts; 55 56 private SettableFuture<Void> mProcessResult; 57 createFilter(MffContext mffContext)58 protected abstract Filter createFilter(MffContext mffContext); 59 60 @Override setUp()61 protected void setUp() throws Exception { 62 super.setUp(); 63 MffContext mffContext = getMffContext(); 64 FilterGraph.Builder graphBuilder = new FilterGraph.Builder(mffContext); 65 Filter filterUnderTest = createFilter(mffContext); 66 graphBuilder.addFilter(filterUnderTest); 67 68 connectInputPorts(mffContext, graphBuilder, filterUnderTest); 69 connectOutputPorts(mffContext, graphBuilder, filterUnderTest); 70 71 mGraph = graphBuilder.build(); 72 mRunner = mGraph.getRunner(); 73 mRunner.setListener(new Listener() { 74 @Override 75 public void onGraphRunnerStopped(GraphRunner runner) { 76 mProcessResult.set(null); 77 } 78 79 @Override 80 public void onGraphRunnerError(Exception exception, boolean closedSuccessfully) { 81 mProcessResult.setException(exception); 82 } 83 }); 84 85 mOutputFrames = new HashMap<String, Frame>(); 86 mProcessResult = SettableFuture.create(); 87 } 88 89 @Override tearDown()90 protected void tearDown() throws Exception { 91 for (Frame frame : mOutputFrames.values()) { 92 frame.release(); 93 } 94 mOutputFrames = null; 95 96 mRunner.stop(); 97 mRunner = null; 98 mGraph = null; 99 100 mProcessResult = null; 101 super.tearDown(); 102 } 103 injectInputFrame(String portName, Frame frame)104 protected void injectInputFrame(String portName, Frame frame) { 105 FrameSourceFilter filter = (FrameSourceFilter) mGraph.getFilter("in_" + portName); 106 filter.injectFrame(frame); 107 } 108 109 /** 110 * Returns the frame pushed out by the filter under test. Should only be called after 111 * {@link #process(long)} has returned. 112 */ getOutputFrame(String outputPortName)113 protected Frame getOutputFrame(String outputPortName) { 114 return mOutputFrames.get("out_" + outputPortName); 115 } 116 process(long timeoutMs)117 protected void process(long timeoutMs) 118 throws ExecutionException, TimeoutException, InterruptedException { 119 mRunner.start(mGraph); 120 mProcessResult.get(timeoutMs, TimeUnit.MILLISECONDS); 121 } 122 process()123 protected void process() throws ExecutionException, TimeoutException, InterruptedException { 124 process(DEFAULT_TIMEOUT_MS); 125 } 126 127 /** 128 * This method should be called to create the input frames inside the test cases (instead of 129 * {@link Frame#create(FrameType, int[])}). This is required to work around a requirement for 130 * the latter method to be called on the MFF thread. 131 */ createFrame(FrameType type, int[] dimensions)132 protected Frame createFrame(FrameType type, int[] dimensions) { 133 return new Frame(type, dimensions, mRunner.getFrameManager()); 134 } 135 connectInputPorts( MffContext mffContext, FilterGraph.Builder graphBuilder, Filter filter)136 private void connectInputPorts( 137 MffContext mffContext, FilterGraph.Builder graphBuilder, Filter filter) { 138 Signature signature = filter.getSignature(); 139 for (Entry<String, PortInfo> inputPortEntry : signature.getInputPorts().entrySet()) { 140 Filter inputFilter = new FrameSourceFilter(mffContext, "in_" + inputPortEntry.getKey()); 141 graphBuilder.addFilter(inputFilter); 142 graphBuilder.connect(inputFilter, "output", filter, inputPortEntry.getKey()); 143 } 144 } 145 connectOutputPorts( MffContext mffContext, FilterGraph.Builder graphBuilder, Filter filter)146 private void connectOutputPorts( 147 MffContext mffContext, FilterGraph.Builder graphBuilder, Filter filter) { 148 Signature signature = filter.getSignature(); 149 mEmptyOutputPorts = new HashSet<String>(); 150 OutputFrameListener outputFrameListener = new OutputFrameListener(); 151 for (Entry<String, PortInfo> outputPortEntry : signature.getOutputPorts().entrySet()) { 152 FrameTargetFilter outputFilter = new FrameTargetFilter( 153 mffContext, "out_" + outputPortEntry.getKey()); 154 graphBuilder.addFilter(outputFilter); 155 graphBuilder.connect(filter, outputPortEntry.getKey(), outputFilter, "input"); 156 outputFilter.setListener(outputFrameListener); 157 mEmptyOutputPorts.add("out_" + outputPortEntry.getKey()); 158 } 159 } 160 161 private class OutputFrameListener implements FrameTargetFilter.Listener { 162 163 @Override onFramePushed(String filterName, Frame frame)164 public void onFramePushed(String filterName, Frame frame) { 165 mOutputFrames.put(filterName, frame); 166 boolean alreadyPushed = !mEmptyOutputPorts.remove(filterName); 167 if (alreadyPushed) { 168 throw new IllegalStateException( 169 "A frame has been pushed twice to the same output port."); 170 } 171 if (mEmptyOutputPorts.isEmpty()) { 172 // All outputs have been pushed, stop the graph. 173 mRunner.stop(); 174 } 175 } 176 177 } 178 179 } 180