相机捕获会话和请求

注意:本页介绍的是 Camera2 软件包。除非您的应用需要 Camera2 中的特定低级功能,否则我们建议您使用 CameraX。CameraX 和 Camera2 都支持 Android 5.0(API 级别 21)及更高版本。

一台 Android 设备可以配备多个摄像头。每个摄像头都是 CameraDevice、 和 CameraDevice 可以同时输出多个流。

这样做的一个原因是,一个视频流、连续的相机帧 (来自 CameraDevice)针对特定任务进行了优化,例如显示 取景器,而其他人则会用于拍照或录像 这些流充当处理原始帧的并行管道 每次一帧图片的效果:

图 1. 构建通用相机应用的图示(2018 年 Google I/O 大会)

并行处理表明可能存在性能限制, CPU、GPU 或其他处理器的可用处理能力。如果 流水线无法跟上传入的帧,便会开始丢弃这些帧。

每个流水线都有自己的输出格式。输入的原始数据是 自动转换为相应的 隐式逻辑的输出格式 与每个流水线相关联此页面中一直使用的 CameraDevice 代码示例并不具体,因此您首先要枚举 所有可用的摄像头,然后再继续。

您可以使用 CameraDevice 创建 CameraCaptureSession, 这是特定于该 CameraDevice 的 ID。CameraDevice 必须收到 每个原始帧的帧配置(使用 CameraCaptureSession)。通过 指定相机属性,例如自动对焦、光圈、效果 和曝光度。由于硬件限制, 相机传感器在任意指定时间都处于活跃状态,这称为 active 配置。

不过,Stream 用例增强并扩展了之前的 CameraDevice 使用方式。 实时捕获视频流,以便您针对 特定用例。例如,在进行优化时,它可以延长电池续航时间 视频通话。

CameraCaptureSession 描述了绑定到 CameraDevice。创建会话后,您无法添加或移除流水线。 CameraCaptureSession 会维护一个 CaptureRequest、 这些配置就会成为活动配置

CaptureRequest 会向队列中添加一项配置,并从中选择多项配置, 用于从服务器接收帧的 CameraDevice。您可以在拍摄的生命周期内发送多个拍摄请求 会话。每个请求都可以更改活跃配置和一组输出 接收原始图像的管道。

使用数据流用例提高性能

视频流用例是提高 Camera2 拍摄性能的一种方法 会话。它们可为硬件设备提供更多信息来调整参数, 以便针对您的特定任务提供更好的摄像头体验。

这个 让相机设备能够优化相机硬件和软件流水线 进行预训练。有关信息流使用的详细信息 如果是支持请求,请参阅 setStreamUseCase

