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