diff options
Diffstat (limited to 'media/mca/filterfw')
107 files changed, 16240 insertions, 0 deletions
diff --git a/media/mca/filterfw/Android.mk b/media/mca/filterfw/Android.mk new file mode 100644 index 0000000..b822e99 --- /dev/null +++ b/media/mca/filterfw/Android.mk @@ -0,0 +1,53 @@ +# 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. +# + +##################### +# Build native sublibraries + +include $(all-subdir-makefiles) + +##################### +# Build main libfilterfw + +include $(CLEAR_VARS) + +LOCAL_MODULE := libfilterfw + +LOCAL_MODULE_TAGS := optional + +LOCAL_WHOLE_STATIC_LIBRARIES := libfilterfw_jni \ + libfilterfw_native + +LOCAL_SHARED_LIBRARIES := libstlport \ + libGLESv2 \ + libEGL \ + libgui \ + libdl \ + libcutils \ + libutils \ + libandroid \ + libjnigraphics \ + libmedia \ + libmedia_native + +# Don't prelink this library. For more efficient code, you may want +# to add this library to the prelink map and set this to true. However, +# it's difficult to do this for applications that are not supplied as +# part of a system image. +LOCAL_PRELINK_MODULE := false + +include $(BUILD_SHARED_LIBRARY) + + diff --git a/media/mca/filterfw/java/android/filterfw/FilterFunctionEnvironment.java b/media/mca/filterfw/java/android/filterfw/FilterFunctionEnvironment.java new file mode 100644 index 0000000..3f36d98 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/FilterFunctionEnvironment.java @@ -0,0 +1,99 @@ +/* + * 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 android.filterfw; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterFactory; +import android.filterfw.core.FilterFunction; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameManager; + +/** + * A FilterFunctionEnvironment provides a simple functional front-end to manually executing + * filters. Use this environment if a graph-based approach is not convenient for your case. + * Typically, a FilterFunctionEnvironment is used as follows: + * 1. Instantiate a new FilterFunctionEnvironment instance. + * 2. Perform any configuration, such as setting a GL environment. + * 3. Wrap Filters into FilterFunctions by calling createFunction(). + * 4. Execute FilterFunctions individually and use the results for further processing. + * Additionally, there is a convenience method to execute a number of filters in sequence. + * @hide + */ +public class FilterFunctionEnvironment extends MffEnvironment { + + /** + * Create a new FilterFunctionEnvironment with the default components. + */ + public FilterFunctionEnvironment() { + super(null); + } + + /** + * Create a new FilterFunctionEnvironment with a custom FrameManager. Pass null to auto-create + * a FrameManager. + * + * @param frameManager The FrameManager to use, or null to auto-create one. + */ + public FilterFunctionEnvironment(FrameManager frameManager) { + super(frameManager); + } + + /** + * Create a new FilterFunction from a specific filter class. The function is initialized with + * the given key-value list of parameters. Note, that this function uses the default shared + * FilterFactory to create the filter instance. + * + * @param filterClass The class of the filter to wrap. This must be a Filter subclass. + * @param parameters An argument list of alternating key-value filter parameters. + * @return A new FilterFunction instance. + */ + public FilterFunction createFunction(Class filterClass, Object... parameters) { + String filterName = "FilterFunction(" + filterClass.getSimpleName() + ")"; + Filter filter = FilterFactory.sharedFactory().createFilterByClass(filterClass, filterName); + filter.initWithAssignmentList(parameters); + return new FilterFunction(getContext(), filter); + } + + /** + * Convenience method to execute a sequence of filter functions. Note that every function in + * the list MUST have one input and one output port, except the first filter (which must not + * have any input ports) and the last filter (which may not have any output ports). + * + * @param functions A list of filter functions. The first filter must be a source filter. + * @return The result of the last filter executed, or null if the last filter did not + produce any output. + * + public Frame executeSequence(FilterFunction[] functions) { + Frame oldFrame = null; + Frame newFrame = null; + for (FilterFunction filterFunction : functions) { + if (oldFrame == null) { + newFrame = filterFunction.executeWithArgList(); + } else { + newFrame = filterFunction.executeWithArgList(oldFrame); + oldFrame.release(); + } + oldFrame = newFrame; + } + if (oldFrame != null) { + oldFrame.release(); + } + return newFrame; + }*/ + +} diff --git a/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java b/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java new file mode 100644 index 0000000..5f6d45c --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java @@ -0,0 +1,197 @@ +/* + * 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 android.filterfw; + +import android.content.Context; +import android.filterfw.core.AsyncRunner; +import android.filterfw.core.FilterGraph; +import android.filterfw.core.FilterContext; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GraphRunner; +import android.filterfw.core.RoundRobinScheduler; +import android.filterfw.core.SyncRunner; +import android.filterfw.io.GraphIOException; +import android.filterfw.io.GraphReader; +import android.filterfw.io.TextGraphReader; + +import java.util.ArrayList; + +/** + * A GraphEnvironment provides a simple front-end to filter graph setup and execution using the + * mobile filter framework. Typically, you use a GraphEnvironment in the following fashion: + * 1. Instantiate a new GraphEnvironment instance. + * 2. Perform any configuration, such as adding graph references and setting a GL environment. + * 3. Load a graph file using loadGraph() or add a graph using addGraph(). + * 4. Obtain a GraphRunner instance using getRunner(). + * 5. Execute the obtained runner. + * Note that it is possible to add multiple graphs and runners to a single GraphEnvironment. + * + * @hide + */ +public class GraphEnvironment extends MffEnvironment { + + public static final int MODE_ASYNCHRONOUS = 1; + public static final int MODE_SYNCHRONOUS = 2; + + private GraphReader mGraphReader; + private ArrayList<GraphHandle> mGraphs = new ArrayList<GraphHandle>(); + + private class GraphHandle { + private FilterGraph mGraph; + private AsyncRunner mAsyncRunner; + private SyncRunner mSyncRunner; + + public GraphHandle(FilterGraph graph) { + mGraph = graph; + } + + public FilterGraph getGraph() { + return mGraph; + } + + public AsyncRunner getAsyncRunner(FilterContext environment) { + if (mAsyncRunner == null) { + mAsyncRunner = new AsyncRunner(environment, RoundRobinScheduler.class); + mAsyncRunner.setGraph(mGraph); + } + return mAsyncRunner; + } + + public GraphRunner getSyncRunner(FilterContext environment) { + if (mSyncRunner == null) { + mSyncRunner = new SyncRunner(environment, mGraph, RoundRobinScheduler.class); + } + return mSyncRunner; + } + } + + /** + * Create a new GraphEnvironment with default components. + */ + public GraphEnvironment() { + super(null); + } + + /** + * Create a new GraphEnvironment with a custom FrameManager and GraphReader. Specifying null + * for either of these, will auto-create a default instance. + * + * @param frameManager The FrameManager to use, or null to auto-create one. + * @param reader The GraphReader to use for graph loading, or null to auto-create one. + * Note, that the reader will not be created until it is required. Pass + * null if you will not load any graph files. + */ + public GraphEnvironment(FrameManager frameManager, GraphReader reader) { + super(frameManager); + mGraphReader = reader; + } + + /** + * Returns the used graph reader. This will create one, if a reader has not been set already. + */ + public GraphReader getGraphReader() { + if (mGraphReader == null) { + mGraphReader = new TextGraphReader(); + } + return mGraphReader; + } + + /** + * Add graph references to resolve during graph reading. The references added here are shared + * among all graphs. + * + * @param references An alternating argument list of keys (Strings) and values. + */ + public void addReferences(Object... references) { + getGraphReader().addReferencesByKeysAndValues(references); + } + + /** + * Loads a graph file from the specified resource and adds it to this environment. + * + * @param context The context in which to read the resource. + * @param resourceId The ID of the graph resource to load. + * @return A unique ID for the graph. + */ + public int loadGraph(Context context, int resourceId) { + // Read the file into a graph + FilterGraph graph = null; + try { + graph = getGraphReader().readGraphResource(context, resourceId); + } catch (GraphIOException e) { + throw new RuntimeException("Could not read graph: " + e.getMessage()); + } + + // Add graph to our list of graphs + return addGraph(graph); + } + + /** + * Add a graph to the environment. Consider using loadGraph() if you are loading a graph from + * a graph file. + * + * @param graph The graph to add to the environment. + * @return A unique ID for the added graph. + */ + public int addGraph(FilterGraph graph) { + GraphHandle graphHandle = new GraphHandle(graph); + mGraphs.add(graphHandle); + return mGraphs.size() - 1; + } + + /** + * Access a specific graph of this environment given a graph ID (previously returned from + * loadGraph() or addGraph()). Throws an InvalidArgumentException if no graph with the + * specified ID could be found. + * + * @param graphId The ID of the graph to get. + * @return The graph with the specified ID. + */ + public FilterGraph getGraph(int graphId) { + if (graphId < 0 || graphId >= mGraphs.size()) { + throw new IllegalArgumentException( + "Invalid graph ID " + graphId + " specified in runGraph()!"); + } + return mGraphs.get(graphId).getGraph(); + } + + /** + * Get a GraphRunner instance for the graph with the specified ID. The GraphRunner instance can + * be used to execute the graph. Throws an InvalidArgumentException if no graph with the + * specified ID could be found. + * + * @param graphId The ID of the graph to get. + * @param executionMode The mode of graph execution. Currently this can be either + MODE_SYNCHRONOUS or MODE_ASYNCHRONOUS. + * @return A GraphRunner instance for this graph. + */ + public GraphRunner getRunner(int graphId, int executionMode) { + switch (executionMode) { + case MODE_ASYNCHRONOUS: + return mGraphs.get(graphId).getAsyncRunner(getContext()); + + case MODE_SYNCHRONOUS: + return mGraphs.get(graphId).getSyncRunner(getContext()); + + default: + throw new RuntimeException( + "Invalid execution mode " + executionMode + " specified in getRunner()!"); + } + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/MffEnvironment.java b/media/mca/filterfw/java/android/filterfw/MffEnvironment.java new file mode 100644 index 0000000..1ab416a --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/MffEnvironment.java @@ -0,0 +1,106 @@ +/* + * 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 android.filterfw; + +import android.filterfw.core.CachedFrameManager; +import android.filterfw.core.FilterContext; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GLEnvironment; + +/** + * Base class for mobile filter framework (MFF) frontend environments. These convenience classes + * allow using the filter framework without the requirement of performing manual setup of its + * required components. + * + * @hide + */ +public class MffEnvironment { + + private FilterContext mContext; + + /** + * Protected constructor to initialize the environment's essential components. These are the + * frame-manager and the filter-context. Passing in null for the frame-manager causes this + * to be auto-created. + * + * @param frameManager The FrameManager to use or null to auto-create one. + */ + protected MffEnvironment(FrameManager frameManager) { + // Get or create the frame manager + if (frameManager == null) { + frameManager = new CachedFrameManager(); + } + + // Setup the environment + mContext = new FilterContext(); + mContext.setFrameManager(frameManager); + + } + + /** + * Returns the environment's filter-context. + */ + public FilterContext getContext() { + return mContext; + } + + /** + * Set the environment's GL environment to the specified environment. This does not activate + * the environment. + */ + public void setGLEnvironment(GLEnvironment glEnvironment) { + mContext.initGLEnvironment(glEnvironment); + } + + /** + * Create and activate a new GL environment for use in this filter context. + */ + public void createGLEnvironment() { + GLEnvironment glEnvironment = new GLEnvironment(); + glEnvironment.initWithNewContext(); + setGLEnvironment(glEnvironment); + } + + /** + * Activate the GL environment for use in the current thread. A GL environment must have been + * previously set or created using setGLEnvironment() or createGLEnvironment()! Call this after + * having switched to a new thread for GL filter execution. + */ + public void activateGLEnvironment() { + GLEnvironment glEnv = mContext.getGLEnvironment(); + if (glEnv != null) { + mContext.getGLEnvironment().activate(); + } else { + throw new NullPointerException("No GLEnvironment in place to activate!"); + } + } + + /** + * Deactivate the GL environment from use in the current thread. A GL environment must have been + * previously set or created using setGLEnvironment() or createGLEnvironment()! Call this before + * running GL filters in another thread. + */ + public void deactivateGLEnvironment() { + GLEnvironment glEnv = mContext.getGLEnvironment(); + if (glEnv != null) { + mContext.getGLEnvironment().deactivate(); + } else { + throw new NullPointerException("No GLEnvironment in place to deactivate!"); + } + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/AsyncRunner.java b/media/mca/filterfw/java/android/filterfw/core/AsyncRunner.java new file mode 100644 index 0000000..70cbad4 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/AsyncRunner.java @@ -0,0 +1,247 @@ +/* + * 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 android.filterfw.core; + +import android.os.AsyncTask; +import android.os.Handler; + +import android.util.Log; + +import java.lang.InterruptedException; +import java.lang.Runnable; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.TimeUnit; + +/** + * @hide + */ +public class AsyncRunner extends GraphRunner{ + + private Class mSchedulerClass; + private SyncRunner mRunner; + private AsyncRunnerTask mRunTask; + + private OnRunnerDoneListener mDoneListener; + private boolean isProcessing; + + private Exception mException; + + private class RunnerResult { + public int status = RESULT_UNKNOWN; + public Exception exception; + } + + private class AsyncRunnerTask extends AsyncTask<SyncRunner, Void, RunnerResult> { + + private static final String TAG = "AsyncRunnerTask"; + + @Override + protected RunnerResult doInBackground(SyncRunner... runner) { + RunnerResult result = new RunnerResult(); + try { + if (runner.length > 1) { + throw new RuntimeException("More than one runner received!"); + } + + runner[0].assertReadyToStep(); + + // Preparation + if (mLogVerbose) Log.v(TAG, "Starting background graph processing."); + activateGlContext(); + + if (mLogVerbose) Log.v(TAG, "Preparing filter graph for processing."); + runner[0].beginProcessing(); + + if (mLogVerbose) Log.v(TAG, "Running graph."); + + // Run loop + result.status = RESULT_RUNNING; + while (!isCancelled() && result.status == RESULT_RUNNING) { + if (!runner[0].performStep()) { + result.status = runner[0].determinePostRunState(); + if (result.status == GraphRunner.RESULT_SLEEPING) { + runner[0].waitUntilWake(); + result.status = RESULT_RUNNING; + } + } + } + + // Cleanup + if (isCancelled()) { + result.status = RESULT_STOPPED; + } + } catch (Exception exception) { + result.exception = exception; + result.status = RESULT_ERROR; + } + + // Deactivate context. + try { + deactivateGlContext(); + } catch (Exception exception) { + result.exception = exception; + result.status = RESULT_ERROR; + } + + if (mLogVerbose) Log.v(TAG, "Done with background graph processing."); + return result; + } + + @Override + protected void onCancelled(RunnerResult result) { + onPostExecute(result); + } + + @Override + protected void onPostExecute(RunnerResult result) { + if (mLogVerbose) Log.v(TAG, "Starting post-execute."); + setRunning(false); + if (result == null) { + // Cancelled before got to doInBackground + result = new RunnerResult(); + result.status = RESULT_STOPPED; + } + setException(result.exception); + if (result.status == RESULT_STOPPED || result.status == RESULT_ERROR) { + if (mLogVerbose) Log.v(TAG, "Closing filters."); + try { + mRunner.close(); + } catch (Exception exception) { + result.status = RESULT_ERROR; + setException(exception); + } + } + if (mDoneListener != null) { + if (mLogVerbose) Log.v(TAG, "Calling graph done callback."); + mDoneListener.onRunnerDone(result.status); + } + if (mLogVerbose) Log.v(TAG, "Completed post-execute."); + } + } + + private boolean mLogVerbose; + private static final String TAG = "AsyncRunner"; + + /** Create a new asynchronous graph runner with the given filter + * context, and the given scheduler class. + * + * Must be created on the UI thread. + */ + public AsyncRunner(FilterContext context, Class schedulerClass) { + super(context); + + mSchedulerClass = schedulerClass; + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + /** Create a new asynchronous graph runner with the given filter + * context. Uses a default scheduler. + * + * Must be created on the UI thread. + */ + public AsyncRunner(FilterContext context) { + super(context); + + mSchedulerClass = SimpleScheduler.class; + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + /** Set a callback to be called in the UI thread once the AsyncRunner + * completes running a graph, whether the completion is due to a stop() call + * or the filters running out of data to process. + */ + @Override + public void setDoneCallback(OnRunnerDoneListener listener) { + mDoneListener = listener; + } + + /** Sets the graph to be run. Will call prepare() on graph. Cannot be called + * when a graph is already running. + */ + synchronized public void setGraph(FilterGraph graph) { + if (isRunning()) { + throw new RuntimeException("Graph is already running!"); + } + mRunner = new SyncRunner(mFilterContext, graph, mSchedulerClass); + } + + @Override + public FilterGraph getGraph() { + return mRunner != null ? mRunner.getGraph() : null; + } + + /** Execute the graph in a background thread. */ + @Override + synchronized public void run() { + if (mLogVerbose) Log.v(TAG, "Running graph."); + setException(null); + + if (isRunning()) { + throw new RuntimeException("Graph is already running!"); + } + if (mRunner == null) { + throw new RuntimeException("Cannot run before a graph is set!"); + } + mRunTask = this.new AsyncRunnerTask(); + + setRunning(true); + mRunTask.execute(mRunner); + } + + /** Stop graph execution. This is an asynchronous call; register a callback + * with setDoneCallback to be notified of when the background processing has + * been completed. Calling stop will close the filter graph. */ + @Override + synchronized public void stop() { + if (mRunTask != null && !mRunTask.isCancelled() ) { + if (mLogVerbose) Log.v(TAG, "Stopping graph."); + mRunTask.cancel(false); + } + } + + @Override + synchronized public void close() { + if (isRunning()) { + throw new RuntimeException("Cannot close graph while it is running!"); + } + if (mLogVerbose) Log.v(TAG, "Closing filters."); + mRunner.close(); + } + + /** Check if background processing is happening */ + @Override + synchronized public boolean isRunning() { + return isProcessing; + } + + @Override + synchronized public Exception getError() { + return mException; + } + + synchronized private void setRunning(boolean running) { + isProcessing = running; + } + + synchronized private void setException(Exception exception) { + mException = exception; + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/CachedFrameManager.java b/media/mca/filterfw/java/android/filterfw/core/CachedFrameManager.java new file mode 100644 index 0000000..a2cf2a0 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/CachedFrameManager.java @@ -0,0 +1,155 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.SimpleFrameManager; + +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * @hide + */ +public class CachedFrameManager extends SimpleFrameManager { + + private SortedMap<Integer, Frame> mAvailableFrames; + private int mStorageCapacity = 24 * 1024 * 1024; // Cap default storage to 24MB + private int mStorageSize = 0; + private int mTimeStamp = 0; + + public CachedFrameManager() { + super(); + mAvailableFrames = new TreeMap<Integer, Frame>(); + } + + @Override + public Frame newFrame(FrameFormat format) { + Frame result = findAvailableFrame(format, Frame.NO_BINDING, 0); + if (result == null) { + result = super.newFrame(format); + } + result.setTimestamp(Frame.TIMESTAMP_NOT_SET); + return result; + } + + @Override + public Frame newBoundFrame(FrameFormat format, int bindingType, long bindingId) { + Frame result = findAvailableFrame(format, bindingType, bindingId); + if (result == null) { + result = super.newBoundFrame(format, bindingType, bindingId); + } + result.setTimestamp(Frame.TIMESTAMP_NOT_SET); + return result; + } + + @Override + public Frame retainFrame(Frame frame) { + return super.retainFrame(frame); + } + + @Override + public Frame releaseFrame(Frame frame) { + if (frame.isReusable()) { + int refCount = frame.decRefCount(); + if (refCount == 0 && frame.hasNativeAllocation()) { + if (!storeFrame(frame)) { + frame.releaseNativeAllocation(); + } + return null; + } else if (refCount < 0) { + throw new RuntimeException("Frame reference count dropped below 0!"); + } + } else { + super.releaseFrame(frame); + } + return frame; + } + + public void clearCache() { + for (Frame frame : mAvailableFrames.values()) { + frame.releaseNativeAllocation(); + } + mAvailableFrames.clear(); + } + + @Override + public void tearDown() { + clearCache(); + } + + private boolean storeFrame(Frame frame) { + synchronized(mAvailableFrames) { + // Make sure this frame alone does not exceed capacity + int frameSize = frame.getFormat().getSize(); + if (frameSize > mStorageCapacity) { + return false; + } + + // Drop frames if adding this frame would exceed capacity + int newStorageSize = mStorageSize + frameSize; + while (newStorageSize > mStorageCapacity) { + dropOldestFrame(); + newStorageSize = mStorageSize + frameSize; + } + + // Store new frame + frame.onFrameStore(); + mStorageSize = newStorageSize; + mAvailableFrames.put(mTimeStamp, frame); + ++mTimeStamp; + return true; + } + } + + private void dropOldestFrame() { + int oldest = mAvailableFrames.firstKey(); + Frame frame = mAvailableFrames.get(oldest); + mStorageSize -= frame.getFormat().getSize(); + frame.releaseNativeAllocation(); + mAvailableFrames.remove(oldest); + } + + private Frame findAvailableFrame(FrameFormat format, int bindingType, long bindingId) { + // Look for a frame that is compatible with the requested format + synchronized(mAvailableFrames) { + for (Map.Entry<Integer, Frame> entry : mAvailableFrames.entrySet()) { + Frame frame = entry.getValue(); + // Check that format is compatible + if (frame.getFormat().isReplaceableBy(format)) { + // Check that binding is compatible (if frame is bound) + if ((bindingType == frame.getBindingType()) + && (bindingType == Frame.NO_BINDING || bindingId == frame.getBindingId())) { + // We found one! Take it out of the set of available frames and attach the + // requested format to it. + super.retainFrame(frame); + mAvailableFrames.remove(entry.getKey()); + frame.onFrameFetch(); + frame.reset(format); + mStorageSize -= format.getSize(); + return frame; + } + } + } + } + return null; + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/FieldPort.java b/media/mca/filterfw/java/android/filterfw/core/FieldPort.java new file mode 100644 index 0000000..b0350cc --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FieldPort.java @@ -0,0 +1,111 @@ +/* + * 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 android.filterfw.core; + +import java.lang.reflect.Field; + +/** + * @hide + */ +public class FieldPort extends InputPort { + + protected Field mField; + protected boolean mHasFrame; + protected boolean mValueWaiting = false; + protected Object mValue; + + public FieldPort(Filter filter, String name, Field field, boolean hasDefault) { + super(filter, name); + mField = field; + mHasFrame = hasDefault; + } + + @Override + public void clear() { + } + + @Override + public void pushFrame(Frame frame) { + setFieldFrame(frame, false); + } + + @Override + public void setFrame(Frame frame) { + setFieldFrame(frame, true); + } + + @Override + public Object getTarget() { + try { + return mField.get(mFilter); + } catch (IllegalAccessException e) { + return null; + } + } + + @Override + public synchronized void transfer(FilterContext context) { + if (mValueWaiting) { + try { + mField.set(mFilter, mValue); + } catch (IllegalAccessException e) { + throw new RuntimeException( + "Access to field '" + mField.getName() + "' was denied!"); + } + mValueWaiting = false; + if (context != null) { + mFilter.notifyFieldPortValueUpdated(mName, context); + } + } + } + + @Override + public synchronized Frame pullFrame() { + throw new RuntimeException("Cannot pull frame on " + this + "!"); + } + + @Override + public synchronized boolean hasFrame() { + return mHasFrame; + } + + @Override + public synchronized boolean acceptsFrame() { + return !mValueWaiting; + } + + @Override + public String toString() { + return "field " + super.toString(); + } + + protected synchronized void setFieldFrame(Frame frame, boolean isAssignment) { + assertPortIsOpen(); + checkFrameType(frame, isAssignment); + + // Store the object value + Object value = frame.getObjectValue(); + if ((value == null && mValue != null) || !value.equals(mValue)) { + mValue = value; + mValueWaiting = true; + } + + // Since a frame was set, mark this port as having a frame to pull + mHasFrame = true; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/Filter.java b/media/mca/filterfw/java/android/filterfw/core/Filter.java new file mode 100644 index 0000000..73b009d --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/Filter.java @@ -0,0 +1,709 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.FilterContext; +import android.filterfw.core.FilterPort; +import android.filterfw.core.KeyValueMap; +import android.filterfw.io.TextGraphReader; +import android.filterfw.io.GraphIOException; +import android.filterfw.format.ObjectFormat; +import android.util.Log; + +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.Thread; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map.Entry; +import java.util.LinkedList; +import java.util.Set; + +/** + * @hide + */ +public abstract class Filter { + + static final int STATUS_PREINIT = 0; + static final int STATUS_UNPREPARED = 1; + static final int STATUS_PREPARED = 2; + static final int STATUS_PROCESSING = 3; + static final int STATUS_SLEEPING = 4; + static final int STATUS_FINISHED = 5; + static final int STATUS_ERROR = 6; + static final int STATUS_RELEASED = 7; + + private String mName; + + private int mInputCount = -1; + private int mOutputCount = -1; + + private HashMap<String, InputPort> mInputPorts; + private HashMap<String, OutputPort> mOutputPorts; + + private HashSet<Frame> mFramesToRelease; + private HashMap<String, Frame> mFramesToSet; + + private int mStatus = 0; + private boolean mIsOpen = false; + private int mSleepDelay; + + private long mCurrentTimestamp; + + private boolean mLogVerbose; + private static final String TAG = "Filter"; + + public Filter(String name) { + mName = name; + mFramesToRelease = new HashSet<Frame>(); + mFramesToSet = new HashMap<String, Frame>(); + mStatus = STATUS_PREINIT; + + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + /** Tests to see if a given filter is installed on the system. Requires + * full filter package name, including filterpack. + */ + public static final boolean isAvailable(String filterName) { + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + Class filterClass; + // First see if a class of that name exists + try { + filterClass = contextClassLoader.loadClass(filterName); + } catch (ClassNotFoundException e) { + return false; + } + // Then make sure it's a subclass of Filter. + try { + filterClass.asSubclass(Filter.class); + } catch (ClassCastException e) { + return false; + } + return true; + } + + public final void initWithValueMap(KeyValueMap valueMap) { + // Initialization + initFinalPorts(valueMap); + + // Setup remaining ports + initRemainingPorts(valueMap); + + // This indicates that final ports can no longer be set + mStatus = STATUS_UNPREPARED; + } + + public final void initWithAssignmentString(String assignments) { + try { + KeyValueMap valueMap = new TextGraphReader().readKeyValueAssignments(assignments); + initWithValueMap(valueMap); + } catch (GraphIOException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } + + public final void initWithAssignmentList(Object... keyValues) { + KeyValueMap valueMap = new KeyValueMap(); + valueMap.setKeyValues(keyValues); + initWithValueMap(valueMap); + } + + public final void init() throws ProtocolException { + KeyValueMap valueMap = new KeyValueMap(); + initWithValueMap(valueMap); + } + + public String getFilterClassName() { + return getClass().getSimpleName(); + } + + public final String getName() { + return mName; + } + + public boolean isOpen() { + return mIsOpen; + } + + public void setInputFrame(String inputName, Frame frame) { + FilterPort port = getInputPort(inputName); + if (!port.isOpen()) { + port.open(); + } + port.setFrame(frame); + } + + public final void setInputValue(String inputName, Object value) { + setInputFrame(inputName, wrapInputValue(inputName, value)); + } + + protected void prepare(FilterContext context) { + } + + protected void parametersUpdated(Set<String> updated) { + } + + protected void delayNextProcess(int millisecs) { + mSleepDelay = millisecs; + mStatus = STATUS_SLEEPING; + } + + public abstract void setupPorts(); + + public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { + return null; + } + + public final FrameFormat getInputFormat(String portName) { + InputPort inputPort = getInputPort(portName); + return inputPort.getSourceFormat(); + } + + public void open(FilterContext context) { + } + + public abstract void process(FilterContext context); + + public final int getSleepDelay() { + return 250; + } + + public void close(FilterContext context) { + } + + public void tearDown(FilterContext context) { + } + + public final int getNumberOfConnectedInputs() { + int c = 0; + for (InputPort inputPort : mInputPorts.values()) { + if (inputPort.isConnected()) { + ++c; + } + } + return c; + } + + public final int getNumberOfConnectedOutputs() { + int c = 0; + for (OutputPort outputPort : mOutputPorts.values()) { + if (outputPort.isConnected()) { + ++c; + } + } + return c; + } + + public final int getNumberOfInputs() { + return mOutputPorts == null ? 0 : mInputPorts.size(); + } + + public final int getNumberOfOutputs() { + return mInputPorts == null ? 0 : mOutputPorts.size(); + } + + public final InputPort getInputPort(String portName) { + if (mInputPorts == null) { + throw new NullPointerException("Attempting to access input port '" + portName + + "' of " + this + " before Filter has been initialized!"); + } + InputPort result = mInputPorts.get(portName); + if (result == null) { + throw new IllegalArgumentException("Unknown input port '" + portName + "' on filter " + + this + "!"); + } + return result; + } + + public final OutputPort getOutputPort(String portName) { + if (mInputPorts == null) { + throw new NullPointerException("Attempting to access output port '" + portName + + "' of " + this + " before Filter has been initialized!"); + } + OutputPort result = mOutputPorts.get(portName); + if (result == null) { + throw new IllegalArgumentException("Unknown output port '" + portName + "' on filter " + + this + "!"); + } + return result; + } + + protected final void pushOutput(String name, Frame frame) { + if (frame.getTimestamp() == Frame.TIMESTAMP_NOT_SET) { + if (mLogVerbose) Log.v(TAG, "Default-setting output Frame timestamp on port " + name + " to " + mCurrentTimestamp); + frame.setTimestamp(mCurrentTimestamp); + } + getOutputPort(name).pushFrame(frame); + } + + protected final Frame pullInput(String name) { + Frame result = getInputPort(name).pullFrame(); + if (mCurrentTimestamp == Frame.TIMESTAMP_UNKNOWN) { + mCurrentTimestamp = result.getTimestamp(); + if (mLogVerbose) Log.v(TAG, "Default-setting current timestamp from input port " + name + " to " + mCurrentTimestamp); + } + // As result is retained, we add it to the release pool here + mFramesToRelease.add(result); + + return result; + } + + public void fieldPortValueUpdated(String name, FilterContext context) { + } + + /** + * Transfers any frame from an input port to its destination. This is useful to force a + * transfer from a FieldPort or ProgramPort to its connected target (field or program variable). + */ + protected void transferInputPortFrame(String name, FilterContext context) { + getInputPort(name).transfer(context); + } + + /** + * Assigns all program variables to the ports they are connected to. Call this after + * constructing a Program instance with attached ProgramPorts. + */ + protected void initProgramInputs(Program program, FilterContext context) { + if (program != null) { + for (InputPort inputPort : mInputPorts.values()) { + if (inputPort.getTarget() == program) { + inputPort.transfer(context); + } + } + } + } + + /** + * Adds an input port to the filter. You should call this from within setupPorts, if your + * filter has input ports. No type-checking is performed on the input. If you would like to + * check against a type mask, use + * {@link #addMaskedInputPort(String, FrameFormat) addMaskedInputPort} instead. + * + * @param name the name of the input port + */ + protected void addInputPort(String name) { + addMaskedInputPort(name, null); + } + + /** + * Adds an input port to the filter. You should call this from within setupPorts, if your + * filter has input ports. When type-checking is performed, the input format is + * checked against the provided format mask. An exception is thrown in case of a conflict. + * + * @param name the name of the input port + * @param formatMask a format mask, which filters the allowable input types + */ + protected void addMaskedInputPort(String name, FrameFormat formatMask) { + InputPort port = new StreamPort(this, name); + if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + port); + mInputPorts.put(name, port); + port.setPortFormat(formatMask); + } + + /** + * Adds an output port to the filter with a fixed output format. You should call this from + * within setupPorts, if your filter has output ports. You cannot use this method, if your + * output format depends on the input format (e.g. in a pass-through filter). In this case, use + * {@link #addOutputBasedOnInput(String, String) addOutputBasedOnInput} instead. + * + * @param name the name of the output port + * @param format the fixed output format of this port + */ + protected void addOutputPort(String name, FrameFormat format) { + OutputPort port = new OutputPort(this, name); + if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + port); + port.setPortFormat(format); + mOutputPorts.put(name, port); + } + + /** + * Adds an output port to the filter. You should call this from within setupPorts, if your + * filter has output ports. Using this method indicates that the output format for this + * particular port, depends on the format of an input port. You MUST also override + * {@link #getOutputFormat(String, FrameFormat) getOutputFormat} to specify what format your + * filter will output for a given input. If the output format of your filter port does not + * depend on the input, use {@link #addOutputPort(String, FrameFormat) addOutputPort} instead. + * + * @param outputName the name of the output port + * @param inputName the name of the input port, that this output depends on + */ + protected void addOutputBasedOnInput(String outputName, String inputName) { + OutputPort port = new OutputPort(this, outputName); + if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + port); + port.setBasePort(getInputPort(inputName)); + mOutputPorts.put(outputName, port); + } + + protected void addFieldPort(String name, + Field field, + boolean hasDefault, + boolean isFinal) { + // Make sure field is accessible + field.setAccessible(true); + + // Create port for this input + InputPort fieldPort = isFinal + ? new FinalPort(this, name, field, hasDefault) + : new FieldPort(this, name, field, hasDefault); + + // Create format for this input + if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + fieldPort); + MutableFrameFormat format = ObjectFormat.fromClass(field.getType(), + FrameFormat.TARGET_SIMPLE); + fieldPort.setPortFormat(format); + + // Add port + mInputPorts.put(name, fieldPort); + } + + protected void addProgramPort(String name, + String varName, + Field field, + Class varType, + boolean hasDefault) { + // Make sure field is accessible + field.setAccessible(true); + + // Create port for this input + InputPort programPort = new ProgramPort(this, name, varName, field, hasDefault); + + // Create format for this input + if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + programPort); + MutableFrameFormat format = ObjectFormat.fromClass(varType, + FrameFormat.TARGET_SIMPLE); + programPort.setPortFormat(format); + + // Add port + mInputPorts.put(name, programPort); + } + + protected void closeOutputPort(String name) { + getOutputPort(name).close(); + } + + /** + * Specifies whether the filter should not be scheduled until a frame is available on that + * input port. Note, that setting this to false, does not block a new frame from coming in + * (though there is no necessity to pull that frame for processing). + * @param portName the name of the input port. + * @param waits true, if the filter should wait for a frame on this port. + */ + protected void setWaitsOnInputPort(String portName, boolean waits) { + getInputPort(portName).setBlocking(waits); + } + + /** + * Specifies whether the filter should not be scheduled until the output port is free, i.e. + * there is no frame waiting on that output. + * @param portName the name of the output port. + * @param waits true, if the filter should wait for the port to become free. + */ + protected void setWaitsOnOutputPort(String portName, boolean waits) { + getOutputPort(portName).setBlocking(waits); + } + + public String toString() { + return "'" + getName() + "' (" + getFilterClassName() + ")"; + } + + // Core internal methods /////////////////////////////////////////////////////////////////////// + final Collection<InputPort> getInputPorts() { + return mInputPorts.values(); + } + + final Collection<OutputPort> getOutputPorts() { + return mOutputPorts.values(); + } + + final synchronized int getStatus() { + return mStatus; + } + + final synchronized void unsetStatus(int flag) { + mStatus &= ~flag; + } + + final synchronized void performOpen(FilterContext context) { + if (!mIsOpen) { + if (mStatus == STATUS_UNPREPARED) { + if (mLogVerbose) Log.v(TAG, "Preparing " + this); + prepare(context); + mStatus = STATUS_PREPARED; + } + if (mStatus == STATUS_PREPARED) { + if (mLogVerbose) Log.v(TAG, "Opening " + this); + open(context); + mStatus = STATUS_PROCESSING; + } + if (mStatus != STATUS_PROCESSING) { + throw new RuntimeException("Filter " + this + " was brought into invalid state during " + + "opening (state: " + mStatus + ")!"); + } + mIsOpen = true; + } + } + + final synchronized void performProcess(FilterContext context) { + if (mStatus == STATUS_RELEASED) { + throw new RuntimeException("Filter " + this + " is already torn down!"); + } + transferInputFrames(context); + if (mStatus < STATUS_PROCESSING) { + performOpen(context); + } + if (mLogVerbose) Log.v(TAG, "Processing " + this); + mCurrentTimestamp = Frame.TIMESTAMP_UNKNOWN; + process(context); + releasePulledFrames(context); + if (filterMustClose()) { + performClose(context); + } + } + + final synchronized void performClose(FilterContext context) { + if (mIsOpen) { + if (mLogVerbose) Log.v(TAG, "Closing " + this); + mIsOpen = false; + mStatus = STATUS_PREPARED; + close(context); + closePorts(); + } + } + + final synchronized void performTearDown(FilterContext context) { + performClose(context); + if (mStatus != STATUS_RELEASED) { + tearDown(context); + mStatus = STATUS_RELEASED; + } + } + + synchronized final boolean canProcess() { + if (mLogVerbose) Log.v(TAG, "Checking if can process: " + this + " (" + mStatus + ")."); + if (mStatus <= STATUS_PROCESSING) { + return inputConditionsMet() && outputConditionsMet(); + } else { + return false; + } + } + + final void openOutputs() { + if (mLogVerbose) Log.v(TAG, "Opening all output ports on " + this + "!"); + for (OutputPort outputPort : mOutputPorts.values()) { + if (!outputPort.isOpen()) { + outputPort.open(); + } + } + } + + final void clearInputs() { + for (InputPort inputPort : mInputPorts.values()) { + inputPort.clear(); + } + } + + final void clearOutputs() { + for (OutputPort outputPort : mOutputPorts.values()) { + outputPort.clear(); + } + } + + final void notifyFieldPortValueUpdated(String name, FilterContext context) { + if (mStatus == STATUS_PROCESSING || mStatus == STATUS_PREPARED) { + fieldPortValueUpdated(name, context); + } + } + + final synchronized void pushInputFrame(String inputName, Frame frame) { + FilterPort port = getInputPort(inputName); + if (!port.isOpen()) { + port.open(); + } + port.pushFrame(frame); + } + + final synchronized void pushInputValue(String inputName, Object value) { + pushInputFrame(inputName, wrapInputValue(inputName, value)); + } + + // Filter internal methods ///////////////////////////////////////////////////////////////////// + private final void initFinalPorts(KeyValueMap values) { + mInputPorts = new HashMap<String, InputPort>(); + mOutputPorts = new HashMap<String, OutputPort>(); + addAndSetFinalPorts(values); + } + + private final void initRemainingPorts(KeyValueMap values) { + addAnnotatedPorts(); + setupPorts(); // TODO: rename to addFilterPorts() ? + setInitialInputValues(values); + } + + private final void addAndSetFinalPorts(KeyValueMap values) { + Class filterClass = getClass(); + Annotation annotation; + for (Field field : filterClass.getDeclaredFields()) { + if ((annotation = field.getAnnotation(GenerateFinalPort.class)) != null) { + GenerateFinalPort generator = (GenerateFinalPort)annotation; + String name = generator.name().isEmpty() ? field.getName() : generator.name(); + boolean hasDefault = generator.hasDefault(); + addFieldPort(name, field, hasDefault, true); + if (values.containsKey(name)) { + setImmediateInputValue(name, values.get(name)); + values.remove(name); + } else if (!generator.hasDefault()) { + throw new RuntimeException("No value specified for final input port '" + + name + "' of filter " + this + "!"); + } + } + } + } + + private final void addAnnotatedPorts() { + Class filterClass = getClass(); + Annotation annotation; + for (Field field : filterClass.getDeclaredFields()) { + if ((annotation = field.getAnnotation(GenerateFieldPort.class)) != null) { + GenerateFieldPort generator = (GenerateFieldPort)annotation; + addFieldGenerator(generator, field); + } else if ((annotation = field.getAnnotation(GenerateProgramPort.class)) != null) { + GenerateProgramPort generator = (GenerateProgramPort)annotation; + addProgramGenerator(generator, field); + } else if ((annotation = field.getAnnotation(GenerateProgramPorts.class)) != null) { + GenerateProgramPorts generators = (GenerateProgramPorts)annotation; + for (GenerateProgramPort generator : generators.value()) { + addProgramGenerator(generator, field); + } + } + } + } + + private final void addFieldGenerator(GenerateFieldPort generator, Field field) { + String name = generator.name().isEmpty() ? field.getName() : generator.name(); + boolean hasDefault = generator.hasDefault(); + addFieldPort(name, field, hasDefault, false); + } + + private final void addProgramGenerator(GenerateProgramPort generator, Field field) { + String name = generator.name(); + String varName = generator.variableName().isEmpty() ? name + : generator.variableName(); + Class varType = generator.type(); + boolean hasDefault = generator.hasDefault(); + addProgramPort(name, varName, field, varType, hasDefault); + } + + private final void setInitialInputValues(KeyValueMap values) { + for (Entry<String, Object> entry : values.entrySet()) { + setInputValue(entry.getKey(), entry.getValue()); + } + } + + private final void setImmediateInputValue(String name, Object value) { + if (mLogVerbose) Log.v(TAG, "Setting immediate value " + value + " for port " + name + "!"); + FilterPort port = getInputPort(name); + port.open(); + port.setFrame(SimpleFrame.wrapObject(value, null)); + } + + private final void transferInputFrames(FilterContext context) { + for (InputPort inputPort : mInputPorts.values()) { + inputPort.transfer(context); + } + } + + private final Frame wrapInputValue(String inputName, Object value) { + MutableFrameFormat inputFormat = ObjectFormat.fromObject(value, FrameFormat.TARGET_SIMPLE); + if (value == null) { + // If the value is null, the format cannot guess the class, so we adjust it to the + // class of the input port here + FrameFormat portFormat = getInputPort(inputName).getPortFormat(); + Class portClass = (portFormat == null) ? null : portFormat.getObjectClass(); + inputFormat.setObjectClass(portClass); + } + + // Serialize if serializable, and type is not an immutable primitive. + boolean shouldSerialize = !(value instanceof Number) + && !(value instanceof Boolean) + && !(value instanceof String) + && value instanceof Serializable; + + // Create frame wrapper + Frame frame = shouldSerialize + ? new SerializedFrame(inputFormat, null) + : new SimpleFrame(inputFormat, null); + frame.setObjectValue(value); + return frame; + } + + private final void releasePulledFrames(FilterContext context) { + for (Frame frame : mFramesToRelease) { + context.getFrameManager().releaseFrame(frame); + } + mFramesToRelease.clear(); + } + + private final boolean inputConditionsMet() { + for (FilterPort port : mInputPorts.values()) { + if (!port.isReady()) { + if (mLogVerbose) Log.v(TAG, "Input condition not met: " + port + "!"); + return false; + } + } + return true; + } + + private final boolean outputConditionsMet() { + for (FilterPort port : mOutputPorts.values()) { + if (!port.isReady()) { + if (mLogVerbose) Log.v(TAG, "Output condition not met: " + port + "!"); + return false; + } + } + return true; + } + + private final void closePorts() { + if (mLogVerbose) Log.v(TAG, "Closing all ports on " + this + "!"); + for (InputPort inputPort : mInputPorts.values()) { + inputPort.close(); + } + for (OutputPort outputPort : mOutputPorts.values()) { + outputPort.close(); + } + } + + private final boolean filterMustClose() { + for (InputPort inputPort : mInputPorts.values()) { + if (inputPort.filterMustClose()) { + if (mLogVerbose) Log.v(TAG, "Filter " + this + " must close due to port " + inputPort); + return true; + } + } + for (OutputPort outputPort : mOutputPorts.values()) { + if (outputPort.filterMustClose()) { + if (mLogVerbose) Log.v(TAG, "Filter " + this + " must close due to port " + outputPort); + return true; + } + } + return false; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterContext.java b/media/mca/filterfw/java/android/filterfw/core/FilterContext.java new file mode 100644 index 0000000..3c79d1b --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FilterContext.java @@ -0,0 +1,126 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Filter; +import android.filterfw.core.Frame; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GLEnvironment; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +/** + * @hide + */ +public class FilterContext { + + private FrameManager mFrameManager; + private GLEnvironment mGLEnvironment; + private HashMap<String, Frame> mStoredFrames = new HashMap<String, Frame>(); + private Set<FilterGraph> mGraphs = new HashSet<FilterGraph>(); + + public FrameManager getFrameManager() { + return mFrameManager; + } + + public void setFrameManager(FrameManager manager) { + if (manager == null) { + throw new NullPointerException("Attempting to set null FrameManager!"); + } else if (manager.getContext() != null) { + throw new IllegalArgumentException("Attempting to set FrameManager which is already " + + "bound to another FilterContext!"); + } else { + mFrameManager = manager; + mFrameManager.setContext(this); + } + } + + public GLEnvironment getGLEnvironment() { + return mGLEnvironment; + } + + public void initGLEnvironment(GLEnvironment environment) { + if (mGLEnvironment == null) { + mGLEnvironment = environment; + } else { + throw new RuntimeException("Attempting to re-initialize GL Environment for " + + "FilterContext!"); + } + } + + public interface OnFrameReceivedListener { + public void onFrameReceived(Filter filter, Frame frame, Object userData); + } + + public synchronized void storeFrame(String key, Frame frame) { + Frame storedFrame = fetchFrame(key); + if (storedFrame != null) { + storedFrame.release(); + } + frame.onFrameStore(); + mStoredFrames.put(key, frame.retain()); + } + + public synchronized Frame fetchFrame(String key) { + Frame frame = mStoredFrames.get(key); + if (frame != null) { + frame.onFrameFetch(); + } + return frame; + } + + public synchronized void removeFrame(String key) { + Frame frame = mStoredFrames.get(key); + if (frame != null) { + mStoredFrames.remove(key); + frame.release(); + } + } + + public synchronized void tearDown() { + // Release stored frames + for (Frame frame : mStoredFrames.values()) { + frame.release(); + } + mStoredFrames.clear(); + + // Release graphs + for (FilterGraph graph : mGraphs) { + graph.tearDown(this); + } + mGraphs.clear(); + + // Release frame manager + if (mFrameManager != null) { + mFrameManager.tearDown(); + mFrameManager = null; + } + + // Release GL context + if (mGLEnvironment != null) { + mGLEnvironment.tearDown(); + mGLEnvironment = null; + } + } + + final void addGraph(FilterGraph graph) { + mGraphs.add(graph); + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java b/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java new file mode 100644 index 0000000..779df99 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FilterFactory.java @@ -0,0 +1,145 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Filter; +import android.util.Log; + +import dalvik.system.PathClassLoader; + +import java.lang.reflect.Constructor; +import java.lang.ClassLoader; +import java.lang.Thread; +import java.util.HashSet; + +/** + * @hide + */ +public class FilterFactory { + + private static FilterFactory mSharedFactory; + private HashSet<String> mPackages = new HashSet<String>(); + + private static ClassLoader mCurrentClassLoader; + private static HashSet<String> mLibraries; + private static Object mClassLoaderGuard; + + static { + mCurrentClassLoader = Thread.currentThread().getContextClassLoader(); + mLibraries = new HashSet<String>(); + mClassLoaderGuard = new Object(); + } + + private static final String TAG = "FilterFactory"; + private static boolean mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + + public static FilterFactory sharedFactory() { + if (mSharedFactory == null) { + mSharedFactory = new FilterFactory(); + } + return mSharedFactory; + } + + /** + * Adds a new Java library to the list to be scanned for filters. + * libraryPath must be an absolute path of the jar file. This needs to be + * static because only one classloader per process can open a shared native + * library, which a filter may well have. + */ + public static void addFilterLibrary(String libraryPath) { + if (mLogVerbose) Log.v(TAG, "Adding filter library " + libraryPath); + synchronized(mClassLoaderGuard) { + if (mLibraries.contains(libraryPath)) { + if (mLogVerbose) Log.v(TAG, "Library already added"); + return; + } + mLibraries.add(libraryPath); + // Chain another path loader to the current chain + mCurrentClassLoader = new PathClassLoader(libraryPath, mCurrentClassLoader); + } + } + + public void addPackage(String packageName) { + if (mLogVerbose) Log.v(TAG, "Adding package " + packageName); + /* TODO: This should use a getPackage call in the caller's context, but no such method exists. + Package pkg = Package.getPackage(packageName); + if (pkg == null) { + throw new IllegalArgumentException("Unknown filter package '" + packageName + "'!"); + } + */ + mPackages.add(packageName); + } + + public Filter createFilterByClassName(String className, String filterName) { + if (mLogVerbose) Log.v(TAG, "Looking up class " + className); + Class filterClass = null; + + // Look for the class in the imported packages + for (String packageName : mPackages) { + try { + if (mLogVerbose) Log.v(TAG, "Trying "+packageName + "." + className); + synchronized(mClassLoaderGuard) { + filterClass = mCurrentClassLoader.loadClass(packageName + "." + className); + } + } catch (ClassNotFoundException e) { + continue; + } + // Exit loop if class was found. + if (filterClass != null) { + break; + } + } + if (filterClass == null) { + throw new IllegalArgumentException("Unknown filter class '" + className + "'!"); + } + return createFilterByClass(filterClass, filterName); + } + + public Filter createFilterByClass(Class filterClass, String filterName) { + // Make sure this is a Filter subclass + try { + filterClass.asSubclass(Filter.class); + } catch (ClassCastException e) { + throw new IllegalArgumentException("Attempting to allocate class '" + filterClass + + "' which is not a subclass of Filter!"); + } + + // Look for the correct constructor + Constructor filterConstructor = null; + try { + filterConstructor = filterClass.getConstructor(String.class); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("The filter class '" + filterClass + + "' does not have a constructor of the form <init>(String name)!"); + } + + // Construct the filter + Filter filter = null; + try { + filter = (Filter)filterConstructor.newInstance(filterName); + } catch (Throwable t) { + // Condition checked below + } + + if (filter == null) { + throw new IllegalArgumentException("Could not construct the filter '" + + filterName + "'!"); + } + return filter; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterFunction.java b/media/mca/filterfw/java/android/filterfw/core/FilterFunction.java new file mode 100644 index 0000000..ce81a18 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FilterFunction.java @@ -0,0 +1,139 @@ +/* + * 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 android.filterfw.core; + +import java.util.Map.Entry; + +/** + * @hide + */ +public class FilterFunction { + + private Filter mFilter; + private FilterContext mFilterContext; + private boolean mFilterIsSetup = false; + private FrameHolderPort[] mResultHolders; + + private class FrameHolderPort extends StreamPort { + public FrameHolderPort() { + super(null, "holder"); + } + } + + public FilterFunction(FilterContext context, Filter filter) { + mFilterContext = context; + mFilter = filter; + } + + public Frame execute(KeyValueMap inputMap) { + int filterOutCount = mFilter.getNumberOfOutputs(); + + // Sanity checks + if (filterOutCount > 1) { + throw new RuntimeException("Calling execute on filter " + mFilter + " with multiple " + + "outputs! Use executeMulti() instead!"); + } + + // Setup filter + if (!mFilterIsSetup) { + connectFilterOutputs(); + mFilterIsSetup = true; + } + + // Make sure GL environment is active + boolean didActivateGLEnv = false; + GLEnvironment glEnv = mFilterContext.getGLEnvironment(); + if (glEnv != null && !glEnv.isActive()) { + glEnv.activate(); + didActivateGLEnv = true; + } + + // Setup the inputs + for (Entry<String, Object> entry : inputMap.entrySet()) { + if (entry.getValue() instanceof Frame) { + mFilter.pushInputFrame(entry.getKey(), (Frame)entry.getValue()); + } else { + mFilter.pushInputValue(entry.getKey(), entry.getValue()); + } + } + + // Process the filter + if (mFilter.getStatus() != Filter.STATUS_PROCESSING) { + mFilter.openOutputs(); + } + + mFilter.performProcess(mFilterContext); + + // Create result handle + Frame result = null; + if (filterOutCount == 1 && mResultHolders[0].hasFrame()) { + result = mResultHolders[0].pullFrame(); + } + + // Deactivate GL environment if activated + if (didActivateGLEnv) { + glEnv.deactivate(); + } + + return result; + } + + public Frame executeWithArgList(Object... inputs) { + return execute(KeyValueMap.fromKeyValues(inputs)); + } + + public void close() { + mFilter.performClose(mFilterContext); + } + + public FilterContext getContext() { + return mFilterContext; + } + + public Filter getFilter() { + return mFilter; + } + + public void setInputFrame(String input, Frame frame) { + mFilter.setInputFrame(input, frame); + } + + public void setInputValue(String input, Object value) { + mFilter.setInputValue(input, value); + } + + public void tearDown() { + mFilter.performTearDown(mFilterContext); + mFilter = null; + } + + @Override + public String toString() { + return mFilter.getName(); + } + + private void connectFilterOutputs() { + int i = 0; + mResultHolders = new FrameHolderPort[mFilter.getNumberOfOutputs()]; + for (OutputPort outputPort : mFilter.getOutputPorts()) { + mResultHolders[i] = new FrameHolderPort(); + outputPort.connectTo(mResultHolders[i]); + ++i; + } + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java b/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java new file mode 100644 index 0000000..12f7892 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java @@ -0,0 +1,363 @@ +/* + * 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 android.filterfw.core; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Stack; + +import android.filterfw.core.FilterContext; +import android.filterfw.core.KeyValueMap; +import android.filterpacks.base.FrameBranch; +import android.filterpacks.base.NullFilter; + +import android.util.Log; + +/** + * @hide + */ +public class FilterGraph { + + private HashSet<Filter> mFilters = new HashSet<Filter>(); + private HashMap<String, Filter> mNameMap = new HashMap<String, Filter>(); + private HashMap<OutputPort, LinkedList<InputPort>> mPreconnections = new + HashMap<OutputPort, LinkedList<InputPort>>(); + + public static final int AUTOBRANCH_OFF = 0; + public static final int AUTOBRANCH_SYNCED = 1; + public static final int AUTOBRANCH_UNSYNCED = 2; + + public static final int TYPECHECK_OFF = 0; + public static final int TYPECHECK_DYNAMIC = 1; + public static final int TYPECHECK_STRICT = 2; + + private boolean mIsReady = false; + private int mAutoBranchMode = AUTOBRANCH_OFF; + private int mTypeCheckMode = TYPECHECK_STRICT; + private boolean mDiscardUnconnectedOutputs = false; + + private boolean mLogVerbose; + private String TAG = "FilterGraph"; + + public FilterGraph() { + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + public boolean addFilter(Filter filter) { + if (!containsFilter(filter)) { + mFilters.add(filter); + mNameMap.put(filter.getName(), filter); + return true; + } + return false; + } + + public boolean containsFilter(Filter filter) { + return mFilters.contains(filter); + } + + public Filter getFilter(String name) { + return mNameMap.get(name); + } + + public void connect(Filter source, + String outputName, + Filter target, + String inputName) { + if (source == null || target == null) { + throw new IllegalArgumentException("Passing null Filter in connect()!"); + } else if (!containsFilter(source) || !containsFilter(target)) { + throw new RuntimeException("Attempting to connect filter not in graph!"); + } + + OutputPort outPort = source.getOutputPort(outputName); + InputPort inPort = target.getInputPort(inputName); + if (outPort == null) { + throw new RuntimeException("Unknown output port '" + outputName + "' on Filter " + + source + "!"); + } else if (inPort == null) { + throw new RuntimeException("Unknown input port '" + inputName + "' on Filter " + + target + "!"); + } + + preconnect(outPort, inPort); + } + + public void connect(String sourceName, + String outputName, + String targetName, + String inputName) { + Filter source = getFilter(sourceName); + Filter target = getFilter(targetName); + if (source == null) { + throw new RuntimeException( + "Attempting to connect unknown source filter '" + sourceName + "'!"); + } else if (target == null) { + throw new RuntimeException( + "Attempting to connect unknown target filter '" + targetName + "'!"); + } + connect(source, outputName, target, inputName); + } + + public Set<Filter> getFilters() { + return mFilters; + } + + public void beginProcessing() { + if (mLogVerbose) Log.v(TAG, "Opening all filter connections..."); + for (Filter filter : mFilters) { + filter.openOutputs(); + } + mIsReady = true; + } + + public void flushFrames() { + for (Filter filter : mFilters) { + filter.clearOutputs(); + } + } + + public void closeFilters(FilterContext context) { + if (mLogVerbose) Log.v(TAG, "Closing all filters..."); + for (Filter filter : mFilters) { + filter.performClose(context); + } + mIsReady = false; + } + + public boolean isReady() { + return mIsReady; + } + + public void setAutoBranchMode(int autoBranchMode) { + mAutoBranchMode = autoBranchMode; + } + + public void setDiscardUnconnectedOutputs(boolean discard) { + mDiscardUnconnectedOutputs = discard; + } + + public void setTypeCheckMode(int typeCheckMode) { + mTypeCheckMode = typeCheckMode; + } + + public void tearDown(FilterContext context) { + if (!mFilters.isEmpty()) { + flushFrames(); + for (Filter filter : mFilters) { + filter.performTearDown(context); + } + mFilters.clear(); + mNameMap.clear(); + mIsReady = false; + } + } + + private boolean readyForProcessing(Filter filter, Set<Filter> processed) { + // Check if this has been already processed + if (processed.contains(filter)) { + return false; + } + + // Check if all dependencies have been processed + for (InputPort port : filter.getInputPorts()) { + Filter dependency = port.getSourceFilter(); + if (dependency != null && !processed.contains(dependency)) { + return false; + } + } + return true; + } + + private void runTypeCheck() { + Stack<Filter> filterStack = new Stack<Filter>(); + Set<Filter> processedFilters = new HashSet<Filter>(); + filterStack.addAll(getSourceFilters()); + + while (!filterStack.empty()) { + // Get current filter and mark as processed + Filter filter = filterStack.pop(); + processedFilters.add(filter); + + // Anchor output formats + updateOutputs(filter); + + // Perform type check + if (mLogVerbose) Log.v(TAG, "Running type check on " + filter + "..."); + runTypeCheckOn(filter); + + // Push connected filters onto stack + for (OutputPort port : filter.getOutputPorts()) { + Filter target = port.getTargetFilter(); + if (target != null && readyForProcessing(target, processedFilters)) { + filterStack.push(target); + } + } + } + + // Make sure all ports were setup + if (processedFilters.size() != getFilters().size()) { + throw new RuntimeException("Could not schedule all filters! Is your graph malformed?"); + } + } + + private void updateOutputs(Filter filter) { + for (OutputPort outputPort : filter.getOutputPorts()) { + InputPort inputPort = outputPort.getBasePort(); + if (inputPort != null) { + FrameFormat inputFormat = inputPort.getSourceFormat(); + FrameFormat outputFormat = filter.getOutputFormat(outputPort.getName(), + inputFormat); + if (outputFormat == null) { + throw new RuntimeException("Filter did not return an output format for " + + outputPort + "!"); + } + outputPort.setPortFormat(outputFormat); + } + } + } + + private void runTypeCheckOn(Filter filter) { + for (InputPort inputPort : filter.getInputPorts()) { + if (mLogVerbose) Log.v(TAG, "Type checking port " + inputPort); + FrameFormat sourceFormat = inputPort.getSourceFormat(); + FrameFormat targetFormat = inputPort.getPortFormat(); + if (sourceFormat != null && targetFormat != null) { + if (mLogVerbose) Log.v(TAG, "Checking " + sourceFormat + " against " + targetFormat + "."); + + boolean compatible = true; + switch (mTypeCheckMode) { + case TYPECHECK_OFF: + inputPort.setChecksType(false); + break; + case TYPECHECK_DYNAMIC: + compatible = sourceFormat.mayBeCompatibleWith(targetFormat); + inputPort.setChecksType(true); + break; + case TYPECHECK_STRICT: + compatible = sourceFormat.isCompatibleWith(targetFormat); + inputPort.setChecksType(false); + break; + } + + if (!compatible) { + throw new RuntimeException("Type mismatch: Filter " + filter + " expects a " + + "format of type " + targetFormat + " but got a format of type " + + sourceFormat + "!"); + } + } + } + } + + private void checkConnections() { + // TODO + } + + private void discardUnconnectedOutputs() { + // Connect unconnected ports to Null filters + LinkedList<Filter> addedFilters = new LinkedList<Filter>(); + for (Filter filter : mFilters) { + int id = 0; + for (OutputPort port : filter.getOutputPorts()) { + if (!port.isConnected()) { + if (mLogVerbose) Log.v(TAG, "Autoconnecting unconnected " + port + " to Null filter."); + NullFilter nullFilter = new NullFilter(filter.getName() + "ToNull" + id); + nullFilter.init(); + addedFilters.add(nullFilter); + port.connectTo(nullFilter.getInputPort("frame")); + ++id; + } + } + } + // Add all added filters to this graph + for (Filter filter : addedFilters) { + addFilter(filter); + } + } + + private void removeFilter(Filter filter) { + mFilters.remove(filter); + mNameMap.remove(filter.getName()); + } + + private void preconnect(OutputPort outPort, InputPort inPort) { + LinkedList<InputPort> targets; + targets = mPreconnections.get(outPort); + if (targets == null) { + targets = new LinkedList<InputPort>(); + mPreconnections.put(outPort, targets); + } + targets.add(inPort); + } + + private void connectPorts() { + int branchId = 1; + for (Entry<OutputPort, LinkedList<InputPort>> connection : mPreconnections.entrySet()) { + OutputPort outputPort = connection.getKey(); + LinkedList<InputPort> inputPorts = connection.getValue(); + if (inputPorts.size() == 1) { + outputPort.connectTo(inputPorts.get(0)); + } else if (mAutoBranchMode == AUTOBRANCH_OFF) { + throw new RuntimeException("Attempting to connect " + outputPort + " to multiple " + + "filter ports! Enable auto-branching to allow this."); + } else { + if (mLogVerbose) Log.v(TAG, "Creating branch for " + outputPort + "!"); + FrameBranch branch = null; + if (mAutoBranchMode == AUTOBRANCH_SYNCED) { + branch = new FrameBranch("branch" + branchId++); + } else { + throw new RuntimeException("TODO: Unsynced branches not implemented yet!"); + } + KeyValueMap branchParams = new KeyValueMap(); + branch.initWithAssignmentList("outputs", inputPorts.size()); + addFilter(branch); + outputPort.connectTo(branch.getInputPort("in")); + Iterator<InputPort> inputPortIter = inputPorts.iterator(); + for (OutputPort branchOutPort : ((Filter)branch).getOutputPorts()) { + branchOutPort.connectTo(inputPortIter.next()); + } + } + } + mPreconnections.clear(); + } + + private HashSet<Filter> getSourceFilters() { + HashSet<Filter> sourceFilters = new HashSet<Filter>(); + for (Filter filter : getFilters()) { + if (filter.getNumberOfConnectedInputs() == 0) { + if (mLogVerbose) Log.v(TAG, "Found source filter: " + filter); + sourceFilters.add(filter); + } + } + return sourceFilters; + } + + // Core internal methods ///////////////////////////////////////////////////////////////////////// + void setupFilters() { + if (mDiscardUnconnectedOutputs) { + discardUnconnectedOutputs(); + } + connectPorts(); + checkConnections(); + runTypeCheck(); + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterPort.java b/media/mca/filterfw/java/android/filterfw/core/FilterPort.java new file mode 100644 index 0000000..9734b89 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FilterPort.java @@ -0,0 +1,134 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Filter; +import android.filterfw.core.FrameFormat; +import android.util.Log; + +/** + * @hide + */ +public abstract class FilterPort { + + protected Filter mFilter; + protected String mName; + protected FrameFormat mPortFormat; + protected boolean mIsBlocking = true; + protected boolean mIsOpen = false; + protected boolean mChecksType = false; + private boolean mLogVerbose; + private static final String TAG = "FilterPort"; + + public FilterPort(Filter filter, String name) { + mName = name; + mFilter = filter; + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + public boolean isAttached() { + return mFilter != null; + } + + public FrameFormat getPortFormat() { + return mPortFormat; + } + + public void setPortFormat(FrameFormat format) { + mPortFormat = format; + } + + public Filter getFilter() { + return mFilter; + } + + public String getName() { + return mName; + } + + public void setBlocking(boolean blocking) { + mIsBlocking = blocking; + } + + public void setChecksType(boolean checksType) { + mChecksType = checksType; + } + + public void open() { + if (!mIsOpen) { + if (mLogVerbose) Log.v(TAG, "Opening " + this); + } + mIsOpen = true; + } + + public void close() { + if (mIsOpen) { + if (mLogVerbose) Log.v(TAG, "Closing " + this); + } + mIsOpen = false; + } + + public boolean isOpen() { + return mIsOpen; + } + + public boolean isBlocking() { + return mIsBlocking; + } + + public abstract boolean filterMustClose(); + + public abstract boolean isReady(); + + public abstract void pushFrame(Frame frame); + + public abstract void setFrame(Frame frame); + + public abstract Frame pullFrame(); + + public abstract boolean hasFrame(); + + public abstract void clear(); + + public String toString() { + return "port '" + mName + "' of " + mFilter; + } + + protected void assertPortIsOpen() { + if (!isOpen()) { + throw new RuntimeException("Illegal operation on closed " + this + "!"); + } + } + + protected void checkFrameType(Frame frame, boolean forceCheck) { + if ((mChecksType || forceCheck) + && mPortFormat != null + && !frame.getFormat().isCompatibleWith(mPortFormat)) { + throw new RuntimeException("Frame passed to " + this + " is of incorrect type! " + + "Expected " + mPortFormat + " but got " + frame.getFormat()); + } + } + + protected void checkFrameManager(Frame frame, FilterContext context) { + if (frame.getFrameManager() != null + && frame.getFrameManager() != context.getFrameManager()) { + throw new RuntimeException("Frame " + frame + " is managed by foreign FrameManager! "); + } + } +} + diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterSurfaceView.java b/media/mca/filterfw/java/android/filterfw/core/FilterSurfaceView.java new file mode 100644 index 0000000..49306b2 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FilterSurfaceView.java @@ -0,0 +1,157 @@ +/* + * 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 android.filterfw.core; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +/** + * @hide + */ +public class FilterSurfaceView extends SurfaceView implements SurfaceHolder.Callback { + + private static int STATE_ALLOCATED = 0; + private static int STATE_CREATED = 1; + private static int STATE_INITIALIZED = 2; + + private int mState = STATE_ALLOCATED; + private SurfaceHolder.Callback mListener; + private GLEnvironment mGLEnv; + private int mFormat; + private int mWidth; + private int mHeight; + private int mSurfaceId = -1; + + public FilterSurfaceView(Context context) { + super(context); + getHolder().addCallback(this); + } + + public FilterSurfaceView(Context context, AttributeSet attrs) { + super(context, attrs); + getHolder().addCallback(this); + } + + public synchronized void bindToListener(SurfaceHolder.Callback listener, GLEnvironment glEnv) { + // Make sure we are not bound already + if (listener == null) { + throw new NullPointerException("Attempting to bind null filter to SurfaceView!"); + } else if (mListener != null && mListener != listener) { + throw new RuntimeException( + "Attempting to bind filter " + listener + " to SurfaceView with another open " + + "filter " + mListener + " attached already!"); + } + + // Set listener + mListener = listener; + + // Set GLEnv + if (mGLEnv != null && mGLEnv != glEnv) { + mGLEnv.unregisterSurfaceId(mSurfaceId); + } + mGLEnv = glEnv; + + // Check if surface has been created already + if (mState >= STATE_CREATED) { + // Register with env (double registration will be ignored by GLEnv, so we can simply + // try to do it here). + registerSurface(); + + // Forward surface created to listener + mListener.surfaceCreated(getHolder()); + + // Forward surface changed to listener + if (mState == STATE_INITIALIZED) { + mListener.surfaceChanged(getHolder(), mFormat, mWidth, mHeight); + } + } + } + + public synchronized void unbind() { + mListener = null; + } + + public synchronized int getSurfaceId() { + return mSurfaceId; + } + + public synchronized GLEnvironment getGLEnv() { + return mGLEnv; + } + + @Override + public synchronized void surfaceCreated(SurfaceHolder holder) { + mState = STATE_CREATED; + + // Register with GLEnvironment if we have it already + if (mGLEnv != null) { + registerSurface(); + } + + // Forward callback to listener + if (mListener != null) { + mListener.surfaceCreated(holder); + } + } + + @Override + public synchronized void surfaceChanged(SurfaceHolder holder, + int format, + int width, + int height) { + // Remember these values + mFormat = format; + mWidth = width; + mHeight = height; + mState = STATE_INITIALIZED; + + // Forward to renderer + if (mListener != null) { + mListener.surfaceChanged(holder, format, width, height); + } + } + + @Override + public synchronized void surfaceDestroyed(SurfaceHolder holder) { + mState = STATE_ALLOCATED; + + // Forward to renderer + if (mListener != null) { + mListener.surfaceDestroyed(holder); + } + + // Get rid of internal objects associated with this surface + unregisterSurface(); + } + + private void registerSurface() { + mSurfaceId = mGLEnv.registerSurface(getHolder().getSurface()); + if (mSurfaceId < 0) { + throw new RuntimeException("Could not register Surface: " + getHolder().getSurface() + + " in FilterSurfaceView!"); + } + } + private void unregisterSurface() { + if (mGLEnv != null && mSurfaceId > 0) { + mGLEnv.unregisterSurfaceId(mSurfaceId); + } + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/FinalPort.java b/media/mca/filterfw/java/android/filterfw/core/FinalPort.java new file mode 100644 index 0000000..ad65169 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FinalPort.java @@ -0,0 +1,48 @@ +/* + * 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 android.filterfw.core; + +import java.lang.reflect.Field; + +/** + * @hide + */ +public class FinalPort extends FieldPort { + + public FinalPort(Filter filter, String name, Field field, boolean hasDefault) { + super(filter, name, field, hasDefault); + } + + @Override + protected synchronized void setFieldFrame(Frame frame, boolean isAssignment) { + assertPortIsOpen(); + checkFrameType(frame, isAssignment); + if (mFilter.getStatus() != Filter.STATUS_PREINIT) { + throw new RuntimeException("Attempting to modify " + this + "!"); + } else { + super.setFieldFrame(frame, isAssignment); + super.transfer(null); + } + } + + @Override + public String toString() { + return "final " + super.toString(); + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/Frame.java b/media/mca/filterfw/java/android/filterfw/core/Frame.java new file mode 100644 index 0000000..ef8c542 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/Frame.java @@ -0,0 +1,236 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.graphics.Bitmap; +import android.util.Log; + +import java.nio.ByteBuffer; + +/** + * @hide + */ +public abstract class Frame { + + public final static int NO_BINDING = 0; + + public final static long TIMESTAMP_NOT_SET = -2; + public final static long TIMESTAMP_UNKNOWN = -1; + + private FrameFormat mFormat; + private FrameManager mFrameManager; + private boolean mReadOnly = false; + private boolean mReusable = false; + private int mRefCount = 1; + private int mBindingType = NO_BINDING; + private long mBindingId = 0; + private long mTimestamp = TIMESTAMP_NOT_SET; + + Frame(FrameFormat format, FrameManager frameManager) { + mFormat = format.mutableCopy(); + mFrameManager = frameManager; + } + + Frame(FrameFormat format, FrameManager frameManager, int bindingType, long bindingId) { + mFormat = format.mutableCopy(); + mFrameManager = frameManager; + mBindingType = bindingType; + mBindingId = bindingId; + } + + public FrameFormat getFormat() { + return mFormat; + } + + public int getCapacity() { + return getFormat().getSize(); + } + + public boolean isReadOnly() { + return mReadOnly; + } + + public int getBindingType() { + return mBindingType; + } + + public long getBindingId() { + return mBindingId; + } + + public void setObjectValue(Object object) { + assertFrameMutable(); + + // Attempt to set the value using a specific setter (which may be more optimized), and + // fall back to the setGenericObjectValue(...) in case of no match. + if (object instanceof int[]) { + setInts((int[])object); + } else if (object instanceof float[]) { + setFloats((float[])object); + } else if (object instanceof ByteBuffer) { + setData((ByteBuffer)object); + } else if (object instanceof Bitmap) { + setBitmap((Bitmap)object); + } else { + setGenericObjectValue(object); + } + } + + public abstract Object getObjectValue(); + + public abstract void setInts(int[] ints); + + public abstract int[] getInts(); + + public abstract void setFloats(float[] floats); + + public abstract float[] getFloats(); + + public abstract void setData(ByteBuffer buffer, int offset, int length); + + public void setData(ByteBuffer buffer) { + setData(buffer, 0, buffer.limit()); + } + + public void setData(byte[] bytes, int offset, int length) { + setData(ByteBuffer.wrap(bytes, offset, length)); + } + + public abstract ByteBuffer getData(); + + public abstract void setBitmap(Bitmap bitmap); + + public abstract Bitmap getBitmap(); + + public void setTimestamp(long timestamp) { + mTimestamp = timestamp; + } + + public long getTimestamp() { + return mTimestamp; + } + + public void setDataFromFrame(Frame frame) { + setData(frame.getData()); + } + + protected boolean requestResize(int[] newDimensions) { + return false; + } + + public int getRefCount() { + return mRefCount; + } + + public Frame release() { + if (mFrameManager != null) { + return mFrameManager.releaseFrame(this); + } else { + return this; + } + } + + public Frame retain() { + if (mFrameManager != null) { + return mFrameManager.retainFrame(this); + } else { + return this; + } + } + + public FrameManager getFrameManager() { + return mFrameManager; + } + + protected void assertFrameMutable() { + if (isReadOnly()) { + throw new RuntimeException("Attempting to modify read-only frame!"); + } + } + + protected void setReusable(boolean reusable) { + mReusable = reusable; + } + + protected void setFormat(FrameFormat format) { + mFormat = format.mutableCopy(); + } + + protected void setGenericObjectValue(Object value) { + throw new RuntimeException( + "Cannot set object value of unsupported type: " + value.getClass()); + } + + protected static Bitmap convertBitmapToRGBA(Bitmap bitmap) { + if (bitmap.getConfig() == Bitmap.Config.ARGB_8888) { + return bitmap; + } else { + Bitmap result = bitmap.copy(Bitmap.Config.ARGB_8888, false); + if (result == null) { + throw new RuntimeException("Error converting bitmap to RGBA!"); + } else if (result.getRowBytes() != result.getWidth() * 4) { + throw new RuntimeException("Unsupported row byte count in bitmap!"); + } + return result; + } + } + + protected void reset(FrameFormat newFormat) { + mFormat = newFormat.mutableCopy(); + mReadOnly = false; + mRefCount = 1; + } + + /** + * Called just before a frame is stored, such as when storing to a cache or context. + */ + protected void onFrameStore() { + } + + /** + * Called when a frame is fetched from an internal store such as a cache. + */ + protected void onFrameFetch() { + } + + // Core internal methods /////////////////////////////////////////////////////////////////////// + protected abstract boolean hasNativeAllocation(); + + protected abstract void releaseNativeAllocation(); + + final int incRefCount() { + ++mRefCount; + return mRefCount; + } + + final int decRefCount() { + --mRefCount; + return mRefCount; + } + + final boolean isReusable() { + return mReusable; + } + + final void markReadOnly() { + mReadOnly = true; + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java b/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java new file mode 100644 index 0000000..8f619be --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java @@ -0,0 +1,439 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.MutableFrameFormat; + +import java.util.Arrays; +import java.util.Map.Entry; + +/** + * @hide + */ +public class FrameFormat { + + public static final int TYPE_UNSPECIFIED = 0; + public static final int TYPE_BIT = 1; + public static final int TYPE_BYTE = 2; + public static final int TYPE_INT16 = 3; + public static final int TYPE_INT32 = 4; + public static final int TYPE_FLOAT = 5; + public static final int TYPE_DOUBLE = 6; + public static final int TYPE_POINTER = 7; + public static final int TYPE_OBJECT = 8; + + public static final int TARGET_UNSPECIFIED = 0; + public static final int TARGET_SIMPLE = 1; + public static final int TARGET_NATIVE = 2; + public static final int TARGET_GPU = 3; + public static final int TARGET_VERTEXBUFFER = 4; + public static final int TARGET_RS = 5; + + public static final int SIZE_UNSPECIFIED = 0; + + // TODO: When convenience formats are used, consider changing this to 0 and have the convenience + // intializers use a proper BPS. + public static final int BYTES_PER_SAMPLE_UNSPECIFIED = 1; + + protected static final int SIZE_UNKNOWN = -1; + + protected int mBaseType = TYPE_UNSPECIFIED; + protected int mBytesPerSample = 1; + protected int mSize = SIZE_UNKNOWN; + protected int mTarget = TARGET_UNSPECIFIED; + protected int[] mDimensions; + protected KeyValueMap mMetaData; + protected Class mObjectClass; + + protected FrameFormat() { + } + + public FrameFormat(int baseType, int target) { + mBaseType = baseType; + mTarget = target; + initDefaults(); + } + + public static FrameFormat unspecified() { + return new FrameFormat(TYPE_UNSPECIFIED, TARGET_UNSPECIFIED); + } + + public int getBaseType() { + return mBaseType; + } + + public boolean isBinaryDataType() { + return mBaseType >= TYPE_BIT && mBaseType <= TYPE_DOUBLE; + } + + public int getBytesPerSample() { + return mBytesPerSample; + } + + public int getValuesPerSample() { + return mBytesPerSample / bytesPerSampleOf(mBaseType); + } + + public int getTarget() { + return mTarget; + } + + public int[] getDimensions() { + return mDimensions; + } + + public int getDimension(int i) { + return mDimensions[i]; + } + + public int getDimensionCount() { + return mDimensions == null ? 0 : mDimensions.length; + } + + public boolean hasMetaKey(String key) { + return mMetaData != null ? mMetaData.containsKey(key) : false; + } + + public boolean hasMetaKey(String key, Class expectedClass) { + if (mMetaData != null && mMetaData.containsKey(key)) { + if (!expectedClass.isAssignableFrom(mMetaData.get(key).getClass())) { + throw new RuntimeException( + "FrameFormat meta-key '" + key + "' is of type " + + mMetaData.get(key).getClass() + " but expected to be of type " + + expectedClass + "!"); + } + return true; + } + return false; + } + + public Object getMetaValue(String key) { + return mMetaData != null ? mMetaData.get(key) : null; + } + + public int getNumberOfDimensions() { + return mDimensions != null ? mDimensions.length : 0; + } + + public int getLength() { + return (mDimensions != null && mDimensions.length >= 1) ? mDimensions[0] : -1; + } + + public int getWidth() { + return getLength(); + } + + public int getHeight() { + return (mDimensions != null && mDimensions.length >= 2) ? mDimensions[1] : -1; + } + + public int getDepth() { + return (mDimensions != null && mDimensions.length >= 3) ? mDimensions[2] : -1; + } + + public int getSize() { + if (mSize == SIZE_UNKNOWN) mSize = calcSize(mDimensions); + return mSize; + } + + public Class getObjectClass() { + return mObjectClass; + } + + public MutableFrameFormat mutableCopy() { + MutableFrameFormat result = new MutableFrameFormat(); + result.setBaseType(getBaseType()); + result.setTarget(getTarget()); + result.setBytesPerSample(getBytesPerSample()); + result.setDimensions(getDimensions()); + result.setObjectClass(getObjectClass()); + result.mMetaData = mMetaData == null ? null : (KeyValueMap)mMetaData.clone(); + return result; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + + if (!(object instanceof FrameFormat)) { + return false; + } + + FrameFormat format = (FrameFormat)object; + return format.mBaseType == mBaseType && + format.mTarget == mTarget && + format.mBytesPerSample == mBytesPerSample && + Arrays.equals(format.mDimensions, mDimensions) && + format.mMetaData.equals(mMetaData); + } + + @Override + public int hashCode() { + return 4211 ^ mBaseType ^ mBytesPerSample ^ getSize(); + } + + public boolean isCompatibleWith(FrameFormat specification) { + // Check base type + if (specification.getBaseType() != TYPE_UNSPECIFIED + && getBaseType() != specification.getBaseType()) { + return false; + } + + // Check target + if (specification.getTarget() != TARGET_UNSPECIFIED + && getTarget() != specification.getTarget()) { + return false; + } + + // Check bytes per sample + if (specification.getBytesPerSample() != BYTES_PER_SAMPLE_UNSPECIFIED + && getBytesPerSample() != specification.getBytesPerSample()) { + return false; + } + + // Check number of dimensions + if (specification.getDimensionCount() > 0 + && getDimensionCount() != specification.getDimensionCount()) { + return false; + } + + // Check dimensions + for (int i = 0; i < specification.getDimensionCount(); ++i) { + int specDim = specification.getDimension(i); + if (specDim != SIZE_UNSPECIFIED && getDimension(i) != specDim) { + return false; + } + } + + // Check class + if (specification.getObjectClass() != null) { + if (getObjectClass() == null + || !specification.getObjectClass().isAssignableFrom(getObjectClass())) { + return false; + } + } + + // Check meta-data + if (specification.mMetaData != null) { + for (String specKey : specification.mMetaData.keySet()) { + if (mMetaData == null + || !mMetaData.containsKey(specKey) + || !mMetaData.get(specKey).equals(specification.mMetaData.get(specKey))) { + return false; + } + } + } + + // Passed all the tests + return true; + } + + public boolean mayBeCompatibleWith(FrameFormat specification) { + // Check base type + if (specification.getBaseType() != TYPE_UNSPECIFIED + && getBaseType() != TYPE_UNSPECIFIED + && getBaseType() != specification.getBaseType()) { + return false; + } + + // Check target + if (specification.getTarget() != TARGET_UNSPECIFIED + && getTarget() != TARGET_UNSPECIFIED + && getTarget() != specification.getTarget()) { + return false; + } + + // Check bytes per sample + if (specification.getBytesPerSample() != BYTES_PER_SAMPLE_UNSPECIFIED + && getBytesPerSample() != BYTES_PER_SAMPLE_UNSPECIFIED + && getBytesPerSample() != specification.getBytesPerSample()) { + return false; + } + + // Check number of dimensions + if (specification.getDimensionCount() > 0 + && getDimensionCount() > 0 + && getDimensionCount() != specification.getDimensionCount()) { + return false; + } + + // Check dimensions + for (int i = 0; i < specification.getDimensionCount(); ++i) { + int specDim = specification.getDimension(i); + if (specDim != SIZE_UNSPECIFIED + && getDimension(i) != SIZE_UNSPECIFIED + && getDimension(i) != specDim) { + return false; + } + } + + // Check class + if (specification.getObjectClass() != null && getObjectClass() != null) { + if (!specification.getObjectClass().isAssignableFrom(getObjectClass())) { + return false; + } + } + + // Check meta-data + if (specification.mMetaData != null && mMetaData != null) { + for (String specKey : specification.mMetaData.keySet()) { + if (mMetaData.containsKey(specKey) + && !mMetaData.get(specKey).equals(specification.mMetaData.get(specKey))) { + return false; + } + } + } + + // Passed all the tests + return true; + } + + public static int bytesPerSampleOf(int baseType) { + // Defaults based on base-type + switch (baseType) { + case TYPE_BIT: + case TYPE_BYTE: + return 1; + case TYPE_INT16: + return 2; + case TYPE_INT32: + case TYPE_FLOAT: + case TYPE_POINTER: + return 4; + case TYPE_DOUBLE: + return 8; + default: + return 1; + } + } + + public static String dimensionsToString(int[] dimensions) { + StringBuffer buffer = new StringBuffer(); + if (dimensions != null) { + int n = dimensions.length; + for (int i = 0; i < n; ++i) { + if (dimensions[i] == SIZE_UNSPECIFIED) { + buffer.append("[]"); + } else { + buffer.append("[" + String.valueOf(dimensions[i]) + "]"); + } + } + } + return buffer.toString(); + } + + public static String baseTypeToString(int baseType) { + switch (baseType) { + case TYPE_UNSPECIFIED: return "unspecified"; + case TYPE_BIT: return "bit"; + case TYPE_BYTE: return "byte"; + case TYPE_INT16: return "int"; + case TYPE_INT32: return "int"; + case TYPE_FLOAT: return "float"; + case TYPE_DOUBLE: return "double"; + case TYPE_POINTER: return "pointer"; + case TYPE_OBJECT: return "object"; + default: return "unknown"; + } + } + + public static String targetToString(int target) { + switch (target) { + case TARGET_UNSPECIFIED: return "unspecified"; + case TARGET_SIMPLE: return "simple"; + case TARGET_NATIVE: return "native"; + case TARGET_GPU: return "gpu"; + case TARGET_VERTEXBUFFER: return "vbo"; + case TARGET_RS: return "renderscript"; + default: return "unknown"; + } + } + + public static String metaDataToString(KeyValueMap metaData) { + if (metaData == null) { + return ""; + } else { + StringBuffer buffer = new StringBuffer(); + buffer.append("{ "); + for (Entry<String, Object> entry : metaData.entrySet()) { + buffer.append(entry.getKey() + ": " + entry.getValue() + " "); + } + buffer.append("}"); + return buffer.toString(); + } + } + + public static int readTargetString(String targetString) { + if (targetString.equalsIgnoreCase("CPU") || targetString.equalsIgnoreCase("NATIVE")) { + return FrameFormat.TARGET_NATIVE; + } else if (targetString.equalsIgnoreCase("GPU")) { + return FrameFormat.TARGET_GPU; + } else if (targetString.equalsIgnoreCase("SIMPLE")) { + return FrameFormat.TARGET_SIMPLE; + } else if (targetString.equalsIgnoreCase("VERTEXBUFFER")) { + return FrameFormat.TARGET_VERTEXBUFFER; + } else if (targetString.equalsIgnoreCase("UNSPECIFIED")) { + return FrameFormat.TARGET_UNSPECIFIED; + } else { + throw new RuntimeException("Unknown target type '" + targetString + "'!"); + } + } + + // TODO: FromString + + public String toString() { + int valuesPerSample = getValuesPerSample(); + String sampleCountString = valuesPerSample == 1 ? "" : String.valueOf(valuesPerSample); + String targetString = mTarget == TARGET_UNSPECIFIED ? "" : (targetToString(mTarget) + " "); + String classString = mObjectClass == null + ? "" + : (" class(" + mObjectClass.getSimpleName() + ") "); + + return targetString + + baseTypeToString(mBaseType) + + sampleCountString + + dimensionsToString(mDimensions) + + classString + + metaDataToString(mMetaData); + } + + private void initDefaults() { + mBytesPerSample = bytesPerSampleOf(mBaseType); + } + + // Core internal methods /////////////////////////////////////////////////////////////////////// + int calcSize(int[] dimensions) { + if (dimensions != null && dimensions.length > 0) { + int size = getBytesPerSample(); + for (int dim : dimensions) { + size *= dim; + } + return size; + } + return 0; + } + + boolean isReplaceableBy(FrameFormat format) { + return mTarget == format.mTarget + && getSize() == format.getSize() + && Arrays.equals(format.mDimensions, mDimensions); + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/FrameManager.java b/media/mca/filterfw/java/android/filterfw/core/FrameManager.java new file mode 100644 index 0000000..8d6c483 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/FrameManager.java @@ -0,0 +1,67 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.MutableFrameFormat; + +/** + * @hide + */ +public abstract class FrameManager { + + private FilterContext mContext; + + public abstract Frame newFrame(FrameFormat format); + + public abstract Frame newBoundFrame(FrameFormat format, int bindingType, long bindingId); + + public Frame duplicateFrame(Frame frame) { + Frame result = newFrame(frame.getFormat()); + result.setDataFromFrame(frame); + return result; + } + + public Frame duplicateFrameToTarget(Frame frame, int newTarget) { + MutableFrameFormat newFormat = frame.getFormat().mutableCopy(); + newFormat.setTarget(newTarget); + Frame result = newFrame(newFormat); + result.setDataFromFrame(frame); + return result; + } + + public abstract Frame retainFrame(Frame frame); + + public abstract Frame releaseFrame(Frame frame); + + public FilterContext getContext() { + return mContext; + } + + public GLEnvironment getGLEnvironment() { + return mContext != null ? mContext.getGLEnvironment() : null; + } + + public void tearDown() { + } + + void setContext(FilterContext context) { + mContext = context; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java b/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java new file mode 100644 index 0000000..fcf5f5d --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java @@ -0,0 +1,180 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.NativeAllocatorTag; +import android.graphics.SurfaceTexture; +import android.os.Looper; +import android.util.Log; +import android.view.Surface; +import android.media.MediaRecorder; + +/** + * @hide + */ +public class GLEnvironment { + + private int glEnvId; + + public GLEnvironment() { + nativeAllocate(); + } + + private GLEnvironment(NativeAllocatorTag tag) { + } + + public synchronized void tearDown() { + if (glEnvId != -1) { + nativeDeallocate(); + glEnvId = -1; + } + } + + @Override + protected void finalize() throws Throwable { + tearDown(); + } + + public void initWithNewContext() { + if (!nativeInitWithNewContext()) { + throw new RuntimeException("Could not initialize GLEnvironment with new context!"); + } + } + + public void initWithCurrentContext() { + if (!nativeInitWithCurrentContext()) { + throw new RuntimeException("Could not initialize GLEnvironment with current context!"); + } + } + + public boolean isActive() { + return nativeIsActive(); + } + + public boolean isContextActive() { + return nativeIsContextActive(); + } + + public static boolean isAnyContextActive() { + return nativeIsAnyContextActive(); + } + + public void activate() { + if (Looper.myLooper() != null && Looper.myLooper().equals(Looper.getMainLooper())) { + Log.e("FilterFramework", "Activating GL context in UI thread!"); + } + if (!nativeActivate()) { + throw new RuntimeException("Could not activate GLEnvironment!"); + } + } + + public void deactivate() { + if (!nativeDeactivate()) { + throw new RuntimeException("Could not deactivate GLEnvironment!"); + } + } + + public void swapBuffers() { + if (!nativeSwapBuffers()) { + throw new RuntimeException("Error swapping EGL buffers!"); + } + } + + public int registerSurface(Surface surface) { + int result = nativeAddSurface(surface); + if (result < 0) { + throw new RuntimeException("Error registering surface " + surface + "!"); + } + return result; + } + + public int registerSurfaceTexture(SurfaceTexture surfaceTexture, int width, int height) { + Surface surface = new Surface(surfaceTexture); + int result = nativeAddSurfaceWidthHeight(surface, width, height); + surface.release(); + if (result < 0) { + throw new RuntimeException("Error registering surfaceTexture " + surfaceTexture + "!"); + } + return result; + } + + public int registerSurfaceFromMediaRecorder(MediaRecorder mediaRecorder) { + int result = nativeAddSurfaceFromMediaRecorder(mediaRecorder); + if (result < 0) { + throw new RuntimeException("Error registering surface from " + + "MediaRecorder" + mediaRecorder + "!"); + } + return result; + } + + public void activateSurfaceWithId(int surfaceId) { + if (!nativeActivateSurfaceId(surfaceId)) { + throw new RuntimeException("Could not activate surface " + surfaceId + "!"); + } + } + + public void unregisterSurfaceId(int surfaceId) { + if (!nativeRemoveSurfaceId(surfaceId)) { + throw new RuntimeException("Could not unregister surface " + surfaceId + "!"); + } + } + + public void setSurfaceTimestamp(long timestamp) { + if (!nativeSetSurfaceTimestamp(timestamp)) { + throw new RuntimeException("Could not set timestamp for current surface!"); + } + } + + static { + System.loadLibrary("filterfw"); + } + + private native boolean nativeInitWithNewContext(); + + private native boolean nativeInitWithCurrentContext(); + + private native boolean nativeIsActive(); + + private native boolean nativeIsContextActive(); + + private static native boolean nativeIsAnyContextActive(); + + private native boolean nativeActivate(); + + private native boolean nativeDeactivate(); + + private native boolean nativeSwapBuffers(); + + private native boolean nativeAllocate(); + + private native boolean nativeDeallocate(); + + private native int nativeAddSurface(Surface surface); + + private native int nativeAddSurfaceWidthHeight(Surface surface, int width, int height); + + private native int nativeAddSurfaceFromMediaRecorder(MediaRecorder mediaRecorder); + + private native boolean nativeDisconnectSurfaceMediaSource(MediaRecorder mediaRecorder); + + private native boolean nativeActivateSurfaceId(int surfaceId); + + private native boolean nativeRemoveSurfaceId(int surfaceId); + + private native boolean nativeSetSurfaceTimestamp(long timestamp); +} diff --git a/media/mca/filterfw/java/android/filterfw/core/GLFrame.java b/media/mca/filterfw/java/android/filterfw/core/GLFrame.java new file mode 100644 index 0000000..1558cc6 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/GLFrame.java @@ -0,0 +1,417 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.StopWatchMap; +import android.graphics.Bitmap; +import android.opengl.GLES20; +import android.graphics.Rect; + +import java.nio.ByteBuffer; + +class GLFrameTimer { + + private static StopWatchMap mTimer = null; + + public static StopWatchMap get() { + if (mTimer == null) { + mTimer = new StopWatchMap(); + } + return mTimer; + } + +} + +/** + * @hide + */ +public class GLFrame extends Frame { + + // GL-related binding types + public final static int EXISTING_TEXTURE_BINDING = 100; + public final static int EXISTING_FBO_BINDING = 101; + public final static int NEW_TEXTURE_BINDING = 102; // TODO: REMOVE THIS + public final static int NEW_FBO_BINDING = 103; // TODO: REMOVE THIS + public final static int EXTERNAL_TEXTURE = 104; + + private int glFrameId = -1; + + /** + * Flag whether we own the texture or not. If we do not, we must be careful when caching or + * storing the frame, as the user may delete, and regenerate it. + */ + private boolean mOwnsTexture = true; + + /** + * Keep a reference to the GL environment, so that it does not get deallocated while there + * are still frames living in it. + */ + private GLEnvironment mGLEnvironment; + + GLFrame(FrameFormat format, FrameManager frameManager) { + super(format, frameManager); + } + + GLFrame(FrameFormat format, FrameManager frameManager, int bindingType, long bindingId) { + super(format, frameManager, bindingType, bindingId); + } + + void init(GLEnvironment glEnv) { + FrameFormat format = getFormat(); + mGLEnvironment = glEnv; + + // Check that we have a valid format + if (format.getBytesPerSample() != 4) { + throw new IllegalArgumentException("GL frames must have 4 bytes per sample!"); + } else if (format.getDimensionCount() != 2) { + throw new IllegalArgumentException("GL frames must be 2-dimensional!"); + } else if (getFormat().getSize() < 0) { + throw new IllegalArgumentException("Initializing GL frame with zero size!"); + } + + // Create correct frame + int bindingType = getBindingType(); + boolean reusable = true; + if (bindingType == Frame.NO_BINDING) { + initNew(false); + } else if (bindingType == EXTERNAL_TEXTURE) { + initNew(true); + reusable = false; + } else if (bindingType == EXISTING_TEXTURE_BINDING) { + initWithTexture((int)getBindingId()); + } else if (bindingType == EXISTING_FBO_BINDING) { + initWithFbo((int)getBindingId()); + } else if (bindingType == NEW_TEXTURE_BINDING) { + initWithTexture((int)getBindingId()); + } else if (bindingType == NEW_FBO_BINDING) { + initWithFbo((int)getBindingId()); + } else { + throw new RuntimeException("Attempting to create GL frame with unknown binding type " + + bindingType + "!"); + } + setReusable(reusable); + } + + private void initNew(boolean isExternal) { + if (isExternal) { + if (!nativeAllocateExternal(mGLEnvironment)) { + throw new RuntimeException("Could not allocate external GL frame!"); + } + } else { + if (!nativeAllocate(mGLEnvironment, getFormat().getWidth(), getFormat().getHeight())) { + throw new RuntimeException("Could not allocate GL frame!"); + } + } + } + + private void initWithTexture(int texId) { + int width = getFormat().getWidth(); + int height = getFormat().getHeight(); + if (!nativeAllocateWithTexture(mGLEnvironment, texId, width, height)) { + throw new RuntimeException("Could not allocate texture backed GL frame!"); + } + mOwnsTexture = false; + markReadOnly(); + } + + private void initWithFbo(int fboId) { + int width = getFormat().getWidth(); + int height = getFormat().getHeight(); + if (!nativeAllocateWithFbo(mGLEnvironment, fboId, width, height)) { + throw new RuntimeException("Could not allocate FBO backed GL frame!"); + } + } + + void flushGPU(String message) { + StopWatchMap timer = GLFrameTimer.get(); + if (timer.LOG_MFF_RUNNING_TIMES) { + timer.start("glFinish " + message); + GLES20.glFinish(); + timer.stop("glFinish " + message); + } + } + + @Override + protected synchronized boolean hasNativeAllocation() { + return glFrameId != -1; + } + + @Override + protected synchronized void releaseNativeAllocation() { + nativeDeallocate(); + glFrameId = -1; + } + + public GLEnvironment getGLEnvironment() { + return mGLEnvironment; + } + + @Override + public Object getObjectValue() { + assertGLEnvValid(); + return ByteBuffer.wrap(getNativeData()); + } + + @Override + public void setInts(int[] ints) { + assertFrameMutable(); + assertGLEnvValid(); + if (!setNativeInts(ints)) { + throw new RuntimeException("Could not set int values for GL frame!"); + } + } + + @Override + public int[] getInts() { + assertGLEnvValid(); + flushGPU("getInts"); + return getNativeInts(); + } + + @Override + public void setFloats(float[] floats) { + assertFrameMutable(); + assertGLEnvValid(); + if (!setNativeFloats(floats)) { + throw new RuntimeException("Could not set int values for GL frame!"); + } + } + + @Override + public float[] getFloats() { + assertGLEnvValid(); + flushGPU("getFloats"); + return getNativeFloats(); + } + + @Override + public void setData(ByteBuffer buffer, int offset, int length) { + assertFrameMutable(); + assertGLEnvValid(); + byte[] bytes = buffer.array(); + if (getFormat().getSize() != bytes.length) { + throw new RuntimeException("Data size in setData does not match GL frame size!"); + } else if (!setNativeData(bytes, offset, length)) { + throw new RuntimeException("Could not set GL frame data!"); + } + } + + @Override + public ByteBuffer getData() { + assertGLEnvValid(); + flushGPU("getData"); + return ByteBuffer.wrap(getNativeData()); + } + + @Override + public void setBitmap(Bitmap bitmap) { + assertFrameMutable(); + assertGLEnvValid(); + if (getFormat().getWidth() != bitmap.getWidth() || + getFormat().getHeight() != bitmap.getHeight()) { + throw new RuntimeException("Bitmap dimensions do not match GL frame dimensions!"); + } else { + Bitmap rgbaBitmap = convertBitmapToRGBA(bitmap); + if (!setNativeBitmap(rgbaBitmap, rgbaBitmap.getByteCount())) { + throw new RuntimeException("Could not set GL frame bitmap data!"); + } + } + } + + @Override + public Bitmap getBitmap() { + assertGLEnvValid(); + flushGPU("getBitmap"); + Bitmap result = Bitmap.createBitmap(getFormat().getWidth(), + getFormat().getHeight(), + Bitmap.Config.ARGB_8888); + if (!getNativeBitmap(result)) { + throw new RuntimeException("Could not get bitmap data from GL frame!"); + } + return result; + } + + @Override + public void setDataFromFrame(Frame frame) { + assertGLEnvValid(); + + // Make sure frame fits + if (getFormat().getSize() < frame.getFormat().getSize()) { + throw new RuntimeException( + "Attempting to assign frame of size " + frame.getFormat().getSize() + " to " + + "smaller GL frame of size " + getFormat().getSize() + "!"); + } + + // Invoke optimized implementations if possible + if (frame instanceof NativeFrame) { + nativeCopyFromNative((NativeFrame)frame); + } else if (frame instanceof GLFrame) { + nativeCopyFromGL((GLFrame)frame); + } else if (frame instanceof SimpleFrame) { + setObjectValue(frame.getObjectValue()); + } else { + super.setDataFromFrame(frame); + } + } + + public void setViewport(int x, int y, int width, int height) { + assertFrameMutable(); + setNativeViewport(x, y, width, height); + } + + public void setViewport(Rect rect) { + assertFrameMutable(); + setNativeViewport(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top); + } + + public void generateMipMap() { + assertFrameMutable(); + assertGLEnvValid(); + if (!generateNativeMipMap()) { + throw new RuntimeException("Could not generate mip-map for GL frame!"); + } + } + + public void setTextureParameter(int param, int value) { + assertFrameMutable(); + assertGLEnvValid(); + if (!setNativeTextureParam(param, value)) { + throw new RuntimeException("Could not set texture value " + param + " = " + value + " " + + "for GLFrame!"); + } + } + + public int getTextureId() { + return getNativeTextureId(); + } + + public int getFboId() { + return getNativeFboId(); + } + + public void focus() { + if (!nativeFocus()) { + throw new RuntimeException("Could not focus on GLFrame for drawing!"); + } + } + + @Override + public String toString() { + return "GLFrame id: " + glFrameId + " (" + getFormat() + ") with texture ID " + + getTextureId() + ", FBO ID " + getFboId(); + } + + @Override + protected void reset(FrameFormat newFormat) { + if (!nativeResetParams()) { + throw new RuntimeException("Could not reset GLFrame texture parameters!"); + } + super.reset(newFormat); + } + + @Override + protected void onFrameStore() { + if (!mOwnsTexture) { + // Detach texture from FBO in case user manipulates it. + nativeDetachTexFromFbo(); + } + } + + @Override + protected void onFrameFetch() { + if (!mOwnsTexture) { + // Reattach texture to FBO when using frame again. This may reallocate the texture + // in case it has become invalid. + nativeReattachTexToFbo(); + } + } + + private void assertGLEnvValid() { + if (!mGLEnvironment.isContextActive()) { + if (GLEnvironment.isAnyContextActive()) { + throw new RuntimeException("Attempting to access " + this + " with foreign GL " + + "context active!"); + } else { + throw new RuntimeException("Attempting to access " + this + " with no GL context " + + " active!"); + } + } + } + + static { + System.loadLibrary("filterfw"); + } + + private native boolean nativeAllocate(GLEnvironment env, int width, int height); + + private native boolean nativeAllocateWithTexture(GLEnvironment env, + int textureId, + int width, + int height); + + private native boolean nativeAllocateWithFbo(GLEnvironment env, + int fboId, + int width, + int height); + + private native boolean nativeAllocateExternal(GLEnvironment env); + + private native boolean nativeDeallocate(); + + private native boolean setNativeData(byte[] data, int offset, int length); + + private native byte[] getNativeData(); + + private native boolean setNativeInts(int[] ints); + + private native boolean setNativeFloats(float[] floats); + + private native int[] getNativeInts(); + + private native float[] getNativeFloats(); + + private native boolean setNativeBitmap(Bitmap bitmap, int size); + + private native boolean getNativeBitmap(Bitmap bitmap); + + private native boolean setNativeViewport(int x, int y, int width, int height); + + private native int getNativeTextureId(); + + private native int getNativeFboId(); + + private native boolean generateNativeMipMap(); + + private native boolean setNativeTextureParam(int param, int value); + + private native boolean nativeResetParams(); + + private native boolean nativeCopyFromNative(NativeFrame frame); + + private native boolean nativeCopyFromGL(GLFrame frame); + + private native boolean nativeFocus(); + + private native boolean nativeReattachTexToFbo(); + + private native boolean nativeDetachTexFromFbo(); +} diff --git a/media/mca/filterfw/java/android/filterfw/core/GenerateFieldPort.java b/media/mca/filterfw/java/android/filterfw/core/GenerateFieldPort.java new file mode 100644 index 0000000..3e37d4f --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/GenerateFieldPort.java @@ -0,0 +1,30 @@ +/* + * 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 android.filterfw.core; + +import java.lang.annotation.*; + +/** + * @hide + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface GenerateFieldPort { + String name() default ""; + boolean hasDefault() default false; +} diff --git a/media/mca/filterfw/java/android/filterfw/core/GenerateFinalPort.java b/media/mca/filterfw/java/android/filterfw/core/GenerateFinalPort.java new file mode 100644 index 0000000..0dec8cc --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/GenerateFinalPort.java @@ -0,0 +1,30 @@ +/* + * 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 android.filterfw.core; + +import java.lang.annotation.*; + +/** + * @hide + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface GenerateFinalPort { + String name() default ""; + boolean hasDefault() default false; +} diff --git a/media/mca/filterfw/java/android/filterfw/core/GenerateProgramPort.java b/media/mca/filterfw/java/android/filterfw/core/GenerateProgramPort.java new file mode 100644 index 0000000..fb40416 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/GenerateProgramPort.java @@ -0,0 +1,32 @@ +/* + * 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 android.filterfw.core; + +import java.lang.annotation.*; + +/** + * @hide + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface GenerateProgramPort { + String name(); + Class type(); + String variableName() default ""; + boolean hasDefault() default false; +} diff --git a/media/mca/filterfw/java/android/filterfw/core/GenerateProgramPorts.java b/media/mca/filterfw/java/android/filterfw/core/GenerateProgramPorts.java new file mode 100644 index 0000000..354126d --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/GenerateProgramPorts.java @@ -0,0 +1,29 @@ +/* + * 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 android.filterfw.core; + +import java.lang.annotation.*; + +/** + * @hide + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface GenerateProgramPorts { + GenerateProgramPort[] value(); +} diff --git a/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java b/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java new file mode 100644 index 0000000..b496c54 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java @@ -0,0 +1,100 @@ +/* + * 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 android.filterfw.core; + +/** + * @hide + */ +public abstract class GraphRunner { + + protected FilterContext mFilterContext = null; + + /** Interface for listeners waiting for the runner to complete. */ + public interface OnRunnerDoneListener { + /** Callback method to be called when the runner completes a + * {@link #run()} call. + * + * @param result will be RESULT_FINISHED if the graph finished running + * on its own, RESULT_STOPPED if the runner was stopped by a call + * to stop(), RESULT_BLOCKED if no filters could run due to lack + * of inputs or outputs or due to scheduling policies, and + * RESULT_SLEEPING if a filter node requested sleep. + */ + public void onRunnerDone(int result); + } + + public static final int RESULT_UNKNOWN = 0; + public static final int RESULT_RUNNING = 1; + public static final int RESULT_FINISHED = 2; + public static final int RESULT_SLEEPING = 3; + public static final int RESULT_BLOCKED = 4; + public static final int RESULT_STOPPED = 5; + public static final int RESULT_ERROR = 6; + + public GraphRunner(FilterContext context) { + mFilterContext = context; + } + + public abstract FilterGraph getGraph(); + + public FilterContext getContext() { + return mFilterContext; + } + + /** + * Helper function for subclasses to activate the GL environment before running. + * @return true, if the GL environment was activated. Returns false, if the GL environment + * was already active. + */ + protected boolean activateGlContext() { + GLEnvironment glEnv = mFilterContext.getGLEnvironment(); + if (glEnv != null && !glEnv.isActive()) { + glEnv.activate(); + return true; + } + return false; + } + + /** + * Helper function for subclasses to deactivate the GL environment after running. + */ + protected void deactivateGlContext() { + GLEnvironment glEnv = mFilterContext.getGLEnvironment(); + if (glEnv != null) { + glEnv.deactivate(); + } + } + + /** Starts running the graph. Will open the filters in the graph if they are not already open. */ + public abstract void run(); + + public abstract void setDoneCallback(OnRunnerDoneListener listener); + public abstract boolean isRunning(); + + /** Stops graph execution. As part of stopping, also closes the graph nodes. */ + public abstract void stop(); + + /** Closes the filters in a graph. Can only be called if the graph is not running. */ + public abstract void close(); + + /** + * Returns the last exception that happened during an asynchronous run. Returns null if + * there is nothing to report. + */ + public abstract Exception getError(); +} diff --git a/media/mca/filterfw/java/android/filterfw/core/InputPort.java b/media/mca/filterfw/java/android/filterfw/core/InputPort.java new file mode 100644 index 0000000..de5cccc --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/InputPort.java @@ -0,0 +1,85 @@ +/* + * 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 android.filterfw.core; + +/** + * @hide + */ +public abstract class InputPort extends FilterPort { + + protected OutputPort mSourcePort; + + public InputPort(Filter filter, String name) { + super(filter, name); + } + + public void setSourcePort(OutputPort source) { + if (mSourcePort != null) { + throw new RuntimeException(this + " already connected to " + mSourcePort + "!"); + } + mSourcePort = source; + } + + public boolean isConnected() { + return mSourcePort != null; + } + + public void open() { + super.open(); + if (mSourcePort != null && !mSourcePort.isOpen()) { + mSourcePort.open(); + } + } + + public void close() { + if (mSourcePort != null && mSourcePort.isOpen()) { + mSourcePort.close(); + } + super.close(); + } + + public OutputPort getSourcePort() { + return mSourcePort; + } + + public Filter getSourceFilter() { + return mSourcePort == null ? null : mSourcePort.getFilter(); + } + + public FrameFormat getSourceFormat() { + return mSourcePort != null ? mSourcePort.getPortFormat() : getPortFormat(); + } + + public Object getTarget() { + return null; + } + + public boolean filterMustClose() { + return !isOpen() && isBlocking() && !hasFrame(); + } + + public boolean isReady() { + return hasFrame() || !isBlocking(); + } + + public boolean acceptsFrame() { + return !hasFrame(); + } + + public abstract void transfer(FilterContext context); +} diff --git a/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java b/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java new file mode 100644 index 0000000..8cf9a13 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/KeyValueMap.java @@ -0,0 +1,82 @@ +/* + * 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 android.filterfw.core; + +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +/** + * @hide + */ +public class KeyValueMap extends HashMap<String, Object> { + + public void setKeyValues(Object... keyValues) { + if (keyValues.length % 2 != 0) { + throw new RuntimeException("Key-Value arguments passed into setKeyValues must be " + + "an alternating list of keys and values!"); + } + for (int i = 0; i < keyValues.length; i += 2) { + if (!(keyValues[i] instanceof String)) { + throw new RuntimeException("Key-value argument " + i + " must be a key of type " + + "String, but found an object of type " + keyValues[i].getClass() + "!"); + } + String key = (String)keyValues[i]; + Object value = keyValues[i+1]; + put(key, value); + } + } + + public static KeyValueMap fromKeyValues(Object... keyValues) { + KeyValueMap result = new KeyValueMap(); + result.setKeyValues(keyValues); + return result; + } + + public String getString(String key) { + Object result = get(key); + return result != null ? (String)result : null; + } + + public int getInt(String key) { + Object result = get(key); + return result != null ? (Integer)result : null; + } + + public float getFloat(String key) { + Object result = get(key); + return result != null ? (Float)result : null; + } + + @Override + public String toString() { + StringWriter writer = new StringWriter(); + for (Map.Entry<String, Object> entry : entrySet()) { + String valueString; + Object value = entry.getValue(); + if (value instanceof String) { + valueString = "\"" + value + "\""; + } else { + valueString = value.toString(); + } + writer.write(entry.getKey() + " = " + valueString + ";\n"); + } + return writer.toString(); + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java b/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java new file mode 100644 index 0000000..8c78975 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java @@ -0,0 +1,96 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.FrameFormat; +import android.filterfw.core.KeyValueMap; + +import java.util.Arrays; + +/** + * @hide + */ +public class MutableFrameFormat extends FrameFormat { + + public MutableFrameFormat() { + super(); + } + + public MutableFrameFormat(int baseType, int target) { + super(baseType, target); + } + + public void setBaseType(int baseType) { + mBaseType = baseType; + mBytesPerSample = bytesPerSampleOf(baseType); + } + + public void setTarget(int target) { + mTarget = target; + } + + public void setBytesPerSample(int bytesPerSample) { + mBytesPerSample = bytesPerSample; + mSize = SIZE_UNKNOWN; + } + + public void setDimensions(int[] dimensions) { + mDimensions = (dimensions == null) ? null : Arrays.copyOf(dimensions, dimensions.length); + mSize = SIZE_UNKNOWN; + } + + public void setDimensions(int size) { + int[] dimensions = new int[1]; + dimensions[0] = size; + mDimensions = dimensions; + mSize = SIZE_UNKNOWN; + } + + public void setDimensions(int width, int height) { + int[] dimensions = new int[2]; + dimensions[0] = width; + dimensions[1] = height; + mDimensions = dimensions; + mSize = SIZE_UNKNOWN; + } + + public void setDimensions(int width, int height, int depth) { + int[] dimensions = new int[3]; + dimensions[0] = width; + dimensions[1] = height; + dimensions[2] = depth; + mDimensions = dimensions; + mSize = SIZE_UNKNOWN; + } + + public void setDimensionCount(int count) { + mDimensions = new int[count]; + } + + public void setObjectClass(Class objectClass) { + mObjectClass = objectClass; + } + + public void setMetaValue(String key, Object value) { + if (mMetaData == null) { + mMetaData = new KeyValueMap(); + } + mMetaData.put(key, value); + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/NativeAllocatorTag.java b/media/mca/filterfw/java/android/filterfw/core/NativeAllocatorTag.java new file mode 100644 index 0000000..4d43d7c --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/NativeAllocatorTag.java @@ -0,0 +1,28 @@ +/* + * 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 android.filterfw.core; + +/** + * This class is simply a place-holder type used to identify calls coming + * from the native layer. This way method signatures can be selected + * that are to be accessed from the native layer only. + * + * @hide + **/ +public class NativeAllocatorTag { +} diff --git a/media/mca/filterfw/java/android/filterfw/core/NativeBuffer.java b/media/mca/filterfw/java/android/filterfw/core/NativeBuffer.java new file mode 100644 index 0000000..80da5ea --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/NativeBuffer.java @@ -0,0 +1,129 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; + +/** + * @hide + */ +public class NativeBuffer { + + // These are set by the native layer + private long mDataPointer = 0; + private int mSize = 0; + + private Frame mAttachedFrame; + + private boolean mOwnsData = false; + private int mRefCount = 1; + + public NativeBuffer() { + } + + public NativeBuffer(int count) { + allocate(count * getElementSize()); + mOwnsData = true; + } + + public NativeBuffer mutableCopy() { + NativeBuffer result = null; + try { + Class myClass = getClass(); + result = (NativeBuffer)myClass.newInstance(); + } catch (Exception e) { + throw new RuntimeException("Unable to allocate a copy of " + getClass() + "! Make " + + "sure the class has a default constructor!"); + } + if (mSize > 0 && !nativeCopyTo(result)) { + throw new RuntimeException("Failed to copy NativeBuffer to mutable instance!"); + } + return result; + } + + public int size() { + return mSize; + } + + public int count() { + return (mDataPointer != 0) ? mSize / getElementSize() : 0; + } + + public int getElementSize() { + return 1; + } + + public NativeBuffer retain() { + if (mAttachedFrame != null) { + mAttachedFrame.retain(); + } else if (mOwnsData) { + ++mRefCount; + } + return this; + } + + public NativeBuffer release() { + // Decrement refcount + boolean doDealloc = false; + if (mAttachedFrame != null) { + doDealloc = (mAttachedFrame.release() == null); + } else if (mOwnsData) { + --mRefCount; + doDealloc = (mRefCount == 0); + } + + // Deallocate if necessary + if (doDealloc) { + deallocate(mOwnsData); + return null; + } else { + return this; + } + } + + public boolean isReadOnly() { + return (mAttachedFrame != null) ? mAttachedFrame.isReadOnly() : false; + } + + static { + System.loadLibrary("filterfw"); + } + + void attachToFrame(Frame frame) { + // We do not auto-retain. We expect the user to call retain() if they want to hold on to + // the frame. + mAttachedFrame = frame; + } + + protected void assertReadable() { + if (mDataPointer == 0 || mSize == 0 + || (mAttachedFrame != null && !mAttachedFrame.hasNativeAllocation())) { + throw new NullPointerException("Attempting to read from null data frame!"); + } + } + + protected void assertWritable() { + if (isReadOnly()) { + throw new RuntimeException("Attempting to modify read-only native (structured) data!"); + } + } + + private native boolean allocate(int size); + private native boolean deallocate(boolean ownsData); + private native boolean nativeCopyTo(NativeBuffer buffer); +} diff --git a/media/mca/filterfw/java/android/filterfw/core/NativeFrame.java b/media/mca/filterfw/java/android/filterfw/core/NativeFrame.java new file mode 100644 index 0000000..bfd09ba --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/NativeFrame.java @@ -0,0 +1,265 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GLFrame; +import android.filterfw.core.NativeBuffer; +import android.graphics.Bitmap; + +import android.util.Log; + +import java.nio.ByteBuffer; + +/** + * @hide + */ +public class NativeFrame extends Frame { + + private int nativeFrameId = -1; + + NativeFrame(FrameFormat format, FrameManager frameManager) { + super(format, frameManager); + int capacity = format.getSize(); + nativeAllocate(capacity); + setReusable(capacity != 0); + } + + @Override + protected synchronized void releaseNativeAllocation() { + nativeDeallocate(); + nativeFrameId = -1; + } + + @Override + protected synchronized boolean hasNativeAllocation() { + return nativeFrameId != -1; + } + + @Override + public int getCapacity() { + return getNativeCapacity(); + } + + /** + * Returns the native frame's Object value. + * + * If the frame's base-type is not TYPE_OBJECT, this returns a data buffer containing the native + * data (this is equivalent to calling getData(). + * If the frame is based on an object type, this type is expected to be a subclass of + * NativeBuffer. The NativeBuffer returned is only valid for as long as the frame is alive. If + * you need to hold on to the returned value, you must retain it. + */ + @Override + public Object getObjectValue() { + // If this is not a structured frame, return our data + if (getFormat().getBaseType() != FrameFormat.TYPE_OBJECT) { + return getData(); + } + + // Get the structure class + Class structClass = getFormat().getObjectClass(); + if (structClass == null) { + throw new RuntimeException("Attempting to get object data from frame that does " + + "not specify a structure object class!"); + } + + // Make sure it is a NativeBuffer subclass + if (!NativeBuffer.class.isAssignableFrom(structClass)) { + throw new RuntimeException("NativeFrame object class must be a subclass of " + + "NativeBuffer!"); + } + + // Instantiate a new empty instance of this class + NativeBuffer structData = null; + try { + structData = (NativeBuffer)structClass.newInstance(); + } catch (Exception e) { + throw new RuntimeException("Could not instantiate new structure instance of type '" + + structClass + "'!"); + } + + // Wrap it around our data + if (!getNativeBuffer(structData)) { + throw new RuntimeException("Could not get the native structured data for frame!"); + } + + // Attach this frame to it + structData.attachToFrame(this); + + return structData; + } + + @Override + public void setInts(int[] ints) { + assertFrameMutable(); + if (ints.length * nativeIntSize() > getFormat().getSize()) { + throw new RuntimeException( + "NativeFrame cannot hold " + ints.length + " integers. (Can only hold " + + (getFormat().getSize() / nativeIntSize()) + " integers)."); + } else if (!setNativeInts(ints)) { + throw new RuntimeException("Could not set int values for native frame!"); + } + } + + @Override + public int[] getInts() { + return getNativeInts(getFormat().getSize()); + } + + @Override + public void setFloats(float[] floats) { + assertFrameMutable(); + if (floats.length * nativeFloatSize() > getFormat().getSize()) { + throw new RuntimeException( + "NativeFrame cannot hold " + floats.length + " floats. (Can only hold " + + (getFormat().getSize() / nativeFloatSize()) + " floats)."); + } else if (!setNativeFloats(floats)) { + throw new RuntimeException("Could not set int values for native frame!"); + } + } + + @Override + public float[] getFloats() { + return getNativeFloats(getFormat().getSize()); + } + + // TODO: This function may be a bit confusing: Is the offset the target or source offset? Maybe + // we should allow specifying both? (May be difficult for other frame types). + @Override + public void setData(ByteBuffer buffer, int offset, int length) { + assertFrameMutable(); + byte[] bytes = buffer.array(); + if ((length + offset) > buffer.limit()) { + throw new RuntimeException("Offset and length exceed buffer size in native setData: " + + (length + offset) + " bytes given, but only " + buffer.limit() + + " bytes available!"); + } else if (getFormat().getSize() != length) { + throw new RuntimeException("Data size in setData does not match native frame size: " + + "Frame size is " + getFormat().getSize() + " bytes, but " + + length + " bytes given!"); + } else if (!setNativeData(bytes, offset, length)) { + throw new RuntimeException("Could not set native frame data!"); + } + } + + @Override + public ByteBuffer getData() { + byte[] data = getNativeData(getFormat().getSize()); + return data == null ? null : ByteBuffer.wrap(data); + } + + @Override + public void setBitmap(Bitmap bitmap) { + assertFrameMutable(); + if (getFormat().getNumberOfDimensions() != 2) { + throw new RuntimeException("Attempting to set Bitmap for non 2-dimensional native frame!"); + } else if (getFormat().getWidth() != bitmap.getWidth() || + getFormat().getHeight() != bitmap.getHeight()) { + throw new RuntimeException("Bitmap dimensions do not match native frame dimensions!"); + } else { + Bitmap rgbaBitmap = convertBitmapToRGBA(bitmap); + int byteCount = rgbaBitmap.getByteCount(); + int bps = getFormat().getBytesPerSample(); + if (!setNativeBitmap(rgbaBitmap, byteCount, bps)) { + throw new RuntimeException("Could not set native frame bitmap data!"); + } + } + } + + @Override + public Bitmap getBitmap() { + if (getFormat().getNumberOfDimensions() != 2) { + throw new RuntimeException("Attempting to get Bitmap for non 2-dimensional native frame!"); + } + Bitmap result = Bitmap.createBitmap(getFormat().getWidth(), + getFormat().getHeight(), + Bitmap.Config.ARGB_8888); + int byteCount = result.getByteCount(); + int bps = getFormat().getBytesPerSample(); + if (!getNativeBitmap(result, byteCount, bps)) { + throw new RuntimeException("Could not get bitmap data from native frame!"); + } + return result; + } + + @Override + public void setDataFromFrame(Frame frame) { + // Make sure frame fits + if (getFormat().getSize() < frame.getFormat().getSize()) { + throw new RuntimeException( + "Attempting to assign frame of size " + frame.getFormat().getSize() + " to " + + "smaller native frame of size " + getFormat().getSize() + "!"); + } + + // Invoke optimized implementations if possible + if (frame instanceof NativeFrame) { + nativeCopyFromNative((NativeFrame)frame); + } else if (frame instanceof GLFrame) { + nativeCopyFromGL((GLFrame)frame); + } else if (frame instanceof SimpleFrame) { + setObjectValue(frame.getObjectValue()); + } else { + super.setDataFromFrame(frame); + } + } + + @Override + public String toString() { + return "NativeFrame id: " + nativeFrameId + " (" + getFormat() + ") of size " + + getCapacity(); + } + + static { + System.loadLibrary("filterfw"); + } + + private native boolean nativeAllocate(int capacity); + + private native boolean nativeDeallocate(); + + private native int getNativeCapacity(); + + private static native int nativeIntSize(); + + private static native int nativeFloatSize(); + + private native boolean setNativeData(byte[] data, int offset, int length); + + private native byte[] getNativeData(int byteCount); + + private native boolean getNativeBuffer(NativeBuffer buffer); + + private native boolean setNativeInts(int[] ints); + + private native boolean setNativeFloats(float[] floats); + + private native int[] getNativeInts(int byteCount); + + private native float[] getNativeFloats(int byteCount); + + private native boolean setNativeBitmap(Bitmap bitmap, int size, int bytesPerSample); + + private native boolean getNativeBitmap(Bitmap bitmap, int size, int bytesPerSample); + + private native boolean nativeCopyFromNative(NativeFrame frame); + + private native boolean nativeCopyFromGL(GLFrame frame); +} diff --git a/media/mca/filterfw/java/android/filterfw/core/NativeProgram.java b/media/mca/filterfw/java/android/filterfw/core/NativeProgram.java new file mode 100644 index 0000000..791ab3c --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/NativeProgram.java @@ -0,0 +1,176 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.Program; + +/** + * @hide + */ +public class NativeProgram extends Program { + + private int nativeProgramId; + private boolean mHasInitFunction = false; + private boolean mHasTeardownFunction = false; + private boolean mHasSetValueFunction = false; + private boolean mHasGetValueFunction = false; + private boolean mHasResetFunction = false; + private boolean mTornDown = false; + + public NativeProgram(String nativeLibName, String nativeFunctionPrefix) { + // Allocate the native instance + allocate(); + + // Open the native library + String fullLibName = "lib" + nativeLibName + ".so"; + if (!openNativeLibrary(fullLibName)) { + throw new RuntimeException("Could not find native library named '" + fullLibName + "' " + + "required for native program!"); + } + + // Bind the native functions + String processFuncName = nativeFunctionPrefix + "_process"; + if (!bindProcessFunction(processFuncName)) { + throw new RuntimeException("Could not find native program function name " + + processFuncName + " in library " + fullLibName + "! " + + "This function is required!"); + } + + String initFuncName = nativeFunctionPrefix + "_init"; + mHasInitFunction = bindInitFunction(initFuncName); + + String teardownFuncName = nativeFunctionPrefix + "_teardown"; + mHasTeardownFunction = bindTeardownFunction(teardownFuncName); + + String setValueFuncName = nativeFunctionPrefix + "_setvalue"; + mHasSetValueFunction = bindSetValueFunction(setValueFuncName); + + String getValueFuncName = nativeFunctionPrefix + "_getvalue"; + mHasGetValueFunction = bindGetValueFunction(getValueFuncName); + + String resetFuncName = nativeFunctionPrefix + "_reset"; + mHasResetFunction = bindResetFunction(resetFuncName); + + // Initialize the native code + if (mHasInitFunction && !callNativeInit()) { + throw new RuntimeException("Could not initialize NativeProgram!"); + } + } + + public void tearDown() { + if (mTornDown) return; + if (mHasTeardownFunction && !callNativeTeardown()) { + throw new RuntimeException("Could not tear down NativeProgram!"); + } + deallocate(); + mTornDown = true; + } + + @Override + public void reset() { + if (mHasResetFunction && !callNativeReset()) { + throw new RuntimeException("Could not reset NativeProgram!"); + } + } + + @Override + protected void finalize() throws Throwable { + tearDown(); + } + + @Override + public void process(Frame[] inputs, Frame output) { + if (mTornDown) { + throw new RuntimeException("NativeProgram already torn down!"); + } + NativeFrame[] nativeInputs = new NativeFrame[inputs.length]; + for (int i = 0; i < inputs.length; ++i) { + if (inputs[i] == null || inputs[i] instanceof NativeFrame) { + nativeInputs[i] = (NativeFrame)inputs[i]; + } else { + throw new RuntimeException("NativeProgram got non-native frame as input "+ i +"!"); + } + } + + // Get the native output frame + NativeFrame nativeOutput = null; + if (output == null || output instanceof NativeFrame) { + nativeOutput = (NativeFrame)output; + } else { + throw new RuntimeException("NativeProgram got non-native output frame!"); + } + + // Process! + if (!callNativeProcess(nativeInputs, nativeOutput)) { + throw new RuntimeException("Calling native process() caused error!"); + } + } + + @Override + public void setHostValue(String variableName, Object value) { + if (mTornDown) { + throw new RuntimeException("NativeProgram already torn down!"); + } + if (!mHasSetValueFunction) { + throw new RuntimeException("Attempting to set native variable, but native code does not " + + "define native setvalue function!"); + } + if (!callNativeSetValue(variableName, value.toString())) { + throw new RuntimeException("Error setting native value for variable '" + variableName + "'!"); + } + } + + @Override + public Object getHostValue(String variableName) { + if (mTornDown) { + throw new RuntimeException("NativeProgram already torn down!"); + } + if (!mHasGetValueFunction) { + throw new RuntimeException("Attempting to get native variable, but native code does not " + + "define native getvalue function!"); + } + return callNativeGetValue(variableName); + } + + static { + System.loadLibrary("filterfw"); + } + + private native boolean allocate(); + + private native boolean deallocate(); + + private native boolean nativeInit(); + + private native boolean openNativeLibrary(String libName); + + private native boolean bindInitFunction(String funcName); + private native boolean bindSetValueFunction(String funcName); + private native boolean bindGetValueFunction(String funcName); + private native boolean bindProcessFunction(String funcName); + private native boolean bindResetFunction(String funcName); + private native boolean bindTeardownFunction(String funcName); + + private native boolean callNativeInit(); + private native boolean callNativeSetValue(String key, String value); + private native String callNativeGetValue(String key); + private native boolean callNativeProcess(NativeFrame[] inputs, NativeFrame output); + private native boolean callNativeReset(); + private native boolean callNativeTeardown(); +} diff --git a/media/mca/filterfw/java/android/filterfw/core/OneShotScheduler.java b/media/mca/filterfw/java/android/filterfw/core/OneShotScheduler.java new file mode 100644 index 0000000..dbc8d16 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/OneShotScheduler.java @@ -0,0 +1,78 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Filter; +import android.filterfw.core.Scheduler; +import android.filterfw.core.RoundRobinScheduler; +import android.util.Log; + +import java.util.HashMap; + +/** + * This OneShotScheduler only schedules source filters at most once. All other + * filters will be scheduled, and possibly repeatedly, until there is no filter + * that can be scheduled. + * + * @hide + */ +public class OneShotScheduler extends RoundRobinScheduler { + private HashMap <String, Integer> scheduled; + + private final boolean mLogVerbose; + private static final String TAG = "OneShotScheduler"; + + public OneShotScheduler(FilterGraph graph) { + super(graph); + scheduled = new HashMap<String, Integer>(); + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + } + + @Override + public void reset() { + super.reset(); + scheduled.clear(); + } + + @Override + public Filter scheduleNextNode() { + Filter first = null; + // return the first filter that is not scheduled before. + while (true) { + Filter filter = super.scheduleNextNode(); + if (filter == null) { + if (mLogVerbose) Log.v(TAG, "No filters available to run."); + return null; + } + if (!scheduled.containsKey(filter.getName())) { + if (filter.getNumberOfConnectedInputs() == 0) + scheduled.put(filter.getName(),1); + if (mLogVerbose) Log.v(TAG, "Scheduling filter \"" + filter.getName() + "\" of type " + filter.getFilterClassName()); + return filter; + } + // if loop back, nothing available + if (first == filter) { + break; + } + // save the first scheduled one + if (first == null) first = filter; + } + if (mLogVerbose) Log.v(TAG, "One pass through graph completed."); + return null; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/OutputPort.java b/media/mca/filterfw/java/android/filterfw/core/OutputPort.java new file mode 100644 index 0000000..872dbdd --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/OutputPort.java @@ -0,0 +1,122 @@ +/* + * 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 android.filterfw.core; + +/** + * @hide + */ +public class OutputPort extends FilterPort { + + protected InputPort mTargetPort; + protected InputPort mBasePort; + + public OutputPort(Filter filter, String name) { + super(filter, name); + } + + public void connectTo(InputPort target) { + if (mTargetPort != null) { + throw new RuntimeException(this + " already connected to " + mTargetPort + "!"); + } + mTargetPort = target; + mTargetPort.setSourcePort(this); + } + + public boolean isConnected() { + return mTargetPort != null; + } + + public void open() { + super.open(); + if (mTargetPort != null && !mTargetPort.isOpen()) { + mTargetPort.open(); + } + } + + public void close() { + super.close(); + if (mTargetPort != null && mTargetPort.isOpen()) { + mTargetPort.close(); + } + } + + public InputPort getTargetPort() { + return mTargetPort; + } + + public Filter getTargetFilter() { + return mTargetPort == null ? null : mTargetPort.getFilter(); + } + + public void setBasePort(InputPort basePort) { + mBasePort = basePort; + } + + public InputPort getBasePort() { + return mBasePort; + } + + public boolean filterMustClose() { + return !isOpen() && isBlocking(); + } + + public boolean isReady() { + return (isOpen() && mTargetPort.acceptsFrame()) || !isBlocking(); + } + + @Override + public void clear() { + if (mTargetPort != null) { + mTargetPort.clear(); + } + } + + @Override + public void pushFrame(Frame frame) { + if (mTargetPort == null) { + throw new RuntimeException( + "Attempting to push frame on unconnected port: " + this + "!"); + } + mTargetPort.pushFrame(frame); + } + + @Override + public void setFrame(Frame frame) { + assertPortIsOpen(); + if (mTargetPort == null) { + throw new RuntimeException( + "Attempting to set frame on unconnected port: " + this + "!"); + } + mTargetPort.setFrame(frame); + } + + @Override + public Frame pullFrame() { + throw new RuntimeException("Cannot pull frame on " + this + "!"); + } + + @Override + public boolean hasFrame() { + return mTargetPort == null ? false : mTargetPort.hasFrame(); + } + + @Override + public String toString() { + return "output " + super.toString(); + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/Program.java b/media/mca/filterfw/java/android/filterfw/core/Program.java new file mode 100644 index 0000000..1930648 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/Program.java @@ -0,0 +1,41 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; + +/** + * @hide + */ +public abstract class Program { + + public abstract void process(Frame[] inputs, Frame output); + + public void process(Frame input, Frame output) { + Frame[] inputs = new Frame[1]; + inputs[0] = input; + process(inputs, output); + } + + public abstract void setHostValue(String variableName, Object value); + + public abstract Object getHostValue(String variableName); + + public void reset() { + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/ProgramPort.java b/media/mca/filterfw/java/android/filterfw/core/ProgramPort.java new file mode 100644 index 0000000..3cab26d --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/ProgramPort.java @@ -0,0 +1,62 @@ +/* + * 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 android.filterfw.core; + +import java.lang.reflect.Field; + +/** + * @hide + */ +public class ProgramPort extends FieldPort { + + protected String mVarName; + + public ProgramPort(Filter filter, + String name, + String varName, + Field field, + boolean hasDefault) { + super(filter, name, field, hasDefault); + mVarName = varName; + } + + @Override + public String toString() { + return "Program " + super.toString(); + } + + @Override + public synchronized void transfer(FilterContext context) { + if (mValueWaiting) { + try { + Object fieldValue = mField.get(mFilter); + if (fieldValue != null) { + Program program = (Program)fieldValue; + program.setHostValue(mVarName, mValue); + mValueWaiting = false; + } + } catch (IllegalAccessException e) { + throw new RuntimeException( + "Access to program field '" + mField.getName() + "' was denied!"); + } catch (ClassCastException e) { + throw new RuntimeException("Non Program field '" + mField.getName() + + "' annotated with ProgramParameter!"); + } + } + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/ProgramVariable.java b/media/mca/filterfw/java/android/filterfw/core/ProgramVariable.java new file mode 100644 index 0000000..5592d37 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/ProgramVariable.java @@ -0,0 +1,57 @@ +/* + * 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 android.filterfw.core; + +/** + * @hide + */ +public class ProgramVariable { + + private Program mProgram; + private String mVarName; + + public ProgramVariable(Program program, String varName) { + mProgram = program; + mVarName = varName; + } + + public Program getProgram() { + return mProgram; + } + + public String getVariableName() { + return mVarName; + } + + public void setValue(Object value) { + if (mProgram == null) { + throw new RuntimeException("Attempting to set program variable '" + mVarName + + "' but the program is null!"); + } + mProgram.setHostValue(mVarName, value); + } + + public Object getValue() { + if (mProgram == null) { + throw new RuntimeException("Attempting to get program variable '" + mVarName + + "' but the program is null!"); + } + return mProgram.getHostValue(mVarName); + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/ProtocolException.java b/media/mca/filterfw/java/android/filterfw/core/ProtocolException.java new file mode 100644 index 0000000..2c7a29a --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/ProtocolException.java @@ -0,0 +1,33 @@ +/* + * 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 android.filterfw.core; + +/** + * @hide + */ +public class ProtocolException extends RuntimeException { + + public ProtocolException() { + super(); + } + + public ProtocolException(String message) { + super(message); + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/RandomScheduler.java b/media/mca/filterfw/java/android/filterfw/core/RandomScheduler.java new file mode 100644 index 0000000..087f5db --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/RandomScheduler.java @@ -0,0 +1,54 @@ +/* + * 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 android.filterfw.core; + +import java.util.Random; +import java.util.Vector; + +import android.filterfw.core.Filter; +import android.filterfw.core.Scheduler; + +/** + * @hide + */ +public class RandomScheduler extends Scheduler { + + private Random mRand = new Random(); + + public RandomScheduler(FilterGraph graph) { + super(graph); + } + + @Override + public void reset() { + } + + @Override + public Filter scheduleNextNode() { + Vector<Filter> candidates = new Vector<Filter>(); + for (Filter filter : getGraph().getFilters()) { + if (filter.canProcess()) + candidates.add(filter); + } + if (candidates.size() > 0) { + int r = mRand.nextInt(candidates.size()); + return candidates.elementAt(r); + } + return null; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/RoundRobinScheduler.java b/media/mca/filterfw/java/android/filterfw/core/RoundRobinScheduler.java new file mode 100644 index 0000000..12cbf19 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/RoundRobinScheduler.java @@ -0,0 +1,73 @@ +/* + * 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 android.filterfw.core; + +import java.util.Set; + +import android.filterfw.core.Filter; +import android.filterfw.core.Scheduler; + +/** + * @hide + */ +public class RoundRobinScheduler extends Scheduler { + + private int mLastPos = -1; + + public RoundRobinScheduler(FilterGraph graph) { + super(graph); + } + + @Override + public void reset() { + mLastPos = -1; + } + + @Override + public Filter scheduleNextNode() { + Set<Filter> all_filters = getGraph().getFilters(); + if (mLastPos >= all_filters.size()) mLastPos = -1; + int pos = 0; + Filter first = null; + int firstNdx = -1; + for (Filter filter : all_filters) { + if (filter.canProcess()) { + if (pos <= mLastPos) { + if (first == null) { + // store the first available filter + first = filter; + firstNdx = pos; + } + } else { + // return the next available filter since last + mLastPos = pos; + return filter; + } + } + pos ++; + } + // going around from the beginning + if (first != null ) { + mLastPos = firstNdx; + return first; + } + // if there is nothing to be scheduled, still keep the previous + // position. + return null; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/Scheduler.java b/media/mca/filterfw/java/android/filterfw/core/Scheduler.java new file mode 100644 index 0000000..6f0864a --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/Scheduler.java @@ -0,0 +1,47 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterGraph; + +/** + * @hide + */ +public abstract class Scheduler { + // All methods are core internal methods as Scheduler internals are only used by the GraphRunner. + + private FilterGraph mGraph; + + Scheduler(FilterGraph graph) { + mGraph = graph; + } + + FilterGraph getGraph() { + return mGraph; + } + + abstract void reset(); + + abstract Filter scheduleNextNode(); + + boolean finished() { + // TODO: Check that the state of all nodes is FINISHED. + return true; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/SerializedFrame.java b/media/mca/filterfw/java/android/filterfw/core/SerializedFrame.java new file mode 100644 index 0000000..f493fd2 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/SerializedFrame.java @@ -0,0 +1,287 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.NativeBuffer; +import android.filterfw.format.ObjectFormat; +import android.graphics.Bitmap; + +import java.io.InputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OptionalDataException; +import java.io.OutputStream; +import java.io.StreamCorruptedException; +import java.lang.reflect.Constructor; +import java.nio.ByteBuffer; + +/** + * A frame that serializes any assigned values. Such a frame is used when passing data objects + * between threads. + * + * @hide + */ +public class SerializedFrame extends Frame { + + /** + * The initial capacity of the serialized data stream. + */ + private final static int INITIAL_CAPACITY = 64; + + /** + * The internal data streams. + */ + private DirectByteOutputStream mByteOutputStream; + private ObjectOutputStream mObjectOut; + + /** + * An unsynchronized output stream that writes data to an accessible byte array. Callers are + * responsible for synchronization. This is more efficient than a ByteArrayOutputStream, as + * there are no array copies or synchronization involved to read back written data. + */ + private class DirectByteOutputStream extends OutputStream { + private byte[] mBuffer = null; + private int mOffset = 0; + private int mDataOffset = 0; + + public DirectByteOutputStream(int size) { + mBuffer = new byte[size]; + } + + private final void ensureFit(int bytesToWrite) { + if (mOffset + bytesToWrite > mBuffer.length) { + byte[] oldBuffer = mBuffer; + mBuffer = new byte[Math.max(mOffset + bytesToWrite, mBuffer.length * 2)]; + System.arraycopy(oldBuffer, 0, mBuffer, 0, mOffset); + oldBuffer = null; + } + } + + public final void markHeaderEnd() { + mDataOffset = mOffset; + } + + public final int getSize() { + return mOffset; + } + + public byte[] getByteArray() { + return mBuffer; + } + + @Override + public final void write(byte b[]) { + write(b, 0, b.length); + } + + @Override + public final void write(byte b[], int off, int len) { + ensureFit(len); + System.arraycopy(b, off, mBuffer, mOffset, len); + mOffset += len; + } + + @Override + public final void write(int b) { + ensureFit(1); + mBuffer[mOffset++] = (byte)b; + } + + public final void reset() { + mOffset = mDataOffset; + } + + public final DirectByteInputStream getInputStream() { + return new DirectByteInputStream(mBuffer, mOffset); + } + } + + /** + * An unsynchronized input stream that reads data directly from a provided byte array. Callers + * are responsible for synchronization and ensuring that the byte buffer is valid. + */ + private class DirectByteInputStream extends InputStream { + + private byte[] mBuffer; + private int mPos = 0; + private int mSize; + + public DirectByteInputStream(byte[] buffer, int size) { + mBuffer = buffer; + mSize = size; + } + + @Override + public final int available() { + return mSize - mPos; + } + + @Override + public final int read() { + return (mPos < mSize) ? (mBuffer[mPos++] & 0xFF) : -1; + } + + @Override + public final int read(byte[] b, int off, int len) { + if (mPos >= mSize) { + return -1; + } + if ((mPos + len) > mSize) { + len = mSize - mPos; + } + System.arraycopy(mBuffer, mPos, b, off, len); + mPos += len; + return len; + } + + @Override + public final long skip(long n) { + if ((mPos + n) > mSize) { + n = mSize - mPos; + } + if (n < 0) { + return 0; + } + mPos += n; + return n; + } + } + + SerializedFrame(FrameFormat format, FrameManager frameManager) { + super(format, frameManager); + setReusable(false); + + // Setup streams + try { + mByteOutputStream = new DirectByteOutputStream(INITIAL_CAPACITY); + mObjectOut = new ObjectOutputStream(mByteOutputStream); + mByteOutputStream.markHeaderEnd(); + } catch (IOException e) { + throw new RuntimeException("Could not create serialization streams for " + + "SerializedFrame!", e); + } + } + + static SerializedFrame wrapObject(Object object, FrameManager frameManager) { + FrameFormat format = ObjectFormat.fromObject(object, FrameFormat.TARGET_SIMPLE); + SerializedFrame result = new SerializedFrame(format, frameManager); + result.setObjectValue(object); + return result; + } + + @Override + protected boolean hasNativeAllocation() { + return false; + } + + @Override + protected void releaseNativeAllocation() { + } + + @Override + public Object getObjectValue() { + return deserializeObjectValue(); + } + + @Override + public void setInts(int[] ints) { + assertFrameMutable(); + setGenericObjectValue(ints); + } + + @Override + public int[] getInts() { + Object result = deserializeObjectValue(); + return (result instanceof int[]) ? (int[])result : null; + } + + @Override + public void setFloats(float[] floats) { + assertFrameMutable(); + setGenericObjectValue(floats); + } + + @Override + public float[] getFloats() { + Object result = deserializeObjectValue(); + return (result instanceof float[]) ? (float[])result : null; + } + + @Override + public void setData(ByteBuffer buffer, int offset, int length) { + assertFrameMutable(); + setGenericObjectValue(ByteBuffer.wrap(buffer.array(), offset, length)); + } + + @Override + public ByteBuffer getData() { + Object result = deserializeObjectValue(); + return (result instanceof ByteBuffer) ? (ByteBuffer)result : null; + } + + @Override + public void setBitmap(Bitmap bitmap) { + assertFrameMutable(); + setGenericObjectValue(bitmap); + } + + @Override + public Bitmap getBitmap() { + Object result = deserializeObjectValue(); + return (result instanceof Bitmap) ? (Bitmap)result : null; + } + + @Override + protected void setGenericObjectValue(Object object) { + serializeObjectValue(object); + } + + private final void serializeObjectValue(Object object) { + try { + mByteOutputStream.reset(); + mObjectOut.writeObject(object); + mObjectOut.flush(); + mObjectOut.close(); + } catch (IOException e) { + throw new RuntimeException("Could not serialize object " + object + " in " + + this + "!", e); + } + } + + private final Object deserializeObjectValue() { + try { + InputStream inputStream = mByteOutputStream.getInputStream(); + ObjectInputStream objectStream = new ObjectInputStream(inputStream); + return objectStream.readObject(); + } catch (IOException e) { + throw new RuntimeException("Could not deserialize object in " + this + "!", e); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Unable to deserialize object of unknown class in " + + this + "!", e); + } + } + + @Override + public String toString() { + return "SerializedFrame (" + getFormat() + ")"; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java b/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java new file mode 100644 index 0000000..a971cb6 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java @@ -0,0 +1,301 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.NativeAllocatorTag; +import android.filterfw.core.Program; +import android.filterfw.core.StopWatchMap; +import android.filterfw.core.VertexFrame; +import android.filterfw.geometry.Quad; +import android.opengl.GLES20; + +/** + * @hide + */ +public class ShaderProgram extends Program { + + private int shaderProgramId; + + private int mMaxTileSize = 0; + + // Keep a reference to the GL environment, so that it does not get deallocated while there + // are still programs living in it. + private GLEnvironment mGLEnvironment; + + private StopWatchMap mTimer = null; + + private void setTimer() { + mTimer = new StopWatchMap(); + } + + // Used from native layer for creating empty wrapper only! + private ShaderProgram() { + } + + private ShaderProgram(NativeAllocatorTag tag) { + } + + public ShaderProgram(FilterContext context, String fragmentShader) { + mGLEnvironment = getGLEnvironment(context); + allocate(mGLEnvironment, null, fragmentShader); + if (!compileAndLink()) { + throw new RuntimeException("Could not compile and link shader!"); + } + this.setTimer(); + } + + public ShaderProgram(FilterContext context, String vertexShader, String fragmentShader) { + mGLEnvironment = getGLEnvironment(context); + allocate(mGLEnvironment, vertexShader, fragmentShader); + if (!compileAndLink()) { + throw new RuntimeException("Could not compile and link shader!"); + } + this.setTimer(); + } + + public static ShaderProgram createIdentity(FilterContext context) { + ShaderProgram program = nativeCreateIdentity(getGLEnvironment(context)); + program.setTimer(); + return program; + } + + @Override + protected void finalize() throws Throwable { + deallocate(); + } + + public GLEnvironment getGLEnvironment() { + return mGLEnvironment; + } + + @Override + public void process(Frame[] inputs, Frame output) { + if (mTimer.LOG_MFF_RUNNING_TIMES) { + mTimer.start("glFinish"); + GLES20.glFinish(); + mTimer.stop("glFinish"); + } + + // Get the GL input frames + // TODO: We do the same in the NativeProgram... can we find a better way?! + GLFrame[] glInputs = new GLFrame[inputs.length]; + for (int i = 0; i < inputs.length; ++i) { + if (inputs[i] instanceof GLFrame) { + glInputs[i] = (GLFrame)inputs[i]; + } else { + throw new RuntimeException("ShaderProgram got non-GL frame as input " + i + "!"); + } + } + + // Get the GL output frame + GLFrame glOutput = null; + if (output instanceof GLFrame) { + glOutput = (GLFrame)output; + } else { + throw new RuntimeException("ShaderProgram got non-GL output frame!"); + } + + // Adjust tiles to meet maximum tile size requirement + if (mMaxTileSize > 0) { + int xTiles = (output.getFormat().getWidth() + mMaxTileSize - 1) / mMaxTileSize; + int yTiles = (output.getFormat().getHeight() + mMaxTileSize - 1) / mMaxTileSize; + setShaderTileCounts(xTiles, yTiles); + } + + // Process! + if (!shaderProcess(glInputs, glOutput)) { + throw new RuntimeException("Error executing ShaderProgram!"); + } + + if (mTimer.LOG_MFF_RUNNING_TIMES) { + GLES20.glFinish(); + } + } + + @Override + public void setHostValue(String variableName, Object value) { + if (!setUniformValue(variableName, value)) { + throw new RuntimeException("Error setting uniform value for variable '" + + variableName + "'!"); + } + } + + @Override + public Object getHostValue(String variableName) { + return getUniformValue(variableName); + } + + public void setAttributeValues(String attributeName, float[] data, int componentCount) { + if (!setShaderAttributeValues(attributeName, data, componentCount)) { + throw new RuntimeException("Error setting attribute value for attribute '" + + attributeName + "'!"); + } + } + + public void setAttributeValues(String attributeName, + VertexFrame vertexData, + int type, + int componentCount, + int strideInBytes, + int offsetInBytes, + boolean normalize) { + if (!setShaderAttributeVertexFrame(attributeName, + vertexData, + type, + componentCount, + strideInBytes, + offsetInBytes, + normalize)) { + throw new RuntimeException("Error setting attribute value for attribute '" + + attributeName + "'!"); + } + } + + public void setSourceRegion(Quad region) { + setSourceRegion(region.p0.x, region.p0.y, + region.p1.x, region.p1.y, + region.p2.x, region.p2.y, + region.p3.x, region.p3.y); + } + + public void setTargetRegion(Quad region) { + setTargetRegion(region.p0.x, region.p0.y, + region.p1.x, region.p1.y, + region.p2.x, region.p2.y, + region.p3.x, region.p3.y); + } + + public void setSourceRect(float x, float y, float width, float height) { + setSourceRegion(x, y, x + width, y, x, y + height, x + width, y + height); + } + + public void setTargetRect(float x, float y, float width, float height) { + setTargetRegion(x, y, x + width, y, x, y + height, x + width, y + height); + } + + public void setClearsOutput(boolean clears) { + if (!setShaderClearsOutput(clears)) { + throw new RuntimeException("Could not set clears-output flag to " + clears + "!"); + } + } + + public void setClearColor(float r, float g, float b) { + if (!setShaderClearColor(r, g, b)) { + throw new RuntimeException("Could not set clear color to " + r + "," + g + "," + b + "!"); + } + } + + public void setBlendEnabled(boolean enable) { + if (!setShaderBlendEnabled(enable)) { + throw new RuntimeException("Could not set Blending " + enable + "!"); + } + } + + public void setBlendFunc(int sfactor, int dfactor) { + if (!setShaderBlendFunc(sfactor, dfactor)) { + throw new RuntimeException("Could not set BlendFunc " + sfactor +","+ dfactor + "!"); + } + } + + public void setDrawMode(int drawMode) { + if (!setShaderDrawMode(drawMode)) { + throw new RuntimeException("Could not set GL draw-mode to " + drawMode + "!"); + } + } + + public void setVertexCount(int count) { + if (!setShaderVertexCount(count)) { + throw new RuntimeException("Could not set GL vertex count to " + count + "!"); + } + } + + public void setMaximumTileSize(int size) { + mMaxTileSize = size; + } + + public void beginDrawing() { + if (!beginShaderDrawing()) { + throw new RuntimeException("Could not prepare shader-program for drawing!"); + } + } + + private static GLEnvironment getGLEnvironment(FilterContext context) { + GLEnvironment result = context != null ? context.getGLEnvironment() : null; + if (result == null) { + throw new NullPointerException("Attempting to create ShaderProgram with no GL " + + "environment in place!"); + } + return result; + } + + static { + System.loadLibrary("filterfw"); + } + + private native boolean allocate(GLEnvironment glEnv, + String vertexShader, + String fragmentShader); + + private native boolean deallocate(); + + private native boolean compileAndLink(); + + private native boolean shaderProcess(GLFrame[] inputs, GLFrame output); + + private native boolean setUniformValue(String name, Object value); + + private native Object getUniformValue(String name); + + public native boolean setSourceRegion(float x0, float y0, float x1, float y1, + float x2, float y2, float x3, float y3); + + private native boolean setTargetRegion(float x0, float y0, float x1, float y1, + float x2, float y2, float x3, float y3); + + private static native ShaderProgram nativeCreateIdentity(GLEnvironment glEnv); + + private native boolean setShaderClearsOutput(boolean clears); + + private native boolean setShaderBlendEnabled(boolean enable); + + private native boolean setShaderBlendFunc(int sfactor, int dfactor); + + private native boolean setShaderClearColor(float r, float g, float b); + + private native boolean setShaderDrawMode(int drawMode); + + private native boolean setShaderTileCounts(int xCount, int yCount); + + private native boolean setShaderVertexCount(int vertexCount); + + private native boolean beginShaderDrawing(); + + private native boolean setShaderAttributeValues(String attributeName, + float[] data, + int componentCount); + + private native boolean setShaderAttributeVertexFrame(String attributeName, + VertexFrame vertexData, + int type, + int componentCount, + int strideInBytes, + int offsetInBytes, + boolean normalize); + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/SimpleFrame.java b/media/mca/filterfw/java/android/filterfw/core/SimpleFrame.java new file mode 100644 index 0000000..534a30d --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/SimpleFrame.java @@ -0,0 +1,161 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.NativeBuffer; +import android.filterfw.format.ObjectFormat; +import android.graphics.Bitmap; + +import java.lang.reflect.Constructor; +import java.nio.ByteBuffer; + +/** + * @hide + */ +public class SimpleFrame extends Frame { + + private Object mObject; + + SimpleFrame(FrameFormat format, FrameManager frameManager) { + super(format, frameManager); + initWithFormat(format); + setReusable(false); + } + + static SimpleFrame wrapObject(Object object, FrameManager frameManager) { + FrameFormat format = ObjectFormat.fromObject(object, FrameFormat.TARGET_SIMPLE); + SimpleFrame result = new SimpleFrame(format, frameManager); + result.setObjectValue(object); + return result; + } + + private void initWithFormat(FrameFormat format) { + final int count = format.getLength(); + final int baseType = format.getBaseType(); + switch (baseType) { + case FrameFormat.TYPE_BYTE: + mObject = new byte[count]; + break; + case FrameFormat.TYPE_INT16: + mObject = new short[count]; + break; + case FrameFormat.TYPE_INT32: + mObject = new int[count]; + break; + case FrameFormat.TYPE_FLOAT: + mObject = new float[count]; + break; + case FrameFormat.TYPE_DOUBLE: + mObject = new double[count]; + break; + default: + mObject = null; + break; + } + } + + @Override + protected boolean hasNativeAllocation() { + return false; + } + + @Override + protected void releaseNativeAllocation() { + } + + @Override + public Object getObjectValue() { + return mObject; + } + + @Override + public void setInts(int[] ints) { + assertFrameMutable(); + setGenericObjectValue(ints); + } + + @Override + public int[] getInts() { + return (mObject instanceof int[]) ? (int[])mObject : null; + } + + @Override + public void setFloats(float[] floats) { + assertFrameMutable(); + setGenericObjectValue(floats); + } + + @Override + public float[] getFloats() { + return (mObject instanceof float[]) ? (float[])mObject : null; + } + + @Override + public void setData(ByteBuffer buffer, int offset, int length) { + assertFrameMutable(); + setGenericObjectValue(ByteBuffer.wrap(buffer.array(), offset, length)); + } + + @Override + public ByteBuffer getData() { + return (mObject instanceof ByteBuffer) ? (ByteBuffer)mObject : null; + } + + @Override + public void setBitmap(Bitmap bitmap) { + assertFrameMutable(); + setGenericObjectValue(bitmap); + } + + @Override + public Bitmap getBitmap() { + return (mObject instanceof Bitmap) ? (Bitmap)mObject : null; + } + + private void setFormatObjectClass(Class objectClass) { + MutableFrameFormat format = getFormat().mutableCopy(); + format.setObjectClass(objectClass); + setFormat(format); + } + + @Override + protected void setGenericObjectValue(Object object) { + // Update the FrameFormat class + // TODO: Take this out! FrameFormats should not be modified and convenience formats used + // instead! + FrameFormat format = getFormat(); + if (format.getObjectClass() == null) { + setFormatObjectClass(object.getClass()); + } else if (!format.getObjectClass().isAssignableFrom(object.getClass())) { + throw new RuntimeException( + "Attempting to set object value of type '" + object.getClass() + "' on " + + "SimpleFrame of type '" + format.getObjectClass() + "'!"); + } + + // Set the object value + mObject = object; + } + + @Override + public String toString() { + return "SimpleFrame (" + getFormat() + ")"; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/SimpleFrameManager.java b/media/mca/filterfw/java/android/filterfw/core/SimpleFrameManager.java new file mode 100644 index 0000000..e2b9047 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/SimpleFrameManager.java @@ -0,0 +1,107 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.filterfw.core.GLFrame; +import android.filterfw.core.NativeFrame; +import android.filterfw.core.SimpleFrame; +import android.filterfw.core.VertexFrame; + +/** + * @hide + */ +public class SimpleFrameManager extends FrameManager { + + public SimpleFrameManager() { + } + + @Override + public Frame newFrame(FrameFormat format) { + return createNewFrame(format); + } + + @Override + public Frame newBoundFrame(FrameFormat format, int bindingType, long bindingId) { + Frame result = null; + switch(format.getTarget()) { + case FrameFormat.TARGET_GPU: { + GLFrame glFrame = new GLFrame(format, this, bindingType, bindingId); + glFrame.init(getGLEnvironment()); + result = glFrame; + break; + } + + default: + throw new RuntimeException("Attached frames are not supported for target type: " + + FrameFormat.targetToString(format.getTarget()) + "!"); + } + return result; + } + + private Frame createNewFrame(FrameFormat format) { + Frame result = null; + switch(format.getTarget()) { + case FrameFormat.TARGET_SIMPLE: + result = new SimpleFrame(format, this); + break; + + case FrameFormat.TARGET_NATIVE: + result = new NativeFrame(format, this); + break; + + case FrameFormat.TARGET_GPU: { + GLFrame glFrame = new GLFrame(format, this); + glFrame.init(getGLEnvironment()); + result = glFrame; + break; + } + + case FrameFormat.TARGET_VERTEXBUFFER: { + result = new VertexFrame(format, this); + break; + } + + default: + throw new RuntimeException("Unsupported frame target type: " + + FrameFormat.targetToString(format.getTarget()) + "!"); + } + return result; + } + + @Override + public Frame retainFrame(Frame frame) { + frame.incRefCount(); + return frame; + } + + @Override + public Frame releaseFrame(Frame frame) { + int refCount = frame.decRefCount(); + if (refCount == 0 && frame.hasNativeAllocation()) { + frame.releaseNativeAllocation(); + return null; + } else if (refCount < 0) { + throw new RuntimeException("Frame reference count dropped below 0!"); + } + return frame; + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/SimpleScheduler.java b/media/mca/filterfw/java/android/filterfw/core/SimpleScheduler.java new file mode 100644 index 0000000..bb4e5ba --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/SimpleScheduler.java @@ -0,0 +1,45 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Filter; +import android.filterfw.core.Scheduler; + +/** + * @hide + */ +public class SimpleScheduler extends Scheduler { + + public SimpleScheduler(FilterGraph graph) { + super(graph); + } + + @Override + public void reset() { + } + + @Override + public Filter scheduleNextNode() { + for (Filter filter : getGraph().getFilters()) { + if (filter.canProcess()) + return filter; + } + return null; + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/StopWatchMap.java b/media/mca/filterfw/java/android/filterfw/core/StopWatchMap.java new file mode 100644 index 0000000..444a1fc --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/StopWatchMap.java @@ -0,0 +1,101 @@ +/* + * 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 android.filterfw.core; + +import android.os.SystemClock; +import android.util.Log; +import java.util.HashMap; + +/** + * @hide + */ +class StopWatch { + + private int STOP_WATCH_LOGGING_PERIOD = 200; + private String TAG = "MFF"; + + private String mName; + private long mStartTime; + private long mTotalTime; + private int mNumCalls; + + public StopWatch(String name) { + mName = name; + mStartTime = -1; + mTotalTime = 0; + mNumCalls = 0; + } + + public void start() { + if (mStartTime != -1) { + throw new RuntimeException( + "Calling start with StopWatch already running"); + } + mStartTime = SystemClock.elapsedRealtime(); + } + + public void stop() { + if (mStartTime == -1) { + throw new RuntimeException( + "Calling stop with StopWatch already stopped"); + } + long stopTime = SystemClock.elapsedRealtime(); + mTotalTime += stopTime - mStartTime; + ++mNumCalls; + mStartTime = -1; + if (mNumCalls % STOP_WATCH_LOGGING_PERIOD == 0) { + Log.i(TAG, "AVG ms/call " + mName + ": " + + String.format("%.1f", mTotalTime * 1.0f / mNumCalls)); + mTotalTime = 0; + mNumCalls = 0; + } + } + +} + +public class StopWatchMap { + + public boolean LOG_MFF_RUNNING_TIMES = false; + + private HashMap<String, StopWatch> mStopWatches = null; + + public StopWatchMap() { + mStopWatches = new HashMap<String, StopWatch>(); + } + + public void start(String stopWatchName) { + if (!LOG_MFF_RUNNING_TIMES) { + return; + } + if (!mStopWatches.containsKey(stopWatchName)) { + mStopWatches.put(stopWatchName, new StopWatch(stopWatchName)); + } + mStopWatches.get(stopWatchName).start(); + } + + public void stop(String stopWatchName) { + if (!LOG_MFF_RUNNING_TIMES) { + return; + } + if (!mStopWatches.containsKey(stopWatchName)) { + throw new RuntimeException( + "Calling stop with unknown stopWatchName: " + stopWatchName); + } + mStopWatches.get(stopWatchName).stop(); + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/core/StreamPort.java b/media/mca/filterfw/java/android/filterfw/core/StreamPort.java new file mode 100644 index 0000000..8520a0b --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/StreamPort.java @@ -0,0 +1,100 @@ +/* + * 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 android.filterfw.core; + +/** + * @hide + */ +public class StreamPort extends InputPort { + + private Frame mFrame; + private boolean mPersistent; + + public StreamPort(Filter filter, String name) { + super(filter, name); + } + + @Override + public void clear() { + if (mFrame != null) { + mFrame.release(); + mFrame = null; + } + } + + @Override + public void setFrame(Frame frame) { + assignFrame(frame, true); + } + + @Override + public void pushFrame(Frame frame) { + assignFrame(frame, false); + } + + protected synchronized void assignFrame(Frame frame, boolean persistent) { + assertPortIsOpen(); + checkFrameType(frame, persistent); + + if (persistent) { + if (mFrame != null) { + mFrame.release(); + } + } else if (mFrame != null) { + throw new RuntimeException( + "Attempting to push more than one frame on port: " + this + "!"); + } + mFrame = frame.retain(); + mFrame.markReadOnly(); + mPersistent = persistent; + } + + @Override + public synchronized Frame pullFrame() { + // Make sure we have a frame + if (mFrame == null) { + throw new RuntimeException("No frame available to pull on port: " + this + "!"); + } + + // Return a retained result + Frame result = mFrame; + if (mPersistent) { + mFrame.retain(); + } else { + mFrame = null; + } + return result; + } + + @Override + public synchronized boolean hasFrame() { + return mFrame != null; + } + + @Override + public String toString() { + return "input " + super.toString(); + } + + @Override + public synchronized void transfer(FilterContext context) { + if (mFrame != null) { + checkFrameManager(mFrame, context); + } + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/SyncRunner.java b/media/mca/filterfw/java/android/filterfw/core/SyncRunner.java new file mode 100644 index 0000000..abbd359 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/SyncRunner.java @@ -0,0 +1,227 @@ +/* + * 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 android.filterfw.core; + +import android.os.ConditionVariable; +import android.util.Log; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * @hide + */ +public class SyncRunner extends GraphRunner { + + private Scheduler mScheduler = null; + + private OnRunnerDoneListener mDoneListener = null; + private ScheduledThreadPoolExecutor mWakeExecutor = new ScheduledThreadPoolExecutor(1); + private ConditionVariable mWakeCondition = new ConditionVariable(); + + private StopWatchMap mTimer = null; + + private final boolean mLogVerbose; + private final static String TAG = "SyncRunner"; + + // TODO: Provide factory based constructor? + public SyncRunner(FilterContext context, FilterGraph graph, Class schedulerClass) { + super(context); + + mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); + + if (mLogVerbose) Log.v(TAG, "Initializing SyncRunner"); + + // Create the scheduler + if (Scheduler.class.isAssignableFrom(schedulerClass)) { + try { + Constructor schedulerConstructor = schedulerClass.getConstructor(FilterGraph.class); + mScheduler = (Scheduler)schedulerConstructor.newInstance(graph); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Scheduler does not have constructor <init>(FilterGraph)!", e); + } catch (InstantiationException e) { + throw new RuntimeException("Could not instantiate the Scheduler instance!", e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Cannot access Scheduler constructor!", e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Scheduler constructor threw an exception", e); + } catch (Exception e) { + throw new RuntimeException("Could not instantiate Scheduler", e); + } + } else { + throw new IllegalArgumentException("Class provided is not a Scheduler subclass!"); + } + + // Associate this runner and the graph with the context + mFilterContext = context; + mFilterContext.addGraph(graph); + + mTimer = new StopWatchMap(); + + if (mLogVerbose) Log.v(TAG, "Setting up filters"); + + // Setup graph filters + graph.setupFilters(); + } + + @Override + public FilterGraph getGraph() { + return mScheduler != null ? mScheduler.getGraph() : null; + } + + public int step() { + assertReadyToStep(); + if (!getGraph().isReady() ) { + throw new RuntimeException("Trying to process graph that is not open!"); + } + return performStep() ? RESULT_RUNNING : determinePostRunState(); + } + + public void beginProcessing() { + mScheduler.reset(); + getGraph().beginProcessing(); + } + + public void close() { + // Close filters + if (mLogVerbose) Log.v(TAG, "Closing graph."); + getGraph().closeFilters(mFilterContext); + mScheduler.reset(); + } + + @Override + public void run() { + if (mLogVerbose) Log.v(TAG, "Beginning run."); + + assertReadyToStep(); + + // Preparation + beginProcessing(); + boolean glActivated = activateGlContext(); + + // Run + boolean keepRunning = true; + while (keepRunning) { + keepRunning = performStep(); + } + + // Cleanup + if (glActivated) { + deactivateGlContext(); + } + + // Call completion callback if set + if (mDoneListener != null) { + if (mLogVerbose) Log.v(TAG, "Calling completion listener."); + mDoneListener.onRunnerDone(determinePostRunState()); + } + if (mLogVerbose) Log.v(TAG, "Run complete"); + } + + @Override + public boolean isRunning() { + return false; + } + + @Override + public void setDoneCallback(OnRunnerDoneListener listener) { + mDoneListener = listener; + } + + @Override + public void stop() { + throw new RuntimeException("SyncRunner does not support stopping a graph!"); + } + + @Override + synchronized public Exception getError() { + return null; + } + + protected void waitUntilWake() { + mWakeCondition.block(); + } + + protected void processFilterNode(Filter filter) { + if (mLogVerbose) Log.v(TAG, "Processing filter node"); + filter.performProcess(mFilterContext); + if (filter.getStatus() == Filter.STATUS_ERROR) { + throw new RuntimeException("There was an error executing " + filter + "!"); + } else if (filter.getStatus() == Filter.STATUS_SLEEPING) { + if (mLogVerbose) Log.v(TAG, "Scheduling filter wakeup"); + scheduleFilterWake(filter, filter.getSleepDelay()); + } + } + + protected void scheduleFilterWake(Filter filter, int delay) { + // Close the wake condition + mWakeCondition.close(); + + // Schedule the wake-up + final Filter filterToSchedule = filter; + final ConditionVariable conditionToWake = mWakeCondition; + + mWakeExecutor.schedule(new Runnable() { + @Override + public void run() { + filterToSchedule.unsetStatus(Filter.STATUS_SLEEPING); + conditionToWake.open(); + } + }, delay, TimeUnit.MILLISECONDS); + } + + protected int determinePostRunState() { + boolean isBlocked = false; + for (Filter filter : mScheduler.getGraph().getFilters()) { + if (filter.isOpen()) { + if (filter.getStatus() == Filter.STATUS_SLEEPING) { + // If ANY node is sleeping, we return our state as sleeping + return RESULT_SLEEPING; + } else { + // If a node is still open, it is blocked (by input or output) + return RESULT_BLOCKED; + } + } + } + return RESULT_FINISHED; + } + + // Core internal methods /////////////////////////////////////////////////////////////////////// + boolean performStep() { + if (mLogVerbose) Log.v(TAG, "Performing one step."); + Filter filter = mScheduler.scheduleNextNode(); + if (filter != null) { + mTimer.start(filter.getName()); + processFilterNode(filter); + mTimer.stop(filter.getName()); + return true; + } else { + return false; + } + } + + void assertReadyToStep() { + if (mScheduler == null) { + throw new RuntimeException("Attempting to run schedule with no scheduler in place!"); + } else if (getGraph() == null) { + throw new RuntimeException("Calling step on scheduler with no graph in place!"); + } + } +} diff --git a/media/mca/filterfw/java/android/filterfw/core/VertexFrame.java b/media/mca/filterfw/java/android/filterfw/core/VertexFrame.java new file mode 100644 index 0000000..6982ce3 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/VertexFrame.java @@ -0,0 +1,143 @@ +/* + * 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 android.filterfw.core; + +import android.filterfw.core.Frame; +import android.filterfw.core.FrameFormat; +import android.filterfw.core.FrameManager; +import android.graphics.Bitmap; + +import java.nio.ByteBuffer; + +/** + * @hide + */ +public class VertexFrame extends Frame { + + private int vertexFrameId = -1; + + VertexFrame(FrameFormat format, FrameManager frameManager) { + super(format, frameManager); + if (getFormat().getSize() <= 0) { + throw new IllegalArgumentException("Initializing vertex frame with zero size!"); + } else { + if (!nativeAllocate(getFormat().getSize())) { + throw new RuntimeException("Could not allocate vertex frame!"); + } + } + } + + @Override + protected synchronized boolean hasNativeAllocation() { + return vertexFrameId != -1; + } + + @Override + protected synchronized void releaseNativeAllocation() { + nativeDeallocate(); + vertexFrameId = -1; + } + + @Override + public Object getObjectValue() { + throw new RuntimeException("Vertex frames do not support reading data!"); + } + + @Override + public void setInts(int[] ints) { + assertFrameMutable(); + if (!setNativeInts(ints)) { + throw new RuntimeException("Could not set int values for vertex frame!"); + } + } + + @Override + public int[] getInts() { + throw new RuntimeException("Vertex frames do not support reading data!"); + } + + @Override + public void setFloats(float[] floats) { + assertFrameMutable(); + if (!setNativeFloats(floats)) { + throw new RuntimeException("Could not set int values for vertex frame!"); + } + } + + @Override + public float[] getFloats() { + throw new RuntimeException("Vertex frames do not support reading data!"); + } + + @Override + public void setData(ByteBuffer buffer, int offset, int length) { + assertFrameMutable(); + byte[] bytes = buffer.array(); + if (getFormat().getSize() != bytes.length) { + throw new RuntimeException("Data size in setData does not match vertex frame size!"); + } else if (!setNativeData(bytes, offset, length)) { + throw new RuntimeException("Could not set vertex frame data!"); + } + } + + @Override + public ByteBuffer getData() { + throw new RuntimeException("Vertex frames do not support reading data!"); + } + + @Override + public void setBitmap(Bitmap bitmap) { + throw new RuntimeException("Unsupported: Cannot set vertex frame bitmap value!"); + } + + @Override + public Bitmap getBitmap() { + throw new RuntimeException("Vertex frames do not support reading data!"); + } + + @Override + public void setDataFromFrame(Frame frame) { + // TODO: Optimize + super.setDataFromFrame(frame); + } + + public int getVboId() { + return getNativeVboId(); + } + + @Override + public String toString() { + return "VertexFrame (" + getFormat() + ") with VBO ID " + getVboId(); + } + + static { + System.loadLibrary("filterfw"); + } + + private native boolean nativeAllocate(int size); + + private native boolean nativeDeallocate(); + + private native boolean setNativeData(byte[] data, int offset, int length); + + private native boolean setNativeInts(int[] ints); + + private native boolean setNativeFloats(float[] floats); + + private native int getNativeVboId(); +} diff --git a/media/mca/filterfw/java/android/filterfw/core/package-info.java b/media/mca/filterfw/java/android/filterfw/core/package-info.java new file mode 100644 index 0000000..4afda1b --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/core/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterfw.core; diff --git a/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java b/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java new file mode 100644 index 0000000..d57f47c --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java @@ -0,0 +1,92 @@ +/* + * 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 android.filterfw.format; + +import android.filterfw.core.FrameFormat; +import android.filterfw.core.MutableFrameFormat; +import android.graphics.Bitmap; + +/** + * @hide + */ +public class ImageFormat { + + public static final String COLORSPACE_KEY = "colorspace"; + + public static final int COLORSPACE_GRAY = 1; + public static final int COLORSPACE_RGB = 2; + public static final int COLORSPACE_RGBA = 3; + public static final int COLORSPACE_YUV = 4; + + public static MutableFrameFormat create(int width, + int height, + int colorspace, + int bytesPerSample, + int target) { + MutableFrameFormat result = new MutableFrameFormat(FrameFormat.TYPE_BYTE, target); + result.setDimensions(width, height); + result.setBytesPerSample(bytesPerSample); + result.setMetaValue(COLORSPACE_KEY, colorspace); + if (target == FrameFormat.TARGET_SIMPLE) { + result.setObjectClass(Bitmap.class); + } + return result; + } + + public static MutableFrameFormat create(int width, + int height, + int colorspace, + int target) { + return create(width, + height, + colorspace, + bytesPerSampleForColorspace(colorspace), + target); + } + + public static MutableFrameFormat create(int colorspace, int target) { + return create(FrameFormat.SIZE_UNSPECIFIED, + FrameFormat.SIZE_UNSPECIFIED, + colorspace, + bytesPerSampleForColorspace(colorspace), + target); + } + + public static MutableFrameFormat create(int colorspace) { + return create(FrameFormat.SIZE_UNSPECIFIED, + FrameFormat.SIZE_UNSPECIFIED, + colorspace, + bytesPerSampleForColorspace(colorspace), + FrameFormat.TARGET_UNSPECIFIED); + } + + public static int bytesPerSampleForColorspace(int colorspace) { + switch (colorspace) { + case COLORSPACE_GRAY: + return 1; + case COLORSPACE_RGB: + return 3; + case COLORSPACE_RGBA: + return 4; + case COLORSPACE_YUV: + return 3; + default: + throw new RuntimeException("Unknown colorspace id " + colorspace + "!"); + } + } +} diff --git a/media/mca/filterfw/java/android/filterfw/format/ObjectFormat.java b/media/mca/filterfw/java/android/filterfw/format/ObjectFormat.java new file mode 100644 index 0000000..ae39628 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/format/ObjectFormat.java @@ -0,0 +1,105 @@ +/* + * 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 android.filterfw.format; + +import android.filterfw.core.FrameFormat; +import android.filterfw.core.MutableFrameFormat; +import android.filterfw.core.NativeBuffer; + +/** + * @hide + */ +public class ObjectFormat { + + public static MutableFrameFormat fromClass(Class clazz, int count, int target) { + // Create frame format + MutableFrameFormat result = new MutableFrameFormat(FrameFormat.TYPE_OBJECT, target); + result.setObjectClass(getBoxedClass(clazz)); + if (count != FrameFormat.SIZE_UNSPECIFIED) { + result.setDimensions(count); + } + result.setBytesPerSample(bytesPerSampleForClass(clazz, target)); + return result; + } + + public static MutableFrameFormat fromClass(Class clazz, int target) { + return fromClass(clazz, FrameFormat.SIZE_UNSPECIFIED, target); + } + + public static MutableFrameFormat fromObject(Object object, int target) { + return object == null + ? new MutableFrameFormat(FrameFormat.TYPE_OBJECT, target) + : fromClass(object.getClass(), FrameFormat.SIZE_UNSPECIFIED, target); + } + + public static MutableFrameFormat fromObject(Object object, int count, int target) { + return object == null + ? new MutableFrameFormat(FrameFormat.TYPE_OBJECT, target) + : fromClass(object.getClass(), count, target); + } + + private static int bytesPerSampleForClass(Class clazz, int target) { + // Native targets have objects manifested in a byte buffer. Thus it is important to + // correctly determine the size of single element here. + if (target == FrameFormat.TARGET_NATIVE) { + if (!NativeBuffer.class.isAssignableFrom(clazz)) { + throw new IllegalArgumentException("Native object-based formats must be of a " + + "NativeBuffer subclass! (Received class: " + clazz + ")."); + } + try { + return ((NativeBuffer)clazz.newInstance()).getElementSize(); + } catch (Exception e) { + throw new RuntimeException("Could not determine the size of an element in a " + + "native object-based frame of type " + clazz + "! Perhaps it is missing a " + + "default constructor?"); + } + } else { + return FrameFormat.BYTES_PER_SAMPLE_UNSPECIFIED; + } + } + + private static Class getBoxedClass(Class type) { + // Check if type is primitive + if (type.isPrimitive()) { + // Yes -> box it + if (type == boolean.class) { + return java.lang.Boolean.class; + } else if (type == byte.class) { + return java.lang.Byte.class; + } else if (type == char.class) { + return java.lang.Character.class; + } else if (type == short.class) { + return java.lang.Short.class; + } else if (type == int.class) { + return java.lang.Integer.class; + } else if (type == long.class) { + return java.lang.Long.class; + } else if (type == float.class) { + return java.lang.Float.class; + } else if (type == double.class) { + return java.lang.Double.class; + } else { + throw new IllegalArgumentException( + "Unknown primitive type: " + type.getSimpleName() + "!"); + } + } else { + // No -> return it + return type; + } + } +} diff --git a/media/mca/filterfw/java/android/filterfw/format/PrimitiveFormat.java b/media/mca/filterfw/java/android/filterfw/format/PrimitiveFormat.java new file mode 100644 index 0000000..40f07aa --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/format/PrimitiveFormat.java @@ -0,0 +1,79 @@ +/* + * 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 android.filterfw.format; + +import android.filterfw.core.FrameFormat; +import android.filterfw.core.MutableFrameFormat; + +/** + * @hide + */ +public class PrimitiveFormat { + + public static MutableFrameFormat createByteFormat(int count, int target) { + return createFormat(FrameFormat.TYPE_BYTE, count, target); + } + + public static MutableFrameFormat createInt16Format(int count, int target) { + return createFormat(FrameFormat.TYPE_INT16, count, target); + } + + public static MutableFrameFormat createInt32Format(int count, int target) { + return createFormat(FrameFormat.TYPE_INT32, count, target); + } + + public static MutableFrameFormat createFloatFormat(int count, int target) { + return createFormat(FrameFormat.TYPE_FLOAT, count, target); + } + + public static MutableFrameFormat createDoubleFormat(int count, int target) { + return createFormat(FrameFormat.TYPE_DOUBLE, count, target); + } + + public static MutableFrameFormat createByteFormat(int target) { + return createFormat(FrameFormat.TYPE_BYTE, target); + } + + public static MutableFrameFormat createInt16Format(int target) { + return createFormat(FrameFormat.TYPE_INT16, target); + } + + public static MutableFrameFormat createInt32Format(int target) { + return createFormat(FrameFormat.TYPE_INT32, target); + } + + public static MutableFrameFormat createFloatFormat(int target) { + return createFormat(FrameFormat.TYPE_FLOAT, target); + } + + public static MutableFrameFormat createDoubleFormat(int target) { + return createFormat(FrameFormat.TYPE_DOUBLE, target); + } + + private static MutableFrameFormat createFormat(int baseType, int count, int target) { + MutableFrameFormat result = new MutableFrameFormat(baseType, target); + result.setDimensions(count); + return result; + } + + private static MutableFrameFormat createFormat(int baseType, int target) { + MutableFrameFormat result = new MutableFrameFormat(baseType, target); + result.setDimensionCount(1); + return result; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/format/package-info.java b/media/mca/filterfw/java/android/filterfw/format/package-info.java new file mode 100644 index 0000000..dfd9a3f --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/format/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterfw.format; diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Point.java b/media/mca/filterfw/java/android/filterfw/geometry/Point.java new file mode 100644 index 0000000..8207c72c --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/geometry/Point.java @@ -0,0 +1,113 @@ +/* + * 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 android.filterfw.geometry; + +import java.lang.Math; + +/** + * @hide + */ +public class Point { + + public float x; + public float y; + + public Point() { + } + + public Point(float x, float y) { + this.x = x; + this.y = y; + } + + public void set(float x, float y) { + this.x = x; + this.y = y; + } + + public boolean IsInUnitRange() { + return x >= 0.0f && x <= 1.0f && + y >= 0.0f && y <= 1.0f; + } + + public Point plus(float x, float y) { + return new Point(this.x + x, this.y + y); + } + + public Point plus(Point point) { + return this.plus(point.x, point.y); + } + + public Point minus(float x, float y) { + return new Point(this.x - x, this.y - y); + } + + public Point minus(Point point) { + return this.minus(point.x, point.y); + } + + public Point times(float s) { + return new Point(this.x * s, this.y * s); + } + + public Point mult(float x, float y) { + return new Point(this.x * x, this.y * y); + } + + public float length() { + return (float)Math.sqrt(x*x + y*y); + } + + public float distanceTo(Point p) { + return p.minus(this).length(); + } + + public Point scaledTo(float length) { + return this.times(length / this.length()); + } + + public Point normalize() { + return this.scaledTo(1.0f); + } + + public Point rotated90(int count) { + float nx = this.x; + float ny = this.y; + for (int i = 0; i < count; ++i) { + float ox = nx; + nx = ny; + ny = -ox; + } + return new Point(nx, ny); + } + + public Point rotated(float radians) { + // TODO(renn): Optimize: Keep cache of cos/sin values + return new Point((float)(Math.cos(radians) * x - Math.sin(radians) * y), + (float)(Math.sin(radians) * x + Math.cos(radians) * y)); + } + + public Point rotatedAround(Point center, float radians) { + return this.minus(center).rotated(radians).plus(center); + } + + @Override + public String toString() { + return "(" + x + ", " + y + ")"; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Quad.java b/media/mca/filterfw/java/android/filterfw/geometry/Quad.java new file mode 100644 index 0000000..ee092fd --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/geometry/Quad.java @@ -0,0 +1,94 @@ +/* + * 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 android.filterfw.geometry; + +import android.filterfw.geometry.Point; + +import java.lang.Float; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * @hide + */ +public class Quad { + + public Point p0; + public Point p1; + public Point p2; + public Point p3; + + public Quad() { + } + + public Quad(Point p0, Point p1, Point p2, Point p3) { + this.p0 = p0; + this.p1 = p1; + this.p2 = p2; + this.p3 = p3; + } + + public boolean IsInUnitRange() { + return p0.IsInUnitRange() && + p1.IsInUnitRange() && + p2.IsInUnitRange() && + p3.IsInUnitRange(); + } + + public Quad translated(Point t) { + return new Quad(p0.plus(t), p1.plus(t), p2.plus(t), p3.plus(t)); + } + + public Quad translated(float x, float y) { + return new Quad(p0.plus(x, y), p1.plus(x, y), p2.plus(x, y), p3.plus(x, y)); + } + + public Quad scaled(float s) { + return new Quad(p0.times(s), p1.times(s), p2.times(s), p3.times(s)); + } + + public Quad scaled(float x, float y) { + return new Quad(p0.mult(x, y), p1.mult(x, y), p2.mult(x, y), p3.mult(x, y)); + } + + public Rectangle boundingBox() { + List<Float> xs = Arrays.asList(p0.x, p1.x, p2.x, p3.x); + List<Float> ys = Arrays.asList(p0.y, p1.y, p2.y, p3.y); + float x0 = Collections.min(xs); + float y0 = Collections.min(ys); + float x1 = Collections.max(xs); + float y1 = Collections.max(ys); + return new Rectangle(x0, y0, x1 - x0, y1 - y0); + } + + public float getBoundingWidth() { + List<Float> xs = Arrays.asList(p0.x, p1.x, p2.x, p3.x); + return Collections.max(xs) - Collections.min(xs); + } + + public float getBoundingHeight() { + List<Float> ys = Arrays.asList(p0.y, p1.y, p2.y, p3.y); + return Collections.max(ys) - Collections.min(ys); + } + + @Override + public String toString() { + return "{" + p0 + ", " + p1 + ", " + p2 + ", " + p3 + "}"; + } +} diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Rectangle.java b/media/mca/filterfw/java/android/filterfw/geometry/Rectangle.java new file mode 100644 index 0000000..e4bd622 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/geometry/Rectangle.java @@ -0,0 +1,95 @@ +/* + * 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 android.filterfw.geometry; + +import android.filterfw.geometry.Point; +import android.filterfw.geometry.Quad; + +/** + * @hide + */ +public class Rectangle extends Quad { + + public Rectangle() { + } + + public Rectangle(float x, float y, float width, float height) { + super(new Point(x, y), + new Point(x + width, y), + new Point(x, y + height), + new Point(x + width, y + height)); + } + + public Rectangle(Point origin, Point size) { + super(origin, + origin.plus(size.x, 0.0f), + origin.plus(0.0f, size.y), + origin.plus(size.x, size.y)); + } + + public static Rectangle fromRotatedRect(Point center, Point size, float rotation) { + Point p0 = new Point(center.x - size.x/2f, center.y - size.y/2f); + Point p1 = new Point(center.x + size.x/2f, center.y - size.y/2f); + Point p2 = new Point(center.x - size.x/2f, center.y + size.y/2f); + Point p3 = new Point(center.x + size.x/2f, center.y + size.y/2f); + return new Rectangle(p0.rotatedAround(center, rotation), + p1.rotatedAround(center, rotation), + p2.rotatedAround(center, rotation), + p3.rotatedAround(center, rotation)); + } + + private Rectangle(Point p0, Point p1, Point p2, Point p3) { + super(p0, p1, p2, p3); + } + + public static Rectangle fromCenterVerticalAxis(Point center, Point vAxis, Point size) { + Point dy = vAxis.scaledTo(size.y / 2.0f); + Point dx = vAxis.rotated90(1).scaledTo(size.x / 2.0f); + return new Rectangle(center.minus(dx).minus(dy), + center.plus(dx).minus(dy), + center.minus(dx).plus(dy), + center.plus(dx).plus(dy)); + } + + public float getWidth() { + return p1.minus(p0).length(); + } + + public float getHeight() { + return p2.minus(p0).length(); + } + + public Point center() { + return p0.plus(p1).plus(p2).plus(p3).times(0.25f); + } + + @Override + public Rectangle scaled(float s) { + return new Rectangle(p0.times(s), p1.times(s), p2.times(s), p3.times(s)); + } + + @Override + public Rectangle scaled(float x, float y) { + return new Rectangle(p0.mult(x, y), p1.mult(x, y), p2.mult(x, y), p3.mult(x, y)); + } + + //public Rectangle rotated(float radians) { + // TODO: Implement this. + //} + +} diff --git a/media/mca/filterfw/java/android/filterfw/geometry/package-info.java b/media/mca/filterfw/java/android/filterfw/geometry/package-info.java new file mode 100644 index 0000000..5547622 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/geometry/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterfw.geometry; diff --git a/media/mca/filterfw/java/android/filterfw/io/GraphIOException.java b/media/mca/filterfw/java/android/filterfw/io/GraphIOException.java new file mode 100644 index 0000000..940b393 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/io/GraphIOException.java @@ -0,0 +1,33 @@ +/* + * 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 android.filterfw.io; + +/** + * @hide + */ +public class GraphIOException extends Exception { + + public GraphIOException() { + super(); + } + + public GraphIOException(String message) { + super(message); + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/io/GraphReader.java b/media/mca/filterfw/java/android/filterfw/io/GraphReader.java new file mode 100644 index 0000000..deb06e2 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/io/GraphReader.java @@ -0,0 +1,69 @@ +/* + * 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 android.filterfw.io; + +import android.content.Context; +import android.filterfw.core.FilterGraph; +import android.filterfw.core.KeyValueMap; +import android.filterfw.io.GraphIOException; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.StringWriter; + +/** + * @hide + */ +public abstract class GraphReader { + + protected KeyValueMap mReferences = new KeyValueMap(); + + public abstract FilterGraph readGraphString(String graphString) throws GraphIOException; + + public abstract KeyValueMap readKeyValueAssignments(String assignments) throws GraphIOException; + + public FilterGraph readGraphResource(Context context, int resourceId) throws GraphIOException { + InputStream inputStream = context.getResources().openRawResource(resourceId); + InputStreamReader reader = new InputStreamReader(inputStream); + StringWriter writer = new StringWriter(); + char[] buffer = new char[1024]; + try { + int bytesRead; + while ((bytesRead = reader.read(buffer, 0, 1024)) > 0) { + writer.write(buffer, 0, bytesRead); + } + } catch (IOException e) { + throw new RuntimeException("Could not read specified resource file!"); + } + return readGraphString(writer.toString()); + } + + public void addReference(String name, Object object) { + mReferences.put(name, object); + } + + public void addReferencesByMap(KeyValueMap refs) { + mReferences.putAll(refs); + } + + public void addReferencesByKeysAndValues(Object... references) { + mReferences.setKeyValues(references); + } + +} diff --git a/media/mca/filterfw/java/android/filterfw/io/PatternScanner.java b/media/mca/filterfw/java/android/filterfw/io/PatternScanner.java new file mode 100644 index 0000000..4f1df02 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/io/PatternScanner.java @@ -0,0 +1,123 @@ +/* + * 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 android.filterfw.io; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @hide + */ +public class PatternScanner { + + private String mInput; + private Pattern mIgnorePattern; + private int mOffset = 0; + private int mLineNo = 0; + private int mStartOfLine = 0; + + public PatternScanner(String input) { + mInput = input; + } + + public PatternScanner(String input, Pattern ignorePattern) { + mInput = input; + mIgnorePattern = ignorePattern; + skip(mIgnorePattern); + } + + public String tryEat(Pattern pattern) { + // Skip ignore pattern + if (mIgnorePattern != null) { + skip(mIgnorePattern); + } + + // Create the matcher + Matcher matcher = pattern.matcher(mInput); + matcher.region(mOffset, mInput.length()); + + // Attempt to match + String result = null; + if (matcher.lookingAt()) { + updateLineCount(mOffset, matcher.end()); + mOffset = matcher.end(); + result = mInput.substring(matcher.start(), matcher.end()); + } + + // Skip ignore pattern + if (result != null && mIgnorePattern != null) { + skip(mIgnorePattern); + } + + return result; + } + + public String eat(Pattern pattern, String tokenName) { + String result = tryEat(pattern); + if (result == null) { + throw new RuntimeException(unexpectedTokenMessage(tokenName)); + } + return result; + } + + public boolean peek(Pattern pattern) { + // Skip ignore pattern + if (mIgnorePattern != null) { + skip(mIgnorePattern); + } + + // Create the matcher + Matcher matcher = pattern.matcher(mInput); + matcher.region(mOffset, mInput.length()); + + // Attempt to match + return matcher.lookingAt(); + } + + public void skip(Pattern pattern) { + Matcher matcher = pattern.matcher(mInput); + matcher.region(mOffset, mInput.length()); + if (matcher.lookingAt()) { + updateLineCount(mOffset, matcher.end()); + mOffset = matcher.end(); + } + } + + public boolean atEnd() { + return mOffset >= mInput.length(); + } + + public int lineNo() { + return mLineNo; + } + + public String unexpectedTokenMessage(String tokenName) { + String line = mInput.substring(mStartOfLine, mOffset); + return "Unexpected token on line " + (mLineNo + 1) + " after '" + line + "' <- Expected " + + tokenName + "!"; + } + + public void updateLineCount(int start, int end) { + for (int i = start; i < end; ++i) { + if (mInput.charAt(i) == '\n') { + ++mLineNo; + mStartOfLine = i + 1; + } + } + } +} diff --git a/media/mca/filterfw/java/android/filterfw/io/TextGraphReader.java b/media/mca/filterfw/java/android/filterfw/io/TextGraphReader.java new file mode 100644 index 0000000..366ef82 --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/io/TextGraphReader.java @@ -0,0 +1,489 @@ +/* + * 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 android.filterfw.io; + +import java.lang.Float; +import java.lang.Integer; +import java.lang.String; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +import android.filterfw.core.Filter; +import android.filterfw.core.FilterFactory; +import android.filterfw.core.FilterGraph; +import android.filterfw.core.KeyValueMap; +import android.filterfw.core.ProtocolException; +import android.filterfw.io.GraphReader; +import android.filterfw.io.GraphIOException; +import android.filterfw.io.PatternScanner; + +/** + * @hide + */ +public class TextGraphReader extends GraphReader { + + private ArrayList<Command> mCommands = new ArrayList<Command>(); + private Filter mCurrentFilter; + private FilterGraph mCurrentGraph; + private KeyValueMap mBoundReferences; + private KeyValueMap mSettings; + private FilterFactory mFactory; + + private interface Command { + public void execute(TextGraphReader reader) throws GraphIOException; + } + + private class ImportPackageCommand implements Command { + private String mPackageName; + + public ImportPackageCommand(String packageName) { + mPackageName = packageName; + } + + @Override + public void execute(TextGraphReader reader) throws GraphIOException { + try { + reader.mFactory.addPackage(mPackageName); + } catch (IllegalArgumentException e) { + throw new GraphIOException(e.getMessage()); + } + } + } + + private class AddLibraryCommand implements Command { + private String mLibraryName; + + public AddLibraryCommand(String libraryName) { + mLibraryName = libraryName; + } + + @Override + public void execute(TextGraphReader reader) { + reader.mFactory.addFilterLibrary(mLibraryName); + } + } + + private class AllocateFilterCommand implements Command { + private String mClassName; + private String mFilterName; + + public AllocateFilterCommand(String className, String filterName) { + mClassName = className; + mFilterName = filterName; + } + + public void execute(TextGraphReader reader) throws GraphIOException { + // Create the filter + Filter filter = null; + try { + filter = reader.mFactory.createFilterByClassName(mClassName, mFilterName); + } catch (IllegalArgumentException e) { + throw new GraphIOException(e.getMessage()); + } + + // Set it as the current filter + reader.mCurrentFilter = filter; + } + } + + private class InitFilterCommand implements Command { + private KeyValueMap mParams; + + public InitFilterCommand(KeyValueMap params) { + mParams = params; + } + + @Override + public void execute(TextGraphReader reader) throws GraphIOException { + Filter filter = reader.mCurrentFilter; + try { + filter.initWithValueMap(mParams); + } catch (ProtocolException e) { + throw new GraphIOException(e.getMessage()); + } + reader.mCurrentGraph.addFilter(mCurrentFilter); + } + } + + private class ConnectCommand implements Command { + private String mSourceFilter; + private String mSourcePort; + private String mTargetFilter; + private String mTargetName; + + public ConnectCommand(String sourceFilter, + String sourcePort, + String targetFilter, + String targetName) { + mSourceFilter = sourceFilter; + mSourcePort = sourcePort; + mTargetFilter = targetFilter; + mTargetName = targetName; + } + + @Override + public void execute(TextGraphReader reader) { + reader.mCurrentGraph.connect(mSourceFilter, mSourcePort, mTargetFilter, mTargetName); + } + } + + @Override + public FilterGraph readGraphString(String graphString) throws GraphIOException { + FilterGraph result = new FilterGraph(); + + reset(); + mCurrentGraph = result; + parseString(graphString); + applySettings(); + executeCommands(); + reset(); + + return result; + } + + private void reset() { + mCurrentGraph = null; + mCurrentFilter = null; + mCommands.clear(); + mBoundReferences = new KeyValueMap(); + mSettings = new KeyValueMap(); + mFactory = new FilterFactory(); + } + + private void parseString(String graphString) throws GraphIOException { + final Pattern commandPattern = Pattern.compile("@[a-zA-Z]+"); + final Pattern curlyClosePattern = Pattern.compile("\\}"); + final Pattern curlyOpenPattern = Pattern.compile("\\{"); + final Pattern ignorePattern = Pattern.compile("(\\s+|//[^\\n]*\\n)+"); + final Pattern packageNamePattern = Pattern.compile("[a-zA-Z\\.]+"); + final Pattern libraryNamePattern = Pattern.compile("[a-zA-Z\\./:]+"); + final Pattern portPattern = Pattern.compile("\\[[a-zA-Z0-9\\-_]+\\]"); + final Pattern rightArrowPattern = Pattern.compile("=>"); + final Pattern semicolonPattern = Pattern.compile(";"); + final Pattern wordPattern = Pattern.compile("[a-zA-Z0-9\\-_]+"); + + final int STATE_COMMAND = 0; + final int STATE_IMPORT_PKG = 1; + final int STATE_ADD_LIBRARY = 2; + final int STATE_FILTER_CLASS = 3; + final int STATE_FILTER_NAME = 4; + final int STATE_CURLY_OPEN = 5; + final int STATE_PARAMETERS = 6; + final int STATE_CURLY_CLOSE = 7; + final int STATE_SOURCE_FILTERNAME = 8; + final int STATE_SOURCE_PORT = 9; + final int STATE_RIGHT_ARROW = 10; + final int STATE_TARGET_FILTERNAME = 11; + final int STATE_TARGET_PORT = 12; + final int STATE_ASSIGNMENT = 13; + final int STATE_EXTERNAL = 14; + final int STATE_SETTING = 15; + final int STATE_SEMICOLON = 16; + + int state = STATE_COMMAND; + PatternScanner scanner = new PatternScanner(graphString, ignorePattern); + + String curClassName = null; + String curSourceFilterName = null; + String curSourcePortName = null; + String curTargetFilterName = null; + String curTargetPortName = null; + + // State machine main loop + while (!scanner.atEnd()) { + switch (state) { + case STATE_COMMAND: { + String curCommand = scanner.eat(commandPattern, "<command>"); + if (curCommand.equals("@import")) { + state = STATE_IMPORT_PKG; + } else if (curCommand.equals("@library")) { + state = STATE_ADD_LIBRARY; + } else if (curCommand.equals("@filter")) { + state = STATE_FILTER_CLASS; + } else if (curCommand.equals("@connect")) { + state = STATE_SOURCE_FILTERNAME; + } else if (curCommand.equals("@set")) { + state = STATE_ASSIGNMENT; + } else if (curCommand.equals("@external")) { + state = STATE_EXTERNAL; + } else if (curCommand.equals("@setting")) { + state = STATE_SETTING; + } else { + throw new GraphIOException("Unknown command '" + curCommand + "'!"); + } + break; + } + + case STATE_IMPORT_PKG: { + String packageName = scanner.eat(packageNamePattern, "<package-name>"); + mCommands.add(new ImportPackageCommand(packageName)); + state = STATE_SEMICOLON; + break; + } + + case STATE_ADD_LIBRARY: { + String libraryName = scanner.eat(libraryNamePattern, "<library-name>"); + mCommands.add(new AddLibraryCommand(libraryName)); + state = STATE_SEMICOLON; + break; + } + + case STATE_FILTER_CLASS: + curClassName = scanner.eat(wordPattern, "<class-name>"); + state = STATE_FILTER_NAME; + break; + + case STATE_FILTER_NAME: { + String curFilterName = scanner.eat(wordPattern, "<filter-name>"); + mCommands.add(new AllocateFilterCommand(curClassName, curFilterName)); + state = STATE_CURLY_OPEN; + break; + } + + case STATE_CURLY_OPEN: + scanner.eat(curlyOpenPattern, "{"); + state = STATE_PARAMETERS; + break; + + case STATE_PARAMETERS: { + KeyValueMap params = readKeyValueAssignments(scanner, curlyClosePattern); + mCommands.add(new InitFilterCommand(params)); + state = STATE_CURLY_CLOSE; + break; + } + + case STATE_CURLY_CLOSE: + scanner.eat(curlyClosePattern, "}"); + state = STATE_COMMAND; + break; + + case STATE_SOURCE_FILTERNAME: + curSourceFilterName = scanner.eat(wordPattern, "<source-filter-name>"); + state = STATE_SOURCE_PORT; + break; + + case STATE_SOURCE_PORT: { + String portString = scanner.eat(portPattern, "[<source-port-name>]"); + curSourcePortName = portString.substring(1, portString.length() - 1); + state = STATE_RIGHT_ARROW; + break; + } + + case STATE_RIGHT_ARROW: + scanner.eat(rightArrowPattern, "=>"); + state = STATE_TARGET_FILTERNAME; + break; + + case STATE_TARGET_FILTERNAME: + curTargetFilterName = scanner.eat(wordPattern, "<target-filter-name>"); + state = STATE_TARGET_PORT; + break; + + case STATE_TARGET_PORT: { + String portString = scanner.eat(portPattern, "[<target-port-name>]"); + curTargetPortName = portString.substring(1, portString.length() - 1); + mCommands.add(new ConnectCommand(curSourceFilterName, + curSourcePortName, + curTargetFilterName, + curTargetPortName)); + state = STATE_SEMICOLON; + break; + } + + case STATE_ASSIGNMENT: { + KeyValueMap assignment = readKeyValueAssignments(scanner, semicolonPattern); + mBoundReferences.putAll(assignment); + state = STATE_SEMICOLON; + break; + } + + case STATE_EXTERNAL: { + String externalName = scanner.eat(wordPattern, "<external-identifier>"); + bindExternal(externalName); + state = STATE_SEMICOLON; + break; + } + + case STATE_SETTING: { + KeyValueMap setting = readKeyValueAssignments(scanner, semicolonPattern); + mSettings.putAll(setting); + state = STATE_SEMICOLON; + break; + } + + case STATE_SEMICOLON: + scanner.eat(semicolonPattern, ";"); + state = STATE_COMMAND; + break; + } + } + + // Make sure end of input was expected + if (state != STATE_SEMICOLON && state != STATE_COMMAND) { + throw new GraphIOException("Unexpected end of input!"); + } + } + + @Override + public KeyValueMap readKeyValueAssignments(String assignments) throws GraphIOException { + final Pattern ignorePattern = Pattern.compile("\\s+"); + PatternScanner scanner = new PatternScanner(assignments, ignorePattern); + return readKeyValueAssignments(scanner, null); + } + + private KeyValueMap readKeyValueAssignments(PatternScanner scanner, + Pattern endPattern) throws GraphIOException { + // Our parser is a state-machine, and these are our states + final int STATE_IDENTIFIER = 0; + final int STATE_EQUALS = 1; + final int STATE_VALUE = 2; + final int STATE_POST_VALUE = 3; + + final Pattern equalsPattern = Pattern.compile("="); + final Pattern semicolonPattern = Pattern.compile(";"); + final Pattern wordPattern = Pattern.compile("[a-zA-Z]+[a-zA-Z0-9]*"); + final Pattern stringPattern = Pattern.compile("'[^']*'|\\\"[^\\\"]*\\\""); + final Pattern intPattern = Pattern.compile("[0-9]+"); + final Pattern floatPattern = Pattern.compile("[0-9]*\\.[0-9]+f?"); + final Pattern referencePattern = Pattern.compile("\\$[a-zA-Z]+[a-zA-Z0-9]"); + final Pattern booleanPattern = Pattern.compile("true|false"); + + int state = STATE_IDENTIFIER; + KeyValueMap newVals = new KeyValueMap(); + String curKey = null; + String curValue = null; + + while (!scanner.atEnd() && !(endPattern != null && scanner.peek(endPattern))) { + switch (state) { + case STATE_IDENTIFIER: + curKey = scanner.eat(wordPattern, "<identifier>"); + state = STATE_EQUALS; + break; + + case STATE_EQUALS: + scanner.eat(equalsPattern, "="); + state = STATE_VALUE; + break; + + case STATE_VALUE: + if ((curValue = scanner.tryEat(stringPattern)) != null) { + newVals.put(curKey, curValue.substring(1, curValue.length() - 1)); + } else if ((curValue = scanner.tryEat(referencePattern)) != null) { + String refName = curValue.substring(1, curValue.length()); + Object referencedObject = mBoundReferences != null + ? mBoundReferences.get(refName) + : null; + if (referencedObject == null) { + throw new GraphIOException( + "Unknown object reference to '" + refName + "'!"); + } + newVals.put(curKey, referencedObject); + } else if ((curValue = scanner.tryEat(booleanPattern)) != null) { + newVals.put(curKey, Boolean.parseBoolean(curValue)); + } else if ((curValue = scanner.tryEat(floatPattern)) != null) { + newVals.put(curKey, Float.parseFloat(curValue)); + } else if ((curValue = scanner.tryEat(intPattern)) != null) { + newVals.put(curKey, Integer.parseInt(curValue)); + } else { + throw new GraphIOException(scanner.unexpectedTokenMessage("<value>")); + } + state = STATE_POST_VALUE; + break; + + case STATE_POST_VALUE: + scanner.eat(semicolonPattern, ";"); + state = STATE_IDENTIFIER; + break; + } + } + + // Make sure end is expected + if (state != STATE_IDENTIFIER && state != STATE_POST_VALUE) { + throw new GraphIOException( + "Unexpected end of assignments on line " + scanner.lineNo() + "!"); + } + + return newVals; + } + + private void bindExternal(String name) throws GraphIOException { + if (mReferences.containsKey(name)) { + Object value = mReferences.get(name); + mBoundReferences.put(name, value); + } else { + throw new GraphIOException("Unknown external variable '" + name + "'! " + + "You must add a reference to this external in the host program using " + + "addReference(...)!"); + } + } + + /** + * Unused for now: Often you may want to declare references that are NOT in a certain graph, + * e.g. when reading multiple graphs with the same reader. We could print a warning, but even + * that may be too much. + **/ + private void checkReferences() throws GraphIOException { + for (String reference : mReferences.keySet()) { + if (!mBoundReferences.containsKey(reference)) { + throw new GraphIOException( + "Host program specifies reference to '" + reference + "', which is not " + + "declared @external in graph file!"); + } + } + } + + private void applySettings() throws GraphIOException { + for (String setting : mSettings.keySet()) { + Object value = mSettings.get(setting); + if (setting.equals("autoBranch")) { + expectSettingClass(setting, value, String.class); + if (value.equals("synced")) { + mCurrentGraph.setAutoBranchMode(FilterGraph.AUTOBRANCH_SYNCED); + } else if (value.equals("unsynced")) { + mCurrentGraph.setAutoBranchMode(FilterGraph.AUTOBRANCH_UNSYNCED); + } else if (value.equals("off")) { + mCurrentGraph.setAutoBranchMode(FilterGraph.AUTOBRANCH_OFF); + } else { + throw new GraphIOException("Unknown autobranch setting: " + value + "!"); + } + } else if (setting.equals("discardUnconnectedOutputs")) { + expectSettingClass(setting, value, Boolean.class); + mCurrentGraph.setDiscardUnconnectedOutputs((Boolean)value); + } else { + throw new GraphIOException("Unknown @setting '" + setting + "'!"); + } + } + } + + private void expectSettingClass(String setting, + Object value, + Class expectedClass) throws GraphIOException { + if (value.getClass() != expectedClass) { + throw new GraphIOException("Setting '" + setting + "' must have a value of type " + + expectedClass.getSimpleName() + ", but found a value of type " + + value.getClass().getSimpleName() + "!"); + } + } + + private void executeCommands() throws GraphIOException { + for (Command command : mCommands) { + command.execute(this); + } + } +} diff --git a/media/mca/filterfw/java/android/filterfw/io/package-info.java b/media/mca/filterfw/java/android/filterfw/io/package-info.java new file mode 100644 index 0000000..ea3e70f --- /dev/null +++ b/media/mca/filterfw/java/android/filterfw/io/package-info.java @@ -0,0 +1,4 @@ +/** + * @hide + */ +package android.filterfw.io; diff --git a/media/mca/filterfw/jni/Android.mk b/media/mca/filterfw/jni/Android.mk new file mode 100644 index 0000000..5aa5af1 --- /dev/null +++ b/media/mca/filterfw/jni/Android.mk @@ -0,0 +1,51 @@ +# 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. +# + +LOCAL_PATH := $(call my-dir) + +##################### +# Build module libfilterfw_jni +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_MODULE = libfilterfw_jni + +LOCAL_SRC_FILES = jni_init.cpp \ + jni_gl_environment.cpp \ + jni_gl_frame.cpp \ + jni_native_buffer.cpp \ + jni_native_frame.cpp \ + jni_native_program.cpp \ + jni_shader_program.cpp \ + jni_util.cpp \ + jni_vertex_frame.cpp + +# Need FilterFW lib +include $(LOCAL_PATH)/../native/libfilterfw.mk + +# Also need the JNI headers. +LOCAL_C_INCLUDES += \ + $(JNI_H_INCLUDE) \ + $(LOCAL_PATH)/.. + +# Don't prelink this library. For more efficient code, you may want +# to add this library to the prelink map and set this to true. However, +# it's difficult to do this for applications that are not supplied as +# part of a system image. +LOCAL_PRELINK_MODULE := false + +include $(BUILD_STATIC_LIBRARY) + diff --git a/media/mca/filterfw/jni/jni_gl_environment.cpp b/media/mca/filterfw/jni/jni_gl_environment.cpp new file mode 100644 index 0000000..3c596a4 --- /dev/null +++ b/media/mca/filterfw/jni/jni_gl_environment.cpp @@ -0,0 +1,377 @@ +/* + * 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. + */ +// #define LOG_NDEBUG 0 + +#include <stdint.h> +#include <android/native_window_jni.h> + +#include "jni/jni_gl_environment.h" +#include "jni/jni_util.h" +#include <media/mediarecorder.h> +#include "native/core/gl_env.h" + +#include <gui/ISurfaceTexture.h> +#include <gui/SurfaceTextureClient.h> +#include <utils/Errors.h> +#include <system/window.h> + + +using android::filterfw::GLEnv; +using android::filterfw::WindowHandle; +using android::MediaRecorder; +using android::sp; +using android::ISurfaceTexture; +using android::SurfaceTextureClient; + + +class NativeWindowHandle : public WindowHandle { + public: + NativeWindowHandle(ANativeWindow* window) : window_(window) { + } + + virtual ~NativeWindowHandle() { + } + + virtual void Destroy() { + ALOGI("Releasing ANativeWindow!"); + ANativeWindow_release(window_); + } + + virtual const void* InternalHandle() const { + return window_; + } + + virtual void* InternalHandle() { + return window_; + } + + private: + ANativeWindow* window_; +}; + +jboolean Java_android_filterfw_core_GLEnvironment_nativeAllocate(JNIEnv* env, jobject thiz) { + return ToJBool(WrapObjectInJava(new GLEnv(), env, thiz, true)); +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeDeallocate(JNIEnv* env, jobject thiz) { + return ToJBool(DeleteNativeObject<GLEnv>(env, thiz)); +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeInitWithNewContext(JNIEnv* env, + jobject thiz) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + return gl_env ? ToJBool(gl_env->InitWithNewContext()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeInitWithCurrentContext(JNIEnv* env, + jobject thiz) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + return gl_env ? ToJBool(gl_env->InitWithCurrentContext()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeIsActive(JNIEnv* env, jobject thiz) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + return gl_env ? ToJBool(gl_env->IsActive()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeIsContextActive(JNIEnv* env, jobject thiz) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + return gl_env ? ToJBool(gl_env->IsContextActive()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeIsAnyContextActive(JNIEnv* env, + jclass clazz) { + return ToJBool(GLEnv::IsAnyContextActive()); +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeActivate(JNIEnv* env, jobject thiz) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + return gl_env ? ToJBool(gl_env->Activate()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeDeactivate(JNIEnv* env, jobject thiz) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + return gl_env ? ToJBool(gl_env->Deactivate()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeSwapBuffers(JNIEnv* env, jobject thiz) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + return gl_env ? ToJBool(gl_env->SwapBuffers()) : JNI_FALSE; +} + +// Get the native mediarecorder object corresponding to the java object +static sp<MediaRecorder> getMediaRecorder(JNIEnv* env, jobject jmediarecorder) { + jclass clazz = env->FindClass("android/media/MediaRecorder"); + if (clazz == NULL) { + return NULL; + } + + jfieldID context = env->GetFieldID(clazz, "mNativeContext", "I"); + if (context == NULL) { + return NULL; + } + + MediaRecorder* const p = (MediaRecorder*)env->GetIntField(jmediarecorder, context); + env->DeleteLocalRef(clazz); + return sp<MediaRecorder>(p); +} + + +jint Java_android_filterfw_core_GLEnvironment_nativeAddSurface(JNIEnv* env, + jobject thiz, + jobject surface) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + if (!surface) { + ALOGE("GLEnvironment: Null Surface passed!"); + return -1; + } else if (gl_env) { + // Get the ANativeWindow + ANativeWindow* window = ANativeWindow_fromSurface(env, surface); + if (!window) { + ALOGE("GLEnvironment: Error creating window!"); + return -1; + } + + NativeWindowHandle* winHandle = new NativeWindowHandle(window); + int result = gl_env->FindSurfaceIdForWindow(winHandle); + if (result == -1) { + // Configure surface + EGLConfig config; + EGLint numConfigs = -1; + EGLint configAttribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_RECORDABLE_ANDROID, EGL_TRUE, + EGL_NONE + }; + + + + eglChooseConfig(gl_env->display(), configAttribs, &config, 1, &numConfigs); + if (numConfigs < 1) { + ALOGE("GLEnvironment: No suitable EGL configuration found for surface!"); + return -1; + } + + // Create the EGL surface + EGLSurface egl_surface = eglCreateWindowSurface(gl_env->display(), + config, + window, + NULL); + + if (GLEnv::CheckEGLError("eglCreateWindowSurface")) { + ALOGE("GLEnvironment: Error creating window surface!"); + return -1; + } + + // Add it to GL Env and assign ID + result = gl_env->AddWindowSurface(egl_surface, winHandle); + } else { + delete winHandle; + } + return result; + } + return -1; +} + +jint Java_android_filterfw_core_GLEnvironment_nativeAddSurfaceWidthHeight(JNIEnv* env, + jobject thiz, + jobject surface, + jint width, + jint height) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + if (!surface) { + ALOGE("GLEnvironment: Null SurfaceTexture passed!"); + return -1; + } else if (gl_env) { + // Get the ANativeWindow + ANativeWindow* window = ANativeWindow_fromSurface(env, surface); + if (!window) { + ALOGE("GLEnvironment: Error creating window!"); + return -1; + } + + // Don't care about format (will get overridden by SurfaceTexture + // anyway), but do care about width and height + // TODO: Probably, this should be just be + // ANativeWindow_setBuffersDimensions. The pixel format is + // set during the eglCreateWindowSurface + ANativeWindow_setBuffersGeometry(window, width, height, 0); + + NativeWindowHandle* winHandle = new NativeWindowHandle(window); + int result = gl_env->FindSurfaceIdForWindow(winHandle); + if (result == -1) { + // Configure surface + EGLConfig config; + EGLint numConfigs = -1; + EGLint configAttribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_RECORDABLE_ANDROID, EGL_TRUE, + EGL_NONE + }; + + + + eglChooseConfig(gl_env->display(), configAttribs, &config, 1, &numConfigs); + if (numConfigs < 1) { + ALOGE("GLEnvironment: No suitable EGL configuration found for surface texture!"); + return -1; + } + + // Create the EGL surface + EGLSurface egl_surface = eglCreateWindowSurface(gl_env->display(), + config, + window, + NULL); + + if (GLEnv::CheckEGLError("eglCreateWindowSurface")) { + ALOGE("GLEnvironment: Error creating window surface!"); + return -1; + } + + // Add it to GL Env and assign ID + result = gl_env->AddWindowSurface(egl_surface, winHandle); + } else { + delete winHandle; + } + return result; + } + return -1; +} + +// nativeAddSurfaceFromMediaRecorder gets an EGLSurface +// using a MediaRecorder object. +// When Mediarecorder is used for recording GL Frames, it +// will have a reference to a Native Handle (a SurfaceTexureClient) +// which talks to the StageFrightRecorder in mediaserver via +// a binder interface. +jint Java_android_filterfw_core_GLEnvironment_nativeAddSurfaceFromMediaRecorder( + JNIEnv* env, + jobject thiz, + jobject jmediarecorder) { + ALOGV("GLEnv Jni: nativeAddSurfaceFromMediaRecorder"); + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + if (!gl_env) { + return -1; + } + // get a native mediarecorder object from the java object + sp<MediaRecorder> mr = getMediaRecorder(env, jmediarecorder); + if (mr == NULL) { + ALOGE("GLEnvironment: Error- MediaRecorder could not be initialized!"); + return -1; + } + + // Ask the mediarecorder to return a handle to a surfacemediasource + // This will talk to the StageFrightRecorder via MediaRecorderClient + // over binder calls + sp<ISurfaceTexture> surfaceMS = mr->querySurfaceMediaSourceFromMediaServer(); + if (surfaceMS == NULL) { + ALOGE("GLEnvironment: Error- MediaRecorder returned a null \ + <ISurfaceTexture> handle."); + return -1; + } + sp<SurfaceTextureClient> surfaceTC = new SurfaceTextureClient(surfaceMS); + // Get the ANativeWindow + sp<ANativeWindow> window = surfaceTC; + + + if (window == NULL) { + ALOGE("GLEnvironment: Error creating window!"); + return -1; + } + window->incStrong((void*)ANativeWindow_acquire); + + // In case of encoding, no need to set the dimensions + // on the buffers. The dimensions for the final encoding are set by + // the consumer side. + // The pixel format is dictated by the GL, and set during the + // eglCreateWindowSurface + + NativeWindowHandle* winHandle = new NativeWindowHandle(window.get()); + int result = gl_env->FindSurfaceIdForWindow(winHandle); + // If we find a surface with that window handle, just return that id + if (result != -1) { + delete winHandle; + return result; + } + // If we do not find a surface with that window handle, create + // one and assign to it the handle + // Configure surface + EGLConfig config; + EGLint numConfigs = -1; + EGLint configAttribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_RECORDABLE_ANDROID, EGL_TRUE, + EGL_NONE + }; + + + eglChooseConfig(gl_env->display(), configAttribs, &config, 1, &numConfigs); + if (numConfigs < 1) { + ALOGE("GLEnvironment: No suitable EGL configuration found for surface texture!"); + delete winHandle; + return -1; + } + + // Create the EGL surface + EGLSurface egl_surface = eglCreateWindowSurface(gl_env->display(), + config, + window.get(), + NULL); + + if (GLEnv::CheckEGLError("eglCreateWindowSurface")) { + ALOGE("GLEnvironment: Error creating window surface!"); + delete winHandle; + return -1; + } + + // Add it to GL Env and assign ID + result = gl_env->AddWindowSurface(egl_surface, winHandle); + return result; +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeActivateSurfaceId(JNIEnv* env, + jobject thiz, + jint surfaceId) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + return gl_env ? ToJBool(gl_env->SwitchToSurfaceId(surfaceId) && gl_env->Activate()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeRemoveSurfaceId(JNIEnv* env, + jobject thiz, + jint surfaceId) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + return gl_env ? ToJBool(gl_env->ReleaseSurfaceId(surfaceId)) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLEnvironment_nativeSetSurfaceTimestamp(JNIEnv* env, + jobject thiz, + jlong timestamp) { + GLEnv* gl_env = ConvertFromJava<GLEnv>(env, thiz); + int64_t timestamp_native = timestamp; + return gl_env ? ToJBool(gl_env->SetSurfaceTimestamp(timestamp_native)) : JNI_FALSE; +} diff --git a/media/mca/filterfw/jni/jni_gl_environment.h b/media/mca/filterfw/jni/jni_gl_environment.h new file mode 100644 index 0000000..af9c744 --- /dev/null +++ b/media/mca/filterfw/jni/jni_gl_environment.h @@ -0,0 +1,97 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_JNI_GL_ENVIRONMENT_H +#define ANDROID_FILTERFW_JNI_GL_ENVIRONMENT_H + +#include <jni.h> + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeAllocate(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeDeallocate(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeInitWithNewContext(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeInitWithCurrentContext(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeIsActive(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeIsContextActive(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeIsAnyContextActive(JNIEnv* env, jclass clazz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeActivate(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeDeactivate(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeSwapBuffers(JNIEnv* env, jobject thiz); + +JNIEXPORT jint JNICALL +Java_android_filterfw_core_GLEnvironment_nativeAddSurface(JNIEnv* env, + jobject thiz, + jobject surface); + +JNIEXPORT jint JNICALL +Java_android_filterfw_core_GLEnvironment_nativeAddSurfaceWidthHeight(JNIEnv* env, + jobject thiz, + jobject surface, + jint width, + jint height); + +// The call to hook up the SurfaceMediaSource (in MediaServer) to the GL. +// We get a sp<ISurfaceTexure> from the MediaServer and talks to MediaServer +// over a binder interface. GL hooked up to the MediaServer by using the native +// window created using the <ISurfaceTexture> handle +JNIEXPORT jint JNICALL +Java_android_filterfw_core_GLEnvironment_nativeAddSurfaceFromMediaRecorder( + JNIEnv* env, + jobject thiz, + jobject mediarecorder); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeActivateSurfaceId(JNIEnv* env, + jobject thiz, + jint surfaceId); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeRemoveSurfaceId(JNIEnv* env, + jobject thiz, + jint surfaceId); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLEnvironment_nativeSetSurfaceTimestamp(JNIEnv* env, + jobject thiz, + jlong timestamp); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_FILTERFW_JNI_GL_ENVIRONMENT_H diff --git a/media/mca/filterfw/jni/jni_gl_frame.cpp b/media/mca/filterfw/jni/jni_gl_frame.cpp new file mode 100644 index 0000000..61340f9 --- /dev/null +++ b/media/mca/filterfw/jni/jni_gl_frame.cpp @@ -0,0 +1,322 @@ +/* + * 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. + */ + +#include "android/bitmap.h" + +#include "jni/jni_gl_frame.h" +#include "jni/jni_util.h" + +#include "native/core/gl_env.h" +#include "native/core/gl_frame.h" +#include "native/core/native_frame.h" + +using android::filterfw::GLEnv; +using android::filterfw::GLFrame; +using android::filterfw::NativeFrame; + +// Helper functions //////////////////////////////////////////////////////////////////////////////// +void ConvertFloatsToRGBA(const float* floats, int length, uint8_t* result) { + for (int i = 0; i < length; ++i) { + result[i] = static_cast<uint8_t>(floats[i] * 255.0); + } +} + +void ConvertRGBAToFloats(const uint8_t* rgba, int length, float* result) { + for (int i = 0; i < length; ++i) { + result[i] = rgba[i] / 255.0; + } +} + +// GLFrame JNI implementation ////////////////////////////////////////////////////////////////////// +jboolean Java_android_filterfw_core_GLFrame_nativeAllocate(JNIEnv* env, + jobject thiz, + jobject gl_env, + jint width, + jint height) { + GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env); + if (!gl_env_ptr) return JNI_FALSE; + GLFrame* frame = new GLFrame(gl_env_ptr); + if (frame->Init(width, height)) { + return ToJBool(WrapObjectInJava(frame, env, thiz, true)); + } else { + delete frame; + return JNI_FALSE; + } +} + +jboolean Java_android_filterfw_core_GLFrame_nativeAllocateWithTexture(JNIEnv* env, + jobject thiz, + jobject gl_env, + jint tex_id, + jint width, + jint height) { + GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env); + if (!gl_env_ptr) return JNI_FALSE; + GLFrame* frame = new GLFrame(gl_env_ptr); + if (frame->InitWithTexture(tex_id, width, height)) { + return ToJBool(WrapObjectInJava(frame, env, thiz, true)); + } else { + delete frame; + return JNI_FALSE; + } +} + +jboolean Java_android_filterfw_core_GLFrame_nativeAllocateWithFbo(JNIEnv* env, + jobject thiz, + jobject gl_env, + jint fbo_id, + jint width, + jint height) { + GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env); + if (!gl_env_ptr) return JNI_FALSE; + GLFrame* frame = new GLFrame(gl_env_ptr); + if (frame->InitWithFbo(fbo_id, width, height)) { + return ToJBool(WrapObjectInJava(frame, env, thiz, true)); + } else { + delete frame; + return JNI_FALSE; + } +} + +jboolean Java_android_filterfw_core_GLFrame_nativeAllocateExternal(JNIEnv* env, + jobject thiz, + jobject gl_env) { + GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env); + if (!gl_env_ptr) return JNI_FALSE; + GLFrame* frame = new GLFrame(gl_env_ptr); + if (frame->InitWithExternalTexture()) { + return ToJBool(WrapObjectInJava(frame, env, thiz, true)); + } else { + delete frame; + return JNI_FALSE; + } +} + +jboolean Java_android_filterfw_core_GLFrame_nativeDeallocate(JNIEnv* env, jobject thiz) { + return ToJBool(DeleteNativeObject<GLFrame>(env, thiz)); +} + +jboolean Java_android_filterfw_core_GLFrame_setNativeData(JNIEnv* env, + jobject thiz, + jbyteArray data, + jint offset, + jint length) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + if (frame && data) { + jbyte* bytes = env->GetByteArrayElements(data, NULL); + if (bytes) { + const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(bytes + offset), length); + env->ReleaseByteArrayElements(data, bytes, JNI_ABORT); + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jbyteArray Java_android_filterfw_core_GLFrame_getNativeData(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + if (frame && frame->Size() > 0) { + jbyteArray result = env->NewByteArray(frame->Size()); + jbyte* data = env->GetByteArrayElements(result, NULL); + frame->CopyDataTo(reinterpret_cast<uint8_t*>(data), frame->Size()); + env->ReleaseByteArrayElements(result, data, 0); + return result; + } + return NULL; +} + +jboolean Java_android_filterfw_core_GLFrame_setNativeInts(JNIEnv* env, + jobject thiz, + jintArray ints) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + if (frame && ints) { + jint* int_ptr = env->GetIntArrayElements(ints, NULL); + const int length = env->GetArrayLength(ints); + if (int_ptr) { + const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(int_ptr), + length * sizeof(jint)); + env->ReleaseIntArrayElements(ints, int_ptr, JNI_ABORT); + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jintArray Java_android_filterfw_core_GLFrame_getNativeInts(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + if (frame && frame->Size() > 0 && (frame->Size() % sizeof(jint) == 0)) { + jintArray result = env->NewIntArray(frame->Size() / sizeof(jint)); + jint* data = env->GetIntArrayElements(result, NULL); + frame->CopyDataTo(reinterpret_cast<uint8_t*>(data), frame->Size()); + env->ReleaseIntArrayElements(result, data, 0); + return result; + } + return NULL; +} + +jboolean Java_android_filterfw_core_GLFrame_setNativeFloats(JNIEnv* env, + jobject thiz, + jfloatArray floats) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + if (frame && floats) { + jfloat* float_ptr = env->GetFloatArrayElements(floats, NULL); + const int length = env->GetArrayLength(floats); + if (float_ptr) { + // Convert floats to RGBA buffer + uint8_t* rgba_buffer = new uint8_t[length]; + ConvertFloatsToRGBA(float_ptr, length, rgba_buffer); + env->ReleaseFloatArrayElements(floats, float_ptr, JNI_ABORT); + + // Write RGBA buffer to frame + const bool success = frame->WriteData(rgba_buffer, length); + + // Clean-up + delete[] rgba_buffer; + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jfloatArray Java_android_filterfw_core_GLFrame_getNativeFloats(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + if (frame && frame->Size() > 0) { + // Create the result array + jfloatArray result = env->NewFloatArray(frame->Size()); + jfloat* float_array = env->GetFloatArrayElements(result, NULL); + + // Read the frame pixels + uint8_t* pixels = new uint8_t[frame->Size()]; + frame->CopyDataTo(pixels, frame->Size()); + + // Convert them to floats + ConvertRGBAToFloats(pixels, frame->Size(), float_array); + + // Clean-up + delete[] pixels; + env->ReleaseFloatArrayElements(result, float_array, 0); + return result; + } + return NULL; +} + +jboolean Java_android_filterfw_core_GLFrame_setNativeBitmap(JNIEnv* env, + jobject thiz, + jobject bitmap, + jint size) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + if (frame && bitmap) { + uint8_t* pixels; + const int result = AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void**>(&pixels)); + if (result == ANDROID_BITMAP_RESUT_SUCCESS) { + const bool success = frame->WriteData(pixels, size); + return ToJBool(success && + AndroidBitmap_unlockPixels(env, bitmap) == ANDROID_BITMAP_RESUT_SUCCESS); + } + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLFrame_getNativeBitmap(JNIEnv* env, + jobject thiz, + jobject bitmap) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + if (frame && bitmap) { + uint8_t* pixels; + const int result = AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void**>(&pixels)); + if (result == ANDROID_BITMAP_RESUT_SUCCESS) { + frame->CopyDataTo(pixels, frame->Size()); + return (AndroidBitmap_unlockPixels(env, bitmap) == ANDROID_BITMAP_RESUT_SUCCESS); + } + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLFrame_setNativeViewport(JNIEnv* env, + jobject thiz, + jint x, + jint y, + jint width, + jint height) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + return frame ? ToJBool(frame->SetViewport(x, y, width, height)) : JNI_FALSE; +} + +jint Java_android_filterfw_core_GLFrame_getNativeTextureId(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + return frame ? frame->GetTextureId() : -1; +} + +jint Java_android_filterfw_core_GLFrame_getNativeFboId(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + return frame ? frame->GetFboId() : -1; +} + +jboolean Java_android_filterfw_core_GLFrame_generateNativeMipMap(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + return frame ? ToJBool(frame->GenerateMipMap()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLFrame_setNativeTextureParam(JNIEnv* env, + jobject thiz, + jint param, + jint value) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + return frame ? ToJBool(frame->SetTextureParameter(param, value)) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLFrame_nativeResetParams(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + return frame ? ToJBool(frame->ResetTexParameters()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLFrame_nativeCopyFromNative(JNIEnv* env, + jobject thiz, + jobject frame) { + GLFrame* this_frame = ConvertFromJava<GLFrame>(env, thiz); + NativeFrame* other_frame = ConvertFromJava<NativeFrame>(env, frame); + if (this_frame && other_frame) { + return ToJBool(this_frame->WriteData(other_frame->Data(), other_frame->Size())); + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLFrame_nativeCopyFromGL(JNIEnv* env, + jobject thiz, + jobject frame) { + GLFrame* this_frame = ConvertFromJava<GLFrame>(env, thiz); + GLFrame* other_frame = ConvertFromJava<GLFrame>(env, frame); + if (this_frame && other_frame) { + return ToJBool(this_frame->CopyPixelsFrom(other_frame)); + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_GLFrame_nativeFocus(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + return ToJBool(frame && frame->FocusFrameBuffer()); +} + +jboolean Java_android_filterfw_core_GLFrame_nativeReattachTexToFbo(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + return ToJBool(frame && frame->ReattachTextureToFbo()); +} + +jboolean Java_android_filterfw_core_GLFrame_nativeDetachTexFromFbo(JNIEnv* env, jobject thiz) { + GLFrame* frame = ConvertFromJava<GLFrame>(env, thiz); + return ToJBool(frame && frame->DetachTextureFromFbo()); +} + diff --git a/media/mca/filterfw/jni/jni_gl_frame.h b/media/mca/filterfw/jni/jni_gl_frame.h new file mode 100644 index 0000000..8a25aea --- /dev/null +++ b/media/mca/filterfw/jni/jni_gl_frame.h @@ -0,0 +1,137 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_JNI_GL_FRAME_H +#define ANDROID_FILTERFW_JNI_GL_FRAME_H + +#include <jni.h> + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeAllocate(JNIEnv* env, + jobject thiz, + jobject gl_env, + jint width, + jint height); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeAllocateWithTexture(JNIEnv* env, + jobject thiz, + jobject gl_env, + jint tex_id, + jint width, + jint height); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeAllocateWithFbo(JNIEnv* env, + jobject thiz, + jobject gl_env, + jint fbo_id, + jint width, + jint height); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeAllocateExternal(JNIEnv* env, + jobject thiz, + jobject gl_env); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeDeallocate(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_setNativeInts(JNIEnv* env, jobject thiz, jintArray ints); + +JNIEXPORT jintArray JNICALL +Java_android_filterfw_core_GLFrame_getNativeInts(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_setNativeFloats(JNIEnv* env, jobject thiz, jfloatArray ints); + +JNIEXPORT jfloatArray JNICALL +Java_android_filterfw_core_GLFrame_getNativeFloats(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_setNativeData(JNIEnv* env, + jobject thiz, + jbyteArray data, + jint offset, + jint length); + +JNIEXPORT jbyteArray JNICALL +Java_android_filterfw_core_GLFrame_getNativeData(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_setNativeBitmap(JNIEnv* env, + jobject thiz, + jobject bitmap, + jint size); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_getNativeBitmap(JNIEnv* env, jobject thiz, jobject bitmap); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_setNativeViewport(JNIEnv* env, + jobject thiz, + jint x, + jint y, + jint width, + jint height); + +JNIEXPORT jint JNICALL +Java_android_filterfw_core_GLFrame_getNativeTextureId(JNIEnv* env, jobject thiz); + +JNIEXPORT jint JNICALL +Java_android_filterfw_core_GLFrame_getNativeFboId(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_generateNativeMipMap(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_setNativeTextureParam(JNIEnv* env, + jobject thiz, + jint param, + jint value); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeResetParams(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeCopyFromNative(JNIEnv* env, + jobject thiz, + jobject frame); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeCopyFromGL(JNIEnv* env, + jobject thiz, + jobject frame); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeFocus(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeReattachTexToFbo(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_GLFrame_nativeDetachTexFromFbo(JNIEnv* env, jobject thiz); + +#ifdef __cplusplus +} +#endif + +#endif /* ANDROID_FILTERFW_JNI_GL_FRAME_H */ diff --git a/media/mca/filterfw/jni/jni_init.cpp b/media/mca/filterfw/jni/jni_init.cpp new file mode 100644 index 0000000..3b131f1 --- /dev/null +++ b/media/mca/filterfw/jni/jni_init.cpp @@ -0,0 +1,43 @@ +/* + * 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. + */ + +#include "jni/jni_util.h" + +#include "native/core/native_frame.h" +#include "native/core/native_program.h" +#include "native/core/gl_env.h" +#include "native/core/gl_frame.h" +#include "native/core/shader_program.h" +#include "native/core/vertex_frame.h" + +using namespace android::filterfw; + +JavaVM* g_current_java_vm_ = NULL; + +jint JNI_OnLoad(JavaVM* vm, void* reserved) { + // Set the current vm pointer + g_current_java_vm_ = vm; + + // Initialize object pools + ObjectPool<NativeFrame>::Setup("android/filterfw/core/NativeFrame", "nativeFrameId"); + ObjectPool<NativeProgram>::Setup("android/filterfw/core/NativeProgram", "nativeProgramId"); + ObjectPool<GLFrame>::Setup("android/filterfw/core/GLFrame", "glFrameId"); + ObjectPool<ShaderProgram>::Setup("android/filterfw/core/ShaderProgram", "shaderProgramId"); + ObjectPool<GLEnv>::Setup("android/filterfw/core/GLEnvironment", "glEnvId"); + ObjectPool<VertexFrame>::Setup("android/filterfw/core/VertexFrame", "vertexFrameId"); + + return JNI_VERSION_1_4; +} diff --git a/media/mca/filterfw/jni/jni_native_buffer.cpp b/media/mca/filterfw/jni/jni_native_buffer.cpp new file mode 100644 index 0000000..097c145 --- /dev/null +++ b/media/mca/filterfw/jni/jni_native_buffer.cpp @@ -0,0 +1,84 @@ +/* + * 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. + */ + +#include "jni/jni_native_buffer.h" +#include "jni/jni_util.h" + +char* GetJBufferData(JNIEnv* env, jobject buffer, int* size) { + jclass base_class = env->FindClass("android/filterfw/core/NativeBuffer"); + + // Get fields + jfieldID ptr_field = env->GetFieldID(base_class, "mDataPointer", "J"); + jfieldID size_field = env->GetFieldID(base_class, "mSize", "I"); + + // Get their values + char* data = reinterpret_cast<char*>(env->GetLongField(buffer, ptr_field)); + if (size) { + *size = env->GetIntField(buffer, size_field); + } + + // Clean-up + env->DeleteLocalRef(base_class); + + return data; +} + +bool AttachDataToJBuffer(JNIEnv* env, jobject buffer, char* data, int size) { + jclass base_class = env->FindClass("android/filterfw/core/NativeBuffer"); + + // Get fields + jfieldID ptr_field = env->GetFieldID(base_class, "mDataPointer", "J"); + jfieldID size_field = env->GetFieldID(base_class, "mSize", "I"); + + // Set their values + env->SetLongField(buffer, ptr_field, reinterpret_cast<jlong>(data)); + env->SetIntField(buffer, size_field, size); + + return true; +} + +jboolean Java_android_filterfw_core_NativeBuffer_allocate(JNIEnv* env, jobject thiz, jint size) { + char* data = new char[size]; + return ToJBool(AttachDataToJBuffer(env, thiz, data, size)); +} + +jboolean Java_android_filterfw_core_NativeBuffer_deallocate(JNIEnv* env, + jobject thiz, + jboolean owns_data) { + if (ToCppBool(owns_data)) { + char* data = GetJBufferData(env, thiz, NULL); + delete[] data; + } + return JNI_TRUE; +} + +jboolean Java_android_filterfw_core_NativeBuffer_nativeCopyTo(JNIEnv* env, + jobject thiz, + jobject new_buffer) { + // Get source buffer + int size; + char* source_data = GetJBufferData(env, thiz, &size); + + // Make copy + char* target_data = new char[size]; + memcpy(target_data, source_data, size); + + // Attach it to new buffer + AttachDataToJBuffer(env, new_buffer, target_data, size); + + return JNI_TRUE; +} + diff --git a/media/mca/filterfw/jni/jni_native_buffer.h b/media/mca/filterfw/jni/jni_native_buffer.h new file mode 100644 index 0000000..73c12be --- /dev/null +++ b/media/mca/filterfw/jni/jni_native_buffer.h @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTEFW_JNI_NATIVE_BUFFER_H +#define ANDROID_FILTEFW_JNI_NATIVE_BUFFER_H + +#include <jni.h> + +// Internal Buffer Unwrapping functions //////////////////////////////////////////////////////////// +/** + * Given a Java NativeBuffer instance, get access to the underlying C pointer and its size. The + * size argument may be NULL, in which case the object is not queried for its size. + **/ +char* GetJBufferData(JNIEnv* env, jobject buffer, int* size); + +/** + * Attach a given C data buffer and its size to a given allocated Java NativeBuffer instance. After + * this call, the java instance will have the given C buffer as its backing. Note, that the Java + * instance contains the flag on whether or not it owns the buffer or not, so make sure it is what + * you expect. + **/ +bool AttachDataToJBuffer(JNIEnv* env, jobject buffer, char* data, int size); + +#ifdef __cplusplus +extern "C" { +#endif + +// JNI Wrappers //////////////////////////////////////////////////////////////////////////////////// +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeBuffer_allocate(JNIEnv* env, jobject thiz, jint size); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeBuffer_deallocate(JNIEnv* env, jobject thiz, jboolean owns_data); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeBuffer_nativeCopyTo(JNIEnv* env, jobject thiz, jobject new_buffer); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_FILTEFW_JNI_NATIVE_BUFFER_H diff --git a/media/mca/filterfw/jni/jni_native_frame.cpp b/media/mca/filterfw/jni/jni_native_frame.cpp new file mode 100644 index 0000000..1dfa3e6 --- /dev/null +++ b/media/mca/filterfw/jni/jni_native_frame.cpp @@ -0,0 +1,293 @@ +/* + * 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. + */ + +#include "android/bitmap.h" + +#include "jni/jni_native_frame.h" +#include "jni/jni_native_buffer.h" +#include "jni/jni_util.h" + +#include "native/base/logging.h" +#include "native/core/gl_frame.h" +#include "native/core/native_frame.h" + +using android::filterfw::NativeFrame; +using android::filterfw::GLFrame; + +typedef union { + uint32_t value; + uint8_t rgba[4]; +} Pixel; + +jboolean Java_android_filterfw_core_NativeFrame_nativeAllocate(JNIEnv* env, + jobject thiz, + jint size) { + return ToJBool(WrapObjectInJava(new NativeFrame(size), env, thiz, true)); +} + +jboolean Java_android_filterfw_core_NativeFrame_nativeDeallocate(JNIEnv* env, jobject thiz) { + return ToJBool(DeleteNativeObject<NativeFrame>(env, thiz)); +} + +jint Java_android_filterfw_core_NativeFrame_nativeIntSize(JNIEnv*, jclass) { + return sizeof(jint); +} + +jint Java_android_filterfw_core_NativeFrame_nativeFloatSize(JNIEnv*, jclass) { + return sizeof(jfloat); +} + +jboolean Java_android_filterfw_core_NativeFrame_setNativeData(JNIEnv* env, + jobject thiz, + jbyteArray data, + jint offset, + jint length) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + if (frame && data) { + jbyte* bytes = env->GetByteArrayElements(data, NULL); + if (bytes) { + const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(bytes + offset), + 0, + length); + env->ReleaseByteArrayElements(data, bytes, JNI_ABORT); + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jbyteArray Java_android_filterfw_core_NativeFrame_getNativeData(JNIEnv* env, + jobject thiz, + jint size) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + if (frame) { + const uint8_t* data = frame->Data(); + if (!data || size > frame->Size()) + return NULL; + jbyteArray result = env->NewByteArray(size); + env->SetByteArrayRegion(result, 0, size, reinterpret_cast<const jbyte*>(data)); + return result; + } + return NULL; +} + +jboolean Java_android_filterfw_core_NativeFrame_getNativeBuffer(JNIEnv* env, + jobject thiz, + jobject buffer) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + if (frame) { + char* data = reinterpret_cast<char*>(frame->MutableData()); + return ToJBool(AttachDataToJBuffer(env, buffer, data, frame->Size())); + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_NativeFrame_setNativeInts(JNIEnv* env, + jobject thiz, + jintArray ints) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + if (frame && ints) { + jint* int_ptr = env->GetIntArrayElements(ints, NULL); + const int length = env->GetArrayLength(ints); + if (int_ptr) { + const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(int_ptr), + 0, + length * sizeof(jint)); + env->ReleaseIntArrayElements(ints, int_ptr, JNI_ABORT); + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jintArray Java_android_filterfw_core_NativeFrame_getNativeInts(JNIEnv* env, + jobject thiz, + jint size) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + if (frame) { + const uint8_t* data = frame->Data(); + if (!data || size > frame->Size() || (size % sizeof(jint)) != 0) + return NULL; + const int count = size / sizeof(jint); + jintArray result = env->NewIntArray(count); + env->SetIntArrayRegion(result, 0, count, reinterpret_cast<const jint*>(data)); + return result; + } + return NULL; +} + +jboolean Java_android_filterfw_core_NativeFrame_setNativeFloats(JNIEnv* env, + jobject thiz, + jfloatArray floats) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + if (frame && floats) { + jfloat* float_ptr = env->GetFloatArrayElements(floats, NULL); + const int length = env->GetArrayLength(floats); + if (float_ptr) { + const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(float_ptr), + 0, + length * sizeof(jfloat)); + env->ReleaseFloatArrayElements(floats, float_ptr, JNI_ABORT); + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jfloatArray Java_android_filterfw_core_NativeFrame_getNativeFloats(JNIEnv* env, + jobject thiz, + jint size) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + if (frame) { + const uint8_t* data = frame->Data(); + if (!data || size > frame->Size() || (size % sizeof(jfloat)) != 0) + return NULL; + const int count = size / sizeof(jfloat); + jfloatArray result = env->NewFloatArray(count); + env->SetFloatArrayRegion(result, 0, count, reinterpret_cast<const jfloat*>(data)); + return result; + } + return NULL; +} + +jboolean Java_android_filterfw_core_NativeFrame_setNativeBitmap(JNIEnv* env, + jobject thiz, + jobject bitmap, + jint size, + jint bytes_per_sample) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + if (frame && bitmap) { + // Make sure frame size matches bitmap size + if ((size / 4) != (frame->Size() / bytes_per_sample)) { + ALOGE("Size mismatch in native setBitmap()!"); + return JNI_FALSE; + } + + Pixel* src_ptr; + const int result = AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void**>(&src_ptr)); + if (result == ANDROID_BITMAP_RESUT_SUCCESS) { + // Create destination pointers + uint8_t* dst_ptr = reinterpret_cast<uint8_t*>(frame->MutableData()); + const uint8_t* end_ptr = dst_ptr + frame->Size(); + switch (bytes_per_sample) { + case 1: { // RGBA -> GRAY + while (dst_ptr < end_ptr) { + const Pixel pixel = *(src_ptr++); + *(dst_ptr++) = (pixel.rgba[0] + pixel.rgba[1] + pixel.rgba[2]) / 3; + } + break; + } + case 3: { // RGBA -> RGB + while (dst_ptr < end_ptr) { + const Pixel pixel = *(src_ptr++); + *(dst_ptr++) = pixel.rgba[0]; + *(dst_ptr++) = pixel.rgba[1]; + *(dst_ptr++) = pixel.rgba[2]; + } + break; + } + case 4: { // RGBA -> RGBA + memcpy(dst_ptr, src_ptr, frame->Size()); + break; + } + default: + ALOGE("Unsupported bytes-per-pixel %d in setBitmap!", bytes_per_sample); + break; + } + return (AndroidBitmap_unlockPixels(env, bitmap) == ANDROID_BITMAP_RESUT_SUCCESS); + } + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_NativeFrame_getNativeBitmap(JNIEnv* env, + jobject thiz, + jobject bitmap, + jint size, + jint bytes_per_sample) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + if (frame && bitmap) { + Pixel* dst_ptr; + const int result = AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void**>(&dst_ptr)); + if (result == ANDROID_BITMAP_RESUT_SUCCESS) { + // Make sure frame size matches bitmap size + if ((size / 4) != (frame->Size() / bytes_per_sample)) { + ALOGE("Size mismatch in native getBitmap()!"); + return JNI_FALSE; + } + + const uint8_t* src_ptr = frame->Data(); + const uint8_t* end_ptr = src_ptr + frame->Size(); + switch (bytes_per_sample) { + case 1: { // GRAY -> RGBA + while (src_ptr < end_ptr) { + const uint8_t value = *(src_ptr++); + dst_ptr->rgba[0] = dst_ptr->rgba[1] = dst_ptr->rgba[2] = value; + dst_ptr->rgba[3] = 255; + ++dst_ptr; + } + break; + } + case 3: { // RGB -> RGBA + while (src_ptr < end_ptr) { + dst_ptr->rgba[0] = *(src_ptr++); + dst_ptr->rgba[1] = *(src_ptr++); + dst_ptr->rgba[2] = *(src_ptr++); + dst_ptr->rgba[3] = 255; + ++dst_ptr; + } + break; + } + case 4: { // RGBA -> RGBA + memcpy(dst_ptr, src_ptr, frame->Size()); + break; + } + default: + ALOGE("Unsupported bytes-per-pixel %d in getBitmap!", bytes_per_sample); + break; + } + return (AndroidBitmap_unlockPixels(env, bitmap) == ANDROID_BITMAP_RESUT_SUCCESS); + } + } + return JNI_FALSE; +} + +jint Java_android_filterfw_core_NativeFrame_getNativeCapacity(JNIEnv* env, jobject thiz) { + NativeFrame* frame = ConvertFromJava<NativeFrame>(env, thiz); + return frame ? frame->Capacity() : -1; +} + +jboolean Java_android_filterfw_core_NativeFrame_nativeCopyFromNative(JNIEnv* env, + jobject thiz, + jobject frame) { + NativeFrame* this_frame = ConvertFromJava<NativeFrame>(env, thiz); + NativeFrame* other_frame = ConvertFromJava<NativeFrame>(env, frame); + if (this_frame && other_frame) { + return ToJBool(this_frame->WriteData(other_frame->Data(), 0, other_frame->Size())); + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_NativeFrame_nativeCopyFromGL(JNIEnv* env, + jobject thiz, + jobject frame) { + NativeFrame* this_frame = ConvertFromJava<NativeFrame>(env, thiz); + GLFrame* other_frame = ConvertFromJava<GLFrame>(env, frame); + if (this_frame && other_frame) { + return ToJBool(other_frame->CopyDataTo(this_frame->MutableData(), this_frame->Size())); + } + return JNI_FALSE; +} diff --git a/media/mca/filterfw/jni/jni_native_frame.h b/media/mca/filterfw/jni/jni_native_frame.h new file mode 100644 index 0000000..ecd9f82 --- /dev/null +++ b/media/mca/filterfw/jni/jni_native_frame.h @@ -0,0 +1,94 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_JNI_NATIVE_FRAME_H +#define ANDROID_FILTERFW_JNI_NATIVE_FRAME_H + +#include <jni.h> + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_nativeAllocate(JNIEnv* env, jobject thiz, jint size); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_nativeDeallocate(JNIEnv* env, jobject thiz); + +JNIEXPORT jint JNICALL +Java_android_filterfw_core_NativeFrame_nativeIntSize(JNIEnv* env, jclass clazz); + +JNIEXPORT jint JNICALL +Java_android_filterfw_core_NativeFrame_nativeFloatSize(JNIEnv* env, jclass clazz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_setNativeInts(JNIEnv* env, jobject thiz, jintArray ints); + +JNIEXPORT jintArray JNICALL +Java_android_filterfw_core_NativeFrame_getNativeInts(JNIEnv* env, jobject thiz, jint size); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_setNativeFloats(JNIEnv* env, jobject thiz, jfloatArray ints); + +JNIEXPORT jfloatArray JNICALL +Java_android_filterfw_core_NativeFrame_getNativeFloats(JNIEnv* env, jobject thiz, jint size); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_setNativeData(JNIEnv* env, + jobject thiz, + jbyteArray data, + jint offset, + jint length); + +JNIEXPORT jbyteArray JNICALL +Java_android_filterfw_core_NativeFrame_getNativeData(JNIEnv* env, jobject thiz, jint size); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_getNativeBuffer(JNIEnv* env, jobject thiz, jobject buffer); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_setNativeBitmap(JNIEnv* env, + jobject thiz, + jobject bitmap, + jint size, + jint bytes_per_sample); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_getNativeBitmap(JNIEnv* env, + jobject thiz, + jobject bitmap, + jint size, + jint bytes_per_sample); + +JNIEXPORT jint JNICALL +Java_android_filterfw_core_NativeFrame_getNativeCapacity(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_nativeCopyFromNative(JNIEnv* env, + jobject thiz, + jobject frame); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeFrame_nativeCopyFromGL(JNIEnv* env, + jobject thiz, + jobject frame); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_FILTERFW_JNI_NATIVE_FRAME_H diff --git a/media/mca/filterfw/jni/jni_native_program.cpp b/media/mca/filterfw/jni/jni_native_program.cpp new file mode 100644 index 0000000..b30b769 --- /dev/null +++ b/media/mca/filterfw/jni/jni_native_program.cpp @@ -0,0 +1,187 @@ +/* + * 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. + */ + +#include <string> +#include <vector> + +#include "jni/jni_native_program.h" +#include "jni/jni_util.h" + +#include "native/base/logging.h" +#include "native/core/native_frame.h" +#include "native/core/native_program.h" + +using android::filterfw::NativeFrame; +using android::filterfw::NativeProgram; + +jboolean Java_android_filterfw_core_NativeProgram_allocate(JNIEnv* env, jobject thiz) { + return ToJBool(WrapObjectInJava(new NativeProgram(), env, thiz, true)); +} + +jboolean Java_android_filterfw_core_NativeProgram_deallocate(JNIEnv* env, jobject thiz) { + return ToJBool(DeleteNativeObject<NativeProgram>(env, thiz)); +} + +jboolean Java_android_filterfw_core_NativeProgram_nativeInit(JNIEnv* env, jobject thiz) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && program->CallInit()); +} + +jboolean Java_android_filterfw_core_NativeProgram_openNativeLibrary(JNIEnv* env, + jobject thiz, + jstring lib_name) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && lib_name && program->OpenLibrary(ToCppString(env, lib_name))); +} + +jboolean Java_android_filterfw_core_NativeProgram_bindInitFunction(JNIEnv* env, + jobject thiz, + jstring func_name) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && func_name && program->BindInitFunction(ToCppString(env, func_name))); +} + +jboolean Java_android_filterfw_core_NativeProgram_bindSetValueFunction(JNIEnv* env, + jobject thiz, + jstring func_name) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && + func_name && + program->BindSetValueFunction(ToCppString(env, func_name))); +} + +jboolean Java_android_filterfw_core_NativeProgram_bindGetValueFunction(JNIEnv* env, + jobject thiz, + jstring func_name) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && + func_name && + program->BindGetValueFunction(ToCppString(env, func_name))); +} + +jboolean Java_android_filterfw_core_NativeProgram_bindProcessFunction(JNIEnv* env, + jobject thiz, + jstring func_name) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && func_name && program->BindProcessFunction(ToCppString(env, func_name))); +} + +jboolean Java_android_filterfw_core_NativeProgram_bindResetFunction(JNIEnv* env, + jobject thiz, + jstring func_name) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && + func_name && + program->BindResetFunction(ToCppString(env, func_name))); +} + +jboolean Java_android_filterfw_core_NativeProgram_bindTeardownFunction(JNIEnv* env, + jobject thiz, + jstring func_name) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && + func_name && + program->BindTeardownFunction(ToCppString(env, func_name))); +} + +jboolean Java_android_filterfw_core_NativeProgram_callNativeInit(JNIEnv* env, jobject thiz) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && program->CallInit()); +} + +jboolean Java_android_filterfw_core_NativeProgram_callNativeSetValue(JNIEnv* env, + jobject thiz, + jstring key, + jstring value) { + if (!value) { + ALOGE("Native Program: Attempting to set null value for key %s!", + ToCppString(env, key).c_str()); + } + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + const std::string c_value = ToCppString(env, value); + const std::string c_key = ToCppString(env, key); + return ToJBool(program && program->CallSetValue(c_key, c_value)); +} + +jstring Java_android_filterfw_core_NativeProgram_callNativeGetValue(JNIEnv* env, + jobject thiz, + jstring key) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + const std::string c_key = ToCppString(env, key); + if (program) { + return ToJString(env, program->CallGetValue(c_key)); + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_NativeProgram_callNativeProcess(JNIEnv* env, + jobject thiz, + jobjectArray inputs, + jobject output) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + + // Sanity checks + if (!program || !inputs) { + return JNI_FALSE; + } + + // Get the input buffers + const int input_count = env->GetArrayLength(inputs); + std::vector<const char*> input_buffers(input_count, NULL); + std::vector<int> input_sizes(input_count, 0); + for (int i = 0 ; i < input_count; ++i) { + const char* input_data = NULL; + int input_size = 0; + jobject input = env->GetObjectArrayElement(inputs, i); + if (input) { + NativeFrame* native_frame = ConvertFromJava<NativeFrame>(env, input); + if (!native_frame) { + ALOGE("NativeProgram: Could not grab NativeFrame input %d!", i); + return JNI_FALSE; + } + input_data = reinterpret_cast<const char*>(native_frame->Data()); + input_size = native_frame->Size(); + } + input_buffers[i] = input_data; + input_sizes[i] = input_size; + } + + // Get the output buffer + char* output_data = NULL; + int output_size = 0; + if (output) { + NativeFrame* output_frame = ConvertFromJava<NativeFrame>(env, output); + if (!output_frame) { + ALOGE("NativeProgram: Could not grab NativeFrame output!"); + return JNI_FALSE; + } + output_data = reinterpret_cast<char*>(output_frame->MutableData()); + output_size = output_frame->Size(); + } + + // Process the frames! + return ToJBool(program->CallProcess(input_buffers, input_sizes, output_data, output_size)); +} + +jboolean Java_android_filterfw_core_NativeProgram_callNativeReset(JNIEnv* env, jobject thiz) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && program->CallReset()); +} + +jboolean Java_android_filterfw_core_NativeProgram_callNativeTeardown(JNIEnv* env, jobject thiz) { + NativeProgram* program = ConvertFromJava<NativeProgram>(env, thiz); + return ToJBool(program && program->CallTeardown()); +} diff --git a/media/mca/filterfw/jni/jni_native_program.h b/media/mca/filterfw/jni/jni_native_program.h new file mode 100644 index 0000000..fa97c39 --- /dev/null +++ b/media/mca/filterfw/jni/jni_native_program.h @@ -0,0 +1,100 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_JNI_NATIVE_PROGRAM_H +#define ANDROID_FILTERFW_JNI_NATIVE_PROGRAM_H + +#include <jni.h> + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_allocate(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_deallocate(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_nativeInit(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_openNativeLibrary(JNIEnv* env, + jobject thiz, + jstring lib_name); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_bindInitFunction(JNIEnv* env, + jobject thiz, + jstring func_name); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_bindSetValueFunction(JNIEnv* env, + jobject thiz, + jstring func_name); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_bindGetValueFunction(JNIEnv* env, + jobject thiz, + jstring func_name); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_bindProcessFunction(JNIEnv* env, + jobject thiz, + jstring func_name); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_bindResetFunction(JNIEnv* env, + jobject thiz, + jstring func_name); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_bindTeardownFunction(JNIEnv* env, + jobject thiz, + jstring func_name); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_callNativeInit(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_callNativeSetValue(JNIEnv* env, + jobject thiz, + jstring key, + jstring value); + +JNIEXPORT jstring JNICALL +Java_android_filterfw_core_NativeProgram_callNativeGetValue(JNIEnv* env, + jobject thiz, + jstring key); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_callNativeProcess(JNIEnv* env, + jobject thiz, + jobjectArray inputs, + jobject output); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_callNativeReset(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_NativeProgram_callNativeTeardown(JNIEnv* env, jobject thiz); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_FILTERFW_JNI_NATIVE_PROGRAM_H diff --git a/media/mca/filterfw/jni/jni_shader_program.cpp b/media/mca/filterfw/jni/jni_shader_program.cpp new file mode 100644 index 0000000..19f43cd --- /dev/null +++ b/media/mca/filterfw/jni/jni_shader_program.cpp @@ -0,0 +1,326 @@ +/* + * 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. + */ + +#include <string> +#include <vector> + +#include "jni/jni_shader_program.h" +#include "jni/jni_util.h" + +#include "native/base/logging.h" +#include "native/core/geometry.h" +#include "native/core/gl_env.h" +#include "native/core/gl_frame.h" +#include "native/core/shader_program.h" +#include "native/core/vertex_frame.h" + +using android::filterfw::GLEnv; +using android::filterfw::GLFrame; +using android::filterfw::Point; +using android::filterfw::ProgramVar; +using android::filterfw::Quad; +using android::filterfw::ShaderProgram; +using android::filterfw::VertexFrame; + +jboolean Java_android_filterfw_core_ShaderProgram_allocate(JNIEnv* env, + jobject thiz, + jobject gl_env, + jstring vertex_shader, + jstring fragment_shader) { + // Get the GLEnv pointer + GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env); + + // Create the shader + if (!fragment_shader || !gl_env_ptr) + return false; + else if (!vertex_shader) + return ToJBool(WrapObjectInJava(new ShaderProgram( + gl_env_ptr, + ToCppString(env, fragment_shader)), + env, + thiz, + true)); + else + return ToJBool(WrapObjectInJava(new ShaderProgram( + gl_env_ptr, + ToCppString(env, vertex_shader), + ToCppString(env, fragment_shader)), + env, + thiz, + true)); +} + +jboolean Java_android_filterfw_core_ShaderProgram_deallocate(JNIEnv* env, jobject thiz) { + return ToJBool(DeleteNativeObject<ShaderProgram>(env, thiz)); +} + +jboolean Java_android_filterfw_core_ShaderProgram_compileAndLink(JNIEnv* env, jobject thiz) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + return program ? ToJBool(program->CompileAndLink()) : JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setUniformValue(JNIEnv* env, + jobject thiz, + jstring key, + jobject value) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + const Value c_value = ToCValue(env, value); + const std::string c_key = ToCppString(env, key); + if (c_value.value) { + return ToJBool(program && program->SetUniformValue(c_key, c_value)); + } else { + ALOGE("ShaderProgram: Could not convert java object value passed for key '%s'!", c_key.c_str()); + return JNI_FALSE; + } +} + +jobject Java_android_filterfw_core_ShaderProgram_getUniformValue(JNIEnv* env, + jobject thiz, + jstring key) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + const std::string c_key = ToCppString(env, key); + return program ? ToJObject(env, program->GetUniformValue(c_key)) : JNI_NULL; +} + +jboolean Java_android_filterfw_core_ShaderProgram_shaderProcess(JNIEnv* env, + jobject thiz, + jobjectArray inputs, + jobject output) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + std::vector<const GLFrame*> input_frames; + if (program && inputs && output) { + // Get the input frames + const int input_count = env->GetArrayLength(inputs); + for (int i = 0; i < input_count; ++i) { + jobject input = env->GetObjectArrayElement(inputs, i); + const GLFrame* input_frame = ConvertFromJava<GLFrame>(env, input); + if (!input || !input_frame) { + ALOGE("ShaderProgram: invalid input frame %d!", i); + return JNI_FALSE; + } + input_frames.push_back(input_frame); + } + + // Get the output frame + GLFrame* output_frame = ConvertFromJava<GLFrame>(env, output); + if (!output_frame) { + ALOGE("ShaderProgram: no output frame found!"); + return JNI_FALSE; + } + + // Process the frames! + if (!program->Process(input_frames, output_frame)) { + ALOGE("ShaderProgram: error processing shader!"); + return JNI_FALSE; + } + + return JNI_TRUE; + } + return JNI_FALSE; +} + +jobject Java_android_filterfw_core_ShaderProgram_nativeCreateIdentity(JNIEnv* env, + jclass, + jobject gl_env) { + GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env); + ShaderProgram* program = gl_env_ptr ? ShaderProgram::CreateIdentity(gl_env_ptr) : NULL; + return program ? WrapNewObjectInJava(program, env, false) : NULL; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setSourceRegion(JNIEnv* env, + jobject thiz, + jfloat x0, + jfloat y0, + jfloat x1, + jfloat y1, + jfloat x2, + jfloat y2, + jfloat x3, + jfloat y3) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + program->SetSourceRegion(Quad(Point(x0, y0), Point(x1, y1), Point(x2, y2), Point(x3, y3))); + return JNI_TRUE; + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setTargetRegion(JNIEnv* env, + jobject thiz, + jfloat x0, + jfloat y0, + jfloat x1, + jfloat y1, + jfloat x2, + jfloat y2, + jfloat x3, + jfloat y3) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + program->SetTargetRegion(Quad(Point(x0, y0), Point(x1, y1), Point(x2, y2), Point(x3, y3))); + return JNI_TRUE; + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setShaderClearsOutput(JNIEnv* env, + jobject thiz, + jboolean clears) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + program->SetClearsOutput(ToCppBool(clears)); + return JNI_TRUE; + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setShaderBlendEnabled(JNIEnv* env, + jobject thiz, + jboolean enable) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + program->SetBlendEnabled(ToCppBool(enable)); + return JNI_TRUE; + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setShaderBlendFunc(JNIEnv* env, + jobject thiz, + jint sfactor, + jint dfactor) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + program->SetBlendFunc(sfactor, dfactor); + return JNI_TRUE; + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setShaderClearColor(JNIEnv* env, + jobject thiz, + jfloat r, + jfloat g, + jfloat b) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + program->SetClearColor(r, g, b, 1.0f); + return JNI_TRUE; + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setShaderDrawMode(JNIEnv* env, + jobject thiz, + jint draw_mode) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + program->SetDrawMode(draw_mode); + return JNI_TRUE; + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setShaderTileCounts(JNIEnv* env, + jobject thiz, + jint x_count, + jint y_count) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + program->SetTileCounts(x_count, y_count); + return JNI_TRUE; + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setShaderVertexCount(JNIEnv* env, + jobject thiz, + jint vertex_count) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + program->SetVertexCount(vertex_count); + return JNI_TRUE; + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_beginShaderDrawing(JNIEnv* env, jobject thiz) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + return ToJBool(program && program->BeginDraw()); +} + +jboolean Java_android_filterfw_core_ShaderProgram_setShaderAttributeValues( + JNIEnv* env, + jobject thiz, + jstring attr_name, + jfloatArray values, + jint component_count) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + // Get the floats to set + jfloat* float_ptr = env->GetFloatArrayElements(values, NULL); + const int length = env->GetArrayLength(values); + + // Get the program variable to set + const std::string attr_string = ToCppString(env, attr_name); + ProgramVar program_var = program->GetAttribute(attr_string); + + // Set the variable + if (float_ptr && ShaderProgram::IsVarValid(program_var)) { + const bool success = program->SetAttributeValues(program_var, + reinterpret_cast<float*>(float_ptr), + length, + component_count); + env->ReleaseFloatArrayElements(values, float_ptr, JNI_ABORT); + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_ShaderProgram_setShaderAttributeVertexFrame( + JNIEnv* env, + jobject thiz, + jstring attr_name, + jobject vertex_frame, + jint type, + jint component_count, + jint stride, + jint offset, + jboolean normalize) { + ShaderProgram* program = ConvertFromJava<ShaderProgram>(env, thiz); + if (program) { + // Get the vertex frame + VertexFrame* v_frame = ConvertFromJava<VertexFrame>(env, vertex_frame); + + // Get the program variable to set + const std::string attr_string = ToCppString(env, attr_name); + ProgramVar program_var = program->GetAttribute(attr_string); + + // Set the variable + if (v_frame && ShaderProgram::IsVarValid(program_var)) { + const bool success = program->SetAttributeValues(program_var, + v_frame, + type, + component_count, + stride, + offset, + ToCppBool(normalize)); + return ToJBool(success); + } + } + return JNI_FALSE; +} diff --git a/media/mca/filterfw/jni/jni_shader_program.h b/media/mca/filterfw/jni/jni_shader_program.h new file mode 100644 index 0000000..94a1dd4 --- /dev/null +++ b/media/mca/filterfw/jni/jni_shader_program.h @@ -0,0 +1,150 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_JNI_SHADER_PROGRAM_H +#define ANDROID_FILTERFW_JNI_SHADER_PROGRAM_H + +#include <jni.h> + +#include "native/core/value.h" + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_allocate(JNIEnv* env, + jobject thiz, + jobject gl_env, + jstring vertex_shader, + jstring fragment_shader); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_deallocate(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_compileAndLink(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setUniformValue(JNIEnv* env, + jobject thiz, + jstring key, + jobject value); + +JNIEXPORT jobject JNICALL +Java_android_filterfw_core_ShaderProgram_getUniformValue(JNIEnv* env, + jobject thiz, + jstring key); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_shaderProcess(JNIEnv* env, + jobject thiz, + jobjectArray inputs, + jobject output); + +JNIEXPORT jobject JNICALL +Java_android_filterfw_core_ShaderProgram_nativeCreateIdentity(JNIEnv* env, + jclass clazz, + jobject gl_env); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setSourceRegion(JNIEnv* env, + jobject thiz, + jfloat x0, + jfloat y0, + jfloat x1, + jfloat y1, + jfloat x2, + jfloat y2, + jfloat x3, + jfloat y3); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setTargetRegion(JNIEnv* env, + jobject thiz, + jfloat x0, + jfloat y0, + jfloat x1, + jfloat y1, + jfloat x2, + jfloat y2, + jfloat x3, + jfloat y3); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setShaderClearsOutput(JNIEnv* env, + jobject thiz, + jboolean clears); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setShaderClearColor(JNIEnv* env, + jobject thiz, + jfloat r, + jfloat g, + jfloat b); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setShaderBlendEnabled(JNIEnv* env, + jobject thiz, + jboolean enable); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setShaderBlendFunc(JNIEnv* env, + jobject thiz, + jint sfactor, + jint dfactor); +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setShaderDrawMode(JNIEnv* env, + jobject thiz, + jint draw_mode); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setShaderTileCounts(JNIEnv* env, + jobject thiz, + jint x_count, + jint y_count); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setShaderVertexCount(JNIEnv* env, + jobject thiz, + jint vertex_count); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_beginShaderDrawing(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setShaderAttributeValues(JNIEnv* env, + jobject thiz, + jstring attr_name, + jfloatArray values, + jint component_count); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_ShaderProgram_setShaderAttributeVertexFrame(JNIEnv* env, + jobject thiz, + jstring attr_name, + jobject vertex_frame, + jint type, + jint component_count, + jint stride, + jint offset, + jboolean normalize); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_FILTERFW_JNI_SHADER_PROGRAM_H diff --git a/media/mca/filterfw/jni/jni_util.cpp b/media/mca/filterfw/jni/jni_util.cpp new file mode 100644 index 0000000..30c0898 --- /dev/null +++ b/media/mca/filterfw/jni/jni_util.cpp @@ -0,0 +1,188 @@ +/* + * 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. + */ + +#include <string> + +#include "jni/jni_util.h" + +#include "base/logging.h" + +#if 0 +JavaObject::JavaObject() + : object_(JNI_NULL), + ref_count_(new int(0)) { +} + +JavaObject::JavaObject(const JavaObject& java_obj) + : object_(java_obj.object_), + ref_count_(java_obj.ref_count_) { + Retain(); +} + +JavaObject::JavaObject(jobject object, JNIEnv* env) + : object_(NULL), + ref_count_(new int(0)) { + Retain(); + object_ = env->NewGlobalRef(object_); +} + +JavaObject::~JavaObject() { + Release(); +} + +JavaObject& JavaObject::operator=(const JavaObject& java_obj) { + Release(); + object_ = java_obj.object_; + ref_count_ = java_obj.ref_count_; + Retain(); + return *this; +} + +void JavaObject::Retain() { + if (ref_count_) + ++(*ref_count_); + else + ALOGE("JavaObject: Reference count is NULL! JavaObject may be corrupted."); +} + +void JavaObject::Release() { + if (ref_count_) { + if (*ref_count_ > 0) + --(*ref_count_); + if (*ref_count_ == 0) { + JNIEnv* env = GetCurrentJNIEnv(); + if (!env) + ALOGE("JavaObject: Releasing outside of Java thread. Will just leak!"); + else if (object_) + env->DeleteGlobalRef(object_); + delete ref_count_; + ref_count_ = NULL; + } + } else { + ALOGE("JavaObject: Reference count is NULL! JavaObject may be corrupted."); + } +} + +void JavaObject::Reset() { + Release(); + object_ = NULL; + ref_count_ = new int(0); +} + +JavaVM* GetCurrentJavaVM() { + return g_current_java_vm_; +} + +JNIEnv* GetCurrentJNIEnv() { + JavaVM* vm = GetCurrentJavaVM(); + JNIEnv* env = NULL; + const jint result = vm->GetEnv(reinterpret_cast<void**>(&env), + JNI_VERSION_1_4); + return result == JNI_OK ? env : NULL; +} +#endif + +jstring ToJString(JNIEnv* env, const std::string& value) { + return env->NewStringUTF(value.c_str()); +} + +std::string ToCppString(JNIEnv* env, jstring value) { + jboolean isCopy; + const char* c_value = env->GetStringUTFChars(value, &isCopy); + std::string result(c_value); + if (isCopy == JNI_TRUE) + env->ReleaseStringUTFChars(value, c_value); + return result; +} + +jboolean ToJBool(bool value) { + return value ? JNI_TRUE : JNI_FALSE; +} + +bool ToCppBool(jboolean value) { + return value == JNI_TRUE; +} + +// TODO: We actually shouldn't use such a function as it requires a class name lookup at every +// invocation. Instead, store the class objects and use those. +bool IsJavaInstanceOf(JNIEnv* env, jobject object, const std::string& class_name) { + jclass clazz = env->FindClass(class_name.c_str()); + return clazz ? env->IsInstanceOf(object, clazz) == JNI_TRUE : false; +} + +template<typename T> +jobject CreateJObject(JNIEnv* env, const std::string& class_name, const std::string& signature, T value) { + jobject result = JNI_NULL; + + return result; +} + +Value ToCValue(JNIEnv* env, jobject object) { + Value result = MakeNullValue(); + if (object != NULL) { + if (IsJavaInstanceOf(env, object, "java/lang/Boolean")) { + jmethodID method = env->GetMethodID(env->GetObjectClass(object), "booleanValue", "()Z"); + result = MakeIntValue(env->CallBooleanMethod(object, method) == JNI_TRUE ? 1 : 0); + } else if (IsJavaInstanceOf(env, object, "java/lang/Integer")) { + jmethodID method = env->GetMethodID(env->GetObjectClass(object), "intValue", "()I"); + result = MakeIntValue(env->CallIntMethod(object, method)); + } else if (IsJavaInstanceOf(env, object, "java/lang/Float")) { + jmethodID method = env->GetMethodID(env->GetObjectClass(object), "floatValue", "()F"); + result = MakeFloatValue(env->CallFloatMethod(object, method)); + } else if (IsJavaInstanceOf(env, object, "java/lang/String")) { + result = MakeStringValue(ToCppString(env, static_cast<jstring>(object)).c_str()); + } else if (IsJavaInstanceOf(env, object, "[I")) { + jint* elems = env->GetIntArrayElements(static_cast<jintArray>(object), NULL); + const jint count = env->GetArrayLength(static_cast<jintArray>(object)); + result = MakeIntArrayValue(elems, count); + env->ReleaseIntArrayElements(static_cast<jintArray>(object), elems, JNI_ABORT); + } else if (IsJavaInstanceOf(env, object, "[F")) { + jfloat* elems = env->GetFloatArrayElements(static_cast<jfloatArray>(object), NULL); + const jint count = env->GetArrayLength(static_cast<jfloatArray>(object)); + result = MakeFloatArrayValue(elems, count); + env->ReleaseFloatArrayElements(static_cast<jfloatArray>(object), elems, JNI_ABORT); + } + } + return result; +} + +jobject ToJObject(JNIEnv* env, const Value& value) { + jobject result = JNI_NULL; + if (ValueIsInt(value)) { + jclass clazz = env->FindClass("java/lang/Integer"); + jmethodID constructorID = env->GetMethodID(clazz, "<init>", "(I)V"); + result = env->NewObject(clazz, constructorID, GetIntValue(value)); + } else if (ValueIsFloat(value)) { + jclass clazz = env->FindClass("java/lang/Float"); + jmethodID constructorID = env->GetMethodID(clazz, "<init>", "(F)V"); + result = env->NewObject(clazz, constructorID, GetFloatValue(value)); + } else if (ValueIsString(value)) { + result = ToJString(env, GetStringValue(value)); + } else if (ValueIsIntArray(value)) { + result = env->NewIntArray(GetValueCount(value)); + env->SetIntArrayRegion(static_cast<jintArray>(result), + 0, + GetValueCount(value), + reinterpret_cast<const jint*>(GetIntArrayValue(value))); + } else if (ValueIsFloatArray(value)) { + result = env->NewFloatArray(GetValueCount(value)); + env->SetFloatArrayRegion(static_cast<jfloatArray>(result), + 0, + GetValueCount(value), + reinterpret_cast<const jfloat*>(GetFloatArrayValue(value))); + } + return result; +} diff --git a/media/mca/filterfw/jni/jni_util.h b/media/mca/filterfw/jni/jni_util.h new file mode 100644 index 0000000..68ff653 --- /dev/null +++ b/media/mca/filterfw/jni/jni_util.h @@ -0,0 +1,283 @@ +/* + * 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. + */ + +#include <jni.h> + +#include <hash_map> +#include <string> + +#include "base/utilities.h" +#include "core/value.h" + +#ifndef ANDROID_FILTERFW_JNI_JNI_UTIL_H +#define ANDROID_FILTERFW_JNI_JNI_UTIL_H + +// We add this JNI_NULL macro to allow consistent code separation of Java and +// C++ types. +#define JNI_NULL NULL + +#if 0 +// Pointer to current JavaVM. Do not use this directly. Instead use the funciton +// GetCurrentJavaVM(). +extern JavaVM* g_current_java_vm_; + +// Wrapper around a java object pointer, which includes the environment +// pointer in which the object "lives". This is used for passing down Java +// objects from the Java layer to C++. +// While an instance of this class does not own the underlying java object, it +// does hold a global reference to it, so that the Java garbage collector does +// not destroy it. It uses reference counting to determine when it can destroy +// the reference. +// TODO: Add multi-thread support! +class JavaObject { + public: + // Creates a NULL JavaObject. + JavaObject(); + + // Creates a wrapper around the given object in the given JNI environment. + JavaObject(jobject object, JNIEnv* env); + + // Copy constructor. + JavaObject(const JavaObject& java_obj); + + // Destructor. + ~JavaObject(); + + // Assignment operator. + JavaObject& operator=(const JavaObject& java_obj); + + // Access to the object (non-const as JNI functions are non-const). + jobject object() const { + return object_; + } + + // Resets this object to the NULL JavaObject. + void Reset(); + + private: + // Retain the instance, i.e. increase reference count. + void Retain(); + + // Release the instance, i.e. decrease reference count. + void Release(); + + // The object pointer (not owned). + jobject object_; + + // The reference count of this object + int* ref_count_; +}; +#endif + +// ObjectPool template class. This class keeps track of C++ instances that are +// coupled to Java objects. This is done by using an "id" field in the Java +// object, which is then mapped to the correct instance here. It should not be +// necessary to use this class directly. Instead, the convenience functions +// below can be used. +template<class T> +class ObjectPool { + public: + // Create a new ObjectPool for a specific object type. Pass the path to the + // Java equivalent class of the C++ class, and the name of the java member + // field that will store the object's ID. + static void Setup(const std::string& jclass_name, + const std::string& id_fld_name) { + instance_ = new ObjectPool<T>(jclass_name, id_fld_name); + } + + // Return the shared instance to this type's pool. + static ObjectPool* Instance() { + return instance_; + } + + // Delete this type's pool. + static void TearDown() { + delete instance_; + } + + // Register a new C++ object with the pool. This does not affect the Java + // layer. Use WrapObject() instead to perform the necessary Java-side + // assignments. Pass true to owns if the object pool owns the object. + int RegisterObject(T* object, bool owns) { + const int id = next_id_; + objects_[id] = object; + owns_[id] = owns; + ++next_id_; + return id; + } + + // Return the object in the pool with the specified ID. + T* ObjectWithID(int obj_id) const { + typename CObjMap::const_iterator iter = objects_.find(obj_id); + return iter == objects_.end() ? NULL : iter->second; + } + + // Get the ID of a Java object. This ID can be used to look-up the C++ + // object. + int GetObjectID(JNIEnv* env, jobject j_object) { + jclass cls = env->GetObjectClass(j_object); + jfieldID id_field = env->GetFieldID(cls, id_field_name_.c_str(), "I"); + const int result = env->GetIntField(j_object, id_field); + env->DeleteLocalRef(cls); + return result; + } + + // Take a C++ object and wrap it with a given Java object. This will + // essentially set the ID member of the Java object to the ID of the C++ + // object. Pass true to owns if the object pool owns the object. + bool WrapObject(T* c_object, JNIEnv* env, jobject j_object, bool owns) { + const int id = RegisterObject(c_object, owns); + jclass cls = env->GetObjectClass(j_object); + jfieldID id_field = env->GetFieldID(cls, id_field_name_.c_str(), "I"); + env->SetIntField(j_object, id_field, id); + env->DeleteLocalRef(cls); + return true; + } + + // Remove the object with the given ID from this pool, and delete it. This + // does not affect the Java layer. + bool DeleteObjectWithID(int obj_id) { + typename CObjMap::iterator iter = objects_.find(obj_id); + const bool found = iter != objects_.end(); + if (found) { + if (owns_[obj_id]) + delete iter->second; + objects_.erase(iter); + } + return found; + } + + // Instantiates a new java object for this class. The Java class must have + // a default constructor for this to succeed. + jobject CreateJavaObject(JNIEnv* env) { + jclass cls = env->FindClass(jclass_name_.c_str()); + jmethodID constructor = env->GetMethodID( + cls, + "<init>", + "(Landroid/filterfw/core/NativeAllocatorTag;)V"); + jobject result = env->NewObject(cls, constructor, JNI_NULL); + env->DeleteLocalRef(cls); + return result; + } + + int GetObjectCount() const { + return objects_.size(); + } + + const std::string& GetJavaClassName() const { + return jclass_name_; + } + + private: + explicit ObjectPool(const std::string& jclass_name, + const std::string& id_fld_name) + : jclass_name_(jclass_name), + id_field_name_(id_fld_name), + next_id_(0) { } + + typedef std::hash_map<int, T*> CObjMap; + typedef std::hash_map<int, bool> FlagMap; + static ObjectPool* instance_; + std::string jclass_name_; + std::string id_field_name_; + int next_id_; + CObjMap objects_; + FlagMap owns_; + + DISALLOW_COPY_AND_ASSIGN(ObjectPool); +}; + +template<typename T> ObjectPool<T>* ObjectPool<T>::instance_ = NULL; + +// Convenience Functions /////////////////////////////////////////////////////// + +// This function "links" the C++ instance and the Java instance, so that they +// can be mapped to one another. This must be called for every C++ instance +// which is wrapped by a Java front-end interface. Pass true to owns, if the +// Java layer should own the object. +template<typename T> +bool WrapObjectInJava(T* c_object, JNIEnv* env, jobject j_object, bool owns) { + ObjectPool<T>* pool = ObjectPool<T>::Instance(); + return pool ? pool->WrapObject(c_object, env, j_object, owns) : false; +} + +// Creates a new Java instance, which wraps the passed C++ instance. Returns +// the wrapped object or JNI_NULL if there was an error. Pass true to owns, if +// the Java layer should own the object. +template<typename T> +jobject WrapNewObjectInJava(T* c_object, JNIEnv* env, bool owns) { + ObjectPool<T>* pool = ObjectPool<T>::Instance(); + if (pool) { + jobject result = pool->CreateJavaObject(env); + if (WrapObjectInJava(c_object, env, result, owns)) + return result; + } + return JNI_NULL; +} + +// Use ConvertFromJava to obtain a C++ instance given a Java object. This +// instance must have been wrapped in Java using the WrapObjectInJava() +// function. +template<typename T> +T* ConvertFromJava(JNIEnv* env, jobject j_object) { + ObjectPool<T>* pool = ObjectPool<T>::Instance(); + return pool && j_object + ? pool->ObjectWithID(pool->GetObjectID(env, j_object)) + : NULL; +} + +// Delete the native object given a Java instance. This should be called from +// the Java object's finalizer. +template<typename T> +bool DeleteNativeObject(JNIEnv* env, jobject j_object) { + ObjectPool<T>* pool = ObjectPool<T>::Instance(); + return pool && j_object + ? pool->DeleteObjectWithID(pool->GetObjectID(env, j_object)) + : false; +} + +#if 0 +// Get the current JNI VM, or NULL if there is no current VM +JavaVM* GetCurrentJavaVM(); + +// Get the current JNI environment, or NULL if this is not a JNI thread +JNIEnv* GetCurrentJNIEnv(); +#endif + +// Convert C++ boolean to Java boolean. +jboolean ToJBool(bool value); + +// Convert Java boolean to C++ boolean. +bool ToCppBool(jboolean value); + +// Convert Java String to C++ string. +jstring ToJString(JNIEnv* env, const std::string& value); + +// Convert C++ string to Java String. +std::string ToCppString(JNIEnv* env, jstring value); + +// Convert Java object to a (C) Value object. +Value ToCValue(JNIEnv* env, jobject object); + +// Convert a (C) Value object to a Java object. +jobject ToJObject(JNIEnv* env, const Value& value); + +// Returns true, iff the passed object is an instance of the class specified +// by its fully qualified class name. +bool IsJavaInstanceOf(JNIEnv* env, jobject object, + const std::string& class_name); + +#endif // ANDROID_FILTERFW_JNI_JNI_UTIL_H diff --git a/media/mca/filterfw/jni/jni_vertex_frame.cpp b/media/mca/filterfw/jni/jni_vertex_frame.cpp new file mode 100644 index 0000000..caae938 --- /dev/null +++ b/media/mca/filterfw/jni/jni_vertex_frame.cpp @@ -0,0 +1,90 @@ +/* + * 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. + */ + +#include "jni/jni_vertex_frame.h" +#include "jni/jni_util.h" + +#include "native/core/vertex_frame.h" + +using android::filterfw::VertexFrame; + +jboolean Java_android_filterfw_core_VertexFrame_nativeAllocate(JNIEnv* env, + jobject thiz, + jint size) { + return ToJBool(WrapObjectInJava(new VertexFrame(size), env, thiz, true)); +} + +jboolean Java_android_filterfw_core_VertexFrame_nativeDeallocate(JNIEnv* env, jobject thiz) { + return ToJBool(DeleteNativeObject<VertexFrame>(env, thiz)); +} + +jboolean Java_android_filterfw_core_VertexFrame_setNativeInts(JNIEnv* env, + jobject thiz, + jintArray ints) { + + VertexFrame* frame = ConvertFromJava<VertexFrame>(env, thiz); + if (frame && ints) { + jint* int_ptr = env->GetIntArrayElements(ints, NULL); + const int length = env->GetArrayLength(ints); + if (int_ptr) { + const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(int_ptr), + length * sizeof(jint)); + env->ReleaseIntArrayElements(ints, int_ptr, JNI_ABORT); + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_VertexFrame_setNativeFloats(JNIEnv* env, + jobject thiz, + jfloatArray floats) { + VertexFrame* frame = ConvertFromJava<VertexFrame>(env, thiz); + if (frame && floats) { + jfloat* float_ptr = env->GetFloatArrayElements(floats, NULL); + const int length = env->GetArrayLength(floats); + if (float_ptr) { + const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(float_ptr), + length * sizeof(jfloat)); + env->ReleaseFloatArrayElements(floats, float_ptr, JNI_ABORT); + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jboolean Java_android_filterfw_core_VertexFrame_setNativeData(JNIEnv* env, + jobject thiz, + jbyteArray data, + jint offset, + jint length) { + VertexFrame* frame = ConvertFromJava<VertexFrame>(env, thiz); + if (frame && data) { + jbyte* bytes = env->GetByteArrayElements(data, NULL); + if (bytes) { + const bool success = frame->WriteData(reinterpret_cast<const uint8_t*>(bytes + offset), + length); + env->ReleaseByteArrayElements(data, bytes, JNI_ABORT); + return ToJBool(success); + } + } + return JNI_FALSE; +} + +jint Java_android_filterfw_core_VertexFrame_getNativeVboId(JNIEnv* env, jobject thiz) { + VertexFrame* frame = ConvertFromJava<VertexFrame>(env, thiz); + return frame ? frame->GetVboId() : -1; +} diff --git a/media/mca/filterfw/jni/jni_vertex_frame.h b/media/mca/filterfw/jni/jni_vertex_frame.h new file mode 100644 index 0000000..fcd7ea1 --- /dev/null +++ b/media/mca/filterfw/jni/jni_vertex_frame.h @@ -0,0 +1,54 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_JNI_VERTEX_FRAME_H +#define ANDROID_FILTERFW_JNI_VERTEX_FRAME_H + +#include <jni.h> + +#ifdef __cplusplus +extern "C" { +#endif + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_VertexFrame_nativeAllocate(JNIEnv* env, jobject thiz, jint size); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_VertexFrame_nativeDeallocate(JNIEnv* env, jobject thiz); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_VertexFrame_setNativeInts(JNIEnv* env, jobject thiz, jintArray ints); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_VertexFrame_setNativeFloats(JNIEnv* env, + jobject thiz, + jfloatArray floats); + +JNIEXPORT jboolean JNICALL +Java_android_filterfw_core_VertexFrame_setNativeData(JNIEnv* env, + jobject thiz, + jbyteArray data, + jint offset, + jint length); + +JNIEXPORT jint JNICALL +Java_android_filterfw_core_VertexFrame_getNativeVboId(JNIEnv* env, jobject thiz); + +#ifdef __cplusplus +} +#endif + +#endif // ANDROID_FILTERFW_JNI_VERTEX_FRAME_H diff --git a/media/mca/filterfw/native/Android.mk b/media/mca/filterfw/native/Android.mk new file mode 100644 index 0000000..46ee283 --- /dev/null +++ b/media/mca/filterfw/native/Android.mk @@ -0,0 +1,44 @@ +# 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. +# + +LOCAL_PATH := $(call my-dir) + +##################### +# Build module libfilterfw_static + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_MODULE := libfilterfw_native + +LOCAL_SRC_FILES += core/geometry.cpp \ + core/gl_env.cpp \ + core/gl_frame.cpp \ + core/native_frame.cpp \ + core/native_program.cpp \ + core/shader_program.cpp \ + core/vertex_frame.cpp \ + core/value.cpp + +# add local includes +include $(LOCAL_PATH)/libfilterfw.mk + +# gcc should always be placed at the end. +LOCAL_EXPORT_LDLIBS := -llog -lgcc + +# TODO: Build a shared library as well? +include $(BUILD_STATIC_LIBRARY) + diff --git a/media/mca/filterfw/native/base/logging.h b/media/mca/filterfw/native/base/logging.h new file mode 100644 index 0000000..1236d0b --- /dev/null +++ b/media/mca/filterfw/native/base/logging.h @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_BASE_LOGGING_H +#define ANDROID_FILTERFW_BASE_LOGGING_H + +#define LOG_EVERY_FRAME false + +#define LOG_FRAME(...) if (LOG_EVERY_FRAME) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) + +#define LOG_TAG "MCA" +#include <utils/Log.h> + +#endif // ANDROID_FILTERFW_BASE_LOGGING_H diff --git a/media/mca/filterfw/native/base/utilities.h b/media/mca/filterfw/native/base/utilities.h new file mode 100644 index 0000000..6bb3b7f --- /dev/null +++ b/media/mca/filterfw/native/base/utilities.h @@ -0,0 +1,160 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_BASE_UTILITIES_H +#define ANDROID_FILTERFW_BASE_UTILITIES_H + +#include <set> +#include <utility> + +namespace android { +namespace filterfw { + +// Convenience Macro to make copy constructor and assignment operator private +// (thereby disallowing copying and assigning). +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +// A macro to disallow all the implicit constructors, namely the +// default constructor, copy constructor and operator= functions. +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName(); \ + DISALLOW_COPY_AND_ASSIGN(TypeName) + +// STLDeleteContainerPointers() +// For a range within a container of pointers, calls delete +// (non-array version) on these pointers. +// NOTE: for these three functions, we could just implement a DeleteObject +// functor and then call for_each() on the range and functor, but this +// requires us to pull in all of algorithm.h, which seems expensive. +// For hash_[multi]set, it is important that this deletes behind the iterator +// because the hash_set may call the hash function on the iterator when it is +// advanced, which could result in the hash function trying to deference a +// stale pointer. +template <class ForwardIterator> +void STLDeleteContainerPointers(ForwardIterator begin, + ForwardIterator end) { + while (begin != end) { + ForwardIterator temp = begin; + ++begin; + delete *temp; + } +} + +// Given an STL container consisting of (key, value) pairs, STLDeleteValues +// deletes all the "value" components and clears the container. Does nothing +// in the case it's given a NULL pointer. +template <class T> +void STLDeleteValues(T *v) { + if (!v) return; + for (typename T::iterator i = v->begin(); i != v->end(); ++i) { + delete i->second; + } + v->clear(); +} + +// Perform a lookup in a map or hash_map. +// If the key is present a const pointer to the associated value is returned, +// otherwise a NULL pointer is returned. +template <class Collection> +const typename Collection::value_type::second_type* +FindOrNull(const Collection& collection, + const typename Collection::value_type::first_type& key) { + typename Collection::const_iterator it = collection.find(key); + if (it == collection.end()) { + return 0; + } + return &it->second; +} + +// A simple class that gives checklist functionality: There are essemtially two +// operations defined on a CheckList: +// - Adding a new (unchecked) item. +// - Checking off an item. +// When checking off the last remaining item CheckItem() returns true. +template<typename T> +class CheckList { + public: + // Add a new unchecked item. Does nothing if item is already in checklist. + void AddItem(const T& item); + + // Check off an item in the checklist. Returns true if all items have been + // checked. + bool CheckItem(const T& item); + + // Clear the checklist. + void Clear() { + items_.clear(); + } + + private: + std::set<T> items_; +}; + +template<typename T> +void CheckList<T>::AddItem(const T& item) { + if (!ContainsKey(items_, item)) + items_.insert(item); +} + +template<typename T> +bool CheckList<T>::CheckItem(const T& item) { + typename std::set<T>::iterator iter = items_.find(item); + if (iter != items_.end()) + items_.erase(iter); + return items_.empty(); +} + +// Perform a lookup in a map or hash_map whose values are pointers. +// If the key is present a const pointer to the associated value is returned, +// otherwise a NULL pointer is returned. +// This function does not distinguish between a missing key and a key mapped +// to a NULL value. +template <class Collection> +const typename Collection::value_type::second_type +FindPtrOrNull(const Collection& collection, + const typename Collection::value_type::first_type& key) { + typename Collection::const_iterator it = collection.find(key); + if (it == collection.end()) { + return 0; + } + return it->second; +} + +// Test to see if a set, map, hash_set or hash_map contains a particular key. +// Returns true if the key is in the collection. +template <typename Collection, typename Key> +bool ContainsKey(const Collection& collection, const Key& key) { + return collection.find(key) != collection.end(); +} + +// Insert a new key and value into a map or hash_map. +// If the key is not present in the map the key and value are +// inserted, otherwise nothing happens. True indicates that an insert +// took place, false indicates the key was already present. +template <class Collection, class Key, class Value> +bool InsertIfNotPresent(Collection * const collection, + const Key& key, const Value& value) { + std::pair<typename Collection::iterator, bool> ret = + collection->insert(typename Collection::value_type(key, value)); + return ret.second; +} + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_BASE_UTILITIES_H diff --git a/media/mca/filterfw/native/core/geometry.cpp b/media/mca/filterfw/native/core/geometry.cpp new file mode 100644 index 0000000..677b91d --- /dev/null +++ b/media/mca/filterfw/native/core/geometry.cpp @@ -0,0 +1,137 @@ +/* + * 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. + */ + +#include "core/geometry.h" + +#include <cmath> + +#include "base/logging.h" + +namespace android { +namespace filterfw { + +float Point::Length() const { + return std::sqrt(x_ * x_ + y_ * y_); +} + +bool Point::ScaleTo(float new_length) { + float length = Length(); + if (length == 0.0f) { + return false; + } + x_ *= new_length / length; + y_ *= new_length / length; + return true; +} + +float Point::Distance(const Point& p0, const Point& p1) { + Point diff = p1 - p0; + return diff.Length(); +} + +Point Point::operator+(const Point& other) const { + Point out; + out.x_ = x_ + other.x_; + out.y_ = y_ + other.y_; + return out; +} + +Point Point::operator-(const Point& other) const { + Point out; + out.x_ = x_ - other.x_; + out.y_ = y_ - other.y_; + return out; +} + +Point Point::operator*(float factor) const { + Point out; + out.x_ = factor * x_; + out.y_ = factor * y_; + return out; +} + +void Point::Rotate90Clockwise() { + const float x = x_; + x_ = y_; + y_ = -x; +} + +bool Rect::ExpandToAspectRatio(float ratio) { + if (width <= 0.0f || height <= 0.0f || ratio <= 0.0f) { + return false; + } + + const float current_ratio = width / height; + if (current_ratio < ratio) { + const float dx = width * (ratio / current_ratio - 1.0f); + x -= dx / 2.0f; + width += dx; + } else { + const float dy = height * (current_ratio / ratio - 1.0f); + y -= dy / 2.0f; + height += dy; + } + return true; +} + +bool Rect::ExpandToMinLength(float length) { + if (width <= 0.0f || height <= 0.0f || length <= 0.0f) { + return false; + } + + const float current_length = width > height ? width : height; + if (length > current_length) { + const float dx = width * (length / current_length - 1.0f); + x -= dx / 2.0f; + width += dx; + const float dy = height * (length / current_length - 1.0f); + y -= dy / 2.0f; + height += dy; + } + return true; +} + +bool Rect::ScaleWithLengthLimit(float factor, float max_length) { + if (width <= 0.0f || height <= 0.0f || factor <= 0.0f) { + return false; + } + + const float current_length = width > height ? width : height; + if (current_length >= max_length) { + return true; + } + + float f = factor; + if (current_length * f > max_length) { + f *= max_length / (current_length * f); + } + + const float dx = width * (f - 1.0f); + x -= dx / 2.0f; + width += dx; + const float dy = height * (f - 1.0f); + y -= dy / 2.0f; + height += dy; + return true; +} + +const Point& Quad::point(int ix) const { + ALOG_ASSERT(ix < static_cast<int>(points_.size()), "Access out of bounds"); + return points_[ix]; +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterfw/native/core/geometry.h b/media/mca/filterfw/native/core/geometry.h new file mode 100644 index 0000000..7f89eef --- /dev/null +++ b/media/mca/filterfw/native/core/geometry.h @@ -0,0 +1,94 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_GEOMETRY_H +#define ANDROID_FILTERFW_CORE_GEOMETRY_H + +#include <vector> + +namespace android { +namespace filterfw { + +// This is an initial implementation of some geometrical structures. This is +// likely to grow and become more sophisticated in the future. + +class Point { + public: + Point() : x_(0.0f), y_(0.0f) {} + Point(float x, float y) : x_(x), y_(y) {} + + float x() const { return x_; } + float y() const { return y_; } + + float Length() const; + bool ScaleTo(float new_length); + static float Distance(const Point& p0, const Point& p1); + + // Add more of these as needed: + Point operator+(const Point& other) const; + Point operator-(const Point& other) const; + Point operator*(float factor) const; + + void Rotate90Clockwise(); + + private: + float x_, y_; +}; + +class Quad { + public: + Quad() : points_(4) {} + virtual ~Quad() {} + + Quad(const Point& p0, const Point& p1, const Point& p2, const Point& p3) + : points_(4) { + points_[0] = p0; + points_[1] = p1; + points_[2] = p2; + points_[3] = p3; + } + + const std::vector<Point>& points() const { return points_; } + const Point& point(int ix) const; + + protected: + std::vector<Point> points_; +}; + +struct Rect { + float x, y, width, height; + + Rect() { + x = y = 0.0f; + width = height = 1.0f; + } + + Rect(float x, float y, float width, float height) { + this->x = x; + this->y = y; + this->width = width; + this->height = height; + } + + bool ExpandToAspectRatio(float ratio); + bool ExpandToMinLength(float length); + bool ScaleWithLengthLimit(float factor, float max_length); +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_GEOMETRY_H diff --git a/media/mca/filterfw/native/core/gl_buffer_interface.h b/media/mca/filterfw/native/core/gl_buffer_interface.h new file mode 100644 index 0000000..24b1db9 --- /dev/null +++ b/media/mca/filterfw/native/core/gl_buffer_interface.h @@ -0,0 +1,71 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_GL_BUFFER_INTERFACE_H +#define ANDROID_FILTERFW_CORE_GL_BUFFER_INTERFACE_H + +#include <GLES2/gl2.h> + +namespace android { +namespace filterfw { + +class GLTextureHandle { + public: + virtual ~GLTextureHandle() { } + + // Returns the held texture id. + virtual GLuint GetTextureId() const = 0; + + // Binds the held texture. This may result in creating the texture if it + // is not yet available. + virtual bool FocusTexture() = 0; + + // Generates the mipmap chain of the held texture. Returns true, iff + // generating was successful. + virtual bool GenerateMipMap() = 0; + + // Set a texture parameter (see glTextureParameter documentation). Returns + // true iff the parameter was set successfully. + virtual bool SetTextureParameter(GLenum pname, GLint value) = 0; + + // Returns the texture target used. + // Texture Target should be: GL_TEXTURE_2D, GL_TEXTURE_EXTERNAL_OES. + virtual GLuint GetTextureTarget() const = 0; +}; + +class GLFrameBufferHandle { + public: + virtual ~GLFrameBufferHandle() { } + + // Returns the held FBO id. + virtual GLuint GetFboId() const = 0; + + // Binds the held FBO. This may result in creating the FBO if it + // is not yet available. + virtual bool FocusFrameBuffer() = 0; +}; + +// Interface to instances that hold GL textures and frame-buffer-objects. +// The GLFrame class implements this interface. +class GLBufferHandle : public GLTextureHandle, public GLFrameBufferHandle { + public: + virtual ~GLBufferHandle() { } +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_GL_BUFFER_INTERFACE_H diff --git a/media/mca/filterfw/native/core/gl_env.cpp b/media/mca/filterfw/native/core/gl_env.cpp new file mode 100644 index 0000000..738b8e0 --- /dev/null +++ b/media/mca/filterfw/native/core/gl_env.cpp @@ -0,0 +1,408 @@ +/* + * 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. + */ +// #define LOG_NDEBUG 0 + +#include "base/logging.h" +#include "base/utilities.h" +#include "core/gl_env.h" +#include "core/shader_program.h" +#include "core/vertex_frame.h" +#include "system/window.h" + +#include <map> +#include <string> +#include <EGL/eglext.h> + +namespace android { +namespace filterfw { + +GLEnv::GLEnv() + : display_(EGL_NO_DISPLAY), + context_id_(0), + surface_id_(0), + max_surface_id_(0), + created_context_(false), + created_surface_(false), + initialized_(false) { +} + +GLEnv::~GLEnv() { + // Destroy surfaces + for (std::map<int, SurfaceWindowPair>::iterator it = surfaces_.begin(); + it != surfaces_.end(); + ++it) { + if (it->first != 0 || created_surface_) { + eglDestroySurface(display(), it->second.first); + if (it->second.second) { + it->second.second->Destroy(); + delete it->second.second; + } + } + } + + // Destroy contexts + for (std::map<int, EGLContext>::iterator it = contexts_.begin(); + it != contexts_.end(); + ++it) { + if (it->first != 0 || created_context_) + eglDestroyContext(display(), it->second); + } + + // Destroy attached shaders and frames + STLDeleteValues(&attached_shaders_); + STLDeleteValues(&attached_vframes_); + + // Destroy display + if (initialized_) + eglTerminate(display()); + + // Log error if this did not work + if (CheckEGLError("TearDown!")) + ALOGE("GLEnv: Error tearing down GL Environment!"); +} + +bool GLEnv::IsInitialized() const { + return (contexts_.size() > 0 && + surfaces_.size() > 0 && + display_ != EGL_NO_DISPLAY); +} + +bool GLEnv::Deactivate() { + eglMakeCurrent(display(), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + return !CheckEGLError("eglMakeCurrent"); +} + +bool GLEnv::Activate() { + ALOGV("Activate()"); + if (display() != eglGetCurrentDisplay() || + context() != eglGetCurrentContext() || + surface() != eglGetCurrentSurface(EGL_DRAW)) { + // Make sure we are initialized + if (context() == EGL_NO_CONTEXT || surface() == EGL_NO_SURFACE) + return false; + + // Make our context current + ALOGV("eglMakeCurrent"); + eglMakeCurrent(display(), surface(), surface(), context()); + + return !CheckEGLMakeCurrentError(); + } + return true; +} + +bool GLEnv::SwapBuffers() { + const bool result = eglSwapBuffers(display(), surface()) == EGL_TRUE; + return !CheckEGLError("eglSwapBuffers") && result; +} + +bool GLEnv::InitWithCurrentContext() { + if (IsInitialized()) + return true; + + display_ = eglGetCurrentDisplay(); + contexts_[0] = eglGetCurrentContext(); + surfaces_[0] = SurfaceWindowPair(eglGetCurrentSurface(EGL_DRAW), NULL); + + return (context() != EGL_NO_CONTEXT) && + (display() != EGL_NO_DISPLAY) && + (surface() != EGL_NO_SURFACE); +} + +bool GLEnv::InitWithNewContext() { + if (IsInitialized()) { + ALOGE("GLEnv: Attempting to reinitialize environment!"); + return false; + } + + display_ = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (CheckEGLError("eglGetDisplay")) return false; + + EGLint majorVersion; + EGLint minorVersion; + eglInitialize(display(), &majorVersion, &minorVersion); + if (CheckEGLError("eglInitialize")) return false; + initialized_ = true; + + // Configure context/surface + EGLConfig config; + EGLint numConfigs = -1; + + // TODO(renn): Do we need the window bit here? + // TODO: Currently choosing the config that includes all + // This is not needed if the encoding is not being used + EGLint configAttribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_RECORDABLE_ANDROID, EGL_TRUE, + EGL_NONE + }; + + eglChooseConfig(display(), configAttribs, &config, 1, &numConfigs); + if (numConfigs < 1) { + ALOGE("GLEnv::Init: No suitable EGL configuration found!"); + return false; + } + + // Create dummy surface using a SurfaceTexture + surfaceTexture_ = new SurfaceTexture(0); + window_ = new SurfaceTextureClient(static_cast<sp<ISurfaceTexture> >( + surfaceTexture_->getBufferQueue())); + + surfaces_[0] = SurfaceWindowPair(eglCreateWindowSurface(display(), config, window_.get(), NULL), NULL); + if (CheckEGLError("eglCreateWindowSurface")) return false; + + // Create context + EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; + contexts_[0] = eglCreateContext(display(), + config, + EGL_NO_CONTEXT, + context_attribs); + if (CheckEGLError("eglCreateContext")) return false; + + created_context_ = created_surface_ = true; + + return true; +} + +bool GLEnv::IsActive() const { + ALOGV("IsActive()"); + return context() == eglGetCurrentContext() + && display() == eglGetCurrentDisplay() + && surface() == eglGetCurrentSurface(EGL_DRAW); +} + +bool GLEnv::IsContextActive() const { + return context() == eglGetCurrentContext(); +} + +bool GLEnv::IsAnyContextActive() { + return eglGetCurrentContext() != EGL_NO_CONTEXT; +} + +int GLEnv::AddWindowSurface(const EGLSurface& surface, WindowHandle* window_handle) { + const int id = ++max_surface_id_; + surfaces_[id] = SurfaceWindowPair(surface, window_handle); + return id; +} + +int GLEnv::AddSurface(const EGLSurface& surface) { + return AddWindowSurface(surface, NULL); +} + +bool GLEnv::SwitchToSurfaceId(int surface_id) { + ALOGV("SwitchToSurfaceId"); + if (surface_id_ != surface_id) { + const SurfaceWindowPair* surface = FindOrNull(surfaces_, surface_id); + if (surface) { + bool wasActive = IsActive(); + surface_id_ = surface_id; + return wasActive ? Activate() : true; + } + return false; + } + return true; +} + +bool GLEnv::ReleaseSurfaceId(int surface_id) { + if (surface_id > 0) { + const SurfaceWindowPair* surface_window_pair = FindOrNull(surfaces_, surface_id); + if (surface_window_pair) { + if (surface_id_ == surface_id) + SwitchToSurfaceId(0); + eglDestroySurface(display(), surface_window_pair->first); + if (surface_window_pair->second) { + surface_window_pair->second->Destroy(); + delete surface_window_pair->second; + } + surfaces_.erase(surface_id); + return true; + } + } + return false; +} + +bool GLEnv::SetSurfaceTimestamp(int64_t timestamp) { + if (surface_id_ > 0) { + const SurfaceWindowPair* surface_window_pair = FindOrNull(surfaces_, + surface_id_); + if (surface_window_pair) { + ANativeWindow *window = static_cast<ANativeWindow*>( + surface_window_pair->second->InternalHandle()); + native_window_set_buffers_timestamp(window, timestamp); + return true; + } + } + return false; +} + +int GLEnv::FindSurfaceIdForWindow(const WindowHandle* window_handle) { + for (std::map<int, SurfaceWindowPair>::iterator it = surfaces_.begin(); + it != surfaces_.end(); + ++it) { + const WindowHandle* my_handle = it->second.second; + if (my_handle && my_handle->Equals(window_handle)) { + return it->first; + } + } + return -1; +} + + +int GLEnv::AddContext(const EGLContext& context) { + const int id = contexts_.size(); + contexts_[id] = context; + return id; +} + +bool GLEnv::SwitchToContextId(int context_id) { + const EGLContext* context = FindOrNull(contexts_, context_id); + if (context) { + if (context_id_ != context_id) { + context_id_ = context_id; + return Activate(); + } + return true; + } + return false; +} + +void GLEnv::ReleaseContextId(int context_id) { + if (context_id > 0) { + const EGLContext* context = FindOrNull(contexts_, context_id); + if (context) { + contexts_.erase(context_id); + if (context_id_ == context_id && IsActive()) + SwitchToContextId(0); + eglDestroyContext(display(), *context); + } + } +} + +bool GLEnv::CheckGLError(const std::string& op) { + bool err = false; + for (GLint error = glGetError(); error; error = glGetError()) { + ALOGE("GL Error: Operation '%s' caused GL error (0x%x)\n", + op.c_str(), + error); + err = true; + } + return err; +} + +bool GLEnv::CheckEGLError(const std::string& op) { + bool err = false; + for (EGLint error = eglGetError(); + error != EGL_SUCCESS; + error = eglGetError()) { + ALOGE("EGL Error: Operation '%s' caused EGL error (0x%x)\n", + op.c_str(), + error); + err = true; + } + return err; +} + +bool GLEnv::CheckEGLMakeCurrentError() { + bool err = false; + for (EGLint error = eglGetError(); + error != EGL_SUCCESS; + error = eglGetError()) { + switch (error) { + case EGL_BAD_DISPLAY: + ALOGE("EGL Error: Attempting to activate context with bad display!"); + break; + case EGL_BAD_SURFACE: + ALOGE("EGL Error: Attempting to activate context with bad surface!"); + break; + case EGL_BAD_ACCESS: + ALOGE("EGL Error: Attempting to activate context, which is " + "already active in another thread!"); + break; + default: + ALOGE("EGL Error: Making EGL rendering context current caused " + "error: 0x%x\n", error); + } + err = true; + } + return err; +} + +GLuint GLEnv::GetCurrentProgram() { + GLint result; + glGetIntegerv(GL_CURRENT_PROGRAM, &result); + ALOG_ASSERT(result >= 0); + return static_cast<GLuint>(result); +} + +EGLDisplay GLEnv::GetCurrentDisplay() { + return eglGetCurrentDisplay(); +} + +int GLEnv::NumberOfComponents(GLenum type) { + switch (type) { + case GL_BOOL: + case GL_FLOAT: + case GL_INT: + return 1; + case GL_BOOL_VEC2: + case GL_FLOAT_VEC2: + case GL_INT_VEC2: + return 2; + case GL_INT_VEC3: + case GL_FLOAT_VEC3: + case GL_BOOL_VEC3: + return 3; + case GL_BOOL_VEC4: + case GL_FLOAT_VEC4: + case GL_INT_VEC4: + case GL_FLOAT_MAT2: + return 4; + case GL_FLOAT_MAT3: + return 9; + case GL_FLOAT_MAT4: + return 16; + default: + return 0; + } +} + +void GLEnv::AttachShader(int key, ShaderProgram* shader) { + ShaderProgram* existingShader = ShaderWithKey(key); + if (existingShader) + delete existingShader; + attached_shaders_[key] = shader; +} + +void GLEnv::AttachVertexFrame(int key, VertexFrame* frame) { + VertexFrame* existingFrame = VertexFrameWithKey(key); + if (existingFrame) + delete existingFrame; + attached_vframes_[key] = frame; +} + +ShaderProgram* GLEnv::ShaderWithKey(int key) { + return FindPtrOrNull(attached_shaders_, key); +} + +VertexFrame* GLEnv::VertexFrameWithKey(int key) { + return FindPtrOrNull(attached_vframes_, key); +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterfw/native/core/gl_env.h b/media/mca/filterfw/native/core/gl_env.h new file mode 100644 index 0000000..b61785f --- /dev/null +++ b/media/mca/filterfw/native/core/gl_env.h @@ -0,0 +1,261 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_GL_ENV_H +#define ANDROID_FILTERFW_CORE_GL_ENV_H + +#include <string> +#include <utility> +#include <map> + +#include "base/logging.h" +#include "base/utilities.h" + +#include <GLES2/gl2.h> +#include <EGL/egl.h> + +#include <gui/ISurfaceTexture.h> +#include <gui/SurfaceTextureClient.h> + +namespace android { +namespace filterfw { + +class ShaderProgram; +class VertexFrame; + +class WindowHandle { + public: + virtual ~WindowHandle() { + } + + virtual void Destroy() = 0; + + virtual bool Equals(const WindowHandle* window) const { + return InternalHandle() == window->InternalHandle(); + } + + virtual const void* InternalHandle() const = 0; + + virtual void* InternalHandle() = 0; +}; + +// The GLEnv class provides functionality related to the EGL environment, which +// includes the display, context, and surface. It is possible to either create +// a new environment or base it off the currently active EGL environment. In +// order to do the latter, an EGL environment must be setup already (though not +// necessarily through this class), and have an active display, context, and +// surface. +class GLEnv { + public: + // Constructing and Activating ///////////////////////////////////////////// + // Constructs a new GLEnv object. This does not create a GL context. + GLEnv(); + + // Destructor. Tears down and deallocates any GL objects that were created + // by this instance. + ~GLEnv(); + + // Inits a new GL environment, including a new surface and context. You + // must call Activate() before performing any GL operations. + bool InitWithNewContext(); + + // Inits the GL environment from the current GL environment. Use this when + // there is already a display, surface and context available (possibly + // created by the host application). You do not need to call Activate() as + // this context is active already. + bool InitWithCurrentContext(); + + // Activates the environment, and makes the associated GL context the + // current context. Creates the environment, if it has not been created + // already. Returns true if the activation was successful. + bool Activate(); + + // Deactivates the environment. Returns true if the deactivation was + // successful. You may want to call this when moving a context to another + // thread. In this case, deactivate the GLEnv in the old thread, and + // reactivate it in the new thread. + bool Deactivate(); + + // When rendering to a visible surface, call this to swap between the + // offscreen and onscreen buffers. Returns true if the buffer swap was + // successful. + bool SwapBuffers(); + + // Working with Surfaces /////////////////////////////////////////////////// + + // Add a surface to the environment. This surface will now be managed (and + // owned) by the GLEnv instance. Returns the id of the surface. + int AddSurface(const EGLSurface& surface); + + // Add a window surface to the environment. The window is passed in as + // an opaque window handle. + // This surface will now be managed (and owned) by the GLEnv instance. + // Returns the id of the surface. + int AddWindowSurface(const EGLSurface& surface, WindowHandle* window_handle); + + // Switch to the surface with the specified id. This will make the surface + // active, if it is not active already. Specify an ID of 0 if you would like + // to switch to the default surface. Returns true if successful. + bool SwitchToSurfaceId(int surface_id); + + // Release the surface with the specified id. This will deallocate the + // surface. If this is the active surface, the environment will switch to + // the default surface (0) first. You cannot release the default surface. + bool ReleaseSurfaceId(int surface_id); + + // Set the timestamp for the current surface. Must be called + // before swapBuffers to associate the timestamp with the frame + // resulting from swapBuffers. + bool SetSurfaceTimestamp(int64_t timestamp); + + // Looks for a surface with the associated window handle. Returns -1 if no + // surface with such a window was found. + int FindSurfaceIdForWindow(const WindowHandle* window_handle); + + // Obtain the environment's EGL surface. + const EGLSurface& surface() const { + return surfaces_.find(surface_id_)->second.first; + } + + // Working with Contexts /////////////////////////////////////////////////// + + // Add a context to the environment. This context will now be managed (and + // owned) by the GLEnv instance. Returns the id of the context. + int AddContext(const EGLContext& context); + + // Switch to the context with the specified id. This will make the context + // active, if it is not active already. Specify an ID of 0 if you would like + // to switch to the default context. Returns true if successful. + bool SwitchToContextId(int context_id); + + // Release the context with the specified id. This will deallocate the + // context. If this is the active context, the environment will switch to + // the default context (0) first. You cannot release the default context. + void ReleaseContextId(int context_id); + + // Obtain the environment's EGL context. + const EGLContext& context() const { + return contexts_.find(context_id_)->second; + } + + // Working with the Display //////////////////////////////////////////////// + + // Obtain the environment's EGL display. + const EGLDisplay& display() const { + return display_; + } + + // Inspecting the environment ////////////////////////////////////////////// + // Returns true if the environment is active in the current thread. + bool IsActive() const; + + // Returns true if the environment's context is active in the curent thread. + bool IsContextActive() const; + + // Returns true if there is any EGL context active in the current thread. + // This need not be a context created by a GLEnv instance. + static bool IsAnyContextActive(); + + // Attaching GL objects //////////////////////////////////////////////////// + + // Attach a shader to the environment. The environment takes ownership of + // the shader. + void AttachShader(int key, ShaderProgram* shader); + + // Attach a vertex frame to the environment. The environment takes ownership + // of the frame. + void AttachVertexFrame(int key, VertexFrame* frame); + + // Return the shader with the specified key, or NULL if there is no such + // shader attached to this environment. + ShaderProgram* ShaderWithKey(int key); + + // Return the vertex frame with the specified key, or NULL if there is no + // such frame attached to this environment. + VertexFrame* VertexFrameWithKey(int key); + + // Static methods ////////////////////////////////////////////////////////// + // These operate on the currently active environment! + + // Checks if the current environment is in a GL error state. If so, it will + // output an error message referencing the given operation string. Returns + // true if there was at least one error. + static bool CheckGLError(const std::string& operation); + + // Checks if the current environment is in an EGL error state. If so, it + // will output an error message referencing the given operation string. + // Returns true if there was at least one error. + static bool CheckEGLError(const std::string& operation); + + // Get the currently used (shader) program. + static GLuint GetCurrentProgram(); + + // Get the currently active display. + static EGLDisplay GetCurrentDisplay(); + + // Returns the number of components for a given GL type. For instance, + // returns 4 for vec4, and 16 for mat4. + static int NumberOfComponents(GLenum type); + + private: + typedef std::pair<EGLSurface, WindowHandle*> SurfaceWindowPair; + + // Initializes a new GL environment. + bool Init(); + + // Returns true if one of the Inits has been called successfully on this + // instance. + bool IsInitialized() const; + + // Outputs error messages specific to the operation eglMakeCurrent(). + // Returns true if there was at least one error. + static bool CheckEGLMakeCurrentError(); + + // The EGL display, contexts, and surfaces. + EGLDisplay display_; + std::map<int, EGLContext> contexts_; + std::map<int, SurfaceWindowPair> surfaces_; + + // The currently active context and surface ids. + int context_id_; + int surface_id_; + + // Dummy surface for context + sp<ANativeWindow> window_; + + // Dummy SurfaceTexture for context + sp<SurfaceTexture> surfaceTexture_; + + // The maximum surface id used. + int max_surface_id_; + + // These bools keep track of which objects this GLEnv has created (and + // owns). + bool created_context_; + bool created_surface_; + bool initialized_; + + // Attachments that GL objects can add to the environment. + std::map<int, ShaderProgram*> attached_shaders_; + std::map<int, VertexFrame*> attached_vframes_; + + DISALLOW_COPY_AND_ASSIGN(GLEnv); +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_GL_ENV_H diff --git a/media/mca/filterfw/native/core/gl_frame.cpp b/media/mca/filterfw/native/core/gl_frame.cpp new file mode 100644 index 0000000..0f8b4a1 --- /dev/null +++ b/media/mca/filterfw/native/core/gl_frame.cpp @@ -0,0 +1,467 @@ +/* + * 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. + */ + +#include "base/logging.h" + +#include "core/gl_env.h" +#include "core/gl_frame.h" +#include "core/shader_program.h" + +#include <vector> + +namespace android { +namespace filterfw { + +static const int kIdentityShaderKey = 1; + +// +// A GLFrame stores pixel data on the GPU. It uses two kinds of GL data +// containers for this: Textures and Frame Buffer Objects (FBOs). Textures are +// used whenever pixel data needs to be read into a shader or the host program, +// and when pixel data is uploaded to a GLFrame. The FBO is used as a rendering +// target for shaders. +// + +GLFrame::GLFrame(GLEnv* gl_env) + : gl_env_(gl_env), + width_(0), + height_(0), + vp_x_(0), + vp_y_(0), + vp_width_(0), + vp_height_(0), + texture_id_(0), + fbo_id_(0), + texture_target_(GL_TEXTURE_2D), + texture_state_(kStateUninitialized), + fbo_state_(kStateUninitialized), + owns_texture_(false), + owns_fbo_(false) { + SetDefaultTexParameters(); +} + +bool GLFrame::Init(int width, int height) { + // Make sure we haven't been initialized already + if (width_ == 0 && height_ == 0) { + InitDimensions(width, height); + return true; + } + return false; +} + +bool GLFrame::InitWithTexture(GLint texture_id, int width, int height) { + texture_id_ = texture_id; + texture_state_ = glIsTexture(texture_id) ? kStateComplete : kStateGenerated; + InitDimensions(width, height); + return true; +} + +bool GLFrame::InitWithFbo(GLint fbo_id, int width, int height) { + fbo_id_ = fbo_id; + fbo_state_ = glIsFramebuffer(fbo_id) ? kStateComplete : kStateGenerated; + texture_state_ = kStateUnmanaged; + InitDimensions(width, height); + return true; +} + +bool GLFrame::InitWithExternalTexture() { + texture_target_ = GL_TEXTURE_EXTERNAL_OES; + width_ = 0; + height_ = 0; + return GenerateTextureName(); +} + +void GLFrame::InitDimensions(int width, int height) { + width_ = width; + height_ = height; + vp_width_ = width; + vp_height_ = height; +} + +GLFrame::~GLFrame() { + // Delete texture + if (owns_texture_) { + // Bind FBO so that texture is unbound from it during deletion + if (fbo_state_ == kStateComplete) { + glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_); + } + glDeleteTextures(1, &texture_id_); + } + + // Delete FBO + if (owns_fbo_) { + glDeleteFramebuffers(1, &fbo_id_); + } +} + +bool GLFrame::GenerateMipMap() { + if (FocusTexture()) { + glGenerateMipmap(GL_TEXTURE_2D); + return !GLEnv::CheckGLError("Generating MipMap!"); + } + return false; +} + +bool GLFrame::SetTextureParameter(GLenum pname, GLint value) { + if (value != tex_params_[pname]) { + if (FocusTexture()) { + glTexParameteri(GL_TEXTURE_2D, pname, value); + if (!GLEnv::CheckGLError("Setting texture parameter!")) { + tex_params_[pname] = value; + return true; + } + } + } else { + return true; + } + return false; +} + +bool GLFrame::UpdateTexParameters() { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, tex_params_[GL_TEXTURE_MAG_FILTER]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, tex_params_[GL_TEXTURE_MIN_FILTER]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, tex_params_[GL_TEXTURE_WRAP_S]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tex_params_[GL_TEXTURE_WRAP_T]); + return !GLEnv::CheckGLError("Resetting texture parameters!"); +} + +bool GLFrame::TexParametersModifed() { + return tex_params_[GL_TEXTURE_MAG_FILTER] != GL_LINEAR + || tex_params_[GL_TEXTURE_MIN_FILTER] != GL_LINEAR + || tex_params_[GL_TEXTURE_WRAP_S] != GL_CLAMP_TO_EDGE + || tex_params_[GL_TEXTURE_WRAP_T] != GL_CLAMP_TO_EDGE; +} + +void GLFrame::SetDefaultTexParameters() { + tex_params_[GL_TEXTURE_MAG_FILTER] = GL_LINEAR; + tex_params_[GL_TEXTURE_MIN_FILTER] = GL_LINEAR; + tex_params_[GL_TEXTURE_WRAP_S] = GL_CLAMP_TO_EDGE; + tex_params_[GL_TEXTURE_WRAP_T] = GL_CLAMP_TO_EDGE; +} + +bool GLFrame::ResetTexParameters() { + if (TexParametersModifed()) { + if (BindTexture()) { + SetDefaultTexParameters(); + return UpdateTexParameters(); + } + return false; + } + return true; +} + +bool GLFrame::CopyDataTo(uint8_t* buffer, int size) { + return (size >= Size()) + ? CopyPixelsTo(buffer) + : false; +} + +bool GLFrame::CopyPixelsTo(uint8_t* buffer) { + // Use one of the pixel reading methods below, ordered from most + // efficient to least efficient. + if (fbo_state_ == kStateComplete) + return ReadFboPixels(buffer); + else if (texture_state_ == kStateComplete) + return ReadTexturePixels(buffer); + else + return false; +} + +bool GLFrame::WriteData(const uint8_t* data, int data_size) { + return (data_size == Size()) ? UploadTexturePixels(data) : false; +} + +bool GLFrame::SetViewport(int x, int y, int width, int height) { + vp_x_ = x; + vp_y_ = y; + vp_width_ = width; + vp_height_ = height; + return true; +} + +GLFrame* GLFrame::Clone() const { + GLFrame* target = new GLFrame(gl_env_); + target->Init(width_, height_); + target->CopyPixelsFrom(this); + return target; +} + +bool GLFrame::CopyPixelsFrom(const GLFrame* frame) { + if (frame == this) { + return true; + } else if (frame && frame->width_ == width_ && frame->height_ == height_) { + std::vector<const GLFrame*> sources; + sources.push_back(frame); + GetIdentity()->Process(sources, this); + return true; + } + return false; +} + +int GLFrame::Size() const { + return width_ * height_ * 4; +} + +ShaderProgram* GLFrame::GetIdentity() const { + ShaderProgram* stored_shader = gl_env_->ShaderWithKey(kIdentityShaderKey); + if (!stored_shader) { + stored_shader = ShaderProgram::CreateIdentity(gl_env_); + gl_env_->AttachShader(kIdentityShaderKey, stored_shader); + } + return stored_shader; +} + +bool GLFrame::BindFrameBuffer() const { + // Bind the FBO + glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_); + if (GLEnv::CheckGLError("FBO Binding")) return false; + + // Set viewport + glViewport(vp_x_, vp_y_, vp_width_, vp_height_); + if (GLEnv::CheckGLError("ViewPort Setup")) return false; + + return true; +} + +bool GLFrame::FocusFrameBuffer() { + // Create texture backing if necessary + if (texture_state_ == kStateUninitialized) { + if (!GenerateTextureName()) + return false; + } + + // Create and bind FBO to texture if necessary + if (fbo_state_ != kStateComplete) { + if (!GenerateFboName() || !AttachTextureToFbo()) + return false; + } + + // And bind it. + return BindFrameBuffer(); +} + +bool GLFrame::BindTexture() const { + glBindTexture(GL_TEXTURE_2D, texture_id_); + return !GLEnv::CheckGLError("Texture Binding"); +} + +GLuint GLFrame::GetTextureId() const { + return texture_id_; +} + +// Returns the held FBO id. Only call this if the GLFrame holds an FBO. You +// can check this by calling HoldsFbo(). +GLuint GLFrame::GetFboId() const { + return fbo_id_; +} + +bool GLFrame::FocusTexture() { + // Make sure we have a texture + if (!GenerateTextureName()) + return false; + + // Bind the texture + if (!BindTexture()) + return false; + + return !GLEnv::CheckGLError("Texture Binding"); +} + +bool GLFrame::GenerateTextureName() { + if (texture_state_ == kStateUninitialized) { + // Make sure texture not in use already + if (glIsTexture(texture_id_)) { + ALOGE("GLFrame: Cannot generate texture id %d, as it is in use already!", texture_id_); + return false; + } + + // Generate the texture + glGenTextures (1, &texture_id_); + if (GLEnv::CheckGLError("Texture Generation")) + return false; + texture_state_ = kStateGenerated; + owns_texture_ = true; + } + + return true; +} + +bool GLFrame::AllocateTexture() { + // Allocate or re-allocate (if texture was deleted externally). + if (texture_state_ == kStateGenerated || TextureWasDeleted()) { + LOG_FRAME("GLFrame: Allocating texture: %d", texture_id_); + glBindTexture(GL_TEXTURE_2D, texture_id_); + glTexImage2D(GL_TEXTURE_2D, + 0, + GL_RGBA, + width_, + height_, + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + NULL); + if (!GLEnv::CheckGLError("Texture Allocation")) { + UpdateTexParameters(); + texture_state_ = kStateComplete; + } + } + return texture_state_ == kStateComplete; +} + +bool GLFrame::TextureWasDeleted() const { + return texture_state_ == kStateComplete && !glIsTexture(texture_id_); +} + +bool GLFrame::GenerateFboName() { + if (fbo_state_ == kStateUninitialized) { + // Make sure FBO not in use already + if (glIsFramebuffer(fbo_id_)) { + ALOGE("GLFrame: Cannot generate FBO id %d, as it is in use already!", fbo_id_); + return false; + } + + // Create FBO + glGenFramebuffers(1, &fbo_id_); + if (GLEnv::CheckGLError("FBO Generation")) + return false; + fbo_state_ = kStateGenerated; + owns_fbo_ = true; + } + + return true; +} + +bool GLFrame::ReadFboPixels(uint8_t* pixels) const { + if (fbo_state_ == kStateComplete) { + BindFrameBuffer(); + glReadPixels(0, + 0, + width_, + height_, + GL_RGBA, + GL_UNSIGNED_BYTE, + pixels); + return !GLEnv::CheckGLError("FBO Pixel Readout"); + } + return false; +} + +bool GLFrame::ReadTexturePixels(uint8_t* pixels) const { + // Read pixels from texture if we do not have an FBO + // NOTE: OpenGL ES does NOT support glGetTexImage() for reading out texture + // data. The only way for us to get texture data is to create a new FBO and + // render the current texture frame into it. As this is quite inefficient, + // and unnecessary (this can only happen if the user is reading out data + // that was just set, and not run through a filter), we warn the user about + // this here. + ALOGW("Warning: Reading pixel data from unfiltered GL frame. This is highly " + "inefficient. Please consider using your original pixel buffer " + "instead!"); + + // Create source frame set (unfortunately this requires an ugly const-cast, + // as we need to wrap ourselves in a frame-set. Still, as this set is used + // as input only, we are certain we will not be modified). + std::vector<const GLFrame*> sources; + sources.push_back(this); + + // Create target frame + GLFrame target(gl_env_); + target.Init(width_, height_); + + // Render the texture to the target + GetIdentity()->Process(sources, &target); + + // Get the pixel data + return target.ReadFboPixels(pixels); +} + +bool GLFrame::AttachTextureToFbo() { + // Check FBO and texture state. We do not do anything if we are not managing the texture. + if (fbo_state_ == kStateComplete || texture_state_ == kStateUnmanaged) { + return true; + } else if (fbo_state_ != kStateGenerated) { + ALOGE("Attempting to attach texture to FBO with no FBO in place!"); + return false; + } + + // If texture has been generated, make sure it is allocated. + if (!AllocateTexture()) + return false; + + // Bind the frame buffer, and check if we it is already bound + glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_); + + // Bind the texture to the frame buffer + LOG_FRAME("Attaching tex %d w %d h %d to fbo %d", texture_id_, width_, height_, fbo_id_); + glFramebufferTexture2D(GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, + texture_id_, + 0); + + // Cleanup + glBindTexture(GL_TEXTURE_2D, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + if (GLEnv::CheckGLError("Texture Binding to FBO")) + return false; + else + fbo_state_ = kStateComplete; + + return true; +} + +bool GLFrame::ReattachTextureToFbo() { + return (fbo_state_ == kStateGenerated) ? AttachTextureToFbo() : true; +} + +bool GLFrame::DetachTextureFromFbo() { + if (fbo_state_ == kStateComplete && texture_state_ == kStateComplete) { + LOG_FRAME("Detaching tex %d w %d h %d from fbo %d", texture_id_, width_, height_, fbo_id_); + glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_); + glFramebufferTexture2D(GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, + 0, + 0); + if (GLEnv::CheckGLError("Detaching texture to FBO")) + return false; + else + fbo_state_ = kStateGenerated; + } + return true; +} + +bool GLFrame::UploadTexturePixels(const uint8_t* pixels) { + // Bind the texture object + FocusTexture(); + + // Load mipmap level 0 + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width_, height_, + 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Set the user specified texture parameters + UpdateTexParameters(); + + if (GLEnv::CheckGLError("Texture Pixel Upload")) + return false; + + texture_state_ = kStateComplete; + return true; +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterfw/native/core/gl_frame.h b/media/mca/filterfw/native/core/gl_frame.h new file mode 100644 index 0000000..f2a1ad5 --- /dev/null +++ b/media/mca/filterfw/native/core/gl_frame.h @@ -0,0 +1,217 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_GL_FRAME_H +#define ANDROID_FILTERFW_CORE_GL_FRAME_H + +#include <map> + +#include <GLES2/gl2.h> + +#include "core/gl_buffer_interface.h" + +namespace android { +namespace filterfw { + +class GLEnv; +class ShaderProgram; + +// A GLFrame stores pixel data on the GPU. While pixel data may be uploaded to +// a GLFrame and also read out of a GLFrame (access in place is not supported), +// it is strongly recommended to use ShaderProgram objects for any kind of +// processing from one GLFrame to another. +class GLFrame : public GLBufferHandle { + public: + // Create an empty GL frame in the specified GL environment. Note, that the GLFrame does NOT + // take ownership. The caller must make sure the GLEnv stays valid as long as the GLFrame is + // alive. + GLFrame(GLEnv* gl_env); + + // Deallocate a GL frame. + ~GLFrame(); + + // Initialize a GL frame to the given width, height, format. Also specify + // whether this is a read-only GL frame or not. + bool Init(int width, int height); + + // Initialize as using an external texture. + bool InitWithExternalTexture(); + + // Initialize using an existing texture. + bool InitWithTexture(GLint texture_id, int width, int height); + + // Initialize using an existing FBO. + bool InitWithFbo(GLint fbo_id, int width, int height); + + // Write the data with the given size in bytes to the frame. The frame size must match the + // size of the data. + bool WriteData(const uint8_t* data, int size); + + // Copies the frame data to the given buffer. + bool CopyDataTo(uint8_t* buffer, int size); + + // Copies the pixels from another GL frame to this frame. + bool CopyPixelsFrom(const GLFrame* frame); + + // Returns the size of the buffer in bytes. + int Size() const; + + // Clone the current frame by creating a new GL frame and copying all data to it. + GLFrame* Clone() const; + + // Returns the held texture id. Only call this if the GLFrame holds a + // texture. You can check this by calling HoldsTexture(). + // Note, that a texture is created only when needed. If you are creating a + // new GLFrame, and you need it to be bound to a texture, upload (zeroed) + // data to it first, before calling this method. + GLuint GetTextureId() const; + + // Returns the held FBO id. Only call this if the GLFrame holds an FBO. You + // can check this by calling HoldsFbo(). + GLuint GetFboId() const; + + // Returns the texture target: GL_TEXTURE_2D or GL_TEXTURE_EXTERNAL_OES. + GLuint GetTextureTarget() const { + return texture_target_; + } + + // Set the viewport that will be used when focusing this frame for rendering. Defaults to + // the dimensions of the frame. + bool SetViewport(int x, int y, int width, int height); + + // Binds the held texture. This may result in creating the texture if it + // is not yet available. + bool FocusTexture(); + + // Binds the held FBO. This may result in creating the FBO if it + // is not yet available. + bool FocusFrameBuffer(); + + // Generates the mipmap chain of the held texture. Returns true, iff + // generating was successful. + bool GenerateMipMap(); + + // Set a texture parameter (see glTextureParameter documentation). Returns + // true iff the parameter was set successfully. + bool SetTextureParameter(GLenum pname, GLint value); + + // Reset any modifed texture parameters. + bool ResetTexParameters(); + + // Detaches the internal texture from the FBO. + bool DetachTextureFromFbo(); + + // Reattaches the internal texture to the FBO after detachment. + bool ReattachTextureToFbo(); + + private: + // Type to keep track of texture and FBO states + enum GLObjectState { + kStateUnmanaged, // We do not manage this object (externally managed) + kStateUninitialized, // Not yet initialized + kStateGenerated, // Tex/FBO id is generated + kStateComplete // FBO has valid attachment / Tex has valid pixel data + }; + + // Sets the frame and viewport dimensions. + void InitDimensions(int width, int height); + + // Generates the internal texture name. + bool GenerateTextureName(); + + // Allocates the internal texture. + bool AllocateTexture(); + + // Creates the internal FBO. + bool GenerateFboName(); + + // Copies pixels from texture or FBO to the specified buffer. + bool CopyPixelsTo(uint8_t* buffer); + + // Reads the pixels from the internal texture to the given buffer. + bool ReadTexturePixels(uint8_t* pixels) const; + + // Reads the pixels from the internal FBO to the given buffer. + bool ReadFboPixels(uint8_t* pixels) const; + + // Writes the specified pixels to the internal texture. + bool UploadTexturePixels(const uint8_t* pixels); + + // Binds the internal texture. + bool BindTexture() const; + + // Binds the internal FBO. + bool BindFrameBuffer() const; + + // Attaches the internal texture to the internal FBO. + bool AttachTextureToFbo(); + + // Update the texture parameters to the user specified parameters + bool UpdateTexParameters(); + + // Returns true if the current texture parameters are not the GLES2 + // default parameters. + bool TexParametersModifed(); + + // Sets the current texture parameters to the GLES2 default + // parameters. This still requires a call to UpdateTexParameters() + // for the changes to take effect. + void SetDefaultTexParameters(); + + // Returns true if the texture we assume to be allocated has been + // deleted externally. In this case we assume the texture name is + // still valid (otherwise we were provided with a bad texture id). + bool TextureWasDeleted() const; + + // Get the (cached) identity shader. + ShaderProgram* GetIdentity() const; + + // The GL environment this frame belongs to + GLEnv* gl_env_; + + // The width, height and format of the frame + int width_; + int height_; + + // The viewport dimensions + int vp_x_; + int vp_y_; + int vp_width_; + int vp_height_; + + // The texture and FBO ids + GLuint texture_id_; + GLuint fbo_id_; + + // The texture target: GL_TEXTURE_2D or GL_TEXTURE_EXTERNAL_OES + GLuint texture_target_; + + // Flags whether or not frame holds a texture and FBO + GLObjectState texture_state_; + GLObjectState fbo_state_; + + // Set of current texture parameters + std::map<GLenum, GLint> tex_params_; + + // Flag whether frame owns the texture and FBO + bool owns_texture_; + bool owns_fbo_; +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_GL_FRAME_H diff --git a/media/mca/filterfw/native/core/native_frame.cpp b/media/mca/filterfw/native/core/native_frame.cpp new file mode 100644 index 0000000..957ecb1 --- /dev/null +++ b/media/mca/filterfw/native/core/native_frame.cpp @@ -0,0 +1,61 @@ +/* + * 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. + */ + +#include "core/native_frame.h" + +namespace android { +namespace filterfw { + +NativeFrame::NativeFrame(int size) : data_(NULL), size_(size), capacity_(size) { + data_ = capacity_ == 0 ? NULL : new uint8_t[capacity_]; +} + +NativeFrame::~NativeFrame() { + delete[] data_; +} + +bool NativeFrame::WriteData(const uint8_t* data, int offset, int size) { + if (size_ >= (offset + size)) { + memcpy(data_ + offset, data, size); + return true; + } + return false; +} + +bool NativeFrame::SetData(uint8_t* data, int size) { + delete[] data_; + size_ = capacity_ = size; + data_ = data; + return true; +} + +NativeFrame* NativeFrame::Clone() const { + NativeFrame* result = new NativeFrame(size_); + if (data_) + result->WriteData(data_, 0, size_); + return result; +} + +bool NativeFrame::Resize(int newSize) { + if (newSize <= capacity_ && newSize >= 0) { + size_ = newSize; + return true; + } + return false; +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterfw/native/core/native_frame.h b/media/mca/filterfw/native/core/native_frame.h new file mode 100644 index 0000000..0d335b3 --- /dev/null +++ b/media/mca/filterfw/native/core/native_frame.h @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_NATIVE_FRAME_H +#define ANDROID_FILTERFW_CORE_NATIVE_FRAME_H + +#include "base/utilities.h" + +namespace android { +namespace filterfw { + +// A NativeFrame stores data in a memory buffer (on the heap). It is used for +// data processing on the CPU. +class NativeFrame { + public: + // Create an empty native frame. + NativeFrame(int size); + + ~NativeFrame(); + + // Set the frame data and size in bytes. The NativeFrame object takes ownership of the data. + // To copy data into an existing frame, use WriteData(). + bool SetData(uint8_t* data, int size); + + // Write the specified data of the given size to the frame at the specified offset. The + // receiver must be large enough to hold the data. + bool WriteData(const uint8_t* data, int offset, int size); + + // Returns a pointer to the data, or NULL if no data was set. + const uint8_t* Data() const { + return data_; + } + + // Returns a non-const pointer to the data, or NULL if no data was set. + uint8_t* MutableData() { + return data_; + } + + // Resize the frame. You can only resize to a size that fits within the frame's capacity. + // Returns true if the resize was successful. + bool Resize(int newSize); + + // Returns the size of the frame in bytes. + int Size() { + return size_; + } + + // Returns the capacity of the frame in bytes. + int Capacity() { + return capacity_; + } + + // Returns a new native frame + NativeFrame* Clone() const; + + private: + // Pointer to the data. Owned by the frame. + uint8_t* data_; + + // Size of data buffer in bytes. + int size_; + + // Capacity of data buffer in bytes. + int capacity_; + + DISALLOW_COPY_AND_ASSIGN(NativeFrame); +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_NATIVE_FRAME_H diff --git a/media/mca/filterfw/native/core/native_program.cpp b/media/mca/filterfw/native/core/native_program.cpp new file mode 100644 index 0000000..c46c46f --- /dev/null +++ b/media/mca/filterfw/native/core/native_program.cpp @@ -0,0 +1,162 @@ +/* + * 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. + */ + +#include <dlfcn.h> + +#include "base/logging.h" +#include "core/native_frame.h" +#include "core/native_program.h" + +#include <string> +#include <vector> + +namespace android { +namespace filterfw { + +NativeProgram::NativeProgram() + : lib_handle_(NULL), + init_function_(NULL), + setvalue_function_(NULL), + getvalue_function_(NULL), + process_function_(NULL), + reset_function_(NULL), + teardown_function_(NULL), + user_data_(NULL) { +} + +NativeProgram::~NativeProgram() { + if (lib_handle_) + dlclose(lib_handle_); +} + +bool NativeProgram::OpenLibrary(const std::string& lib_name) { + if (!lib_handle_) { + lib_handle_ = dlopen(lib_name.c_str(), RTLD_NOW); + if (!lib_handle_) { + ALOGE("NativeProgram: Error opening library: '%s': %s", lib_name.c_str(), dlerror()); + return false; + } + return true; + } + return false; +} + +bool NativeProgram::BindProcessFunction(const std::string& func_name) { + if (!lib_handle_) + return false; + process_function_ = reinterpret_cast<ProcessFunctionPtr>(dlsym(lib_handle_, func_name.c_str())); + if (!process_function_) { + ALOGE("NativeProgram: Could not find process function symbol: '%s'!", func_name.c_str()); + return false; + } + return true; +} + +bool NativeProgram::BindInitFunction(const std::string& func_name) { + if (!lib_handle_) + return false; + init_function_ = reinterpret_cast<InitFunctionPtr>(dlsym(lib_handle_, func_name.c_str())); + return init_function_ != NULL; +} + +bool NativeProgram::BindSetValueFunction(const std::string& func_name) { + if (!lib_handle_) + return false; + setvalue_function_ = reinterpret_cast<SetValueFunctionPtr>(dlsym(lib_handle_, func_name.c_str())); + return setvalue_function_ != NULL; +} + +bool NativeProgram::BindGetValueFunction(const std::string& func_name) { + if (!lib_handle_) + return false; + getvalue_function_ = reinterpret_cast<GetValueFunctionPtr>(dlsym(lib_handle_, func_name.c_str())); + return getvalue_function_ != NULL; +} + +bool NativeProgram::BindResetFunction(const std::string& func_name) { + if (!lib_handle_) + return false; + reset_function_ = reinterpret_cast<ResetFunctionPtr>(dlsym(lib_handle_, func_name.c_str())); + return reset_function_ != NULL; +} + +bool NativeProgram::BindTeardownFunction(const std::string& func_name) { + if (!lib_handle_) + return false; + teardown_function_ = reinterpret_cast<TeardownFunctionPtr>(dlsym(lib_handle_, func_name.c_str())); + return teardown_function_ != NULL; +} + +bool NativeProgram::CallProcess(const std::vector<const char*>& inputs, + const std::vector<int>& input_sizes, + char* output, + int output_size) { + if (process_function_) { + return process_function_(const_cast<const char**>(&inputs[0]), + &input_sizes[0], + inputs.size(), + output, + output_size, + user_data_) == 1; + } + return false; +} + +bool NativeProgram::CallInit() { + if (init_function_) { + init_function_(&user_data_); + return true; + } + return false; +} + +bool NativeProgram::CallSetValue(const std::string& key, const std::string& value) { + if (setvalue_function_) { + setvalue_function_(key.c_str(), value.c_str(), user_data_); + return true; + } + return false; +} + +std::string NativeProgram::CallGetValue(const std::string& key) { + if (getvalue_function_) { + static const int buffer_size = 1024; + char result[buffer_size]; + result[buffer_size - 1] = '\0'; + getvalue_function_(key.c_str(), result, buffer_size, user_data_); + return std::string(result); + } + return std::string(); +} + +bool NativeProgram::CallReset() { + if (reset_function_) { + reset_function_(user_data_); + return true; + } + return false; +} + +bool NativeProgram::CallTeardown() { + if (teardown_function_) { + teardown_function_(user_data_); + return true; + } + return false; +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterfw/native/core/native_program.h b/media/mca/filterfw/native/core/native_program.h new file mode 100644 index 0000000..ce704af --- /dev/null +++ b/media/mca/filterfw/native/core/native_program.h @@ -0,0 +1,84 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_NATIVE_PROGRAM_H +#define ANDROID_FILTERFW_CORE_NATIVE_PROGRAM_H + +#include <vector> +#include <string> + +#include "base/utilities.h" + +namespace android { +namespace filterfw { + +class NativeFrame; + +typedef void (*InitFunctionPtr)(void**); +typedef void (*SetValueFunctionPtr)(const char*, const char*, void*); +typedef void (*GetValueFunctionPtr)(const char*, char*, int, void*); +typedef int (*ProcessFunctionPtr)(const char**, const int*, int, char*, int, void*); +typedef void (*ResetFunctionPtr)(void*); +typedef void (*TeardownFunctionPtr)(void*); + +class NativeProgram { + public: + // Create an empty native frame. + NativeProgram(); + + ~NativeProgram(); + + bool OpenLibrary(const std::string& lib_name); + + bool BindInitFunction(const std::string& func_name); + bool BindSetValueFunction(const std::string& func_name); + bool BindGetValueFunction(const std::string& func_name); + bool BindProcessFunction(const std::string& func_name); + bool BindResetFunction(const std::string& func_name); + bool BindTeardownFunction(const std::string& func_name); + + bool CallInit(); + bool CallSetValue(const std::string& key, const std::string& value); + std::string CallGetValue(const std::string& key); + bool CallProcess(const std::vector<const char*>& inputs, + const std::vector<int>& input_sizes, + char* output, + int output_size); + bool CallReset(); + bool CallTeardown(); + + private: + // Pointer to the data. Owned by the frame. + void* lib_handle_; + + // The function pointers to the native function implementations. + InitFunctionPtr init_function_; + SetValueFunctionPtr setvalue_function_; + GetValueFunctionPtr getvalue_function_; + ProcessFunctionPtr process_function_; + ResetFunctionPtr reset_function_; + TeardownFunctionPtr teardown_function_; + + // Pointer to user data + void* user_data_; + + DISALLOW_COPY_AND_ASSIGN(NativeProgram); +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_NATIVE_PROGRAM_H diff --git a/media/mca/filterfw/native/core/shader_program.cpp b/media/mca/filterfw/native/core/shader_program.cpp new file mode 100644 index 0000000..d92eb31 --- /dev/null +++ b/media/mca/filterfw/native/core/shader_program.cpp @@ -0,0 +1,1122 @@ +/* + * 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. + */ + +#include "base/logging.h" + +#include "core/geometry.h" +#include "core/gl_buffer_interface.h" +#include "core/gl_env.h" +#include "core/gl_frame.h" +#include "core/shader_program.h" +#include "core/vertex_frame.h" + +#include <string> +#include <sstream> +#include <vector> + +namespace android { +namespace filterfw { + +// VBO attachment keys +static const int kDefaultVboKey = 1; + +static const char* s_default_vertex_shader_source_ = + "attribute vec4 a_position;\n" + "attribute vec2 a_texcoord;\n" + "varying vec2 v_texcoord;\n" + "void main() {\n" + " gl_Position = a_position;\n" + " v_texcoord = a_texcoord;\n" + "}\n"; + +// Helper Functions //////////////////////////////////////////////////////////// +// Maps coordinates x,y in the unit rectangle over to the quadrangle specified +// by the four points in b. The result coordinates are written to xt and yt. +static void GetTileCoords(const float* b, float x, float y, float* xt, float* yt) { + const float w0 = (1.0f - x) * (1.0f - y); + const float w1 = x * (1.0f - y); + const float w2 = (1.0f - x) * y; + const float w3 = x * y; + + *xt = w0 * b[0] + w1 * b[2] + w2 * b[4] + w3 * b[6]; + *yt = w0 * b[1] + w1 * b[3] + w2 * b[5] + w3 * b[7]; +} + +static inline float AdjustRatio(float current, float next) { + return (current + next) / 2.0; +} + +// VertexAttrib implementation ///////////////////////////////////////////////// +ShaderProgram::VertexAttrib::VertexAttrib() + : is_const(true), + index(-1), + normalized(false), + stride(0), + components(0), + offset(0), + type(GL_FLOAT), + vbo(0), + values(NULL), + owned_data(NULL) { +} + +// ShaderProgram implementation //////////////////////////////////////////////// +ShaderProgram::ShaderProgram(GLEnv* gl_env, const std::string& fragment_shader) + : fragment_shader_source_(fragment_shader), + vertex_shader_source_(s_default_vertex_shader_source_), + fragment_shader_(0), + vertex_shader_(0), + program_(0), + gl_env_(gl_env), + base_texture_unit_(GL_TEXTURE0), + source_coords_(NULL), + target_coords_(NULL), + manage_coordinates_(false), + tile_x_count_(1), + tile_y_count_(1), + vertex_count_(4), + draw_mode_(GL_TRIANGLE_STRIP), + clears_(false), + blending_(false), + sfactor_(GL_SRC_ALPHA), + dfactor_(GL_ONE_MINUS_SRC_ALPHA) { + SetDefaultCoords(); +} + +ShaderProgram::ShaderProgram(GLEnv* gl_env, + const std::string& vertex_shader, + const std::string& fragment_shader) + : fragment_shader_source_(fragment_shader), + vertex_shader_source_(vertex_shader), + fragment_shader_(0), + vertex_shader_(0), + program_(0), + gl_env_(gl_env), + base_texture_unit_(GL_TEXTURE0), + source_coords_(NULL), + target_coords_(NULL), + manage_coordinates_(false), + tile_x_count_(1), + tile_y_count_(1), + vertex_count_(4), + draw_mode_(GL_TRIANGLE_STRIP), + clears_(false), + blending_(false), + sfactor_(GL_SRC_ALPHA), + dfactor_(GL_ONE_MINUS_SRC_ALPHA) { + SetDefaultCoords(); +} + +ShaderProgram::~ShaderProgram() { + // Delete our vertex data + delete[] source_coords_; + delete[] target_coords_; + + // Delete any owned attribute data + VertexAttribMap::const_iterator iter; + for (iter = attrib_values_.begin(); iter != attrib_values_.end(); ++iter) { + const VertexAttrib& attrib = iter->second; + if (attrib.owned_data) + delete[] attrib.owned_data; + } +} + +void ShaderProgram::SetDefaultCoords() { + if (!source_coords_) + source_coords_ = new float[8]; + if (!target_coords_) + target_coords_ = new float[8]; + + source_coords_[0] = 0.0f; + source_coords_[1] = 0.0f; + source_coords_[2] = 1.0f; + source_coords_[3] = 0.0f; + source_coords_[4] = 0.0f; + source_coords_[5] = 1.0f; + source_coords_[6] = 1.0f; + source_coords_[7] = 1.0f; + + target_coords_[0] = -1.0f; + target_coords_[1] = -1.0f; + target_coords_[2] = 1.0f; + target_coords_[3] = -1.0f; + target_coords_[4] = -1.0f; + target_coords_[5] = 1.0f; + target_coords_[6] = 1.0f; + target_coords_[7] = 1.0f; + +} + +ShaderProgram* ShaderProgram::CreateIdentity(GLEnv* gl_env) { + const char* s_id_fragment_shader = + "precision mediump float;\n" + "uniform sampler2D tex_sampler_0;\n" + "varying vec2 v_texcoord;\n" + "void main() {\n" + " gl_FragColor = texture2D(tex_sampler_0, v_texcoord);\n" + "}\n"; + ShaderProgram* result = new ShaderProgram(gl_env, s_id_fragment_shader); + result->CompileAndLink(); + return result; +} + +bool ShaderProgram::IsVarValid(ProgramVar var) { + return var != -1; +} + +bool ShaderProgram::Process(const std::vector<const GLTextureHandle*>& input, + GLFrameBufferHandle* output) { + // TODO: This can be optimized: If the input and output are the same, as in + // the last iteration (typical of a multi-pass filter), a lot of this setup + // may be skipped. + + // Abort if program did not successfully compile and link + if (!IsExecutable()) { + ALOGE("ShaderProgram: unexecutable program!"); + return false; + } + + // Focus the FBO of the output + if (!output->FocusFrameBuffer()) { + ALOGE("Unable to focus frame buffer"); + return false; + } + + // Get all required textures + std::vector<GLuint> textures; + std::vector<GLenum> targets; + for (unsigned i = 0; i < input.size(); ++i) { + // Get the current input frame and make sure it is a GL frame + if (input[i]) { + // Get the texture bound to that frame + const GLuint tex_id = input[i]->GetTextureId(); + const GLenum target = input[i]->GetTextureTarget(); + if (tex_id == 0) { + ALOGE("ShaderProgram: invalid texture id at input: %d!", i); + return false; + } + textures.push_back(tex_id); + targets.push_back(target); + } + } + + // And render! + if (!RenderFrame(textures, targets)) { + ALOGE("Unable to render frame"); + return false; + } + return true; +} + +bool ShaderProgram::Process(const std::vector<const GLFrame*>& input, GLFrame* output) { + std::vector<const GLTextureHandle*> textures(input.size()); + std::copy(input.begin(), input.end(), textures.begin()); + return Process(textures, output); +} + +void ShaderProgram::SetSourceRect(float x, float y, float width, float height) { + Quad quad(Point(x, y), + Point(x + width, y), + Point(x, y + height), + Point(x + width, y + height)); + SetSourceRegion(quad); +} + +void ShaderProgram::SetSourceRegion(const Quad& quad) { + int index = 0; + for (int i = 0; i < 4; ++i, index += 2) { + source_coords_[index] = quad.point(i).x(); + source_coords_[index+1] = quad.point(i).y(); + } +} + +void ShaderProgram::SetTargetRect(float x, float y, float width, float height) { + Quad quad(Point(x, y), + Point(x + width, y), + Point(x, y + height), + Point(x + width, y + height)); + SetTargetRegion(quad); +} + +void ShaderProgram::SetTargetRegion(const Quad& quad) { + int index = 0; + for (int i = 0; i < 4; ++i, index += 2) { + target_coords_[index] = (quad.point(i).x() * 2.0) - 1.0; + target_coords_[index+1] = (quad.point(i).y() * 2.0) - 1.0; + } +} + +bool ShaderProgram::CompileAndLink() { + // Make sure we haven't compiled and linked already + if (vertex_shader_ != 0 || fragment_shader_ != 0 || program_ != 0) { + ALOGE("Attempting to re-compile shaders!"); + return false; + } + + // Compile vertex shader + vertex_shader_ = CompileShader(GL_VERTEX_SHADER, + vertex_shader_source_.c_str()); + if (!vertex_shader_) { + ALOGE("Shader compilation failed!"); + return false; + } + + // Compile fragment shader + fragment_shader_ = CompileShader(GL_FRAGMENT_SHADER, + fragment_shader_source_.c_str()); + if (!fragment_shader_) + return false; + + // Link + GLuint shaders[2] = { vertex_shader_, fragment_shader_ }; + program_ = LinkProgram(shaders, 2); + + // Scan for all uniforms in the program + ScanUniforms(); + + // Check if we manage all coordinates + if (program_ != 0) { + ProgramVar tex_coord_attr = glGetAttribLocation(program_, TexCoordAttributeName().c_str()); + ProgramVar pos_coord_attr = glGetAttribLocation(program_, PositionAttributeName().c_str()); + manage_coordinates_ = (tex_coord_attr >= 0 && pos_coord_attr >= 0); + } else { + ALOGE("Could not link shader program!"); + return false; + } + + return true; +} + +GLuint ShaderProgram::CompileShader(GLenum shader_type, const char* source) { + LOG_FRAME("Compiling source:\n[%s]", source); + + // Create shader + GLuint shader = glCreateShader(shader_type); + if (shader) { + // Compile source + glShaderSource(shader, 1, &source, NULL); + glCompileShader(shader); + + // Check for compilation errors + GLint compiled = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (!compiled) { + // Log the compilation error messages + ALOGE("Problem compiling shader! Source:"); + ALOGE("%s", source); + std::string src(source); + unsigned int cur_pos = 0; + unsigned int next_pos = 0; + int line_number = 1; + while ( (next_pos = src.find_first_of('\n', cur_pos)) != std::string::npos) { + ALOGE("%03d : %s", line_number, src.substr(cur_pos, next_pos-cur_pos).c_str()); + cur_pos = next_pos + 1; + line_number++; + } + ALOGE("%03d : %s", line_number, src.substr(cur_pos, next_pos-cur_pos).c_str()); + + GLint log_length = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length); + if (log_length) { + char* error_log = new char[log_length]; + if (error_log) { + glGetShaderInfoLog(shader, log_length, NULL, error_log); + ALOGE("Shader compilation error %d:\n%s\n", shader_type, error_log); + delete[] error_log; + } + } + glDeleteShader(shader); + shader = 0; + } + } + return shader; +} + +GLuint ShaderProgram::LinkProgram(GLuint* shaders, GLuint count) { + GLuint program = glCreateProgram(); + if (program) { + // Attach all compiled shaders + for (GLuint i = 0; i < count; ++i) { + glAttachShader(program, shaders[i]); + if (GLEnv::CheckGLError("glAttachShader")) return 0; + } + + // Link + glLinkProgram(program); + + // Check for linking errors + GLint linked = 0; + glGetProgramiv(program, GL_LINK_STATUS, &linked); + if (linked != GL_TRUE) { + // Log the linker error messages + GLint log_length = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length); + if (log_length) { + char* error_log = new char[log_length]; + if (error_log) { + glGetProgramInfoLog(program, log_length, NULL, error_log); + ALOGE("Program Linker Error:\n%s\n", error_log); + delete[] error_log; + } + } + glDeleteProgram(program); + program = 0; + } + } + return program; +} + +void ShaderProgram::ScanUniforms() { + int uniform_count; + int buffer_size; + GLenum type; + GLint capacity; + glGetProgramiv(program_, GL_ACTIVE_UNIFORMS, &uniform_count); + glGetProgramiv(program_, GL_ACTIVE_UNIFORM_MAX_LENGTH, &buffer_size); + std::vector<GLchar> name(buffer_size); + for (int i = 0; i < uniform_count; ++i) { + glGetActiveUniform(program_, i, buffer_size, NULL, &capacity, &type, &name[0]); + ProgramVar uniform_id = glGetUniformLocation(program_, &name[0]); + uniform_indices_[uniform_id] = i; + } +} + +bool ShaderProgram::PushCoords(ProgramVar attr, float* coords) { + // If the shader does not define these, we simply ignore the coordinates, and assume that the + // user is managing coordinates. + if (attr >= 0) { + const uint8_t* data = reinterpret_cast<const uint8_t*>(coords); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glVertexAttribPointer(attr, 2, GL_FLOAT, false, 2 * sizeof(float), data); + glEnableVertexAttribArray(attr); + return !GLEnv::CheckGLError("Pushing vertex coordinates"); + } + return true; +} + +bool ShaderProgram::PushSourceCoords(float* coords) { + ProgramVar tex_coord_attr = glGetAttribLocation(program_, TexCoordAttributeName().c_str()); + return PushCoords(tex_coord_attr, coords); +} + +bool ShaderProgram::PushTargetCoords(float* coords) { + ProgramVar pos_coord_attr = glGetAttribLocation(program_, PositionAttributeName().c_str()); + return PushCoords(pos_coord_attr, coords); +} + +std::string ShaderProgram::InputTextureUniformName(int index) { + std::stringstream tex_name; + tex_name << "tex_sampler_" << index; + return tex_name.str(); +} + +bool ShaderProgram::BindInputTextures(const std::vector<GLuint>& textures, + const std::vector<GLenum>& targets) { + for (unsigned i = 0; i < textures.size(); ++i) { + // Activate texture unit i + glActiveTexture(BaseTextureUnit() + i); + if (GLEnv::CheckGLError("Activating Texture Unit")) + return false; + + // Bind our texture + glBindTexture(targets[i], textures[i]); + LOG_FRAME("Binding texture %d", textures[i]); + if (GLEnv::CheckGLError("Binding Texture")) + return false; + + // Set the texture handle in the shader to unit i + ProgramVar tex_var = GetUniform(InputTextureUniformName(i)); + if (tex_var >= 0) { + glUniform1i(tex_var, i); + } else { + ALOGE("ShaderProgram: Shader does not seem to support %d number of " + "inputs! Missing uniform 'tex_sampler_%d'!", textures.size(), i); + return false; + } + + if (GLEnv::CheckGLError("Texture Variable Binding")) + return false; + } + + return true; +} + +bool ShaderProgram::UseProgram() { + if (GLEnv::GetCurrentProgram() != program_) { + LOG_FRAME("Using program %d", program_); + glUseProgram(program_); + return !GLEnv::CheckGLError("Use Program"); + } + return true; +} + +bool ShaderProgram::RenderFrame(const std::vector<GLuint>& textures, + const std::vector<GLenum>& targets) { + // Make sure we have enough texture units to accomodate the textures + if (textures.size() > static_cast<unsigned>(MaxTextureUnits())) { + ALOGE("ShaderProgram: Number of input textures is unsupported on this " + "platform!"); + return false; + } + + // Prepare to render + if (!BeginDraw()) { + ALOGE("ShaderProgram: couldn't initialize gl for drawing!"); + return false; + } + + // Bind input textures + if (!BindInputTextures(textures, targets)) { + ALOGE("BindInputTextures failed"); + return false; + } + + if (LOG_EVERY_FRAME) { + int fbo, program, buffer; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &fbo); + glGetIntegerv(GL_CURRENT_PROGRAM, &program); + glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &buffer); + ALOGV("RenderFrame: fbo %d prog %d buff %d", fbo, program, buffer); + } + + // Render! + const bool requestTile = (tile_x_count_ != 1 || tile_y_count_ != 1); + const bool success = (!requestTile || !manage_coordinates_ || vertex_count_ != 4) + ? Draw() + : DrawTiled(); + + // Pop vertex attributes + PopAttributes(); + + return success && !GLEnv::CheckGLError("Rendering"); +} + +bool ShaderProgram::Draw() { + if (PushSourceCoords(source_coords_) && PushTargetCoords(target_coords_)) { + glDrawArrays(draw_mode_, 0, vertex_count_); + return true; + } + return false; +} + +bool ShaderProgram::DrawTiled() { + // Target coordinate step size + float s[8]; + float t[8]; + + // Step sizes + const float xs = 1.0f / static_cast<float>(tile_x_count_); + const float ys = 1.0f / static_cast<float>(tile_y_count_); + + // Tile drawing loop + for (int i = 0; i < tile_x_count_; ++i) { + for (int j = 0; j < tile_y_count_; ++j) { + // Current coordinates in unit rectangle + const float x = i / static_cast<float>(tile_x_count_); + const float y = j / static_cast<float>(tile_y_count_); + + // Source coords + GetTileCoords(source_coords_, x, y, &s[0], &s[1]); + GetTileCoords(source_coords_, x + xs, y, &s[2], &s[3]); + GetTileCoords(source_coords_, x, y + ys, &s[4], &s[5]); + GetTileCoords(source_coords_, x + xs, y + ys, &s[6], &s[7]); + + // Target coords + GetTileCoords(target_coords_, x, y, &t[0], &t[1]); + GetTileCoords(target_coords_, x + xs, y, &t[2], &t[3]); + GetTileCoords(target_coords_, x, y + ys, &t[4], &t[5]); + GetTileCoords(target_coords_, x + xs, y + ys, &t[6], &t[7]); + + if (PushSourceCoords(s) && PushTargetCoords(t)) { + glDrawArrays(draw_mode_, 0, vertex_count_); + Yield(); + } else { + return false; + } + } + } + return true; +} + +void ShaderProgram::Yield() { + glFinish(); +} + +bool ShaderProgram::BeginDraw() { + // Activate shader program + if (!UseProgram()) + return false; + + // Push vertex attributes + PushAttributes(); + + // Clear output, if requested + if (clears_) { + glClearColor(clear_color_.red, + clear_color_.green, + clear_color_.blue, + clear_color_.alpha); + glClear(GL_COLOR_BUFFER_BIT); + } + + // Enable/Disable blending + if (blending_) { + glEnable(GL_BLEND); + glBlendFunc(sfactor_, dfactor_); + } else glDisable(GL_BLEND); + + return true; +} + +int ShaderProgram::MaxVaryingCount() { + GLint result; + glGetIntegerv(GL_MAX_VARYING_VECTORS, &result); + return result; +} + +int ShaderProgram::MaxTextureUnits() { + return GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS - 1; +} + +void ShaderProgram::SetDrawMode(GLenum mode) { + draw_mode_ = mode; +} + +void ShaderProgram::SetClearsOutput(bool clears) { + clears_ = clears; +} + +void ShaderProgram::SetClearColor(float red, float green, float blue, float alpha) { + clear_color_.red = red; + clear_color_.green = green; + clear_color_.blue = blue; + clear_color_.alpha = alpha; +} + +void ShaderProgram::SetTileCounts(int x_count, int y_count) { + tile_x_count_ = x_count; + tile_y_count_ = y_count; +} + +// Variable Value Setting Helpers ////////////////////////////////////////////// +bool ShaderProgram::CheckValueCount(const std::string& var_type, + const std::string& var_name, + int expected_count, + int components, + int value_size) { + if (expected_count != (value_size / components)) { + ALOGE("Shader Program: %s Value Error (%s): Expected value length %d " + "(%d components), but received length of %d (%d components)!", + var_type.c_str(), var_name.c_str(), + expected_count, components * expected_count, + value_size / components, value_size); + return false; + } + return true; +} + +bool ShaderProgram::CheckValueMult(const std::string& var_type, + const std::string& var_name, + int components, + int value_size) { + if (value_size % components != 0) { + ALOGE("Shader Program: %s Value Error (%s): Value must be multiple of %d, " + "but %d elements were passed!", var_type.c_str(), var_name.c_str(), + components, value_size); + return false; + } + return true; +} + +bool ShaderProgram::CheckVarValid(ProgramVar var) { + if (!IsVarValid(var)) { + ALOGE("Shader Program: Attempting to access invalid variable!"); + return false; + } + return true; +} + +// Uniforms //////////////////////////////////////////////////////////////////// +bool ShaderProgram::CheckUniformValid(ProgramVar var) { + if (!IsVarValid(var) || uniform_indices_.find(var) == uniform_indices_.end()) { + ALOGE("Shader Program: Attempting to access unknown uniform %d!", var); + return false; + } + return true; +} + +int ShaderProgram::MaxUniformCount() { + // Here we return the minimum of the max uniform count for fragment and vertex + // shaders. + GLint count1, count2; + glGetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS, &count1); + glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, &count2); + return count1 < count2 ? count1 : count2; +} + +ProgramVar ShaderProgram::GetUniform(const std::string& name) const { + if (!IsExecutable()) { + ALOGE("ShaderProgram: Error: Must link program before querying uniforms!"); + return -1; + } + return glGetUniformLocation(program_, name.c_str()); +} + +bool ShaderProgram::SetUniformValue(ProgramVar var, int value) { + if (!CheckVarValid(var)) + return false; + + // Uniforms are local to the currently used program. + if (UseProgram()) { + glUniform1i(var, value); + return !GLEnv::CheckGLError("Set Uniform Value (int)"); + } + return false; +} + +bool ShaderProgram::SetUniformValue(ProgramVar var, float value) { + if (!CheckVarValid(var)) + return false; + + // Uniforms are local to the currently used program. + if (UseProgram()) { + glUniform1f(var, value); + return !GLEnv::CheckGLError("Set Uniform Value (float)"); + } + return false; +} + +bool ShaderProgram::SetUniformValue(ProgramVar var, + const int* values, + int count) { + if (!CheckUniformValid(var)) + return false; + + // Make sure we have values at all + if (count == 0) + return false; + + // Uniforms are local to the currently used program. + if (UseProgram()) { + // Get uniform information + GLint capacity; + GLenum type; + char name[128]; + glGetActiveUniform(program_, IndexOfUniform(var), 128, NULL, &capacity, &type, name); + + // Make sure passed values are compatible + const int components = GLEnv::NumberOfComponents(type); + if (!CheckValueCount("Uniform (int)", name, capacity, components, count) + || !CheckValueMult ("Uniform (int)", name, components, count)) + return false; + + // Set value based on type + const int n = count / components; + switch(type) { + case GL_INT: + glUniform1iv(var, n, values); + break; + + case GL_INT_VEC2: + glUniform2iv(var, n, values); + break; + + case GL_INT_VEC3: + glUniform3iv(var, n, values); + break; + + case GL_INT_VEC4: + glUniform4iv(var, n, values); + break; + + default: + return false; + }; + return !GLEnv::CheckGLError("Set Uniform Value"); + } + return false; +} + +bool ShaderProgram::SetUniformValue(ProgramVar var, + const float* values, + int count) { + if (!CheckUniformValid(var)) + return false; + + // Make sure we have values at all + if (count == 0) + return false; + + // Uniforms are local to the currently used program. + if (UseProgram()) { + // Get uniform information + GLint capacity; + GLenum type; + char name[128]; + glGetActiveUniform(program_, IndexOfUniform(var), 128, NULL, &capacity, &type, name); + + // Make sure passed values are compatible + const int components = GLEnv::NumberOfComponents(type); + if (!CheckValueCount("Uniform (float)", name, capacity, components, count) + || !CheckValueMult ("Uniform (float)", name, components, count)) + return false; + + // Set value based on type + const int n = count / components; + switch(type) { + case GL_FLOAT: + glUniform1fv(var, n, values); + break; + + case GL_FLOAT_VEC2: + glUniform2fv(var, n, values); + break; + + case GL_FLOAT_VEC3: + glUniform3fv(var, n, values); + break; + + case GL_FLOAT_VEC4: + glUniform4fv(var, n, values); + break; + + case GL_FLOAT_MAT2: + glUniformMatrix2fv(var, n, GL_FALSE, values); + break; + + case GL_FLOAT_MAT3: + glUniformMatrix3fv(var, n, GL_FALSE, values); + break; + + case GL_FLOAT_MAT4: + glUniformMatrix4fv(var, n, GL_FALSE, values); + break; + + default: + return false; + }; + return !GLEnv::CheckGLError("Set Uniform Value"); + } + return false; +} + +bool ShaderProgram::SetUniformValue(ProgramVar var, const std::vector<int>& values) { + return SetUniformValue(var, &values[0], values.size()); +} + +bool ShaderProgram::SetUniformValue(ProgramVar var, + const std::vector<float>& values) { + return SetUniformValue(var, &values[0], values.size()); +} + +bool ShaderProgram::SetUniformValue(const std::string& name, const Value& value) { + if (ValueIsFloat(value)) + return SetUniformValue(GetUniform(name), GetFloatValue(value)); + else if (ValueIsInt(value)) + return SetUniformValue(GetUniform(name), GetIntValue(value)); + else if (ValueIsFloatArray(value)) + return SetUniformValue(GetUniform(name), GetFloatArrayValue(value), GetValueCount(value)); + else if (ValueIsIntArray(value)) + return SetUniformValue(GetUniform(name), GetIntArrayValue(value), GetValueCount(value)); + else + return false; +} + +Value ShaderProgram::GetUniformValue(const std::string& name) { + ProgramVar var = GetUniform(name); + if (CheckUniformValid(var)) { + // Get uniform information + GLint capacity; + GLenum type; + glGetActiveUniform(program_, IndexOfUniform(var), 0, NULL, &capacity, &type, NULL); + if (!GLEnv::CheckGLError("Get Active Uniform")) { + // Get value based on type, and wrap in value object + switch(type) { + case GL_INT: { + int value; + glGetUniformiv(program_, var, &value); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeIntValue(value) + : MakeNullValue(); + } break; + + case GL_INT_VEC2: { + int value[2]; + glGetUniformiv(program_, var, &value[0]); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeIntArrayValue(value, 2) + : MakeNullValue(); + } break; + + case GL_INT_VEC3: { + int value[3]; + glGetUniformiv(program_, var, &value[0]); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeIntArrayValue(value, 3) + : MakeNullValue(); + } break; + + case GL_INT_VEC4: { + int value[4]; + glGetUniformiv(program_, var, &value[0]); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeIntArrayValue(value, 4) + : MakeNullValue(); + } break; + + case GL_FLOAT: { + float value; + glGetUniformfv(program_, var, &value); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatValue(value) + : MakeNullValue(); + } break; + + case GL_FLOAT_VEC2: { + float value[2]; + glGetUniformfv(program_, var, &value[0]); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatArrayValue(value, 2) + : MakeNullValue(); + } break; + + case GL_FLOAT_VEC3: { + float value[3]; + glGetUniformfv(program_, var, &value[0]); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatArrayValue(value, 3) + : MakeNullValue(); + } break; + + case GL_FLOAT_VEC4: { + float value[4]; + glGetUniformfv(program_, var, &value[0]); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatArrayValue(value, 4) + : MakeNullValue(); + } break; + + case GL_FLOAT_MAT2: { + float value[4]; + glGetUniformfv(program_, var, &value[0]); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatArrayValue(value, 4) + : MakeNullValue(); + } break; + + case GL_FLOAT_MAT3: { + float value[9]; + glGetUniformfv(program_, var, &value[0]); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatArrayValue(value, 9) + : MakeNullValue(); + } break; + + case GL_FLOAT_MAT4: { + float value[16]; + glGetUniformfv(program_, var, &value[0]); + return !GLEnv::CheckGLError("GetVariableValue") ? MakeFloatArrayValue(value, 16) + : MakeNullValue(); + } break; + } + } + } + return MakeNullValue(); +} + +GLuint ShaderProgram::IndexOfUniform(ProgramVar var) { + return uniform_indices_[var]; +} + +// Attributes ////////////////////////////////////////////////////////////////////////////////////// +int ShaderProgram::MaxAttributeCount() { + GLint result; + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &result); + return result; +} + +ProgramVar ShaderProgram::GetAttribute(const std::string& name) const { + if (!IsExecutable()) { + ALOGE("ShaderProgram: Error: Must link program before querying attributes!"); + return -1; + } else if (name == PositionAttributeName() || name == TexCoordAttributeName()) { + ALOGW("ShaderProgram: Attempting to overwrite internal vertex attribute '%s'!", name.c_str()); + } + return glGetAttribLocation(program_, name.c_str()); +} + +bool ShaderProgram::SetAttributeValues(ProgramVar var, + const VertexFrame* vbo, + GLenum type, + int components, + int stride, + int offset, + bool normalize) { + if (!CheckVarValid(var)) + return false; + + if (vbo) { + VertexAttrib attrib; + attrib.is_const = false; + attrib.index = var; + attrib.components = components; + attrib.normalized = normalize; + attrib.stride = stride; + attrib.type = type; + attrib.vbo = vbo->GetVboId(); + attrib.offset = offset; + + return StoreAttribute(attrib); + } + return false; +} + +bool ShaderProgram::SetAttributeValues(ProgramVar var, + const uint8_t* data, + GLenum type, + int components, + int stride, + int offset, + bool normalize) { + if (!CheckVarValid(var)) + return false; + + if (data) { + VertexAttrib attrib; + attrib.is_const = false; + attrib.index = var; + attrib.components = components; + attrib.normalized = normalize; + attrib.stride = stride; + attrib.type = type; + attrib.values = data + offset; + + return StoreAttribute(attrib); + } + return false; +} + +bool ShaderProgram::SetAttributeValues(ProgramVar var, + const std::vector<float>& data, + int components) { + return SetAttributeValues(var, &data[0], data.size(), components); +} + +bool ShaderProgram::SetAttributeValues(ProgramVar var, + const float* data, + int total, + int components) { + if (!CheckVarValid(var)) + return false; + + // Make sure the passed data vector has a valid size + if (total % components != 0) { + ALOGE("ShaderProgram: Invalid attribute vector given! Specified a component " + "count of %d, but passed a non-multiple vector of size %d!", + components, total); + return false; + } + + // Copy the data to a buffer we own + float* data_cpy = new float[total]; + memcpy(data_cpy, data, sizeof(float) * total); + + // Store the attribute + VertexAttrib attrib; + attrib.is_const = false; + attrib.index = var; + attrib.components = components; + attrib.normalized = false; + attrib.stride = components * sizeof(float); + attrib.type = GL_FLOAT; + attrib.values = data_cpy; + attrib.owned_data = data_cpy; // Marks this for deletion later on + + return StoreAttribute(attrib); +} + +bool ShaderProgram::StoreAttribute(VertexAttrib attrib) { + if (attrib.index >= 0) { + attrib_values_[attrib.index] = attrib; + return true; + } + return false; +} + +bool ShaderProgram::PushAttributes() { + for (VertexAttribMap::const_iterator iter = attrib_values_.begin(); + iter != attrib_values_.end(); + ++iter) { + const VertexAttrib& attrib = iter->second; + + if (attrib.is_const) { + // Set constant attribute values (must be specified as host values) + if (!attrib.values) + return false; + + const float* values = reinterpret_cast<const float*>(attrib.values); + switch (attrib.components) { + case 1: glVertexAttrib1fv(attrib.index, values); break; + case 2: glVertexAttrib2fv(attrib.index, values); break; + case 3: glVertexAttrib3fv(attrib.index, values); break; + case 4: glVertexAttrib4fv(attrib.index, values); break; + default: return false; + } + glDisableVertexAttribArray(attrib.index); + } else { + // Set per-vertex values + if (attrib.values) { + // Make sure no buffer is bound and set attribute + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glVertexAttribPointer(attrib.index, + attrib.components, + attrib.type, + attrib.normalized, + attrib.stride, + attrib.values); + } else if (attrib.vbo) { + // Bind VBO and set attribute + glBindBuffer(GL_ARRAY_BUFFER, attrib.vbo); + + glVertexAttribPointer(attrib.index, + attrib.components, + attrib.type, + attrib.normalized, + attrib.stride, + reinterpret_cast<const void*>(attrib.offset)); + } else { + return false; + } + glEnableVertexAttribArray(attrib.index); + } + + // Make sure everything worked + if (GLEnv::CheckGLError("Pushing Vertex Attributes")) + return false; + } + + return true; +} + +bool ShaderProgram::PopAttributes() { + // Disable vertex attributes + for (VertexAttribMap::const_iterator iter = attrib_values_.begin(); + iter != attrib_values_.end(); + ++iter) { + glDisableVertexAttribArray(iter->second.index); + } + // Unbind buffer: Very important as this greatly affects what glVertexAttribPointer does! + glBindBuffer(GL_ARRAY_BUFFER, 0); + return !GLEnv::CheckGLError("Popping Vertex Attributes"); +} + +void ShaderProgram::SetVertexCount(int count) { + vertex_count_ = count; +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterfw/native/core/shader_program.h b/media/mca/filterfw/native/core/shader_program.h new file mode 100644 index 0000000..2063175 --- /dev/null +++ b/media/mca/filterfw/native/core/shader_program.h @@ -0,0 +1,553 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_SHADER_PROGRAM_H +#define ANDROID_FILTERFW_CORE_SHADER_PROGRAM_H + +#include <vector> +#include <map> +#include <string> + +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <EGL/egl.h> + +#include "core/gl_env.h" +#include "core/value.h" + +namespace android { +namespace filterfw { + +class GLFrame; +class GLFrameBufferHandle; +class GLTextureHandle; +class Quad; +class VertexFrame; + +typedef GLint ProgramVar; + +// A ShaderProgram is a Program object that holds a GLSL shader implementation. +// It provides functionality for compiling, linking, and executing the shader. +// On top of that, it provides access to the shaders source code, uniforms, +// attributes, and other properties. +// By default a ShaderProgram provides its own vertex shader. However, a custom +// vertex shader may be passed and used instead. +// When implementing a vertex shader, the following attribute names have special +// meaning: +// +// - a_position: The vertex position +// - a_texcoord: The texture cooridnates +// +// The shader program will bind these attributes to the correct values, if they +// are present in the vertex shader source code. +// +// When implementing the fragment shader, the following variable names must be +// defined: +// +// - tex_sampler_<n>: The n'th input texture. For instance, use tex_sampler_0 +// for the first input texture. Must be a uniform sampler2D. +// - v_texcoord: The current texture coordinate. +// +// If more input textures are given than the shader can handle, this will result +// in an error. +// +class ShaderProgram { + public: + // General Functionality /////////////////////////////////////////////////// + // Create a new shader program with the given fragment shader source code. + // A default vertex shader is used, which renders the input texture to a + // rectangular region of the output texture. You can modify the input and + // output regions by using the SetSourceRegion(...) and SetTargetRegion(...) + // (and related) functions below. + // This program will not be executable until you have compiled and linked + // it. + // Note, that the ShaderProgram does NOT take ownership of the GLEnv. The + // caller must make sure the GLEnv stays valid as long as the GLFrame is + // alive. + explicit ShaderProgram(GLEnv* gl_env, const std::string& fragment_shader); + + // Create a new shader program with the given fragment and vertex shader + // source code. This program will not be executable until you have compiled + // and linked it. + // Note, that the ShaderProgram does NOT take ownership of the GLEnv. The + // caller must make sure the GLEnv stays valid as long as the GLFrame is + // alive. + ShaderProgram(GLEnv* gl_env, + const std::string& vertex_shader, + const std::string& fragment_shader); + + // Destructor. + ~ShaderProgram(); + + // Process the given input frames and write the result to the output frame. + // Returns false if there was an error processing. + bool Process(const std::vector<const GLFrame*>& inputs, GLFrame* output); + + // Same as above, but pass GL interfaces rather than frame objects. Use this + // only if you are not working on Frame objects, but rather directly on GL + // textures and FBOs. + bool Process(const std::vector<const GLTextureHandle*>& input, + GLFrameBufferHandle* output); + + // Compile and link the shader source code. Returns true if compilation + // and linkage was successful. Compilation and linking error messages are + // written to the error log. + bool CompileAndLink(); + + // Returns true if this Program has been compiled and linked successfully. + bool IsExecutable() const { + return program_ != 0; + } + + // Returns true if the shader program variable is valid. + static bool IsVarValid(ProgramVar var); + + // Special ShaderPrograms ////////////////////////////////////////////////// + // A (compiled) shader program which assigns the sampled pixels from the + // input to the output. Note that transformations may be applied to achieve + // effects such as cropping, scaling or rotation. + // The caller takes ownership of the result! + static ShaderProgram* CreateIdentity(GLEnv* env); + + // Geometry //////////////////////////////////////////////////////////////// + // These functions modify the source and target regions used during + // rasterization. Note, that these functions will ONLY take effect if + // the default vertex shader is used, or your custom vertex shader defines + // the a_position and a_texcoord attributes. + + // Set the program to read from a subregion of the input frame, given by + // the origin (x, y) and dimensions (width, height). Values are considered + // normalized between 0.0 and 1.0. If this region exceeds the input frame + // dimensions the results are undefined. + void SetSourceRect(float x, float y, float width, float height) ; + + // Set the program to read from a subregion of the input frame, given by + // the passed Quad. Values are considered normalized between 0.0 and 1.0. + // The Quad points are expected to be in the order top-left, top-right, + // bottom-left, bottom-right. + // If this region exceeds the input frame dimensions the results are + // undefined. + void SetSourceRegion(const Quad& quad); + + // Set the program to write to a subregion of the output frame, given by + // the origin (x, y) and dimensions (width, height). Values are considered + // normalized between 0.0 and 1.0. If this region exceeds the output frame + // dimensions the image will be clipped. + void SetTargetRect(float x, float y, float width, float height); + + // Set the program to write to a subregion of the output frame, given by + // the passed Quad. Values are considered normalized between 0.0 and 1.0. + // The Quad points are expected to be in the order top-left, top-right, + // bottom-left, bottom-right. + // If this region exceeds the output frame dimensions the image will be + // clipped. + void SetTargetRegion(const Quad& quad); + + // Uniform Variable access ///////////////////////////////////////////////// + // Note: In order to get and set uniforms, the program must have been + // successfully compiled and linked. Otherwise, the getters will return an + // invalid ProgramVar variable (check with IsVarValid()). + // When setting values, the value type must be match the type of the uniform + // in the shader. For instance, a vector of 3 elements cannot be assigned to + // a vec2. Similarly, an integer value cannot be assigned to a float value. + // Such a type mismatch will result in failure to set the value (which will + // remain untouched). Check the return value of the setters to determine + // success. + + // Returns the maximum number of uniforms supported by this implementation. + static int MaxUniformCount(); + + // Returns a handle to the uniform with the given name, or invalid if no + // such uniform variable exists in the shader. + ProgramVar GetUniform(const std::string& name) const; + + // Set the specified uniform value to the given integer value. Returns true + // if the assignment was successful. + bool SetUniformValue(ProgramVar var, int value); + + // Set the specified uniform value to the given float value. Returns true + // if the assignment was successful. + bool SetUniformValue(ProgramVar var, float value); + + // Set the specified uniform value to the given values. Returns true + // if the assignment was successful. + bool SetUniformValue(ProgramVar var, const int* values, int count); + + // Set the specified uniform value to the given values. Returns true + // if the assignment was successful. + bool SetUniformValue(ProgramVar var, const float* values, int count); + + // Set the specified uniform value to the given vector value. Returns true + // if the assignment was successful. + bool SetUniformValue(ProgramVar var, const std::vector<int>& values); + + // Set the specified uniform value to the given vector value. Returns true + // if the assignment was successful. + bool SetUniformValue(ProgramVar var, const std::vector<float>& values); + + // Generic variable setter, which in the case of GL programs always attempts + // to set the value of a uniform variable with the given name. Only values + // of type float, float array (or vector), and int are supported. + bool SetUniformValue(const std::string& name, const Value& value); + + // Generic variable getter, which in the case of GL programs always attempts + // to get the value of a uniform variable with the given name. + Value GetUniformValue(const std::string& name); + + // Returns the default name of the input texture uniform variable for the + // given input index. + static std::string InputTextureUniformName(int index); + + // Attribute access //////////////////////////////////////////////////////// + // Note: In order to get and set attributes, the program must have been + // successfully compiled and linked. Otherwise, the getters will return an + // invalid ProgramVar variable (check with IsVarValid()). Constant attribute + // values must be floats. Attribute pointers must be associated with a + // specific type, which can be any of the following: + // GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_FLOAT, + // GL_FIXED, GL_HALF_FLOAT_OES. + // When storing vertex data, it is recommended to use VertexFrames when + // possible as these will be kept in GPU memory, and no copying of vertex + // attributes between system and GPU memory needs to take place. + + // Returns the maximum number of attributes supported by this + // implementation. + static int MaxAttributeCount(); + + // Returns a handle to the attribute with the given name, or invalid if no + // such attribute exists in the vertex shader. + ProgramVar GetAttribute(const std::string& name) const; + + // Set an attribute value that will be constant for each vertex. Returns + // true if the assignment was successful. + bool SetConstAttributeValue(ProgramVar var, float value); + + // Set an attribute vector value that will be constant for each vertex. + // Returns true if the assignment was successful. + bool SetConstAttributeValue(ProgramVar var, const std::vector<float>& value); + + // Set attribute values that differ across vertexes, using a VertexFrame. + // This is the recommended method of specifying vertex data, that does not + // change from iteration to iteration. The parameters are as follows: + // var: The shader variable to bind the values to. + // data: The vertex frame which holds the vertex data. This may be a + // superset of the data required for this particular vertex. Use the + // offset and stride to select the correct data portion. + // type: The type of the data values. This may differ from the type of the + // shader variables. See the normalize flag on how values are + // converted. + // components: The number of components per value. Valid values are 1-4. + // stride: The delta of one element to the next in bytes. + // offset: The offset of the first element. + // normalize: True, if not float values should be normalized to the range + // 0-1, when converted to a float. + // Returns true, if the assignment was successful. + bool SetAttributeValues(ProgramVar var, + const VertexFrame* data, + GLenum type, + int components, + int stride, + int offset, + bool normalize); + + // Set attribute values that differ across vertexes, using a data buffer. + // This is the recommended method of specifying vertex data, if your data + // changes often. Note that this data may need to be copied to GPU memory + // for each render pass. Please see above for a description of the + // parameters. + // Note: The data passed here MUST be valid until all executions of this + // Program instance have been completed! + bool SetAttributeValues(ProgramVar var, + const uint8_t* data, + GLenum type, + int components, + int stride, + int offset, + bool normalize); + + // Convenience method for setting vertex values using a vector of floats. + // The components parameter specifies how many elements per variable should + // be assigned (The variable must be able to fit the number of components). + // It must be a value between 1 and 4. + // While this method is not as flexible as the methods above, this can be + // used when more advanced methods are not necessary. Note, that if your + // vertex data does not change, it is recommended to use a VertexFrame. + bool SetAttributeValues(ProgramVar var, + const std::vector<float>& data, + int components); + + // Same as above, but using a float pointer instead of vector. Pass the + // total number of elements in total. + bool SetAttributeValues(ProgramVar var, + const float* data, + int total, + int components); + + // By default, rendering only uses the first 4 vertices. You should only + // adjust this value if you are providing your own vertex attributes with + // a count unequal to 4. Adjust this value before calling Process(). + void SetVertexCount(int count); + + // Returns the default name of the attribute used to hold the texture + // coordinates. Use this when you need to access the texture coordinate + // attribute of the shader's default vertex shader. + static const std::string& TexCoordAttributeName() { + static std::string s_texcoord("a_texcoord"); + return s_texcoord; + } + + // Returns the default name of the attribute used to hold the output + // coordinates. Use this when you need to access the output coordinate + // attribute of the shader's default vertex shader. + static const std::string& PositionAttributeName() { + static std::string s_position("a_position"); + return s_position; + } + + // Rendering /////////////////////////////////////////////////////////////// + // Set the draw mode, which can be any of GL_POINTS, GL_LINES, + // GL_LINE_STRIP, GL_LINE_LOOP, GL_TRIANGLES, GL_TRIANGLE_STRIP, + // GL_TRIANGLE_FAN. The default is GL_TRIANGLE_STRIP. + // Warning: Do NOT change this if you are not specifying your own vertex + // data with SetAttributeValues(...). + void SetDrawMode(GLenum mode); + + // If you are doing your own drawing you should call this before beginning + // to draw. This will activate the program, push all used attributes, and + // clear the frame if requested. You do not need to call this if you are + // not doing your own GL drawing! + bool BeginDraw(); + + // Render a single frame with the given input textures. You may override + // this, if you need custom rendering behavior. However, you must take + // care of the following things when overriding: + // - Use the correct program (e.g. by calling UseProgram()). + // - Bind the given textures + // - Bind vertex attributes + // - Draw + bool RenderFrame(const std::vector<GLuint>& textures, + const std::vector<GLenum>& targets); + + // Pass true to clear the output frame before rendering. The color used + // to clear is set in SetClearColor(). + void SetClearsOutput(bool clears); + + // Set the color used to clear the output frame before rendering. You + // must activate clearing by calling SetClearsOutput(true). + void SetClearColor(float red, float green, float blue, float alpha); + + // Set the number of tiles to split rendering into. Higher tile numbers + // will affect performance negatively, but will allow other GPU threads + // to render more frequently. Defaults to 1, 1. + void SetTileCounts(int x_count, int y_count); + + // Enable or Disable Blending + // Set to true to enable, false to disable. + void SetBlendEnabled(bool enable) { + blending_ = enable; + } + + // Specify pixel arithmetic for blending + // The values of sfactor and dfactor can be: + // GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, + // GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, + // GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA_SATURATE + // Default values for blending are set to: + // sfactor = GL_SRC_ALPHA + // dfactor = GL_ONE_MINUS_SRC_ALPHA + void SetBlendFunc(int sfactor, int dfactor) { + sfactor_ = sfactor; + dfactor_ = dfactor; + } + + // Accessing the Compiled Program ////////////////////////////////////////// + // Use the compiled and linked program for rendering. You should not need + // to call this, unless you are implementing your own rendering method. + bool UseProgram(); + + // Other Properties //////////////////////////////////////////////////////// + // Returns the maximum number of varyings supported by this implementation. + static int MaxVaryingCount(); + + // Returns the maximum number of texture units supported by this + // implementation. + static int MaxTextureUnits(); + + // Lower level functionality /////////////////////////////////////////////// + // Compile the shader with the given source. The shader_type must be either + // GL_VERTEX_SHADER or GL_FRAGMENT_SHADER. + static GLuint CompileShader(GLenum shader_type, const char* source); + + // Link the compiled shader objects and return the resulting program. + static GLuint LinkProgram(GLuint* shaders, GLuint count); + + // Returns the lowest texture unit that will be used to bind textures. + GLuint BaseTextureUnit() const { + return base_texture_unit_; + } + + // Sets the lowest texture unit that will be used to bind textures. The + // default value is GL_TEXTURE0. + void SetBaseTextureUnit(GLuint texture_unit) { + base_texture_unit_ = texture_unit; + } + + private: + // Structure to store vertex attribute data. + struct VertexAttrib { + bool is_const; + int index; + bool normalized; + int stride; + int components; + int offset; + GLenum type; + GLuint vbo; + const void* values; + float* owned_data; + + VertexAttrib(); + }; + typedef std::map<ProgramVar, VertexAttrib> VertexAttribMap; + + struct RGBAColor { + float red; + float green; + float blue; + float alpha; + + RGBAColor() : red(0), green(0), blue(0), alpha(1) { + } + }; + + // Scans for all uniforms in the shader and creates index -> id map. + void ScanUniforms(); + + // Returns the index of the given uniform. The caller must make sure + // that the variable id passed is valid. + GLuint IndexOfUniform(ProgramVar var); + + // Binds the given input textures. + bool BindInputTextures(const std::vector<GLuint>& textures, + const std::vector<GLenum>& targets); + + // Sets the default source and target coordinates. + void SetDefaultCoords(); + + // Pushes the specified coordinates to the shader attribute. + bool PushCoords(ProgramVar attr, float* coords); + + // Pushes the source coordinates. + bool PushSourceCoords(float* coords); + + // Pushes the target coordinates. + bool PushTargetCoords(float* coords); + + // Performs (simple) GL drawing. + bool Draw(); + + // Performs tiled GL drawing. + bool DrawTiled(); + + // Yields to other GPU threads. + void Yield(); + + // Helper method to assert that the variable value passed has the correct + // total size. + static bool CheckValueCount(const std::string& var_type, + const std::string& var_name, + int expected_count, + int components, + int value_size); + + // Helper method to assert that the variable value passed has a size, that + // is compatible with the type size (must be divisible). + static bool CheckValueMult(const std::string& var_type, + const std::string& var_name, + int components, + int value_size); + + // Checks that the variable is valid. Logs an error and returns false if + // not. + static bool CheckVarValid(ProgramVar var); + + // Returns true if the uniform specified by var is an active uniform in the + // program. + bool CheckUniformValid(ProgramVar var); + + // Store an attribute to use when rendering. + bool StoreAttribute(VertexAttrib attrib); + + // Push all assigned attributes before rendering. + bool PushAttributes(); + + // Pop all assigned attributes after rendering. + bool PopAttributes(); + + // The shader source code + std::string fragment_shader_source_; + std::string vertex_shader_source_; + + // The compiled shaders and linked program + GLuint fragment_shader_; + GLuint vertex_shader_; + GLuint program_; + + // The GL environment this shader lives in. + GLEnv* gl_env_; + + // The lowest texture unit this program will use + GLuint base_texture_unit_; + + // The current source and target coordinates to render from/to. + float* source_coords_; + float* target_coords_; + + // True, if the program has control over both source and target coordinates. + bool manage_coordinates_; + + // The number of tiles to split rendering into. + int tile_x_count_; + int tile_y_count_; + + // List of attribute data that we need to set before rendering + VertexAttribMap attrib_values_; + + // The number of vertices to render + int vertex_count_; + + // The draw mode used during rendering + GLenum draw_mode_; + + // True, iff the output frame is cleared before rendering + bool clears_; + + // The color used to clear the output frame. + RGBAColor clear_color_; + + // Set to true to enable blending. + bool blending_; + int sfactor_; + int dfactor_; + + // Map from uniform ids to indices + std::map<ProgramVar, GLuint> uniform_indices_; +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_SHADER_PROGRAM_H diff --git a/media/mca/filterfw/native/core/statistics.cpp b/media/mca/filterfw/native/core/statistics.cpp new file mode 100644 index 0000000..6f7fee7 --- /dev/null +++ b/media/mca/filterfw/native/core/statistics.cpp @@ -0,0 +1,57 @@ +/* + * 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. + */ + +#include "core/statistics.h" + +#include <math.h> + +namespace android { +namespace filterfw { + +IncrementalGaussian::IncrementalGaussian() + : n_(0), + sum_x_(0.0f), + sum_x2_(0.0f), + mean_(0.0f), + var_(0.0f), + exp_denom_(0.0f), + pdf_denom_(0.0f) { +} + +void IncrementalGaussian::Add(float value) { + ++n_; + sum_x_ += value; + sum_x2_ += value * value; + + mean_ = sum_x_ / n_; + var_ = sum_x2_ / n_ - mean_ * mean_; + + exp_denom_ = 2.0f * var_; + pdf_denom_ = sqrtf(M_PI * exp_denom_); +} + +float IncrementalGaussian::Std() const { + return sqrtf(var_); +} + +float IncrementalGaussian::Pdf(float value) const { + if (var_ == 0.0f) { return n_ > 0 ? value == mean_ : 0.0f; } + const float diff = value - mean_; + return expf(-diff * diff / exp_denom_) / pdf_denom_; +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterfw/native/core/statistics.h b/media/mca/filterfw/native/core/statistics.h new file mode 100644 index 0000000..ce73b2b --- /dev/null +++ b/media/mca/filterfw/native/core/statistics.h @@ -0,0 +1,71 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_STATISTICS_H +#define ANDROID_FILTERFW_CORE_STATISTICS_H + +namespace android { +namespace filterfw { + +// An incrementally-constructed Normal distribution. +class IncrementalGaussian { + public: + IncrementalGaussian(); + + void Add(float value); + + float NumSamples() const { return n_; } + float Mean() const { return mean_; } + float Var() const { return var_; } + float Std() const; + float Pdf(float value) const; + + private: + int n_; + float sum_x_; + float sum_x2_; + float mean_; + float var_; + float exp_denom_; + float pdf_denom_; +}; + +// Discrete-time implementation of a simple RC low-pass filter: +// exponentially-weighted moving average. +class RCFilter { + public: + explicit RCFilter(float gain) + : gain_(gain), n_(0), value_(0.0f) {} + + void Add(float measurement) { + value_ = n_++ ? gain_ * measurement + (1.0f - gain_) * value_ : measurement; + } + + void Reset() { n_ = 0; } + + int NumMeasurements() const { return n_; } + float Output() const { return value_; } + + private: + float gain_; + int n_; + float value_; +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_STATISTICS_H diff --git a/media/mca/filterfw/native/core/time_util.cpp b/media/mca/filterfw/native/core/time_util.cpp new file mode 100644 index 0000000..c86c80d --- /dev/null +++ b/media/mca/filterfw/native/core/time_util.cpp @@ -0,0 +1,92 @@ +/* + * 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. + */ + +#include "base/logging.h" +#include "base/utilities.h" + +#include "core/time_util.h" + +#include <map> +#include <string> +#include <sys/time.h> + +namespace android { +namespace filterfw { + +uint64_t getTimeUs() { + static long basesec; + struct timeval tv; + uint64_t nowtime; + gettimeofday(&tv, 0); + if (basesec == 0) { + basesec = tv.tv_sec; + } + nowtime = (uint64_t)(tv.tv_sec - basesec) * (uint64_t)1000000 + + (uint64_t)tv.tv_usec; + return nowtime; +} + +const uint64_t NamedStopWatch::kDefaultLoggingPeriodInFrames = 100; + +NamedStopWatch::NamedStopWatch(const std::string& name) + : mName(name), + mLoggingPeriodInFrames(kDefaultLoggingPeriodInFrames), + mStartUSec(0), + mNumCalls(0), + mTotalUSec(0) { +} + +void NamedStopWatch::Start() { + mStartUSec = getTimeUs(); +} + +void NamedStopWatch::Stop() { + if (!mStartUSec) { + return; + } + uint64_t stopUSec = getTimeUs(); + if (stopUSec > mStartUSec) { + ++mNumCalls; + mTotalUSec += stopUSec - mStartUSec; + if (mNumCalls % mLoggingPeriodInFrames == 0) { + const float mSec = TotalUSec() * 1.0E-3f / NumCalls(); + ALOGE("%s: %f ms", Name().c_str(), mSec); + } + } + mStartUSec = 0; +} + +namespace { +static NamedStopWatch* GetWatchForName(const string& watch_name) { + // TODO: this leaks the NamedStopWatch objects. Replace it with a + // singleton to avoid that and make it thread safe. + static map<string, NamedStopWatch*> watches; + NamedStopWatch* watch = FindPtrOrNull(watches, watch_name); + if (!watch) { + watch = new NamedStopWatch(watch_name); + watches[watch_name] = watch; + } + return watch; +}; +} // namespace + +ScopedTimer::ScopedTimer(const string& stop_watch_name) { + mWatch = GetWatchForName(stop_watch_name); + mWatch->Start(); +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterfw/native/core/time_util.h b/media/mca/filterfw/native/core/time_util.h new file mode 100644 index 0000000..3cf2ec9 --- /dev/null +++ b/media/mca/filterfw/native/core/time_util.h @@ -0,0 +1,69 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_TIME_UTIL_H +#define ANDROID_FILTERFW_CORE_TIME_UTIL_H + +#include <string> +#include <utils/RefBase.h> + +#define LOG_MFF_RUNNING_TIMES 0 + +namespace android { +namespace filterfw { + +uint64_t getTimeUs(); + +class NamedStopWatch : public RefBase { + public: + static const uint64_t kDefaultLoggingPeriodInFrames; + + explicit NamedStopWatch(const string& name); + void Start(); + void Stop(); + + void SetName(const string& name) { mName = name; } + void SetLoggingPeriodInFrames(uint64_t numFrames) { + mLoggingPeriodInFrames = numFrames; + } + + const string& Name() const { return mName; } + uint64_t NumCalls() const { return mNumCalls; } + uint64_t TotalUSec() const { return mTotalUSec; } + + private: + string mName; + uint64_t mLoggingPeriodInFrames; + uint64_t mStartUSec; + uint64_t mNumCalls; + uint64_t mTotalUSec; +}; + +class ScopedTimer { + public: + explicit ScopedTimer(const string& stop_watch_name); + explicit ScopedTimer(NamedStopWatch* watch) + : mWatch(watch) { mWatch->Start(); } + ~ScopedTimer() { mWatch->Stop(); } + + private: + NamedStopWatch* mWatch; +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_TIME_UTIL_H diff --git a/media/mca/filterfw/native/core/value.cpp b/media/mca/filterfw/native/core/value.cpp new file mode 100644 index 0000000..04bf0ef --- /dev/null +++ b/media/mca/filterfw/native/core/value.cpp @@ -0,0 +1,236 @@ +/* + * 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. + */ + +#include <stddef.h> +#include <stdlib.h> + +#include "value.h" + +#define NULL_VALUE_TYPE 0 +#define INT_VALUE_TYPE 1 +#define FLOAT_VALUE_TYPE 2 +#define STRING_VALUE_TYPE 3 +#define BUFFER_VALUE_TYPE 4 +#define MUTABLE_BUFFER_VALUE_TYPE 5 +#define INT_ARRAY_VALUE_TYPE 6 +#define FLOAT_ARRAY_VALUE_TYPE 7 + +// Templated versions ////////////////////////////////////////////////////////////////////////////// +template<typename POD, int TYPEID> +POD GetPODValue(Value value) { + return value.type == TYPEID ? *reinterpret_cast<POD*>(value.value) : POD(); +} + +template<typename PTR, int TYPEID> +PTR GetPtrValue(Value value) { + return value.type == TYPEID ? reinterpret_cast<PTR>(value.value) : NULL; +} + +template<typename POD, int TYPEID> +Value MakePODValue(POD value) { + Value result; + result.type = TYPEID; + result.value = malloc(sizeof(POD)); + result.count = 1; + *reinterpret_cast<POD*>(result.value) = value; + return result; +} + +template<typename BASE, int TYPEID> +Value MakePtrValue(const BASE* values, int count) { + Value result; + result.type = TYPEID; + result.value = malloc(sizeof(BASE) * count); + memcpy(result.value, values, sizeof(BASE) * count); + result.count = count; + return result; +} + +template<typename POD, int TYPEID> +int SetPODValue(Value* value, POD new_value) { + if (value->type == NULL_VALUE_TYPE) { + value->type = TYPEID; + value->value = malloc(sizeof(POD)); + value->count = 1; + } + if (value->type == TYPEID) { + *reinterpret_cast<POD*>(value->value) = new_value; + return 1; + } + return 0; +} + +template<typename BASE, int TYPEID> +int SetPtrValue(Value* value, const BASE* new_values, int count) { + if (value->type == NULL_VALUE_TYPE) { + value->type = TYPEID; + value->value = malloc(sizeof(BASE) * count); + value->count = count; + } + if (value->type == TYPEID && value->count == count) { + memcpy(value->value, new_values, sizeof(BASE) * count); + return 1; + } + return 0; +} + +// C Wrappers ////////////////////////////////////////////////////////////////////////////////////// +int GetIntValue(Value value) { + return GetPODValue<int, INT_VALUE_TYPE>(value); +} + +float GetFloatValue(Value value) { + return GetPODValue<float, FLOAT_VALUE_TYPE>(value); +} + +const char* GetStringValue(Value value) { + return GetPtrValue<const char*, STRING_VALUE_TYPE>(value); +} + +const char* GetBufferValue(Value value) { + return (value.type == BUFFER_VALUE_TYPE || value.type == MUTABLE_BUFFER_VALUE_TYPE) + ? (const char*)value.value + : NULL; +} + +char* GetMutableBufferValue(Value value) { + return GetPtrValue<char*, MUTABLE_BUFFER_VALUE_TYPE>(value); +} + +int* GetIntArrayValue(Value value) { + return GetPtrValue<int*, INT_ARRAY_VALUE_TYPE>(value); +} + +float* GetFloatArrayValue(Value value) { + return GetPtrValue<float*, FLOAT_ARRAY_VALUE_TYPE>(value); +} + +int ValueIsNull(Value value) { + return value.type == NULL_VALUE_TYPE; +} + +int ValueIsInt(Value value) { + return value.type == INT_VALUE_TYPE; +} + +int ValueIsFloat(Value value) { + return value.type == FLOAT_VALUE_TYPE; +} + +int ValueIsString(Value value) { + return value.type == STRING_VALUE_TYPE; +} + +int ValueIsBuffer(Value value) { + return value.type == BUFFER_VALUE_TYPE || value.type == MUTABLE_BUFFER_VALUE_TYPE; +} + +int ValueIsIntArray(Value value) { + return value.type == INT_ARRAY_VALUE_TYPE; +} + +int ValueIsFloatArray(Value value) { + return value.type == FLOAT_ARRAY_VALUE_TYPE; +} + +Value MakeNullValue() { + Value result; + result.type = NULL_VALUE_TYPE; + result.value = NULL; + result.count = 0; + return result; +} + +Value MakeIntValue(int value) { + return MakePODValue<int, INT_VALUE_TYPE>(value); +} + +Value MakeFloatValue(float value) { + return MakePODValue<float, FLOAT_VALUE_TYPE>(value); +} + +Value MakeStringValue(const char* value) { + return MakePtrValue<char, STRING_VALUE_TYPE>(value, strlen(value) + 1); +} + +Value MakeBufferValue(const char* buffer, int size) { + return MakePtrValue<char, BUFFER_VALUE_TYPE>(buffer, size); +} + +Value MakeBufferValueNoCopy(const char* buffer, int size) { + Value result; + result.type = BUFFER_VALUE_TYPE; + result.value = (void*)buffer; + result.count = size; + return result; +} + +Value MakeMutableBufferValue(const char* buffer, int size) { + return MakePtrValue<const char, MUTABLE_BUFFER_VALUE_TYPE>(buffer, size); +} + +Value MakeMutableBufferValueNoCopy(char* buffer, int size) { + Value result; + result.type = MUTABLE_BUFFER_VALUE_TYPE; + result.value = (void*)buffer; + result.count = size; + return result; +} + +Value MakeIntArrayValue(const int* values, int count) { + return MakePtrValue<int, INT_ARRAY_VALUE_TYPE>(values, count); +} + +Value MakeFloatArrayValue(const float* values, int count) { + return MakePtrValue<float, FLOAT_ARRAY_VALUE_TYPE>(values, count); +} + +int SetIntValue(Value* value, int new_value) { + return SetPODValue<int, INT_VALUE_TYPE>(value, new_value); +} + +int SetFloatValue(Value* value, float new_value) { + return SetPODValue<float, FLOAT_VALUE_TYPE>(value, new_value); +} + +int SetStringValue(Value* value, const char* new_value) { + return SetPtrValue<char, STRING_VALUE_TYPE>(value, new_value, strlen(new_value) + 1); +} + +int SetMutableBufferValue(Value* value, const char* new_data, int size) { + return SetPtrValue<char, MUTABLE_BUFFER_VALUE_TYPE>(value, new_data, size); +} + +int SetIntArrayValue(Value* value, const int* new_values, int count) { + return SetPtrValue<int, INT_ARRAY_VALUE_TYPE>(value, new_values, count); +} + +int SetFloatArrayValue(Value* value, const float* new_values, int count) { + return SetPtrValue<float, FLOAT_ARRAY_VALUE_TYPE>(value, new_values, count); +} + +int GetValueCount(Value value) { + return value.count; +} + +void ReleaseValue(Value* value) { + if (value && value->value) { + free(value->value); + value->value = NULL; + value->type = NULL_VALUE_TYPE; + } +} + diff --git a/media/mca/filterfw/native/core/value.h b/media/mca/filterfw/native/core/value.h new file mode 100644 index 0000000..37e8800 --- /dev/null +++ b/media/mca/filterfw/native/core/value.h @@ -0,0 +1,80 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_VALUE_H +#define ANDROID_FILTERFW_CORE_VALUE_H + +#ifdef __cplusplus +extern "C" { +#endif + +// TODO: As this is no longer part of the proposed NDK, should we make this object-oriented (C++) +// instead? We can also probably clean this up a bit. + +// TODO: Change this to an opaque handle? +typedef struct { + void* value; + int type; + int count; +} Value; + +// TODO: Probably should make these const Value*? +int GetIntValue(Value value); +float GetFloatValue(Value value); +const char* GetStringValue(Value value); +const char* GetBufferValue(Value value); +char* GetMutableBufferValue(Value value); +int* GetIntArrayValue(Value value); +float* GetFloatArrayValue(Value value); + +// TODO: Probably should make these const Value*? +int ValueIsNull(Value value); +int ValueIsInt(Value value); +int ValueIsFloat(Value value); +int ValueIsString(Value value); +int ValueIsBuffer(Value value); +int ValueIsMutableBuffer(Value value); +int ValueIsIntArray(Value value); +int ValueIsFloatArray(Value value); + +Value MakeNullValue(); +Value MakeIntValue(int value); +Value MakeFloatValue(float value); +Value MakeStringValue(const char* value); +Value MakeBufferValue(const char* data, int size); +Value MakeBufferValueNoCopy(const char* data, int size); +Value MakeMutableBufferValue(const char* data, int size); +Value MakeMutableBufferValueNoCopy(char* data, int size); +Value MakeIntArrayValue(const int* values, int count); +Value MakeFloatArrayValue(const float* values, int count); + +// Note: These only alloc if value is Null! Otherwise they overwrite, so data must fit! +int SetIntValue(Value* value, int new_value); +int SetFloatValue(Value* value, float new_value); +int SetStringValue(Value* value, const char* new_value); +int SetMutableBufferValue(Value* value, const char* new_data, int size); +int SetIntArrayValue(Value* value, const int* new_values, int count); +int SetFloatArrayValue(Value* value, const float* new_values, int count); + +int GetValueCount(Value value); + +void ReleaseValue(Value* value); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // ANDROID_FILTERFW_FILTER_VALUE_H diff --git a/media/mca/filterfw/native/core/vertex_frame.cpp b/media/mca/filterfw/native/core/vertex_frame.cpp new file mode 100644 index 0000000..822573f --- /dev/null +++ b/media/mca/filterfw/native/core/vertex_frame.cpp @@ -0,0 +1,84 @@ +/* + * 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. + */ + +#include "base/logging.h" + +#include "core/gl_env.h" +#include "core/vertex_frame.h" + +#include <GLES2/gl2ext.h> +#include <EGL/egl.h> + +namespace android { +namespace filterfw { + +// GL Extensions that are dynamically looked up at runtime +static PFNGLMAPBUFFEROESPROC GLMapBufferOES = NULL; +static PFNGLUNMAPBUFFEROESPROC GLUnmapBufferOES = NULL; + +VertexFrame::VertexFrame(int size) + : vbo_(0), + size_(size) { +} + +VertexFrame::~VertexFrame() { + glDeleteBuffers(1, &vbo_); +} + +bool VertexFrame::CreateBuffer() { + glGenBuffers(1, &vbo_); + return !GLEnv::CheckGLError("Generating VBO"); +} + +bool VertexFrame::WriteData(const uint8_t* data, int size) { + // Create buffer if not created already + const bool first_upload = !HasVBO(); + if (first_upload && !CreateBuffer()) { + ALOGE("VertexFrame: Could not create vertex buffer!"); + return false; + } + + // Upload the data + glBindBuffer(GL_ARRAY_BUFFER, vbo_); + if (GLEnv::CheckGLError("VBO Bind Buffer")) + return false; + + if (first_upload && size == size_) + glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW); + else if (!first_upload && size <= size_) + glBufferSubData(GL_ARRAY_BUFFER, 0, size, data); + else { + ALOGE("VertexFrame: Attempting to upload more data (%d bytes) than fits " + "inside the vertex frame (%d bytes)!", size, size_); + return false; + } + + // Make sure it worked + if (GLEnv::CheckGLError("VBO Data Upload")) + return false; + + // Subsequent uploads are now bound to the size given here + size_ = size; + + return true; +} + +int VertexFrame::Size() const { + return size_; +} + +} // namespace filterfw +} // namespace android diff --git a/media/mca/filterfw/native/core/vertex_frame.h b/media/mca/filterfw/native/core/vertex_frame.h new file mode 100644 index 0000000..1205096 --- /dev/null +++ b/media/mca/filterfw/native/core/vertex_frame.h @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#ifndef ANDROID_FILTERFW_CORE_VERTEXFRAME_H +#define ANDROID_FILTERFW_CORE_VERTEXFRAME_H + +#include <GLES2/gl2.h> + +namespace android { +namespace filterfw { + +// A VertexFrame stores vertex attribute data in a VBO. Unlike other frames, +// you often create instances of VertexFrame yourself, to pass vertex data to +// a ShaderProgram. Note, that any kind of reading from VertexFrames is NOT +// supported. Once data is uploaded to a VertexFrame, it cannot be read from +// again. +class VertexFrame { + public: + // Create a VertexFrame of the specified size (in bytes). + explicit VertexFrame(int size); + + ~VertexFrame(); + + // Upload the given data to the vertex buffer. The size must match the size + // passed in the constructor for the first upload. Subsequent uploads must + // be able to fit within the allocated space (i.e. size must not exceed the + // frame's size). + bool WriteData(const uint8_t* data, int size); + + // The size of the vertex buffer in bytes. + int Size() const; + + // Return the id of the internal VBO. Returns 0 if no VBO has been + // generated yet. The internal VBO is generated the first time data is + // uploaded. + GLuint GetVboId() const { + return vbo_; + } + + // Returns true if the frame contains an allocated VBO. + bool HasBuffer() const { + return vbo_ != 0; + } + + private: + // Create the VBO + bool CreateBuffer(); + + // Returns true if the VBO has been created. + bool HasVBO() const { + return vbo_ != 0; + } + + // The internal VBO handle + GLuint vbo_; + + // The size of this frame in bytes + int size_; +}; + +} // namespace filterfw +} // namespace android + +#endif // ANDROID_FILTERFW_CORE_VERTEXFRAME_H diff --git a/media/mca/filterfw/native/libfilterfw.mk b/media/mca/filterfw/native/libfilterfw.mk new file mode 100644 index 0000000..4e88e6f --- /dev/null +++ b/media/mca/filterfw/native/libfilterfw.mk @@ -0,0 +1,33 @@ +# 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. +# + +# Add include paths for native code. +FFW_PATH := $(call my-dir) + +# Uncomment the requirements below, once we need them: + +# STLport +include external/stlport/libstlport.mk + +# Neven FaceDetect SDK +#LOCAL_C_INCLUDES += external/neven/FaceRecEm/common/src/b_FDSDK \ +# external/neven/FaceRecEm/common/src \ +# external/neven/Embedded/common/conf \ +# external/neven/Embedded/common/src \ +# external/neven/unix/src + +# Finally, add this directory +LOCAL_C_INCLUDES += $(FFW_PATH) + |