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