注意:本页介绍的是 Camera2 软件包。除非您的应用需要 Camera2 的特定底层功能,否则我们建议您使用 CameraX。CameraX 和 Camera2 都支持 Android 5.0(API 级别 21)及更高版本。
一台 Android 设备可以有多个摄像头。每个摄像头都是一个 CameraDevice,一个 CameraDevice 可以同时输出多个数据流。
这样做的一个原因是为了针对特定任务(例如显示取景器)优化一个数据流(来自 CameraDevice 的连续相机帧),而其他数据流可能用于拍照或录制视频。这些数据流充当并行流水线,一次处理一个来自相机的原始帧:
并行处理意味着,根据 CPU、GPU 或其他处理器提供的可用处理能力,可能会存在性能限制。如果流水线无法跟上传入帧,则会开始丢弃这些帧。
每个流水线都有自己的输出格式。传入的原始数据会通过与每个流水线关联的隐式逻辑自动转换为适当的输出格式。本页代码示例中使用的 CameraDevice 是非特定的,因此您需要先枚举所有可用的摄像头,然后再继续。
您可以使用 CameraDevice 创建特定于该 CameraDevice 的 CameraCaptureSession。CameraDevice 必须使用 CameraCaptureSession 接收每个原始帧的帧配置。该配置指定了摄像头属性,例如自动对焦、光圈、效果和曝光。由于硬件限制,在任何给定时间,摄像头感应器中只有一个配置处于活动状态,该配置称为活动配置。
不过,信息流用例增强并扩展了之前使用 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 流用例添加到 previewSurface,并将 STILL_CAPTURE 流用例添加到 imReaderSurface,可让设备硬件进一步优化这些流。
// 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) }
此时,您尚未定义摄像头的有效配置。 配置会话后,您可以创建和分派拍摄请求来执行此操作。
输入在写入其缓冲区时所应用的转换由每个目标的类型决定,该类型必须为 Surface。Android 框架知道如何将有效配置中的原始图片转换为适合每个目标的格式。转换由特定 Surface 的像素格式和大小控制。
框架会尽力而为,但某些 Surface 配置组合可能无法正常运行,从而导致会话未创建、在您分派请求时抛出运行时错误或性能下降等问题。该框架为设备、Surface 和请求参数的特定组合提供保证。createCaptureSession() 文档提供了更多信息。
单个 CaptureRequest
用于每个帧的配置在 CaptureRequest 中编码,并发送到相机。如需创建拍摄请求,您可以使用其中一个预定义的模板,也可以使用 TEMPLATE_MANUAL 进行完全控制。选择模板时,您需要提供一个或多个输出缓冲区,以便与请求搭配使用。您只能使用已在您要使用的拍摄会话中定义的缓冲区。
拍摄请求使用构建器模式,并让开发者有机会设置许多不同的选项,包括自动曝光、自动对焦和镜头光圈。
在设置字段之前,请通过调用 CameraCharacteristics.getAvailableCaptureRequestKeys() 确保特定选项可用于设备,并通过检查适当的相机特性(例如可用的自动曝光模式)确保所需的值受支持。
如需使用专为预览设计的模板为 SurfaceView 创建拍摄请求,而不进行任何修改,请使用 CameraDevice.TEMPLATE_PREVIEW:
val session: CameraCaptureSession = /* ... */ // from CameraCaptureSession.StateCallback val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) captureRequest.addTarget(previewSurface)
定义拍摄请求后,您现在可以将其分派到相机拍摄会话:
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback val captureRequest: CaptureRequest.Builder = ... // 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
单摄像头请求很容易实现,但对于显示实时预览或视频,它们并不是很有用。在这种情况下,您需要接收连续的帧流,而不仅仅是单个帧。以下代码段展示了如何向会话添加重复请求:
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback val captureRequest: CaptureRequest.Builder = ... // 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 来使用相机拍摄视频,如 GitHub 上的这个 Camera2 示例代码库中所示。它还可以通过使用重复连拍 CaptureRequests 拍摄高速(慢动作)视频来呈现慢动作视频,如 GitHub 上的 Camera2 慢动作视频示例应用所示。
交错 CaptureRequest
如需在重复拍摄请求处于活动状态时发送第二个拍摄请求(例如,为了显示取景器并让用户拍摄照片),您无需停止正在进行的重复请求。而是在重复请求继续运行的同时,发出非重复捕获请求。
首次创建相机拍摄会话时,需要将所用的任何输出缓冲区配置为该会话的一部分。重复请求的优先级低于单帧或突发请求,这使得以下示例能够正常运行:
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)
不过,这种方法有一个缺点:您无法确切知道单个请求何时发生。在下图中,如果 A 是重复拍摄请求,B 是单帧拍摄请求,则会话将按以下方式处理请求队列:
在请求 B 激活之前,A 的最后一个重复请求与下一次再次使用 A 之间的延迟时间无法保证,因此您可能会遇到一些跳帧。您可以采取一些措施来缓解此问题:
将请求 A 中的输出目标添加到请求 B。这样,当 B 的帧准备就绪时,系统会将其复制到 A 的输出目标中。例如,在拍摄视频快照时,这对于保持稳定的帧速率至关重要。在前面的代码中,您在构建请求之前添加了
singleRequest.addTarget(previewSurface)。使用专为此特定场景设计的模板组合,例如零快门延迟。