[go: nahoru, domu]

blob: 8304760e6d091ac5a2075921c12abecde25c624d [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.impl.Log.warn
import kotlinx.coroutines.CoroutineDispatcher
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 {
var requestProcessor: RequestProcessor?
/**
* This method puts the [GraphProcessor] into a started state. Starting the [GraphProcessor]
* will cause it to attempt to submit all requests to the current [RequestProcessor] instance,
* and any subsequent requests will be immediately submitted to the current [RequestProcessor].
*/
fun start()
/**
* This method puts the [GraphProcessor] into a stopped state and clears the current
* [RequestProcessor] instance. While the graph processor is stopped, all requests are
* buffered.
*/
fun stop()
fun setRepeating(request: Request)
fun submit(request: Request)
fun submit(requests: List<Request>)
/**
* 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
class GraphProcessorImpl @Inject constructor(
@ForCameraGraph private val graphScope: CoroutineScope,
@ForCameraGraph private val graphDispatcher: CoroutineDispatcher,
@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
@GuardedBy("lock")
private var active = false
override var requestProcessor: RequestProcessor?
get() = synchronized(lock) {
_requestProcessor
}
set(value) {
val processorToClose: RequestProcessor?
val processorToDisconnect: RequestProcessor?
synchronized(lock) {
processorToDisconnect = _requestProcessor
if (closed) {
processorToClose = value
} else {
processorToClose = null
_requestProcessor = value
}
}
if (value === processorToDisconnect) {
warn { "RequestProcessor was set more than once." }
return
}
// Setting the request processor to null will disconnect the old processor.
if (processorToDisconnect != null) {
synchronized(processorToDisconnect) {
processorToDisconnect.disconnect()
}
}
if (processorToClose != null) {
synchronized(processorToClose) {
processorToClose.stop()
}
return
}
if (value != null) {
graphScope.launch {
trySetRepeating()
submitLoop()
}
}
}
override fun start() {
synchronized(lock) {
active = true
}
Log.debug { "Starting GraphProcessor" }
// TODO: Start the camera and configure the capture session.
}
/**
* This method puts the [GraphProcessorImpl] into a stopped state. While the graph processor is
* in this state, all requests are buffered in the RequestQueue.
*/
override fun stop() {
val processor = synchronized(lock) {
active = false
_requestProcessor.also { _requestProcessor = null }
}
Log.debug { "Stopping GraphProcessor" }
if (processor == null) {
return
}
// There are about ~3 main ways a Camera2 CameraCaptureSession can be shut down and closed,
// and the behavior will be different depending on the circumstances.
//
// A session can be replaced by another session by simply calling createCaptureSession on
// the CameraDevice. Internally this will reconfigure the camera capture session, and there
// are optimizations present in the CameraFramework and Camera HAL that can optimize how
// fast the new session is created and started. The most obvious example of this is
// replacing a surface with a new one after recording a video, which can effectively cause
// the new session to be created and replaced without dropping a frame.
//
// Second, a session can be _stopped_ by calling stopRepeating and/or abortCaptures. This
// keeps the session alive but may abort pending requests. In some cases it's faster to
// switch sessions if these methods are invoked before creating a new session on the
// device because requests that are in-flight can be explicitly aborted.
//
// Finally, a session may be closed as a result of the underlying CameraDevice being closed
// or disconnected. This can happen if a higher priority process steals the camera, or
// during switches from one camera to another.
graphScope.launch {
processor.stop()
}
}
override fun setRepeating(request: Request) {
synchronized(lock) {
if (closed) return
nextRepeatingRequest = request
}
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.
*/
suspend fun submit(parameters: Map<CaptureRequest.Key<*>, Any>): Boolean =
withContext(graphDispatcher) {
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 {
// Start with requests that have already been submitted
if (processor != null) {
synchronized(processor) {
processor.abort()
}
}
// Then abort requests that have not been submitted
for (burst in requests) {
abortBurst(burst)
}
}
}
override fun close() {
synchronized(lock) {
if (closed) {
return
}
closed = true
}
abort()
stop()
}
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 || !active) return
processor = _requestProcessor
request = nextRepeatingRequest ?: currentRepeatingRequest
}
if (processor != null && request != null) {
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) {
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
}
}
}
}
}
}
private fun submitLoop() {
var burst: List<Request>
var processor: RequestProcessor
synchronized(lock) {
if (closed || !active) 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
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 {
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) {
// If we did not submit, and we are also not dirty, then exit the loop
submitting = false
return
} else {
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
}
}
}
}
}
}
}