1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.media.filterfw; 18 19 import android.annotation.TargetApi; 20 import android.app.Activity; 21 import android.app.ActivityManager; 22 import android.content.Context; 23 import android.content.pm.ConfigurationInfo; 24 import android.os.Build; 25 import android.os.Handler; 26 import android.os.Looper; 27 import android.renderscript.RenderScript; 28 import android.util.Log; 29 import android.view.SurfaceHolder; 30 import android.view.SurfaceView; 31 import android.view.ViewGroup; 32 33 import java.util.HashSet; 34 import java.util.Set; 35 36 /** 37 * The MffContext holds the state and resources of a Mobile Filter Framework processing instance. 38 * Though it is possible to create multiple MffContext instances, typical applications will rely on 39 * a single MffContext to perform all processing within the Mobile Filter Framework. 40 * 41 * The MffContext class declares two methods {@link #onPause()} and {@link #onResume()}, that are 42 * typically called when the application activity is paused and resumed. This will take care of 43 * halting any processing in the context, and releasing resources while the activity is paused. 44 */ 45 public class MffContext { 46 47 /** 48 * Class to hold configuration information for MffContexts. 49 */ 50 public static class Config { 51 /** 52 * Set to true, if this context will make use of the camera. 53 * If your application does not require the camera, the context does not guarantee that 54 * a camera is available for streaming. That is, you may only use a CameraStreamer if 55 * the context's {@link #isCameraStreamingSupported()} returns true. 56 */ 57 public boolean requireCamera = true; 58 59 /** 60 * Set to true, if this context requires OpenGL. 61 * If your application does not require OpenGL, the context does not guarantee that OpenGL 62 * is available. That is, you may only use OpenGL (within filters running in this context) 63 * if the context's {@link #isOpenGLSupported()} method returns true. 64 */ 65 public boolean requireOpenGL = true; 66 67 /** 68 * On older Android versions the Camera may need a SurfaceView to render into in order to 69 * function. You may specify a dummy SurfaceView here if you do not want the context to 70 * create its own view. Note, that your view may or may not be used. You cannot rely on 71 * your dummy view to be used by the Camera. If you pass null, no dummy view will be used. 72 * In this case your application may not run correctly on older devices if you use the 73 * camera. This flag has no effect if you do not require the camera. 74 */ 75 public SurfaceView dummySurface = null; 76 77 /** Force MFF to not use OpenGL in its processing. */ 78 public boolean forceNoGL = false; 79 } 80 81 static private class State { 82 public static final int STATE_RUNNING = 1; 83 public static final int STATE_PAUSED = 2; 84 public static final int STATE_DESTROYED = 3; 85 86 public int current = STATE_RUNNING; 87 } 88 89 /** The application context. */ 90 private Context mApplicationContext = null; 91 92 /** The set of filter graphs within this context */ 93 private Set<FilterGraph> mGraphs = new HashSet<FilterGraph>(); 94 95 /** The set of graph runners within this context */ 96 private Set<GraphRunner> mRunners = new HashSet<GraphRunner>(); 97 98 /** True, if the context preserves frames when paused. */ 99 private boolean mPreserveFramesOnPause = false; 100 101 /** The shared CameraStreamer that streams camera frames to CameraSource filters. */ 102 private CameraStreamer mCameraStreamer = null; 103 104 /** The current context state. */ 105 private State mState = new State(); 106 107 /** A dummy SurfaceView that is required for Camera operation on older devices. */ 108 private SurfaceView mDummySurfaceView = null; 109 110 /** Handler to execute code in the context's thread, such as issuing callbacks. */ 111 private Handler mHandler = null; 112 113 /** Flag whether OpenGL ES 2 is supported in this context. */ 114 private boolean mGLSupport; 115 116 /** Flag whether camera streaming is supported in this context. */ 117 private boolean mCameraStreamingSupport; 118 119 /** RenderScript base master class. */ 120 private RenderScript mRenderScript; 121 122 /** 123 * Creates a new MffContext with the default configuration. 124 * 125 * An MffContext must be attached to a Context object of an application. You may create 126 * multiple MffContexts, however data between them cannot be shared. The context must be 127 * created in a thread with a Looper (such as the main/UI thread). 128 * 129 * On older versions of Android, the MffContext may create a visible dummy view for the 130 * camera to render into. This is a 1x1 SurfaceView that is placed into the top-left corner. 131 * 132 * @param context The application context to attach the MffContext to. 133 */ MffContext(Context context)134 public MffContext(Context context) { 135 init(context, new Config()); 136 } 137 138 /** 139 * Creates a new MffContext with the specified configuration. 140 * 141 * An MffContext must be attached to a Context object of an application. You may create 142 * multiple MffContexts, however data between them cannot be shared. The context must be 143 * created in a thread with a Looper (such as the main/UI thread). 144 * 145 * On older versions of Android, the MffContext may create a visible dummy view for the 146 * camera to render into. This is a 1x1 SurfaceView that is placed into the top-left corner. 147 * You may alternatively specify your own SurfaceView in the configuration. 148 * 149 * @param context The application context to attach the MffContext to. 150 * @param config The configuration to use. 151 * 152 * @throws RuntimeException If no context for the requested configuration could be created. 153 */ MffContext(Context context, Config config)154 public MffContext(Context context, Config config) { 155 init(context, config); 156 } 157 158 /** 159 * Put all processing in the context on hold. 160 * This is typically called from your application's <code>onPause()</code> method, and will 161 * stop all running graphs (closing their filters). If the context does not preserve frames on 162 * pause (see {@link #setPreserveFramesOnPause(boolean)}) all frames attached to this context 163 * are released. 164 */ onPause()165 public void onPause() { 166 synchronized (mState) { 167 if (mState.current == State.STATE_RUNNING) { 168 if (mCameraStreamer != null) { 169 mCameraStreamer.halt(); 170 } 171 stopRunners(true); 172 mState.current = State.STATE_PAUSED; 173 } 174 } 175 } 176 177 /** 178 * Resumes the processing in this context. 179 * This is typically called from the application's <code>onResume()</code> method, and will 180 * resume processing any of the previously stopped filter graphs. 181 */ onResume()182 public void onResume() { 183 synchronized (mState) { 184 if (mState.current == State.STATE_PAUSED) { 185 resumeRunners(); 186 resumeCamera(); 187 mState.current = State.STATE_RUNNING; 188 } 189 } 190 } 191 192 /** 193 * Release all resources associated with this context. 194 * This will also stop any running graphs. 195 */ release()196 public void release() { 197 synchronized (mState) { 198 if (mState.current != State.STATE_DESTROYED) { 199 if (mCameraStreamer != null) { 200 mCameraStreamer.stop(); 201 mCameraStreamer.tearDown(); 202 } 203 if (Build.VERSION.SDK_INT >= 11) { 204 maybeDestroyRenderScript(); 205 } 206 stopRunners(false); 207 waitUntilStopped(); 208 tearDown(); 209 mState.current = State.STATE_DESTROYED; 210 } 211 } 212 } 213 214 /** 215 * Set whether frames are preserved when the context is paused. 216 * When passing false, all Frames associated with this context are released. The default 217 * value is true. 218 * 219 * @param preserve true, to preserve frames when the context is paused. 220 * 221 * @see #getPreserveFramesOnPause() 222 */ setPreserveFramesOnPause(boolean preserve)223 public void setPreserveFramesOnPause(boolean preserve) { 224 mPreserveFramesOnPause = preserve; 225 } 226 227 /** 228 * Returns whether frames are preserved when the context is paused. 229 * 230 * @return true, if frames are preserved when the context is paused. 231 * 232 * @see #setPreserveFramesOnPause(boolean) 233 */ getPreserveFramesOnPause()234 public boolean getPreserveFramesOnPause() { 235 return mPreserveFramesOnPause; 236 } 237 238 /** 239 * Returns the application context that the MffContext is attached to. 240 * 241 * @return The application context for this context. 242 */ getApplicationContext()243 public Context getApplicationContext() { 244 return mApplicationContext; 245 } 246 247 /** 248 * Returns the context's shared CameraStreamer. 249 * Use the CameraStreamer to control the Camera. Frames from the Camera are typically streamed 250 * to CameraSource filters. 251 * 252 * @return The context's CameraStreamer instance. 253 */ getCameraStreamer()254 public CameraStreamer getCameraStreamer() { 255 if (mCameraStreamer == null) { 256 mCameraStreamer = new CameraStreamer(this); 257 } 258 return mCameraStreamer; 259 } 260 261 /** 262 * Set the default EGL config chooser. 263 * 264 * When an EGL context is required by the MFF, the channel sizes specified here are used. The 265 * default sizes are 8 bits per R,G,B,A channel and 0 bits for depth and stencil channels. 266 * 267 * @param redSize The size of the red channel in bits. 268 * @param greenSize The size of the green channel in bits. 269 * @param blueSize The size of the blue channel in bits. 270 * @param alphaSize The size of the alpha channel in bits. 271 * @param depthSize The size of the depth channel in bits. 272 * @param stencilSize The size of the stencil channel in bits. 273 */ setEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize)274 public static void setEGLConfigChooser(int redSize, 275 int greenSize, 276 int blueSize, 277 int alphaSize, 278 int depthSize, 279 int stencilSize) { 280 RenderTarget.setEGLConfigChooser(redSize, 281 greenSize, 282 blueSize, 283 alphaSize, 284 depthSize, 285 stencilSize); 286 } 287 288 /** 289 * Returns true, if this context supports using OpenGL. 290 * @return true, if this context supports using OpenGL. 291 */ isOpenGLSupported()292 public final boolean isOpenGLSupported() { 293 return mGLSupport; 294 } 295 296 /** 297 * Returns true, if this context supports camera streaming. 298 * @return true, if this context supports camera streaming. 299 */ isCameraStreamingSupported()300 public final boolean isCameraStreamingSupported() { 301 return mCameraStreamingSupport; 302 } 303 304 @TargetApi(11) getRenderScript()305 public final RenderScript getRenderScript() { 306 if (mRenderScript == null) { 307 mRenderScript = RenderScript.create(mApplicationContext); 308 } 309 return mRenderScript; 310 } 311 assertOpenGLSupported()312 final void assertOpenGLSupported() { 313 if (!isOpenGLSupported()) { 314 throw new RuntimeException("Attempting to use OpenGL ES 2 in a context that does not " 315 + "support it!"); 316 } 317 } 318 addGraph(FilterGraph graph)319 void addGraph(FilterGraph graph) { 320 synchronized (mGraphs) { 321 mGraphs.add(graph); 322 } 323 } 324 addRunner(GraphRunner runner)325 void addRunner(GraphRunner runner) { 326 synchronized (mRunners) { 327 mRunners.add(runner); 328 } 329 } 330 getDummySurfaceView()331 SurfaceView getDummySurfaceView() { 332 return mDummySurfaceView; 333 } 334 postRunnable(Runnable runnable)335 void postRunnable(Runnable runnable) { 336 mHandler.post(runnable); 337 } 338 init(Context context, Config config)339 private void init(Context context, Config config) { 340 determineGLSupport(context, config); 341 determineCameraSupport(config); 342 createHandler(); 343 mApplicationContext = context.getApplicationContext(); 344 fetchDummySurfaceView(context, config); 345 } 346 fetchDummySurfaceView(Context context, Config config)347 private void fetchDummySurfaceView(Context context, Config config) { 348 if (config.requireCamera && CameraStreamer.requireDummySurfaceView()) { 349 mDummySurfaceView = config.dummySurface != null 350 ? config.dummySurface 351 : createDummySurfaceView(context); 352 } 353 } 354 determineGLSupport(Context context, Config config)355 private void determineGLSupport(Context context, Config config) { 356 if (config.forceNoGL) { 357 mGLSupport = false; 358 } else { 359 mGLSupport = getPlatformSupportsGLES2(context); 360 if (config.requireOpenGL && !mGLSupport) { 361 throw new RuntimeException("Cannot create context that requires GL support on " 362 + "this platform!"); 363 } 364 } 365 } 366 determineCameraSupport(Config config)367 private void determineCameraSupport(Config config) { 368 mCameraStreamingSupport = (CameraStreamer.getNumberOfCameras() > 0); 369 if (config.requireCamera && !mCameraStreamingSupport) { 370 throw new RuntimeException("Cannot create context that requires a camera on " 371 + "this platform!"); 372 } 373 } 374 getPlatformSupportsGLES2(Context context)375 private static boolean getPlatformSupportsGLES2(Context context) { 376 ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 377 ConfigurationInfo configurationInfo = am.getDeviceConfigurationInfo(); 378 return configurationInfo.reqGlEsVersion >= 0x20000; 379 } 380 createHandler()381 private void createHandler() { 382 if (Looper.myLooper() == null) { 383 throw new RuntimeException("MffContext must be created in a thread with a Looper!"); 384 } 385 mHandler = new Handler(); 386 } 387 stopRunners(boolean haltOnly)388 private void stopRunners(boolean haltOnly) { 389 synchronized (mRunners) { 390 // Halt all runners (does nothing if not running) 391 for (GraphRunner runner : mRunners) { 392 if (haltOnly) { 393 runner.halt(); 394 } else { 395 runner.stop(); 396 } 397 } 398 // Flush all graphs if requested (this is queued up after the call to halt) 399 if (!mPreserveFramesOnPause) { 400 for (GraphRunner runner : mRunners) { 401 runner.flushFrames(); 402 } 403 } 404 } 405 } 406 resumeRunners()407 private void resumeRunners() { 408 synchronized (mRunners) { 409 for (GraphRunner runner : mRunners) { 410 runner.restart(); 411 } 412 } 413 } 414 resumeCamera()415 private void resumeCamera() { 416 // Restart only affects previously halted cameras that were running. 417 if (mCameraStreamer != null) { 418 mCameraStreamer.restart(); 419 } 420 } 421 waitUntilStopped()422 private void waitUntilStopped() { 423 for (GraphRunner runner : mRunners) { 424 runner.waitUntilStop(); 425 } 426 } 427 tearDown()428 private void tearDown() { 429 // Tear down graphs 430 for (FilterGraph graph : mGraphs) { 431 graph.tearDown(); 432 } 433 434 // Tear down runners 435 for (GraphRunner runner : mRunners) { 436 runner.tearDown(); 437 } 438 } 439 440 @SuppressWarnings("deprecation") createDummySurfaceView(Context context)441 private SurfaceView createDummySurfaceView(Context context) { 442 // This is only called on Gingerbread devices, so deprecation warning is unnecessary. 443 SurfaceView dummySurfaceView = new SurfaceView(context); 444 dummySurfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 445 // If we have an activity for this context we'll add the SurfaceView to it (as a 1x1 view 446 // in the top-left corner). If not, we warn the user that they may need to add one manually. 447 Activity activity = findActivityForContext(context); 448 if (activity != null) { 449 ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(1, 1); 450 activity.addContentView(dummySurfaceView, params); 451 } else { 452 Log.w("MffContext", "Could not find activity for dummy surface! Consider specifying " 453 + "your own SurfaceView!"); 454 } 455 return dummySurfaceView; 456 } 457 findActivityForContext(Context context)458 private Activity findActivityForContext(Context context) { 459 return (context instanceof Activity) ? (Activity) context : null; 460 } 461 462 @TargetApi(11) maybeDestroyRenderScript()463 private void maybeDestroyRenderScript() { 464 if (mRenderScript != null) { 465 mRenderScript.destroy(); 466 mRenderScript = null; 467 } 468 } 469 470 } 471