/* * 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 mGraphs = new HashSet(); /** The set of graph runners within this context */ private Set mRunners = new HashSet(); /** 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 onPause() 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 onResume() 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; } } }