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.hardware.camera2.impl.CameraDeviceImpl;
21import android.os.ConditionVariable;
22import android.os.Handler;
23import android.os.Message;
24import android.util.Log;
25import android.util.Pair;
26import android.util.Size;
27import android.view.Surface;
28
29import java.util.Collection;
30
31import static com.android.internal.util.Preconditions.*;
32
33/**
34 * GLThreadManager handles the thread used for rendering into the configured output surfaces.
35 */
36public 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
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     */
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     */
154    public void start() {
155        mGLHandlerThread.start();
156    }
157
158    /**
159     * Wait until the thread has started.
160     */
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     */
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     */
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     */
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     */
240    public SurfaceTexture getCurrentSurfaceTexture() {
241        return mTextureRenderer.getSurfaceTexture();
242    }
243
244    /**
245     * Ignore any subsequent calls to {@link #queueNewFrame(java.util.Collection)}.
246     */
247    public void ignoreNewFrames() {
248        mGLHandlerThread.getHandler().sendEmptyMessage(MSG_DROP_FRAMES);
249    }
250
251    /**
252     * Wait until no messages are queued.
253     */
254    public void waitUntilIdle() {
255        mGLHandlerThread.waitUntilIdle();
256    }
257
258    /**
259     * Re-enable drawing new frames after a call to {@link #ignoreNewFrames()}.
260     */
261    public void allowNewFrames() {
262        mGLHandlerThread.getHandler().sendEmptyMessage(MSG_ALLOW_FRAMES);
263    }
264}
265