/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.media.filterfw; import java.lang.reflect.Field; /** * Input ports are the receiving ports of frames in a filter. *

* InputPort instances receive Frame data from connected OutputPort instances of a previous filter. * Frames flow from output ports to input ports. Filters can process frame data by calling * {@link #pullFrame()} on an input port. If the input port is set to wait for an input frame * (see {@link #setWaitsForFrame(boolean)}), there is guaranteed to be Frame on the port before * {@code onProcess()} is called. This is the default setting. Otherwise, calling * {@link #pullFrame()} may return a value of {@code null}. *

* InputPorts may be bound to fields of the Filter. When an input port is bound to a field, Frame * values will be assigned to the field once a Frame is received on that port. The Frame value must * be of a type that is compatible with the field type. *

*/ public final class InputPort { private Filter mFilter; private String mName; private Signature.PortInfo mInfo; private FrameListener mListener = null; private FrameQueue.Builder mQueueBuilder = null; private FrameQueue mQueue = null; private boolean mWaitForFrame = true; private boolean mAutoPullEnabled = false; public interface FrameListener { public void onFrameReceived(InputPort port, Frame frame); } private class FieldBinding implements FrameListener { private Field mField; public FieldBinding(Field field) { mField = field; } @Override public void onFrameReceived(InputPort port, Frame frame) { try { if(port.mInfo.type.getNumberOfDimensions() > 0) { FrameValues frameValues = frame.asFrameValues(); mField.set(mFilter, frameValues.getValues()); } else { FrameValue frameValue = frame.asFrameValue(); mField.set(mFilter, frameValue.getValue()); } } catch (Exception e) { throw new RuntimeException("Assigning frame " + frame + " to field " + mField + " of filter " + mFilter + " caused exception!", e); } } } /** * Attach this input port to an output port for frame passing. * * Use this method whenever you plan on passing a Frame through from an input port to an * output port. This must be called from inside * {@link Filter#onInputPortAttached(InputPort) onInputPortAttached}. * * @param outputPort the output port that Frames will be pushed to. */ public void attachToOutputPort(OutputPort outputPort) { assertInAttachmentStage(); mFilter.openOutputPort(outputPort); mQueueBuilder.attachQueue(outputPort.getQueue()); } /** * Bind this input port to the specified listener. * * Use this when you wish to be notified of incoming frames. The listener method * {@link FrameListener#onFrameReceived(InputPort, Frame)} will be called once a Frame is pulled * on this port. Typically this is called from inside * {@link Filter#onInputPortAttached(InputPort) onInputPortAttached}, and used in * conjunction with {@link #setAutoPullEnabled(boolean)}. Overrides any previous bindings. * * @param listener the listener to handle incoming Frames. */ public void bindToListener(FrameListener listener) { assertInAttachmentStage(); mListener = listener; } /** * Bind this input port to the specified field. * * Use this when you wish to pull frames directly into a field of the filter. This requires * that the input frames can be interpreted as object-based frames of the field's class. * Overrides any previous bindings. * * This is typically called from inside * {@link Filter#onInputPortAttached(InputPort) onInputPortAttached}, and used in * conjunction with {@link #setAutoPullEnabled(boolean)}. * * @param field the field to pull frame data into. * @see #bindToFieldNamed(String) * @see #setAutoPullEnabled(boolean) */ public void bindToField(Field field) { assertInAttachmentStage(); mListener = new FieldBinding(field); } /** * Bind this input port to the field with the specified name. * * Use this when you wish to pull frames directly into a field of the filter. This requires * that the input frames can be interpreted as object-based frames of the field's class. * Overrides any previous bindings. * * This is typically called from inside * {@link Filter#onInputPortAttached(InputPort) onInputPortAttached}, and used in * conjunction with {@link #setAutoPullEnabled(boolean)}. * * @param fieldName the field to pull frame data into. * @see #bindToField(Field) * @see #setAutoPullEnabled(boolean) */ public void bindToFieldNamed(String fieldName) { Field field = findFieldNamed(fieldName, mFilter.getClass()); if (field == null) { throw new IllegalArgumentException("Attempting to bind to unknown field '" + fieldName + "'!"); } bindToField(field); } /** * Set whether the InputPort automatically pulls frames. * This is typically only used when the port is bound to another target. * @param enabled true, if frames should be automatically pulled on this port. */ public void setAutoPullEnabled(boolean enabled) { mAutoPullEnabled = enabled; } /** * Returns whether the InputPort automatically pulls frames. * @return true, if frames are automatically pulled on this port. */ public boolean isAutoPullEnabled() { return mAutoPullEnabled; } /** * Pull a waiting a frame from the port. * * Call this to pull a frame from the input port for processing. If no frame is waiting on the * input port, returns null. After this call the port will have no Frame waiting (empty port). * Note, that this returns a frame owned by the input queue. You must detach the frame if you * wish to hold on to it. * * @return Frame instance, or null if no frame is available for pulling. */ public synchronized Frame pullFrame() { if (mQueue == null) { throw new IllegalStateException("Cannot pull frame from closed input port!"); } Frame frame = mQueue.pullFrame(); if (frame != null) { if (mListener != null) { mListener.onFrameReceived(this, frame); } //Log.i("InputPort", "Adding frame " + frame + " to auto-release pool"); mFilter.addAutoReleaseFrame(frame); long timestamp = frame.getTimestamp(); if (timestamp != Frame.TIMESTAMP_NOT_SET) { mFilter.onPulledFrameWithTimestamp(frame.getTimestamp()); } } return frame; } public synchronized Frame peek() { if (mQueue == null) { throw new IllegalStateException("Cannot pull frame from closed input port!"); } return mQueue.peek(); } /** * Returns true, if the port is connected. * @return true, if there is an output port that connects to this port. */ public boolean isConnected() { return mQueue != null; } /** * Returns true, if there is a frame waiting on this port. * @return true, if there is a frame waiting on this port. */ public synchronized boolean hasFrame() { return mQueue != null && mQueue.canPull(); } /** * Sets whether to wait for a frame on this port before processing. * When set to true, the Filter will not be scheduled for processing unless there is a Frame * waiting on this port. The default value is true. * * @param wait true, if the Filter should wait for a Frame before processing. * @see #waitsForFrame() */ public void setWaitsForFrame(boolean wait) { mWaitForFrame = wait; } /** * Returns whether the filter waits for a frame on this port before processing. * @return true, if the filter waits for a frame on this port before processing. * @see #setWaitsForFrame(boolean) */ public boolean waitsForFrame() { return mWaitForFrame; } /** * Returns the input port's name. * This is the name that was specified when the input port was connected. * * @return the input port's name. */ public String getName() { return mName; } /** * Returns the FrameType of this port. * This is the type that was specified when the input port was declared. * * @return the input port's FrameType. */ public FrameType getType() { return getQueue().getType(); } /** * Return the filter object that this port belongs to. * * @return the input port's filter. */ public Filter getFilter() { return mFilter; } @Override public String toString() { return mFilter.getName() + ":" + mName; } // Internal only /////////////////////////////////////////////////////////////////////////////// InputPort(Filter filter, String name, Signature.PortInfo info) { mFilter = filter; mName = name; mInfo = info; } boolean conditionsMet() { return !mWaitForFrame || hasFrame(); } void onOpen(FrameQueue.Builder builder) { mQueueBuilder = builder; mQueueBuilder.setReadType(mInfo.type); mFilter.onInputPortOpen(this); } void setQueue(FrameQueue queue) { mQueue = queue; mQueueBuilder = null; } FrameQueue getQueue() { return mQueue; } void clear() { if (mQueue != null) { mQueue.clear(); } } private void assertInAttachmentStage() { if (mQueueBuilder == null) { throw new IllegalStateException("Attempting to attach port while not in attachment " + "stage!"); } } private Field findFieldNamed(String fieldName, Class clazz) { Field field = null; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true); } catch (NoSuchFieldException e) { Class superClass = clazz.getSuperclass(); if (superClass != null) { field = findFieldNamed(fieldName, superClass); } } return field; } }