1 /*
<lambda>null2  * Copyright 2020 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 @file:Suppress("NOTHING_TO_INLINE")
18 
19 package androidx.camera.camera2.pipe.integration.impl
20 
21 import android.hardware.camera2.CaptureRequest
22 import androidx.annotation.GuardedBy
23 import androidx.camera.camera2.pipe.AeMode
24 import androidx.camera.camera2.pipe.AfMode
25 import androidx.camera.camera2.pipe.AwbMode
26 import androidx.camera.camera2.pipe.CameraGraph
27 import androidx.camera.camera2.pipe.FrameInfo
28 import androidx.camera.camera2.pipe.FrameNumber
29 import androidx.camera.camera2.pipe.Metadata
30 import androidx.camera.camera2.pipe.Request
31 import androidx.camera.camera2.pipe.RequestFailure
32 import androidx.camera.camera2.pipe.RequestMetadata
33 import androidx.camera.camera2.pipe.RequestTemplate
34 import androidx.camera.camera2.pipe.StreamId
35 import androidx.camera.camera2.pipe.core.Log.debug
36 import androidx.camera.camera2.pipe.integration.compat.workaround.TemplateParamsOverride
37 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraScope
38 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
39 import androidx.camera.core.Preview
40 import androidx.camera.core.impl.SessionConfig
41 import androidx.camera.core.impl.TagBundle
42 import androidx.camera.core.streamsharing.StreamSharing
43 import javax.inject.Inject
44 import kotlin.collections.removeFirst as removeFirstKt
45 import kotlinx.atomicfu.atomic
46 import kotlinx.coroutines.CancellationException
47 import kotlinx.coroutines.CompletableDeferred
48 import kotlinx.coroutines.CoroutineStart
49 import kotlinx.coroutines.Deferred
50 import kotlinx.coroutines.launch
51 
52 /**
53  * This object keeps track of the state of the current [UseCaseCamera].
54  *
55  * Updates to the camera from this class are batched together. That is, if multiple updates happen
56  * while some other system is holding the cameraGraph session, those updates will be aggregated
57  * together and applied when the session becomes available. This also serves as a form of primitive
58  * rate limiting that ensures that updates arriving too quickly are only sent to the underlying
59  * camera graph as fast as the camera is capable of consuming them.
60  */
61 @UseCaseCameraScope
62 public class UseCaseCameraState
63 @Inject
64 constructor(
65     useCaseGraphConfig: UseCaseGraphConfig,
66     private val threads: UseCaseThreads,
67     private val sessionProcessorManager: SessionProcessorManager?,
68     private val templateParamsOverride: TemplateParamsOverride,
69 ) {
70     private val lock = Any()
71 
72     private val cameraGraph = useCaseGraphConfig.graph
73 
74     @GuardedBy("lock") private var updateSignal: CompletableDeferred<Unit>? = null
75 
76     @GuardedBy("lock") private val submittedRequestCounter = atomic(0)
77 
78     public data class RequestSignal(val requestNo: Int, val signal: CompletableDeferred<Unit>)
79 
80     @GuardedBy("lock") private var updateSignals = ArrayDeque<RequestSignal>()
81 
82     @GuardedBy("lock") private var updating = false
83 
84     @GuardedBy("lock") private val currentParameters = mutableMapOf<CaptureRequest.Key<*>, Any>()
85 
86     @GuardedBy("lock") private val currentInternalParameters = mutableMapOf<Metadata.Key<*>, Any>()
87 
88     @GuardedBy("lock") private val currentStreams = mutableSetOf<StreamId>()
89 
90     @GuardedBy("lock") private val currentListeners = mutableSetOf<Request.Listener>()
91 
92     @GuardedBy("lock") private var currentTemplate: RequestTemplate? = null
93 
94     @GuardedBy("lock") private var currentSessionConfig: SessionConfig? = null
95 
96     private val requestListener = RequestListener()
97 
98     /**
99      * Updates the camera state by applying the provided parameters to a repeating request and
100      * returns a [Deferred] signal that is completed only when a capture request with equal or
101      * larger request number is completed or failed.
102      *
103      * In case the corresponding capture request of a signal is aborted, it is not completed right
104      * then. This is because a quick succession of update requests may lead to the previous request
105      * being aborted while the request parameters should still be applied unless it was changed in
106      * the new request. If the new request has a value change for some parameter, it is the
107      * responsibility of the caller to keep track of that and take necessary action.
108      *
109      * @return A [Deferred] signal to represent if the update operation has been completed.
110      */
111     public fun updateAsync(
112         parameters: Map<CaptureRequest.Key<*>, Any>? = null,
113         appendParameters: Boolean = true,
114         internalParameters: Map<Metadata.Key<*>, Any>? = null,
115         appendInternalParameters: Boolean = true,
116         streams: Set<StreamId>? = null,
117         template: RequestTemplate? = null,
118         listeners: Set<Request.Listener>? = null,
119         sessionConfig: SessionConfig? = null,
120     ): Deferred<Unit> {
121         val result: Deferred<Unit>
122         synchronized(lock) {
123             // This block does several things while locked, and is paired with another
124             // synchronized(lock) section in the submitLatest() method below that prevents these
125             // two blocks from ever executing at the same time, even if invoked by multiple
126             // threads.
127             // 1) Update the internal state (locked)
128             // 2) Since a prior update may have happened that didn't need a completion signal,
129             //    it is possible that updateSignal is null. Regardless of the need to resubmit or
130             //    not, the updateSignal must have a value to be returned.
131             // 3) If an update is already dispatched, return existing update signal. This
132             //    updateSignal may be the value from #2 (this is fine).
133             // 4) If we get this far, we need to dispatch an update. Mark this as updating, and
134             //    exit the locked section.
135             // 5) If updating, invoke submit without holding the lock.
136 
137             updateState(
138                 parameters,
139                 appendParameters,
140                 internalParameters,
141                 appendInternalParameters,
142                 streams,
143                 template,
144                 listeners,
145                 sessionConfig
146             )
147 
148             if (updateSignal == null) {
149                 updateSignal = CompletableDeferred()
150             }
151             if (updating) {
152                 return updateSignal!!
153             }
154 
155             // Fall through to submit if there is no pending update.
156             updating = true
157             result = updateSignal!!
158         }
159 
160         submitLatest()
161         return result
162     }
163 
164     public fun update(
165         parameters: Map<CaptureRequest.Key<*>, Any>? = null,
166         appendParameters: Boolean = true,
167         internalParameters: Map<Metadata.Key<*>, Any>? = null,
168         appendInternalParameters: Boolean = true,
169         streams: Set<StreamId>? = null,
170         template: RequestTemplate? = null,
171         listeners: Set<Request.Listener>? = null
172     ) {
173         synchronized(lock) {
174             // See updateAsync for details.
175             updateState(
176                 parameters,
177                 appendParameters,
178                 internalParameters,
179                 appendInternalParameters,
180                 streams,
181                 template,
182                 listeners
183             )
184             if (updating) {
185                 return
186             }
187             updating = true
188         }
189         submitLatest()
190     }
191 
192     public fun capture(requests: List<Request>) {
193         threads.sequentialScope.launch(start = CoroutineStart.UNDISPATCHED) {
194             cameraGraph.acquireSession().use { it.submit(requests) }
195         }
196     }
197 
198     @GuardedBy("lock")
199     private inline fun updateState(
200         parameters: Map<CaptureRequest.Key<*>, Any>? = null,
201         appendParameters: Boolean = true,
202         internalParameters: Map<Metadata.Key<*>, Any>? = null,
203         appendInternalParameters: Boolean = true,
204         streams: Set<StreamId>? = null,
205         template: RequestTemplate? = null,
206         listeners: Set<Request.Listener>? = null,
207         sessionConfig: SessionConfig? = null,
208     ) {
209         // TODO: Consider if this should detect changes and only invoke an update if state has
210         //  actually changed.
211         debug {
212             "UseCaseCameraState#updateState: parameters = $parameters, internalParameters = " +
213                 "$internalParameters, streams = $streams, template = $template"
214         }
215 
216         if (parameters != null) {
217             if (!appendParameters) {
218                 currentParameters.clear()
219             }
220             currentParameters.putAll(parameters)
221         }
222         if (internalParameters != null) {
223             if (!appendInternalParameters) {
224                 currentInternalParameters.clear()
225             }
226             currentInternalParameters.putAll(internalParameters)
227         }
228         if (streams != null) {
229             currentStreams.clear()
230             currentStreams.addAll(streams)
231         }
232         if (template != null) {
233             currentTemplate = template
234         }
235         if (listeners != null) {
236             currentListeners.clear()
237             currentListeners.addAll(listeners)
238         }
239         if (sessionConfig != null) {
240             currentSessionConfig = sessionConfig
241         }
242     }
243 
244     /**
245      * Tries to invoke [androidx.camera.camera2.pipe.CameraGraph.Session.startRepeating] with
246      * current (the most recent) set of values.
247      */
248     public fun tryStartRepeating(): Unit = submitLatest()
249 
250     private fun submitLatest() {
251         if (sessionProcessorManager != null) {
252             submitLatestWithSessionProcessor()
253             return
254         }
255 
256         // Update the cameraGraph with the most recent set of values.
257         // Since acquireSession is a suspending function, it's possible that subsequent updates
258         // can occur while waiting for the acquireSession call to complete. If this happens,
259         // updates to the internal state are aggregated together, and the Request is built
260         // synchronously with the latest values. The startRepeating/stopRepeating call happens
261         // outside of the synchronized block to avoid holding a lock while updating the camera
262         // state.
263         threads.sequentialScope.launch(start = CoroutineStart.UNDISPATCHED) {
264             val result: CompletableDeferred<Unit>?
265             val request: Request?
266             try {
267                     cameraGraph.acquireSession()
268                 } catch (e: CancellationException) {
269                     debug(e) { "Cannot acquire session at ${this@UseCaseCameraState}" }
270                     null
271                 }
272                 .let { session ->
273                     synchronized(lock) {
274                         request =
275                             if (currentStreams.isEmpty() || session == null) {
276                                 null
277                             } else {
278                                 Request(
279                                     template = currentTemplate,
280                                     streams = currentStreams.toList(),
281                                     parameters =
282                                         templateParamsOverride.getOverrideParams(currentTemplate) +
283                                             currentParameters.toMap(),
284                                     extras =
285                                         currentInternalParameters.toMutableMap().also { parameters
286                                             ->
287                                             parameters[USE_CASE_CAMERA_STATE_CUSTOM_TAG] =
288                                                 submittedRequestCounter.incrementAndGet()
289                                         },
290                                     listeners =
291                                         currentListeners.toMutableList().also { listeners ->
292                                             listeners.add(requestListener)
293                                         }
294                                 )
295                             }
296                         result = updateSignal
297                         updating = false
298                         updateSignal = null
299                     }
300                     session?.use {
301                         if (request == null) {
302                             it.stopRepeating()
303                         } else {
304                             result?.let { result ->
305                                 synchronized(lock) {
306                                     updateSignals.add(
307                                         RequestSignal(submittedRequestCounter.value, result)
308                                     )
309                                 }
310                             }
311                             debug { "Update RepeatingRequest: $request" }
312                             it.startRepeating(request)
313                             // TODO: Invoke update3A only if required e.g. a 3A value has changed
314                             it.update3A(request.parameters)
315                         }
316                     }
317                 }
318 
319             // complete the result instantly only when the request was not submitted
320             if (request == null) {
321                 // Complete the result after the session closes to allow other threads to acquire a
322                 // lock. This also avoids cases where complete() synchronously invokes expensive
323                 // calls.
324                 result?.complete(Unit)
325             }
326         }
327     }
328 
329     private fun submitLatestWithSessionProcessor() {
330         checkNotNull(sessionProcessorManager)
331         synchronized(lock) {
332             updating = false
333             val signal = updateSignal
334             updateSignal = null
335 
336             if (currentSessionConfig == null) {
337                 signal?.complete(Unit)
338                 return
339             }
340 
341             // Here we're intentionally building a new SessionConfig. Various request parameters,
342             // such as zoom or 3A are directly translated to corresponding CameraPipe types and
343             // APIs. As such, we need to build a new, "combined" SessionConfig that has these
344             // updated request parameters set. Otherwise, certain settings like zoom would be
345             // disregarded.
346             SessionConfig.Builder()
347                 .apply {
348                     currentTemplate?.let { setTemplateType(it.value) }
349                     setImplementationOptions(
350                         Camera2ImplConfig.Builder()
351                             .apply {
352                                 for ((key, value) in currentParameters) {
353                                     setCaptureRequestOptionWithType(key, value)
354                                 }
355                             }
356                             .build()
357                     )
358                     currentInternalParameters[CAMERAX_TAG_BUNDLE]?.let {
359                         val tagBundleMap = (it as TagBundle).toMap()
360                         for ((tag, value) in tagBundleMap) {
361                             addTag(tag, value)
362                         }
363                     }
364                     currentSessionConfig?.let {
365                         addAllCameraCaptureCallbacks(
366                             it.repeatingCaptureConfig.cameraCaptureCallbacks
367                         )
368                     }
369                 }
370                 .build()
371                 .also { sessionConfig -> sessionProcessorManager.sessionConfig = sessionConfig }
372 
373             if (
374                 currentSessionConfig!!.repeatingCaptureConfig.surfaces.any {
375                     it.containerClass == Preview::class.java ||
376                         it.containerClass == StreamSharing::class.java
377                 }
378             ) {
379                 sessionProcessorManager.startRepeating(
380                     currentSessionConfig!!.repeatingCaptureConfig.tagBundle
381                 )
382             } else {
383                 sessionProcessorManager.stopRepeating()
384             }
385             signal?.complete(Unit)
386         }
387     }
388 
389     private fun CameraGraph.Session.update3A(parameters: Map<CaptureRequest.Key<*>, Any>?) {
390         val aeMode =
391             parameters.getIntOrNull(CaptureRequest.CONTROL_AE_MODE)?.let {
392                 AeMode.fromIntOrNull(it)
393             }
394         val afMode =
395             parameters.getIntOrNull(CaptureRequest.CONTROL_AF_MODE)?.let {
396                 AfMode.fromIntOrNull(it)
397             }
398         val awbMode =
399             parameters.getIntOrNull(CaptureRequest.CONTROL_AWB_MODE)?.let {
400                 AwbMode.fromIntOrNull(it)
401             }
402 
403         if (aeMode != null || afMode != null || awbMode != null) {
404             update3A(aeMode = aeMode, afMode = afMode, awbMode = awbMode)
405         }
406     }
407 
408     private fun Map<CaptureRequest.Key<*>, Any>?.getIntOrNull(key: CaptureRequest.Key<*>): Int? =
409         this?.get(key) as? Int
410 
411     @Suppress("UNCHECKED_CAST")
412     private fun <T> Camera2ImplConfig.Builder.setCaptureRequestOptionWithType(
413         key: CaptureRequest.Key<T>,
414         value: Any
415     ) {
416         setCaptureRequestOption(key, value as T)
417     }
418 
419     public inner class RequestListener : Request.Listener {
420         override fun onTotalCaptureResult(
421             requestMetadata: RequestMetadata,
422             frameNumber: FrameNumber,
423             totalCaptureResult: FrameInfo,
424         ) {
425             super.onTotalCaptureResult(requestMetadata, frameNumber, totalCaptureResult)
426             threads.scope.launch(start = CoroutineStart.UNDISPATCHED) {
427                 requestMetadata[USE_CASE_CAMERA_STATE_CUSTOM_TAG]?.let { requestNo ->
428                     synchronized(lock) { updateSignals.complete(requestNo) }
429                 }
430             }
431         }
432 
433         override fun onFailed(
434             requestMetadata: RequestMetadata,
435             frameNumber: FrameNumber,
436             requestFailure: RequestFailure,
437         ) {
438             @Suppress("DEPRECATION") super.onFailed(requestMetadata, frameNumber, requestFailure)
439             completeExceptionally(requestMetadata, requestFailure)
440         }
441 
442         private fun completeExceptionally(
443             requestMetadata: RequestMetadata,
444             requestFailure: RequestFailure? = null
445         ) {
446             threads.scope.launch(start = CoroutineStart.UNDISPATCHED) {
447                 requestMetadata[USE_CASE_CAMERA_STATE_CUSTOM_TAG]?.let { requestNo ->
448                     synchronized(lock) {
449                         updateSignals.completeExceptionally(
450                             requestNo,
451                             Throwable(
452                                 "Failed in framework level" +
453                                     (requestFailure?.reason?.let {
454                                         " with CaptureFailure.reason = $it"
455                                     } ?: "")
456                             )
457                         )
458                     }
459                 }
460             }
461         }
462 
463         private fun ArrayDeque<RequestSignal>.complete(requestNo: Int) {
464             while (isNotEmpty() && first().requestNo <= requestNo) {
465                 first().signal.complete(Unit)
466                 removeFirstKt()
467             }
468         }
469 
470         private fun ArrayDeque<RequestSignal>.completeExceptionally(
471             requestNo: Int,
472             throwable: Throwable
473         ) {
474             while (isNotEmpty() && first().requestNo <= requestNo) {
475                 first().signal.completeExceptionally(throwable)
476                 removeFirstKt()
477             }
478         }
479     }
480 }
481