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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 300 public final boolean isCameraStreamingSupported() { 301 return mCameraStreamingSupport; 302 } 303 304 @TargetApi(11) 305 public final RenderScript getRenderScript() { 306 if (mRenderScript == null) { 307 mRenderScript = RenderScript.create(mApplicationContext); 308 } 309 return mRenderScript; 310 } 311 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 319 void addGraph(FilterGraph graph) { 320 synchronized (mGraphs) { 321 mGraphs.add(graph); 322 } 323 } 324 325 void addRunner(GraphRunner runner) { 326 synchronized (mRunners) { 327 mRunners.add(runner); 328 } 329 } 330 331 SurfaceView getDummySurfaceView() { 332 return mDummySurfaceView; 333 } 334 335 void postRunnable(Runnable runnable) { 336 mHandler.post(runnable); 337 } 338 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 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 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 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 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 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 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 407 private void resumeRunners() { 408 synchronized (mRunners) { 409 for (GraphRunner runner : mRunners) { 410 runner.restart(); 411 } 412 } 413 } 414 415 private void resumeCamera() { 416 // Restart only affects previously halted cameras that were running. 417 if (mCameraStreamer != null) { 418 mCameraStreamer.restart(); 419 } 420 } 421 422 private void waitUntilStopped() { 423 for (GraphRunner runner : mRunners) { 424 runner.waitUntilStop(); 425 } 426 } 427 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") 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 458 private Activity findActivityForContext(Context context) { 459 return (context instanceof Activity) ? (Activity) context : null; 460 } 461 462 @TargetApi(11) 463 private void maybeDestroyRenderScript() { 464 if (mRenderScript != null) { 465 mRenderScript.destroy(); 466 mRenderScript = null; 467 } 468 } 469 470 } 471