注意:本页介绍的是 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
的情况使设备硬件可以优化这些流,
。
// 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)
}
// 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
:
val session: CameraCaptureSession = ... // from CameraCaptureSession.StateCallback
val captureRequest = session.device.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
captureRequest.addTarget(previewSurface)
CameraCaptureSession session = ...; // from CameraCaptureSession.StateCallback
CaptureRequest.Builder captureRequest =
session.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
captureRequest.addTarget(previewSurface);
定义拍摄请求后,您现在可以分派 传递给相机会话:
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)
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
单摄像头请求很简单,但用于显示实时 预览或视频时,它们并不是很有用。在这种情况下,您需要 连续的帧流,而不仅仅是单个帧。以下代码段 展示了如何将 重复请求 会话:
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)
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
如需在重复拍摄请求处于活跃状态时发送第二个拍摄请求,请执行以下操作: 例如显示取景器并让用户拍摄照片, 停止正在进行的重复请求。而是会发出非重复捕获 请求,而重复请求会继续运行。
使用的任何输出缓冲区都需要配置为相机会话的一部分 将在首次创建会话时触发重复请求的优先级低于 单帧请求或突发请求,这些请求能让以下示例正常运行:
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)
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)
,然后再构建请求。使用专为此特定场景而设计的模板组合, 例如零快门延迟。