blob: 83f7a578f8661750957a3c892a6e28de9d0df3ee [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.
*/
package androidx.camera.camera2.pipe.impl
import android.hardware.camera2.CaptureRequest
import androidx.annotation.GuardedBy
import androidx.camera.camera2.pipe.Request
import androidx.camera.camera2.pipe.formatForLogs
import androidx.camera.camera2.pipe.impl.Log.debug
import androidx.camera.camera2.pipe.impl.Log.warn
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
/**
* The [GraphProcessor] is responsible for queuing and submitting requests to a single
* [RequestProcessor] instance, and for maintaining state across one or more [RequestProcessor]
* instances.
*/
interface GraphProcessor {
fun setRepeating(request: Request)
fun submit(request: Request)
fun submit(requests: List<Request>)
suspend fun submit(parameters: Map<CaptureRequest.Key<*>, Any>): Boolean
/**
* 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()
fun attach(requestProcessor: RequestProcessor)
fun detach(requestProcessor: RequestProcessor)
fun invalidate()
}
/**
* The graph processor handles *cross-session* state, such as the most recent repeating request.
*/
@CameraGraphScope
class GraphProcessorImpl @Inject constructor(
private val threads: Threads,
@ForCameraGraph private val graphScope: CoroutineScope,
@ForCameraGraph private val graphListeners: java.util.ArrayList<Request.Listener>
) : GraphProcessor {
private val lock = Any()
@GuardedBy("lock")
private val requestQueue: 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 attach(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 detach(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 invalidate() {
resubmit()
}
override fun setRepeating(request: Request) {
synchronized(lock) {
if (closed) return
nextRepeatingRequest = request
debug { "Set repeating request to ${request.formatForLogs()}" }
}
graphScope.launch {
trySetRepeating()
}
}
override fun submit(request: Request) {
submit(listOf(request))
}
override fun submit(requests: List<Request>) {
synchronized(lock) {
if (closed) {
graphScope.launch {
abortBurst(requests)
}
return
}
requestQueue.add(requests)
}
graphScope.launch {
submitLoop()
}
}
/**
* Submit a request to the camera using only the current repeating request.
*/
override suspend fun submit(parameters: Map<CaptureRequest.Key<*>, Any>): Boolean =
withContext(threads.ioDispatcher) {
val processor: RequestProcessor?
val request: Request?
synchronized(lock) {
if (closed) return@withContext false
processor = _requestProcessor
request = currentRepeatingRequest
}
return@withContext when {
processor == null || request == null -> false
else -> processor.submit(
request,
parameters,
requireSurfacesForAllStreams = false
)
}
}
override fun abort() {
val processor: RequestProcessor?
val requests: List<List<Request>>
synchronized(lock) {
processor = _requestProcessor
requests = requestQueue.toList()
requestQueue.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 {
trySetRepeating()
submitLoop()
}
}
private fun read3AState(): Map<CaptureRequest.Key<*>, Any> {
// TODO: Build extras from 3A state
return mapOf()
}
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 trySetRepeating() {
val processor: RequestProcessor?
val request: Request?
synchronized(lock) {
if (closed) return
processor = _requestProcessor
request = nextRepeatingRequest ?: currentRepeatingRequest
}
if (processor != null && request != null) {
Debug.traceStart { "$this#setRepeating" }
val extras: Map<CaptureRequest.Key<*>, Any> = read3AState()
synchronized(processor) {
if (processor.setRepeating(request, extras, requireSurfacesForAllStreams = true)) {
// 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 = requestQueue.firstOrNull()
if (nullableProcessor == null || nullableBurst == null) {
return
}
processor = nullableProcessor
burst = nullableBurst
submitting = true
}
while (true) {
var submitted = false
Debug.traceStart { "$this#submit" }
try {
val extras: Map<CaptureRequest.Key<*>, Any> = read3AState()
submitted = synchronized(processor) {
if (burst.size == 1) {
processor.submit(burst[0], extras, true)
} else {
processor.submit(burst, extras, true)
}
}
} finally {
Debug.traceStop()
synchronized(lock) {
if (submitted) {
check(requestQueue.removeAt(0) === burst)
val nullableBurst = requestQueue.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
}
}
}
}
}
}
}