• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 com.android.cts.verifier.camera.its;
18 
19 import android.content.Context;
20 import android.graphics.SurfaceTexture;
21 import android.media.CamcorderProfile;
22 import android.media.EncoderProfiles;
23 import android.media.MediaCodec;
24 import android.media.MediaRecorder;
25 import android.opengl.EGL14;
26 import android.opengl.EGLConfig;
27 import android.opengl.EGLContext;
28 import android.opengl.EGLDisplay;
29 import android.opengl.EGLExt;
30 import android.opengl.EGLSurface;
31 import android.opengl.GLES11Ext;
32 import android.opengl.GLES20;
33 import android.os.ConditionVariable;
34 import android.os.Handler;
35 import android.util.Pair;
36 import android.util.Size;
37 import android.view.Surface;
38 
39 import java.io.IOException;
40 import java.nio.ByteBuffer;
41 import java.nio.ByteOrder;
42 import java.nio.FloatBuffer;
43 import java.nio.IntBuffer;
44 import java.util.ArrayList;
45 import java.util.Comparator;
46 import java.util.List;
47 
48 /**
49  * Class to record a preview like stream. It sets up a SurfaceTexture that the camera can write to,
50  * and copies over the camera frames to a MediaRecorder surface.
51  */
52 class PreviewRecorder implements AutoCloseable {
53     private static final String TAG = PreviewRecorder.class.getSimpleName();
54 
55     // Default bitrate to use for recordings when querying CamcorderProfile fails.
56     private static final int DEFAULT_RECORDING_BITRATE = 25_000_000; // 25 Mbps
57 
58     // Simple Vertex Shader that rotates the texture before passing it to Fragment shader.
59     private static final String VERTEX_SHADER = String.join(
60             "\n",
61             "",
62             "attribute vec4 vPosition;",
63             "uniform mat4 texMatrix;", // provided by SurfaceTexture
64             "uniform mat2 texRotMatrix;", // optional rotation matrix, from Sensor Orientation
65             "varying vec2 vTextureCoord;",
66             "void main() {",
67             "    gl_Position = vPosition;",
68             "    vec2 texCoords = texRotMatrix * vPosition.xy;", // rotate the coordinates before
69                                                                  // applying transform from
70                                                                  // SurfaceTexture
71             "    texCoords = (texCoords + vec2(1.0, 1.0)) / 2.0;", // Texture coordinates
72                                                                    // have range [0, 1]
73             "    vTextureCoord = (texMatrix * vec4(texCoords, 0.0, 1.0)).xy;",
74             "}",
75             ""
76     );
77 
78     // Simple Fragment Shader that samples the passed texture at a given coordinate.
79     private static final String FRAGMENT_SHADER = String.join(
80             "\n",
81             "",
82             "#extension GL_OES_EGL_image_external : require",
83             "precision mediump float;",
84             "varying vec2 vTextureCoord;",
85             "uniform samplerExternalOES sTexture;", // implicitly populated by SurfaceTexture
86             "void main() {",
87             "    gl_FragColor = texture2D(sTexture, vTextureCoord);",
88             "}",
89             ""
90     );
91 
92     // column-major vertices list of a rectangle that fills the entire screen
93     private static final float[] FULLSCREEN_VERTICES = {
94             -1, -1, // bottom left
95             1, -1, // bottom right
96             -1,  1, // top left
97             1,  1, // top right
98     };
99 
100     // used to find a good-enough recording bitrate for a given resolution. "Good enough" for the
101     // ITS test to run its calculations and still be supported by the HAL.
102     // NOTE: Keep sorted for convenience
103     private final List<Pair<Integer, Integer>> mResolutionToCamcorderProfile = List.of(
104             Pair.create(176  * 144,  CamcorderProfile.QUALITY_QCIF),
105             Pair.create(320  * 240,  CamcorderProfile.QUALITY_QVGA),
106             Pair.create(352  * 288,  CamcorderProfile.QUALITY_CIF),
107             Pair.create(640  * 480,  CamcorderProfile.QUALITY_VGA),
108             Pair.create(720  * 480,  CamcorderProfile.QUALITY_480P),
109             Pair.create(1280 * 720,  CamcorderProfile.QUALITY_720P),
110             Pair.create(1920 * 1080, CamcorderProfile.QUALITY_1080P),
111             Pair.create(2048 * 1080, CamcorderProfile.QUALITY_2K),
112             Pair.create(2560 * 1440, CamcorderProfile.QUALITY_QHD),
113             Pair.create(3840 * 2160, CamcorderProfile.QUALITY_2160P),
114             Pair.create(4096 * 2160, CamcorderProfile.QUALITY_4KDCI)
115             // should be safe to assume that we don't have previews over 4k
116     );
117 
118     private boolean mMediaRecorderConsumed = false; // tracks if the MediaRecorder instance was
119                                                     // already used to record a video.
120 
121     // Lock to protect reads/writes to the various Surfaces below.
122     private final Object mRecorderLock = new Object();
123     // Tracks if the mMediaRecorder is currently recording. Protected by mRecorderLock.
124     private volatile boolean mIsRecording = false;
125 
126     private final Size mPreviewSize;
127     private final Handler mHandler;
128 
129     private Surface mRecorderSurface; // MediaRecorder source. EGL writes to this surface
130     private MediaRecorder mMediaRecorder;
131 
132     private SurfaceTexture mCameraTexture; // Handles writing frames from camera as texture to
133                                            // the GLSL program.
134     private Surface mCameraSurface; // Surface corresponding to mCameraTexture that the
135                                     // Camera HAL writes to
136 
137     private int mGLShaderProgram = 0;
138     private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
139     private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
140     private EGLSurface mEGLRecorderSurface; // EGL Surface corresponding to mRecorderSurface
141 
142     private int mVPositionLoc;
143     private int mTexMatrixLoc;
144     private int mTexRotMatrixLoc;
145 
146     private final float[] mTexRotMatrix; // length = 4
147     private final float[] mTransformMatrix = new float[16];
148 
149     /**
150      * Initializes MediaRecorder and EGL context. The result of recorded video will be stored in
151      * {@code outputFile}.
152      */
PreviewRecorder(int cameraId, Size previewSize, int sensorOrientation, String outputFile, Handler handler, Context context)153     PreviewRecorder(int cameraId, Size previewSize, int sensorOrientation, String outputFile,
154             Handler handler, Context context) throws ItsException {
155         // Ensure that we can record the given size
156         int maxSupportedResolution = mResolutionToCamcorderProfile
157                                         .stream()
158                                         .map(p -> p.first)
159                                         .max(Integer::compareTo)
160                                         .orElse(0);
161         int currentResolution = previewSize.getHeight() * previewSize.getWidth();
162         if (currentResolution > maxSupportedResolution) {
163             throw new ItsException("Requested preview size is greater than maximum "
164                     + "supported preview size.");
165         }
166 
167         mHandler = handler;
168         mPreviewSize = previewSize;
169         // rotate the texture as needed by the sensor orientation
170         mTexRotMatrix = getRotationMatrix(sensorOrientation);
171 
172         ConditionVariable cv = new ConditionVariable();
173         cv.close();
174         // Init fields in the passed handler to bind egl context to the handler thread.
175         mHandler.post(() -> {
176             try {
177                 initPreviewRecorder(cameraId, outputFile, context);
178             } catch (ItsException e) {
179                 Logt.e(TAG, "Failed to init preview recorder", e);
180                 throw new ItsRuntimeException("Failed to init preview recorder", e);
181             } finally {
182                 cv.open();
183             }
184         });
185         // Wait for up to 1s for handler to finish initializing
186         if (!cv.block(1000)) {
187             throw new ItsException("Preview recorder did not initialize in 1000ms");
188         }
189 
190     }
191 
initPreviewRecorder(int cameraId, String outputFile, Context context)192     private void initPreviewRecorder(int cameraId, String outputFile,
193             Context context) throws ItsException {
194         // order of initialization is important
195         setupMediaRecorder(cameraId, outputFile, context);
196         initEGL(); // requires MediaRecorder surfaces to be set up
197         compileShaders(); // requires EGL context to be set up
198         setupCameraTexture(); // requires EGL context to be set up
199 
200 
201         mCameraTexture.setOnFrameAvailableListener(surfaceTexture -> {
202             // Synchronized on mRecorderLock to ensure that all surface are valid while encoding
203             // frames. All surfaces should be valid for as long as mIsRecording is true.
204             synchronized (mRecorderLock) {
205                 if (surfaceTexture.isReleased()) {
206                     return; // surface texture already cleaned up, do nothing.
207                 }
208 
209                 // Bind EGL context to the current thread (just in case the
210                 // executing thread changes)
211                 EGL14.eglMakeCurrent(mEGLDisplay, mEGLRecorderSurface,
212                         mEGLRecorderSurface, mEGLContext);
213                 surfaceTexture.updateTexImage(); // update texture to the latest frame
214 
215                 // Only update the frame if the recorder is currently recording.
216                 if (!mIsRecording) {
217                     return;
218                 }
219                 try {
220                     copyFrameToRecorder();
221                 } catch (ItsException e) {
222                     Logt.e(TAG, "Failed to copy texture to recorder.", e);
223                     throw new ItsRuntimeException("Failed to copy texture to recorder.", e);
224                 }
225             }
226         }, mHandler);
227     }
228 
setupMediaRecorder(int cameraId, String outputFile, Context context)229     private void setupMediaRecorder(int cameraId, String outputFile, Context context)
230             throws ItsException {
231         mRecorderSurface = MediaCodec.createPersistentInputSurface();
232 
233         mMediaRecorder = new MediaRecorder(context);
234         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
235         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
236         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
237 
238         mMediaRecorder.setVideoSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
239         mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
240         mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
241         mMediaRecorder.setVideoEncodingBitRate(calculateBitrate(cameraId));
242         mMediaRecorder.setInputSurface(mRecorderSurface);
243         mMediaRecorder.setVideoFrameRate(30);
244         mMediaRecorder.setOutputFile(outputFile);
245 
246         try {
247             mMediaRecorder.prepare();
248         } catch (IOException e) {
249             throw new ItsException("Error preparing MediaRecorder", e);
250         }
251     }
252 
initEGL()253     private void initEGL() throws ItsException {
254         // set up EGL Display
255         mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
256         if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
257             throw new ItsException("Unable to get EGL display");
258         }
259 
260         int[] version = {0, 0};
261         if (!EGL14.eglInitialize(mEGLDisplay, version, /* majorOffset= */0,
262                 version, /* minorOffset= */1)) {
263             mEGLDisplay = null;
264             throw new ItsException("unable to initialize EGL14");
265         }
266 
267         int[] configAttribList = {
268                 EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
269                 EGL14.EGL_RED_SIZE, 8,
270                 EGL14.EGL_GREEN_SIZE, 8,
271                 EGL14.EGL_BLUE_SIZE, 8,
272                 EGL14.EGL_ALPHA_SIZE, 8,
273                 EGL14.EGL_DEPTH_SIZE, 0,
274                 EGL14.EGL_STENCIL_SIZE, 0,
275                 EGLExt.EGL_RECORDABLE_ANDROID, 1,
276                 EGL14.EGL_NONE
277         };
278 
279         // set up EGL Config
280         EGLConfig[] configs = new EGLConfig[1];
281         int[] numConfigs = {1};
282         EGL14.eglChooseConfig(mEGLDisplay, configAttribList, 0, configs,
283                 0, configs.length, numConfigs, 0);
284         if (configs[0] == null) {
285             throw new ItsException("Unable to initialize EGL config");
286         }
287 
288         EGLConfig EGLConfig = configs[0];
289 
290         int[] contextAttribList = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE };
291 
292         mEGLContext = EGL14.eglCreateContext(mEGLDisplay, EGLConfig, EGL14.EGL_NO_CONTEXT,
293                 contextAttribList, 0);
294         if (mEGLContext == EGL14.EGL_NO_CONTEXT) {
295             throw new ItsException("Failed to create EGL context");
296         }
297 
298         int[] clientVersion = {0};
299         EGL14.eglQueryContext(mEGLDisplay, mEGLContext, EGL14.EGL_CONTEXT_CLIENT_VERSION,
300                 clientVersion, /* offset= */0);
301         Logt.i(TAG, "EGLContext created, client version " + clientVersion[0]);
302 
303         // Create EGL Surface to write to the MediaRecorder Surface.
304         int[] surfaceAttribs = {EGL14.EGL_NONE};
305         mEGLRecorderSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, EGLConfig, mRecorderSurface,
306                 surfaceAttribs, /* offset= */0);
307         if (mEGLRecorderSurface == EGL14.EGL_NO_SURFACE) {
308             throw new ItsException("Failed to create EGL recorder surface");
309         }
310 
311         // Bind EGL context to the current (handler) thread.
312         EGL14.eglMakeCurrent(mEGLDisplay, mEGLRecorderSurface, mEGLRecorderSurface, mEGLContext);
313     }
314 
setupCameraTexture()315     private void setupCameraTexture() throws ItsException {
316         mCameraTexture = new SurfaceTexture(createTexture());
317         mCameraTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
318         mCameraSurface = new Surface(mCameraTexture);
319     }
320 
321     /**
322      * Compiles the vertex and fragment shader into a shader program, and sets up the location
323      * fields that will be written to later.
324      */
compileShaders()325     private void compileShaders() throws ItsException {
326         int vertexShader = createShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);
327         int fragmentShader = createShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER);
328 
329         mGLShaderProgram = GLES20.glCreateProgram();
330         GLES20.glAttachShader(mGLShaderProgram, vertexShader);
331         GLES20.glAttachShader(mGLShaderProgram, fragmentShader);
332         GLES20.glLinkProgram(mGLShaderProgram);
333 
334         int[] linkStatus = {0};
335         GLES20.glGetProgramiv(mGLShaderProgram, GLES20.GL_LINK_STATUS, linkStatus, 0);
336         if (linkStatus[0] == 0) {
337             String msg = "Could not link program: " + GLES20.glGetProgramInfoLog(mGLShaderProgram);
338             GLES20.glDeleteProgram(mGLShaderProgram);
339             throw new ItsException(msg);
340         }
341 
342         mVPositionLoc = GLES20.glGetAttribLocation(mGLShaderProgram, "vPosition");
343         mTexMatrixLoc = GLES20.glGetUniformLocation(mGLShaderProgram, "texMatrix");
344         mTexRotMatrixLoc = GLES20.glGetUniformLocation(mGLShaderProgram, "texRotMatrix");
345         GLES20.glUseProgram(mGLShaderProgram);
346         assertNoGLError("glUseProgram");
347     }
348 
349     /**
350      * Creates a new GLSL texture that can be populated by {@link SurfaceTexture} and returns the
351      * corresponding ID. Throws {@link ItsException} if there is an error creating the textures.
352      */
createTexture()353     private int createTexture() throws ItsException {
354         IntBuffer buffer = IntBuffer.allocate(1);
355         GLES20.glGenTextures(1, buffer);
356         int texId = buffer.get(0);
357 
358         // This flags the texture to be implicitly populated by SurfaceTexture
359         GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
360 
361         GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
362                 GLES20.GL_NEAREST);
363         GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
364                 GLES20.GL_LINEAR);
365         GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
366                 GLES20.GL_CLAMP_TO_EDGE);
367         GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
368                 GLES20.GL_CLAMP_TO_EDGE);
369 
370         boolean isTexture = GLES20.glIsTexture(texId);
371         if (!isTexture) {
372             throw new ItsException("Failed to create texture id. Returned texture id: " + texId);
373         }
374 
375         return texId;
376     }
377 
378     /**
379      * Compiles the gives {@code source} as a shader of the provided {@code type}. Throws an
380      * {@link ItsException} if there are errors while compiling the shader.
381      */
createShader(int type, String source)382     private int createShader(int type, String source) throws ItsException {
383         int shader = GLES20.glCreateShader(type);
384         GLES20.glShaderSource(shader, source);
385         GLES20.glCompileShader(shader);
386 
387         int[] compiled = new int[]{0};
388         GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
389         if (compiled[0] == GLES20.GL_FALSE) {
390             String msg = "Could not compile shader " + type + ": "
391                     + GLES20.glGetShaderInfoLog(shader);
392             GLES20.glDeleteShader(shader);
393             throw new ItsException(msg);
394         }
395 
396         return shader;
397     }
398 
399     /**
400      * Throws an {@link ItsException} if the previous GL call resulted in an error. No-op otherwise.
401      */
assertNoGLError(String op)402     private void assertNoGLError(String op) throws ItsException {
403         int error = GLES20.glGetError();
404         if (error != GLES20.GL_NO_ERROR) {
405             String msg = op + ": glError 0x" + Integer.toHexString(error);
406             throw new ItsException(msg);
407         }
408     }
409 
410     /**
411      * Looks up a reasonable recording bitrate from {@link CamcorderProfile} for the given
412      * {@code mPreviewSize}. This is not the most optimal bitrate, but should be good enough for ITS
413      * tests to run their analyses.
414      */
calculateBitrate(int cameraId)415     private int calculateBitrate(int cameraId) throws ItsException {
416         int previewResolution = mPreviewSize.getHeight() * mPreviewSize.getWidth();
417 
418         List<Pair<Integer, Integer>> resToProfile = new ArrayList<>(mResolutionToCamcorderProfile);
419         // ensure that the list is sorted in ascending order of resolution
420         resToProfile.sort(Comparator.comparingInt(a -> a.first));
421 
422         // Choose the first available resolution that is >= the requested preview size.
423         for (Pair<Integer, Integer> entry : resToProfile) {
424             if (previewResolution > entry.first) continue;
425             if (!CamcorderProfile.hasProfile(cameraId, entry.second)) continue;
426 
427             EncoderProfiles profiles = CamcorderProfile.getAll(
428                     String.valueOf(cameraId), entry.second);
429             if (profiles == null) continue;
430 
431             List<EncoderProfiles.VideoProfile> videoProfiles = profiles.getVideoProfiles();
432             for (EncoderProfiles.VideoProfile profile : videoProfiles) {
433                 if (profile == null) continue;
434                 Logt.i(TAG, "Recording bitrate: " + profile.getBitrate());
435                 return  profile.getBitrate();
436             }
437         }
438 
439         // TODO(b/223439995): There is a bug where some devices might populate result of
440         //                    CamcorderProfile.getAll with nulls even when a given quality is
441         //                    supported. Until this bug is fixed, fall back to the "deprecated"
442         //                    CamcorderProfile.get call to get the video bitrate. This logic can be
443         //                    removed once the bug is fixed.
444         Logt.i(TAG, "No matching EncoderProfile found. Falling back to CamcorderProfiles");
445         // Mimic logic from above, but use CamcorderProfiles instead
446         for (Pair<Integer, Integer> entry : resToProfile) {
447             if (previewResolution > entry.first) continue;
448             if (!CamcorderProfile.hasProfile(cameraId, entry.second)) continue;
449 
450             CamcorderProfile profile = CamcorderProfile.get(cameraId, entry.second);
451             if (profile == null) continue;
452 
453             Logt.i(TAG, "Recording bitrate: " + profile.videoBitRate);
454             return profile.videoBitRate;
455         }
456 
457         // Ideally, we should always find a Camcorder/Encoder Profile corresponding
458         // to the preview size.
459         Logt.w(TAG, "Could not find bitrate for any resolution >= " + mPreviewSize
460                 + " for cameraId " + cameraId + ". Using default bitrate");
461         return DEFAULT_RECORDING_BITRATE;
462     }
463 
464     /**
465      * Copies a frame encoded as a texture by {@code mCameraTexture} to
466      * {@code mRecorderSurface} by running our simple shader program for one frame that draws
467      * to {@code mEGLRecorderSurface}.
468      */
copyFrameToRecorder()469     private void copyFrameToRecorder() throws ItsException {
470         // Clear color buffer
471         GLES20.glClearColor(0f, 0f, 0f, 1f);
472         assertNoGLError("glClearColor");
473         GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
474         assertNoGLError("glClear");
475 
476         // read texture transformation matrix from SurfaceTexture and write it to GLSL program.
477         mCameraTexture.getTransformMatrix(mTransformMatrix);
478         GLES20.glUniformMatrix4fv(mTexMatrixLoc, /* count= */1, /* transpose= */false,
479                 mTransformMatrix, /* offset= */0);
480         assertNoGLError("glUniformMatrix4fv");
481 
482         // write texture rotation matrix to GLSL program
483         GLES20.glUniformMatrix2fv(mTexRotMatrixLoc, /* count= */1, /* transpose= */false,
484                 mTexRotMatrix, /* offset= */0);
485         assertNoGLError("glUniformMatrix2fv");
486 
487         // write vertices of the full-screen rectangle to the GLSL program
488         ByteBuffer nativeBuffer = ByteBuffer.allocateDirect(
489                   FULLSCREEN_VERTICES.length * Float.BYTES);
490         nativeBuffer.order(ByteOrder.nativeOrder());
491         FloatBuffer vertexBuffer = nativeBuffer.asFloatBuffer();
492         vertexBuffer.put(FULLSCREEN_VERTICES);
493         nativeBuffer.position(0);
494         vertexBuffer.position(0);
495 
496         GLES20.glEnableVertexAttribArray(mVPositionLoc);
497         assertNoGLError("glEnableVertexAttribArray");
498         GLES20.glVertexAttribPointer(mVPositionLoc, /* size= */ 2, GLES20.GL_FLOAT,
499                 /* normalized= */ false, /* stride= */ 8, vertexBuffer);
500         assertNoGLError("glVertexAttribPointer");
501 
502 
503         // viewport size should match the frame dimensions to prevent stretching/cropping
504         GLES20.glViewport(0, 0, mPreviewSize.getWidth(), mPreviewSize.getHeight());
505         assertNoGLError("glViewport");
506 
507         GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */0, /* count= */4);
508         assertNoGLError("glDrawArrays");
509 
510         EGL14.eglSwapBuffers(mEGLDisplay, mEGLRecorderSurface); // flush surface
511     }
512 
513     /**
514      * Returns column major 2D rotation matrix that can be fed directly to GLSL.
515      * This matrix rotates around the origin.
516      */
getRotationMatrix(int orientationDegrees)517     private static float[] getRotationMatrix(int orientationDegrees) {
518         double rads = orientationDegrees * Math.PI / 180;
519         return new float[] {
520                 (float) Math.cos(rads), (float) Math.sin(rads),
521                 (float) -Math.sin(rads), (float) Math.cos(rads)
522         };
523     }
524 
getCameraSurface()525     Surface getCameraSurface() {
526         return mCameraSurface;
527     }
528 
529     /**
530      * Records frames from mCameraSurface for the specified {@code durationMs}. This method should
531      * only be called once. Throws {@link ItsException} on subsequent calls.
532      */
recordPreview(long durationMs)533     void recordPreview(long durationMs) throws ItsException {
534         if (mMediaRecorderConsumed) {
535             throw new ItsException("Attempting to record on a stale PreviewRecorder. "
536                     + "Create a new instance instead.");
537         }
538         mMediaRecorderConsumed = true;
539 
540         try {
541             Logt.i(TAG, "Starting Preview Recording.");
542             synchronized (mRecorderLock) {
543                 mIsRecording = true;
544                 mMediaRecorder.start();
545             }
546             Thread.sleep(durationMs);
547         } catch (InterruptedException e) {
548             throw new ItsException("Recording interrupted.", e);
549         } finally {
550             Logt.i(TAG, "Stopping Preview Recording.");
551             synchronized (mRecorderLock) {
552                 mIsRecording = false;
553                 mMediaRecorder.stop();
554             }
555         }
556     }
557 
558     @Override
close()559     public void close() {
560         // synchronized to prevent reads and writes to surfaces while they are being released.
561         synchronized (mRecorderLock) {
562             mCameraSurface.release();
563             mCameraTexture.release();
564             mMediaRecorder.release();
565             mRecorderSurface.release();
566 
567             ConditionVariable cv = new ConditionVariable();
568             cv.close();
569             // GL Cleanup should happen on the thread EGL Context was bound to
570             mHandler.post(() -> {
571                 try {
572                     cleanupEgl();
573                 } finally {
574                     cv.open();
575                 }
576             });
577 
578             // Wait for up to a second for egl to clean up.
579             // Since this is clean up, do nothing if the handler takes longer than 1s.
580             cv.block(/*timeoutMs=*/ 1000);
581         }
582     }
583 
cleanupEgl()584     private void cleanupEgl() {
585         if (mGLShaderProgram == 0) {
586             // egl program was never set up, no cleanup needed
587             return;
588         }
589 
590         Logt.i(TAG, "Cleaning up EGL Context");
591         GLES20.glDeleteProgram(mGLShaderProgram);
592         // Release the egl surfaces and context from the handler
593         EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE,
594                 EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
595         EGL14.eglDestroySurface(mEGLDisplay, mEGLRecorderSurface);
596         EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
597 
598         EGL14.eglTerminate(mEGLDisplay);
599     }
600 }
601