Use new image capture pipeline for software JPEG path / remove unused classes and methods.
1. use new image capture pipeline for software JPEG path
2. add test in coretestapp/ImageCaptureTest for saving file with software JPEG capture
3. remove isRGBA8888Required
4. remove unused classes and methods.
Test: all auto tests
Bug: 260184815
Change-Id: I0dd21967c4bf12617bc6938c6778f998c2004dec
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerDeviceTest.kt b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerDeviceTest.kt
index c7ef572..053529d 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerDeviceTest.kt
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerDeviceTest.kt
@@ -21,8 +21,6 @@
import android.hardware.camera2.CameraCharacteristics
import android.os.Handler
import android.os.Looper
-import android.util.Size
-import android.view.Surface
import androidx.camera.camera2.Camera2Config
import androidx.camera.camera2.internal.Camera2CameraFactory
import androidx.camera.camera2.internal.compat.quirk.DeviceQuirks
@@ -38,10 +36,8 @@
import androidx.camera.core.impl.CameraConfig
import androidx.camera.core.impl.CameraInfoInternal
import androidx.camera.core.impl.CameraThreadConfig
-import androidx.camera.core.impl.CaptureProcessor
import androidx.camera.core.impl.Config
import androidx.camera.core.impl.Identifier
-import androidx.camera.core.impl.ImageProxyBundle
import androidx.camera.core.impl.MutableOptionsBundle
import androidx.camera.core.impl.SessionProcessor
import androidx.camera.core.impl.SurfaceCombination
@@ -372,21 +368,4 @@
assertThat(latch.await(timeout, TimeUnit.MILLISECONDS)).isTrue()
}
}
-
- private class FakePreviewCaptureProcessor : CaptureProcessor {
- override fun onOutputSurface(surface: Surface, imageFormat: Int) {
- // No-op
- }
-
- override fun process(bundle: ImageProxyBundle) {
- bundle.captureIds.forEach {
- val image = bundle.getImageProxy(it).get()
- image.close()
- }
- }
-
- override fun onResolutionUpdate(size: Size) {
- // No-op
- }
- }
}
\ No newline at end of file
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/ProcessingImageReaderDeviceTest.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/ProcessingImageReaderDeviceTest.kt
deleted file mode 100644
index 7d1c8fd..0000000
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/ProcessingImageReaderDeviceTest.kt
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright 2020 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.core
-
-import android.graphics.ImageFormat
-import android.media.ImageWriter
-import android.util.Pair
-import android.util.Size
-import android.view.Surface
-import androidx.camera.core.impl.CameraCaptureCallback
-import androidx.camera.core.impl.CaptureBundle
-import androidx.camera.core.impl.CaptureProcessor
-import androidx.camera.core.impl.ImageProxyBundle
-import androidx.camera.core.impl.ImageReaderProxy
-import androidx.camera.core.impl.TagBundle
-import androidx.camera.core.impl.utils.executor.CameraXExecutors
-import androidx.camera.testing.fakes.FakeCameraCaptureResult
-import androidx.camera.testing.fakes.FakeCaptureStage
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SdkSuppress
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.async
-import kotlinx.coroutines.runBlocking
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import kotlin.coroutines.resume
-import kotlin.coroutines.suspendCoroutine
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-@SdkSuppress(minSdkVersion = 23) // This test uses ImageWriter which is supported from api 23.
-class ProcessingImageReaderDeviceTest {
- private companion object Bundle {
- private const val CAPTURE_ID_0 = 0
- private const val CAPTURE_ID_1 = 1
- private const val TIMESTAMP_0 = 0L
- private const val TIMESTAMP_1 = 1000L
- }
-
- // A processor that will generate a garbage image but has the timestamp of the first image in
- // the bundle
- private val mProcessor = object : CaptureProcessor {
- private lateinit var mImageWriter: ImageWriter
-
- override fun onOutputSurface(surface: Surface, imageFormat: Int) {
- mImageWriter = ImageWriter.newInstance(surface, 2)
- }
-
- override fun process(bundle: ImageProxyBundle) {
- val image = mImageWriter.dequeueInputImage()
- image.timestamp = bundle.getImageProxy(bundle.captureIds[0]).get().imageInfo.timestamp
- mImageWriter.queueInputImage(image)
- }
-
- override fun onResolutionUpdate(size: Size) = Unit
- }
-
- private val mCaptureStage0 = FakeCaptureStage(CAPTURE_ID_0, null)
- private val mCaptureStage1 = FakeCaptureStage(CAPTURE_ID_1, null)
-
- private lateinit var mCaptureBundle: CaptureBundle
-
- @Before
- fun setUp() {
- mCaptureBundle = CaptureBundles.createCaptureBundle(mCaptureStage0, mCaptureStage1)
- }
-
- @Test
- fun processesImage_whenImageInBundleEnqueued() = runBlocking {
- val processingImageReader = ProcessingImageReader.Builder(
- 640,
- 480,
- ImageFormat.YUV_420_888,
- 2,
- mCaptureBundle,
- mProcessor
- ).build()
-
- val job = async {
- suspendCoroutine<ImageProxy?> { cont ->
- // Waiting on the ProcessingImageReader to produce an ImageProxy
- processingImageReader.setOnImageAvailableListener(
- ImageReaderProxy.OnImageAvailableListener { imageReader ->
- cont.resume(imageReader.acquireNextImage())
- },
- CameraXExecutors.directExecutor()
- )
-
- processingImageReader.setCaptureBundle(mCaptureBundle)
- val imageWriter = ImageWriter.newInstance(processingImageReader.surface!!, 2)
- val callback = processingImageReader.cameraCaptureCallback!!
-
- // Trigger the bundle of images required for processing to occur
- triggerImage(imageWriter, callback, TIMESTAMP_0, CAPTURE_ID_0)
- triggerImage(imageWriter, callback, TIMESTAMP_1, CAPTURE_ID_1)
- }
- }
- val image = job.await()
-
- // Check the values of the images that are captured
- assertThat(image).isNotNull()
- assertThat(image!!.imageInfo.timestamp).isEqualTo(TIMESTAMP_0)
- }
-
- private fun triggerImage(
- imageWriter: ImageWriter,
- callback: CameraCaptureCallback,
- timestamp: Long,
- captureId: Int
- ) {
- val image = imageWriter.dequeueInputImage()
- image.timestamp = timestamp
- imageWriter.queueInputImage(image)
- val fakeCameraCaptureResult = FakeCameraCaptureResult()
- fakeCameraCaptureResult.timestamp = timestamp
- val tagBundle = TagBundle.create(
- Pair(
- mCaptureBundle.hashCode().toString(),
- captureId
- )
- )
- fakeCameraCaptureResult.setTag(tagBundle)
- callback.onCaptureCompleted(fakeCameraCaptureResult)
- }
-}
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/SurfaceRequestTest.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/SurfaceRequestTest.kt
index d5b344c..ebe18d2 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/SurfaceRequestTest.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/SurfaceRequestTest.kt
@@ -351,7 +351,7 @@
autoCleanup: Boolean = true,
onInvalidated: () -> Unit = {},
): SurfaceRequest {
- val request = SurfaceRequest(size, FakeCamera(), false, expectedFrameRate, onInvalidated)
+ val request = SurfaceRequest(size, FakeCamera(), expectedFrameRate, onInvalidated)
if (autoCleanup) {
surfaceRequests.add(request)
}
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt
index 884de88..a16b6f3 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/processing/DefaultSurfaceProcessorTest.kt
@@ -303,7 +303,7 @@
}
private fun createInputSurfaceRequest(): SurfaceRequest {
- return SurfaceRequest(Size(WIDTH, HEIGHT), fakeCamera, false) {}.apply {
+ return SurfaceRequest(Size(WIDTH, HEIGHT), fakeCamera) {}.apply {
inputSurfaceRequestsToClose.add(this)
}
}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
index 17254cc..45076b8 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
@@ -17,7 +17,6 @@
package androidx.camera.core;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_BUFFER_FORMAT;
-import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_CAPTURE_BUNDLE;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_CAPTURE_CONFIG_UNPACKER;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_DEFAULT_CAPTURE_CONFIG;
import static androidx.camera.core.impl.ImageCaptureConfig.OPTION_DEFAULT_SESSION_CONFIG;
@@ -92,10 +91,7 @@
import androidx.camera.core.impl.CameraConfig;
import androidx.camera.core.impl.CameraInfoInternal;
import androidx.camera.core.impl.CameraInternal;
-import androidx.camera.core.impl.CaptureBundle;
import androidx.camera.core.impl.CaptureConfig;
-import androidx.camera.core.impl.CaptureProcessor;
-import androidx.camera.core.impl.CaptureStage;
import androidx.camera.core.impl.Config;
import androidx.camera.core.impl.ConfigProvider;
import androidx.camera.core.impl.DeferrableSurface;
@@ -117,7 +113,6 @@
import androidx.camera.core.impl.utils.futures.Futures;
import androidx.camera.core.internal.IoConfig;
import androidx.camera.core.internal.TargetConfig;
-import androidx.camera.core.internal.YuvToJpegProcessor;
import androidx.camera.core.internal.compat.quirk.SoftwareJpegEncodingPreferredQuirk;
import androidx.camera.core.internal.compat.workaround.ExifRotationAvailability;
import androidx.camera.core.internal.utils.ImageUtil;
@@ -136,6 +131,7 @@
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
@@ -143,12 +139,8 @@
import java.util.UUID;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -318,9 +310,6 @@
////////////////////////////////////////////////////////////////////////////////////////////
// [UseCase attached constant] - Is only valid when the UseCase is attached to a camera.
////////////////////////////////////////////////////////////////////////////////////////////
-
- private ExecutorService mExecutor;
-
private CaptureConfig mCaptureConfig;
/**
@@ -339,11 +328,6 @@
@SuppressWarnings("WeakerAccess")
SafeCloseImageReaderProxy mImageReader;
- @SuppressWarnings("WeakerAccess")
- ProcessingImageReader mProcessingImageReader;
-
- private ListenableFuture<Void> mImageReaderCloseFuture = Futures.immediateFuture(null);
-
/** Callback used to match the {@link ImageProxy} with the {@link ImageInfo}. */
private CameraCaptureCallback mMetadataMatchingCaptureCallback;
@@ -391,7 +375,6 @@
return createPipelineWithNode(cameraId, config, resolution);
}
SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config);
- YuvToJpegProcessor softwareJpegProcessor = null;
if (Build.VERSION.SDK_INT >= 23 && getCaptureMode() == CAPTURE_MODE_ZERO_SHUTTER_LAG) {
getCameraControl().addZslConfig(sessionConfigBuilder);
@@ -419,32 +402,6 @@
throw new IllegalArgumentException("Unsupported image format:" + getImageFormat());
}
mImageReader = new SafeCloseImageReaderProxy(imageReader);
- } else if (mUseSoftwareJpeg) {
- CaptureProcessor captureProcessor;
- int inputFormat = getImageFormat();
- // API check to satisfy linter
- if (Build.VERSION.SDK_INT >= 26) {
- Logger.i(TAG, "Using software JPEG encoder.");
- captureProcessor = softwareJpegProcessor =
- new YuvToJpegProcessor(getJpegQualityInternal(), MAX_IMAGES);
- } else {
- // Note: This should never be hit due to SDK_INT check before setting
- // useSoftwareJpeg.
- throw new IllegalStateException("Software JPEG only supported on API 26+");
- }
-
- // TODO: To allow user to use an Executor for the image processing.
- mProcessingImageReader = new ProcessingImageReader.Builder(
- resolution.getWidth(),
- resolution.getHeight(),
- inputFormat,
- MAX_IMAGES,
- CaptureBundles.singleDefaultCaptureBundle(),
- captureProcessor
- ).setPostProcessExecutor(mExecutor).setOutputFormat(ImageFormat.JPEG).build();
-
- mMetadataMatchingCaptureCallback = mProcessingImageReader.getCameraCaptureCallback();
- mImageReader = new SafeCloseImageReaderProxy(mProcessingImageReader);
} else {
MetadataImageReader metadataImageReader = new MetadataImageReader(resolution.getWidth(),
resolution.getHeight(), getImageFormat(), MAX_IMAGES);
@@ -457,27 +414,8 @@
new CancellationException("Request is canceled."));
}
- final YuvToJpegProcessor finalSoftwareJpegProcessor = softwareJpegProcessor;
-
mImageCaptureRequestProcessor = new ImageCaptureRequestProcessor(MAX_IMAGES,
- this::takePictureInternal, finalSoftwareJpegProcessor == null ? null :
- (ImageCaptureRequestProcessor.RequestProcessCallback) imageCaptureRequest -> {
- //noinspection ConstantConditions
- if (Build.VERSION.SDK_INT >= 26) {
- // Updates output JPEG compression quality of YuvToJpegProcessor
- // according to current request. This was determined by whether the
- // final output image needs to be cropped (uncompress and recompress)
- // again when the capture request was created.
- finalSoftwareJpegProcessor.setJpegQuality(
- imageCaptureRequest.mJpegQuality);
-
- // Updates output rotation degrees value to the YuvToJpegProcessor so
- // that it can write the correct value to the ExifData in the output
- // JPEG image file.
- finalSoftwareJpegProcessor.setRotationDegrees(
- imageCaptureRequest.mRotationDegrees);
- }
- });
+ this::takePictureInternal);
// By default close images that come from the listener.
mImageReader.setOnImageAvailableListener(mClosingListener,
@@ -494,9 +432,6 @@
/* get the surface image format using getImageFormat */
getImageFormat());
- mImageReaderCloseFuture =
- mProcessingImageReader != null ? mProcessingImageReader.getCloseFuture()
- : Futures.immediateFuture(null);
mDeferrableSurface.getTerminationFuture().addListener(mImageReader::safeClose,
CameraXExecutors.mainThreadExecutor());
@@ -559,8 +494,6 @@
DeferrableSurface deferrableSurface = mDeferrableSurface;
mDeferrableSurface = null;
mImageReader = null;
- mProcessingImageReader = null;
- mImageReaderCloseFuture = Futures.immediateFuture(null);
if (deferrableSurface != null) {
deferrableSurface.close();
@@ -611,9 +544,8 @@
@Override
protected UseCaseConfig<?> onMergeConfig(@NonNull CameraInfoInternal cameraInfo,
@NonNull UseCaseConfig.Builder<?, ?, ?> builder) {
- if (cameraInfo.getCameraQuirks().contains(
- SoftwareJpegEncodingPreferredQuirk.class)) {
- // Request software JPEG encoder if quirk exists on this device and the software JPEG
+ if (cameraInfo.getCameraQuirks().contains(SoftwareJpegEncodingPreferredQuirk.class)) {
+ // Request software JPEG encoder if quirk exists on this device, and the software JPEG
// option has not already been explicitly set.
if (Boolean.FALSE.equals(builder.getMutableConfig().retrieveOption(
OPTION_USE_SOFTWARE_JPEG_ENCODER, true))) {
@@ -625,7 +557,7 @@
}
}
- // If software JPEG is requested, disable if it can't be supported on current API level.
+ // If software JPEG is requested, disable if it is incompatible.
boolean useSoftwareJpeg = enforceSoftwareJpegConstraints(builder.getMutableConfig());
// Update the input format base on the other options set (mainly whether processing
@@ -1293,11 +1225,10 @@
completer.setException(throwable);
}
},
- mExecutor);
+ CameraXExecutors.mainThreadExecutor());
completer.addCancellationListener(() -> future.cancel(true),
CameraXExecutors.directExecutor());
-
return "takePictureInternal";
});
}
@@ -1328,24 +1259,13 @@
@GuardedBy("mLock")
private final ImageCaptor mImageCaptor;
-
private final int mMaxImages;
-
- @Nullable
- private final RequestProcessCallback mRequestProcessCallback;
-
@SuppressWarnings("WeakerAccess") /* synthetic accessor */
final Object mLock = new Object();
ImageCaptureRequestProcessor(int maxImages, @NonNull ImageCaptor imageCaptor) {
- this(maxImages, imageCaptor, null);
- }
-
- ImageCaptureRequestProcessor(int maxImages, @NonNull ImageCaptor imageCaptor,
- @Nullable RequestProcessCallback requestProcessCallback) {
mMaxImages = maxImages;
mImageCaptor = imageCaptor;
- mRequestProcessCallback = requestProcessCallback;
}
/**
@@ -1448,9 +1368,6 @@
}
mCurrentRequest = imageCaptureRequest;
- if (mRequestProcessCallback != null) {
- mRequestProcessCallback.onPreProcessRequest(mCurrentRequest);
- }
mCurrentRequestFuture = mImageCaptor.capture(imageCaptureRequest);
Futures.addCallback(mCurrentRequestFuture, new FutureCallback<ImageProxy>() {
@Override
@@ -1539,17 +1456,16 @@
*
* @return {@code true} if software JPEG will be used after applying constraints.
*/
- static boolean enforceSoftwareJpegConstraints(@NonNull MutableConfig mutableConfig) {
+ boolean enforceSoftwareJpegConstraints(@NonNull MutableConfig mutableConfig) {
// Software encoder currently only supports API 26+.
if (Boolean.TRUE.equals(
mutableConfig.retrieveOption(OPTION_USE_SOFTWARE_JPEG_ENCODER, false))) {
boolean supported = true;
- if (Build.VERSION.SDK_INT < 26) {
- Logger.w(TAG, "Software JPEG only supported on API 26+, but current API level is "
- + Build.VERSION.SDK_INT);
+ if (isSessionProcessorEnabledInCurrentCamera()) {
+ // SessionProcessor requires JPEG input format so it is incompatible with SW Jpeg.
+ Logger.w(TAG, "Software JPEG cannot be used with Extensions.");
supported = false;
}
-
Integer bufferFormat = mutableConfig.retrieveOption(OPTION_BUFFER_FORMAT, null);
if (bufferFormat != null && bufferFormat != ImageFormat.JPEG) {
Logger.w(TAG, "Software JPEG cannot be used with non-JPEG output buffer format.");
@@ -1574,20 +1490,9 @@
@RestrictTo(Scope.LIBRARY_GROUP)
@Override
public void onDetached() {
- ListenableFuture<Void> imageReaderCloseFuture = mImageReaderCloseFuture;
-
abortImageCaptureRequests();
clearPipeline();
mUseSoftwareJpeg = false;
-
- // Shutdowns the executor after mImageReader is closed. This can avoid
- // RejectedExecutionException if a ProcessingImageReader is used to processing the
- // captured images.
- ExecutorService executorService = mExecutor;
- if (executorService != null) {
- imageReaderCloseFuture.addListener(executorService::shutdown,
- CameraXExecutors.directExecutor());
- }
}
/**
@@ -1609,21 +1514,6 @@
CameraInternal camera = getCamera();
checkNotNull(camera, "Attached camera cannot be null");
-
- mExecutor =
- Executors.newFixedThreadPool(
- 1,
- new ThreadFactory() {
- private final AtomicInteger mId = new AtomicInteger(0);
-
- @Override
- public Thread newThread(@NonNull Runnable r) {
- return new Thread(
- r,
- CameraXThreads.TAG + "image_capture_"
- + mId.getAndIncrement());
- }
- });
}
/**
@@ -1658,80 +1548,33 @@
ListenableFuture<Void> issueTakePicture(@NonNull ImageCaptureRequest imageCaptureRequest) {
Logger.d(TAG, "issueTakePicture");
- final List<CaptureConfig> captureConfigs = new ArrayList<>();
- String tagBundleKey = null;
+ final CaptureConfig.Builder builder = new CaptureConfig.Builder();
+ builder.setTemplateType(mCaptureConfig.getTemplateType());
- CaptureBundle captureBundle;
- if (mProcessingImageReader != null) {
- // If the Processor is provided, check if we have valid CaptureBundle and update
- // ProcessingImageReader before actually issuing a take picture request.
- captureBundle = CaptureBundles.singleDefaultCaptureBundle();
- mProcessingImageReader.setCaptureBundle(captureBundle);
- mProcessingImageReader.setOnProcessingErrorCallback(
- CameraXExecutors.directExecutor(),
- (message, cause) -> {
- Logger.e(TAG, "Processing image failed! " + message);
- imageCaptureRequest.notifyCallbackError(ERROR_CAPTURE_FAILED, message,
- cause);
- });
- tagBundleKey = mProcessingImageReader.getTagBundleKey();
- } else {
- captureBundle = CaptureBundles.singleDefaultCaptureBundle();
- if (captureBundle == null) {
- return Futures.immediateFailedFuture(new IllegalArgumentException(
- "ImageCapture cannot set empty CaptureBundle."));
- }
+ // Add the default implementation options of ImageCapture
+ builder.addImplementationOptions(mCaptureConfig.getImplementationOptions());
+ builder.addAllCameraCaptureCallbacks(
+ mSessionConfigBuilder.getSingleCameraCaptureCallbacks());
- List<CaptureStage> captureStages = captureBundle.getCaptureStages();
- if (captureStages == null) {
- return Futures.immediateFailedFuture(new IllegalArgumentException(
- "ImageCapture has CaptureBundle with null capture stages"));
- }
+ builder.addSurface(mDeferrableSurface);
- if (captureStages.size() > 1) {
- return Futures.immediateFailedFuture(new IllegalArgumentException(
- "ImageCapture have no CaptureProcess set with CaptureBundle size > 1."));
+ // Only sets the JPEG rotation and quality capture request options when capturing
+ // images in JPEG format. Some devices do not handle these CaptureRequest key values
+ // when capturing a non-JPEG image. Setting these capture requests and checking the
+ // returned capture results for specific purpose might cause problems. See b/204375890.
+ if (getImageFormat() == ImageFormat.JPEG) {
+ // Add the dynamic implementation options of ImageCapture
+ if (EXIF_ROTATION_AVAILABILITY.isRotationOptionSupported()) {
+ builder.addImplementationOption(CaptureConfig.OPTION_ROTATION,
+ imageCaptureRequest.mRotationDegrees);
}
+ builder.addImplementationOption(CaptureConfig.OPTION_JPEG_QUALITY,
+ imageCaptureRequest.mJpegQuality);
}
- for (final CaptureStage captureStage : captureBundle.getCaptureStages()) {
- final CaptureConfig.Builder builder = new CaptureConfig.Builder();
- builder.setTemplateType(mCaptureConfig.getTemplateType());
+ builder.addCameraCaptureCallback(mMetadataMatchingCaptureCallback);
- // Add the default implementation options of ImageCapture
- builder.addImplementationOptions(mCaptureConfig.getImplementationOptions());
- builder.addAllCameraCaptureCallbacks(
- mSessionConfigBuilder.getSingleCameraCaptureCallbacks());
-
- builder.addSurface(mDeferrableSurface);
-
- // Only sets the JPEG rotation and quality capture request options when capturing
- // images in JPEG format. Some devices do not handle these CaptureRequest key values
- // when capturing a non-JPEG image. Setting these capture requests and checking the
- // returned capture results for specific purpose might cause problems. See b/204375890.
- if (getImageFormat() == ImageFormat.JPEG) {
- // Add the dynamic implementation options of ImageCapture
- if (EXIF_ROTATION_AVAILABILITY.isRotationOptionSupported()) {
- builder.addImplementationOption(CaptureConfig.OPTION_ROTATION,
- imageCaptureRequest.mRotationDegrees);
- }
- builder.addImplementationOption(CaptureConfig.OPTION_JPEG_QUALITY,
- imageCaptureRequest.mJpegQuality);
- }
-
- // Add the implementation options required by the CaptureStage
- builder.addImplementationOptions(
- captureStage.getCaptureConfig().getImplementationOptions());
-
- // Use CaptureBundle object as the key for TagBundle
- if (tagBundleKey != null) {
- builder.addTag(tagBundleKey, captureStage.getId());
- }
- builder.addCameraCaptureCallback(mMetadataMatchingCaptureCallback);
- captureConfigs.add(builder.build());
- }
-
- return submitStillCaptureRequest(captureConfigs);
+ return submitStillCaptureRequest(Arrays.asList(builder.build()));
}
/**
@@ -1790,33 +1633,17 @@
return false;
}
if (isSessionProcessorEnabledInCurrentCamera()) {
- // Use old pipeline for advanced Extensions.
+ // Use old pipeline when extension is enabled.
return false;
}
- if (getCaptureStageSize(config) > 1) {
- // Use old pipeline for multiple stages capture.
- return false;
- }
- if (requireNonNull(config.retrieveOption(OPTION_INPUT_FORMAT, ImageFormat.JPEG))
- != ImageFormat.JPEG) {
+
+ if (config.getBufferFormat(ImageFormat.JPEG) != ImageFormat.JPEG) {
// Use old pipeline for non-JPEG output format.
return false;
}
return mUseProcessingPipeline;
}
- private int getCaptureStageSize(@NonNull ImageCaptureConfig config) {
- CaptureBundle captureBundle = config.getCaptureBundle(null);
- if (captureBundle == null) {
- return 1;
- }
- List<CaptureStage> captureStages = captureBundle.getCaptureStages();
- if (captureStages == null) {
- return 1;
- }
- return captureStages.size();
- }
-
/**
* Creates the pipeline for both capture request configuration and image post-processing.
*
@@ -2770,20 +2597,6 @@
}
/**
- * Sets the {@link CaptureBundle}.
- *
- * @param captureBundle The requested capture bundle for extension.
- * @return The current Builder.
- * @hide
- */
- @RestrictTo(Scope.LIBRARY_GROUP)
- @NonNull
- public Builder setCaptureBundle(@NonNull CaptureBundle captureBundle) {
- getMutableConfig().insertOption(OPTION_CAPTURE_BUNDLE, captureBundle);
- return this;
- }
-
- /**
* Sets the {@link ImageFormat} of the {@link ImageProxy} returned by the
* {@link ImageCapture.OnImageCapturedCallback}.
*
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
index 36096c8..8f0865500 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
@@ -219,7 +219,7 @@
// CaptureProcessor. e.g. only update the output Surface (between Processor/App), and
// still use the same input Surface (between Camera/Processor). It's just simpler for now.
final SurfaceRequest surfaceRequest = new SurfaceRequest(resolution, getCamera(),
- /* isRGBA8888Required */ false, this::notifyReset);
+ this::notifyReset);
mCurrentSurfaceRequest = surfaceRequest;
if (mSurfaceProvider != null) {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ProcessingImageReader.java b/camera/camera-core/src/main/java/androidx/camera/core/ProcessingImageReader.java
deleted file mode 100644
index 97fb3ec..0000000
--- a/camera/camera-core/src/main/java/androidx/camera/core/ProcessingImageReader.java
+++ /dev/null
@@ -1,598 +0,0 @@
-/*
- * Copyright 2019 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.core;
-
-import android.graphics.ImageFormat;
-import android.media.ImageReader;
-import android.util.Size;
-import android.view.Surface;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.camera.core.impl.CameraCaptureCallback;
-import androidx.camera.core.impl.CaptureBundle;
-import androidx.camera.core.impl.CaptureProcessor;
-import androidx.camera.core.impl.CaptureStage;
-import androidx.camera.core.impl.ImageReaderProxy;
-import androidx.camera.core.impl.utils.executor.CameraXExecutors;
-import androidx.camera.core.impl.utils.futures.FutureCallback;
-import androidx.camera.core.impl.utils.futures.Futures;
-import androidx.concurrent.futures.CallbackToFutureAdapter;
-import androidx.core.util.Preconditions;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-/**
- * An {@link ImageReaderProxy} which takes one or more {@link android.media.Image}, processes it,
- * then output the final result {@link ImageProxy} to
- * {@link ImageReaderProxy.OnImageAvailableListener}.
- *
- * <p>ProcessingImageReader takes {@link CaptureBundle} as the expected set of
- * {@link CaptureStage}. Once all the ImageProxy from the captures are ready. It invokes
- * the {@link CaptureProcessor} set, then returns a single output ImageProxy to
- * OnImageAvailableListener.
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-class ProcessingImageReader implements ImageReaderProxy {
- private static final String TAG = "ProcessingImageReader";
-
- // Exif metadata are restricted in size to 64 kB in JPEG images because according to
- // the specification this information must be contained within a single JPEG APP1
- // segment. (See: https://en.wikipedia.org/wiki/Exif)
- private static final int EXIF_MAX_SIZE_BYTES = 64000;
-
- final Object mLock = new Object();
-
- // Callback when Image is ready from InputImageReader.
- private ImageReaderProxy.OnImageAvailableListener mTransformedListener =
- new ImageReaderProxy.OnImageAvailableListener() {
- @Override
- public void onImageAvailable(@NonNull ImageReaderProxy reader) {
- imageIncoming(reader);
- }
- };
-
- // Callback when Image is ready from OutputImageReader.
- private ImageReaderProxy.OnImageAvailableListener mImageProcessedListener =
- new ImageReaderProxy.OnImageAvailableListener() {
- @Override
- public void onImageAvailable(@NonNull ImageReaderProxy reader) {
- // Callback the output OnImageAvailableListener.
- ImageReaderProxy.OnImageAvailableListener listener;
- Executor executor;
- synchronized (mLock) {
- listener = mListener;
- executor = mExecutor;
-
- // Resets SettableImageProxyBundle after the processor finishes processing.
- mSettableImageProxyBundle.reset();
- setupSettableImageProxyBundleCallbacks();
- }
- if (listener != null) {
- if (executor != null) {
- executor.execute(
- () -> listener.onImageAvailable(ProcessingImageReader.this));
- } else {
- listener.onImageAvailable(ProcessingImageReader.this);
- }
- }
- }
- };
-
- // Callback when all the ImageProxies in SettableImageProxyBundle are ready.
- private FutureCallback<List<ImageProxy>> mCaptureStageReadyCallback =
- new FutureCallback<List<ImageProxy>>() {
- @Override
- public void onSuccess(@Nullable List<ImageProxy> imageProxyList) {
- SettableImageProxyBundle settableImageProxyBundle;
- OnProcessingErrorCallback errorCallback;
- Executor errorCallbackExecutor;
- synchronized (mLock) {
- if (mClosed) {
- return;
- }
- mProcessing = true;
- settableImageProxyBundle = mSettableImageProxyBundle;
- errorCallback = mOnProcessingErrorCallback;
- errorCallbackExecutor = mErrorCallbackExecutor;
- }
- try {
- mCaptureProcessor.process(settableImageProxyBundle);
- } catch (Exception e) {
- synchronized (mLock) {
- // Resets mSettableImageProxyBundle to close the held images.
- mSettableImageProxyBundle.reset();
-
- if (errorCallback != null && errorCallbackExecutor != null) {
- errorCallbackExecutor.execute(
- () -> errorCallback.notifyProcessingError(
- e.getMessage(), e.getCause()));
- }
- }
- }
-
- synchronized (mLock) {
- mProcessing = false;
- }
-
- closeAndCompleteFutureIfNecessary();
- }
-
- @Override
- public void onFailure(@NonNull Throwable throwable) {
-
- }
- };
-
- @GuardedBy("mLock")
- boolean mClosed = false;
-
- @GuardedBy("mLock")
- boolean mProcessing = false;
-
- @GuardedBy("mLock")
- final ImageReaderProxy mInputImageReader;
-
- @GuardedBy("mLock")
- final ImageReaderProxy mOutputImageReader;
-
- @GuardedBy("mLock")
- @Nullable
- ImageReaderProxy.OnImageAvailableListener mListener;
-
- @GuardedBy("mLock")
- @Nullable
- Executor mExecutor;
-
- @GuardedBy("mLock")
- CallbackToFutureAdapter.Completer<Void> mCloseCompleter;
- @GuardedBy("mLock")
- private ListenableFuture<Void> mCloseFuture;
-
- /** The Executor to execute the image post processing task. */
- @NonNull
- final Executor mPostProcessExecutor;
-
- @NonNull
- final CaptureProcessor mCaptureProcessor;
-
- @NonNull
- private final ListenableFuture<Void> mUnderlyingCaptureProcessorCloseFuture;
-
- private String mTagBundleKey = new String();
-
- @GuardedBy("mLock")
- @NonNull
- SettableImageProxyBundle mSettableImageProxyBundle =
- new SettableImageProxyBundle(Collections.emptyList(), mTagBundleKey);
-
- private final List<Integer> mCaptureIdList = new ArrayList<>();
-
- private ListenableFuture<List<ImageProxy>> mSettableImageProxyFutureList =
- Futures.immediateFuture(new ArrayList<>());
-
- @GuardedBy("mLock")
- OnProcessingErrorCallback mOnProcessingErrorCallback;
-
- @GuardedBy("mLock")
- Executor mErrorCallbackExecutor;
-
- ProcessingImageReader(@NonNull Builder builder) {
- if (builder.mInputImageReader.getMaxImages()
- < builder.mCaptureBundle.getCaptureStages().size()) {
- throw new IllegalArgumentException(
- "MetadataImageReader is smaller than CaptureBundle.");
- }
-
- mInputImageReader = builder.mInputImageReader;
-
- // For JPEG ImageReaders, the Surface that is created will have format BLOB which can
- // only be allocated with a height of 1. The output Image from the image reader will read
- // its dimensions from the JPEG data's EXIF in order to set the final dimensions.
- int outputWidth = mInputImageReader.getWidth();
- int outputHeight = mInputImageReader.getHeight();
-
- if (builder.mOutputFormat == ImageFormat.JPEG) {
- // The output JPEG compression quality is 100 when taking a picture in MAX_QUALITY
- // mode. It might cause the compressed data size exceeds image's width * height.
- // YUV_420_888 should be 1.5 times of image's width * height. The compressed data
- // size shouldn't exceed it. Therefore, scales the output image reader byte buffer to
- // 1.5 times when the JPEG compression quality setting is 100.
- outputWidth = (int) (outputWidth * outputHeight * 1.5f) + EXIF_MAX_SIZE_BYTES;
- outputHeight = 1;
- }
- mOutputImageReader = new AndroidImageReaderProxy(
- ImageReader.newInstance(outputWidth, outputHeight, builder.mOutputFormat,
- mInputImageReader.getMaxImages()));
-
- mPostProcessExecutor = builder.mPostProcessExecutor;
- mCaptureProcessor = builder.mCaptureProcessor;
- mCaptureProcessor.onOutputSurface(mOutputImageReader.getSurface(), builder.mOutputFormat);
- mCaptureProcessor.onResolutionUpdate(
- new Size(mInputImageReader.getWidth(), mInputImageReader.getHeight()));
-
- mUnderlyingCaptureProcessorCloseFuture = mCaptureProcessor.getCloseFuture();
-
- setCaptureBundle(builder.mCaptureBundle);
- }
-
- @Override
- @Nullable
- public ImageProxy acquireLatestImage() {
- synchronized (mLock) {
- return mOutputImageReader.acquireLatestImage();
- }
- }
-
- @Override
- @Nullable
- public ImageProxy acquireNextImage() {
- synchronized (mLock) {
- return mOutputImageReader.acquireNextImage();
- }
- }
-
- @Override
- public void close() {
- synchronized (mLock) {
- if (mClosed) {
- return;
- }
-
- // Prevent the ImageAvailableListener from being triggered after the close function
- // is called.
- mInputImageReader.clearOnImageAvailableListener();
- mOutputImageReader.clearOnImageAvailableListener();
-
- mClosed = true;
- }
-
- mCaptureProcessor.close();
- closeAndCompleteFutureIfNecessary();
- }
-
- void closeAndCompleteFutureIfNecessary() {
- boolean closed;
- boolean processing;
- CallbackToFutureAdapter.Completer<Void> closeCompleter;
-
- synchronized (mLock) {
- closed = mClosed;
- processing = mProcessing;
- closeCompleter = mCloseCompleter;
-
- // If the CaptureProcessor is in the middle of processing then don't close the
- // ImageReaderProxys and associated ImageProxy. Let the processing complete before
- // closing them.
- if (closed && !processing) {
- mInputImageReader.close();
- mSettableImageProxyBundle.close();
- mOutputImageReader.close();
- }
- }
-
- if (closed && !processing) {
- // Complete the capture process pipeline's close future after the underlying capture
- // processor is closed.
- mUnderlyingCaptureProcessorCloseFuture.addListener(() -> {
- cancelSettableImageProxyBundleFutureList();
- if (closeCompleter != null) {
- closeCompleter.set(null);
- }
- }, CameraXExecutors.directExecutor());
- }
- }
-
- /**
- * Returns a future that will complete when the ProcessingImageReader is actually closed.
- *
- * @return A future that signals when the ProcessingImageReader is actually closed
- * (after all processing). Cancelling this future has no effect.
- */
- @NonNull
- ListenableFuture<Void> getCloseFuture() {
- ListenableFuture<Void> closeFuture;
- synchronized (mLock) {
- if (mClosed && !mProcessing) {
- // Everything should be closed but still need to wait for underlying capture
- // processors being closed.
- closeFuture = Futures.transform(mUnderlyingCaptureProcessorCloseFuture,
- nullVoid -> null, CameraXExecutors.directExecutor());
- } else {
- if (mCloseFuture == null) {
- mCloseFuture = CallbackToFutureAdapter.getFuture(completer -> {
- // Should already be locked, but lock again to satisfy linter.
- synchronized (mLock) {
- mCloseCompleter = completer;
- }
- return "ProcessingImageReader-close";
- });
- }
- closeFuture = Futures.nonCancellationPropagating(mCloseFuture);
- }
- }
- return closeFuture;
- }
-
- @Override
- public int getHeight() {
- synchronized (mLock) {
- return mInputImageReader.getHeight();
- }
- }
-
- @Override
- public int getWidth() {
- synchronized (mLock) {
- return mInputImageReader.getWidth();
- }
- }
-
- @Override
- public int getImageFormat() {
- synchronized (mLock) {
- return mOutputImageReader.getImageFormat();
- }
- }
-
- @Override
- public int getMaxImages() {
- synchronized (mLock) {
- return mInputImageReader.getMaxImages();
- }
- }
-
- @Nullable
- @Override
- public Surface getSurface() {
- synchronized (mLock) {
- return mInputImageReader.getSurface();
- }
- }
-
- @Override
- public void setOnImageAvailableListener(@NonNull OnImageAvailableListener listener,
- @NonNull Executor executor) {
- synchronized (mLock) {
- mListener = Preconditions.checkNotNull(listener);
- mExecutor = Preconditions.checkNotNull(executor);
- mInputImageReader.setOnImageAvailableListener(mTransformedListener, executor);
- mOutputImageReader.setOnImageAvailableListener(mImageProcessedListener, executor);
- }
- }
-
- @Override
- public void clearOnImageAvailableListener() {
- synchronized (mLock) {
- mListener = null;
- mExecutor = null;
- mInputImageReader.clearOnImageAvailableListener();
- mOutputImageReader.clearOnImageAvailableListener();
-
- if (!mProcessing) {
- mSettableImageProxyBundle.close();
- }
- }
- }
-
- /** Sets a CaptureBundle */
- public void setCaptureBundle(@NonNull CaptureBundle captureBundle) {
- synchronized (mLock) {
- if (mClosed) {
- return;
- }
-
- cancelSettableImageProxyBundleFutureList();
-
- if (captureBundle.getCaptureStages() != null) {
- if (mInputImageReader.getMaxImages() < captureBundle.getCaptureStages().size()) {
- throw new IllegalArgumentException(
- "CaptureBundle is larger than InputImageReader.");
- }
-
- mCaptureIdList.clear();
-
- for (CaptureStage captureStage : captureBundle.getCaptureStages()) {
- if (captureStage != null) {
- mCaptureIdList.add(captureStage.getId());
- }
- }
- }
-
- // Use the mCaptureBundle as the key for TagBundle
- mTagBundleKey = Integer.toString(captureBundle.hashCode());
- mSettableImageProxyBundle = new SettableImageProxyBundle(mCaptureIdList, mTagBundleKey);
- setupSettableImageProxyBundleCallbacks();
- }
- }
-
- private void cancelSettableImageProxyBundleFutureList() {
- synchronized (mLock) {
- if (!mSettableImageProxyFutureList.isDone()) {
- mSettableImageProxyFutureList.cancel(true);
- }
-
- mSettableImageProxyBundle.reset();
- }
- }
-
- /** Returns a TagBundleKey which is used in this processing image reader.*/
- @NonNull
- public String getTagBundleKey() {
- return mTagBundleKey;
- }
-
- /** Returns necessary camera callbacks to retrieve metadata from camera result. */
- @Nullable
- CameraCaptureCallback getCameraCaptureCallback() {
- synchronized (mLock) {
- if (mInputImageReader instanceof MetadataImageReader) {
- return ((MetadataImageReader) mInputImageReader).getCameraCaptureCallback();
- } else {
- return new CameraCaptureCallback() {};
- }
- }
- }
-
- /**
- * Sets {@link OnProcessingErrorCallback} to receive error notifications.
- *
- * @param executor The executor in which the callback methods will be run.
- * @param callback Callback to be invoked if an error occurs when processing the images.
- */
- public void setOnProcessingErrorCallback(@NonNull Executor executor,
- @NonNull OnProcessingErrorCallback callback) {
- synchronized (mLock) {
- mErrorCallbackExecutor = executor;
- mOnProcessingErrorCallback = callback;
- }
- }
-
- @GuardedBy("mLock")
- void setupSettableImageProxyBundleCallbacks() {
- List<ListenableFuture<ImageProxy>> futureList = new ArrayList<>();
- for (Integer id : mCaptureIdList) {
- futureList.add(mSettableImageProxyBundle.getImageProxy(id));
- }
-
- mSettableImageProxyFutureList = Futures.allAsList(futureList);
-
- Futures.addCallback(Futures.allAsList(futureList), mCaptureStageReadyCallback,
- mPostProcessExecutor);
- }
-
- // Incoming Image from InputImageReader. Acquires it and add to SettableImageProxyBundle.
- void imageIncoming(ImageReaderProxy imageReader) {
- synchronized (mLock) {
- if (mClosed) {
- return;
- }
-
- ImageProxy image = null;
- try {
- image = imageReader.acquireNextImage();
- } catch (IllegalStateException e) {
- Logger.e(TAG, "Failed to acquire latest image.", e);
- } finally {
- if (image != null) {
- // Currently use the same key which intends to get a captureStage id value.
- Integer tagValue =
- (Integer) image.getImageInfo().getTagBundle().getTag(mTagBundleKey);
-
- if (!mCaptureIdList.contains(tagValue)) {
- Logger.w(TAG, "ImageProxyBundle does not contain this id: " + tagValue);
- image.close();
- } else {
- mSettableImageProxyBundle.addImageProxy(image);
- }
- }
- }
- }
- }
-
- /**
- * The builder to create a {@link ProcessingImageReader} object.
- */
- static final class Builder {
- @NonNull
- protected final ImageReaderProxy mInputImageReader;
- @NonNull
- protected final CaptureBundle mCaptureBundle;
- @NonNull
- protected final CaptureProcessor mCaptureProcessor;
-
- protected int mOutputFormat;
-
- @NonNull
- protected Executor mPostProcessExecutor = Executors.newSingleThreadExecutor();
-
- /**
- * Create a {@link Builder} with specific configurations.
- *
- * @param imageReader The input image reader.
- * @param captureBundle The {@link CaptureBundle} includes the processing information
- * @param captureProcessor The {@link CaptureProcessor} to be invoked when the Images are
- * ready
- */
- Builder(@NonNull ImageReaderProxy imageReader, @NonNull CaptureBundle captureBundle,
- @NonNull CaptureProcessor captureProcessor) {
- mInputImageReader = imageReader;
- mCaptureBundle = captureBundle;
- mCaptureProcessor = captureProcessor;
- mOutputFormat = imageReader.getImageFormat();
- }
-
- /**
- * Create a {@link Builder} with specific configurations.
- *
- * @param width Width of the ImageReader
- * @param height Height of the ImageReader
- * @param inputFormat Input image format
- * @param maxImages Maximum Image number the ImageReader can hold. The capacity
- * should be greater than the captureBundle size in order to hold
- * all the Images needed with this processing.
- * @param captureBundle The {@link CaptureBundle} includes the processing information
- * @param captureProcessor The {@link CaptureProcessor} to be invoked when the Images are
- * ready
- */
- Builder(int width, int height, int inputFormat, int maxImages,
- @NonNull CaptureBundle captureBundle, @NonNull CaptureProcessor captureProcessor) {
- this(new MetadataImageReader(width, height, inputFormat, maxImages), captureBundle,
- captureProcessor);
- }
-
- /**
- * Sets an Executor to execute the post-process of the image result.
- */
- @NonNull
- Builder setPostProcessExecutor(@NonNull Executor postProcessExecutor) {
- mPostProcessExecutor = postProcessExecutor;
- return this;
- }
-
- /**
- * Sets the output image format.
- */
- @NonNull
- Builder setOutputFormat(int outputFormat) {
- mOutputFormat = outputFormat;
- return this;
- }
-
- /**
- * Builds an {@link ProcessingImageReader} from current configurations.
- */
- ProcessingImageReader build() {
- return new ProcessingImageReader(this);
- }
- }
-
- /**
- * Callback for notifying processing errors.
- */
- interface OnProcessingErrorCallback {
- void notifyProcessingError(@Nullable String message, @Nullable Throwable cause);
- }
-}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SettableImageProxyBundle.java b/camera/camera-core/src/main/java/androidx/camera/core/SettableImageProxyBundle.java
deleted file mode 100644
index 8bf8a73..0000000
--- a/camera/camera-core/src/main/java/androidx/camera/core/SettableImageProxyBundle.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright 2019 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.core;
-
-import android.util.SparseArray;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-import androidx.camera.core.impl.ImageProxyBundle;
-import androidx.concurrent.futures.CallbackToFutureAdapter;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * A {@link ImageProxyBundle} with a predefined set of captured ids. The {@link ListenableFuture}
- * for the capture id becomes valid when the corresponding {@link ImageProxy} has been set.
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-final class SettableImageProxyBundle implements ImageProxyBundle {
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- final Object mLock = new Object();
- @SuppressWarnings("WeakerAccess") /* synthetic accessor */
- @GuardedBy("mLock")
- final SparseArray<CallbackToFutureAdapter.Completer<ImageProxy>> mCompleters =
- new SparseArray<>();
- /** Map of id to {@link ImageProxy} Future. */
- @GuardedBy("mLock")
- private final SparseArray<ListenableFuture<ImageProxy>> mFutureResults = new SparseArray<>();
-
- @GuardedBy("mLock")
- private final List<ImageProxy> mOwnedImageProxies = new ArrayList<>();
-
- private final List<Integer> mCaptureIdList;
- private String mTagBundleKey = null;
-
- // Whether or not the bundle has been closed or not
- @GuardedBy("mLock")
- private boolean mClosed = false;
-
- /**
- * Create a {@link ImageProxyBundle} for captures with the given ids.
- *
- * @param captureIds The set of captureIds contained by the ImageProxyBundle
- * @param tagBundleKey `The key for checking desired image from TagBundle
- */
- SettableImageProxyBundle(List<Integer> captureIds, String tagBundleKey) {
- mCaptureIdList = captureIds;
- mTagBundleKey = tagBundleKey;
- setup();
- }
-
- @Override
- @NonNull
- public ListenableFuture<ImageProxy> getImageProxy(int captureId) {
- synchronized (mLock) {
- if (mClosed) {
- throw new IllegalStateException("ImageProxyBundle already closed.");
- }
-
- // Returns the future that has been set if it exists
- ListenableFuture<ImageProxy> result = mFutureResults.get(captureId);
- if (result == null) {
- throw new IllegalArgumentException(
- "ImageProxyBundle does not contain this id: " + captureId);
- }
-
- return result;
- }
- }
-
- @Override
- @NonNull
- public List<Integer> getCaptureIds() {
- return Collections.unmodifiableList(mCaptureIdList);
- }
-
- /**
- * Add an {@link ImageProxy} to synchronize.
- */
- void addImageProxy(ImageProxy imageProxy) {
- synchronized (mLock) {
- if (mClosed) {
- return;
- }
-
- Integer captureId =
- (Integer) imageProxy.getImageInfo().getTagBundle().getTag(mTagBundleKey);
- if (captureId == null) {
- throw new IllegalArgumentException("CaptureId is null.");
- }
-
- // If the CaptureId is associated with this SettableImageProxyBundle, set the
- // corresponding Future. Otherwise, throws exception.
- CallbackToFutureAdapter.Completer<ImageProxy> completer = mCompleters.get(captureId);
- if (completer != null) {
- mOwnedImageProxies.add(imageProxy);
- completer.set(imageProxy);
- } else {
- throw new IllegalArgumentException(
- "ImageProxyBundle does not contain this id: " + captureId);
- }
- }
- }
-
- /**
- * Flush all {@link ImageProxy} that have been added.
- */
- void close() {
- synchronized (mLock) {
- if (mClosed) {
- return;
- }
- for (ImageProxy imageProxy : mOwnedImageProxies) {
- imageProxy.close();
- }
- mOwnedImageProxies.clear();
- mFutureResults.clear();
- mCompleters.clear();
- mClosed = true;
- }
- }
-
- /**
- * Clear all {@link ImageProxy} that have been added and recreate the entries from the bundle.
- */
- void reset() {
- synchronized (mLock) {
- if (mClosed) {
- return;
- }
- for (ImageProxy imageProxy : mOwnedImageProxies) {
- imageProxy.close();
- }
- mOwnedImageProxies.clear();
- mFutureResults.clear();
- mCompleters.clear();
- setup();
- }
- }
-
- private void setup() {
- synchronized (mLock) {
- for (final int captureId : mCaptureIdList) {
- ListenableFuture<ImageProxy> futureResult = CallbackToFutureAdapter.getFuture(
- new CallbackToFutureAdapter.Resolver<ImageProxy>() {
- @Override
- public Object attachCompleter(
- @NonNull CallbackToFutureAdapter.Completer<ImageProxy>
- completer) {
- synchronized (mLock) { // Not technically needed since
- // attachCompleter is called inline, but mLock is re-entrant
- // so there's no harm.
- mCompleters.put(captureId, completer);
- }
- return "getImageProxy(id: " + captureId + ")";
- }
- });
- mFutureResults.put(captureId, futureResult);
- }
- }
- }
-}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java
index 88986d5..3137f24 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceRequest.java
@@ -71,8 +71,6 @@
@Nullable
private final Range<Integer> mExpectedFrameRate;
- private final boolean mRGBA8888Required;
-
private final CameraInternal mCamera;
// For the camera to retrieve the surface from the user
@@ -114,9 +112,8 @@
public SurfaceRequest(
@NonNull Size resolution,
@NonNull CameraInternal camera,
- boolean isRGBA8888Required,
@NonNull Runnable onInvalidated) {
- this(resolution, camera, isRGBA8888Required, /*expectedFrameRate=*/null, onInvalidated);
+ this(resolution, camera, /*expectedFrameRate=*/null, onInvalidated);
}
/**
@@ -129,13 +126,11 @@
public SurfaceRequest(
@NonNull Size resolution,
@NonNull CameraInternal camera,
- boolean isRGBA8888Required,
@Nullable Range<Integer> expectedFrameRate,
@NonNull Runnable onInvalidated) {
super();
mResolution = resolution;
mCamera = camera;
- mRGBA8888Required = isRGBA8888Required;
mExpectedFrameRate = expectedFrameRate;
// To ensure concurrency and ordering, operations are chained. Completion can only be
@@ -324,17 +319,6 @@
}
/**
- * Returns whether a surface of RGBA_8888 pixel format is required.
- *
- * @return true if a surface of RGBA_8888 pixel format is required.
- * @hide
- */
- @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
- public boolean isRGBA8888Required() {
- return mRGBA8888Required;
- }
-
- /**
* Completes the request for a {@link Surface} if it has not already been
* completed or cancelled.
*
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CaptureProcessor.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CaptureProcessor.java
deleted file mode 100644
index 38b82cf..0000000
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CaptureProcessor.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright 2019 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.core.impl;
-
-import android.util.Size;
-import android.view.Surface;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-import androidx.camera.core.ImageCapture;
-import androidx.camera.core.ImageProxy;
-import androidx.camera.core.Preview;
-import androidx.camera.core.impl.utils.futures.Futures;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-/**
- * A processing step of the image capture pipeline.
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public interface CaptureProcessor {
- /**
- * This gets called to update where the CaptureProcessor should write the output of {@link
- * #process(ImageProxyBundle)}.
- *
- * @param surface The {@link Surface} that the CaptureProcessor should write data into.
- * @param imageFormat The format of that the surface expects.
- */
- void onOutputSurface(@NonNull Surface surface, int imageFormat);
-
- /**
- * Process a {@link ImageProxyBundle} for the set of captures that were
- * requested.
- *
- * <p> A result of the processing step must be written to the {@link Surface} that was
- * received by {@link #onOutputSurface(Surface, int)}. Otherwise, it might cause the
- * {@link ImageCapture#takePicture} can't be complete or frame lost in {@link Preview}.
- * @param bundle The set of images to process. The ImageProxyBundle and the {@link ImageProxy}
- * that are retrieved from it will become invalid after this method completes, so
- * no references to them should be kept.
- */
- void process(@NonNull ImageProxyBundle bundle);
-
- /**
- * This will be invoked when the input surface resolution is updated.
- *
- * @param size for the surface.
- */
- void onResolutionUpdate(@NonNull Size size);
-
- /**
- * Triggers to close the capture processor.
- *
- * <p>The capture processor might stop the in-progress task or have a flag to stop handling
- * new tasks after this function is called. When the capture processor is closed completely,
- * the {@link ListenableFuture} returned by {@link #getCloseFuture()} needs to be completed.
- */
- default void close() {
- // No-op by default.
- }
-
- /**
- * Returns the {@link ListenableFuture} which allows to know when the capture processor has
- * been closed completely.
- */
- @NonNull
- default ListenableFuture<Void> getCloseFuture() {
- // Returns immediate future by default.
- return Futures.immediateFuture(null);
- }
-}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/YuvToJpegProcessor.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/YuvToJpegProcessor.java
deleted file mode 100644
index 12c9144..0000000
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/YuvToJpegProcessor.java
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
- * Copyright 2020 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.core.internal;
-
-import android.graphics.ImageFormat;
-import android.graphics.Rect;
-import android.graphics.YuvImage;
-import android.media.Image;
-import android.media.ImageWriter;
-import android.util.Size;
-import android.view.Surface;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.IntRange;
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-import androidx.camera.core.ImageProxy;
-import androidx.camera.core.Logger;
-import androidx.camera.core.impl.CaptureProcessor;
-import androidx.camera.core.impl.ImageOutputConfig;
-import androidx.camera.core.impl.ImageProxyBundle;
-import androidx.camera.core.impl.utils.ExifData;
-import androidx.camera.core.impl.utils.ExifOutputStream;
-import androidx.camera.core.impl.utils.futures.Futures;
-import androidx.camera.core.internal.compat.ImageWriterCompat;
-import androidx.camera.core.internal.utils.ImageUtil;
-import androidx.concurrent.futures.CallbackToFutureAdapter;
-import androidx.core.util.Preconditions;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.util.List;
-
-/**
- * A CaptureProcessor which produces JPEGs from input YUV images.
- */
-@RequiresApi(26)
-public class YuvToJpegProcessor implements CaptureProcessor {
- private static final String TAG = "YuvToJpegProcessor";
-
- private static final Rect UNINITIALIZED_RECT = new Rect(0, 0, 0, 0);
-
- private final int mMaxImages;
-
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- @IntRange(from = 0, to = 100)
- private int mQuality;
- @GuardedBy("mLock")
- @ImageOutputConfig.RotationDegreesValue
- private int mRotationDegrees = 0;
-
- @GuardedBy("mLock")
- private boolean mClosed = false;
- @GuardedBy("mLock")
- private int mProcessingImages = 0;
- @GuardedBy("mLock")
- private ImageWriter mImageWriter;
- @GuardedBy("mLock")
- private Rect mImageRect = UNINITIALIZED_RECT;
-
- @GuardedBy("mLock")
- CallbackToFutureAdapter.Completer<Void> mCloseCompleter;
- @GuardedBy("mLock")
- private ListenableFuture<Void> mCloseFuture;
-
- public YuvToJpegProcessor(@IntRange(from = 0, to = 100) int quality, int maxImages) {
- mQuality = quality;
- mMaxImages = maxImages;
- }
-
- /**
- * Sets the compression quality for the output JPEG image.
- */
- public void setJpegQuality(@IntRange(from = 0, to = 100) int quality) {
- synchronized (mLock) {
- mQuality = quality;
- }
- }
-
- /**
- * Sets the rotation degrees value of the output images.
- *
- * @param rotationDegrees The rotation in degrees which will be a value in {0, 90, 180, 270}.
- */
- public void setRotationDegrees(@ImageOutputConfig.RotationDegreesValue int rotationDegrees) {
- synchronized (mLock) {
- mRotationDegrees = rotationDegrees;
- }
- }
-
- @Override
- public void onOutputSurface(@NonNull Surface surface, int imageFormat) {
- Preconditions.checkState(imageFormat == ImageFormat.JPEG, "YuvToJpegProcessor only "
- + "supports JPEG output format.");
- synchronized (mLock) {
- if (!mClosed) {
- if (mImageWriter != null) {
- throw new IllegalStateException("Output surface already set.");
- }
- mImageWriter = ImageWriterCompat.newInstance(surface, mMaxImages, imageFormat);
- } else {
- Logger.w(TAG, "Cannot set output surface. Processor is closed.");
- }
- }
- }
-
- @Override
- public void process(@NonNull ImageProxyBundle bundle) {
- List<Integer> ids = bundle.getCaptureIds();
- Preconditions.checkArgument(ids.size() == 1,
- "Processing image bundle have single capture id, but found " + ids.size());
-
- ListenableFuture<ImageProxy> imageProxyListenableFuture = bundle.getImageProxy(ids.get(0));
- Preconditions.checkArgument(imageProxyListenableFuture.isDone());
-
- ImageWriter imageWriter;
- Rect imageRect;
- boolean processing;
- int quality;
- int rotationDegrees;
- synchronized (mLock) {
- imageWriter = mImageWriter;
- processing = !mClosed;
- imageRect = mImageRect;
- if (processing) {
- mProcessingImages++;
- }
- quality = mQuality;
- rotationDegrees = mRotationDegrees;
- }
-
- ImageProxy imageProxy = null;
- Image jpegImage = null;
- try {
- imageProxy = imageProxyListenableFuture.get();
- if (!processing) {
- Logger.w(TAG, "Image enqueued for processing on closed processor.");
- imageProxy.close();
- imageProxy = null;
- return;
- }
-
- jpegImage = imageWriter.dequeueInputImage();
-
- imageProxy = imageProxyListenableFuture.get();
- Preconditions.checkState(imageProxy.getFormat() == ImageFormat.YUV_420_888,
- "Input image is not expected YUV_420_888 image format");
- byte[] yuvBytes = ImageUtil.yuv_420_888toNv21(imageProxy);
-
- YuvImage yuvImage = new YuvImage(yuvBytes, ImageFormat.NV21, imageProxy.getWidth(),
- imageProxy.getHeight(), null);
-
- ByteBuffer jpegBuf = jpegImage.getPlanes()[0].getBuffer();
- int initialPos = jpegBuf.position();
- OutputStream os = new ExifOutputStream(new ByteBufferOutputStream(jpegBuf),
- ExifData.create(imageProxy, rotationDegrees));
- yuvImage.compressToJpeg(imageRect, quality, os);
-
- // Input can now be closed.
- imageProxy.close();
- imageProxy = null;
-
- // Set limits on jpeg buffer and rewind
- jpegBuf.limit(jpegBuf.position());
- jpegBuf.position(initialPos);
-
- // Enqueue the completed jpeg image
- imageWriter.queueInputImage(jpegImage);
- jpegImage = null;
- } catch (Exception e) {
- // InterruptedException, ExecutionException and EOFException might be caught here.
- //
- // InterruptedException should not be possible here since
- // imageProxyListenableFuture.isDone() returned true, but we have to handle the
- // exception case so bundle it with ExecutionException.
- //
- // EOFException might happen if the compressed JPEG data size exceeds the byte buffer
- // size of the output image reader.
- if (processing) {
- Logger.e(TAG, "Failed to process YUV -> JPEG", e);
- // Something went wrong attempting to retrieve ImageProxy. Enqueue an invalid buffer
- // to make sure the downstream isn't blocked.
- jpegImage = imageWriter.dequeueInputImage();
- ByteBuffer jpegBuf = jpegImage.getPlanes()[0].getBuffer();
- jpegBuf.rewind();
- jpegBuf.limit(0);
- imageWriter.queueInputImage(jpegImage);
- }
- } finally {
- boolean shouldCloseImageWriter;
- CallbackToFutureAdapter.Completer<Void> closeCompleter;
-
- synchronized (mLock) {
- // Note: order of condition is important here due to short circuit of &&
- shouldCloseImageWriter = processing && (mProcessingImages-- == 0) && mClosed;
- closeCompleter = mCloseCompleter;
- }
-
- // Fallback in case something went wrong during processing.
- if (jpegImage != null) {
- jpegImage.close();
- }
- if (imageProxy != null) {
- imageProxy.close();
- }
-
- if (shouldCloseImageWriter) {
- imageWriter.close();
- Logger.d(TAG, "Closed after completion of last image processed.");
-
- if (closeCompleter != null) {
- // Notify listeners of close
- closeCompleter.set(null);
- }
- }
- }
- }
-
- /**
- * Closes the YuvToJpegProcessor so that no more processing will occur.
- *
- * This should only be called once no more images will be produced for processing. Otherwise
- * the images may not be propagated to the output surface and the pipeline could stall.
- */
- @Override
- public void close() {
- CallbackToFutureAdapter.Completer<Void> closeCompleter = null;
-
- synchronized (mLock) {
- if (mClosed) {
- return;
- }
-
- mClosed = true;
- // Close the ImageWriter if no images are currently processing. Otherwise the
- // ImageWriter will be closed once the last image is closed.
- if (mProcessingImages == 0 && mImageWriter != null) {
- Logger.d(TAG, "No processing in progress. Closing immediately.");
- mImageWriter.close();
- closeCompleter = mCloseCompleter;
- } else {
- Logger.d(TAG, "close() called while processing. Will close after completion.");
- }
- }
-
- if (closeCompleter != null) {
- closeCompleter.set(null);
- }
- }
-
- /**
- * Returns a future that will complete when the YuvToJpegProcessor is actually closed.
- *
- * @return A future that signals when the YuvToJpegProcessor is actually closed
- * (after all processing). Cancelling this future has no effect.
- */
- @NonNull
- @Override
- public ListenableFuture<Void> getCloseFuture() {
- ListenableFuture<Void> closeFuture;
- synchronized (mLock) {
- if (mClosed && mProcessingImages == 0) {
- // Everything should be closed. Return immediate future.
- closeFuture = Futures.immediateFuture(null);
- } else {
- if (mCloseFuture == null) {
- mCloseFuture = CallbackToFutureAdapter.getFuture(completer -> {
- // Should already be locked, but lock again to satisfy linter.
- synchronized (mLock) {
- mCloseCompleter = completer;
- }
- return "YuvToJpegProcessor-close";
- });
- }
- closeFuture = Futures.nonCancellationPropagating(mCloseFuture);
- }
- }
- return closeFuture;
- }
-
- @Override
- public void onResolutionUpdate(@NonNull Size size) {
- synchronized (mLock) {
- mImageRect = new Rect(0, 0, size.getWidth(), size.getHeight());
- }
- }
-}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/SettableSurface.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/SettableSurface.java
index 8584670..ef86985 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/SettableSurface.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/SettableSurface.java
@@ -227,7 +227,7 @@
@Nullable Range<Integer> expectedFpsRange) {
checkMainThread();
// TODO(b/238230154) figure out how to support HDR.
- SurfaceRequest surfaceRequest = new SurfaceRequest(getSize(), cameraInternal, false,
+ SurfaceRequest surfaceRequest = new SurfaceRequest(getSize(), cameraInternal,
expectedFpsRange, this::invalidate);
try {
setProvider(surfaceRequest.getDeferrableSurface());
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ProcessingImageReaderTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ProcessingImageReaderTest.java
deleted file mode 100644
index 386b37c..0000000
--- a/camera/camera-core/src/test/java/androidx/camera/core/ProcessingImageReaderTest.java
+++ /dev/null
@@ -1,423 +0,0 @@
-/*
- * Copyright 2019 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.core;
-
-import static android.os.Looper.getMainLooper;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.robolectric.Shadows.shadowOf;
-
-import android.graphics.ImageFormat;
-import android.os.Build;
-import android.util.Pair;
-import android.util.Size;
-import android.view.Surface;
-
-import androidx.annotation.NonNull;
-import androidx.camera.core.impl.CaptureBundle;
-import androidx.camera.core.impl.CaptureProcessor;
-import androidx.camera.core.impl.CaptureStage;
-import androidx.camera.core.impl.ImageProxyBundle;
-import androidx.camera.core.impl.ImageReaderProxy;
-import androidx.camera.core.impl.TagBundle;
-import androidx.camera.core.impl.utils.executor.CameraXExecutors;
-import androidx.camera.testing.fakes.FakeCameraCaptureResult;
-import androidx.camera.testing.fakes.FakeCaptureStage;
-import androidx.camera.testing.fakes.FakeImageReaderProxy;
-import androidx.concurrent.futures.CallbackToFutureAdapter;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mockito;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.android.util.concurrent.PausedExecutorService;
-import org.robolectric.annotation.Config;
-import org.robolectric.annotation.internal.DoNotInstrument;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
-
-// UnstableApiUsage is needed because PausedExecutorService is marked @Beta
-@SuppressWarnings({"UnstableApiUsage", "deprecation"})
-@RunWith(RobolectricTestRunner.class)
-@DoNotInstrument
-@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public final class ProcessingImageReaderTest {
- private static final int CAPTURE_ID_0 = 0;
- private static final int CAPTURE_ID_1 = 1;
- private static final int CAPTURE_ID_2 = 2;
- private static final int CAPTURE_ID_3 = 3;
- private static final long TIMESTAMP_0 = 0L;
- private static final long TIMESTAMP_1 = 1000L;
- private static final long TIMESTAMP_2 = 2000L;
- private static final long TIMESTAMP_3 = 4000L;
- private static final CaptureProcessor NOOP_PROCESSOR = new CaptureProcessor() {
- @Override
- public void onOutputSurface(@NonNull Surface surface, int imageFormat) {
-
- }
-
- @Override
- public void process(@NonNull ImageProxyBundle bundle) {
-
- }
-
- @Override
- public void onResolutionUpdate(@NonNull Size size) {
-
- }
- };
- private static PausedExecutorService sPausedExecutor;
- private final CaptureStage mCaptureStage0 = new FakeCaptureStage(CAPTURE_ID_0, null);
- private final CaptureStage mCaptureStage1 = new FakeCaptureStage(CAPTURE_ID_1, null);
- private final CaptureStage mCaptureStage2 = new FakeCaptureStage(CAPTURE_ID_2, null);
- private final CaptureStage mCaptureStage3 = new FakeCaptureStage(CAPTURE_ID_3, null);
- private final FakeImageReaderProxy mImageReaderProxy = new FakeImageReaderProxy(8);
- private MetadataImageReader mMetadataImageReader;
- private CaptureBundle mCaptureBundle;
- private String mTagBundleKey;
-
- @BeforeClass
- public static void setUpClass() {
- sPausedExecutor = new PausedExecutorService();
- }
-
- @AfterClass
- public static void tearDownClass() {
- sPausedExecutor.shutdown();
- }
-
- @Before
- public void setUp() {
- mCaptureBundle = CaptureBundles.createCaptureBundle(mCaptureStage0, mCaptureStage1);
- mTagBundleKey = Integer.toString(mCaptureBundle.hashCode());
- mMetadataImageReader = new MetadataImageReader(mImageReaderProxy);
- }
-
- @After
- public void cleanUp() {
- // Ensure the PausedExecutorService is drained
- sPausedExecutor.runAll();
- }
-
- @Test
- public void canSetFuturesInSettableImageProxyBundle()
- throws InterruptedException, TimeoutException, ExecutionException {
- // Sets the callback from ProcessingImageReader to start processing
- CaptureProcessor captureProcessor = mock(CaptureProcessor.class);
- ProcessingImageReader processingImageReader = new ProcessingImageReader.Builder(
- mMetadataImageReader, mCaptureBundle, captureProcessor).setPostProcessExecutor(
- sPausedExecutor).build();
- processingImageReader.setOnImageAvailableListener(mock(
- ImageReaderProxy.OnImageAvailableListener.class),
- CameraXExecutors.mainThreadExecutor());
- Map<Integer, Long> resultMap = new HashMap<>();
- resultMap.put(CAPTURE_ID_0, TIMESTAMP_0);
- resultMap.put(CAPTURE_ID_1, TIMESTAMP_1);
-
- // Cache current CaptureBundle as the TagBundle key for generate the fake image
- mTagBundleKey = processingImageReader.getTagBundleKey();
- triggerAndVerify(captureProcessor, resultMap);
- Mockito.reset(captureProcessor);
-
- CaptureBundle captureBundle = CaptureBundles.createCaptureBundle(mCaptureStage2,
- mCaptureStage3);
- processingImageReader.setCaptureBundle(captureBundle);
-
- // Reset the key for TagBundle because the CaptureBundle is renewed
- mTagBundleKey = processingImageReader.getTagBundleKey();
-
- Map<Integer, Long> resultMap1 = new HashMap<>();
- resultMap1.put(CAPTURE_ID_2, TIMESTAMP_2);
- resultMap1.put(CAPTURE_ID_3, TIMESTAMP_3);
- triggerAndVerify(captureProcessor, resultMap1);
- }
-
- private void triggerAndVerify(CaptureProcessor captureProcessor,
- Map<Integer, Long> captureIdToTime)
- throws InterruptedException, ExecutionException, TimeoutException {
- // Feeds ImageProxy with all capture id on the initial list.
- for (Integer id : captureIdToTime.keySet()) {
- triggerImageAvailable(id, captureIdToTime.get(id));
- }
-
- // Ensure tasks are posted to the processing executor
- shadowOf(getMainLooper()).idle();
-
- // Run processing
- sPausedExecutor.runAll();
-
- ArgumentCaptor<ImageProxyBundle> imageProxyBundleCaptor =
- ArgumentCaptor.forClass(ImageProxyBundle.class);
- verify(captureProcessor, times(1)).process(imageProxyBundleCaptor.capture());
- assertThat(imageProxyBundleCaptor.getValue()).isNotNull();
-
- // CaptureProcessor.process should be called once all ImageProxies on the
- // initial lists are ready. Then checks if the output has matched timestamp.
- for (Integer id : captureIdToTime.keySet()) {
- assertThat(imageProxyBundleCaptor.getValue().getImageProxy(id).get(0,
- TimeUnit.SECONDS).getImageInfo().getTimestamp()).isEqualTo(
- captureIdToTime.get(id));
- }
- }
-
- // Make sure that closing the ProcessingImageReader while the CaptureProcessor is processing
- // the image is safely done so that the CaptureProcessor will not be accessing closed images
- @Test
- public void canCloseWhileProcessingIsOccurring()
- throws InterruptedException {
- // Sets the callback from ProcessingImageReader to start processing
- WaitingCaptureProcessor waitingCaptureProcessor = new WaitingCaptureProcessor();
- ProcessingImageReader processingImageReader = new ProcessingImageReader.Builder(
- mMetadataImageReader, mCaptureBundle, waitingCaptureProcessor).build();
- processingImageReader.setOnImageAvailableListener(mock(
- ImageReaderProxy.OnImageAvailableListener.class),
- CameraXExecutors.mainThreadExecutor());
- Map<Integer, Long> resultMap = new HashMap<>();
- resultMap.put(CAPTURE_ID_0, TIMESTAMP_0);
- resultMap.put(CAPTURE_ID_1, TIMESTAMP_1);
-
- // Cache current CaptureBundle as the TagBundle key for generate the fake image
- mTagBundleKey = processingImageReader.getTagBundleKey();
-
- // Trigger the Images so that the CaptureProcessor starts
- for (Map.Entry<Integer, Long> idTimestamp : resultMap.entrySet()) {
- triggerImageAvailable(idTimestamp.getKey(), idTimestamp.getValue());
- }
-
- // Ensure tasks are posted to the processing executor
- shadowOf(getMainLooper()).idle();
-
- // Wait for CaptureProcessor.process() to start so that it is in the middle of processing
- assertThat(waitingCaptureProcessor.waitForProcessingToStart(3000)).isTrue();
-
- processingImageReader.close();
-
- // Allow the CaptureProcessor to continue processing. Calling finishProcessing() will
- // cause the CaptureProcessor to start accessing the ImageProxy. If the ImageProxy has
- // already been closed then we will time out at waitForProcessingToComplete().
- waitingCaptureProcessor.finishProcessing();
-
- // The processing will only complete if no exception was thrown during the processing
- // which causes it to return prematurely.
- assertThat(waitingCaptureProcessor.waitForProcessingToComplete(3000)).isTrue();
- }
-
- // Tests that a ProcessingImageReader can be closed while in the process of receiving
- // ImageProxies for an ImageProxyBundle.
- @Test
- public void closeImageHalfway() throws InterruptedException {
- // Sets the callback from ProcessingImageReader to start processing
- ProcessingImageReader processingImageReader = new ProcessingImageReader.Builder(
- mMetadataImageReader, mCaptureBundle, NOOP_PROCESSOR).setPostProcessExecutor(
- sPausedExecutor).build();
- processingImageReader.setOnImageAvailableListener(mock(
- ImageReaderProxy.OnImageAvailableListener.class),
- CameraXExecutors.mainThreadExecutor());
-
- // Cache current CaptureBundle as the TagBundle key for generate the fake image
- mTagBundleKey = processingImageReader.getTagBundleKey();
- triggerImageAvailable(CAPTURE_ID_0, TIMESTAMP_0);
-
- // Ensure the first image is received by the ProcessingImageReader
- shadowOf(getMainLooper()).idle();
-
- // The ProcessingImageReader is closed after receiving the first image, but before
- // receiving enough images for the entire ImageProxyBundle.
- processingImageReader.close();
-
- assertThat(mImageReaderProxy.isClosed()).isTrue();
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void imageReaderSizeIsSmallerThanCaptureBundle() {
- // Creates a ProcessingImageReader with maximum Image number smaller than CaptureBundle
- // size.
- ImageReaderProxy imageReaderProxy = new FakeImageReaderProxy(1);
- MetadataImageReader metadataImageReader = new MetadataImageReader(imageReaderProxy);
-
- // Expects to throw exception when creating ProcessingImageReader.
- new ProcessingImageReader.Builder(metadataImageReader, mCaptureBundle,
- NOOP_PROCESSOR).build();
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void captureStageExceedMaxCaptureStage_setCaptureBundleThrowsException() {
- // Creates a ProcessingImageReader with maximum Image number.
- ProcessingImageReader processingImageReader = new ProcessingImageReader.Builder(100, 100,
- ImageFormat.YUV_420_888, 2, mCaptureBundle, mock(CaptureProcessor.class)).build();
-
- // Expects to throw exception when invoke the setCaptureBundle method with a
- // CaptureBundle size greater than maximum image number.
- processingImageReader.setCaptureBundle(
- CaptureBundles.createCaptureBundle(mCaptureStage1, mCaptureStage2, mCaptureStage3));
- }
-
- @Test
- public void imageReaderFormatIsOutputFormat() {
- // Creates a ProcessingImageReader with input format YUV_420_888 and output JPEG
- ProcessingImageReader processingImageReader = new ProcessingImageReader.Builder(100, 100,
- ImageFormat.YUV_420_888, 2, mCaptureBundle,
- mock(CaptureProcessor.class)).setOutputFormat(ImageFormat.JPEG).build();
-
- assertThat(processingImageReader.getImageFormat()).isEqualTo(ImageFormat.JPEG);
- }
-
- @Test
- public void canCloseUnderlyingCaptureProcessor() throws InterruptedException {
- // Sets up the underlying capture processor
- CaptureProcessor captureProcessor = mock(CaptureProcessor.class);
- AtomicReference<CallbackToFutureAdapter.Completer<Void>> underlyingCompleterReference =
- new AtomicReference<>();
- ListenableFuture<Void> underlyingCloseFuture =
- CallbackToFutureAdapter.getFuture(completer -> {
- underlyingCompleterReference.set(completer);
- return "underlyingCloseFuture";
- });
- when(captureProcessor.getCloseFuture()).thenReturn(underlyingCloseFuture);
- ProcessingImageReader processingImageReader = new ProcessingImageReader.Builder(
- mMetadataImageReader, mCaptureBundle, captureProcessor).build();
-
- // Calls the close() function of the ProcessingImageReader
- processingImageReader.close();
-
- // Verifies whether close() function of the underlying capture processor is called
- verify(captureProcessor, times(1)).close();
-
- // Sets up the listener to monitor whether the close future is closed or not.
- CountDownLatch closedLatch = new CountDownLatch(1);
- processingImageReader.getCloseFuture().addListener(() -> closedLatch.countDown(),
- CameraXExecutors.directExecutor());
-
- // Checks that the close future is not completed before the underlying capture processor
- // complete their close futures
- assertThat(closedLatch.await(1000, TimeUnit.MILLISECONDS)).isFalse();
-
- // Completes the completer of the underlying capture processor to complete their close
- // future
- underlyingCompleterReference.get().set(null);
-
- // Checks whether the close future of ProcessingImageReader is completed after the
- // underlying capture processor complete their close futures
- assertThat(closedLatch.await(1000, TimeUnit.MILLISECONDS)).isTrue();
- }
-
- private void triggerImageAvailable(int captureId, long timestamp) throws InterruptedException {
- TagBundle tagBundle = TagBundle.create(new Pair<>(mTagBundleKey, captureId));
- mImageReaderProxy.triggerImageAvailable(tagBundle, timestamp);
- FakeCameraCaptureResult.Builder builder = new FakeCameraCaptureResult.Builder();
- builder.setTimestamp(timestamp);
- builder.setTag(tagBundle);
-
- mMetadataImageReader.getCameraCaptureCallback().onCaptureCompleted(builder.build());
- }
-
- // Only allows for processing once.
- private static class WaitingCaptureProcessor implements CaptureProcessor {
- // Block processing so that the ProcessingImageReader can be closed before the
- // CaptureProcessor has finished accessing the ImageProxy and ImageProxyBundle
- private final CountDownLatch mProcessingLatch = new CountDownLatch(1);
-
- // To wait for processing to start. This makes sure that the ProcessingImageReader can be
- // closed after processing has started
- private final CountDownLatch mProcessingStartLatch = new CountDownLatch(1);
-
- // Block processing from completing. This ensures that the CaptureProcessor has finished
- // accessing the ImageProxy and ImageProxyBundle successfully.
- private final CountDownLatch mProcessingComplete = new CountDownLatch(1);
-
- WaitingCaptureProcessor() {
- }
-
- @Override
- public void onOutputSurface(@NonNull Surface surface, int imageFormat) {
- }
-
- @Override
- public void process(@NonNull ImageProxyBundle bundle) {
- mProcessingStartLatch.countDown();
- try {
- mProcessingLatch.await();
- } catch (InterruptedException e) {
- e.printStackTrace();
- return;
- }
-
- ImageProxy imageProxy;
- try {
- imageProxy = bundle.getImageProxy(CAPTURE_ID_0).get();
- } catch (ExecutionException | InterruptedException e) {
- e.printStackTrace();
- return;
- }
-
- // Try to get the crop rect. If the image has already been closed it will thrown an
- // IllegalStateException
- try {
- imageProxy.getFormat();
- } catch (IllegalStateException e) {
- e.printStackTrace();
- return;
- }
-
- mProcessingComplete.countDown();
- }
-
- @Override
- public void onResolutionUpdate(@NonNull Size size) {
- }
-
- void finishProcessing() {
- mProcessingLatch.countDown();
- }
-
- /** Returns false if it fails to start processing. */
- boolean waitForProcessingToStart(long timeout) {
- try {
- return mProcessingStartLatch.await(timeout, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- return false;
- }
- }
-
- /** Returns false if processing does not complete. */
- boolean waitForProcessingToComplete(long timeout) {
- try {
- return mProcessingComplete.await(timeout, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- return false;
- }
- }
- }
-}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/SettableImageProxyBundleTest.java b/camera/camera-core/src/test/java/androidx/camera/core/SettableImageProxyBundleTest.java
deleted file mode 100644
index 37fe524..0000000
--- a/camera/camera-core/src/test/java/androidx/camera/core/SettableImageProxyBundleTest.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright 2019 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.core;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.os.Build;
-
-import androidx.camera.testing.fakes.FakeImageInfo;
-import androidx.camera.testing.fakes.FakeImageProxy;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
-import org.robolectric.annotation.internal.DoNotInstrument;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-@RunWith(RobolectricTestRunner.class)
-@DoNotInstrument
-@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class SettableImageProxyBundleTest {
- private static final int CAPTURE_ID_0 = 0;
- private static final int CAPTURE_ID_1 = 1;
- private static final int CAPTURE_ID_NONEXISTANT = 5;
- private static final long TIMESTAMP_0 = 10L;
- private static final long TIMESTAMP_1 = 20L;
- private final ImageInfo mImageInfo0 = new FakeImageInfo();
- private final ImageInfo mImageInfo1 = new FakeImageInfo();
- private ImageProxy mImageProxy0;
- private ImageProxy mImageProxy1;
- private List<Integer> mCaptureIdList;
- private SettableImageProxyBundle mImageProxyBundle;
- private String mTagBundleKey = "fakeTagBundleKey";
- @Before
- public void setup() {
- ((FakeImageInfo) mImageInfo0).setTimestamp(TIMESTAMP_0);
- ((FakeImageInfo) mImageInfo1).setTimestamp(TIMESTAMP_1);
- ((FakeImageInfo) mImageInfo0).setTag(mTagBundleKey, CAPTURE_ID_0);
- ((FakeImageInfo) mImageInfo1).setTag(mTagBundleKey, CAPTURE_ID_1);
- mImageProxy0 = new FakeImageProxy(mImageInfo0);
- mImageProxy1 = new FakeImageProxy(mImageInfo1);
-
- mCaptureIdList = new ArrayList<>();
- mCaptureIdList.add(CAPTURE_ID_0);
- mCaptureIdList.add(CAPTURE_ID_1);
-
- mImageProxyBundle = new SettableImageProxyBundle(mCaptureIdList, mTagBundleKey);
- }
-
- @Test
- public void canInvokeMatchedImageProxyFuture() throws InterruptedException,
- ExecutionException, TimeoutException {
-
- // Inputs two ImageProxy to SettableImageProxyBundle.
- mImageProxyBundle.addImageProxy(mImageProxy0);
- mImageProxyBundle.addImageProxy(mImageProxy1);
-
- // Tries to get the Images for the ListenableFutures got from SettableImageProxyBundle.
- ImageProxy result0 = mImageProxyBundle.getImageProxy(CAPTURE_ID_0).get(0, TimeUnit.SECONDS);
- ImageProxy result1 = mImageProxyBundle.getImageProxy(CAPTURE_ID_1).get(0, TimeUnit.SECONDS);
-
- // Checks if the results match what was input.
- assertThat(result0.getImageInfo()).isSameInstanceAs(mImageInfo0);
- assertThat(result0).isSameInstanceAs(mImageProxy0);
- assertThat(result1.getImageInfo()).isSameInstanceAs(mImageInfo1);
- assertThat(result1).isSameInstanceAs(mImageProxy1);
- }
-
- @Test
- public void canInvokeMatchedImageProxyFutureWithMultiTag() throws InterruptedException,
- ExecutionException, TimeoutException {
- FakeImageInfo imageInfo0 = new FakeImageInfo();
- imageInfo0.setTimestamp(TIMESTAMP_0);
- FakeImageInfo imageInfo1 = new FakeImageInfo();
- imageInfo1.setTimestamp(TIMESTAMP_1);
-
- imageInfo0.setTag(mTagBundleKey, CAPTURE_ID_0);
- imageInfo1.setTag(mTagBundleKey, CAPTURE_ID_1);
-
- ImageProxy imageProxy0;
- imageProxy0 = new FakeImageProxy(imageInfo0);
- ImageProxy imageProxy1;
- imageProxy1 = new FakeImageProxy(imageInfo1);
-
- mImageProxyBundle.addImageProxy(imageProxy0);
- mImageProxyBundle.addImageProxy(imageProxy1);
-
- // Tries to get the Images for the ListenableFutures got from SettableImageProxyBundle.
- ImageProxy result0 = mImageProxyBundle.getImageProxy(CAPTURE_ID_0).get(0, TimeUnit.SECONDS);
- ImageProxy result1 = mImageProxyBundle.getImageProxy(CAPTURE_ID_1).get(0, TimeUnit.SECONDS);
-
- // Checks if the results match what was input.
- assertThat(result0.getImageInfo()).isSameInstanceAs(imageInfo0);
- assertThat(result0).isSameInstanceAs(imageProxy0);
- assertThat(result1.getImageInfo()).isSameInstanceAs(imageInfo1);
- assertThat(result1).isSameInstanceAs(imageProxy1);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void exceptionWhenAddingImageWithInvalidCaptureId() {
- ImageInfo imageInfo = new FakeImageInfo();
- ImageProxy imageProxy = new FakeImageProxy(imageInfo);
-
- // Adds an ImageProxy with a capture id which doesn't exist in the initial list.
- ((FakeImageInfo) imageInfo).setTag(mTagBundleKey, CAPTURE_ID_NONEXISTANT);
- ((FakeImageProxy) imageProxy).setImageInfo(imageInfo);
-
- // Expects to throw exception while adding ImageProxy.
- mImageProxyBundle.addImageProxy(imageProxy);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void exceptionWhenRetrievingImageWithInvalidCaptureId() throws InterruptedException,
- ExecutionException, TimeoutException {
- // Tries to get a ImageProxy with non-existed capture id. Expects to throw exception
- // while getting ImageProxy.
- mImageProxyBundle.getImageProxy(CAPTURE_ID_NONEXISTANT).get(0, TimeUnit.SECONDS);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void exceptionWhenAddingImageWithInvalidCaptureIdInMultiTagCase() {
- ImageInfo imageInfo = new FakeImageInfo();
- ImageProxy imageProxy = new FakeImageProxy(imageInfo);
-
- // Adds an ImageProxy with a capture id which doesn't exist in the initial list.
- ((FakeImageInfo) imageInfo).setTag(mTagBundleKey,
- CAPTURE_ID_NONEXISTANT);
- ((FakeImageProxy) imageProxy).setImageInfo(imageInfo);
-
- // Expects to throw exception while adding ImageProxy.
- mImageProxyBundle.addImageProxy(imageProxy);
- }
-}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorWithExecutorTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorWithExecutorTest.kt
index 9047555..80aadbac 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorWithExecutorTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/processing/SurfaceProcessorWithExecutorTest.kt
@@ -93,7 +93,7 @@
}
}, executor)
// Act: invoke methods.
- processorWithExecutor.onInputSurface(SurfaceRequest(SIZE, FakeCamera(), false) {})
+ processorWithExecutor.onInputSurface(SurfaceRequest(SIZE, FakeCamera()) {})
processorWithExecutor.onOutputSurface(mock(SurfaceOutput::class.java))
shadowOf(getMainLooper()).idle()
shadowOf(executorThread.looper).idle()
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdaptingCaptureProcessor.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdaptingCaptureProcessor.java
deleted file mode 100644
index dd46bbc..0000000
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdaptingCaptureProcessor.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright 2020 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.internal;
-
-import android.content.Context;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.TotalCaptureResult;
-import android.media.Image;
-import android.util.Pair;
-import android.util.Size;
-import android.view.Surface;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.camera.camera2.impl.Camera2CameraCaptureResultConverter;
-import androidx.camera.core.ExperimentalGetImage;
-import androidx.camera.core.ImageInfo;
-import androidx.camera.core.ImageProxy;
-import androidx.camera.core.impl.CameraCaptureResult;
-import androidx.camera.core.impl.CameraCaptureResults;
-import androidx.camera.core.impl.CaptureProcessor;
-import androidx.camera.core.impl.ImageProxyBundle;
-import androidx.camera.extensions.impl.CaptureProcessorImpl;
-import androidx.camera.extensions.impl.ExtenderStateListener;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * A {@link CaptureProcessor} that calls a vendor provided implementation.
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public final class AdaptingCaptureProcessor implements CaptureProcessor, VendorProcessor {
- @NonNull
- private final CaptureProcessorImpl mImpl;
- @Nullable
- private volatile Surface mSurface;
- private volatile int mImageFormat;
- private volatile Size mResolution;
-
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- private boolean mActive = false;
-
- private BlockingCloseAccessCounter mAccessCounter = new BlockingCloseAccessCounter();
-
- public AdaptingCaptureProcessor(@NonNull CaptureProcessorImpl impl) {
- mImpl = impl;
- }
-
- /**
- * Invoked after
- * {@link ExtenderStateListener#onInit(String, CameraCharacteristics, Context)}()} to
- * initialize the processor.
- */
- @Override
- public void onInit() {
- if (!mAccessCounter.tryIncrement()) {
- return;
- }
-
- // Delay the onOutputSurface / onImageFormatUpdate/ onResolutionUpdate calls because on
- // some OEM devices, these CaptureProcessImpl configuration should be performed only after
- // onInit. Otherwise it will cause black preview issue.
- try {
- mImpl.onOutputSurface(mSurface, mImageFormat);
- mImpl.onImageFormatUpdate(mImageFormat);
- mImpl.onResolutionUpdate(mResolution);
- } finally {
- mAccessCounter.decrement();
- }
-
- synchronized (mLock) {
- mActive = true;
- }
- }
-
- @Override
- public void onDeInit() {
- synchronized (mLock) {
- mActive = false;
- }
- }
-
- @Override
- public void close() {
- mAccessCounter.destroyAndWaitForZeroAccess();
- mSurface = null;
- mResolution = null;
- }
-
- @Override
- public void onOutputSurface(@NonNull Surface surface, int imageFormat) {
- mSurface = surface;
- mImageFormat = imageFormat;
- }
-
- @Override
- public void onResolutionUpdate(@NonNull Size size) {
- mResolution = size;
- }
-
- @Override
- @ExperimentalGetImage
- public void process(@NonNull ImageProxyBundle bundle) {
- synchronized (mLock) {
- if (!mActive) {
- return;
- }
-
- List<Integer> ids = bundle.getCaptureIds();
-
- Map<Integer, Pair<Image, TotalCaptureResult>> bundleMap = new HashMap<>();
-
- for (Integer id : ids) {
- ListenableFuture<ImageProxy> imageProxyListenableFuture = bundle.getImageProxy(id);
- try {
- ImageProxy imageProxy = imageProxyListenableFuture.get(5, TimeUnit.SECONDS);
- Image image = imageProxy.getImage();
- if (image == null) {
- return;
- }
-
- ImageInfo imageInfo = imageProxy.getImageInfo();
-
- CameraCaptureResult result =
- CameraCaptureResults.retrieveCameraCaptureResult(imageInfo);
- if (result == null) {
- return;
- }
-
- CaptureResult captureResult =
- Camera2CameraCaptureResultConverter.getCaptureResult(result);
- if (captureResult == null) {
- return;
- }
-
- TotalCaptureResult totalCaptureResult = (TotalCaptureResult) captureResult;
- if (totalCaptureResult == null) {
- return;
- }
-
- Pair<Image, TotalCaptureResult> imageCapturePair = new Pair<>(
- imageProxy.getImage(), totalCaptureResult);
- bundleMap.put(id, imageCapturePair);
- } catch (TimeoutException | ExecutionException | InterruptedException e) {
- return;
- }
- }
-
- if (!mAccessCounter.tryIncrement()) {
- return;
- }
-
- try {
- mImpl.process(bundleMap);
- } finally {
- mAccessCounter.decrement();
- }
- }
- }
-}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdaptingCaptureStage.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdaptingCaptureStage.java
deleted file mode 100644
index f9bc984..0000000
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdaptingCaptureStage.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2020 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.internal;
-
-import android.hardware.camera2.CaptureRequest;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.OptIn;
-import androidx.annotation.RequiresApi;
-import androidx.camera.camera2.impl.Camera2ImplConfig;
-import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
-import androidx.camera.core.impl.CaptureConfig;
-import androidx.camera.core.impl.CaptureStage;
-import androidx.camera.extensions.impl.CaptureStageImpl;
-
-/** A {@link CaptureStage} that calls a vendor provided implementation. */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public final class AdaptingCaptureStage implements CaptureStage {
-
- private final CaptureConfig mCaptureRequestConfiguration;
- private final int mId;
-
- @SuppressWarnings("unchecked")
- @OptIn(markerClass = ExperimentalCamera2Interop.class)
- public AdaptingCaptureStage(@NonNull CaptureStageImpl impl) {
- mId = impl.getId();
- Camera2ImplConfig.Builder camera2ConfigurationBuilder = new Camera2ImplConfig.Builder();
-
- for (Pair<CaptureRequest.Key, Object> captureParameter : impl.getParameters()) {
- camera2ConfigurationBuilder.setCaptureRequestOption(captureParameter.first,
- captureParameter.second);
- }
-
- CaptureConfig.Builder captureConfigBuilder = new CaptureConfig.Builder();
- captureConfigBuilder.addImplementationOptions(camera2ConfigurationBuilder.build());
- mCaptureRequestConfiguration = captureConfigBuilder.build();
- }
-
- @Override
- public int getId() {
- return mId;
- }
-
- @Override
- @NonNull
- public CaptureConfig getCaptureConfig() {
- return mCaptureRequestConfiguration;
- }
-}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdaptingPreviewProcessor.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdaptingPreviewProcessor.java
deleted file mode 100644
index e3fda54..0000000
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdaptingPreviewProcessor.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Copyright 2020 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.internal;
-
-import android.content.Context;
-import android.graphics.ImageFormat;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.TotalCaptureResult;
-import android.media.Image;
-import android.util.Size;
-import android.view.Surface;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-import androidx.camera.camera2.impl.Camera2CameraCaptureResultConverter;
-import androidx.camera.core.ExperimentalGetImage;
-import androidx.camera.core.ImageInfo;
-import androidx.camera.core.ImageProxy;
-import androidx.camera.core.Logger;
-import androidx.camera.core.impl.CameraCaptureResult;
-import androidx.camera.core.impl.CameraCaptureResults;
-import androidx.camera.core.impl.CaptureProcessor;
-import androidx.camera.core.impl.ImageProxyBundle;
-import androidx.camera.extensions.impl.ExtenderStateListener;
-import androidx.camera.extensions.impl.PreviewImageProcessorImpl;
-import androidx.core.util.Preconditions;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-
-/**
- * A {@link CaptureProcessor} that calls a vendor provided preview processing implementation.
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public final class AdaptingPreviewProcessor implements CaptureProcessor, VendorProcessor {
- private static final String TAG = "AdaptingPreviewProcesso";
- private final PreviewImageProcessorImpl mImpl;
- private volatile Surface mSurface;
- private volatile int mImageFormat;
- private volatile Size mResolution;
-
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- private boolean mActive = false;
-
- /**
- * Invoked after
- * {@link ExtenderStateListener#onInit(String, CameraCharacteristics, Context)}()} to
- * initialize the processor.
- */
- @Override
- public void onInit() {
- // Delay the onOutputSurface / onImageFormatUpdate/ onResolutionUpdate calls because on
- // some OEM devices, these CaptureProcessImpl configuration should be performed only after
- // onInit. Otherwise it will cause black preview issue.
- if (!mAccessCounter.tryIncrement()) {
- return;
- }
-
- try {
- mImpl.onResolutionUpdate(mResolution);
- mImpl.onOutputSurface(mSurface, mImageFormat);
- // No input formats other than YUV_420_888 are allowed.
- mImpl.onImageFormatUpdate(ImageFormat.YUV_420_888);
- } finally {
- mAccessCounter.decrement();
- }
-
- synchronized (mLock) {
- mActive = true;
- }
- }
-
- private BlockingCloseAccessCounter mAccessCounter = new BlockingCloseAccessCounter();
-
- public AdaptingPreviewProcessor(@NonNull PreviewImageProcessorImpl impl) {
- mImpl = impl;
- }
-
- @Override
- public void onOutputSurface(@NonNull Surface surface, int imageFormat) {
- if (!mAccessCounter.tryIncrement()) {
- return;
- }
-
- try {
- mSurface = surface;
- mImageFormat = imageFormat;
- } finally {
- mAccessCounter.decrement();
- }
- }
-
- @ExperimentalGetImage
- @Override
- public void process(@NonNull ImageProxyBundle bundle) {
- synchronized (mLock) {
- if (!mActive) {
- return;
- }
-
- List<Integer> ids = bundle.getCaptureIds();
- Preconditions.checkArgument(ids.size() == 1,
- "Processing preview bundle must be 1, but found " + ids.size());
-
- ListenableFuture<ImageProxy> imageProxyListenableFuture = bundle.getImageProxy(
- ids.get(0));
- Preconditions.checkArgument(imageProxyListenableFuture.isDone());
-
- ImageProxy imageProxy;
- try {
- imageProxy = imageProxyListenableFuture.get();
- } catch (ExecutionException | InterruptedException e) {
- Logger.e(TAG, "Unable to retrieve ImageProxy from bundle");
- return;
- }
-
- Image image = imageProxy.getImage();
-
- ImageInfo imageInfo = imageProxy.getImageInfo();
- CameraCaptureResult result =
- CameraCaptureResults.retrieveCameraCaptureResult(imageInfo);
- CaptureResult captureResult =
- Camera2CameraCaptureResultConverter.getCaptureResult(result);
-
- TotalCaptureResult totalCaptureResult = null;
- if (captureResult instanceof TotalCaptureResult) {
- totalCaptureResult = (TotalCaptureResult) captureResult;
- }
-
- if (image == null) {
- return;
- }
-
- if (!mAccessCounter.tryIncrement()) {
- return;
- }
-
- try {
- mImpl.process(image, totalCaptureResult);
- } finally {
- mAccessCounter.decrement();
- }
- }
- }
-
- @Override
- public void onResolutionUpdate(@NonNull Size size) {
- if (!mAccessCounter.tryIncrement()) {
- return;
- }
-
- try {
- mResolution = size;
- } finally {
- mAccessCounter.decrement();
- }
- }
-
- @Override
- public void onDeInit() {
- synchronized (mLock) {
- mActive = false;
- }
- }
-
- @Override
- public void close() {
- mAccessCounter.destroyAndWaitForZeroAccess();
- mSurface = null;
- mResolution = null;
- }
-}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdaptingRequestUpdateProcessor.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdaptingRequestUpdateProcessor.java
deleted file mode 100644
index f10a4aa..0000000
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/AdaptingRequestUpdateProcessor.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2020 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.internal;
-
-import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.TotalCaptureResult;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.camera.camera2.impl.Camera2CameraCaptureResultConverter;
-import androidx.camera.core.ImageInfo;
-import androidx.camera.core.impl.CameraCaptureResult;
-import androidx.camera.core.impl.CameraCaptureResults;
-import androidx.camera.core.impl.CaptureStage;
-import androidx.camera.core.impl.ImageInfoProcessor;
-import androidx.camera.extensions.impl.CaptureStageImpl;
-import androidx.camera.extensions.impl.PreviewExtenderImpl;
-import androidx.camera.extensions.impl.RequestUpdateProcessorImpl;
-import androidx.core.util.Preconditions;
-
-/**
- * A {@link ImageInfoProcessor} that calls a vendor provided preview processing implementation.
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-public final class AdaptingRequestUpdateProcessor implements ImageInfoProcessor, VendorProcessor {
- private final PreviewExtenderImpl mPreviewExtenderImpl;
- private final RequestUpdateProcessorImpl mProcessorImpl;
- private BlockingCloseAccessCounter mAccessCounter = new BlockingCloseAccessCounter();
-
- public AdaptingRequestUpdateProcessor(@NonNull PreviewExtenderImpl previewExtenderImpl) {
- Preconditions.checkArgument(previewExtenderImpl.getProcessorType()
- == PreviewExtenderImpl.ProcessorType.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY,
- "AdaptingRequestUpdateProcess can only adapt extender with "
- + "PROCESSOR_TYPE_REQUEST_UPDATE_ONLY ProcessorType.");
- mPreviewExtenderImpl = previewExtenderImpl;
- mProcessorImpl = (RequestUpdateProcessorImpl) mPreviewExtenderImpl.getProcessor();
- }
-
- @Override
- @Nullable
- public CaptureStage getCaptureStage() {
- if (!mAccessCounter.tryIncrement()) {
- return null;
- }
-
- try {
- return new AdaptingCaptureStage(mPreviewExtenderImpl.getCaptureStage());
- } finally {
- mAccessCounter.decrement();
- }
-
- }
-
- @Override
- public boolean process(@NonNull ImageInfo imageInfo) {
- if (!mAccessCounter.tryIncrement()) {
- return false;
- }
-
- try {
- boolean processResult = false;
-
- CameraCaptureResult result = CameraCaptureResults.retrieveCameraCaptureResult(
- imageInfo);
- CaptureResult captureResult = Camera2CameraCaptureResultConverter.getCaptureResult(
- result);
-
- if (captureResult instanceof TotalCaptureResult) {
-
- CaptureStageImpl captureStageImpl =
- mProcessorImpl.process((TotalCaptureResult) captureResult);
- processResult = captureStageImpl != null;
- }
- return processResult;
- } finally {
- mAccessCounter.decrement();
- }
- }
-
- @Override
- public void close() {
- mAccessCounter.destroyAndWaitForZeroAccess();
- }
-}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BlockingCloseAccessCounter.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BlockingCloseAccessCounter.java
deleted file mode 100644
index b41659e..0000000
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/BlockingCloseAccessCounter.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright 2020 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.internal;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.RequiresApi;
-
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * A counter for blocking closing until all access to the counter has been completed.
- *
- * <pre>{@code
- * public void callingMethod() {
- * if (!mAtomicAccessCounter.tryIncrement()) {
- * return;
- * }
- *
- * try {
- * // Some work that needs to be done
- * } finally {
- * mAtomicAccessCounter.decrement();
- * }
- * }
- *
- * // Method that can only be called after all callingMethods are done with access
- * public void blockingMethod() {
- * destroyAndWaitForZeroAccess();
- * }
- * }</pre>
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-final class BlockingCloseAccessCounter {
- @GuardedBy("mLock")
- private AtomicInteger mAccessCount = new AtomicInteger(0);
- private final Lock mLock = new ReentrantLock();
- private final Condition mDoneCondition = mLock.newCondition();
-
- private static final int COUNTER_DESTROYED_FLAG = -1;
-
- /**
- * Attempt to increment the access counter.
- *
- * <p>Once {@link #destroyAndWaitForZeroAccess()} has returned this will always fail to
- * increment, meaning access is not safe.
- *
- * @return true if the counter was incremented, false otherwise
- */
- boolean tryIncrement() {
- mLock.lock();
- try {
- if (mAccessCount.get() == COUNTER_DESTROYED_FLAG) {
- return false;
- }
- mAccessCount.getAndIncrement();
- } finally {
- mLock.unlock();
- }
- return true;
- }
-
- /**
- * Decrement the access counter.
- **/
- void decrement() {
- mLock.lock();
- try {
- switch (mAccessCount.getAndDecrement()) {
- case COUNTER_DESTROYED_FLAG:
- throw new IllegalStateException("Unable to decrement. Counter already "
- + "destroyed");
- case 0:
- throw new IllegalStateException("Unable to decrement. No corresponding "
- + "counter increment");
- default:
- //
- }
- mDoneCondition.signal();
- } finally {
- mLock.unlock();
- }
- }
-
- /**
- * Blocks until there are zero accesses in the counter.
- *
- * <p>Once this call completes, the counter is destroyed and can not be incremented and
- * decremented.
- */
- void destroyAndWaitForZeroAccess() {
- mLock.lock();
-
- try {
- while (!mAccessCount.compareAndSet(0, COUNTER_DESTROYED_FLAG)) {
- try {
- mDoneCondition.await();
- } catch (InterruptedException e) {
- // Continue to check
- }
- }
- } finally {
- mLock.unlock();
- }
- }
-}
diff --git a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/AdaptingCaptureProcessorTest.kt b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/AdaptingCaptureProcessorTest.kt
deleted file mode 100644
index 54cb68d..0000000
--- a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/AdaptingCaptureProcessorTest.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright 2022 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.internal
-
-import android.hardware.camera2.TotalCaptureResult
-import android.media.Image
-import android.os.Build
-import android.util.Pair
-import android.util.Size
-import android.view.Surface
-import androidx.camera.camera2.internal.Camera2CameraCaptureResult
-import androidx.camera.core.impl.ImageProxyBundle
-import androidx.camera.core.impl.SingleImageProxyBundle
-import androidx.camera.core.impl.TagBundle
-import androidx.camera.core.internal.CameraCaptureResultImageInfo
-import androidx.camera.extensions.impl.CaptureProcessorImpl
-import androidx.camera.testing.fakes.FakeImageProxy
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mockito
-import org.mockito.Mockito.inOrder
-import org.robolectric.RobolectricTestRunner
-import org.robolectric.annotation.Config
-import org.robolectric.annotation.internal.DoNotInstrument
-
-private const val TAG_BUNDLE_KEY = "FakeTagBundleKey"
-private const val CAPTURE_ID = 0
-
-@RunWith(RobolectricTestRunner::class)
-@DoNotInstrument
-@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-class AdaptingCaptureProcessorTest {
- private val captureProcessorImpl = Mockito.mock(CaptureProcessorImpl::class.java)
- private var adaptingCaptureProcessor = AdaptingCaptureProcessor(captureProcessorImpl)
- private val imageProxyBundle = createFakeImageProxyBundle()
-
- @Test
- fun processDoesNotCallImplAfterClose() {
- callOnInitAndVerify()
- adaptingCaptureProcessor.close()
- adaptingCaptureProcessor.process(imageProxyBundle)
- Mockito.verifyNoMoreInteractions(captureProcessorImpl)
- }
-
- @Test
- fun onImageFormatUpdateDoesNotCallImplAfterClose() {
- adaptingCaptureProcessor.close()
- adaptingCaptureProcessor.onOutputSurface(Mockito.mock(Surface::class.java), 0)
- adaptingCaptureProcessor.onInit()
- Mockito.verifyNoMoreInteractions(captureProcessorImpl)
- }
-
- @Test
- fun onResolutionUpdateDoesNotCallImplAfterClose() {
- adaptingCaptureProcessor.close()
- adaptingCaptureProcessor.onResolutionUpdate(Size(640, 480))
- adaptingCaptureProcessor.onInit()
- Mockito.verifyNoMoreInteractions(captureProcessorImpl)
- }
-
- @Test
- fun processCanCallImplBeforeDeInit() {
- callOnInitAndVerify()
- adaptingCaptureProcessor.process(imageProxyBundle)
- Mockito.verify(captureProcessorImpl, Mockito.times(1)).process(any())
- }
-
- @Test
- fun processDoesNotCallImplAfterDeInit() {
- callOnInitAndVerify()
- adaptingCaptureProcessor.onDeInit()
- adaptingCaptureProcessor.process(imageProxyBundle)
- Mockito.verifyNoMoreInteractions(captureProcessorImpl)
- }
-
- private fun createFakeImageProxyBundle(
- bundleKey: String = TAG_BUNDLE_KEY,
- captureId: Int = CAPTURE_ID
- ): ImageProxyBundle {
- val fakeCameraCaptureResult = Mockito.mock(Camera2CameraCaptureResult::class.java)
- Mockito.`when`(fakeCameraCaptureResult.tagBundle)
- .thenReturn(TagBundle.create(Pair.create(bundleKey, captureId)))
- Mockito.`when`(fakeCameraCaptureResult.captureResult)
- .thenReturn(Mockito.mock(TotalCaptureResult::class.java))
- val fakeImageInfo = CameraCaptureResultImageInfo(fakeCameraCaptureResult)
- val fakeImageProxy = FakeImageProxy(fakeImageInfo)
- fakeImageProxy.image = Mockito.mock(Image::class.java)
- return SingleImageProxyBundle(fakeImageProxy, bundleKey)
- }
-
- private fun callOnInitAndVerify() {
- adaptingCaptureProcessor.onInit()
-
- inOrder(captureProcessorImpl).apply {
- verify(captureProcessorImpl).onOutputSurface(any(), anyInt())
- verify(captureProcessorImpl).onImageFormatUpdate(anyInt())
- verify(captureProcessorImpl).onResolutionUpdate(any())
- }
- }
-}
\ No newline at end of file
diff --git a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/AdaptingPreviewProcessorTest.java b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/AdaptingPreviewProcessorTest.java
deleted file mode 100644
index ba9d15b..0000000
--- a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/AdaptingPreviewProcessorTest.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright 2020 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.internal;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-
-import android.media.Image;
-import android.os.Build;
-import android.util.Size;
-import android.view.Surface;
-
-import androidx.camera.core.impl.ImageProxyBundle;
-import androidx.camera.core.impl.SingleImageProxyBundle;
-import androidx.camera.extensions.impl.PreviewImageProcessorImpl;
-import androidx.camera.testing.fakes.FakeImageInfo;
-import androidx.camera.testing.fakes.FakeImageProxy;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.InOrder;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
-import org.robolectric.annotation.internal.DoNotInstrument;
-
-@RunWith(RobolectricTestRunner.class)
-@DoNotInstrument
-@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class AdaptingPreviewProcessorTest {
- private AdaptingPreviewProcessor mAdaptingPreviewProcessor;
- private PreviewImageProcessorImpl mImpl;
- private ImageProxyBundle mImageProxyBundle;
- private String mTagBundleKey = "FakeTagBundleKey";
-
- @Before
- public void setup() {
- mImpl = mock(PreviewImageProcessorImpl.class);
-
- FakeImageInfo fakeImageInfo = new FakeImageInfo();
- // Use the key which SingleImageProxyBundle is used to get tag.
- fakeImageInfo.setTag(mTagBundleKey, 1);
-
- FakeImageProxy fakeImageProxy = new FakeImageProxy(fakeImageInfo);
- fakeImageProxy.setImage(mock(Image.class));
-
- mImageProxyBundle = new SingleImageProxyBundle(fakeImageProxy, mTagBundleKey);
- mAdaptingPreviewProcessor = new AdaptingPreviewProcessor(mImpl);
- }
-
- @Test
- public void processDoesNotCallImplAfterClose() {
- mAdaptingPreviewProcessor.close();
- mAdaptingPreviewProcessor.process(mImageProxyBundle);
- mAdaptingPreviewProcessor.onInit();
-
- verifyNoMoreInteractions(mImpl);
- }
-
- @Test
- public void onImageFormatUpdateDoesNotCallImplAfterClose() {
- mAdaptingPreviewProcessor.close();
- mAdaptingPreviewProcessor.onOutputSurface(mock(Surface.class), 0);
- mAdaptingPreviewProcessor.onInit();
-
- verifyNoMoreInteractions(mImpl);
- }
-
- @Test
- public void onResolutionUpdateDoesNotCallImplAfterClose() {
- mAdaptingPreviewProcessor.close();
- mAdaptingPreviewProcessor.onResolutionUpdate(new Size(640, 480));
- mAdaptingPreviewProcessor.onInit();
-
- verifyNoMoreInteractions(mImpl);
- }
-
- @Test
- public void processCanCallImplBeforeDeInit() {
- callOnInitAndVerify();
- mAdaptingPreviewProcessor.process(mImageProxyBundle);
- verify(mImpl, times(1)).process(any(), any());
- }
-
- @Test
- public void processDoesNotCallImplAfterDeInit() {
- callOnInitAndVerify();
- mAdaptingPreviewProcessor.onDeInit();
- mAdaptingPreviewProcessor.process(mImageProxyBundle);
-
- verifyNoMoreInteractions(mImpl);
- }
-
- private void callOnInitAndVerify() {
- mAdaptingPreviewProcessor.onInit();
-
- InOrder inOrderImpl = inOrder(mImpl);
- inOrderImpl.verify(mImpl).onResolutionUpdate(any());
- inOrderImpl.verify(mImpl).onOutputSurface(any(), anyInt());
- inOrderImpl.verify(mImpl).onImageFormatUpdate(anyInt());
- }
-}
diff --git a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/AdaptingRequestUpdateProcessorTest.java b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/AdaptingRequestUpdateProcessorTest.java
deleted file mode 100644
index ff8b784..0000000
--- a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/AdaptingRequestUpdateProcessorTest.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2020 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.internal;
-
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.hardware.camera2.CaptureResult;
-import android.hardware.camera2.TotalCaptureResult;
-import android.os.Build;
-
-import androidx.camera.camera2.impl.Camera2CameraCaptureResultConverter;
-import androidx.camera.core.ImageInfo;
-import androidx.camera.core.impl.CameraCaptureResult;
-import androidx.camera.core.impl.CameraCaptureResults;
-import androidx.camera.extensions.impl.PreviewExtenderImpl;
-import androidx.camera.extensions.impl.RequestUpdateProcessorImpl;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
-import org.robolectric.annotation.Implementation;
-import org.robolectric.annotation.Implements;
-import org.robolectric.annotation.internal.DoNotInstrument;
-
-@RunWith(RobolectricTestRunner.class)
-@DoNotInstrument
-@Config(minSdk = Build.VERSION_CODES.LOLLIPOP,
- shadows = {
- AdaptingRequestUpdateProcessorTest.ShadowCameraCaptureResults.class,
- AdaptingRequestUpdateProcessorTest.ShadowCamera2CameraCaptureResultConverter.class})
-public class AdaptingRequestUpdateProcessorTest {
- private AdaptingRequestUpdateProcessor mAdaptingRequestUpdateProcessor;
- private PreviewExtenderImpl mPreviewExtenderImpl;
- private RequestUpdateProcessorImpl mImpl;
- private ImageInfo mImageInfo;
-
- @Before
- public void setup() {
- mImpl = mock(RequestUpdateProcessorImpl.class);
- mPreviewExtenderImpl = mock(PreviewExtenderImpl.class);
- when(mPreviewExtenderImpl.getProcessor()).thenReturn(mImpl);
- when(mPreviewExtenderImpl.getProcessorType()).thenReturn(
- PreviewExtenderImpl.ProcessorType.PROCESSOR_TYPE_REQUEST_UPDATE_ONLY);
-
- mImageInfo = mock(ImageInfo.class);
-
- mAdaptingRequestUpdateProcessor = new AdaptingRequestUpdateProcessor(mPreviewExtenderImpl);
- }
-
- @Test
- public void getCaptureStageDoesNotCallImplAfterClose() {
- clearInvocations(mPreviewExtenderImpl);
- mAdaptingRequestUpdateProcessor.close();
-
- mAdaptingRequestUpdateProcessor.getCaptureStage();
-
- verifyNoMoreInteractions(mPreviewExtenderImpl);
- }
-
- @Test
- public void processDoesNotCallImplAfterClose() {
- mAdaptingRequestUpdateProcessor.close();
-
- mAdaptingRequestUpdateProcessor.process(mImageInfo);
-
- verifyNoMoreInteractions(mImpl);
- }
-
- /**
- * Shadow of {@link Camera2CameraCaptureResultConverter} to control return of
- * {@link #getCaptureResult(CameraCaptureResult)}.
- */
- @Implements(
- value = Camera2CameraCaptureResultConverter.class,
- minSdk = 21
- )
- static final class ShadowCamera2CameraCaptureResultConverter {
- /** Returns {@link TotalCaptureResult} regardless of input. */
- @Implementation
- public static CaptureResult getCaptureResult(CameraCaptureResult cameraCaptureResult) {
- return mock(TotalCaptureResult.class);
- }
- }
-
- /**
- * Shadow of {@link CameraCaptureResults} to control return of
- * {@link #retrieveCameraCaptureResult(ImageInfo)}.
- */
- @Implements(
- value = CameraCaptureResults.class,
- minSdk = 21
- )
- static final class ShadowCameraCaptureResults {
- /** Returns {@link CameraCaptureResult} regardless of input. */
- @Implementation
- public static CameraCaptureResult retrieveCameraCaptureResult(ImageInfo imageInfo) {
- return mock(CameraCaptureResult.class);
- }
- }
-
-}
diff --git a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/BlockingCloseAccessCounterTest.java b/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/BlockingCloseAccessCounterTest.java
deleted file mode 100644
index ececd6d..0000000
--- a/camera/camera-extensions/src/test/java/androidx/camera/extensions/internal/BlockingCloseAccessCounterTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2020 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.internal;
-
-import static org.junit.Assert.assertFalse;
-
-import android.os.Build;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.annotation.Config;
-import org.robolectric.annotation.internal.DoNotInstrument;
-
-@RunWith(RobolectricTestRunner.class)
-@DoNotInstrument
-@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class BlockingCloseAccessCounterTest {
- @Test(expected = IllegalStateException.class)
- public void decrementWithoutIncrementThrowsException() {
- BlockingCloseAccessCounter counter = new BlockingCloseAccessCounter();
-
- // Expect a IllegalStateException to be thrown
- counter.decrement();
- }
-
- @Test(expected = IllegalStateException.class)
- public void decrementAfterDestroy() {
- BlockingCloseAccessCounter counter = new BlockingCloseAccessCounter();
- counter.destroyAndWaitForZeroAccess();
-
- // Expect a IllegalStateException to be thrown
- counter.decrement();
- }
-
- @Test
- public void incrementAfterDestroyDoesNotIncrement() {
- BlockingCloseAccessCounter counter = new BlockingCloseAccessCounter();
- counter.destroyAndWaitForZeroAccess();
-
- assertFalse(counter.tryIncrement());
- }
-}
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/TimestampCaptureProcessor.java b/camera/camera-testing/src/main/java/androidx/camera/testing/TimestampCaptureProcessor.java
deleted file mode 100644
index 72c18fa..0000000
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/TimestampCaptureProcessor.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright 2019 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.testing;
-
-import android.media.Image;
-import android.util.Size;
-import android.view.Surface;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.RestrictTo;
-import androidx.camera.core.ExperimentalGetImage;
-import androidx.camera.core.ImageProxy;
-import androidx.camera.core.impl.CaptureProcessor;
-import androidx.camera.core.impl.ImageProxyBundle;
-import androidx.core.util.Preconditions;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-
-/**
- * A {@link CaptureProcessor} that wraps another CaptureProcessor and captures the timestamps of all
- * the {@link ImageProxy} that are processed by it.
- *
- * <p>This class is used for testing of preview processing only. The expectation is that each
- * {@link ImageProxyBundle} will only have a single {@link ImageProxy}.
- *
- * @hide
- */
-@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-@RestrictTo(RestrictTo.Scope.TESTS)
-public class TimestampCaptureProcessor implements CaptureProcessor {
- private CaptureProcessor mCaptureProcessor;
- private TimestampListener mTimestampListener;
-
- /**
- * @param captureProcessor The {@link CaptureProcessor} that is wrapped.
- * @param timestampListener The listener which receives the timestamp of all
- * {@link ImageProxy} which are processed.
- */
- public TimestampCaptureProcessor(@NonNull CaptureProcessor captureProcessor,
- @NonNull TimestampListener timestampListener) {
- mCaptureProcessor = captureProcessor;
- mTimestampListener = timestampListener;
- }
-
- /**
- * Interface for receiving the timestamps of all {@link ImageProxy} which are processed by the
- * wrapped {@link CaptureProcessor}.
- */
- @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
- public interface TimestampListener {
- /**
- * Called whenever an {@link ImageProxy} is processed.
- *
- * @param timestamp The timestamp of the {@link ImageProxy} that is processed.
- */
- void onTimestampAvailable(long timestamp);
- }
-
- @Override
- public void onOutputSurface(@NonNull Surface surface, int imageFormat) {
- mCaptureProcessor.onOutputSurface(surface, imageFormat);
- }
-
- @Override
- @ExperimentalGetImage
- public void process(@NonNull ImageProxyBundle bundle) {
- List<Integer> ids = bundle.getCaptureIds();
- Preconditions.checkArgument(ids.size() == 1,
- "Processing preview bundle must be 1, but found " + ids.size());
-
- ListenableFuture<ImageProxy> imageProxyListenableFuture = bundle.getImageProxy(ids.get(0));
- Preconditions.checkArgument(imageProxyListenableFuture.isDone());
-
- try {
- ImageProxy imageProxy = imageProxyListenableFuture.get();
- Image image = imageProxy.getImage();
- if (image == null) {
- return;
- }
-
- // Send timestamp
- mTimestampListener.onTimestampAvailable(image.getTimestamp());
- mCaptureProcessor.process(bundle);
- } catch (ExecutionException | InterruptedException e) {
- // Intentionally empty. Only the ImageProxy which can be retrieved need to have its
- // timestamp captured.
- }
- }
-
- @Override
- public void onResolutionUpdate(@NonNull Size size) {
- mCaptureProcessor.onResolutionUpdate(size);
- }
-}
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
index 3b47dfb..91b870c 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
@@ -544,7 +544,7 @@
}
}, CameraXExecutors.mainThreadExecutor());
} else {
- mSurfaceRequest = new SurfaceRequest(resolution, camera, false, targetFpsRange,
+ mSurfaceRequest = new SurfaceRequest(resolution, camera, targetFpsRange,
onSurfaceInvalidated);
mDeferrableSurface = mSurfaceRequest.getDeferrableSurface();
// When camera buffers from a REALTIME device are passed directly to a video encoder
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewDeviceTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewDeviceTest.kt
index 5df9f75..bccd2e9 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewDeviceTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/PreviewViewDeviceTest.kt
@@ -17,7 +17,6 @@
import android.content.Context
import android.graphics.Bitmap
-import android.graphics.PixelFormat
import android.graphics.Rect
import android.graphics.drawable.ColorDrawable
import android.os.Build
@@ -44,15 +43,12 @@
import androidx.camera.core.SurfaceRequest
import androidx.camera.core.ViewPort
import androidx.camera.core.impl.CameraInfoInternal
-import androidx.camera.core.impl.utils.executor.CameraXExecutors
-import androidx.camera.core.impl.utils.futures.FutureCallback
import androidx.camera.core.impl.utils.futures.Futures
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.testing.CameraPipeConfigTestRule
import androidx.camera.testing.CameraUtil
import androidx.camera.testing.CameraUtil.PreTestCameraIdList
import androidx.camera.testing.CoreAppTestUtil
-import androidx.camera.testing.SurfaceFormatUtil
import androidx.camera.testing.fakes.FakeActivity
import androidx.camera.testing.fakes.FakeCamera
import androidx.camera.testing.fakes.FakeCameraInfoInternal
@@ -578,33 +574,6 @@
}
@Test
- fun correctSurfacePixelFormat_whenRGBA8888IsRequired() {
- val cameraInfo = createCameraInfo(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2)
- val surfaceRequest = createRgb8888SurfaceRequest(cameraInfo)
- val future = surfaceRequest.deferrableSurface.surface
- activityScenario!!.onActivity {
- val previewView = PreviewView(context)
- setContentView(previewView)
- previewView.implementationMode = ImplementationMode.PERFORMANCE
- val surfaceProvider = previewView.surfaceProvider
- surfaceProvider.onSurfaceRequested(surfaceRequest)
- }
- val surface = arrayOfNulls<Surface>(1)
- val countDownLatch = CountDownLatch(1)
- Futures.addCallback(future, object : FutureCallback<Surface?> {
- override fun onSuccess(result: Surface?) {
- surface[0] = result
- countDownLatch.countDown()
- }
-
- override fun onFailure(t: Throwable) {}
- }, CameraXExecutors.directExecutor())
- Truth.assertThat(countDownLatch.await(TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)).isTrue()
- Truth.assertThat(SurfaceFormatUtil.getSurfaceFormat(surface[0]))
- .isEqualTo(PixelFormat.RGBA_8888)
- }
-
- @Test
fun canCreateValidMeteringPoint() {
val cameraInfo = createCameraInfo(
90,
@@ -1060,18 +1029,12 @@
activityScenario!!.onActivity { activity: FakeActivity -> activity.setContentView(view) }
}
- private fun createRgb8888SurfaceRequest(cameraInfo: CameraInfoInternal): SurfaceRequest {
- return createSurfaceRequest(cameraInfo, true)
- }
-
private fun createSurfaceRequest(
cameraInfo: CameraInfoInternal,
- isRGBA8888Required: Boolean = false
): SurfaceRequest {
val fakeCamera = FakeCamera( /*cameraControl=*/null, cameraInfo)
val surfaceRequest = SurfaceRequest(
- DEFAULT_SURFACE_SIZE, fakeCamera,
- isRGBA8888Required
+ DEFAULT_SURFACE_SIZE, fakeCamera
) {}
surfaceRequestList.add(surfaceRequest)
return surfaceRequest
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/SurfaceViewImplementationTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/SurfaceViewImplementationTest.kt
index 3e0fede..a3f1971 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/SurfaceViewImplementationTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/SurfaceViewImplementationTest.kt
@@ -68,7 +68,7 @@
mParent = FrameLayout(mContext)
setContentView(mParent)
- mSurfaceRequest = SurfaceRequest(ANY_SIZE, FakeCamera(), false) {}
+ mSurfaceRequest = SurfaceRequest(ANY_SIZE, FakeCamera()) {}
mImplementation = SurfaceViewImplementation(mParent, PreviewTransformation())
}
@@ -92,7 +92,7 @@
val previousSurfaceView = mImplementation.mSurfaceView
// Act.
- val sameResolutionSurfaceRequest = SurfaceRequest(ANY_SIZE, FakeCamera(), false) {}
+ val sameResolutionSurfaceRequest = SurfaceRequest(ANY_SIZE, FakeCamera()) {}
mImplementation.testSurfaceRequest(sameResolutionSurfaceRequest)
val newSurfaceView = mImplementation.mSurfaceView
@@ -110,7 +110,7 @@
// Act.
val differentSize: Size by lazy { Size(720, 480) }
val differentResolutionSurfaceRequest =
- SurfaceRequest(differentSize, FakeCamera(), false) {}
+ SurfaceRequest(differentSize, FakeCamera()) {}
mImplementation.testSurfaceRequest(differentResolutionSurfaceRequest)
val newSurfaceView = mImplementation.mSurfaceView
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/TextureViewImplementationTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/TextureViewImplementationTest.kt
index c6998bd..20d9291 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/TextureViewImplementationTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/TextureViewImplementationTest.kt
@@ -50,7 +50,7 @@
private val surfaceRequest: SurfaceRequest
get() {
if (_surfaceRequest == null) {
- _surfaceRequest = SurfaceRequest(ANY_SIZE, FakeCamera(), false) {}
+ _surfaceRequest = SurfaceRequest(ANY_SIZE, FakeCamera()) {}
}
return _surfaceRequest!!
}
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java b/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
index 1d141c6..1f3921e 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/PreviewView.java
@@ -681,10 +681,9 @@
.getImplementationType().equals(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2_LEGACY);
boolean hasSurfaceViewQuirk = DeviceQuirks.get(SurfaceViewStretchedQuirk.class) != null
|| DeviceQuirks.get(SurfaceViewNotCroppedByParentQuirk.class) != null;
- if (surfaceRequest.isRGBA8888Required() || Build.VERSION.SDK_INT <= 24 || isLegacyDevice
- || hasSurfaceViewQuirk) {
+ if (Build.VERSION.SDK_INT <= 24 || isLegacyDevice || hasSurfaceViewQuirk) {
// Force to use TextureView when the device is running android 7.0 and below, legacy
- // level, RGBA8888 is required or SurfaceView has quirks.
+ // level or SurfaceView has quirks.
return true;
}
switch (implementationMode) {
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/PreviewViewTest.java b/camera/camera-view/src/test/java/androidx/camera/view/PreviewViewTest.java
index 804de8a..fb1c45d 100644
--- a/camera/camera-view/src/test/java/androidx/camera/view/PreviewViewTest.java
+++ b/camera/camera-view/src/test/java/androidx/camera/view/PreviewViewTest.java
@@ -139,7 +139,6 @@
FakeCameraInfoInternal cameraInfoInternal = new FakeCameraInfoInternal();
cameraInfoInternal.setImplementationType(CameraInfo.IMPLEMENTATION_TYPE_CAMERA2);
return new SurfaceRequest(new Size(800, 600),
- new FakeCamera(null, cameraInfoInternal),
- /*isRGB8888Required*/ false, () -> {});
+ new FakeCamera(null, cameraInfoInternal), () -> {});
}
}
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
index 1d83c85..e812cab 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/ImageCaptureTest.kt
@@ -1197,10 +1197,7 @@
assertThat(imageCapture.flashMode).isEqualTo(ImageCapture.FLASH_MODE_ON)
}
- // Output JPEG format image when setting a CaptureProcessor is only enabled for devices whose
- // API level is at least 29.
@Test
- @SdkSuppress(minSdkVersion = 29)
fun returnJpegImage_whenSoftwareJpegIsEnabled() = runBlocking {
val builder = ImageCapture.Builder()
@@ -1234,7 +1231,61 @@
}
@Test
- @SdkSuppress(minSdkVersion = 26)
+ fun canSaveJpegFileWithRotation_whenSoftwareJpegIsEnabled() = runBlocking {
+ val builder = ImageCapture.Builder()
+
+ // Enables software Jpeg
+ builder.mutableConfig.insertOption(
+ ImageCaptureConfig.OPTION_USE_SOFTWARE_JPEG_ENCODER,
+ true
+ )
+ val useCase = builder.build()
+ var camera: Camera
+ withContext(Dispatchers.Main) {
+ camera = cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, useCase)
+ }
+
+ val saveLocation = File.createTempFile("test", ".jpg")
+ saveLocation.deleteOnExit()
+ val callback = FakeImageSavedCallback(capturesCount = 1)
+ useCase.takePicture(
+ ImageCapture.OutputFileOptions.Builder(saveLocation).build(),
+ mainExecutor, callback)
+
+ // Wait for the signal that the image has been captured and saved.
+ callback.awaitCapturesAndAssert(savedImagesCount = 1)
+
+ // For YUV to JPEG case, the rotation will only be in Exif.
+ val exif = Exif.createFromFile(saveLocation)
+ assertThat(exif.rotation).isEqualTo(
+ camera.cameraInfo.getSensorRotationDegrees(useCase.targetRotation))
+ }
+
+ @Test
+ fun returnYuvImage_withYuvBufferFormat() = runBlocking {
+ val builder = ImageCapture.Builder().setBufferFormat(ImageFormat.YUV_420_888)
+ val useCase = builder.build()
+ var camera: Camera
+ withContext(Dispatchers.Main) {
+ camera = cameraProvider.bindToLifecycle(fakeLifecycleOwner, BACK_SELECTOR, useCase)
+ }
+
+ val callback = FakeImageCaptureCallback(capturesCount = 1)
+ useCase.takePicture(mainExecutor, callback)
+
+ // Wait for the signal that the image has been captured.
+ callback.awaitCapturesAndAssert(capturedImagesCount = 1)
+
+ val imageProperties = callback.results.first()
+ // Check the output image rotation degrees value is correct.
+ assertThat(imageProperties.rotationDegrees).isEqualTo(
+ camera.cameraInfo.getSensorRotationDegrees(useCase.targetRotation)
+ )
+ // Check the output format is correct.
+ assertThat(imageProperties.format).isEqualTo(ImageFormat.YUV_420_888)
+ }
+
+ @Test
fun returnYuvImage_whenSoftwareJpegIsEnabledWithYuvBufferFormat() = runBlocking {
val builder = ImageCapture.Builder().setBufferFormat(ImageFormat.YUV_420_888)
@@ -1268,7 +1319,7 @@
@Test
@SdkSuppress(minSdkVersion = 28)
- fun returnJpegImage_whenSessionProcessorIsSet_outputFormantYuv() = runBlocking {
+ fun returnJpegImage_whenSessionProcessorIsSet() = runBlocking {
val builder = ImageCapture.Builder()
val sessionProcessor = FakeSessionProcessor(
inputFormatPreview = null, // null means using the same output surface
@@ -1306,7 +1357,7 @@
@Test
@SdkSuppress(minSdkVersion = 28)
- fun returnJpegImage_whenSessionProcessorIsSet_outputFormantJpeg() = runBlocking {
+ fun returnJpegImage_whenSessionProcessorIsSet_outputFormatJpeg() = runBlocking {
assumeFalse(
"Cuttlefish does not correctly handle Jpeg exif. Unable to test.",
Build.MODEL.contains("Cuttlefish")