[go: nahoru, domu]

blob: 75a210e4f36358f50e0cf73a8c9d9470ebb5cb29 [file] [log] [blame]
/*
* Copyright 2020 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.
*/
@file:Suppress("NOTHING_TO_INLINE")
package androidx.camera.camera2.pipe.integration.impl
import android.hardware.camera2.CaptureFailure
import android.hardware.camera2.CaptureRequest
import androidx.annotation.GuardedBy
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.AeMode
import androidx.camera.camera2.pipe.AfMode
import androidx.camera.camera2.pipe.AwbMode
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.FrameInfo
import androidx.camera.camera2.pipe.FrameNumber
import androidx.camera.camera2.pipe.Metadata
import androidx.camera.camera2.pipe.Request
import androidx.camera.camera2.pipe.RequestMetadata
import androidx.camera.camera2.pipe.RequestTemplate
import androidx.camera.camera2.pipe.StreamId
import androidx.camera.camera2.pipe.core.Log
import androidx.camera.camera2.pipe.integration.config.UseCaseCameraScope
import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
import javax.inject.Inject
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.launch
/**
* This object keeps track of the state of the current [UseCaseCamera].
*
* Updates to the camera from this class are batched together. That is, if multiple updates
* happen while some other system is holding the cameraGraph session, those updates will be
* aggregated together and applied when the session becomes available. This also serves as a form
* of primitive rate limiting that ensures that updates arriving too quickly are only sent to the
* underlying camera graph as fast as the camera is capable of consuming them.
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
@UseCaseCameraScope
class UseCaseCameraState @Inject constructor(
useCaseGraphConfig: UseCaseGraphConfig,
private val threads: UseCaseThreads
) {
private val lock = Any()
private val cameraGraph = useCaseGraphConfig.graph
@GuardedBy("lock")
private var updateSignal: CompletableDeferred<Unit>? = null
@GuardedBy("lock")
private val submittedRequestCounter = atomic(0)
data class RequestSignal(val requestNo: Int, val signal: CompletableDeferred<Unit>)
@GuardedBy("lock")
private var updateSignals = ArrayDeque<RequestSignal>()
@GuardedBy("lock")
private var updating = false
@GuardedBy("lock")
private val currentParameters = mutableMapOf<CaptureRequest.Key<*>, Any>()
@GuardedBy("lock")
private val currentInternalParameters = mutableMapOf<Metadata.Key<*>, Any>()
@GuardedBy("lock")
private val currentStreams = mutableSetOf<StreamId>()
@GuardedBy("lock")
private val currentListeners = mutableSetOf<Request.Listener>()
@GuardedBy("lock")
private var currentTemplate: RequestTemplate? = null
private val requestListener = RequestListener()
/**
* Updates the camera state by applying the provided parameters to a repeating request and
* returns a [Deferred] signal that is completed only when a capture request with equal or
* larger request number is completed or failed.
*
* In case the corresponding capture request of a signal is aborted, it is not completed right
* then. This is because a quick succession of update requests may lead to the previous request
* being aborted while the request parameters should still be applied unless it was changed in
* the new request. If the new request has a value change for some parameter, it is the
* responsibility of the caller to keep track of that and take necessary action.
*
* @return A [Deferred] signal to represent if the update operation has been completed.
*/
fun updateAsync(
parameters: Map<CaptureRequest.Key<*>, Any>? = null,
appendParameters: Boolean = true,
internalParameters: Map<Metadata.Key<*>, Any>? = null,
appendInternalParameters: Boolean = true,
streams: Set<StreamId>? = null,
template: RequestTemplate? = null,
listeners: Set<Request.Listener>? = null,
): Deferred<Unit> {
val result: Deferred<Unit>
synchronized(lock) {
// This block does several things while locked, and is paired with another
// synchronized(lock) section in the submitLatest() method below that prevents these
// two blocks from ever executing at the same time, even if invoked by multiple
// threads.
// 1) Update the internal state (locked)
// 2) Since a prior update may have happened that didn't need a completion signal,
// it is possible that updateSignal is null. Regardless of the need to resubmit or
// not, the updateSignal must have a value to be returned.
// 3) If an update is already dispatched, return existing update signal. This
// updateSignal may be the value from #2 (this is fine).
// 4) If we get this far, we need to dispatch an update. Mark this as updating, and
// exit the locked section.
// 5) If updating, invoke submit without holding the lock.
updateState(
parameters, appendParameters, internalParameters,
appendInternalParameters, streams, template,
listeners
)
if (updateSignal == null) {
updateSignal = CompletableDeferred()
}
if (updating) {
return updateSignal!!
}
// Fall through to submit if there is no pending update.
updating = true
result = updateSignal!!
}
submitLatest()
return result
}
fun update(
parameters: Map<CaptureRequest.Key<*>, Any>? = null,
appendParameters: Boolean = true,
internalParameters: Map<Metadata.Key<*>, Any>? = null,
appendInternalParameters: Boolean = true,
streams: Set<StreamId>? = null,
template: RequestTemplate? = null,
listeners: Set<Request.Listener>? = null
) {
synchronized(lock) {
// See updateAsync for details.
updateState(
parameters, appendParameters, internalParameters,
appendInternalParameters, streams, template,
listeners
)
if (updating) {
return
}
updating = true
}
submitLatest()
}
fun capture(requests: List<Request>) {
threads.scope.launch(start = CoroutineStart.UNDISPATCHED) {
cameraGraph.acquireSession().use {
it.submit(requests)
}
}
}
@GuardedBy("lock")
private inline fun updateState(
parameters: Map<CaptureRequest.Key<*>, Any>? = null,
appendParameters: Boolean = true,
internalParameters: Map<Metadata.Key<*>, Any>? = null,
appendInternalParameters: Boolean = true,
streams: Set<StreamId>? = null,
template: RequestTemplate? = null,
listeners: Set<Request.Listener>? = null
) {
// TODO: Consider if this should detect changes and only invoke an update if state has
// actually changed.
if (parameters != null) {
if (!appendParameters) {
currentParameters.clear()
}
currentParameters.putAll(parameters)
}
if (internalParameters != null) {
if (!appendInternalParameters) {
currentInternalParameters.clear()
}
currentInternalParameters.putAll(internalParameters)
}
if (streams != null) {
currentStreams.clear()
currentStreams.addAll(streams)
}
if (template != null) {
currentTemplate = template
}
if (listeners != null) {
currentListeners.clear()
currentListeners.addAll(listeners)
}
}
/**
* Tries to invoke [androidx.camera.camera2.pipe.CameraGraph.Session.startRepeating]
* with current (the most recent) set of values.
*/
fun tryStartRepeating() = submitLatest()
private fun submitLatest() {
// Update the cameraGraph with the most recent set of values.
// Since acquireSession is a suspending function, it's possible that subsequent updates
// can occur while waiting for the acquireSession call to complete. If this happens,
// updates to the internal state are aggregated together, and the Request is built
// synchronously with the latest values. The startRepeating/stopRepeating call happens
// outside of the synchronized block to avoid holding a lock while updating the camera
// state.
threads.scope.launch(start = CoroutineStart.UNDISPATCHED) {
val result: CompletableDeferred<Unit>?
val request: Request?
cameraGraph.acquireSession().use {
synchronized(lock) {
request = if (currentStreams.isEmpty()) {
null
} else {
Request(
template = currentTemplate,
streams = currentStreams.toList(),
parameters = currentParameters.toMap(),
extras = currentInternalParameters.toMutableMap().also { parameters ->
parameters[USE_CASE_CAMERA_STATE_CUSTOM_TAG] =
submittedRequestCounter.incrementAndGet()
},
listeners = currentListeners.toMutableList().also { listeners ->
listeners.add(requestListener)
}
)
}
result = updateSignal
updating = false
updateSignal = null
}
if (request == null) {
it.stopRepeating()
} else {
result?.let { result ->
synchronized(lock) {
updateSignals.add(RequestSignal(submittedRequestCounter.value, result))
}
}
Log.debug { "Update RepeatingRequest: $request" }
it.startRepeating(request)
it.update3A(request.parameters)
}
}
// complete the result instantly only when the request was not submitted
if (request == null) {
// Complete the result after the session closes to allow other threads to acquire a
// lock. This also avoids cases where complete() synchronously invokes expensive
// calls.
result?.complete(Unit)
}
}
}
private fun CameraGraph.Session.update3A(parameters: Map<CaptureRequest.Key<*>, Any>?) {
val aeMode = parameters.getIntOrNull(CaptureRequest.CONTROL_AE_MODE)?.let {
AeMode.fromIntOrNull(it)
}
val afMode = parameters.getIntOrNull(CaptureRequest.CONTROL_AF_MODE)?.let {
AfMode.fromIntOrNull(it)
}
val awbMode = parameters.getIntOrNull(CaptureRequest.CONTROL_AWB_MODE)?.let {
AwbMode.fromIntOrNull(it)
}
if (aeMode != null || afMode != null || awbMode != null) {
update3A(aeMode = aeMode, afMode = afMode, awbMode = awbMode)
}
}
private fun Map<CaptureRequest.Key<*>, Any>?.getIntOrNull(
key: CaptureRequest.Key<*>
): Int? = this?.get(key) as? Int
inner class RequestListener : Request.Listener {
override fun onTotalCaptureResult(
requestMetadata: RequestMetadata,
frameNumber: FrameNumber,
totalCaptureResult: FrameInfo,
) {
super.onTotalCaptureResult(requestMetadata, frameNumber, totalCaptureResult)
threads.scope.launch(start = CoroutineStart.UNDISPATCHED) {
requestMetadata[USE_CASE_CAMERA_STATE_CUSTOM_TAG]?.let { requestNo ->
synchronized(lock) {
updateSignals.complete(requestNo)
}
}
}
}
@Deprecated(
message = "Migrating to using RequestFailureWrapper instead of CaptureFailure",
level = DeprecationLevel.WARNING
)
override fun onFailed(
requestMetadata: RequestMetadata,
frameNumber: FrameNumber,
captureFailure: CaptureFailure,
) {
@Suppress("DEPRECATION")
super.onFailed(requestMetadata, frameNumber, captureFailure)
completeExceptionally(requestMetadata, captureFailure)
}
private fun completeExceptionally(
requestMetadata: RequestMetadata,
captureFailure: CaptureFailure? = null
) {
threads.scope.launch(start = CoroutineStart.UNDISPATCHED) {
requestMetadata[USE_CASE_CAMERA_STATE_CUSTOM_TAG]?.let { requestNo ->
synchronized(lock) {
updateSignals.completeExceptionally(
requestNo,
Throwable(
"Failed in framework level" + (captureFailure?.reason?.let {
" with CaptureFailure.reason = $it"
} ?: "")
)
)
}
}
}
}
private fun ArrayDeque<RequestSignal>.complete(requestNo: Int) {
while (isNotEmpty() && first().requestNo <= requestNo) {
first().signal.complete(Unit)
removeFirst()
}
}
private fun ArrayDeque<RequestSignal>.completeExceptionally(
requestNo: Int,
throwable: Throwable
) {
while (isNotEmpty() && first().requestNo <= requestNo) {
first().signal.completeExceptionally(throwable)
removeFirst()
}
}
}
}