注意:本页介绍的是 Camera2 软件包。除非您的应用需要 Camera2 中的特定低级功能,否则我们建议您使用 CameraX。CameraX 和 Camera2 都支持 Android 5.0(API 级别 21)及更高版本。
一台 Android 设备可以配备多个摄像头。每个摄像头都是
CameraDevice、
和 CameraDevice 可以同时输出多个流。
这样做的一个原因是,一个视频流、连续的相机帧
(来自 CameraDevice)针对特定任务进行了优化,例如显示
取景器,而其他人则会用于拍照或录像
这些流充当处理原始帧的并行管道
每次一帧图片的效果:
并行处理表明可能存在性能限制, 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 数据流用例添加到 previewSurface 并
STILL_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
配置组合可能不起作用,从而导致出现会话
在创建请求后抛出运行时错误
性能下降该框架可保证特定产品
和请求参数的组合。有关
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 表示单帧捕获请求,这就是 会话会处理请求队列:
对于从 Cloud Storage 发出的最后一个重复请求, 先激活 A,再激活请求 B,下次使用 A 时 因此您可能会遇到一些跳过的帧。您有一些 采取以下措施来缓解此问题:
将请求 A 中的输出目标添加到请求 B。这样,当 B 的帧已准备就绪,被复制到 A 的输出目标中。 例如,在截取视频快照以保持 稳定的帧速率在前面的代码中,您将
singleRequest.addTarget(previewSurface),然后再构建请求。使用专为此特定场景而设计的模板组合, 例如零快门延迟。