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 { 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