blob: bde605143c0e80b909d8a437d74ea274e739d893 [file] [log] [blame]
/*
* Copyright 2022 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.
*/
package androidx.camera.camera2.pipe.graph
import android.hardware.camera2.CameraAccessException
import androidx.annotation.GuardedBy
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.CaptureSequence
import androidx.camera.camera2.pipe.CaptureSequenceProcessor
import androidx.camera.camera2.pipe.CaptureSequences.invokeOnRequests
import androidx.camera.camera2.pipe.Request
import androidx.camera.camera2.pipe.compat.ObjectUnavailableException
import androidx.camera.camera2.pipe.core.Log
import kotlinx.atomicfu.atomic
internal val graphRequestProcessorIds = atomic(0)
/**
* The GraphRequestProcessor wraps and tracks the internal state for requests that are submitted to
* a [CaptureSequenceProcessor] instance.
*
* GraphRequestProcessors are intended to be in conjunction with a [GraphListener].
*/
@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
@Suppress("NOTHING_TO_INLINE")
class GraphRequestProcessor
private constructor(
private val captureSequenceProcessor: CaptureSequenceProcessor<Any, CaptureSequence<Any>>
) {
companion object {
/** Create a [GraphRequestProcessor] from a [CaptureSequenceProcessor] instance. */
fun from(captureSequenceProcessor: CaptureSequenceProcessor<*, *>): GraphRequestProcessor {
@Suppress("UNCHECKED_CAST")
return GraphRequestProcessor(
captureSequenceProcessor as CaptureSequenceProcessor<Any, CaptureSequence<Any>>
)
}
}
private val debugId = graphRequestProcessorIds.incrementAndGet()
private val closed = atomic(false)
@GuardedBy("activeCaptureSequences")
private val activeCaptureSequences = mutableListOf<CaptureSequence<*>>()
private val activeBurstListener =
object : CaptureSequence.CaptureSequenceListener {
override fun onCaptureSequenceComplete(captureSequence: CaptureSequence<*>) {
// Listen to the completion of active capture sequences and remove them from the
// list
// of currently active capture sequences. Since repeating requests are not required
// to
// execute, only non-repeating capture sequences are tracked.
if (!captureSequence.repeating) {
synchronized(activeCaptureSequences) {
activeCaptureSequences.remove(captureSequence)
}
}
}
}
internal fun abortCaptures() {
// Note: abortCaptures is not affected by active state.
// TODO: Consider adding a synchronization lock of some kind to prevent requests from being
// submitted while an abort is actively occurring. This could increase the risk of
// deadlock. Not locking could increase the risk that additional capture sequences are
// submitted in-between reading the list of activeCaptureSequences and calling the
// abortCaptures method on the captureSequenceProcessor.
// Create a copy of the list of non-repeating capture sequences (thread safe), clear the
// list, then invoke the onAborted listeners for all capture sequences that were in progress
// at the time abort was invoked.
val requestsToAbort =
synchronized(activeCaptureSequences) {
val copy = activeCaptureSequences.toList()
activeCaptureSequences.clear()
copy
}
// Invoke onAbort to indicate that the actual abort is about to happen.
for (sequence in requestsToAbort) {
sequence.invokeOnAborted()
}
// Finally, invoke abortCaptures on the underlying captureSequenceProcessor instance.
captureSequenceProcessor.abortCaptures()
}
internal fun stopRepeating() {
// Note: stopRepeating is not affected by active state.
captureSequenceProcessor.stopRepeating()
}
internal fun close() {
Log.warn { "Closing $this" }
if (closed.compareAndSet(expect = false, update = true)) {
captureSequenceProcessor.close()
}
}
internal fun submit(
isRepeating: Boolean,
requests: List<Request>,
defaultParameters: Map<*, Any?>,
requiredParameters: Map<*, Any?>,
listeners: List<Request.Listener>,
): Boolean {
// Reject incoming requests if this instance has been stopped or closed.
if (closed.value) {
Log.warn { "Rejecting requests $requests: Request processor is closed." }
return false
}
// This can fail for various reasons and may throw exceptions.
val captureSequence =
captureSequenceProcessor.build(
isRepeating,
requests,
defaultParameters,
requiredParameters,
listeners,
activeBurstListener
)
// Reject incoming requests if this instance has been stopped or closed.
if (captureSequence == null) {
Log.warn { "Rejecting requests $requests: Could not create the capture sequence." }
// We do not need to invoke the sequenceCompleteListener since it has not been added to
// the list of activeCaptureSequences yet.
return false
}
// Re-check again and reject requests if this instance has been closed or stopped.
// This is an optimization since building the captureSequence can take non-zero time.
if (closed.value) {
Log.warn { "Rejecting requests $requests: Request processor is closed." }
return false
}
// Reject incorrectly structured capture sequences:
check(captureSequence.captureRequestList.size == captureSequence.captureMetadataList.size) {
"CaptureSequence ($captureSequence) has mismatched request and metadata lists!"
}
// Non-repeating requests must always be aware of abort calls.
if (!captureSequence.repeating) {
synchronized(activeCaptureSequences) { activeCaptureSequences.add(captureSequence) }
}
var captured = false
return try {
Log.debug { "Submitting $captureSequence" }
captureSequence.invokeOnRequestSequenceCreated()
// NOTE: This is an unusual synchronization call. The purpose is to avoid a rare but
// possible situation where calling submit causes one of the callback methods to be
// invoked before this method call returns and sequenceNumber has been set on the
// callback. Both this call and the synchronized behavior on the captureSequence have
// been designed to minimize the number of synchronized calls.
val result =
synchronized(lock = captureSequence) {
// Check closed state right before submitting.
if (closed.value) {
Log.warn { "Did not submit $captureSequence, $this was closed!" }
return false
}
val sequenceNumber = captureSequenceProcessor.submit(captureSequence)
captureSequence.sequenceNumber = sequenceNumber
sequenceNumber
}
if (result != -1) {
captureSequence.invokeOnRequestSequenceSubmitted()
captured = true
Log.debug { "Submitted $captureSequence" }
true
} else {
Log.warn { "Did not submit $captureSequence, SequenceNumber was -1" }
false
}
} catch (closedException: ObjectUnavailableException) {
false
} catch (accessException: CameraAccessException) {
false
} finally {
// If ANY unhandled exception occurs, don't throw, but make sure we remove it from the
// list of in-flight requests.
if (!captured && !captureSequence.repeating) {
synchronized(activeCaptureSequences) {
activeCaptureSequences.remove(captureSequence)
}
captureSequence.invokeOnAborted()
}
}
}
override fun toString(): String = "GraphRequestProcessor-$debugId"
/**
* Custom implementation that informs all listeners that the request had not completed when
* abort was called.
*/
private inline fun <T> CaptureSequence<T>.invokeOnAborted() {
invokeOnRequests { request, _, listener -> listener.onAborted(request.request) }
}
private inline fun <T> CaptureSequence<T>.invokeOnRequestSequenceCreated() {
invokeOnRequests { request, _, listener -> listener.onRequestSequenceCreated(request) }
}
private inline fun <T> CaptureSequence<T>.invokeOnRequestSequenceSubmitted() {
invokeOnRequests { request, _, listener -> listener.onRequestSequenceSubmitted(request) }
}
}