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