blob: 6e00d101d07ccf127d099cd5b7e3dbdaa5f3000c [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:RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
package androidx.camera.camera2.pipe
import android.hardware.camera2.CameraCaptureSession
import android.hardware.camera2.CameraDevice
import android.hardware.camera2.CaptureFailure
import android.hardware.camera2.CaptureRequest
import android.view.Surface
import androidx.annotation.RequiresApi
import androidx.annotation.RestrictTo
import androidx.camera.camera2.pipe.core.Debug
import androidx.camera.camera2.pipe.core.Log
import androidx.camera.camera2.pipe.media.ImageWrapper
/**
* A [RequestNumber] is an artificial identifier that is created for each request that is submitted
* to the Camera.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@JvmInline
value class RequestNumber(val value: Long)
/**
* A [Request] is an immutable package of outputs and parameters needed to issue a [CaptureRequest]
* to a Camera2 [CameraCaptureSession].
*
* [Request] objects are handled by camera2 via the [RequestProcessor] interface, and will translate
* each [Request] object into a corresponding [CaptureRequest] object using the active
* [CameraDevice], [CameraCaptureSession], and [CameraGraph.Config]. Requests may be queued up and
* submitted after a delay, or reused (in the case of repeating requests) if the
* [CameraCaptureSession] is reconfigured or recreated.
*
* Depending on the [CameraGraph.Config], it is possible that not all parameters that are set on the
* [Request] will be honored when a [Request] is sent to the camera. Specifically, Camera2
* parameters related to 3A State and any required parameters specified on the [CameraGraph.Config]
* will override parameters specified in a [Request]
*
* @param streams The list of streams to submit. Each request *must* have 1 or more valid streams.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class Request(
val streams: List<StreamId>,
val parameters: Map<CaptureRequest.Key<*>, Any> = emptyMap(),
val extras: Map<Metadata.Key<*>, Any> = emptyMap(),
val listeners: List<Listener> = emptyList(),
val template: RequestTemplate? = null,
val inputRequest: InputRequest? = null
) {
operator fun <T> get(key: CaptureRequest.Key<T>): T? = getUnchecked(key)
operator fun <T> get(key: Metadata.Key<T>): T? = getUnchecked(key)
/**
* This listener is used to observe the state and progress of a [Request] that has been issued
* to the [CameraGraph]. Listeners will be invoked on background threads at high speed, and
* should avoid blocking work or accessing synchronized resources if possible. [Listener]s used
* in a repeating request may be issued multiple times within the same session, and should not
* rely on [onRequestSequenceSubmitted] from being invoked only once.
*/
interface Listener {
/**
* This event indicates that the camera sensor has started exposing the frame associated
* with this Request. The timestamp will either be the beginning or end of the sensors
* exposure time depending on the device, and may be in a different timebase from the
* timestamps that are returned from the underlying buffers.
*
* @param requestMetadata the data about the camera2 request that was sent to the camera.
* @param frameNumber the android frame number for this exposure
* @param timestamp the android timestamp in nanos for this exposure
* @see android.hardware.camera2.CameraCaptureSession.CaptureCallback.onCaptureStarted
*/
fun onStarted(
requestMetadata: RequestMetadata,
frameNumber: FrameNumber,
timestamp: CameraTimestamp
) {
}
/**
* This event indicates that the camera sensor has additional information about the frame
* associated with this Request. This method may be invoked 0 or more times before the frame
* receives onComplete.
*
* @param requestMetadata the data about the camera2 request that was sent to the camera.
* @param frameNumber the android frame number for this exposure
* @param captureResult the current android capture result for this exposure
* @see android.hardware.camera2.CameraCaptureSession.CaptureCallback.onCaptureStarted
*/
fun onPartialCaptureResult(
requestMetadata: RequestMetadata,
frameNumber: FrameNumber,
captureResult: FrameMetadata
) {
}
/**
* This event indicates that all of the metadata associated with this frame has been
* produced. If [onPartialCaptureResult] was invoked, the values returned in the
* totalCaptureResult map be a superset of the values produced from the
* [onPartialCaptureResult] calls.
*
* @param requestMetadata the data about the camera2 request that was sent to the camera.
* @param frameNumber the android frame number for this exposure
* @param totalCaptureResult the final android capture result for this exposure
* @see android.hardware.camera2.CameraCaptureSession.CaptureCallback.onCaptureStarted
*/
fun onTotalCaptureResult(
requestMetadata: RequestMetadata,
frameNumber: FrameNumber,
totalCaptureResult: FrameInfo
) {
}
/**
* This is an artificial event that will be invoked after onTotalCaptureResult. This may be
* invoked several frames after onTotalCaptureResult due to incorrect HAL implementations
* that return metadata that get shifted several frames in the future. See b/154568653 for
* real examples of this. The actual amount of shifting and required transformations may
* vary per device.
*
* @param requestMetadata the data about the camera2 request that was sent to the camera.
* @param frameNumber the android frame number for this exposure
* @param result the package of metadata associated with this result.
*/
fun onComplete(
requestMetadata: RequestMetadata,
frameNumber: FrameNumber,
result: FrameInfo
) {
}
/**
* onFailed occurs when a CaptureRequest failed in some way and the frame will not receive
* the [onTotalCaptureResult] callback.
*
* Surfaces may not received images if "wasImagesCaptured" is set to false.
*
* @param requestMetadata the data about the camera2 request that was sent to the camera.
* @param frameNumber the android frame number for this exposure
* @param requestFailure the android [RequestFailure] data wrapper
* @see android.hardware.camera2.CameraCaptureSession.CaptureCallback.onCaptureFailed
*/
fun onFailed(
requestMetadata: RequestMetadata,
frameNumber: FrameNumber,
requestFailure: RequestFailure
) {
}
/**
* onBufferLost occurs when a CaptureRequest failed to create an image for a given output
* stream. This method may be invoked multiple times per frame if multiple buffers were
* lost. This method may not be invoked when an image is lost in some situations.
*
* @param requestMetadata the data about the camera2 request that was sent to the camera.
* @param frameNumber the android frame number for this exposure
* @param stream the internal stream that will not receive a buffer for this frame.
* @see android.hardware.camera2.CameraCaptureSession.CaptureCallback.onCaptureBufferLost
*/
fun onBufferLost(
requestMetadata: RequestMetadata,
frameNumber: FrameNumber,
stream: StreamId
) {
}
/**
* This is an artificial callback that will be invoked if a specific request was pending or
* had already been submitted to when an abort was requested. The behavior of the request is
* undefined if this method is invoked and images or metadata may or may not be produced for
* this request. Repeating requests will not receive onAborted.
*
* @param request information about this specific request.
*/
fun onAborted(request: Request) {}
/**
* Invoked after the CaptureRequest(s) have been created, but before the request is
* submitted to the Camera. This method may be invoked multiple times if the request fails
* to submit or if this is a repeating request.
*
* @param requestMetadata information about this specific request.
*/
fun onRequestSequenceCreated(requestMetadata: RequestMetadata) {}
/**
* Invoked after the CaptureRequest(s) has been submitted. This method may be invoked
* multiple times if the request was submitted as a repeating request.
*
* @param requestMetadata the data about the camera2 request that was sent to the camera.
*/
fun onRequestSequenceSubmitted(requestMetadata: RequestMetadata) {}
/**
* Invoked by Camera2 if the request was aborted after having been submitted. This method is
* distinct from onAborted, which is directly invoked when aborting captures.
*
* @param requestMetadata the data about the camera2 request that was sent to the camera.
* @see
* android.hardware.camera2.CameraCaptureSession.CaptureCallback.onCaptureSequenceAborted
*/
fun onRequestSequenceAborted(requestMetadata: RequestMetadata) {}
/**
* Invoked by Camera2 if the request was completed after having been submitted. This method
* is distinct from onCompleted which is invoked for each frame when used with a repeating
* request.
*
* @param requestMetadata the data about the camera2 request that was sent to the camera.
* @param frameNumber the final frame number of this sequence.
* @see
* android.hardware.camera2.CameraCaptureSession.CaptureCallback.onCaptureSequenceCompleted
*/
fun onRequestSequenceCompleted(
requestMetadata: RequestMetadata,
frameNumber: FrameNumber
) {
}
}
@Suppress("UNCHECKED_CAST")
private fun <T> getUnchecked(key: Metadata.Key<T>): T? = this.extras[key] as T?
@Suppress("UNCHECKED_CAST")
private fun <T> getUnchecked(key: CaptureRequest.Key<T>): T? =
this.parameters[key] as T?
override fun toString(): String {
val parametersString =
if (parameters.isEmpty()) {
""
} else {
", parameters=${Debug.formatParameterMap(parameters, limit = 5)}"
}
val extrasString =
if (extras.isEmpty()) "" else ", extras=${Debug.formatParameterMap(extras, limit = 5)}"
val templateString = if (template == null) "" else ", template=$template"
// Ignore listener count, always include stream list (required), and use super.toString to
// reference the class name.
return "Request(streams=$streams$templateString$parametersString$extrasString)"
}
}
/**
* Interface wrapper for [CaptureFailure].
*
* This interface should be used instead of [CaptureFailure] because its package-private
* constructor prevents directly creating an instance of it.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
interface RequestFailure : UnsafeWrapper {
/** Metadata about the request that has failed. */
val requestMetadata: RequestMetadata
/** The Camera [FrameNumber] for the request that has failed. */
val frameNumber: FrameNumber
/** Indicates the reason the particular request failed, see [CaptureFailure] for details. */
val reason: Int
/**
* Indicates if images were still captured for this request. If this is true, the camera should
* invoke [Request.Listener.onBufferLost] individually for each output that failed. If this is
* false, these outputs will never arrive, and the individual callbacks will not be invoked.
*/
val wasImageCaptured: Boolean
}
/**
* A [RequestTemplate] indicates which preset set list of parameters will be applied to a request by
* default. These values are defined by camera2.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@JvmInline
value class RequestTemplate(val value: Int) {
val name: String
get() {
return when (value) {
1 -> "TEMPLATE_PREVIEW"
2 -> "TEMPLATE_STILL_CAPTURE"
3 -> "TEMPLATE_RECORD"
4 -> "TEMPLATE_VIDEO_SNAPSHOT"
5 -> "TEMPLATE_ZERO_SHUTTER_LAG"
6 -> "TEMPLATE_MANUAL"
else -> "UNKNOWN-$value"
}
}
}
/**
* The intended use for this class is to submit the input needed for a reprocessing request, the
* [ImageWrapper] and [FrameInfo]. Both values are non-nullable because
* both values are needed for reprocessing.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
data class InputRequest(
val image: ImageWrapper,
val frameInfo: FrameInfo
)
/**
* RequestMetadata wraps together all of the information about a specific CaptureRequest that was
* submitted to Camera2.
*
* <p> This class is distinct from [Request] which is used to configure and issue a request to the
* [CameraGraph]. This class will report the actual keys / values that were sent to camera2 (if
* different) from the request that was used to create the Camera2 [CaptureRequest].
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
interface RequestMetadata : Metadata, UnsafeWrapper {
operator fun <T> get(key: CaptureRequest.Key<T>): T?
fun <T> getOrDefault(key: CaptureRequest.Key<T>, default: T): T
/** The actual Camera2 template that was used when creating this [CaptureRequest] */
val template: RequestTemplate
/**
* A Map of StreamId(s) that were submitted with this CaptureRequest and the Surface(s) used for
* this request. It's possible that not all of the streamId's specified in the [Request] are
* present in the [CaptureRequest].
*/
val streams: Map<StreamId, Surface>
/** Returns true if this is used in a repeating request. */
val repeating: Boolean
/** The request object that was used to create this [CaptureRequest] */
val request: Request
/** An internal number used to identify a specific [CaptureRequest] */
val requestNumber: RequestNumber
}
/**
* This is a timestamp from the Camera, and corresponds to the nanosecond exposure time of a Frame.
* While the value is expressed in nano-seconds, the precision may be much lower. In addition, the
* time-base of the Camera is undefined, although it's common for it to be in either Monotonic or
* Realtime.
*
* <p> Timestamp may differ from timestamps that are obtained from other parts of the Camera and
* media systems within the same device. For example, it's common for high frequency sensors to
* operate based on a real-time clock, while audio/visual systems commonly operate based on a
* monotonic clock.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@JvmInline
value class CameraTimestamp(val value: Long)
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun <T> Request.getOrDefault(key: Metadata.Key<T>, default: T): T = this[key] ?: default
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun <T> Request.getOrDefault(key: CaptureRequest.Key<T>, default: T): T =
this[key] ?: default
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun Request.formatForLogs(): String = "Request($streams)@${Integer.toHexString(hashCode())}"
/** Utility function to help deal with the unsafe nature of the typed Key/Value pairs. */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun CaptureRequest.Builder.writeParameters(parameters: Map<*, Any?>) {
for ((key, value) in parameters) {
writeParameter(key, value)
}
}
/** Utility function to help deal with the unsafe nature of the typed Key/Value pairs. */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun CaptureRequest.Builder.writeParameter(key: Any?, value: Any?) {
if (key != null && key is CaptureRequest.Key<*>) {
try {
@Suppress("UNCHECKED_CAST") this.set(key as CaptureRequest.Key<Any>, value)
} catch (e: IllegalArgumentException) {
// Setting keys on CaptureRequest.Builder can fail if the key is defined on some
// OS versions, but not on others. Log and ignore these kinds of failures.
//
// See b/309518353 for an example failure.
Log.warn(e) {
"Failed to set [${key.name}: $value] on CaptureRequest.Builder"
}
}
}
}
/**
* Utility function to put all metadata in the current map through an unchecked cast. The unchecked
* cast is necessary since CameraGraph.Config uses Map<*, Any?> as the standard type for parameters.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun MutableMap<Any, Any?>.putAllMetadata(metadata: Map<*, Any?>) {
@Suppress("UNCHECKED_CAST") this.putAll(metadata as Map<Any, Any?>)
}