视频流用例可让您指定特定相机视频流在 设置模板(位于 CameraDevice.createCaptureRequest()。这样,相机硬件就能 参数,例如调优、传感器模式或摄像头传感器设置,具体取决于 适合特定用例的质量或延迟权衡。

数据流用例包括:

  • DEFAULT:涵盖所有现有的应用行为。它相当于 任何数据流使用场景

  • PREVIEW:推荐用于取景器或应用内图片分析。

  • STILL_CAPTURE:针对高质量、高分辨率拍摄进行了优化 保持与预览一样的帧速率

  • VIDEO_RECORD:针对高品质视频拍摄进行了优化,包括高品质视频拍摄 图像防抖功能(如果设备支持并由应用启用)。 此选项可能会生成与实时、 以实现最高品质的防抖效果或其他处理

  • VIDEO_CALL:建议用于长时间运行相机,并且耗电量非常高 问题。

  • PREVIEW_VIDEO_STILL:推荐用于社交媒体应用或单一串流 案例这是一个多用途流。

  • VENDOR_START:用于 OEM 定义的用例。

创建 CameraCaptureSession

如需创建相机会话,请为其提供一个或多个输出缓冲区 向您的应用写入输出帧每个缓冲区代表一条管道。您必须 请在开始使用相机之前执行此操作,以便框架可以配置 为发送帧而分配内存缓冲区 所需的输出目标

以下代码段展示了如何准备具有两个 输出缓冲区,一个属于 SurfaceView和另一个添加到 ImageReader。将 PREVIEW 数据流用例添加到 previewSurfaceSTILL_CAPTURE 数据流使用 使用 imReaderSurface 的情况使设备硬件可以优化这些流, 。

Kotlin


// Retrieve the target surfaces, which might be coming from a number of places:
// 1. SurfaceView, if you want to display the image directly to the user
// 2. ImageReader, if you want to read each frame or perform frame-by-frame
// analysis
// 3. OpenGL Texture or TextureView, although discouraged for maintainability
      reasons
// 4. RenderScript.Allocation, if you want to do parallel processing
val surfaceView = findViewById<SurfaceView>(...)
val imageReader = ImageReader.newInstance(...)

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
val previewSurface = surfaceView.holder.surface
val imReaderSurface = imageReader.surface
val targets = listOf(previewSurface, imReaderSurface)

// Create a capture session using the predefined targets; this also involves
// defining the session state callback to be notified of when the session is
// ready
// Setup Stream Use Case while setting up your Output Configuration.
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun configureSession(device: CameraDevice, targets: List<Surface>){
    val configs = mutableListOf<OutputConfiguration>()
    val streamUseCase = CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    targets.forEach {
        val config = OutputConfiguration(it)
        config.streamUseCase = streamUseCase.toLong()
        configs.add(config)
    }
    ...
    device.createCaptureSession(session)
}

Java


// Retrieve the target surfaces, which might be coming from a number of places:
// 1. SurfaceView, if you want to display the image directly to the user
// 2. ImageReader, if you want to read each frame or perform frame-by-frame
      analysis
// 3. RenderScript.Allocation, if you want to do parallel processing
// 4. OpenGL Texture or TextureView, although discouraged for maintainability
      reasons
Surface surfaceView = findViewById<SurfaceView>(...);
ImageReader imageReader = ImageReader.newInstance(...);

// Remember to call this only *after* SurfaceHolder.Callback.surfaceCreated()
Surface previewSurface = surfaceView.getHolder().getSurface();
Surface imageSurface = imageReader.getSurface();
List<Surface> targets = Arrays.asList(previewSurface, imageSurface);

// Create a capture session using the predefined targets; this also involves defining the
// session state callback to be notified of when the session is ready
private void configureSession(CameraDevice device, List<Surface> targets){
    ArrayList<OutputConfiguration> configs= new ArrayList()
    String streamUseCase=  CameraMetadata
        .SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL

    for(Surface s : targets){
        OutputConfiguration config = new OutputConfiguration(s)
        config.setStreamUseCase(String.toLong(streamUseCase))
        configs.add(config)
}

device.createCaptureSession(session)
}

此时,您尚未定义相机的有效配置。 配置完会话后,您可以创建和分派捕获操作 执行所需的操作

在输入写入缓冲区时,应用于输入的转换为: 取决于每个定位条件的类型, Surface。Android 框架知道如何 将活动配置中的原始图片转换为 。转化受 特定的 Surface

框架会尽力而为,但会有一些Surface 配置组合可能不起作用,从而导致出现会话 在创建请求后抛出运行时错误 性能下降该框架可保证特定产品 设备、Surface 和请求参数的组合。有关 createCaptureSession() 提供了更多信息。

单个 CaptureRequest

每一帧使用的配置都以 CaptureRequest 的形式编码,该 发送到相机。要创建拍摄请求,您可以使用 预定义 模板, 或者,您也可以使用 TEMPLATE_MANUAL 实现完全控制。当您选择一个 因此您需要提供一个或多个输出缓冲区 请求。您只能使用拍摄时已经定义的缓冲区 会话

捕获请求使用 构建器模式 让开发者可以设置许多不同的 包括 自动曝光 自动对焦、 和 镜头光圈。 在设置字段之前,请确保特定选项 设备 CameraCharacteristics.getAvailableCaptureRequestKeys() 并且选中合适的相机,即可支持所需的值 如可用的自动曝光功能, 模式

使用模板为 SurfaceView 创建拍摄请求 专为预览而设计,无需进行任何修改, CameraDevice.TEMPLATE_PREVIEW:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureRequest.addTarget(previewSurface)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest.Builder captureRequest =
    session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequest.addTarget(previewSurface);

定义拍摄请求后,您现在可以分派 传递给相机会话:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // from CameraDevice.createCaptureRequest()

// The first null argument corresponds to the capture callback, which you
// provide if you want to retrieve frame metadata or keep track of failed capture
// requests that can indicate dropped frames; the second null argument
// corresponds to the Handler used by the asynchronous callback, which falls
// back to the current thread's looper if null
session.capture(captureRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // from CameraDevice.createCaptureRequest()

// The first null argument corresponds to the capture callback, which you
// provide if you want to retrieve frame metadata or keep track of failed
// capture
// requests that can indicate dropped frames; the second null argument
// corresponds to the Handler used by the asynchronous callback, which falls
// back to the current thread's looper if null
session.capture(captureRequest.build(), null, null);

当输出帧被放入特定的缓冲区时,捕获 回调 触发。在许多情况下,还会附加回调,如 ImageReader.OnImageAvailableListener、 在处理其中包含的帧时触发。地点为: 此时,您可以从指定的缓冲区中检索图片数据。

重复 CaptureRequest

单摄像头请求很简单,但用于显示实时 预览或视频时,它们并不是很有用。在这种情况下,您需要 连续的帧流,而不仅仅是单个帧。以下代码段 展示了如何将 重复请求 会话:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback
val captureRequest: CaptureRequest = ...  // from CameraDevice.createCaptureRequest()

// This keeps sending the capture request as frequently as possible until
// the
// session is torn down or session.stopRepeating() is called
// session.setRepeatingRequest(captureRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback
CaptureRequest captureRequest = ...;  // from CameraDevice.createCaptureRequest()

// This keeps sending the capture request as frequently as possible until the
// session is torn down or session.stopRepeating() is called
// session.setRepeatingRequest(captureRequest.build(), null, null);

重复拍摄请求会使相机设备持续拍摄 设置 CaptureRequest。Camera2 API 还可以让用户通过发送 重复CaptureRequests,如图所示 Camera2 示例 代码库。它还可以通过拍摄 使用重复连拍 CaptureRequests 的高速(慢动作)视频 如 Camera2 慢动作视频示例应用所示 。

交错 CaptureRequest

如需在重复拍摄请求处于活跃状态时发送第二个拍摄请求,请执行以下操作: 例如显示取景器并让用户拍摄照片, 停止正在进行的重复请求。而是会发出非重复捕获 请求,而重复请求会继续运行。

使用的任何输出缓冲区都需要配置为相机会话的一部分 将在首次创建会话时触发重复请求的优先级低于 单帧请求或突发请求,这些请求能让以下示例正常运行:

Kotlin

val session: CameraCaptureSession = ...  // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it
val repeatingRequest = session.device.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW)
repeatingRequest.addTarget(previewSurface)
session.setRepeatingRequest(repeatingRequest.build(), null, null)

// Some time later...

// Create the single request and dispatch it
// NOTE: This can disrupt the ongoing repeating request momentarily
val singleRequest = session.device.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE)
singleRequest.addTarget(imReaderSurface)
session.capture(singleRequest.build(), null, null)

