4. Camera2拍照
任务说明
本案例要求基于Camera2 API实现RAW格式(DNG)图像的捕获与保存。核心目标包括:配置相机输出RAW格式的ImageReader、处理捕获回调并将RAW数据写入DNG文件、验证生成文件的格式有效性。案例将使用Android的Camera2 API和DngCreator类完成从预览到RAW捕获的完整流程。
实现方案
整体实现分为三个层次:首先通过CameraCharacteristics查询设备是否支持RAW输出(REQUEST_AVAILABLE_CAPABILITIES_RAW);其次配置ImageReader为RAW_SENSOR格式并创建对应的CaptureRequest;最后在ImageAvailableListener回调中通过DngCreator将RAW数据编码为DNG文件。为简化案例复杂度,我们聚焦于单张RAW捕获,不涉及连拍或预览流。
核心代码实现
// RawCaptureManager.java
public class RawCaptureManager {
private CameraDevice cameraDevice;
private CameraCaptureSession captureSession;
private ImageReader rawImageReader;
private CaptureRequest.Builder rawRequestBuilder;
private final File outputDir;
private final CameraCharacteristics characteristics;
private final Handler backgroundHandler;
public RawCaptureManager(Context context, CameraCharacteristics chars, Handler handler) {
this.characteristics = chars;
this.backgroundHandler = handler;
this.outputDir = new File(context.getExternalFilesDir(null), "raw_captures");
if (!outputDir.exists()) outputDir.mkdirs();
}
// 检查RAW支持
public static boolean isRawSupported(CameraCharacteristics chars) {
int[] capabilities = chars.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
if (capabilities == null) return false;
for (int cap : capabilities) {
if (cap == CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_RAW) return true;
}
return false;
}
// 配置RAW ImageReader
public void setupRawReader(Size rawSize) {
rawImageReader = ImageReader.newInstance(rawSize.getWidth(), rawSize.getHeight(),
ImageFormat.RAW_SENSOR, 2);
rawImageReader.setOnImageAvailableListener(this::onRawImageAvailable, backgroundHandler);
}
// 创建RAW捕获请求
public CaptureRequest buildRawRequest() {
rawRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
rawRequestBuilder.addTarget(rawImageReader.getSurface());
rawRequestBuilder.set(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE,
CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE_ON);
return rawRequestBuilder.build();
}
// RAW图像回调处理
private void onRawImageAvailable(ImageReader reader) {
Image image = null;
try {
image = reader.acquireLatestImage();
if (image == null) return;
// 获取必要的相机参数
TotalCaptureResult result = captureSession.getDevice().createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE).build() == null ? null : null;
// 实际项目中result来自CaptureCallback,此处简化处理
// 正确方式:在CaptureCallback中保存TotalCaptureResult
// 使用DngCreator保存DNG
File dngFile = new File(outputDir, "raw_" + System.currentTimeMillis() + ".dng");
try (DngCreator dngCreator = new DngCreator(characteristics, result)) {
dngCreator.writeImage(new FileOutputStream(dngFile), image);
Log.d("RawCapture", "DNG saved: " + dngFile.getAbsolutePath());
}
} catch (Exception e) {
Log.e("RawCapture", "Failed to save RAW", e);
} finally {
if (image != null) image.close();
}
}
// 启动捕获会话
public void startCaptureSession(List<Surface> surfaces, CameraDevice device) {
this.cameraDevice = device;
try {
device.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession session) {
captureSession = session;
try {
session.capture(buildRawRequest(), new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(CameraCaptureSession session,
CaptureRequest request, TotalCaptureResult result) {
// 保存result供DngCreator使用
lastResult = result;
}
}, backgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(CameraCaptureSession session) {
Log.e("RawCapture", "Session configuration failed");
}
}, backgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private TotalCaptureResult lastResult;
}
关键实现说明
上述代码中onRawImageAvailable回调内的result参数在实际工程中应通过CaptureCallback的onCaptureCompleted获取并保存,再传递给DngCreator。为保持代码简洁,此处使用成员变量lastResult保存最近一次捕获的元数据。DngCreator需要三个关键输入:CameraCharacteristics(提供传感器物理信息)、TotalCaptureResult(包含曝光、白平衡等参数)以及RAW图像数据。
DNG文件验证方法
// 验证DNG文件有效性的辅助代码
public static boolean validateDngFile(File dngFile) {
if (!dngFile.exists() || dngFile.length() == 0) return false;
try (InputStream is = new FileInputStream(dngFile)) {
byte[] header = new byte[8];
int read = is.read(header);
if (read != 8) return false;
// DNG文件头固定为"II*\0"(小端)或"MM\0*"(大端)
return (header[0] == 'I' && header[1] == 'I' && header[2] == 42 && header[3] == 0) ||
(header[0] == 'M' && header[1] == 'M' && header[2] == 0 && header[3] == 42);
} catch (IOException e) {
return false;
}
}
运行与验证步骤
1. 环境配置:在build.gradle中添加Camera2权限:<uses-permission android:name="android.permission.CAMERA" />,并在运行时动态申请。
2. 设备要求:需使用支持RAW输出的设备(如Pixel系列、部分旗舰机型)。可通过isRawSupported()方法预先检测。
3. 执行流程:打开相机→查询RAW尺寸(通过StreamConfigurationMap.getOutputSizes(ImageFormat.RAW_SENSOR))→创建ImageReader→配置会话→触发捕获→等待回调。
4. 验证判定:捕获完成后检查outputDir目录下是否生成.dng文件;调用validateDngFile()确认文件头正确;使用支持DNG的图片查看器(如Photoshop、Lightroom)打开文件,确认图像内容与预览场景一致且无色彩异常。
预期输出示例
// 文件头验证通过时的日志输出
D/RawCapture: DNG saved: /storage/emulated/0/Android/data/com.example.rawcapture/files/raw_captures/raw_1712345678901.dng
D/RawCapture: DNG validation passed: true
以上输出为代码逻辑可推出的确定性结果,实际文件路径和文件名中的时间戳会随运行时刻变化。