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