1 /* 2 * Copyright 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 androidx.camera.core.processing; 18 19 import static androidx.camera.core.ImageProcessingUtil.writeJpegBytesToSurface; 20 import static androidx.camera.core.impl.ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE; 21 import static androidx.camera.core.impl.utils.TransformUtils.rotateSize; 22 import static androidx.core.util.Preconditions.checkState; 23 24 import static java.util.Objects.requireNonNull; 25 26 import android.graphics.Bitmap; 27 import android.graphics.ImageFormat; 28 import android.graphics.SurfaceTexture; 29 import android.os.Handler; 30 import android.os.HandlerThread; 31 import android.util.Size; 32 import android.view.Surface; 33 34 import androidx.annotation.IntRange; 35 import androidx.annotation.VisibleForTesting; 36 import androidx.annotation.WorkerThread; 37 import androidx.arch.core.util.Function; 38 import androidx.camera.core.CameraXThreads; 39 import androidx.camera.core.DynamicRange; 40 import androidx.camera.core.Logger; 41 import androidx.camera.core.SurfaceOutput; 42 import androidx.camera.core.SurfaceProcessor; 43 import androidx.camera.core.SurfaceRequest; 44 import androidx.camera.core.impl.utils.MatrixExt; 45 import androidx.camera.core.impl.utils.executor.CameraXExecutors; 46 import androidx.camera.core.impl.utils.futures.Futures; 47 import androidx.camera.core.processing.util.GLUtils.InputFormat; 48 import androidx.concurrent.futures.CallbackToFutureAdapter; 49 50 import com.google.auto.value.AutoValue; 51 import com.google.common.util.concurrent.ListenableFuture; 52 53 import kotlin.Triple; 54 55 import org.jspecify.annotations.NonNull; 56 import org.jspecify.annotations.Nullable; 57 58 import java.io.ByteArrayOutputStream; 59 import java.io.IOException; 60 import java.util.ArrayList; 61 import java.util.Collections; 62 import java.util.Iterator; 63 import java.util.LinkedHashMap; 64 import java.util.List; 65 import java.util.Map; 66 import java.util.concurrent.ExecutionException; 67 import java.util.concurrent.Executor; 68 import java.util.concurrent.RejectedExecutionException; 69 import java.util.concurrent.atomic.AtomicBoolean; 70 71 /** 72 * A default implementation of {@link SurfaceProcessor}. 73 * 74 * <p> This implementation simply copies the frame from the source to the destination with the 75 * transformation defined in {@link SurfaceOutput#updateTransformMatrix}. 76 */ 77 public class DefaultSurfaceProcessor implements SurfaceProcessorInternal, 78 SurfaceTexture.OnFrameAvailableListener { 79 private static final String TAG = "DefaultSurfaceProcessor"; 80 81 private final OpenGlRenderer mGlRenderer; 82 @VisibleForTesting 83 final HandlerThread mGlThread; 84 private final Executor mGlExecutor; 85 @VisibleForTesting 86 final Handler mGlHandler; 87 private final AtomicBoolean mIsReleaseRequested = new AtomicBoolean(false); 88 private final float[] mTextureMatrix = new float[16]; 89 private final float[] mSurfaceOutputMatrix = new float[16]; 90 // Map of current set of available outputs. Only access this on GL thread. 91 @SuppressWarnings("WeakerAccess") /* synthetic access */ 92 final Map<SurfaceOutput, Surface> mOutputSurfaces = new LinkedHashMap<>(); 93 94 // Only access this on GL thread. 95 private int mInputSurfaceCount = 0; 96 // Only access this on GL thread. 97 private boolean mIsReleased = false; 98 // Only access this on GL thread. 99 private final List<PendingSnapshot> mPendingSnapshots = new ArrayList<>(); 100 101 /** Constructs {@link DefaultSurfaceProcessor} with default shaders. */ DefaultSurfaceProcessor(@onNull DynamicRange dynamicRange)102 DefaultSurfaceProcessor(@NonNull DynamicRange dynamicRange) { 103 this(dynamicRange, Collections.emptyMap()); 104 } 105 106 /** 107 * Constructs {@link DefaultSurfaceProcessor} with custom shaders. 108 * 109 * @param shaderProviderOverrides custom shader providers for OpenGL rendering, for each input 110 * format. 111 * @throws IllegalArgumentException if any shaderProvider override provides invalid shader. 112 */ DefaultSurfaceProcessor(@onNull DynamicRange dynamicRange, @NonNull Map<InputFormat, ShaderProvider> shaderProviderOverrides)113 DefaultSurfaceProcessor(@NonNull DynamicRange dynamicRange, 114 @NonNull Map<InputFormat, ShaderProvider> shaderProviderOverrides) { 115 mGlThread = new HandlerThread(CameraXThreads.TAG + "GL Thread"); 116 mGlThread.start(); 117 mGlHandler = new Handler(mGlThread.getLooper()); 118 mGlExecutor = CameraXExecutors.newHandlerExecutor(mGlHandler); 119 mGlRenderer = new OpenGlRenderer(); 120 try { 121 initGlRenderer(dynamicRange, shaderProviderOverrides); 122 } catch (RuntimeException e) { 123 release(); 124 throw e; 125 } 126 } 127 128 /** 129 * {@inheritDoc} 130 */ 131 @Override onInputSurface(@onNull SurfaceRequest surfaceRequest)132 public void onInputSurface(@NonNull SurfaceRequest surfaceRequest) { 133 if (mIsReleaseRequested.get()) { 134 surfaceRequest.willNotProvideSurface(); 135 return; 136 } 137 executeSafely(() -> { 138 mInputSurfaceCount++; 139 SurfaceTexture surfaceTexture = new SurfaceTexture(mGlRenderer.getTextureName()); 140 surfaceTexture.setDefaultBufferSize(surfaceRequest.getResolution().getWidth(), 141 surfaceRequest.getResolution().getHeight()); 142 Surface surface = new Surface(surfaceTexture); 143 surfaceRequest.setTransformationInfoListener(mGlExecutor, transformationInfo -> { 144 InputFormat inputFormat = InputFormat.DEFAULT; 145 if (surfaceRequest.getDynamicRange().is10BitHdr() 146 && transformationInfo.hasCameraTransform()) { 147 inputFormat = InputFormat.YUV; 148 } 149 150 mGlRenderer.setInputFormat(inputFormat); 151 }); 152 surfaceRequest.provideSurface(surface, mGlExecutor, result -> { 153 surfaceRequest.clearTransformationInfoListener(); 154 surfaceTexture.setOnFrameAvailableListener(null); 155 surfaceTexture.release(); 156 surface.release(); 157 mInputSurfaceCount--; 158 checkReadyToRelease(); 159 }); 160 surfaceTexture.setOnFrameAvailableListener(this, mGlHandler); 161 }, surfaceRequest::willNotProvideSurface); 162 } 163 164 /** 165 * {@inheritDoc} 166 */ 167 @Override onOutputSurface(@onNull SurfaceOutput surfaceOutput)168 public void onOutputSurface(@NonNull SurfaceOutput surfaceOutput) { 169 if (mIsReleaseRequested.get()) { 170 surfaceOutput.close(); 171 return; 172 } 173 executeSafely(() -> { 174 Surface surface = surfaceOutput.getSurface(mGlExecutor, event -> { 175 surfaceOutput.close(); 176 Surface removedSurface = mOutputSurfaces.remove(surfaceOutput); 177 if (removedSurface != null) { 178 mGlRenderer.unregisterOutputSurface(removedSurface); 179 } 180 }); 181 mGlRenderer.registerOutputSurface(surface); 182 mOutputSurfaces.put(surfaceOutput, surface); 183 }, surfaceOutput::close); 184 } 185 186 /** 187 * Release the {@link DefaultSurfaceProcessor}. 188 */ 189 @Override release()190 public void release() { 191 if (mIsReleaseRequested.getAndSet(true)) { 192 return; 193 } 194 executeSafely(() -> { 195 mIsReleased = true; 196 checkReadyToRelease(); 197 }); 198 } 199 200 @Override snapshot( @ntRangefrom = 0, to = 100) int jpegQuality, @IntRange(from = 0, to = 359) int rotationDegrees)201 public @NonNull ListenableFuture<Void> snapshot( 202 @IntRange(from = 0, to = 100) int jpegQuality, 203 @IntRange(from = 0, to = 359) int rotationDegrees) { 204 return Futures.nonCancellationPropagating(CallbackToFutureAdapter.getFuture( 205 completer -> { 206 PendingSnapshot pendingSnapshot = PendingSnapshot.of(jpegQuality, 207 rotationDegrees, completer); 208 executeSafely( 209 () -> mPendingSnapshots.add(pendingSnapshot), 210 () -> completer.setException(new Exception( 211 "Failed to snapshot: OpenGLRenderer not ready."))); 212 return "DefaultSurfaceProcessor#snapshot"; 213 })); 214 } 215 216 /** 217 * {@inheritDoc} 218 */ 219 @Override onFrameAvailable(@onNull SurfaceTexture surfaceTexture)220 public void onFrameAvailable(@NonNull SurfaceTexture surfaceTexture) { 221 if (mIsReleaseRequested.get()) { 222 // Ignore frame update if released. 223 return; 224 } 225 surfaceTexture.updateTexImage(); 226 surfaceTexture.getTransformMatrix(mTextureMatrix); 227 // Surface, size and transform matrix for JPEG Surface if exists 228 Triple<Surface, Size, float[]> jpegOutput = null; 229 230 for (Map.Entry<SurfaceOutput, Surface> entry : mOutputSurfaces.entrySet()) { 231 Surface surface = entry.getValue(); 232 SurfaceOutput surfaceOutput = entry.getKey(); 233 surfaceOutput.updateTransformMatrix(mSurfaceOutputMatrix, mTextureMatrix); 234 if (surfaceOutput.getFormat() == INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE) { 235 // Render GPU output directly. 236 try { 237 mGlRenderer.render(surfaceTexture.getTimestamp(), mSurfaceOutputMatrix, 238 surface); 239 } catch (RuntimeException e) { 240 // This should not happen. However, when it happens, we catch the exception 241 // to prevent the crash. 242 Logger.e(TAG, "Failed to render with OpenGL.", e); 243 } 244 } else { 245 checkState(surfaceOutput.getFormat() == ImageFormat.JPEG, 246 "Unsupported format: " + surfaceOutput.getFormat()); 247 checkState(jpegOutput == null, "Only one JPEG output is supported."); 248 jpegOutput = new Triple<>(surface, surfaceOutput.getSize(), 249 mSurfaceOutputMatrix.clone()); 250 } 251 } 252 253 // Execute all pending snapshots. 254 try { 255 takeSnapshotAndDrawJpeg(jpegOutput); 256 } catch (RuntimeException e) { 257 // Propagates error back to the app if failed to take snapshot. 258 failAllPendingSnapshots(e); 259 } 260 } 261 262 /** 263 * Takes a snapshot of the current frame and draws it to given JPEG surface. 264 * 265 * @param jpegOutput The <Surface, Surface size, transform matrix> tuple for drawing. 266 */ 267 @WorkerThread takeSnapshotAndDrawJpeg(@ullable Triple<Surface, Size, float[]> jpegOutput)268 private void takeSnapshotAndDrawJpeg(@Nullable Triple<Surface, Size, float[]> jpegOutput) { 269 if (mPendingSnapshots.isEmpty()) { 270 // No pending snapshot requests, do nothing. 271 return; 272 } 273 274 // No JPEG Surface, fail all snapshot requests. 275 if (jpegOutput == null) { 276 failAllPendingSnapshots(new Exception("Failed to snapshot: no JPEG Surface.")); 277 return; 278 } 279 280 // Write to JPEG surface, once for each snapshot request. 281 try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { 282 byte[] jpegBytes = null; 283 int jpegQuality = -1; 284 int rotationDegrees = -1; 285 Bitmap bitmap = null; 286 Iterator<PendingSnapshot> iterator = mPendingSnapshots.iterator(); 287 while (iterator.hasNext()) { 288 PendingSnapshot pendingSnapshot = iterator.next(); 289 // Take a new snapshot if the rotation is different. 290 if (rotationDegrees != pendingSnapshot.getRotationDegrees() || bitmap == null) { 291 rotationDegrees = pendingSnapshot.getRotationDegrees(); 292 // Recycle the previous bitmap to free up memory. 293 if (bitmap != null) { 294 bitmap.recycle(); 295 } 296 bitmap = getBitmap(jpegOutput.getSecond(), jpegOutput.getThird(), 297 rotationDegrees); 298 // Clear JPEG quality to force re-encoding. 299 jpegQuality = -1; 300 } 301 // Re-encode the bitmap if the quality is different. 302 if (jpegQuality != pendingSnapshot.getJpegQuality()) { 303 outputStream.reset(); 304 jpegQuality = pendingSnapshot.getJpegQuality(); 305 bitmap.compress(Bitmap.CompressFormat.JPEG, jpegQuality, outputStream); 306 jpegBytes = outputStream.toByteArray(); 307 } 308 writeJpegBytesToSurface(jpegOutput.getFirst(), requireNonNull(jpegBytes)); 309 pendingSnapshot.getCompleter().set(null); 310 iterator.remove(); 311 } 312 } catch (IOException e) { 313 failAllPendingSnapshots(e); 314 } 315 } 316 failAllPendingSnapshots(@onNull Throwable throwable)317 private void failAllPendingSnapshots(@NonNull Throwable throwable) { 318 for (PendingSnapshot pendingSnapshot : mPendingSnapshots) { 319 pendingSnapshot.getCompleter().setException(throwable); 320 } 321 mPendingSnapshots.clear(); 322 } 323 getBitmap(@onNull Size size, float @NonNull [] textureTransform, int rotationDegrees)324 private @NonNull Bitmap getBitmap(@NonNull Size size, 325 float @NonNull [] textureTransform, 326 int rotationDegrees) { 327 float[] snapshotTransform = textureTransform.clone(); 328 329 // Rotate the output if requested. 330 MatrixExt.preRotate(snapshotTransform, rotationDegrees, 0.5f, 0.5f); 331 332 // Flip the snapshot. This is for reverting the GL transform added in SurfaceOutputImpl. 333 MatrixExt.preVerticalFlip(snapshotTransform, 0.5f); 334 335 // Update the size based on the rotation degrees. 336 size = rotateSize(size, rotationDegrees); 337 338 // Take a snapshot Bitmap and compress it to JPEG. 339 return mGlRenderer.snapshot(size, snapshotTransform); 340 } 341 342 @WorkerThread checkReadyToRelease()343 private void checkReadyToRelease() { 344 if (mIsReleased && mInputSurfaceCount == 0) { 345 // Once release is called, we can stop sending frame to output surfaces. 346 for (SurfaceOutput surfaceOutput : mOutputSurfaces.keySet()) { 347 surfaceOutput.close(); 348 } 349 for (PendingSnapshot pendingSnapshot : mPendingSnapshots) { 350 pendingSnapshot.getCompleter().setException( 351 new Exception("Failed to snapshot: DefaultSurfaceProcessor is released.")); 352 } 353 mOutputSurfaces.clear(); 354 mGlRenderer.release(); 355 mGlThread.quit(); 356 } 357 } 358 initGlRenderer(@onNull DynamicRange dynamicRange, @NonNull Map<InputFormat, ShaderProvider> shaderProviderOverrides)359 private void initGlRenderer(@NonNull DynamicRange dynamicRange, 360 @NonNull Map<InputFormat, ShaderProvider> shaderProviderOverrides) { 361 ListenableFuture<Void> initFuture = CallbackToFutureAdapter.getFuture(completer -> { 362 executeSafely(() -> { 363 try { 364 mGlRenderer.init(dynamicRange, shaderProviderOverrides); 365 completer.set(null); 366 } catch (RuntimeException e) { 367 completer.setException(e); 368 } 369 }); 370 return "Init GlRenderer"; 371 }); 372 try { 373 initFuture.get(); 374 } catch (ExecutionException | InterruptedException e) { 375 // If the cause is a runtime exception, throw it directly. Otherwise convert to runtime 376 // exception and throw. 377 Throwable cause = e instanceof ExecutionException ? e.getCause() : e; 378 if (cause instanceof RuntimeException) { 379 throw (RuntimeException) cause; 380 } else { 381 throw new IllegalStateException("Failed to create DefaultSurfaceProcessor", cause); 382 } 383 } 384 } 385 executeSafely(@onNull Runnable runnable)386 private void executeSafely(@NonNull Runnable runnable) { 387 executeSafely(runnable, () -> { 388 // Do nothing. 389 }); 390 } 391 executeSafely(@onNull Runnable runnable, @NonNull Runnable onFailure)392 private void executeSafely(@NonNull Runnable runnable, @NonNull Runnable onFailure) { 393 try { 394 mGlExecutor.execute(() -> { 395 if (mIsReleased) { 396 onFailure.run(); 397 } else { 398 runnable.run(); 399 } 400 }); 401 } catch (RejectedExecutionException e) { 402 Logger.w(TAG, "Unable to executor runnable", e); 403 onFailure.run(); 404 } 405 } 406 407 /** 408 * A pending snapshot request to be executed on the next frame available. 409 */ 410 @AutoValue 411 abstract static class PendingSnapshot { 412 413 @IntRange(from = 0, to = 100) getJpegQuality()414 abstract int getJpegQuality(); 415 416 @IntRange(from = 0, to = 359) getRotationDegrees()417 abstract int getRotationDegrees(); 418 getCompleter()419 abstract CallbackToFutureAdapter.@NonNull Completer<Void> getCompleter(); 420 of( @ntRangefrom = 0, to = 100) int jpegQuality, @IntRange(from = 0, to = 359) int rotationDegrees, CallbackToFutureAdapter.@NonNull Completer<Void> completer)421 static @NonNull AutoValue_DefaultSurfaceProcessor_PendingSnapshot of( 422 @IntRange(from = 0, to = 100) int jpegQuality, 423 @IntRange(from = 0, to = 359) int rotationDegrees, 424 CallbackToFutureAdapter.@NonNull Completer<Void> completer) { 425 return new AutoValue_DefaultSurfaceProcessor_PendingSnapshot( 426 jpegQuality, rotationDegrees, completer); 427 } 428 } 429 430 /** 431 * Factory class that produces {@link DefaultSurfaceProcessor}. 432 * 433 * <p> This is for working around the limit that OpenGL cannot be initialized in unit tests. 434 */ 435 public static class Factory { Factory()436 private Factory() { 437 } 438 439 private static Function<DynamicRange, SurfaceProcessorInternal> sSupplier = 440 DefaultSurfaceProcessor::new; 441 442 /** 443 * Creates a new {@link DefaultSurfaceProcessor} with no-op shader. 444 */ newInstance( @onNull DynamicRange dynamicRange)445 public static @NonNull SurfaceProcessorInternal newInstance( 446 @NonNull DynamicRange dynamicRange) { 447 return sSupplier.apply(dynamicRange); 448 } 449 450 /** 451 * Overrides the {@link DefaultSurfaceProcessor} supplier for testing. 452 */ 453 @VisibleForTesting setSupplier( @onNull Function<DynamicRange, SurfaceProcessorInternal> supplier)454 public static void setSupplier( 455 @NonNull Function<DynamicRange, SurfaceProcessorInternal> supplier) { 456 sSupplier = supplier; 457 } 458 } 459 } 460