1 /* <lambda>null2 * Copyright 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.camera.camera2.pipe.integration.adapter 18 19 import android.hardware.camera2.CameraDevice 20 import android.hardware.camera2.CaptureRequest 21 import androidx.annotation.OptIn 22 import androidx.camera.camera2.pipe.CameraMetadata.Companion.isHardwareLevelLegacy 23 import androidx.camera.camera2.pipe.FrameInfo 24 import androidx.camera.camera2.pipe.FrameNumber 25 import androidx.camera.camera2.pipe.InputRequest 26 import androidx.camera.camera2.pipe.Request 27 import androidx.camera.camera2.pipe.RequestFailure 28 import androidx.camera.camera2.pipe.RequestMetadata 29 import androidx.camera.camera2.pipe.RequestTemplate 30 import androidx.camera.camera2.pipe.integration.compat.workaround.TemplateParamsOverride 31 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraScope 32 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig 33 import androidx.camera.camera2.pipe.integration.impl.CAMERAX_TAG_BUNDLE 34 import androidx.camera.camera2.pipe.integration.impl.Camera2ImplConfig 35 import androidx.camera.camera2.pipe.integration.impl.CameraCallbackMap 36 import androidx.camera.camera2.pipe.integration.impl.CameraProperties 37 import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads 38 import androidx.camera.camera2.pipe.integration.impl.toParameters 39 import androidx.camera.camera2.pipe.media.AndroidImage 40 import androidx.camera.core.ExperimentalGetImage 41 import androidx.camera.core.ImageProxy 42 import androidx.camera.core.impl.CameraCaptureResults 43 import androidx.camera.core.impl.CaptureConfig 44 import androidx.camera.core.impl.Config 45 import java.util.concurrent.atomic.AtomicReference 46 import javax.inject.Inject 47 48 /** 49 * Maps a [CaptureConfig] issued by CameraX (e.g. by the image capture use case) to a [Request] that 50 * CameraPipe can submit to the camera. 51 */ 52 @UseCaseCameraScope 53 public class CaptureConfigAdapter 54 @Inject 55 constructor( 56 cameraProperties: CameraProperties, 57 private val useCaseGraphConfig: UseCaseGraphConfig, 58 private val zslControl: ZslControl, 59 private val threads: UseCaseThreads, 60 private val templateParamsOverride: TemplateParamsOverride, 61 ) { 62 private val isLegacyDevice = cameraProperties.metadata.isHardwareLevelLegacy 63 64 /** 65 * Maps [CaptureConfig] to [Request]. 66 * 67 * @throws IllegalStateException When CaptureConfig does not have any surface or a CaptureConfig 68 * surface is not recognized in [UseCaseGraphConfig.surfaceToStreamMap] 69 */ 70 @OptIn(ExperimentalGetImage::class) 71 public fun mapToRequest( 72 captureConfig: CaptureConfig, 73 requestTemplate: RequestTemplate, 74 sessionConfigOptions: Config, 75 additionalListeners: List<Request.Listener> = emptyList(), 76 ): Request { 77 val surfaces = captureConfig.surfaces 78 check(surfaces.isNotEmpty()) { 79 "Attempted to issue a capture without surfaces using $captureConfig" 80 } 81 82 val streamIdList = 83 surfaces.map { 84 checkNotNull(useCaseGraphConfig.surfaceToStreamMap[it]) { 85 "Attempted to issue a capture with an unrecognized surface: $it" 86 } 87 } 88 89 val callbacks = 90 CameraCallbackMap().apply { 91 captureConfig.cameraCaptureCallbacks.forEach { callback -> 92 addCaptureCallback(callback, threads.sequentialExecutor) 93 } 94 } 95 96 val configOptions = captureConfig.implementationOptions 97 val optionBuilder = Camera2ImplConfig.Builder() 98 99 // The override priority for implementation options 100 // P1 Single capture options 101 // P2 SessionConfig options 102 optionBuilder.insertAllOptions(sessionConfigOptions) 103 optionBuilder.insertAllOptions(configOptions) 104 105 // Add capture options defined in CaptureConfig 106 if (configOptions.containsOption(CaptureConfig.OPTION_ROTATION)) { 107 optionBuilder.setCaptureRequestOption( 108 CaptureRequest.JPEG_ORIENTATION, 109 configOptions.retrieveOption(CaptureConfig.OPTION_ROTATION)!! 110 ) 111 } 112 if (configOptions.containsOption(CaptureConfig.OPTION_JPEG_QUALITY)) { 113 optionBuilder.setCaptureRequestOption( 114 CaptureRequest.JPEG_QUALITY, 115 configOptions.retrieveOption(CaptureConfig.OPTION_JPEG_QUALITY)!!.toByte() 116 ) 117 } 118 119 var inputRequest: InputRequest? = null 120 var captureCallback: Request.Listener? = null 121 var requestTemplateToSubmit = RequestTemplate(captureConfig.templateType) 122 if ( 123 captureConfig.templateType == CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG && 124 !zslControl.isZslDisabledByUserCaseConfig() && 125 !zslControl.isZslDisabledByFlashMode() 126 ) { 127 zslControl.dequeueImageFromBuffer()?.let { imageProxy -> 128 CameraCaptureResults.retrieveCameraCaptureResult(imageProxy.imageInfo)?.let { 129 cameraCaptureResult -> 130 check(cameraCaptureResult is CaptureResultAdapter) { 131 "Unexpected capture result type: ${cameraCaptureResult.javaClass}" 132 } 133 val imageWrapper = AndroidImage(checkNotNull(imageProxy.image)) 134 val frameInfo = checkNotNull(cameraCaptureResult.unwrapAs(FrameInfo::class)) 135 inputRequest = InputRequest(imageWrapper, frameInfo) 136 137 // It's essential to call ImageProxy#close(). 138 // To ensure the ImageProxy is closed after the image is written to the output 139 // surface. This is crucial to prevent resource leaks, where images might not 140 // be closed properly if CameraX fails to propagate close events to its internal 141 // components. 142 captureCallback = buildImageClosingRequestListener(imageProxy) 143 } 144 } 145 } 146 147 // Apply still capture template type for regular still capture case 148 if (inputRequest == null) { 149 requestTemplateToSubmit = 150 captureConfig.getStillCaptureTemplate(requestTemplate, isLegacyDevice) 151 } 152 153 val parameters = 154 templateParamsOverride.getOverrideParams(requestTemplateToSubmit) + 155 optionBuilder.build().toParameters() 156 val requestListeners = buildList { 157 add(callbacks) 158 captureCallback?.let { add(it) } 159 addAll(additionalListeners) 160 } 161 162 return Request( 163 streams = streamIdList, 164 listeners = requestListeners, 165 parameters = parameters, 166 extras = mapOf(CAMERAX_TAG_BUNDLE to captureConfig.tagBundle), 167 template = requestTemplateToSubmit, 168 inputRequest = inputRequest, 169 ) 170 } 171 172 private fun buildImageClosingRequestListener(imageProxy: ImageProxy): Request.Listener { 173 val imageProxyToClose = AtomicReference(imageProxy) 174 175 fun closeImageProxy() { 176 imageProxyToClose.getAndSet(null)?.close() 177 } 178 179 return object : Request.Listener { 180 override fun onComplete( 181 requestMetadata: RequestMetadata, 182 frameNumber: FrameNumber, 183 result: FrameInfo 184 ) { 185 closeImageProxy() 186 } 187 188 override fun onFailed( 189 requestMetadata: RequestMetadata, 190 frameNumber: FrameNumber, 191 requestFailure: RequestFailure 192 ) { 193 closeImageProxy() 194 } 195 196 override fun onAborted(request: Request) { 197 closeImageProxy() 198 } 199 200 override fun onTotalCaptureResult( 201 requestMetadata: RequestMetadata, 202 frameNumber: FrameNumber, 203 totalCaptureResult: FrameInfo 204 ) { 205 closeImageProxy() 206 } 207 } 208 } 209 210 public companion object { 211 internal fun CaptureConfig.getStillCaptureTemplate( 212 sessionTemplate: RequestTemplate, 213 isLegacyDevice: Boolean, 214 ): RequestTemplate { 215 var templateToModify = CaptureConfig.TEMPLATE_TYPE_NONE 216 if ( 217 sessionTemplate == RequestTemplate(CameraDevice.TEMPLATE_RECORD) && !isLegacyDevice 218 ) { 219 // Always override template by TEMPLATE_VIDEO_SNAPSHOT when 220 // repeating template is TEMPLATE_RECORD. Note: 221 // TEMPLATE_VIDEO_SNAPSHOT is not supported on legacy device. 222 templateToModify = CameraDevice.TEMPLATE_VIDEO_SNAPSHOT 223 } else if ( 224 templateType == CaptureConfig.TEMPLATE_TYPE_NONE || 225 templateType == CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG 226 ) { 227 templateToModify = CameraDevice.TEMPLATE_STILL_CAPTURE 228 } 229 230 return if (templateToModify != CaptureConfig.TEMPLATE_TYPE_NONE) { 231 RequestTemplate(templateToModify) 232 } else { 233 RequestTemplate(templateType) 234 } 235 } 236 } 237 } 238