Camera2 vs Camera1:为什么你应该升级到Camera2?
如果你还在用着老旧的Camera1 API为你的Android应用添加拍照功能,感觉就像是在用一台老式胶片相机进行数码摄影——虽然能出片,但过程繁琐,限制重重,而且总感觉错过了现代技术的便捷与强大。Camera2 API的出现,彻底改变了Android相机开发的格局,它不仅仅是一次简单的版本迭代,而是一次从“手动挡”到“自动挡”,甚至“赛车级手动模式”的跃迁。对于追求极致用户体验、需要实现高级拍摄功能(如专业模式、长曝光、多帧合成)的开发者而言,继续停留在Camera1无异于自我设限。本文将深入剖析Camera2的核心优势,并通过具体场景告诉你,升级不仅是趋势,更是构建高质量相机应用的必然选择。
1. 架构革新:从管道模型到会话请求模型
Camera1 API采用的是经典的“管道”模型。开发者需要按部就班地设置参数、启动预览、然后才能拍照。这个模型简单直观,但缺乏灵活性,整个流程是线性的、僵化的。想象一下,你只能按照“打开水龙头-接水-关水龙头”的顺序操作,中途想调整水温或水量非常困难。
Camera2 API则引入了全新的“会话-请求”模型。这个模型将相机抽象为一个提供图像数据的服务,而应用则成为向这个服务提交各种“订单”(即CaptureRequest)的客户。
1.1 核心组件解析
在Camera2的世界里,几个关键类扮演着不同角色,理解它们的关系是掌握Camera2的第一步。
- CameraManager: 相机的“总管家”。它是一个系统服务,负责枚举设备上所有可用的摄像头(前置、后置、外接),并管理它们的生命周期。你通过它来获取摄像头的能力清单,并最终打开指定的摄像头设备。
- CameraDevice: 代表一个具体的、已打开的物理摄像头。它是所有相机操作的基础,但本身不直接处理图像。它的核心职责是创建CameraCaptureSession。
- CameraCaptureSession: 这是整个模型的心脏。一个会话代表应用与相机硬件之间建立的一个专属通信通道。所有向相机发出的指令,无论是预览、拍照还是录像,都必须通过这个会话提交。一个会话可以绑定多个输出目标(Surface),这是Camera2实现多路输出的关键。
- CaptureRequest: 这就是发给相机的“订单”。它详细描述了单次图像捕获的所有要求:图像应该输出到哪个Surface、曝光时间多长、对焦模式是什么、是否开启闪光灯等等。每一个
CaptureRequest对应一帧画面的处理参数,实现了帧级精确控制。 - CaptureResult: 这是相机执行完一个
CaptureRequest后返回的“收据”。它不包含图像数据本身,但包含了该帧图像捕获时的所有元数据(Metadata),例如实际使用的曝光值、对焦状态、时间戳等。这对于实现状态反馈和高级算法至关重要。 - Surface: 图像数据的“目的地”。它是一块内存缓冲区,用于接收相机产生的图像流。
SurfaceView、TextureView、ImageReader、MediaCodec等都可以提供Surface。
下表清晰地对比了两种模型的核心差异:
| 特性维度 | Camera1 (管道模型) | Camera2 (会话-请求模型) |
|---|---|---|
| 控制粒度 | 流程级控制,参数设置相对全局。 | 帧级控制,可为每一帧设置独立参数。 |
| 数据流 | 通常为单一的预览流或拍照流。 | 支持多路输出,可同时向多个Surface发送不同格式/尺寸的数据。 |
| 流程耦合 | 预览、拍照、录像流程耦合紧密,切换繁琐。 | 流程解耦,通过提交不同的Request实现功能切换,灵活高效。 |
| 状态获取 | 回调信息有限,难以获取精确的每帧状态。 | 通过CaptureResult提供丰富的每帧元数据,状态可监控。 |
1.2 会话的威力:一次配置,多种输出
创建CameraCaptureSession时,你需要传入一个Surface列表。这个列表定义了本次会话所有可能的图像输出目的地。一旦会话创建成功,你就可以通过提交不同的CaptureRequest,自由地决定将图像数据发送到哪一个或哪几个Surface,而无需重新配置或重启会话。
例如,你可以创建一个同时绑定以下三个Surface的会话:
- 一个用于
TextureView显示的预览Surface(YUV_420_888格式)。 - 一个用于拍摄JPEG照片的
ImageReader的Surface。 - 一个用于录制高清视频的
MediaRecorder的Surface。
之后,在预览时,你提交的CaptureRequest只指向预览Surface;当用户按下拍照按钮时,你提交一个新的CaptureRequest,同时指向预览和JPEG Surface;开始录像时,则提交指向预览和录像Surface的Request。整个过程无缝切换,极大地提升了响应速度和用户体验。
提示:合理规划Surface的数量和格式。虽然Camera2支持多路输出,但受硬件和驱动限制,同时输出的流配置(分辨率、格式组合)必须是相机设备支持的。可以通过
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP查询所有支持的配置。
2. 颠覆性特性:Camera2独有的高级能力
Camera1的局限性在遇到复杂需求时暴露无遗。Camera2则打开了一扇新的大门,提供了许多过去只能在系统原生相机中见到的高级功能。
2.1 无需预览的静默拍照
在某些场景下,显示预览画面是不必要甚至不受欢迎的。例如后台定时拍照、文档扫描应用的自动捕获、或者某些安全验证场景。在Camera1中,你必须先启动预览才能拍照,这造成了资源浪费和潜在隐私问题。
Camera2彻底解决了这个问题。由于预览和拍照都被抽象为向会话提交CaptureRequest,你可以直接创建一个指向ImageReader Surface的CaptureRequest并提交,相机就会捕获一张照片并送到指定的ImageReader中,全程无需任何预览界面。
// 假设 cameraDevice 已打开,imageReader 已初始化并获取其 Surface
Surface captureSurface = imageReader.getSurface();
CaptureRequest.Builder captureBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(captureSurface);
// 设置拍照参数,如JPEG质量、闪光灯模式等
captureBuilder.set(CaptureRequest.JPEG_QUALITY, (byte) 95);
CameraCaptureSession.CaptureCallback callback = ... // 处理拍照结果
// 创建仅用于拍照的会话(也可以复用已有的预览会话)
cameraDevice.createCaptureSession(Arrays.asList(captureSurface), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
session.capture(captureBuilder.build(), callback, backgroundHandler);
}
}, backgroundHandler);
2.2 单次触发,多元捕获:同时获取不同格式和尺寸的图像
这是Camera2最强大的特性之一。在一次拍照指令中,你可以要求相机同时输出多种处理级别的图像数据。最常见的组合是:一张直接可用的高质量JPEG图片,加上一张包含原始传感器数据的RAW(DNG格式)文件。
这对专业摄影和后期处理意味着什么? JPEG文件是经过相机ISP(图像信号处理器)压缩和处理后的成品,方便直接分享。而RAW文件则保留了传感器最原始的感光数据,拥有更高的色深和动态范围,为后期调整白平衡、曝光、降噪提供了巨大的空间。Camera2让第三方应用也能实现类似专业单反的“JPEG+RAW”双格式记录。
实现的关键在于创建会话时,同时添加JPEG格式的ImageReader Surface和RAW格式的ImageReader Surface,然后在拍照的CaptureRequest中将这两个Surface都作为目标。
2.3 掌控光影:手动控制曝光时间
曝光时间(快门速度)是摄影的核心参数之一。在Camera1时代,应用层几乎无法直接干预这个参数,只能依赖自动曝光(AE)算法。Camera2则开放了CaptureRequest.SENSOR_EXPOSURE_TIME的手动控制。
长曝光的应用场景:
- 夜景拍摄:延长曝光时间可以显著提升画面亮度,捕捉更多暗部细节。
- 光绘与车流轨迹:利用长时间曝光记录光点的移动轨迹,创作创意摄影。
- 提升预览亮度:在极暗环境下,甚至可以临时延长预览帧的曝光时间,让用户能看清构图,然后再用正常或更长的曝光时间进行最终拍摄。
// 获取相机支持的最大曝光时间(单位:纳秒)
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
Range<Long> exposureRange = characteristics.get(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE);
long maxExposureNs = exposureRange.getUpper();
// 在CaptureRequest中设置一个较长的曝光时间(例如1秒)
CaptureRequest.Builder previewBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
previewBuilder.addTarget(previewSurface);
if (maxExposureNs > 1_000_000_000L) { // 支持1秒以上曝光
previewBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, 1_000_000_000L); // 1秒
previewBuilder.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_OFF); // 必须关闭自动曝光
}
// 提交这个Request用于预览,用户将看到长曝光效果
注意:手动设置曝光时间时,通常需要同时将
CONTROL_AE_MODE设置为OFF,并可能还需要手动设置感光度(ISO)。务必查询CameraCharacteristics确认设备支持哪些手动控制。
2.4 爆发式连拍与灵活3A控制
Camera2的“多次模式”(Burst)捕获使得实现高速连拍变得简单。你可以构建一个CaptureRequest列表,然后通过session.captureBurst()一次性提交。相机将以最高速率连续执行这些请求,期间不会被其他请求打断,确保了连拍的时序一致性。你甚至可以构建一个请求序列,让每一张连拍照片使用不同的曝光补偿或对焦距离,实现自动包围曝光或焦点堆栈的采集。
3A(自动对焦AF、自动曝光AE、自动白平衡AWB)的控制也更加精细化。你可以:
- 监听3A状态:通过
CaptureResult实时获取对焦是否锁定、曝光是否稳定、白平衡是否收敛等信息。 - 触发并等待3A:在拍照前,先提交一个用于触发AF/AE的预捕获Request,并等待其状态变为成功,然后再提交真正的拍照Request,这能极大提升成片率。
- 区域化控制:指定画面中特定的区域进行测光或对焦,而不是全局平均。
3. 迁移实战:从Camera1思维到Camera2思维
对于熟悉Camera1的开发者,迁移到Camera2需要一次思维转换。以下是一些关键的实践建议和常见陷阱的规避方法。
3.1 线程管理:远离主线程
Camera2的API设计是异步和非阻塞的,但很多操作本身(如打开相机、创建会话)可能涉及硬件初始化,是耗时的。绝对不要在UI主线程中直接调用Camera2的核心方法。最佳实践是使用一个专用的后台HandlerThread来处理所有相机操作。
private HandlerThread mCameraThread;
private Handler mBackgroundHandler;
private void startBackgroundThread() {
mCameraThread = new HandlerThread("CameraBackground");
mCameraThread.start();
mBackgroundHandler = new Handler(mCameraThread.getLooper());
}
private void stopBackgroundThread() {
mCameraThread.quitSafely();
try {
mCameraThread.join();
mCameraThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 打开相机时,使用后台Handler
cameraManager.openCamera(cameraId, mStateCallback, mBackgroundHandler);
3.2 状态驱动编程:拥抱回调
Camera2的操作严重依赖于回调。设备状态(CameraDevice.StateCallback)、会话状态(CameraCaptureSession.StateCallback)和捕获结果(CameraCaptureSession.CaptureCallback)都是通过回调通知。这容易导致代码逻辑分散在各个回调方法中,变得“碎片化”。
应对策略:使用状态机或RxJava等响应式编程库来管理复杂状态。 对于简单场景,也可以使用CountDownLatch或Future在后台线程中进行同步等待,将异步回调“拉直”为同步代码,提高可读性。
// 示例:等待对焦成功的简化同步方法(在后台线程执行)
private boolean waitForFocusLock(CameraCaptureSession session, CaptureRequest.Builder builder) {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicBoolean success = new AtomicBoolean(false);
builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
session.capture(builder.build(), new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
if (afState != null &&
(afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED ||
afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED)) {
success.set(true);
latch.countDown();
}
}
}, mBackgroundHandler);
try {
// 等待最多2秒
return latch.await(2, TimeUnit.SECONDS) && success.get();
} catch (InterruptedException e) {
return false;
}
}
3.3 资源生命周期管理:比以往更重要
Camera2的对象(CameraDevice, CameraCaptureSession, ImageReader)都是重要的系统资源,必须及时关闭。遵循“后申请先释放”的原则,在Activity/Fragment的onPause或onDestroy中妥善关闭它们。特别是ImageReader,在获取到Image后必须尽快调用Image.close(),否则会迅速耗尽缓冲区导致相机停顿。
4. 性能优化与最佳实践
掌握了基础功能和迁移方法后,要打造一流的相机应用,还需要关注性能与细节。
4.1 选择合适的模板
创建CaptureRequest.Builder时,需要传入一个模板类型。系统预定义了几个模板,它们预设了一组适合不同场景的参数:
TEMPLATE_PREVIEW: 适用于预览,优化了低延迟。TEMPLATE_STILL_CAPTURE: 适用于高质量静态照片拍摄,优化了图像质量。TEMPLATE_RECORD: 适用于视频录制,可能启用电子防抖等视频相关特性。TEMPLATE_VIDEO_SNAPSHOT: 适用于在录像过程中拍照。TEMPLATE_MANUAL: 最小化自动控制,为手动参数设置提供空白画布。
正确选择模板可以让你在默认状态下就获得较好的效果,然后再在其基础上进行自定义覆盖。
4.2 表面尺寸选择与比例匹配
选择预览和拍照的Surface尺寸时,需要考虑多方面因素:
- 设备支持:从
StreamConfigurationMap获取支持的大小列表。 - 屏幕比例:预览尺寸应尽可能与显示它的
TextureView/SurfaceView比例匹配,避免拉伸变形。 - 性能与功耗:更大的分辨率意味着更多的数据处理和传输开销。对于预览,通常选择1080p或720p足以平衡清晰度和性能。对于拍照,则选择你需要的最高质量尺寸。
- 格式兼容性:确保你选择的尺寸和格式组合(如YUV_420_888用于预览,JPEG用于拍照)在设备的支持列表中。
一个常见的做法是,先根据屏幕比例从支持列表中筛选出符合比例的尺寸,然后从中选择不大于显示区域且性能最优的一个作为预览尺寸。
4.3 处理图像数据:YUV、JPEG与RAW
Camera2直接提供了不同格式的图像数据,处理方式各异:
- YUV_420_888: 最常用的预览和视频处理格式。它是一种平面(Planar)或半平面(Semi-Planar)格式,需要分别处理Y(亮度)、U、V(色度)三个平面。可以使用
Image.getPlanes()获取各个平面进行图像处理或编码。 - JPEG: 压缩后的静态图像。直接从
ImageReader获取Image,然后将其ByteBuffer保存为文件即可。 - RAW: 原始传感器数据。通常保存为DNG(数字负片)格式。处理RAW需要更多步骤,包括解析传感器标定信息(
CameraCharacteristics中的COLOR_CORRECTION等),并写入DNG文件头。可以考虑使用Android的DngCreator工具类来简化这个过程。
升级到Camera2,初期学习曲线确实比Camera1陡峭,它要求开发者对相机的工作原理有更深的理解。但这份投入的回报是巨大的:你获得了对相机硬件前所未有的控制力,能够实现过去无法想象的功能,为用户提供媲美甚至超越系统相机的体验。在当今这个移动影像竞争白热化的时代,这无疑是构建差异化优势的关键。别再被过时的API束缚,是时候拥抱Camera2,释放你应用的影像潜力了。
216



被折叠的 条评论
为什么被折叠?



