Home | History | Annotate | Download | only in filterfw
      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 
     58     protected abstract Filter createFilter(MffContext mffContext);
     59 
     60     @Override
     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
     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 
    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      */
    113     protected Frame getOutputFrame(String outputPortName) {
    114         return mOutputFrames.get("out_" + outputPortName);
    115     }
    116 
    117     protected void process(long timeoutMs)
    118             throws ExecutionException, TimeoutException, InterruptedException {
    119         mRunner.start(mGraph);
    120         mProcessResult.get(timeoutMs, TimeUnit.MILLISECONDS);
    121     }
    122 
    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      */
    132     protected Frame createFrame(FrameType type, int[] dimensions) {
    133         return new Frame(type, dimensions, mRunner.getFrameManager());
    134     }
    135 
    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 
    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
    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