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