Java

CameraCaptureSession session = ...;  // from CameraCaptureSession.StateCallback

// Create the repeating request and dispatch it
CaptureRequest.Builder repeatingRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
repeatingRequest.addTarget(previewSurface);
session.setRepeatingRequest(repeatingRequest.build(), null, null);

// Some time later...

// Create the single request and dispatch it
// NOTE: This can disrupt the ongoing repeating request momentarily
CaptureRequest.Builder singleRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
singleRequest.addTarget(imReaderSurface);
session.capture(singleRequest.build(), null, null);

但这种方法也有一个缺点: 就会发生一次性请求在下图中,如果 A 是重复项, 捕获请求,B 表示单帧捕获请求,这就是 会话会处理请求队列:

图 2. 图示:正在进行摄像头会话的请求队列

对于从 Cloud Storage 发出的最后一个重复请求, 先激活 A,再激活请求 B,下次使用 A 时 因此您可能会遇到一些跳过的帧。您有一些 采取以下措施来缓解此问题:

  • 将请求 A 中的输出目标添加到请求 B。这样,当 B 的帧已准备就绪,被复制到 A 的输出目标中。 例如,在截取视频快照以保持 稳定的帧速率在前面的代码中,您将 singleRequest.addTarget(previewSurface),然后再构建请求。

  • 使用专为此特定场景而设计的模板组合, 例如零快门延迟。