16、StillCapture流程:StillCapture配置、JPEG编码、Exif信息生成、Burst模式、HDR+实现思路
各位同学,今天我们来聊聊拍照流程里最核心的一块——StillCapture。说白了,就是按下快门之后,系统到底干了些什么。
我刚开始接触Android相机时,觉得拍照不就是“咔嚓”一声吗?后来深入一看,才发现这里面的门道多着呢。从配置到编码,再到Exif信息,每一步都可能踩坑。今天我就把这些经验掰开揉碎了讲给你听。
16.1 StillCapture配置:从Request到Result
拍照的第一步,是配置CaptureRequest。你想想看,相机模组怎么知道你要拍多大的照片?要不要开闪光灯?这些都得通过Request告诉它。
核心配置项:
- 输出尺寸:通过StreamConfigurationMap获取支持的尺寸列表,一般选最大的那个
- 格式:JPEG还是RAW?JPEG是主流,RAW留给专业用户
- AE/AWB/AF模式:自动曝光、白平衡、对焦,这三兄弟得配合好
- 闪光模式:自动、开启、关闭,还有红眼消除
我个人习惯在配置时,先检查一下设备是否支持你要的尺寸。曾经有个项目,我直接写死了4000x3000,结果某款低端机不支持,拍照直接崩了。嗯,从那以后我再也不敢偷懒了。
// 典型的StillCapture配置代码
CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
requestBuilder.set(CaptureRequest.JPEG_QUALITY, (byte) 95);
requestBuilder.set(CaptureRequest.JPEG_ORIENTATION, orientation);
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
requestBuilder.addTarget(imageReader.getSurface());
CaptureRequest request = requestBuilder.build();
cameraCaptureSession.capture(request, captureCallback, backgroundHandler);
小技巧:JPEG质量设95就够了,设100反而会让文件体积暴增,画质提升几乎看不出来。这是我在做图像质量测试时发现的。
16.2 JPEG编码:硬件加速才是王道
拍完照拿到原始数据,接下来就是编码。JPEG编码其实挺吃CPU的,尤其是高像素照片。你想想看,4800万像素的RAW数据,软件编码可能要几百毫秒,这谁受得了?
所以Android推荐用硬件JPEG编码器。从Android 8.0开始,系统提供了ImageReader配合HardwareBuffer的方式,让编码直接在ISP里完成。
| 编码方式 | 耗时(典型值) | CPU占用 | 适用场景 |
|---|---|---|---|
| 软件编码(libjpeg) | 300-500ms | 高 | 低分辨率、兼容性要求高 |
| 硬件编码(ISP) | 50-100ms | 极低 | 高分辨率、连拍 |
| 混合编码 | 100-200ms | 中 | 需要自定义处理 |
我曾经在某个项目里,发现硬件编码出来的图片偏暗。排查了半天,原来是ISP的亮度映射表没配置对。所以啊,硬件虽快,但调参得跟上。
16.3 Exif信息生成:别小看这些元数据
Exif信息,说白了就是照片的“身份证”。拍这张照片时用了什么光圈?快门速度多少?GPS坐标在哪?这些都得写进去。
Android的ExifInterface类可以读写这些信息。但要注意,有些字段是拍照时就得确定的,比如方向、时间戳;有些是编码后才能填的,比如缩略图。
避坑指南:我曾经遇到过一个bug,用户拍完照旋转手机,Exif里的方向信息没更新,导致照片在相册里显示歪了。后来我改成在capture callback里重新读取传感器方向,再写入Exif,问题才解决。
// 写入Exif信息的典型流程
ExifInterface exif = new ExifInterface(jpegFile.getAbsolutePath());
exif.setAttribute(ExifInterface.TAG_ORIENTATION, String.valueOf(orientation));
exif.setAttribute(ExifInterface.TAG_DATETIME, formatDate(System.currentTimeMillis()));
exif.setAttribute(ExifInterface.TAG_MAKE, Build.MANUFACTURER);
exif.setAttribute(ExifInterface.TAG_MODEL, Build.MODEL);
exif.saveAttributes();
16.4 Burst模式:连拍不是简单的重复
Burst模式,就是连拍。但你以为只是把单拍循环执行?那可就错了。
连拍时,每一帧的曝光、对焦、白平衡都得保持一致,否则拍出来的照片会忽明忽暗。Android提供了BurstCaptureCallback,可以批量提交Request。
我记得有个项目要做30fps的连拍,结果发现帧率上不去。查了半天,原来是ImageReader的缓冲区太小,导致丢帧。后来把maxImages设成10,才稳定下来。
连拍的关键点:
- 锁定AE/AWB/AF:用
CONTROL_AE_LOCK等参数锁住 - 缓冲区要够大:ImageReader的maxImages至少设到5-10
- 回调要轻量:不要在onImageAvailable里做耗时操作
16.5 HDR+实现思路:多帧合成才是精髓
HDR+,这是Google Pixel的看家本领。说白了,就是拍多张不同曝光的照片,然后合成一张动态范围更广的图。
实现思路其实不复杂:
- 拍多帧:一般拍3-5帧,曝光时间从短到长
- 对齐:手持拍摄会有抖动,得先做图像配准
- 合成:亮部取短曝光帧,暗部取长曝光帧,中间过渡区做融合
- Tone Mapping:把高动态范围的图像映射到8bit显示
我曾经自己写过一版HDR合成算法,用的是OpenCV。效果嘛,还行,但速度太慢了,一帧要处理2秒。后来改用GPU加速,才降到200ms以内。
个人经验:HDR+最难的不是合成,而是对齐。尤其是拍夜景时,手稍微抖一下,画面就糊了。建议用光流法做对齐,精度比块匹配高不少。
嗯,以上就是StillCapture的核心流程。从配置到编码,再到Exif和连拍,每一步都有它的门道。HDR+虽然复杂,但理解了多帧合成的思路,剩下的就是工程优化了。
记住,拍照不是简单的“按快门”,而是一个系统工程。把每个环节吃透了,你才能写出稳定、高效的相机应用。
公众号:蓝海资料掘金营,微信deep3321