blob: 21b70b412b8a7d4738c1daf517cc86386a5f908f [file] [log] [blame]
/*
* Copyright 2024 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.pipe.integration.adapter
import android.graphics.ImageFormat
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.params.InputConfiguration
import android.hardware.camera2.params.StreamConfigurationMap
import android.os.Build
import android.util.Size
import androidx.annotation.RequiresApi
import androidx.annotation.VisibleForTesting
import androidx.camera.camera2.pipe.CameraMetadata.Companion.supportsPrivateReprocessing
import androidx.camera.camera2.pipe.core.Log
import androidx.camera.camera2.pipe.integration.compat.quirk.DeviceQuirks
import androidx.camera.camera2.pipe.integration.compat.quirk.ZslDisablerQuirk
import androidx.camera.camera2.pipe.integration.config.CameraScope
import androidx.camera.camera2.pipe.integration.impl.CameraProperties
import androidx.camera.camera2.pipe.integration.impl.area
import androidx.camera.core.ImageProxy
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.executor.CameraXExecutors
import androidx.camera.core.internal.utils.ZslRingBuffer
import javax.inject.Inject
interface ZslControl {
/**
* Adds zero-shutter lag config to [SessionConfig].
*
* @param sessionConfigBuilder session config builder.
*/
fun addZslConfig(sessionConfigBuilder: SessionConfig.Builder)
/**
* Determines whether the provided [DeferrableSurface] belongs to ZSL.
*
* @param surface The deferrable Surface to check.
* @param sessionConfig The session configuration where its input configuration will be used to
* determine whether the deferrable Surface belongs to ZSL.
*/
fun isZslSurface(surface: DeferrableSurface, sessionConfig: SessionConfig): Boolean
/**
* Sets the flag if zero-shutter lag needs to be disabled by user case config.
*
*
* Zero-shutter lag will be disabled when any of the following conditions:
*
* * Extension is ON
* * VideoCapture is ON
*
*
* @param disabled True if zero-shutter lag should be disabled. Otherwise, should not be
* disabled. However, enabling zero-shutter lag needs other conditions e.g.
* flash mode OFF, so setting to false doesn't guarantee zero-shutter lag to
* be always ON.
*/
fun setZslDisabledByUserCaseConfig(disabled: Boolean)
/**
* Checks if zero-shutter lag is disabled by user case config.
*
* @return True if zero-shutter lag should be disabled. Otherwise, returns false.
*/
fun isZslDisabledByUserCaseConfig(): Boolean
/**
* Sets the flag if zero-shutter lag needs to be disabled by flash mode.
*
*
* Zero-shutter lag will be disabled when flash mode is not OFF.
*
* @param disabled True if zero-shutter lag should be disabled. Otherwise, should not be
* disabled. However, enabling zero-shutter lag needs other conditions e.g.
* Extension is OFF and VideoCapture is OFF, so setting to false doesn't
* guarantee zero-shutter lag to be always ON.
*/
fun setZslDisabledByFlashMode(disabled: Boolean)
/**
* Checks if zero-shutter lag is disabled by flash mode.
*
* @return True if zero-shutter lag should be disabled. Otherwise, returns false.
*/
fun isZslDisabledByFlashMode(): Boolean
/**
* Dequeues [ImageProxy] from ring buffer.
*
* @return [ImageProxy].
*/
fun dequeueImageFromBuffer(): ImageProxy?
}
@RequiresApi(Build.VERSION_CODES.M)
@CameraScope
class ZslControlImpl @Inject constructor(
private val cameraProperties: CameraProperties
) : ZslControl {
private val cameraMetadata = cameraProperties.metadata
private val streamConfigurationMap: StreamConfigurationMap by lazy {
checkNotNull(cameraMetadata[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP])
}
@VisibleForTesting
internal val zslRingBuffer =
ZslRingBuffer(RING_BUFFER_CAPACITY) { imageProxy -> imageProxy.close() }
private var isZslDisabledByUseCaseConfig = false
private var isZslDisabledByFlashMode = false
private var isZslDisabledByQuirks = DeviceQuirks[ZslDisablerQuirk::class.java] != null
@VisibleForTesting
internal var reprocessingImageReader: SafeCloseImageReaderProxy? = null
private var metadataMatchingCaptureCallback: CameraCaptureCallback? = null
private var reprocessingImageDeferrableSurface: DeferrableSurface? = null
override fun addZslConfig(sessionConfigBuilder: SessionConfig.Builder) {
reset()
// 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 (isZslDisabledByUseCaseConfig) {
return
}
if (isZslDisabledByQuirks) {
return
}
if (!cameraMetadata.supportsPrivateReprocessing) {
Log.info { "ZslControlImpl: Private reprocessing isn't supported" }
return
}
val size = streamConfigurationMap.getInputSizes(FORMAT).toList().maxBy { it.area() }
if (size == null) {
Log.warn { "ZslControlImpl: Unable to find a supported size for ZSL" }
return
}
Log.debug { "ZslControlImpl: Selected ZSL size: $size" }
val isJpegValidOutput =
streamConfigurationMap.getValidOutputFormatsForInput(FORMAT).contains(ImageFormat.JPEG)
if (!isJpegValidOutput) {
Log.warn { "ZslControlImpl: JPEG isn't valid output for ZSL format" }
return
}
val metadataImageReader = MetadataImageReader(
size.width,
size.height,
FORMAT,
MAX_IMAGES
)
val metadataCaptureCallback = metadataImageReader.cameraCaptureCallback
val reprocImageReader = SafeCloseImageReaderProxy(metadataImageReader)
metadataImageReader.setOnImageAvailableListener(
{ reader ->
try {
val imageProxy = reader.acquireLatestImage()
if (imageProxy != null) {
zslRingBuffer.enqueue(imageProxy)
}
} catch (e: IllegalStateException) {
Log.error { "Failed to acquire latest image" }
}
}, CameraXExecutors.ioExecutor()
)
// Init the reprocessing image reader surface and add into the target surfaces of capture
val reprocDeferrableSurface = ImmediateSurface(
checkNotNull(reprocImageReader.surface),
Size(reprocImageReader.width, reprocImageReader.height),
FORMAT
)
reprocDeferrableSurface.terminationFuture.addListener(
{ reprocImageReader.safeClose() },
CameraXExecutors.mainThreadExecutor()
)
sessionConfigBuilder.addSurface(reprocDeferrableSurface)
// Init capture and session state callback and enqueue the total capture result
sessionConfigBuilder.addCameraCaptureCallback(metadataCaptureCallback)
// Set input configuration for reprocessing capture request
sessionConfigBuilder.setInputConfiguration(
InputConfiguration(
reprocImageReader.width,
reprocImageReader.height,
reprocImageReader.imageFormat,
)
)
metadataMatchingCaptureCallback = metadataCaptureCallback
reprocessingImageReader = reprocImageReader
reprocessingImageDeferrableSurface = reprocDeferrableSurface
}
override fun isZslSurface(surface: DeferrableSurface, sessionConfig: SessionConfig): Boolean {
val inputConfig = sessionConfig.inputConfiguration
return surface.prescribedStreamFormat == inputConfig?.format &&
surface.prescribedSize.width == inputConfig.width &&
surface.prescribedSize.height == inputConfig.height
}
override fun setZslDisabledByUserCaseConfig(disabled: Boolean) {
isZslDisabledByUseCaseConfig = disabled
}
override fun isZslDisabledByUserCaseConfig(): Boolean {
return isZslDisabledByUseCaseConfig
}
override fun setZslDisabledByFlashMode(disabled: Boolean) {
isZslDisabledByFlashMode = disabled
}
override fun isZslDisabledByFlashMode(): Boolean {
return isZslDisabledByFlashMode
}
override fun dequeueImageFromBuffer(): ImageProxy? {
return try {
zslRingBuffer.dequeue()
} catch (e: NoSuchElementException) {
Log.warn { "ZslControlImpl#dequeueImageFromBuffer: No such element" }
null
}
}
private fun reset() {
val reprocImageDeferrableSurface = reprocessingImageDeferrableSurface
if (reprocImageDeferrableSurface != null) {
val reprocImageReaderProxy = reprocessingImageReader
if (reprocImageReaderProxy != null) {
reprocImageDeferrableSurface.terminationFuture.addListener(
{ reprocImageReaderProxy.safeClose() },
CameraXExecutors.mainThreadExecutor()
)
// Clear the listener so that no more buffer is enqueued to |zslRingBuffer|.
reprocImageReaderProxy.clearOnImageAvailableListener()
reprocessingImageReader = null
}
reprocImageDeferrableSurface.close()
reprocessingImageDeferrableSurface = null
}
val ringBuffer = zslRingBuffer
while (!ringBuffer.isEmpty) {
ringBuffer.dequeue().close()
}
}
companion object {
// Due to b/232268355 and feedback from pixel team that private format will have better
// performance, we will use private only for zsl.
private const val FORMAT = ImageFormat.PRIVATE
@VisibleForTesting
internal const val RING_BUFFER_CAPACITY = 3
@VisibleForTesting
internal const val MAX_IMAGES = RING_BUFFER_CAPACITY * 3
}
}
/**
* No-Op implementation for [ZslControl].
*/
class ZslControlNoOpImpl @Inject constructor() : ZslControl {
override fun addZslConfig(sessionConfigBuilder: SessionConfig.Builder) {
}
override fun isZslSurface(surface: DeferrableSurface, sessionConfig: SessionConfig) = false
override fun setZslDisabledByUserCaseConfig(disabled: Boolean) {
}
override fun isZslDisabledByUserCaseConfig() = false
override fun setZslDisabledByFlashMode(disabled: Boolean) {
}
override fun isZslDisabledByFlashMode() = false
override fun dequeueImageFromBuffer(): ImageProxy? {
return null
}
}