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