| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.media.filterfw; |
| |
| import android.annotation.TargetApi; |
| import android.app.Activity; |
| import android.app.ActivityManager; |
| import android.content.Context; |
| import android.content.pm.ConfigurationInfo; |
| import android.os.Build; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.renderscript.RenderScript; |
| import android.util.Log; |
| import android.view.SurfaceHolder; |
| import android.view.SurfaceView; |
| import android.view.ViewGroup; |
| |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| /** |
| * The MffContext holds the state and resources of a Mobile Filter Framework processing instance. |
| * Though it is possible to create multiple MffContext instances, typical applications will rely on |
| * a single MffContext to perform all processing within the Mobile Filter Framework. |
| * |
| * The MffContext class declares two methods {@link #onPause()} and {@link #onResume()}, that are |
| * typically called when the application activity is paused and resumed. This will take care of |
| * halting any processing in the context, and releasing resources while the activity is paused. |
| */ |
| public class MffContext { |
| |
| /** |
| * Class to hold configuration information for MffContexts. |
| */ |
| public static class Config { |
| /** |
| * Set to true, if this context will make use of the camera. |
| * If your application does not require the camera, the context does not guarantee that |
| * a camera is available for streaming. That is, you may only use a CameraStreamer if |
| * the context's {@link #isCameraStreamingSupported()} returns true. |
| */ |
| public boolean requireCamera = true; |
| |
| /** |
| * Set to true, if this context requires OpenGL. |
| * If your application does not require OpenGL, the context does not guarantee that OpenGL |
| * is available. That is, you may only use OpenGL (within filters running in this context) |
| * if the context's {@link #isOpenGLSupported()} method returns true. |
| */ |
| public boolean requireOpenGL = true; |
| |
| /** |
| * On older Android versions the Camera may need a SurfaceView to render into in order to |
| * function. You may specify a dummy SurfaceView here if you do not want the context to |
| * create its own view. Note, that your view may or may not be used. You cannot rely on |
| * your dummy view to be used by the Camera. If you pass null, no dummy view will be used. |
| * In this case your application may not run correctly on older devices if you use the |
| * camera. This flag has no effect if you do not require the camera. |
| */ |
| public SurfaceView dummySurface = null; |
| |
| /** Force MFF to not use OpenGL in its processing. */ |
| public boolean forceNoGL = false; |
| } |
| |
| static private class State { |
| public static final int STATE_RUNNING = 1; |
| public static final int STATE_PAUSED = 2; |
| public static final int STATE_DESTROYED = 3; |
| |
| public int current = STATE_RUNNING; |
| } |
| |
| /** The application context. */ |
| private Context mApplicationContext = null; |
| |
| /** The set of filter graphs within this context */ |
| private Set<FilterGraph> mGraphs = new HashSet<FilterGraph>(); |
| |
| /** The set of graph runners within this context */ |
| private Set<GraphRunner> mRunners = new HashSet<GraphRunner>(); |
| |
| /** True, if the context preserves frames when paused. */ |
| private boolean mPreserveFramesOnPause = false; |
| |
| /** The shared CameraStreamer that streams camera frames to CameraSource filters. */ |
| private CameraStreamer mCameraStreamer = null; |
| |
| /** The current context state. */ |
| private State mState = new State(); |
| |
| /** A dummy SurfaceView that is required for Camera operation on older devices. */ |
| private SurfaceView mDummySurfaceView = null; |
| |
| /** Handler to execute code in the context's thread, such as issuing callbacks. */ |
| private Handler mHandler = null; |
| |
| /** Flag whether OpenGL ES 2 is supported in this context. */ |
| private boolean mGLSupport; |
| |
| /** Flag whether camera streaming is supported in this context. */ |
| private boolean mCameraStreamingSupport; |
| |
| /** RenderScript base master class. */ |
| private RenderScript mRenderScript; |
| |
| /** |
| * Creates a new MffContext with the default configuration. |
| * |
| * An MffContext must be attached to a Context object of an application. You may create |
| * multiple MffContexts, however data between them cannot be shared. The context must be |
| * created in a thread with a Looper (such as the main/UI thread). |
| * |
| * On older versions of Android, the MffContext may create a visible dummy view for the |
| * camera to render into. This is a 1x1 SurfaceView that is placed into the top-left corner. |
| * |
| * @param context The application context to attach the MffContext to. |
| */ |
| public MffContext(Context context) { |
| init(context, new Config()); |
| } |
| |
| /** |
| * Creates a new MffContext with the specified configuration. |
| * |
| * An MffContext must be attached to a Context object of an application. You may create |
| * multiple MffContexts, however data between them cannot be shared. The context must be |
| * created in a thread with a Looper (such as the main/UI thread). |
| * |
| * On older versions of Android, the MffContext may create a visible dummy view for the |
| * camera to render into. This is a 1x1 SurfaceView that is placed into the top-left corner. |
| * You may alternatively specify your own SurfaceView in the configuration. |
| * |
| * @param context The application context to attach the MffContext to. |
| * @param config The configuration to use. |
| * |
| * @throws RuntimeException If no context for the requested configuration could be created. |
| */ |
| public MffContext(Context context, Config config) { |
| init(context, config); |
| } |
| |
| /** |
| * Put all processing in the context on hold. |
| * This is typically called from your application's <code>onPause()</code> method, and will |
| * stop all running graphs (closing their filters). If the context does not preserve frames on |
| * pause (see {@link #setPreserveFramesOnPause(boolean)}) all frames attached to this context |
| * are released. |
| */ |
| public void onPause() { |
| synchronized (mState) { |
| if (mState.current == State.STATE_RUNNING) { |
| if (mCameraStreamer != null) { |
| mCameraStreamer.halt(); |
| } |
| stopRunners(true); |
| mState.current = State.STATE_PAUSED; |
| } |
| } |
| } |
| |
| /** |
| * Resumes the processing in this context. |
| * This is typically called from the application's <code>onResume()</code> method, and will |
| * resume processing any of the previously stopped filter graphs. |
| */ |
| public void onResume() { |
| synchronized (mState) { |
| if (mState.current == State.STATE_PAUSED) { |
| resumeRunners(); |
| resumeCamera(); |
| mState.current = State.STATE_RUNNING; |
| } |
| } |
| } |
| |
| /** |
| * Release all resources associated with this context. |
| * This will also stop any running graphs. |
| */ |
| public void release() { |
| synchronized (mState) { |
| if (mState.current != State.STATE_DESTROYED) { |
| if (mCameraStreamer != null) { |
| mCameraStreamer.stop(); |
| mCameraStreamer.tearDown(); |
| } |
| if (Build.VERSION.SDK_INT >= 11) { |
| maybeDestroyRenderScript(); |
| } |
| stopRunners(false); |
| waitUntilStopped(); |
| tearDown(); |
| mState.current = State.STATE_DESTROYED; |
| } |
| } |
| } |
| |
| /** |
| * Set whether frames are preserved when the context is paused. |
| * When passing false, all Frames associated with this context are released. The default |
| * value is true. |
| * |
| * @param preserve true, to preserve frames when the context is paused. |
| * |
| * @see #getPreserveFramesOnPause() |
| */ |
| public void setPreserveFramesOnPause(boolean preserve) { |
| mPreserveFramesOnPause = preserve; |
| } |
| |
| /** |
| * Returns whether frames are preserved when the context is paused. |
| * |
| * @return true, if frames are preserved when the context is paused. |
| * |
| * @see #setPreserveFramesOnPause(boolean) |
| */ |
| public boolean getPreserveFramesOnPause() { |
| return mPreserveFramesOnPause; |
| } |
| |
| /** |
| * Returns the application context that the MffContext is attached to. |
| * |
| * @return The application context for this context. |
| */ |
| public Context getApplicationContext() { |
| return mApplicationContext; |
| } |
| |
| /** |
| * Returns the context's shared CameraStreamer. |
| * Use the CameraStreamer to control the Camera. Frames from the Camera are typically streamed |
| * to CameraSource filters. |
| * |
| * @return The context's CameraStreamer instance. |
| */ |
| public CameraStreamer getCameraStreamer() { |
| if (mCameraStreamer == null) { |
| mCameraStreamer = new CameraStreamer(this); |
| } |
| return mCameraStreamer; |
| } |
| |
| /** |
| * Set the default EGL config chooser. |
| * |
| * When an EGL context is required by the MFF, the channel sizes specified here are used. The |
| * default sizes are 8 bits per R,G,B,A channel and 0 bits for depth and stencil channels. |
| * |
| * @param redSize The size of the red channel in bits. |
| * @param greenSize The size of the green channel in bits. |
| * @param blueSize The size of the blue channel in bits. |
| * @param alphaSize The size of the alpha channel in bits. |
| * @param depthSize The size of the depth channel in bits. |
| * @param stencilSize The size of the stencil channel in bits. |
| */ |
| public static void setEGLConfigChooser(int redSize, |
| int greenSize, |
| int blueSize, |
| int alphaSize, |
| int depthSize, |
| int stencilSize) { |
| RenderTarget.setEGLConfigChooser(redSize, |
| greenSize, |
| blueSize, |
| alphaSize, |
| depthSize, |
| stencilSize); |
| } |
| |
| /** |
| * Returns true, if this context supports using OpenGL. |
| * @return true, if this context supports using OpenGL. |
| */ |
| public final boolean isOpenGLSupported() { |
| return mGLSupport; |
| } |
| |
| /** |
| * Returns true, if this context supports camera streaming. |
| * @return true, if this context supports camera streaming. |
| */ |
| public final boolean isCameraStreamingSupported() { |
| return mCameraStreamingSupport; |
| } |
| |
| @TargetApi(11) |
| public final RenderScript getRenderScript() { |
| if (mRenderScript == null) { |
| mRenderScript = RenderScript.create(mApplicationContext); |
| } |
| return mRenderScript; |
| } |
| |
| final void assertOpenGLSupported() { |
| if (!isOpenGLSupported()) { |
| throw new RuntimeException("Attempting to use OpenGL ES 2 in a context that does not " |
| + "support it!"); |
| } |
| } |
| |
| void addGraph(FilterGraph graph) { |
| synchronized (mGraphs) { |
| mGraphs.add(graph); |
| } |
| } |
| |
| void addRunner(GraphRunner runner) { |
| synchronized (mRunners) { |
| mRunners.add(runner); |
| } |
| } |
| |
| SurfaceView getDummySurfaceView() { |
| return mDummySurfaceView; |
| } |
| |
| void postRunnable(Runnable runnable) { |
| mHandler.post(runnable); |
| } |
| |
| private void init(Context context, Config config) { |
| determineGLSupport(context, config); |
| determineCameraSupport(config); |
| createHandler(); |
| mApplicationContext = context.getApplicationContext(); |
| fetchDummySurfaceView(context, config); |
| } |
| |
| private void fetchDummySurfaceView(Context context, Config config) { |
| if (config.requireCamera && CameraStreamer.requireDummySurfaceView()) { |
| mDummySurfaceView = config.dummySurface != null |
| ? config.dummySurface |
| : createDummySurfaceView(context); |
| } |
| } |
| |
| private void determineGLSupport(Context context, Config config) { |
| if (config.forceNoGL) { |
| mGLSupport = false; |
| } else { |
| mGLSupport = getPlatformSupportsGLES2(context); |
| if (config.requireOpenGL && !mGLSupport) { |
| throw new RuntimeException("Cannot create context that requires GL support on " |
| + "this platform!"); |
| } |
| } |
| } |
| |
| private void determineCameraSupport(Config config) { |
| mCameraStreamingSupport = (CameraStreamer.getNumberOfCameras() > 0); |
| if (config.requireCamera && !mCameraStreamingSupport) { |
| throw new RuntimeException("Cannot create context that requires a camera on " |
| + "this platform!"); |
| } |
| } |
| |
| private static boolean getPlatformSupportsGLES2(Context context) { |
| ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); |
| ConfigurationInfo configurationInfo = am.getDeviceConfigurationInfo(); |
| return configurationInfo.reqGlEsVersion >= 0x20000; |
| } |
| |
| private void createHandler() { |
| if (Looper.myLooper() == null) { |
| throw new RuntimeException("MffContext must be created in a thread with a Looper!"); |
| } |
| mHandler = new Handler(); |
| } |
| |
| private void stopRunners(boolean haltOnly) { |
| synchronized (mRunners) { |
| // Halt all runners (does nothing if not running) |
| for (GraphRunner runner : mRunners) { |
| if (haltOnly) { |
| runner.halt(); |
| } else { |
| runner.stop(); |
| } |
| } |
| // Flush all graphs if requested (this is queued up after the call to halt) |
| if (!mPreserveFramesOnPause) { |
| for (GraphRunner runner : mRunners) { |
| runner.flushFrames(); |
| } |
| } |
| } |
| } |
| |
| private void resumeRunners() { |
| synchronized (mRunners) { |
| for (GraphRunner runner : mRunners) { |
| runner.restart(); |
| } |
| } |
| } |
| |
| private void resumeCamera() { |
| // Restart only affects previously halted cameras that were running. |
| if (mCameraStreamer != null) { |
| mCameraStreamer.restart(); |
| } |
| } |
| |
| private void waitUntilStopped() { |
| for (GraphRunner runner : mRunners) { |
| runner.waitUntilStop(); |
| } |
| } |
| |
| private void tearDown() { |
| // Tear down graphs |
| for (FilterGraph graph : mGraphs) { |
| graph.tearDown(); |
| } |
| |
| // Tear down runners |
| for (GraphRunner runner : mRunners) { |
| runner.tearDown(); |
| } |
| } |
| |
| @SuppressWarnings("deprecation") |
| private SurfaceView createDummySurfaceView(Context context) { |
| // This is only called on Gingerbread devices, so deprecation warning is unnecessary. |
| SurfaceView dummySurfaceView = new SurfaceView(context); |
| dummySurfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); |
| // If we have an activity for this context we'll add the SurfaceView to it (as a 1x1 view |
| // in the top-left corner). If not, we warn the user that they may need to add one manually. |
| Activity activity = findActivityForContext(context); |
| if (activity != null) { |
| ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(1, 1); |
| activity.addContentView(dummySurfaceView, params); |
| } else { |
| Log.w("MffContext", "Could not find activity for dummy surface! Consider specifying " |
| + "your own SurfaceView!"); |
| } |
| return dummySurfaceView; |
| } |
| |
| private Activity findActivityForContext(Context context) { |
| return (context instanceof Activity) ? (Activity) context : null; |
| } |
| |
| @TargetApi(11) |
| private void maybeDestroyRenderScript() { |
| if (mRenderScript != null) { |
| mRenderScript.destroy(); |
| mRenderScript = null; |
| } |
| } |
| |
| } |