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
18package androidx.media.filterfw;
19
20import java.util.HashMap;
21import java.util.HashSet;
22import java.util.Map.Entry;
23import java.util.Set;
24
25/**
26 * A Signature holds the specification for a filter's input and output ports.
27 *
28 * A Signature instance must be returned by the filter's {@link Filter#getSignature()} method. It
29 * specifies the number and names of the filter's input and output ports, whether or not they
30 * are required, how data for those ports are accessed, and more. A Signature does not change over
31 * time. This makes Signatures useful for understanding how a filter can be integrated into a
32 * graph.
33 *
34 * There are a number of flags that can be specified for each input and output port. The flag
35 * {@code PORT_REQUIRED} indicates that the user must connect the specified port. On the other hand,
36 * {@code PORT_OPTIONAL} indicates that a port may be connected by the user.
37 *
38 * If ports other than the ones in the Signature are allowed, they default to the most generic
39 * format, that allows passing in any type of Frame. Thus, if more granular access is needed to
40 * a frame's data, it must be specified in the Signature.
41 */
42public class Signature {
43
44    private HashMap<String, PortInfo> mInputPorts = null;
45    private HashMap<String, PortInfo> mOutputPorts = null;
46    private boolean mAllowOtherInputs = true;
47    private boolean mAllowOtherOutputs = true;
48
49    static class PortInfo {
50        public int flags;
51        public FrameType type;
52
53        public PortInfo() {
54            flags = 0;
55            type = FrameType.any();
56        }
57
58        public PortInfo(int flags, FrameType type) {
59            this.flags = flags;
60            this.type = type;
61        }
62
63        public boolean isRequired() {
64            return (flags & PORT_REQUIRED) != 0;
65        }
66
67        public String toString(String ioMode, String name) {
68            String ioName = ioMode + " " + name;
69            String modeName = isRequired() ? "required" : "optional";
70            return modeName + " " + ioName + ": " + type.toString();
71        }
72    }
73
74    /** Indicates that the port must be connected in the graph. */
75    public static final int PORT_REQUIRED = 0x02;
76    /** Indicates that the port may be connected in the graph . */
77    public static final int PORT_OPTIONAL = 0x01;
78
79    /**
80     * Creates a new empty Signature.
81     */
82    public Signature() {
83    }
84
85    /**
86     * Adds an input port to the Signature.
87     *
88     * @param name the name of the input port. Must be unique among input port names.
89     * @param flags a combination of port flags.
90     * @param type the type of the input frame.
91     * @return this Signature instance.
92     */
93    public Signature addInputPort(String name, int flags, FrameType type) {
94        addInputPort(name, new PortInfo(flags, type));
95        return this;
96    }
97
98    /**
99     * Adds an output port to the Signature.
100     *
101     * @param name the name of the output port. Must be unique among output port names.
102     * @param flags a combination of port flags.
103     * @param type the type of the output frame.
104     * @return this Signature instance.
105     */
106    public Signature addOutputPort(String name, int flags, FrameType type) {
107        addOutputPort(name, new PortInfo(flags, type));
108        return this;
109    }
110
111    /**
112     * Disallows the user from adding any other input ports.
113     * Adding any input port not explicitly specified in this Signature will cause an error.
114     * @return this Signature instance.
115     */
116    public Signature disallowOtherInputs() {
117        mAllowOtherInputs = false;
118        return this;
119    }
120
121    /**
122     * Disallows the user from adding any other output ports.
123     * Adding any output port not explicitly specified in this Signature will cause an error.
124     * @return this Signature instance.
125     */
126    public Signature disallowOtherOutputs() {
127        mAllowOtherOutputs = false;
128        return this;
129    }
130
131    /**
132     * Disallows the user from adding any other ports.
133     * Adding any input or output port not explicitly specified in this Signature will cause an
134     * error.
135     * @return this Signature instance.
136     */
137    public Signature disallowOtherPorts() {
138        mAllowOtherInputs = false;
139        mAllowOtherOutputs = false;
140        return this;
141    }
142
143    @Override
144    public String toString() {
145        StringBuffer stringBuffer = new StringBuffer();
146        for (Entry<String, PortInfo> entry : mInputPorts.entrySet()) {
147            stringBuffer.append(entry.getValue().toString("input", entry.getKey()) + "\n");
148        }
149        for (Entry<String, PortInfo> entry : mOutputPorts.entrySet()) {
150            stringBuffer.append(entry.getValue().toString("output", entry.getKey()) + "\n");
151        }
152        if (!mAllowOtherInputs) {
153            stringBuffer.append("disallow other inputs\n");
154        }
155        if (!mAllowOtherOutputs) {
156            stringBuffer.append("disallow other outputs\n");
157        }
158        return stringBuffer.toString();
159    }
160
161    PortInfo getInputPortInfo(String name) {
162        PortInfo result = mInputPorts != null ? mInputPorts.get(name) : null;
163        return result != null ? result : new PortInfo();
164    }
165
166    PortInfo getOutputPortInfo(String name) {
167        PortInfo result = mOutputPorts != null ? mOutputPorts.get(name) : null;
168        return result != null ? result : new PortInfo();
169    }
170
171    void checkInputPortsConform(Filter filter) {
172        Set<String> filterInputs = new HashSet<String>();
173        filterInputs.addAll(filter.getConnectedInputPortMap().keySet());
174        if (mInputPorts != null) {
175            for (Entry<String, PortInfo> entry : mInputPorts.entrySet()) {
176                String portName = entry.getKey();
177                PortInfo portInfo = entry.getValue();
178                InputPort inputPort = filter.getConnectedInputPort(portName);
179                if (inputPort == null && portInfo.isRequired()) {
180                    throw new RuntimeException("Filter " + filter + " does not have required "
181                        + "input port '" + portName + "'!");
182                }
183                filterInputs.remove(portName);
184            }
185        }
186        if (!mAllowOtherInputs && !filterInputs.isEmpty()) {
187            throw new RuntimeException("Filter " + filter + " has invalid input ports: "
188                + filterInputs + "!");
189        }
190    }
191
192    void checkOutputPortsConform(Filter filter) {
193        Set<String> filterOutputs = new HashSet<String>();
194        filterOutputs.addAll(filter.getConnectedOutputPortMap().keySet());
195        if (mOutputPorts != null) {
196            for (Entry<String, PortInfo> entry : mOutputPorts.entrySet()) {
197                String portName = entry.getKey();
198                PortInfo portInfo = entry.getValue();
199                OutputPort outputPort = filter.getConnectedOutputPort(portName);
200                if (outputPort == null && portInfo.isRequired()) {
201                    throw new RuntimeException("Filter " + filter + " does not have required "
202                        + "output port '" + portName + "'!");
203                }
204                filterOutputs.remove(portName);
205            }
206        }
207        if (!mAllowOtherOutputs && !filterOutputs.isEmpty()) {
208            throw new RuntimeException("Filter " + filter + " has invalid output ports: "
209                + filterOutputs + "!");
210        }
211    }
212
213    HashMap<String, PortInfo> getInputPorts() {
214        return mInputPorts;
215    }
216
217    HashMap<String, PortInfo> getOutputPorts() {
218        return mOutputPorts;
219    }
220
221    private void addInputPort(String name, PortInfo portInfo) {
222        if (mInputPorts == null) {
223            mInputPorts = new HashMap<String, PortInfo>();
224        }
225        if (mInputPorts.containsKey(name)) {
226            throw new RuntimeException("Attempting to add duplicate input port '" + name + "'!");
227        }
228        mInputPorts.put(name, portInfo);
229    }
230
231    private void addOutputPort(String name, PortInfo portInfo) {
232        if (mOutputPorts == null) {
233            mOutputPorts = new HashMap<String, PortInfo>();
234        }
235        if (mOutputPorts.containsKey(name)) {
236            throw new RuntimeException("Attempting to add duplicate output port '" + name + "'!");
237        }
238        mOutputPorts.put(name, portInfo);
239    }
240}
241
242