1 /*
2  * Copyright (C) 2014 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 android.hardware.camera2.legacy;
18 
19 import android.graphics.SurfaceTexture;
20 import android.hardware.camera2.impl.CameraDeviceImpl;
21 import android.os.ConditionVariable;
22 import android.os.Handler;
23 import android.os.Message;
24 import android.util.Log;
25 import android.util.Pair;
26 import android.util.Size;
27 import android.view.Surface;
28 
29 import java.util.Collection;
30 
31 import static com.android.internal.util.Preconditions.*;
32 
33 /**
34  * GLThreadManager handles the thread used for rendering into the configured output surfaces.
35  */
36 public class GLThreadManager {
37     private final String TAG;
38     private static final boolean DEBUG = false;
39 
40     private static final int MSG_NEW_CONFIGURATION = 1;
41     private static final int MSG_NEW_FRAME = 2;
42     private static final int MSG_CLEANUP = 3;
43     private static final int MSG_DROP_FRAMES = 4;
44     private static final int MSG_ALLOW_FRAMES = 5;
45 
46     private CaptureCollector mCaptureCollector;
47 
48     private final CameraDeviceState mDeviceState;
49 
50     private final SurfaceTextureRenderer mTextureRenderer;
51 
52     private final RequestHandlerThread mGLHandlerThread;
53 
54     private final RequestThreadManager.FpsCounter mPrevCounter =
55             new RequestThreadManager.FpsCounter("GL Preview Producer");
56 
57     /**
58      * Container object for Configure messages.
59      */
60     private static class ConfigureHolder {
61         public final ConditionVariable condition;
62         public final Collection<Pair<Surface, Size>> surfaces;
63         public final CaptureCollector collector;
64 
ConfigureHolder(ConditionVariable condition, Collection<Pair<Surface, Size>> surfaces, CaptureCollector collector)65         public ConfigureHolder(ConditionVariable condition, Collection<Pair<Surface,
66                 Size>> surfaces, CaptureCollector collector) {
67             this.condition = condition;
68             this.surfaces = surfaces;
69             this.collector = collector;
70         }
71     }
72 
73     private final Handler.Callback mGLHandlerCb = new Handler.Callback() {
74         private boolean mCleanup = false;
75         private boolean mConfigured = false;
76         private boolean mDroppingFrames = false;
77 
78         @SuppressWarnings("unchecked")
79         @Override
80         public boolean handleMessage(Message msg) {
81             if (mCleanup) {
82                 return true;
83             }
84             try {
85                 switch (msg.what) {
86                     case MSG_NEW_CONFIGURATION:
87                         ConfigureHolder configure = (ConfigureHolder) msg.obj;
88                         mTextureRenderer.cleanupEGLContext();
89                         mTextureRenderer.configureSurfaces(configure.surfaces);
90                         mCaptureCollector = checkNotNull(configure.collector);
91                         configure.condition.open();
92                         mConfigured = true;
93                         break;
94                     case MSG_NEW_FRAME:
95                         if (mDroppingFrames) {
96                             Log.w(TAG, "Ignoring frame.");
97                             break;
98                         }
99                         if (DEBUG) {
100                             mPrevCounter.countAndLog();
101                         }
102                         if (!mConfigured) {
103                             Log.e(TAG, "Dropping frame, EGL context not configured!");
104                         }
105                         mTextureRenderer.drawIntoSurfaces(mCaptureCollector);
106                         break;
107                     case MSG_CLEANUP:
108                         mTextureRenderer.cleanupEGLContext();
109                         mCleanup = true;
110                         mConfigured = false;
111                         break;
112                     case MSG_DROP_FRAMES:
113                         mDroppingFrames = true;
114                         break;
115                     case MSG_ALLOW_FRAMES:
116                         mDroppingFrames = false;
117                         break;
118                     case RequestHandlerThread.MSG_POKE_IDLE_HANDLER:
119                         // OK: Ignore message.
120                         break;
121                     default:
122                         Log.e(TAG, "Unhandled message " + msg.what + " on GLThread.");
123                         break;
124                 }
125             } catch (Exception e) {
126                 Log.e(TAG, "Received exception on GL render thread: ", e);
127                 mDeviceState.setError(CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE);
128             }
129             return true;
130         }
131     };
132 
133     /**
134      * Create a new GL thread and renderer.
135      *
136      * @param cameraId the camera id for this thread.
137      * @param facing direction the camera is facing.
138      * @param state {@link CameraDeviceState} to use for error handling.
139      */
GLThreadManager(int cameraId, int facing, CameraDeviceState state)140     public GLThreadManager(int cameraId, int facing, CameraDeviceState state) {
141         mTextureRenderer = new SurfaceTextureRenderer(facing);
142         TAG = String.format("CameraDeviceGLThread-%d", cameraId);
143         mGLHandlerThread = new RequestHandlerThread(TAG, mGLHandlerCb);
144         mDeviceState = state;
145     }
146 
147     /**
148      * Start the thread.
149      *
150      * <p>
151      * This must be called before queueing new frames.
152      * </p>
153      */
start()154     public void start() {
155         mGLHandlerThread.start();
156     }
157 
158     /**
159      * Wait until the thread has started.
160      */
waitUntilStarted()161     public void waitUntilStarted() {
162         mGLHandlerThread.waitUntilStarted();
163     }
164 
165     /**
166      * Quit the thread.
167      *
168      * <p>
169      * No further methods can be called after this.
170      * </p>
171      */
quit()172     public void quit() {
173         Handler handler = mGLHandlerThread.getHandler();
174         handler.sendMessageAtFrontOfQueue(handler.obtainMessage(MSG_CLEANUP));
175         mGLHandlerThread.quitSafely();
176         try {
177             mGLHandlerThread.join();
178         } catch (InterruptedException e) {
179             Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
180                     mGLHandlerThread.getName(), mGLHandlerThread.getId()));
181         }
182     }
183 
184     /**
185      * Queue a new call to draw into the surfaces specified in the next available preview
186      * request from the {@link CaptureCollector} passed to
187      * {@link #setConfigurationAndWait(java.util.Collection, CaptureCollector)};
188      */
queueNewFrame()189     public void queueNewFrame() {
190         Handler handler = mGLHandlerThread.getHandler();
191 
192         /**
193          * Avoid queuing more than one new frame.  If we are not consuming faster than frames
194          * are produced, drop frames rather than allowing the queue to back up.
195          */
196         if (!handler.hasMessages(MSG_NEW_FRAME)) {
197             handler.sendMessage(handler.obtainMessage(MSG_NEW_FRAME));
198         } else {
199             Log.e(TAG, "GLThread dropping frame.  Not consuming frames quickly enough!");
200         }
201     }
202 
203     /**
204      * Configure the GL renderer for the given set of output surfaces, and block until
205      * this configuration has been applied.
206      *
207      * @param surfaces a collection of pairs of {@link android.view.Surface}s and their
208      *                 corresponding sizes to configure.
209      * @param collector a {@link CaptureCollector} to retrieve requests from.
210      */
setConfigurationAndWait(Collection<Pair<Surface, Size>> surfaces, CaptureCollector collector)211     public void setConfigurationAndWait(Collection<Pair<Surface, Size>> surfaces,
212                                         CaptureCollector collector) {
213         checkNotNull(collector, "collector must not be null");
214         Handler handler = mGLHandlerThread.getHandler();
215 
216         final ConditionVariable condition = new ConditionVariable(/*closed*/false);
217         ConfigureHolder configure = new ConfigureHolder(condition, surfaces, collector);
218 
219         Message m = handler.obtainMessage(MSG_NEW_CONFIGURATION, /*arg1*/0, /*arg2*/0, configure);
220         handler.sendMessage(m);
221 
222         // Block until configuration applied.
223         condition.block();
224     }
225 
226     /**
227      * Get the underlying surface to produce frames from.
228      *
229      * <p>
230      * This returns the surface that is drawn into the set of surfaces passed in for each frame.
231      * This method should only be called after a call to
232      * {@link #setConfigurationAndWait(java.util.Collection)}.  Calling this before the first call
233      * to {@link #setConfigurationAndWait(java.util.Collection)}, after {@link #quit()}, or
234      * concurrently to one of these calls may result in an invalid
235      * {@link android.graphics.SurfaceTexture} being returned.
236      * </p>
237      *
238      * @return an {@link android.graphics.SurfaceTexture} to draw to.
239      */
getCurrentSurfaceTexture()240     public SurfaceTexture getCurrentSurfaceTexture() {
241         return mTextureRenderer.getSurfaceTexture();
242     }
243 
244     /**
245      * Ignore any subsequent calls to {@link #queueNewFrame(java.util.Collection)}.
246      */
ignoreNewFrames()247     public void ignoreNewFrames() {
248         mGLHandlerThread.getHandler().sendEmptyMessage(MSG_DROP_FRAMES);
249     }
250 
251     /**
252      * Wait until no messages are queued.
253      */
waitUntilIdle()254     public void waitUntilIdle() {
255         mGLHandlerThread.waitUntilIdle();
256     }
257 
258     /**
259      * Re-enable drawing new frames after a call to {@link #ignoreNewFrames()}.
260      */
allowNewFrames()261     public void allowNewFrames() {
262         mGLHandlerThread.getHandler().sendEmptyMessage(MSG_ALLOW_FRAMES);
263     }
264 }
265