blob: bcf5423de39fc101eb47dd3384669ac80c74a214 [file] [log] [blame]
/*
* 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.camera2.internal;
import static android.graphics.ImageFormat.PRIVATE;
import static android.hardware.camera2.CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING;
import static androidx.camera.camera2.internal.ZslUtil.isCapabilitySupported;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageWriter;
import android.os.Build;
import android.util.Size;
import android.view.Surface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
import androidx.camera.camera2.internal.compat.quirk.DeviceQuirks;
import androidx.camera.camera2.internal.compat.quirk.ZslDisablerQuirk;
import androidx.camera.core.ExperimentalGetImage;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.Logger;
import androidx.camera.core.MetadataImageReader;
import androidx.camera.core.SafeCloseImageReaderProxy;
import androidx.camera.core.impl.CameraCaptureCallback;
import androidx.camera.core.impl.DeferrableSurface;
import androidx.camera.core.impl.ImmediateSurface;
import androidx.camera.core.impl.SessionConfig;
import androidx.camera.core.impl.utils.CompareSizesByArea;
import androidx.camera.core.impl.utils.executor.CameraXExecutors;
import androidx.camera.core.internal.compat.ImageWriterCompat;
import androidx.camera.core.internal.utils.ZslRingBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
/**
* Implementation for {@link ZslControl}.
*/
@RequiresApi(23)
final class ZslControlImpl implements ZslControl {
private static final String TAG = "ZslControlImpl";
@VisibleForTesting
static final int RING_BUFFER_CAPACITY = 3;
@VisibleForTesting
static final int MAX_IMAGES = RING_BUFFER_CAPACITY * 3;
@NonNull
private final Map<Integer, Size> mReprocessingInputSizeMap;
@NonNull
private final CameraCharacteristicsCompat mCameraCharacteristicsCompat;
@VisibleForTesting
@SuppressWarnings("WeakerAccess")
@NonNull
final ZslRingBuffer mImageRingBuffer;
private boolean mIsZslDisabledByUseCaseConfig = false;
private boolean mIsZslDisabledByFlashMode = false;
private boolean mIsPrivateReprocessingSupported = false;
private boolean mShouldZslDisabledByQuirks = false;
@SuppressWarnings("WeakerAccess")
SafeCloseImageReaderProxy mReprocessingImageReader;
private CameraCaptureCallback mMetadataMatchingCaptureCallback;
private DeferrableSurface mReprocessingImageDeferrableSurface;
@Nullable
ImageWriter mReprocessingImageWriter;
ZslControlImpl(@NonNull CameraCharacteristicsCompat cameraCharacteristicsCompat) {
mCameraCharacteristicsCompat = cameraCharacteristicsCompat;
mIsPrivateReprocessingSupported =
isCapabilitySupported(mCameraCharacteristicsCompat,
REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING);
mReprocessingInputSizeMap = createReprocessingInputSizeMap(mCameraCharacteristicsCompat);
mShouldZslDisabledByQuirks = DeviceQuirks.get(ZslDisablerQuirk.class) != null;
mImageRingBuffer = new ZslRingBuffer(
RING_BUFFER_CAPACITY,
imageProxy -> imageProxy.close());
}
@Override
public void setZslDisabledByUserCaseConfig(boolean disabled) {
mIsZslDisabledByUseCaseConfig = disabled;
}
@Override
public boolean isZslDisabledByUserCaseConfig() {
return mIsZslDisabledByUseCaseConfig;
}
@Override
public void setZslDisabledByFlashMode(boolean disabled) {
mIsZslDisabledByFlashMode = disabled;
}
@Override
public boolean isZslDisabledByFlashMode() {
return mIsZslDisabledByFlashMode;
}
@Override
public void addZslConfig(@NonNull SessionConfig.Builder sessionConfigBuilder) {
cleanup();
// Early return only if use case config doesn't support zsl. If flash mode doesn't
// support zsl, we still create reprocessing capture session but will create a
// regular capture request when taking pictures. So when user switches flash mode, we
// could create reprocessing capture request if flash mode allows.
if (mIsZslDisabledByUseCaseConfig) {
return;
}
if (mShouldZslDisabledByQuirks) {
return;
}
// Due to b/232268355 and feedback from pixel team that private format will have better
// performance, we will use private only for zsl.
if (!mIsPrivateReprocessingSupported
|| mReprocessingInputSizeMap.isEmpty()
|| !mReprocessingInputSizeMap.containsKey(PRIVATE)
|| !isJpegValidOutputForInputFormat(mCameraCharacteristicsCompat, PRIVATE)) {
return;
}
int reprocessingImageFormat = PRIVATE;
Size resolution = mReprocessingInputSizeMap.get(reprocessingImageFormat);
MetadataImageReader metadataImageReader = new MetadataImageReader(
resolution.getWidth(),
resolution.getHeight(),
reprocessingImageFormat,
MAX_IMAGES);
mMetadataMatchingCaptureCallback = metadataImageReader.getCameraCaptureCallback();
mReprocessingImageReader = new SafeCloseImageReaderProxy(metadataImageReader);
metadataImageReader.setOnImageAvailableListener(
imageReader -> {
try {
ImageProxy imageProxy = imageReader.acquireLatestImage();
if (imageProxy != null) {
mImageRingBuffer.enqueue(imageProxy);
}
} catch (IllegalStateException e) {
Logger.e(TAG, "Failed to acquire latest image IllegalStateException = "
+ e.getMessage());
}
}, CameraXExecutors.ioExecutor());
// Init the reprocessing image reader surface and add into the target surfaces of capture
mReprocessingImageDeferrableSurface = new ImmediateSurface(
mReprocessingImageReader.getSurface(),
new Size(mReprocessingImageReader.getWidth(),
mReprocessingImageReader.getHeight()),
reprocessingImageFormat);
SafeCloseImageReaderProxy reprocessingImageReaderProxy = mReprocessingImageReader;
mReprocessingImageDeferrableSurface.getTerminationFuture().addListener(
reprocessingImageReaderProxy::safeClose,
CameraXExecutors.mainThreadExecutor());
sessionConfigBuilder.addSurface(mReprocessingImageDeferrableSurface);
// Init capture and session state callback and enqueue the total capture result
sessionConfigBuilder.addCameraCaptureCallback(mMetadataMatchingCaptureCallback);
sessionConfigBuilder.addSessionStateCallback(
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(
@NonNull CameraCaptureSession cameraCaptureSession) {
Surface surface = cameraCaptureSession.getInputSurface();
if (surface != null) {
mReprocessingImageWriter =
ImageWriterCompat.newInstance(surface, 1);
}
}
@Override
public void onConfigureFailed(
@NonNull CameraCaptureSession cameraCaptureSession) { }
});
// Set input configuration for reprocessing capture request
sessionConfigBuilder.setInputConfiguration(new InputConfiguration(
mReprocessingImageReader.getWidth(),
mReprocessingImageReader.getHeight(),
mReprocessingImageReader.getImageFormat()));
}
@Nullable
@Override
public ImageProxy dequeueImageFromBuffer() {
ImageProxy imageProxy = null;
try {
imageProxy = mImageRingBuffer.dequeue();
} catch (NoSuchElementException e) {
Logger.e(TAG, "dequeueImageFromBuffer no such element");
}
return imageProxy;
}
@Override
public boolean enqueueImageToImageWriter(@NonNull ImageProxy imageProxy) {
@OptIn(markerClass = ExperimentalGetImage.class)
Image image = imageProxy.getImage();
if (Build.VERSION.SDK_INT >= 23 && mReprocessingImageWriter != null && image != null) {
try {
ImageWriterCompat.queueInputImage(mReprocessingImageWriter, image);
} catch (IllegalStateException e) {
Logger.e(TAG, "enqueueImageToImageWriter throws IllegalStateException = "
+ e.getMessage());
return false;
}
return true;
}
return false;
}
private void cleanup() {
// We might need synchronization here when clearing ring buffer while image is enqueued
// at the same time. Will test this case.
ZslRingBuffer imageRingBuffer = mImageRingBuffer;
while (!imageRingBuffer.isEmpty()) {
ImageProxy imageProxy = imageRingBuffer.dequeue();
imageProxy.close();
}
DeferrableSurface reprocessingImageDeferrableSurface = mReprocessingImageDeferrableSurface;
if (reprocessingImageDeferrableSurface != null) {
SafeCloseImageReaderProxy reprocessingImageReaderProxy = mReprocessingImageReader;
if (reprocessingImageReaderProxy != null) {
reprocessingImageDeferrableSurface.getTerminationFuture().addListener(
reprocessingImageReaderProxy::safeClose,
CameraXExecutors.mainThreadExecutor());
mReprocessingImageReader = null;
}
reprocessingImageDeferrableSurface.close();
mReprocessingImageDeferrableSurface = null;
}
ImageWriter reprocessingImageWriter = mReprocessingImageWriter;
if (reprocessingImageWriter != null) {
reprocessingImageWriter.close();
mReprocessingImageWriter = null;
}
}
@NonNull
private Map<Integer, Size> createReprocessingInputSizeMap(
@NonNull CameraCharacteristicsCompat cameraCharacteristicsCompat) {
StreamConfigurationMap map =
cameraCharacteristicsCompat.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null || map.getInputFormats() == null) {
return new HashMap<>();
}
Map<Integer, Size> inputSizeMap = new HashMap<>();
for (int format: map.getInputFormats()) {
Size[] inputSizes = map.getInputSizes(format);
if (inputSizes != null) {
// Sort by descending order
Arrays.sort(inputSizes, new CompareSizesByArea(true));
// TODO(b/233696144): Check if selecting an input size closer to output size will
// improve performance or not.
inputSizeMap.put(format, inputSizes[0]);
}
}
return inputSizeMap;
}
private boolean isJpegValidOutputForInputFormat(
@NonNull CameraCharacteristicsCompat cameraCharacteristicsCompat,
int inputFormat) {
StreamConfigurationMap map =
cameraCharacteristicsCompat.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
return false;
}
int[] validOutputFormats = map.getValidOutputFormatsForInput(inputFormat);
if (validOutputFormats == null) {
return false;
}
for (int outputFormat : validOutputFormats) {
if (outputFormat == ImageFormat.JPEG) {
return true;
}
}
return false;
}
}