Implement v1.4 features in testlibs, Add ExtensionsTestlibControl to switch basic and advanced.
- upgrade testlibs to Extensions-Interface v1.4
- implement postview and onCaptureProcessProgressed in HDR extensions
- Add ExtensionsTestlibControl to be able to switch between basic and advanced.
Bug: 285451054
Test: camera-extensions test.
Change-Id: I1008941e97df4c2ea6fe752d58e5f348047af985
diff --git a/camera/camera-testlib-extensions/build.gradle b/camera/camera-testlib-extensions/build.gradle
index 849e115..7e445ca 100644
--- a/camera/camera-testlib-extensions/build.gradle
+++ b/camera/camera-testlib-extensions/build.gradle
@@ -24,6 +24,7 @@
dependencies {
api("androidx.annotation:annotation:1.1.0")
+ implementation(project(":camera:camera-core"))
}
android {
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
index eabb3d2..930f5a2 100755
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/AutoImageCaptureExtenderImpl.java
@@ -247,7 +247,7 @@
@Override
public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
@NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
-
+ process(results);
}
@Override
@@ -264,7 +264,7 @@
public void processWithPostview(
@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
@NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
-
+ throw new UnsupportedOperationException("Postview is not supported");
}
}
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
index 12a4a61..0d74482 100755
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BeautyImageCaptureExtenderImpl.java
@@ -264,6 +264,7 @@
@Override
public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
@NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
+ process(results);
}
@Override
@@ -290,7 +291,7 @@
public void processWithPostview(
@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
@NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
-
+ throw new UnsupportedOperationException("Postview is not supported");
}
}
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
index fbceb95..962d4bf 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/BokehImageCaptureExtenderImpl.java
@@ -238,7 +238,7 @@
@Override
public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
@NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
-
+ process(results);
}
@Override
@@ -265,7 +265,7 @@
public void processWithPostview(
@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
@NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
-
+ throw new UnsupportedOperationException("Postview is not supported");
}
}
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java
index 212bea0..5235e59 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionVersionImpl.java
@@ -32,7 +32,7 @@
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public class ExtensionVersionImpl {
private static final String TAG = "ExtenderVersionImpl";
- private static final String VERSION = "1.2.0";
+ private static final String VERSION = "1.4.0";
public ExtensionVersionImpl() {
}
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionsTestlibControl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionsTestlibControl.java
new file mode 100644
index 0000000..6cf35b5
--- /dev/null
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/ExtensionsTestlibControl.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.extensions.impl;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+/**
+ * An internal utility class that allows tests to specify whether to enable basic extender or
+ * advanced extender of this testlib.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class ExtensionsTestlibControl {
+ public enum ImplementationType {
+ ADVANCED_EXTENDER,
+ BASIC_EXTENDER
+ }
+
+ private static ExtensionsTestlibControl sInstance;
+ private static Object sLock = new Object();
+
+ private ExtensionsTestlibControl() {
+ }
+
+ /**
+ * Gets the singleton instance.
+ */
+ @NonNull
+ public static ExtensionsTestlibControl getInstance() {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new ExtensionsTestlibControl();
+ }
+ return sInstance;
+ }
+ }
+
+ private ImplementationType mImplementationType = ImplementationType.BASIC_EXTENDER;
+
+ /**
+ * Set the implementation type.
+ */
+ public void setImplementationType(@NonNull ImplementationType type) {
+ mImplementationType = type;
+ }
+
+ /**
+ * Gets the implementation type;
+ */
+ @NonNull
+ public ImplementationType getImplementationType() {
+ return mImplementationType;
+ }
+}
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
index 8b7abda..0ba4752 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrImageCaptureExtenderImpl.java
@@ -19,6 +19,7 @@
import android.annotation.SuppressLint;
import android.content.Context;
+import android.graphics.ImageFormat;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
@@ -39,6 +40,7 @@
import java.nio.ByteBuffer;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
@@ -205,6 +207,7 @@
@RequiresApi(23)
static final class HdrImageCaptureExtenderCaptureProcessorImpl implements CaptureProcessorImpl {
private ImageWriter mImageWriter;
+ private Surface mPostViewSurface;
@Override
public void onOutputSurface(@NonNull Surface surface, int imageFormat) {
@@ -213,8 +216,22 @@
@Override
public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results) {
+ processInternal(results, null, null, false);
+ }
+
+ @Override
+ public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
+ @NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
+ processInternal(results, resultCallback, executor, false);
+ }
+
+ public void processInternal(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
+ @Nullable ProcessResultImpl resultCallback, @Nullable Executor executor,
+ boolean hasPostview) {
Log.d(TAG, "Started HDR CaptureProcessor");
+ Executor executorForCallback = executor != null ? executor : (cmd) -> cmd.run();
+
// Check for availability of all requested images
if (!results.containsKey(UNDER_STAGE_ID)) {
Log.w(TAG,
@@ -246,6 +263,9 @@
// Do processing here
// The sample here simply returns the normal image result
Image normalImage = imageDataPairs.get(NORMAL_STAGE_ID).first;
+ if (hasPostview) {
+ YuvToJpegConverter.writeYuvToJpegSurface(normalImage, mPostViewSurface);
+ }
if (outputImage.getWidth() != normalImage.getWidth()
|| outputImage.getHeight() != normalImage.getHeight()) {
@@ -256,6 +276,12 @@
outputImage.getHeight()));
}
+ if (resultCallback != null) {
+ executorForCallback.execute(() -> {
+ resultCallback.onCaptureProcessProgressed(10);
+ });
+ }
+
try {
// copy y plane
Image.Plane inYPlane = normalImage.getPlanes()[0];
@@ -274,6 +300,11 @@
}
}
+ if (resultCallback != null) {
+ executorForCallback.execute(
+ () -> resultCallback.onCaptureProcessProgressed(50));
+ }
+
// Copy UV
for (int i = 1; i < 3; i++) {
Image.Plane inPlane = normalImage.getPlanes()[i];
@@ -302,16 +333,35 @@
}
mImageWriter.queueInputImage(outputImage);
+ if (resultCallback != null) {
+ executorForCallback.execute(
+ () -> resultCallback.onCaptureProcessProgressed(100));
+ }
+
+ TotalCaptureResult captureResult = results.get(NORMAL_STAGE_ID).second;
+
+ if (resultCallback != null) {
+ executorForCallback.execute(
+ () -> resultCallback.onCaptureCompleted(
+ captureResult.get(CaptureResult.SENSOR_TIMESTAMP),
+ getFilteredResults(captureResult)));
+ }
Log.d(TAG, "Completed HDR CaptureProcessor");
}
- @Override
- public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
- @NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
+ @SuppressWarnings("unchecked")
+ private List<Pair<CaptureResult.Key, Object>> getFilteredResults(
+ TotalCaptureResult captureResult) {
+ List<Pair<CaptureResult.Key, Object>> list = new ArrayList<>();
+ for (CaptureResult.Key key : captureResult.getKeys()) {
+ list.add(new Pair<>(key, captureResult.get(key)));
+ }
+ return list;
}
+
@Override
public void onResolutionUpdate(@NonNull Size size) {
@@ -324,19 +374,20 @@
@Override
public void onPostviewOutputSurface(@NonNull Surface surface) {
-
+ mPostViewSurface = surface;
}
@Override
public void onResolutionUpdate(@NonNull Size size, @NonNull Size postviewSize) {
-
+ onResolutionUpdate(size);
}
@Override
public void processWithPostview(
@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
@NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
-
+ Log.d(TAG, "processWithPostview");
+ processInternal(results, resultCallback, executor, true);
}
}
@@ -354,12 +405,13 @@
@Nullable
@Override
public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(@NonNull Size captureSize) {
- return null;
+ Pair<Integer, Size[]> pair = new Pair<>(ImageFormat.JPEG, new Size[] {captureSize});
+ return Arrays.asList(pair);
}
@Override
public boolean isCaptureProcessProgressAvailable() {
- return false;
+ return true;
}
@Nullable
@@ -370,7 +422,7 @@
@Override
public boolean isPostviewAvailable() {
- return false;
+ return true;
}
@NonNull
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
index e0cab8d..60d2888 100644
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/HdrPreviewExtenderImpl.java
@@ -48,6 +48,8 @@
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
public final class HdrPreviewExtenderImpl implements PreviewExtenderImpl {
+ private static final String TAG = "HdrPreviewExtenderImpl";
+
private static final int DEFAULT_STAGE_ID = 0;
@Nullable
@@ -175,7 +177,7 @@
@Override
public void process(Image image, TotalCaptureResult result,
ProcessResultImpl resultCallback, Executor executor) {
-
+ process(image, result);
}
@Override
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
index fbb9b03..b3e38b7 100755
--- a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/NightImageCaptureExtenderImpl.java
@@ -239,6 +239,7 @@
@Override
public void process(@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
@NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
+ process(results);
}
@Override
@@ -263,7 +264,7 @@
public void processWithPostview(
@NonNull Map<Integer, Pair<Image, TotalCaptureResult>> results,
@NonNull ProcessResultImpl resultCallback, @Nullable Executor executor) {
-
+ throw new UnsupportedOperationException("Postview is not supported");
}
}
diff --git a/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/YuvToJpegConverter.java b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/YuvToJpegConverter.java
new file mode 100644
index 0000000..0681b97
--- /dev/null
+++ b/camera/camera-testlib-extensions/src/main/java/androidx/camera/extensions/impl/YuvToJpegConverter.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.extensions.impl;
+
+import android.graphics.ImageFormat;
+import android.graphics.Rect;
+import android.graphics.YuvImage;
+import android.media.Image;
+import android.view.Surface;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.ImageProcessingUtil;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+
+@RequiresApi(23)
+class YuvToJpegConverter {
+ private YuvToJpegConverter() {}
+ public static void writeYuvToJpegSurface(@NonNull Image yuvMediaImage,
+ @NonNull Surface jpegSurface) {
+ byte[] yuvBytes = yuv_420_888toNv21(yuvMediaImage);
+ YuvImage yuvImage = new YuvImage(yuvBytes, ImageFormat.NV21, yuvMediaImage.getWidth(),
+ yuvMediaImage.getHeight(), null);
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ yuvImage.compressToJpeg(
+ new Rect(0, 0, yuvMediaImage.getWidth(), yuvMediaImage.getHeight()),
+ 95, byteArrayOutputStream);
+ ImageProcessingUtil.writeJpegBytesToSurface(jpegSurface,
+ byteArrayOutputStream.toByteArray());
+ yuvMediaImage.getPlanes()[0].getBuffer().rewind();
+ }
+
+ @NonNull
+ private static byte[] yuv_420_888toNv21(@NonNull Image image) {
+ Image.Plane yPlane = image.getPlanes()[0];
+ Image.Plane uPlane = image.getPlanes()[1];
+ Image.Plane vPlane = image.getPlanes()[2];
+
+ ByteBuffer yBuffer = yPlane.getBuffer();
+ ByteBuffer uBuffer = uPlane.getBuffer();
+ ByteBuffer vBuffer = vPlane.getBuffer();
+ yBuffer.rewind();
+ uBuffer.rewind();
+ vBuffer.rewind();
+
+ int ySize = yBuffer.remaining();
+
+ int position = 0;
+ byte[] nv21 = new byte[ySize + (image.getWidth() * image.getHeight() / 2)];
+
+ // Add the full y buffer to the array. If rowStride > 1, some padding may be skipped.
+ for (int row = 0; row < image.getHeight(); row++) {
+ yBuffer.get(nv21, position, image.getWidth());
+ position += image.getWidth();
+ yBuffer.position(
+ Math.min(ySize, yBuffer.position() - image.getWidth() + yPlane.getRowStride()));
+ }
+
+ int chromaHeight = image.getHeight() / 2;
+ int chromaWidth = image.getWidth() / 2;
+ int vRowStride = vPlane.getRowStride();
+ int uRowStride = uPlane.getRowStride();
+ int vPixelStride = vPlane.getPixelStride();
+ int uPixelStride = uPlane.getPixelStride();
+
+ // Interleave the u and v frames, filling up the rest of the buffer. Use two line buffers to
+ // perform faster bulk gets from the byte buffers.
+ byte[] vLineBuffer = new byte[vRowStride];
+ byte[] uLineBuffer = new byte[uRowStride];
+ for (int row = 0; row < chromaHeight; row++) {
+ vBuffer.get(vLineBuffer, 0, Math.min(vRowStride, vBuffer.remaining()));
+ uBuffer.get(uLineBuffer, 0, Math.min(uRowStride, uBuffer.remaining()));
+ int vLineBufferPosition = 0;
+ int uLineBufferPosition = 0;
+ for (int col = 0; col < chromaWidth; col++) {
+ nv21[position++] = vLineBuffer[vLineBufferPosition];
+ nv21[position++] = uLineBuffer[uLineBufferPosition];
+ vLineBufferPosition += vPixelStride;
+ uLineBufferPosition += uPixelStride;
+ }
+ }
+
+ return nv21;
+ }
+}