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