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 android.os.SystemClock;
20
21import java.util.ArrayList;
22import java.util.HashMap;
23import java.util.Map;
24import java.util.concurrent.atomic.AtomicBoolean;
25
26/**
27 * Filters are the processing nodes of the filter graphs.
28 *
29 * Filters may have any number of input and output ports, through which the data frames flow.
30 * TODO: More documentation on filter life-cycle, port and type checking, GL and RenderScript, ...
31 */
32public abstract class Filter {
33
34    private static class State {
35        private static final int STATE_UNPREPARED = 1;
36        private static final int STATE_PREPARED = 2;
37        private static final int STATE_OPEN = 3;
38        private static final int STATE_CLOSED = 4;
39        private static final int STATE_DESTROYED = 5;
40
41        public int current = STATE_UNPREPARED;
42
43        public synchronized boolean check(int state) {
44            return current == state;
45        }
46
47    }
48
49    private final int REQUEST_FLAG_NONE = 0;
50    private final int REQUEST_FLAG_CLOSE = 1;
51
52    private String mName;
53    private MffContext mContext;
54    private FilterGraph mFilterGraph;
55
56    private State mState = new State();
57    private int mRequests = REQUEST_FLAG_NONE;
58
59    private int mMinimumAvailableInputs = 1;
60    private int mMinimumAvailableOutputs = 1;
61
62    private int mScheduleCount = 0;
63    private long mLastScheduleTime = 0;
64
65    private boolean mIsActive = true;
66    private AtomicBoolean mIsSleeping = new AtomicBoolean(false);
67
68    private long mCurrentTimestamp = Frame.TIMESTAMP_NOT_SET;
69
70    private HashMap<String, InputPort> mConnectedInputPorts = new HashMap<String, InputPort>();
71    private HashMap<String, OutputPort> mConnectedOutputPorts = new HashMap<String, OutputPort>();
72
73    private InputPort[] mConnectedInputPortArray = null;
74    private OutputPort[] mConnectedOutputPortArray = null;
75
76    private ArrayList<Frame> mAutoReleaseFrames = new ArrayList<Frame>();
77
78
79    /**
80     * Constructs a new filter.
81     * A filter is bound to a specific MffContext. Its name can be any String value, but it must
82     * be unique within the filter graph.
83     *
84     * Note that names starting with "$" are reserved for internal use, and should not be used.
85     *
86     * @param context The MffContext in which the filter will live.
87     * @param name The name of the filter.
88     */
89    protected Filter(MffContext context, String name) {
90        mName = name;
91        mContext = context;
92    }
93
94    /**
95     * Checks whether the filter class is available on this platform.
96     * Some filters may not be installed on all platforms and can therefore not be instantiated.
97     * Before instantiating a filter, check if it is available by using this method.
98     *
99     * This method uses the shared FilterFactory to check whether the filter class is available.
100     *
101     * @param filterClassName The fully qualified class name of the Filter class.
102     * @return true, if filters of the specified class name are available.
103     */
104    public static final boolean isAvailable(String filterClassName) {
105        return FilterFactory.sharedFactory().isFilterAvailable(filterClassName);
106    }
107
108    /**
109     * Returns the name of this filter.
110     *
111     * @return the name of the filter (specified during construction).
112     */
113    public String getName() {
114        return mName;
115    }
116
117    /**
118     * Returns the signature of this filter.
119     *
120     * Subclasses should override this and return their filter signature. The default
121     * implementation returns a generic signature with no constraints.
122     *
123     * This method may be called at any time.
124     *
125     * @return the Signature instance for this filter.
126     */
127    public Signature getSignature() {
128        return new Signature();
129    }
130
131    /**
132     * Returns the MffContext that the filter resides in.
133     *
134     * @return the MffContext of the filter.
135     */
136    public MffContext getContext() {
137        return mContext;
138    }
139
140    /**
141     * Returns true, if the filter is active.
142     * TODO: thread safety?
143     *
144     * @return true, if the filter is active.
145     */
146    public boolean isActive() {
147        return mIsActive;
148    }
149
150    /**
151     * Activates the current filter.
152     * Only active filters can be scheduled for execution. This method can only be called if the
153     * GraphRunner that is executing the filter is stopped or paused.
154     */
155    public void activate() {
156        assertIsPaused();
157        if (!mIsActive) {
158            mIsActive = true;
159        }
160    }
161
162    /**
163     * Deactivates the current filter.
164     * Only active filters can be scheduled for execution. This method can only be called if the
165     * GraphRunner that is executing the filter is stopped or paused.
166     */
167    public void deactivate() {
168        // TODO: Support close-on-deactivate (must happen in processing thread).
169        assertIsPaused();
170        if (mIsActive) {
171            mIsActive = false;
172        }
173    }
174
175    /**
176     * Returns the filter's set of input ports.
177     * Note that this contains only the *connected* input ports. To retrieve all
178     * input ports that this filter accepts, one has to go via the filter's Signature.
179     *
180     * @return An array containing all connected input ports.
181     */
182    public final InputPort[] getConnectedInputPorts() {
183        return mConnectedInputPortArray;
184    }
185
186    /**
187     * Returns the filter's set of output ports.
188     * Note that this contains only the *connected* output ports. To retrieve all
189     * output ports that this filter provides, one has to go via the filter's Signature.
190     *
191     * @return An array containing all connected output ports.
192     */
193    public final OutputPort[] getConnectedOutputPorts() {
194        return mConnectedOutputPortArray;
195    }
196
197    /**
198     * Returns the input port with the given name.
199     * Note that this can only access the *connected* input ports. To retrieve all
200     * input ports that this filter accepts, one has to go via the filter's Signature.
201     *
202     * @return the input port with the specified name, or null if no connected input port
203     *  with this name exists.
204     */
205    public final InputPort getConnectedInputPort(String name) {
206        return mConnectedInputPorts.get(name);
207    }
208
209    /**
210     * Returns the output port with the given name.
211     * Note that this can only access the *connected* output ports. To retrieve all
212     * output ports that this filter provides, one has to go via the filter's Signature.
213     *
214     * @return the output port with the specified name, or null if no connected output port
215     *  with this name exists.
216     */
217    public final OutputPort getConnectedOutputPort(String name) {
218        return mConnectedOutputPorts.get(name);
219    }
220
221    /**
222     * Called when an input port has been attached in the graph.
223     * Override this method, in case you want to be informed of any connected input ports, or make
224     * modifications to them. Note that you may not assume that any other ports have been attached
225     * already. If you have dependencies on other ports, override
226     * {@link #onInputPortOpen(InputPort)}. The default implementation does nothing.
227     *
228     * @param port The InputPort instance that was attached.
229     */
230    protected void onInputPortAttached(InputPort port) {
231    }
232
233    /**
234     * Called when an output port has been attached in the graph.
235     * Override this method, in case you want to be informed of any connected output ports, or make
236     * modifications to them. Note that you may not assume that any other ports have been attached
237     * already. If you have dependencies on other ports, override
238     * {@link #onOutputPortOpen(OutputPort)}. The default implementation does nothing.
239     *
240     * @param port The OutputPort instance that was attached.
241     */
242    protected void onOutputPortAttached(OutputPort port) {
243    }
244
245    /**
246     * Called when an input port is opened on this filter.
247     * Input ports are opened by the data produce, that is the filter that is connected to an
248     * input port. Override this if you need to make modifications to the port before processing
249     * begins. Note, that this is only called if the connected filter is scheduled. You may assume
250     * that all ports are attached when this is called.
251     *
252     * @param port The InputPort instance that was opened.
253     */
254    protected void onInputPortOpen(InputPort port) {
255    }
256
257    /**
258     * Called when an output port is opened on this filter.
259     * Output ports are opened when the filter they are attached to is opened. Override this if you
260     * need to make modifications to the port before processing begins. Note, that this is only
261     * called if the filter is scheduled. You may assume that all ports are attached when this is
262     * called.
263     *
264     * @param port The OutputPort instance that was opened.
265     */
266    protected void onOutputPortOpen(OutputPort port) {
267    }
268
269    /**
270     * Returns true, if the filter is currently open.
271     * @return true, if the filter is currently open.
272     */
273    public final boolean isOpen() {
274        return mState.check(State.STATE_OPEN);
275    }
276
277    @Override
278    public String toString() {
279        return mName + " (" + getClass().getSimpleName() + ")";
280    }
281
282    /**
283     * Called when filter is prepared.
284     * Subclasses can override this to prepare the filter for processing. This method gets called
285     * once only just before the filter is scheduled for processing the first time.
286     *
287     * @see #onTearDown()
288     */
289    protected void onPrepare() {
290    }
291
292    /**
293     * Called when the filter is opened.
294     * Subclasses can override this to perform any kind of initialization just before processing
295     * starts. This method may be called any number of times, but is always balanced with an
296     * {@link #onClose()} call.
297     *
298     * @see #onClose()
299     */
300    protected void onOpen() {
301    }
302
303    /**
304     * Called to perform processing on Frame data.
305     * This is the only method subclasses must override. It is called every time the filter is
306     * ready for processing. Typically this is when there is input data to process and available
307     * output ports, but may differ depending on the port configuration.
308     */
309    protected abstract void onProcess();
310
311    /**
312     * Called when the filter is closed.
313     * Subclasses can override this to perform any kind of post-processing steps. Processing will
314     * not resume until {@link #onOpen()} is called again. This method is only called if the filter
315     * is open.
316     *
317     * @see #onOpen()
318     */
319    protected void onClose() {
320    }
321
322    /**
323     * Called when the filter is torn down.
324     * Subclasses can override this to perform clean-up tasks just before the filter is disposed of.
325     * It is called when the filter graph that the filter belongs to is disposed.
326     *
327     * @see #onPrepare()
328     */
329    protected void onTearDown() {
330    }
331
332    /**
333     * Check if the input conditions are met in order to schedule this filter.
334     *
335     * This is used by {@link #canSchedule()} to determine if the input-port conditions given by
336     * the filter are met. Subclasses that override scheduling behavior can make use of this
337     * function.
338     *
339     * @return true, if the filter's input conditions are met.
340     */
341    protected boolean inputConditionsMet() {
342        if (mConnectedInputPortArray.length > 0) {
343            int inputFrames = 0;
344            // [Non-iterator looping]
345            for (int i = 0; i < mConnectedInputPortArray.length; ++i) {
346                if (!mConnectedInputPortArray[i].conditionsMet()) {
347                    return false;
348                } else if (mConnectedInputPortArray[i].hasFrame()) {
349                    ++inputFrames;
350                }
351            }
352            if (inputFrames < mMinimumAvailableInputs) {
353                return false;
354            }
355        }
356        return true;
357    }
358
359    /**
360     * Check if the output conditions are met in order to schedule this filter.
361     *
362     * This is used by {@link #canSchedule()} to determine if the output-port conditions given by
363     * the filter are met. Subclasses that override scheduling behavior can make use of this
364     * function.
365     *
366     * @return true, if the filter's output conditions are met.
367     */
368    protected boolean outputConditionsMet() {
369        if (mConnectedOutputPortArray.length > 0) {
370            int availableOutputs = 0;
371            for (int i = 0; i < mConnectedOutputPortArray.length; ++i) {
372                if (!mConnectedOutputPortArray[i].conditionsMet()) {
373                    return false;
374                } else if (mConnectedOutputPortArray[i].isAvailable()) {
375                    ++availableOutputs;
376                }
377            }
378            if (availableOutputs < mMinimumAvailableOutputs) {
379                return false;
380            }
381        }
382        return true;
383    }
384
385    /**
386     * Check if the Filter is in a state so that it can be scheduled.
387     *
388     * When overriding the filter's {@link #canSchedule()} method, you should never allow
389     * scheduling a filter that is not in a schedulable state. This will result in undefined
390     * behavior.
391     *
392     * @return true, if the filter is in a schedulable state.
393     */
394    protected boolean inSchedulableState() {
395        return (mIsActive && !mState.check(State.STATE_CLOSED));
396    }
397
398    /**
399     * Returns true if the filter can be currently scheduled.
400     *
401     * Filters may override this method if they depend on custom factors that determine whether
402     * they can be scheduled or not. The scheduler calls this method to determine whether or not
403     * a filter can be scheduled for execution. It does not guarantee that it will be executed.
404     * It is strongly recommended to call super's implementation to make sure your filter can be
405     * scheduled based on its state, input and output ports.
406     *
407     * @return true, if the filter can be scheduled.
408     */
409    protected boolean canSchedule() {
410        return inSchedulableState() && inputConditionsMet() && outputConditionsMet();
411    }
412
413    /**
414     * Returns the current FrameManager instance.
415     * @return the current FrameManager instance or null if there is no FrameManager set up yet.
416     */
417    protected final FrameManager getFrameManager() {
418        return mFilterGraph.mRunner != null ? mFilterGraph.mRunner.getFrameManager() : null;
419    }
420
421    /**
422     * Returns whether the GraphRunner for this filter is running.
423     *
424     * Generally, this method should not be used for performing operations that need to be carried
425     * out before running begins. Use {@link #performPreparation(Runnable)} for this.
426     *
427     * @return true, if the GraphRunner for this filter is running.
428     */
429    protected final boolean isRunning() {
430        return mFilterGraph != null && mFilterGraph.mRunner != null
431                && mFilterGraph.mRunner.isRunning();
432    }
433
434    /**
435     * Performs operations before the filter is running.
436     *
437     * Use this method when your filter requires to perform operations while the graph is not
438     * running. The filter will not be scheduled for execution until your method has completed
439     * execution.
440     */
441    protected final boolean performPreparation(Runnable runnable) {
442        synchronized (mState) {
443            if (mState.current == State.STATE_OPEN) {
444                return false;
445            } else {
446                runnable.run();
447                return true;
448            }
449        }
450    }
451
452    /**
453     * Request that this filter be closed after the current processing step.
454     *
455     * Implementations may call this within their {@link #onProcess()} calls to indicate that the
456     * filter is done processing and wishes to be closed. After such a request the filter will be
457     * closed and no longer receive {@link #onProcess()} calls.
458     *
459     * @see #onClose()
460     * @see #onProcess()
461     */
462    protected final void requestClose() {
463        mRequests |= REQUEST_FLAG_CLOSE;
464    }
465
466    /**
467     * Sets the minimum number of input frames required to process.
468     * A filter will not be scheduled unless at least a certain number of input frames are available
469     * on the input ports. This is only relevant if the filter has input ports and is not waiting on
470     * all ports.
471     * The default value is 1.
472     *
473     * @param count the minimum number of frames required to process.
474     * @see #getMinimumAvailableInputs()
475     * @see #setMinimumAvailableOutputs(int)
476     * @see InputPort#setWaitsForFrame(boolean)
477     */
478    protected final void setMinimumAvailableInputs(int count) {
479        mMinimumAvailableInputs = count;
480    }
481
482    /**
483     * Returns the minimum number of input frames required to process this filter.
484     * The default value is 1.
485     *
486     * @return the minimum number of input frames required to process.
487     * @see #setMinimumAvailableInputs(int)
488     */
489    protected final int getMinimumAvailableInputs() {
490        return mMinimumAvailableInputs;
491    }
492
493    /**
494     * Sets the minimum number of available output ports required to process.
495     * A filter will not be scheduled unless atleast a certain number of output ports are available.
496     * This is only relevant if the filter has output ports and is not waiting on all ports. The
497     * default value is 1.
498     *
499     * @param count the minimum number of frames required to process.
500     * @see #getMinimumAvailableOutputs()
501     * @see #setMinimumAvailableInputs(int)
502     * @see OutputPort#setWaitsUntilAvailable(boolean)
503     */
504    protected final void setMinimumAvailableOutputs(int count) {
505        mMinimumAvailableOutputs = count;
506    }
507
508    /**
509     * Returns the minimum number of available outputs required to process this filter.
510     * The default value is 1.
511     *
512     * @return the minimum number of available outputs required to process.
513     * @see #setMinimumAvailableOutputs(int)
514     */
515    protected final int getMinimumAvailableOutputs() {
516        return mMinimumAvailableOutputs;
517    }
518
519    /**
520     * Puts the filter to sleep so that it is no longer scheduled.
521     * To resume scheduling the filter another thread must call wakeUp() on this filter.
522     */
523    protected final void enterSleepState() {
524        mIsSleeping.set(true);
525    }
526
527    /**
528     * Wakes the filter and resumes scheduling.
529     * This is generally called from another thread to signal that this filter should resume
530     * processing. Does nothing if filter is not sleeping.
531     */
532    protected final void wakeUp() {
533        if (mIsSleeping.getAndSet(false)) {
534            if (isRunning()) {
535                mFilterGraph.mRunner.signalWakeUp();
536            }
537        }
538    }
539
540    /**
541     * Returns whether this Filter is allowed to use OpenGL.
542     *
543     * Filters may use OpenGL if the MffContext supports OpenGL and its GraphRunner allows it.
544     *
545     * @return true, if this Filter is allowed to use OpenGL.
546     */
547   protected final boolean isOpenGLSupported() {
548        return mFilterGraph.mRunner.isOpenGLSupported();
549    }
550
551    /**
552     * Connect an output port to an input port of another filter.
553     * Connects the output port with the specified name to the input port with the specified name
554     * of the specified filter. If the input or output ports do not exist already, they are
555     * automatically created and added to the respective filter.
556     */
557    final void connect(String outputName, Filter targetFilter, String inputName) {
558        // Make sure not connected already
559        if (getConnectedOutputPort(outputName) != null) {
560            throw new RuntimeException("Attempting to connect already connected output port '"
561                + outputName + "' of filter " + this + "'!");
562        } else if (targetFilter.getConnectedInputPort(inputName) != null) {
563            throw new RuntimeException("Attempting to connect already connected input port '"
564                + inputName + "' of filter " + targetFilter + "'!");
565        }
566
567        // Establish connection
568        InputPort inputPort = targetFilter.newInputPort(inputName);
569        OutputPort outputPort = newOutputPort(outputName);
570        outputPort.setTarget(inputPort);
571
572        // Fire attachment callbacks
573        targetFilter.onInputPortAttached(inputPort);
574        onOutputPortAttached(outputPort);
575
576        // Update array of ports (which is maintained for more efficient access)
577        updatePortArrays();
578    }
579
580    final Map<String, InputPort> getConnectedInputPortMap() {
581        return mConnectedInputPorts;
582    }
583
584    final Map<String, OutputPort> getConnectedOutputPortMap() {
585        return mConnectedOutputPorts;
586    }
587
588    final void execute() {
589        synchronized (mState) {
590            autoPullInputs();
591            mLastScheduleTime = SystemClock.elapsedRealtime();
592            if (mState.current == State.STATE_UNPREPARED) {
593                onPrepare();
594                mState.current = State.STATE_PREPARED;
595            }
596            if (mState.current == State.STATE_PREPARED) {
597                openPorts();
598                onOpen();
599                mState.current = State.STATE_OPEN;
600            }
601            if (mState.current == State.STATE_OPEN) {
602                onProcess();
603                if (mRequests != REQUEST_FLAG_NONE) {
604                    processRequests();
605                }
606            }
607        }
608        autoReleaseFrames();
609        ++mScheduleCount;
610    }
611
612    final void performClose() {
613        synchronized (mState) {
614            if (mState.current == State.STATE_OPEN) {
615                onClose();
616                mIsSleeping.set(false);
617                mState.current = State.STATE_CLOSED;
618                mCurrentTimestamp = Frame.TIMESTAMP_NOT_SET;
619            }
620        }
621    }
622
623    final void softReset() {
624        synchronized (mState) {
625            performClose();
626            if (mState.current == State.STATE_CLOSED) {
627                mState.current = State.STATE_PREPARED;
628            }
629        }
630    }
631
632    final void performTearDown() {
633        synchronized (mState) {
634            if (mState.current == State.STATE_OPEN) {
635                throw new RuntimeException("Attempting to tear-down filter " + this + " which is "
636                    + "in an open state!");
637            } else if (mState.current != State.STATE_DESTROYED
638                    && mState.current != State.STATE_UNPREPARED) {
639                onTearDown();
640                mState.current = State.STATE_DESTROYED;
641            }
642        }
643    }
644
645    final void insertIntoFilterGraph(FilterGraph graph) {
646        mFilterGraph = graph;
647        updatePortArrays();
648    }
649
650    final int getScheduleCount() {
651        return mScheduleCount;
652    }
653
654    final void resetScheduleCount() {
655        mScheduleCount = 0;
656    }
657
658    final void openPorts() {
659        // Opening the output ports will open the connected input ports
660        for (OutputPort outputPort : mConnectedOutputPorts.values()) {
661            openOutputPort(outputPort);
662        }
663    }
664
665    final void addAutoReleaseFrame(Frame frame) {
666        mAutoReleaseFrames.add(frame);
667    }
668
669    final long getCurrentTimestamp() {
670        return mCurrentTimestamp;
671    }
672
673    final void onPulledFrameWithTimestamp(long timestamp) {
674        if (timestamp > mCurrentTimestamp || mCurrentTimestamp == Frame.TIMESTAMP_NOT_SET) {
675            mCurrentTimestamp = timestamp;
676        }
677    }
678
679    final void openOutputPort(OutputPort outPort) {
680        if (outPort.getQueue() == null) {
681            try {
682                FrameQueue.Builder builder = new FrameQueue.Builder();
683                InputPort inPort = outPort.getTarget();
684                outPort.onOpen(builder);
685                inPort.onOpen(builder);
686                Filter targetFilter = inPort.getFilter();
687                String queueName = mName + "[" + outPort.getName() + "] -> " + targetFilter.mName
688                        + "[" + inPort.getName() + "]";
689                FrameQueue queue = builder.build(queueName);
690                outPort.setQueue(queue);
691                inPort.setQueue(queue);
692            } catch (RuntimeException e) {
693                throw new RuntimeException("Could not open output port " + outPort + "!", e);
694            }
695        }
696    }
697
698    final boolean isSleeping() {
699        return mIsSleeping.get();
700    }
701
702    final long getLastScheduleTime() {
703        return mLastScheduleTime ;
704    }
705
706    private final void autoPullInputs() {
707        // [Non-iterator looping]
708        for (int i = 0; i < mConnectedInputPortArray.length; ++i) {
709            InputPort port = mConnectedInputPortArray[i];
710            if (port.hasFrame() && port.isAutoPullEnabled()) {
711                mConnectedInputPortArray[i].pullFrame();
712            }
713        }
714    }
715
716    private final void autoReleaseFrames() {
717        // [Non-iterator looping]
718        for (int i = 0; i < mAutoReleaseFrames.size(); ++i) {
719            mAutoReleaseFrames.get(i).release();
720        }
721        mAutoReleaseFrames.clear();
722    }
723
724    private final InputPort newInputPort(String name) {
725        InputPort result = mConnectedInputPorts.get(name);
726        if (result == null) {
727            Signature.PortInfo info = getSignature().getInputPortInfo(name);
728            result = new InputPort(this, name, info);
729            mConnectedInputPorts.put(name, result);
730        }
731        return result;
732    }
733
734    private final OutputPort newOutputPort(String name) {
735        OutputPort result = mConnectedOutputPorts.get(name);
736        if (result == null) {
737            Signature.PortInfo info = getSignature().getOutputPortInfo(name);
738            result = new OutputPort(this, name, info);
739            mConnectedOutputPorts.put(name, result);
740        }
741        return result;
742    }
743
744    private final void processRequests() {
745        if ((mRequests & REQUEST_FLAG_CLOSE) != 0) {
746            performClose();
747            mRequests = REQUEST_FLAG_NONE;
748        }
749    }
750
751    private void assertIsPaused() {
752        GraphRunner runner = GraphRunner.current();
753        if (runner != null && !runner.isPaused() && !runner.isStopped()) {
754            throw new RuntimeException("Attempting to modify filter state while runner is "
755                + "executing. Please pause or stop the runner first!");
756        }
757    }
758
759    private final void updatePortArrays() {
760        // Copy our port-maps to arrays for faster non-iterator access
761        mConnectedInputPortArray = mConnectedInputPorts.values().toArray(new InputPort[0]);
762        mConnectedOutputPortArray = mConnectedOutputPorts.values().toArray(new OutputPort[0]);
763    }
764
765}
766
767