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 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  */
36 public 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 {
onFrameReceived(InputPort port, Frame frame)48         public void onFrameReceived(InputPort port, Frame frame);
49     }
50 
51     private class FieldBinding implements FrameListener {
52         private Field mField;
53 
FieldBinding(Field field)54         public FieldBinding(Field field) {
55             mField = field;
56         }
57 
58         @Override
onFrameReceived(InputPort port, Frame frame)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      */
attachToOutputPort(OutputPort outputPort)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      */
bindToListener(FrameListener listener)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      */
bindToField(Field field)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      */
bindToFieldNamed(String fieldName)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      */
setAutoPullEnabled(boolean enabled)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      */
isAutoPullEnabled()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      */
pullFrame()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 
peek()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      */
isConnected()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      */
hasFrame()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      */
setWaitsForFrame(boolean wait)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      */
waitsForFrame()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      */
getName()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      */
getType()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      */
getFilter()265     public Filter getFilter() {
266         return mFilter;
267     }
268 
269     @Override
toString()270     public String toString() {
271         return mFilter.getName() + ":" + mName;
272     }
273 
274     // Internal only ///////////////////////////////////////////////////////////////////////////////
InputPort(Filter filter, String name, Signature.PortInfo info)275     InputPort(Filter filter, String name, Signature.PortInfo info) {
276         mFilter = filter;
277         mName = name;
278         mInfo = info;
279     }
280 
conditionsMet()281     boolean conditionsMet() {
282         return !mWaitForFrame || hasFrame();
283     }
284 
onOpen(FrameQueue.Builder builder)285     void onOpen(FrameQueue.Builder builder) {
286         mQueueBuilder = builder;
287         mQueueBuilder.setReadType(mInfo.type);
288         mFilter.onInputPortOpen(this);
289     }
290 
setQueue(FrameQueue queue)291     void setQueue(FrameQueue queue) {
292         mQueue = queue;
293         mQueueBuilder = null;
294     }
295 
getQueue()296     FrameQueue getQueue() {
297         return mQueue;
298     }
299 
clear()300     void clear() {
301         if (mQueue != null) {
302             mQueue.clear();
303         }
304     }
305 
assertInAttachmentStage()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 
findFieldNamed(String fieldName, Class<?> clazz)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