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