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
17package androidx.media.filterfw;
18
19import java.lang.reflect.Field;
20
21/**
22 * Input ports are the receiving ports of frames in a filter.
23 * <p>
24 * InputPort instances receive Frame data from connected OutputPort instances of a previous filter.
25 * Frames flow from output ports to input ports. Filters can process frame data by calling
26 * {@link #pullFrame()} on an input port. If the input port is set to wait for an input frame
27 * (see {@link #setWaitsForFrame(boolean)}), there is guaranteed to be Frame on the port before
28 * {@code onProcess()} is called. This is the default setting. Otherwise, calling
29 * {@link #pullFrame()} may return a value of {@code null}.
30 * <p/><p>
31 * InputPorts may be bound to fields of the Filter. When an input port is bound to a field, Frame
32 * values will be assigned to the field once a Frame is received on that port. The Frame value must
33 * be of a type that is compatible with the field type.
34 * </p>
35 */
36public final class InputPort {
37
38    private Filter mFilter;
39    private String mName;
40    private Signature.PortInfo mInfo;
41    private FrameListener mListener = null;
42    private FrameQueue.Builder mQueueBuilder = null;
43    private FrameQueue mQueue = null;
44    private boolean mWaitForFrame = true;
45    private boolean mAutoPullEnabled = false;
46
47    public interface FrameListener {
48        public void onFrameReceived(InputPort port, Frame frame);
49    }
50
51    private class FieldBinding implements FrameListener {
52        private Field mField;
53
54        public FieldBinding(Field field) {
55            mField = field;
56        }
57
58        @Override
59        public void onFrameReceived(InputPort port, Frame frame) {
60            try {
61                if(port.mInfo.type.getNumberOfDimensions() > 0) {
62                    FrameValues frameValues = frame.asFrameValues();
63                    mField.set(mFilter, frameValues.getValues());
64                } else {
65                    FrameValue frameValue = frame.asFrameValue();
66                    mField.set(mFilter, frameValue.getValue());
67                }
68            } catch (Exception e) {
69                throw new RuntimeException("Assigning frame " + frame + " to field "
70                    + mField + " of filter " + mFilter + " caused exception!", e);
71            }
72        }
73    }
74
75    /**
76     * Attach this input port to an output port for frame passing.
77     *
78     * Use this method whenever you plan on passing a Frame through from an input port to an
79     * output port. This must be called from inside
80     * {@link Filter#onInputPortAttached(InputPort) onInputPortAttached}.
81     *
82     * @param outputPort the output port that Frames will be pushed to.
83     */
84    public void attachToOutputPort(OutputPort outputPort) {
85        assertInAttachmentStage();
86        mFilter.openOutputPort(outputPort);
87        mQueueBuilder.attachQueue(outputPort.getQueue());
88    }
89
90    /**
91     * Bind this input port to the specified listener.
92     *
93     * Use this when you wish to be notified of incoming frames. The listener method
94     * {@link FrameListener#onFrameReceived(InputPort, Frame)} will be called once a Frame is pulled
95     * on this port. Typically this is called from inside
96     * {@link Filter#onInputPortAttached(InputPort) onInputPortAttached}, and used in
97     * conjunction with {@link #setAutoPullEnabled(boolean)}. Overrides any previous bindings.
98     *
99     * @param listener the listener to handle incoming Frames.
100     */
101    public void bindToListener(FrameListener listener) {
102        assertInAttachmentStage();
103        mListener = listener;
104    }
105
106    /**
107     * Bind this input port to the specified field.
108     *
109     * Use this when you wish to pull frames directly into a field of the filter. This requires
110     * that the input frames can be interpreted as object-based frames of the field's class.
111     * Overrides any previous bindings.
112     *
113     * This is typically called from inside
114     * {@link Filter#onInputPortAttached(InputPort) onInputPortAttached}, and used in
115     * conjunction with {@link #setAutoPullEnabled(boolean)}.
116     *
117     * @param field the field to pull frame data into.
118     * @see #bindToFieldNamed(String)
119     * @see #setAutoPullEnabled(boolean)
120     */
121    public void bindToField(Field field) {
122        assertInAttachmentStage();
123        mListener = new FieldBinding(field);
124    }
125
126    /**
127     * Bind this input port to the field with the specified name.
128     *
129     * Use this when you wish to pull frames directly into a field of the filter. This requires
130     * that the input frames can be interpreted as object-based frames of the field's class.
131     * Overrides any previous bindings.
132     *
133     * This is typically called from inside
134     * {@link Filter#onInputPortAttached(InputPort) onInputPortAttached}, and used in
135     * conjunction with {@link #setAutoPullEnabled(boolean)}.
136     *
137     * @param fieldName the field to pull frame data into.
138     * @see #bindToField(Field)
139     * @see #setAutoPullEnabled(boolean)
140     */
141    public void bindToFieldNamed(String fieldName) {
142        Field field = findFieldNamed(fieldName, mFilter.getClass());
143        if (field == null) {
144            throw new IllegalArgumentException("Attempting to bind to unknown field '"
145                + fieldName + "'!");
146        }
147        bindToField(field);
148    }
149
150    /**
151     * Set whether the InputPort automatically pulls frames.
152     * This is typically only used when the port is bound to another target.
153     * @param enabled true, if frames should be automatically pulled on this port.
154     */
155    public void setAutoPullEnabled(boolean enabled) {
156        mAutoPullEnabled = enabled;
157    }
158
159    /**
160     * Returns whether the InputPort automatically pulls frames.
161     * @return true, if frames are automatically pulled on this port.
162     */
163    public boolean isAutoPullEnabled() {
164        return mAutoPullEnabled;
165    }
166
167    /**
168     * Pull a waiting a frame from the port.
169     *
170     * Call this to pull a frame from the input port for processing. If no frame is waiting on the
171     * input port, returns null. After this call the port will have no Frame waiting (empty port).
172     * Note, that this returns a frame owned by the input queue. You must detach the frame if you
173     * wish to hold on to it.
174     *
175     * @return Frame instance, or null if no frame is available for pulling.
176     */
177    public synchronized Frame pullFrame() {
178        if (mQueue == null) {
179            throw new IllegalStateException("Cannot pull frame from closed input port!");
180        }
181        Frame frame = mQueue.pullFrame();
182        if (frame != null) {
183            if (mListener != null) {
184                mListener.onFrameReceived(this, frame);
185            }
186            //Log.i("InputPort", "Adding frame " + frame + " to auto-release pool");
187            mFilter.addAutoReleaseFrame(frame);
188            long timestamp = frame.getTimestamp();
189            if (timestamp != Frame.TIMESTAMP_NOT_SET) {
190                mFilter.onPulledFrameWithTimestamp(frame.getTimestamp());
191            }
192        }
193        return frame;
194    }
195
196    public synchronized Frame peek() {
197        if (mQueue == null) {
198            throw new IllegalStateException("Cannot pull frame from closed input port!");
199        }
200        return mQueue.peek();
201    }
202
203    /**
204     * Returns true, if the port is connected.
205     * @return true, if there is an output port that connects to this port.
206     */
207    public boolean isConnected() {
208        return mQueue != null;
209    }
210
211    /**
212     * Returns true, if there is a frame waiting on this port.
213     * @return true, if there is a frame waiting on this port.
214     */
215    public synchronized boolean hasFrame() {
216        return mQueue != null && mQueue.canPull();
217    }
218
219    /**
220     * Sets whether to wait for a frame on this port before processing.
221     * When set to true, the Filter will not be scheduled for processing unless there is a Frame
222     * waiting on this port. The default value is true.
223     *
224     * @param wait true, if the Filter should wait for a Frame before processing.
225     * @see #waitsForFrame()
226     */
227    public void setWaitsForFrame(boolean wait) {
228        mWaitForFrame = wait;
229    }
230
231    /**
232     * Returns whether the filter waits for a frame on this port before processing.
233     * @return true, if the filter waits for a frame on this port before processing.
234     * @see #setWaitsForFrame(boolean)
235     */
236    public boolean waitsForFrame() {
237        return mWaitForFrame;
238    }
239
240    /**
241     * Returns the input port's name.
242     * This is the name that was specified when the input port was connected.
243     *
244     * @return the input port's name.
245     */
246    public String getName() {
247        return mName;
248    }
249
250    /**
251     * Returns the FrameType of this port.
252     * This is the type that was specified when the input port was declared.
253     *
254     * @return the input port's FrameType.
255     */
256    public FrameType getType() {
257        return getQueue().getType();
258    }
259
260    /**
261     * Return the filter object that this port belongs to.
262     *
263     * @return the input port's filter.
264     */
265    public Filter getFilter() {
266        return mFilter;
267    }
268
269    @Override
270    public String toString() {
271        return mFilter.getName() + ":" + mName;
272    }
273
274    // Internal only ///////////////////////////////////////////////////////////////////////////////
275    InputPort(Filter filter, String name, Signature.PortInfo info) {
276        mFilter = filter;
277        mName = name;
278        mInfo = info;
279    }
280
281    boolean conditionsMet() {
282        return !mWaitForFrame || hasFrame();
283    }
284
285    void onOpen(FrameQueue.Builder builder) {
286        mQueueBuilder = builder;
287        mQueueBuilder.setReadType(mInfo.type);
288        mFilter.onInputPortOpen(this);
289    }
290
291    void setQueue(FrameQueue queue) {
292        mQueue = queue;
293        mQueueBuilder = null;
294    }
295
296    FrameQueue getQueue() {
297        return mQueue;
298    }
299
300    void clear() {
301        if (mQueue != null) {
302            mQueue.clear();
303        }
304    }
305
306    private void assertInAttachmentStage() {
307        if (mQueueBuilder == null) {
308            throw new IllegalStateException("Attempting to attach port while not in attachment "
309                + "stage!");
310        }
311    }
312
313    private Field findFieldNamed(String fieldName, Class<?> clazz) {
314        Field field = null;
315        try {
316            field = clazz.getDeclaredField(fieldName);
317            field.setAccessible(true);
318        } catch (NoSuchFieldException e) {
319            Class<?> superClass = clazz.getSuperclass();
320            if (superClass != null) {
321                field = findFieldNamed(fieldName, superClass);
322            }
323        }
324        return field;
325    }
326}
327
328