注意:本页介绍的是 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
配置组合可能不起作用,从而导致出现会话
在创建请求后抛出运行时错误
性能下降该框架可保证特定产品
设备、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)
,然后再构建请求。使用专为此特定场景而设计的模板组合, 例如零快门延迟。