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