[go: nahoru, domu]

blob: bc6e84f31bafb4f0dac71476402045dd0cd8e791 [file] [log] [blame]
/*
* Copyright 2021 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:RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
package androidx.camera.camera2.pipe.graph
import androidx.annotation.GuardedBy
import androidx.annotation.RequiresApi
import androidx.camera.camera2.pipe.CameraGraph
import androidx.camera.camera2.pipe.Request
import androidx.camera.camera2.pipe.RequestProcessor
import androidx.camera.camera2.pipe.config.CameraGraphScope
import androidx.camera.camera2.pipe.config.ForCameraGraph
import androidx.camera.camera2.pipe.core.Debug
import androidx.camera.camera2.pipe.core.Log.debug
import androidx.camera.camera2.pipe.core.Log.warn
import androidx.camera.camera2.pipe.core.Threads
import androidx.camera.camera2.pipe.formatForLogs
import androidx.camera.camera2.pipe.putAllMetadata
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* The [GraphProcessor] is responsible for queuing and submitting requests to a single
* [RequestProcessor] instance, and for maintaining state across one or more [RequestProcessor]
* instances.
*/
internal interface GraphProcessor : GraphListener {
fun submit(request: Request)
fun submit(requests: List<Request>)
suspend fun submit(parameters: Map<*, Any?>): Boolean
fun startRepeating(request: Request)
fun stopRepeating()
/**
* Indicates that internal parameters may have changed, and that the repeating request should
* be updated as soon as possible.
*/
fun invalidate()
/**
* Abort all submitted requests that have not yet been submitted to the [RequestProcessor] as
* well as aborting requests on the [RequestProcessor] itself.
*/
fun abort()
/**
* Closing the [GraphProcessor] will abort all queued requests. Any requests submitted after the
* [GraphProcessor] is closed will be immediately aborted.
*/
fun close()
}
/**
* The graph processor handles *cross-session* state, such as the most recent repeating request.
*/
@CameraGraphScope
internal class GraphProcessorImpl @Inject constructor(
private val threads: Threads,
private val cameraGraphConfig: CameraGraph.Config,
private val graphState3A: GraphState3A,
@ForCameraGraph private val graphScope: CoroutineScope,
@ForCameraGraph private val graphListeners: List<@JvmSuppressWildcards Request.Listener>
) : GraphProcessor {
private val lock = Any()
@GuardedBy("lock")
private val submitQueue: MutableList<List<Request>> = ArrayList()
@GuardedBy("lock")
private var currentRepeatingRequest: Request? = null
@GuardedBy("lock")
private var nextRepeatingRequest: Request? = null
@GuardedBy("lock")
private var _requestProcessor: RequestProcessor? = null
@GuardedBy("lock")
private var submitting = false
@GuardedBy("lock")
private var dirty = false
@GuardedBy("lock")
private var closed = false
override fun onGraphStarted(requestProcessor: RequestProcessor) {
var oldRequestProcessor: RequestProcessor? = null
synchronized(lock) {
if (closed) {
requestProcessor.close()
return
}
if (_requestProcessor != null && _requestProcessor !== requestProcessor) {
oldRequestProcessor = _requestProcessor
}
_requestProcessor = requestProcessor
}
val processorToClose = oldRequestProcessor
if (processorToClose != null) {
synchronized(processorToClose) {
processorToClose.close()
}
}
resubmit()
}
override fun onGraphStopped(requestProcessor: RequestProcessor) {
var oldRequestProcessor: RequestProcessor? = null
synchronized(lock) {
if (closed) {
return
}
if (requestProcessor === _requestProcessor) {
oldRequestProcessor = _requestProcessor
_requestProcessor = null
} else {
warn {
"Refusing to detach $requestProcessor. " +
"It is different from $_requestProcessor"
}
}
}
val processorToClose = oldRequestProcessor
if (processorToClose != null) {
synchronized(processorToClose) {
processorToClose.close()
}
}
}
override fun onGraphUpdated(requestProcessor: RequestProcessor) {
synchronized(lock) {
if (closed) {
return
}
if (requestProcessor != _requestProcessor) {
return
}
}
resubmit()
}
override fun startRepeating(request: Request) {
synchronized(lock) {
if (closed) return
nextRepeatingRequest = request
debug { "startRepeating with ${request.formatForLogs()}" }
}
graphScope.launch {
tryStartRepeating()
}
}
override fun stopRepeating() {
val processor: RequestProcessor?
synchronized(lock) {
processor = _requestProcessor
nextRepeatingRequest = null
currentRepeatingRequest = null
}
graphScope.launch {
Debug.traceStart { "$this#stopRepeating" }
// Start with requests that have already been submitted
if (processor != null) {
synchronized(processor) {
processor.stopRepeating()
}
}
Debug.traceStop()
}
}
override fun submit(request: Request) {
submit(listOf(request))
}
override fun submit(requests: List<Request>) {
synchronized(lock) {
if (closed) {
graphScope.launch(threads.lightweightDispatcher) {
abortBurst(requests)
}
return
}
submitQueue.add(requests)
}
graphScope.launch(threads.lightweightDispatcher) {
submitLoop()
}
}
/**
* Submit a request to the camera using only the current repeating request.
*/
override suspend fun submit(parameters: Map<*, Any?>): Boolean =
withContext(threads.lightweightDispatcher) {
val processor: RequestProcessor?
val request: Request?
val requiredParameters: MutableMap<Any, Any?> = mutableMapOf()
synchronized(lock) {
if (closed) return@withContext false
processor = _requestProcessor
request = currentRepeatingRequest
requiredParameters.putAllMetadata(parameters.toMutableMap())
graphState3A.writeTo(requiredParameters)
requiredParameters.putAllMetadata(cameraGraphConfig.requiredParameters)
}
return@withContext when {
processor == null || request == null -> false
else -> processor.submit(
request,
defaultParameters = cameraGraphConfig.defaultParameters,
requiredParameters = requiredParameters,
defaultListeners = graphListeners
)
}
}
override fun invalidate() {
// Invalidate is only used for updates to internal state (listeners, parameters, etc) and
// should not (currently) attempt to resubmit the normal request queue.
graphScope.launch(threads.lightweightDispatcher) {
tryStartRepeating()
}
}
override fun abort() {
val processor: RequestProcessor?
val requests: List<List<Request>>
synchronized(lock) {
processor = _requestProcessor
requests = submitQueue.toList()
submitQueue.clear()
}
graphScope.launch {
Debug.traceStart { "$this#abort" }
// Start with requests that have already been submitted
if (processor != null) {
synchronized(processor) {
processor.abortCaptures()
}
}
// Then abort requests that have not been submitted
for (burst in requests) {
abortBurst(burst)
}
Debug.traceStop()
}
}
override fun close() {
val processor: RequestProcessor?
synchronized(lock) {
if (closed) {
return
}
closed = true
processor = _requestProcessor
_requestProcessor = null
}
processor?.close()
abort()
}
private fun resubmit() {
graphScope.launch(threads.lightweightDispatcher) {
tryStartRepeating()
submitLoop()
}
}
private fun abortBurst(requests: List<Request>) {
for (request in requests) {
abortRequest(request)
}
}
private fun abortRequest(request: Request) {
for (listenerIdx in graphListeners.indices) {
graphListeners[listenerIdx].onAborted(request)
}
for (listenerIdx in request.listeners.indices) {
request.listeners[listenerIdx].onAborted(request)
}
}
private fun tryStartRepeating() {
val processor: RequestProcessor?
val request: Request?
synchronized(lock) {
if (closed) return
processor = _requestProcessor
request = nextRepeatingRequest ?: currentRepeatingRequest
// TODO: It might be a good idea to turn the "nextRepeatingRequest" into a queue to
// help with cases where we want to start the camera early. Example: If we have a
// stream configuration where the viewfinder is deferred, but we have an ImageReader
// that is _not_ deferred, it may be possible to submit the repeating request.
// However, a request with *both* streams would be rejected because not all streams
// are ready.
// Example:
// - Request(listOf(viewfinderStream, otherStream)) // Fails (no viewfinder surface)
// - Request(listOf(otherStream)) // works
// If (as an app developer) we wanted to make sure the camera starts before the
// viewfinder is ready, we would likely want to do something like:
// - startRepeating(listOf(otherStream))
// - startRepeating(listOf(viewfinderStream, otherStream))
// The way this is implemented at the moment, the "nextRepeatingRequest" would be set
// to the second call to startRepeating, which would not work. Since the first call got
// discarded, we would be unable to start the camera before the viewfinder was
// available.
}
if (processor != null && request != null) {
Debug.traceStart { "$this#startRepeating" }
synchronized(processor) {
val requiredParameters = mutableMapOf<Any, Any?>()
graphState3A.writeTo(requiredParameters)
requiredParameters.putAllMetadata(cameraGraphConfig.requiredParameters)
if (processor.startRepeating(
request,
defaultParameters = cameraGraphConfig.defaultParameters,
requiredParameters = requiredParameters,
defaultListeners = graphListeners
)
) {
// ONLY update the current repeating request if the update succeeds
synchronized(lock) {
if (processor === _requestProcessor) {
currentRepeatingRequest = request
// There is a race condition where the nextRepeating request might be changed
// while trying to update the current repeating request. If this happens, do no
// overwrite the pending request.
if (nextRepeatingRequest == request) {
nextRepeatingRequest = null
}
}
}
}
}
Debug.traceStop()
}
}
private fun submitLoop() {
var burst: List<Request>
var processor: RequestProcessor
synchronized(lock) {
if (closed) return
if (submitting) {
dirty = true
return
}
val nullableProcessor = _requestProcessor
val nullableBurst = submitQueue.firstOrNull()
if (nullableProcessor == null || nullableBurst == null) {
return
}
processor = nullableProcessor
burst = nullableBurst
submitting = true
}
while (true) {
var submitted = false
Debug.traceStart { "$this#submit" }
try {
submitted = synchronized(processor) {
val requiredParameters = mutableMapOf<Any, Any?>()
graphState3A.writeTo(requiredParameters)
requiredParameters.putAllMetadata(cameraGraphConfig.requiredParameters)
if (burst.size == 1) {
processor.submit(
burst[0],
defaultParameters = cameraGraphConfig.defaultParameters,
requiredParameters = requiredParameters,
defaultListeners = graphListeners
)
} else {
processor.submit(
burst,
defaultParameters = cameraGraphConfig.defaultParameters,
requiredParameters = requiredParameters,
defaultListeners = graphListeners
)
}
}
} finally {
Debug.traceStop()
synchronized(lock) {
if (submitted) {
check(submitQueue.removeAt(0) === burst)
val nullableBurst = submitQueue.firstOrNull()
if (nullableBurst == null) {
dirty = false
submitting = false
return
}
burst = nullableBurst
} else if (!dirty) {
debug { "Failed to submit $burst, and the queue is not dirty." }
// If we did not submit, and we are also not dirty, then exit the loop
submitting = false
return
} else {
debug {
"Failed to submit $burst but the request queue or processor is " +
"dirty. Clearing dirty flag and attempting retry."
}
dirty = false
// One possible situation is that the _requestProcessor was replaced or
// set to null. If this happens, try to update the requestProcessor we
// are currently using. If the current request processor is null, then
// we cannot submit anyways.
val nullableProcessor = _requestProcessor
if (nullableProcessor != null) {
processor = nullableProcessor
}
}
}
}
}
}
}