目录
  1. 1. 一、Camera2 核心管线
    1. 1.1. 1.1 管线五步模型
    2. 1.2. 1.2 打开相机的完整流程
    3. 1.3. 1.3 创建 CaptureSession 并开始预览
    4. 1.4. 1.4 拍照(Take Picture)
    5. 1.5. 1.5 各种 CaptureRequest 模板
  2. 2. 二、手动控制 —— 曝光、ISO、焦距、白平衡
    1. 2.1. 2.1 检查手动控制能力
    2. 2.2. 2.2 手动曝光控制
    3. 2.3. 2.3 手动对焦
    4. 2.4. 2.4 手动白平衡
    5. 2.5. 2.5 手动曝光补偿(在自动模式下微调)
    6. 2.6. 2.6 锁定 3A
  3. 3. 三、RAW 图像捕获
    1. 3.1. 3.1 RAW_SENSOR 格式
    2. 3.2. 3.2 RAW Image 数据解析
    3. 3.3. 3.3 将 RAW 保存为 DNG
    4. 3.4. 3.4 RAW 应用场景:手动 HDR
  4. 4. 四、高速连拍(Burst Capture)与高帧率(HFR)
    1. 4.1. 4.1 高速连拍
    2. 4.2. 4.2 高帧率录制(HFR, High Frame Rate)
  5. 5. 五、多摄像头(Multi-Camera)
    1. 5.1. 5.1 逻辑相机与物理相机
    2. 5.2. 5.2 为物理相机创建独立流
    3. 5.3. 5.3 双摄同时预览(真正的多流)
  6. 6. 六、CameraX —— Jetpack 相机封装
    1. 6.1. 6.1 基本架构
    2. 6.2. 6.2 ImageAnalysis 的背压策略
    3. 6.3. 6.3 CameraX 的手动控制
  7. 7. 七、YUV_420_888 数据访问
    1. 7.1. 7.1 YUV_420_888 格式
    2. 7.2. 7.2 正确读取 YUV_420_888
    3. 7.3. 7.3 YUV_420_888 的最优读取模式
    4. 7.4. 7.4 使用 RenderScript / GPU 加速 YUV 转换
  8. 8. 八、AHardwareBuffer 零拷贝
    1. 8.1. 8.1 什么是 AHardwareBuffer
    2. 8.2. 8.2 AHardwareBuffer 到 OpenGL 纹理(C++ NDK)
    3. 8.3. 8.3 AHardwareBuffer 零拷贝管线
  9. 9. 九、Camera2 常见问题与解决方案
    1. 9.1. 9.1 预览拉伸/变形
    2. 9.2. 9.2 3A 状态管理
    3. 9.3. 9.3 权限处理
    4. 9.4. 9.4 方向处理
  10. 10. 十、总结
【音视频、图像处理技术】相机开发进阶技术

Android 相机开发从 Camera API(API 1)到 Camera2 API(API 21),再到 CameraX Jetpack 库,经历了从简单易用到高度可控再到简洁封装的演进。Camera2 提供了完整的手动控制能力(曝光时间、ISO、焦距、白平衡)、RAW 图像捕获、高速连拍和多摄像头支持,是专业相机应用的基础。CameraX 则在 Camera2 之上提供了生命周期感知的简化 API。本文从 Camera2 核心管线、手动控制、RAW 捕获、高速连拍、多摄像头、CameraX 核心用例、YUV_420_888 数据访问、AHardwareBuffer 零拷贝等角度系统讲解 Android 相机进阶开发。

一、Camera2 核心管线

1.1 管线五步模型

Camera2 的架构遵循严格的管线(pipeline)模型,每一步都有明确的生命周期和状态转换:

CameraManager → CameraDevice → CameraCaptureSession → CaptureRequest → CaptureResult
│ │ │ │ │
│ getCamera │ createCapture │ setRepeating │ build() │ get()
│ IdList() │ Session() │ Request() │ │
▼ ▼ ▼ ▼ ▼
枚举相机 打开指定相机 创建会话绑定 发送拍照 返回结果
(相机ID) (状态回调) Surface 请求 (元数据)

每一步详细说明:

  1. CameraManager:系统服务,枚举所有相机设备,获取相机特性(CameraCharacteristics)。
  2. CameraDevice:代表已打开的物理或逻辑相机,是相机操作的入口。
  3. CameraCaptureSession:将 CaptureRequest 的流水线输出绑定到一组 Surface(预览、拍照、录像等)。
  4. CaptureRequest:单次/重复拍照请求,包含所有控制参数(曝光、焦点、ISO、白平衡等)。
  5. CaptureResult:每次拍照返回的元数据(时间戳、焦距状态、自动曝光状态等)。

1.2 打开相机的完整流程

public class Camera2Manager {
private CameraManager mCameraManager;
private CameraDevice mCameraDevice;
private CameraCaptureSession mCaptureSession;
private String mCameraId;

// 1. 获取 CameraManager
void init(Context context) {
mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
}

// 2. 枚举相机并选择
String selectCamera() throws CameraAccessException {
for (String cameraId : mCameraManager.getCameraIdList()) {
CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(cameraId);
Integer facing = chars.get(CameraCharacteristics.LENS_FACING);
if (facing != null && facing == CameraCharacteristics.LENS_FACING_BACK) {
mCameraId = cameraId;
return cameraId;
}
}
return null;
}

// 3. 打开相机
void openCamera() throws CameraAccessException {
mCameraManager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
}

private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
mCameraDevice = camera;
createCaptureSession();
}

@Override
public void onDisconnected(@NonNull CameraDevice camera) {
camera.close();
mCameraDevice = null;
}

@Override
public void onError(@NonNull CameraDevice camera, int error) {
camera.close();
mCameraDevice = null;
Log.e(TAG, "Camera error: " + error);
}
};
}

1.3 创建 CaptureSession 并开始预览

void createCaptureSession() {
// 准备输出 Surface 列表
List<Surface> surfaces = new ArrayList<>();

// Surface 1: 预览(TextureView 或 SurfaceView)
SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
surfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
Surface previewSurface = new Surface(surfaceTexture);
surfaces.add(previewSurface);

// Surface 2: 拍照(ImageReader)
mImageReader = ImageReader.newInstance(mPhotoSize.getWidth(), mPhotoSize.getHeight(),
ImageFormat.JPEG, 2); // maxImages=2 表示缓冲两张图片
mImageReader.setOnImageAvailableListener(mImageAvailableListener, mBackgroundHandler);
surfaces.add(mImageReader.getSurface());

try {
mCameraDevice.createCaptureSession(surfaces,
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mCaptureSession = session;
startPreview();
}

@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
Log.e(TAG, "CaptureSession 配置失败");
}
}, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

void startPreview() {
try {
CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW);
builder.addTarget(mPreviewSurface);
// 设置自动对焦、自动曝光、自动白平衡
builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);
builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
builder.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO);

mCaptureSession.setRepeatingRequest(builder.build(), mCaptureCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

1.4 拍照(Take Picture)

void takePicture() {
try {
CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE);
builder.addTarget(mImageReader.getSurface());

// 继承预览时的 3A 设置
builder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 设置 JPEG 方向和 GPS 位置
builder.set(CaptureRequest.JPEG_ORIENTATION, getJpegOrientation());
builder.set(CaptureRequest.JPEG_GPS_LOCATION, getGpsLocation());

mCaptureSession.capture(builder.build(), mCaptureCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

1.5 各种 CaptureRequest 模板

Camera2 提供了多种预设模板,适应不同场景:

模板 用途
TEMPLATE_PREVIEW 相机预览(默认 30fps)
TEMPLATE_STILL_CAPTURE 高质量静态拍照
TEMPLATE_RECORD 视频录制(稳定帧率)
TEMPLATE_VIDEO_SNAPSHOT 录制中的快照
TEMPLATE_ZERO_SHUTTER_LAG 零延迟拍照(用预览帧做输入)
TEMPLATE_MANUAL 全手动控制

二、手动控制 —— 曝光、ISO、焦距、白平衡

Camera2 最强大的特性之一是支持完整的手动控制,让拍照行为如同专业相机。要使用手动控制,必须首先将 CONTROL_MODE 设置为 CONTROL_MODE_OFF_KEEP_STATE 或直接关闭自动 3A。

2.1 检查手动控制能力

在设置手动参数前必须查询设备是否支持:

void checkManualCapabilities(CameraCharacteristics chars) {
int[] capabilities = chars.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
boolean manualSensor = false;
boolean manualPostProcessing = false;

for (int cap : capabilities) {
if (cap == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR) {
manualSensor = true;
}
if (cap == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING) {
manualPostProcessing = true;
}
}
Log.d(TAG, "Manual Sensor: " + manualSensor + ", Manual PP: " + manualPostProcessing);

if (manualSensor) {
// 查询曝光时间范围
Range<Long> exposureRange = chars.get(
CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE);
// 典型值: [100000 ns (1/10000s), 10000000000 ns (10s)]

// 查询 ISO 范围
Range<Integer> isoRange = chars.get(
CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE);
// 典型值: [100, 3200] 或更高

// 查询最大模拟 ISO(超过此值为数字增益)
Integer maxAnalogIso = chars.get(
CameraCharacteristics.SENSOR_MAX_ANALOG_SENSITIVITY);

// 查询光圈
float aperture = chars.get(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES)[0];
// 典型值: f/1.8
}
}

2.2 手动曝光控制

曝光由两个参数决定:曝光时间(exposure time, 单位纳秒 ns)和 ISO(感光度)。Camera2 中的关系:

EV(曝光值)∝ exposure_time × ISO
void setManualExposure(CaptureRequest.Builder builder, long exposureTimeNs, int iso) {
// 1. 关闭自动曝光
builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);

// 2. 设置曝光时间(单位:纳秒)
// 1/30s = 33,333,333 ns
// 1/1000s = 1,000,000 ns
builder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, exposureTimeNs);

// 3. 设置 ISO(感光度)
builder.set(CaptureRequest.SENSOR_SENSITIVITY, iso);

// 4. 可选:设置帧时长(帧率的倒数)
// 30fps → 33,333,333 ns
long frameDuration = 33_333_333L;
builder.set(CaptureRequest.SENSOR_FRAME_DURATION, frameDuration);
}

2.3 手动对焦

void setManualFocus(CaptureRequest.Builder builder, float focusDistance) {
// focusDistance 单位:屈光度(diopter),即 1/米
// 0 表示无限远,正值表示近距离
// 查询有效焦距范围
// Range<Float> focusRange = chars.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE);

builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF);
builder.set(CaptureRequest.LENS_FOCUS_DISTANCE, focusDistance);

// 如果设备不支持 LENS_FOCUS_DISTANCE,可以控制 LENS_FOCUS_DISTANCE_CALIBRATION
// CALIBRATION_UNCALIBRATED = 0, CALIBRATION_APPROXIMATE = 1, CALIBRATION_CALIBRATED = 2
}

2.4 手动白平衡

void setManualWhiteBalance(CaptureRequest.Builder builder, 
float rGain, float gGainEven, float gGainOdd, float bGain) {
// 关闭自动白平衡
builder.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_OFF);

// 手动设置各通道增益(此接口可能因设备而异)
// 创建 ColorSpaceTransform(将传感器 RGB 转到 sRGB 的色彩矩阵)
// 这需要根据传感器特性来设置
ColorSpaceTransform transform = new ColorSpaceTransform(new int[]{
Rational(rGain, 1), Rational(gGainEven, 1), Rational(bGain, 1),
Rational(rGain, 1), Rational(gGainEven, 1), Rational(bGain, 1),
Rational(rGain, 1), Rational(gGainEven, 1), Rational(bGain, 1),
});
builder.set(CaptureRequest.COLOR_CORRECTION_TRANSFORM, transform);

// 更直接的方式:设置色温
// 通过白平衡增益 R/G 和 B/G 来控制,但 Camera2 没有直接的色温参数
// 需要自行查表(色温→RGB增益)转换
}

2.5 手动曝光补偿(在自动模式下微调)

void setExposureCompensation(CaptureRequest.Builder builder, int evSteps) {
// 在 AE 开启时使用,范围通常 [-4, 4]
// 查询支持范围:
// Range<Integer> evRange = chars.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE);
// Rational evStep = chars.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_STEP);

builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
builder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, evSteps);
// evSteps=1 → +1 EV(曝光量翻倍)
// evSteps=-1 → -1 EV(曝光量减半)
}

2.6 锁定 3A

在拍摄前锁定 3A 可避免画质波动,特别是拍夜景或特定场景:

void lock3A(CaptureRequest.Builder builder) {
// AE 锁:锁定当前曝光值
builder.set(CaptureRequest.CONTROL_AE_LOCK, true);

// AWB 锁:锁定当前白平衡
builder.set(CaptureRequest.CONTROL_AWB_LOCK, true);

// AF 触发器:先触发自动对焦,再锁定
builder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CaptureRequest.CONTROL_AF_TRIGGER_START);
// 等待 AF_STATE_FOCUSED_LOCKED 后再发送拍照请求
}

三、RAW 图像捕获

3.1 RAW_SENSOR 格式

RAW 图像是图像传感器输出的原始未经处理的数据,通常是 Bayer 排列(一种颜色滤波阵列,每个像素只记录 R、G、B 中的一种,按特定模式排布)。RAW 的优势在于:

  • 高动态范围:10-14 位深度 vs JPEG 的 8 位。
  • 无损后期:可在后期任意调整白平衡、曝光、去噪、色调。
  • 科学/计算摄影:多帧合成、HDR+、夜景模式等算法需要 RAW 作为输入。
void setupRawCapture(CameraCharacteristics chars) {
// 1. 检查是否支持 RAW
int[] availableFormats = chars.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
.getOutputFormats();

boolean supportsRaw = false;
for (int format : availableFormats) {
if (format == ImageFormat.RAW_SENSOR) {
supportsRaw = true;
break;
}
}

if (!supportsRaw) {
Log.w(TAG, "设备不支持 RAW 捕获");
return;
}

// 2. 查询 Bayer 模式
int bayerPattern = chars.get(CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT);
// RGGB = 0, GRBG = 1, GBRG = 2, BGGR = 3 (最常见)
String[] bayerNames = {"RGGB", "GRBG", "GBRG", "BGGR"};
Log.d(TAG, "Bayer pattern: " + bayerNames[bayerPattern]);

// 3. 查询白电平(传感器饱和值)
Integer whiteLevel = chars.get(CameraCharacteristics.SENSOR_INFO_WHITE_LEVEL);
Log.d(TAG, "White level: " + whiteLevel);
// 典型值: 1023 (10位) 或 4095 (12位)

// 4. 查询黑电平(随时间/ISO 变化,通常从 CaptureResult 获取)
// 黑电平屏蔽值在 SENSOR_DYNAMIC_BLACK_LEVEL 中

// 5. 创建 RAW ImageReader
Size rawSize = chars.get(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE);
ImageReader rawReader = ImageReader.newInstance(
rawSize.getWidth(), rawSize.getHeight(),
ImageFormat.RAW_SENSOR, 1);
rawReader.setOnImageAvailableListener(rawListener, mBackgroundHandler);
}

3.2 RAW Image 数据解析

private final ImageReader.OnImageAvailableListener rawListener = reader -> {
Image image = reader.acquireLatestImage();
if (image == null) return;

Image.Plane[] planes = image.getPlanes();
// RAW_SENSOR 通常只有 1 个 plane(部分设备可能有 2 个,第二个是 metadata)
Image.Plane rawPlane = planes[0];

ByteBuffer buffer = rawPlane.getBuffer();
int pixelStride = rawPlane.getPixelStride(); // RAW 中通常为 1
int rowStride = rawPlane.getRowStride(); // 每行的字节数(含 padding)

// 创建一个足够容纳一行数据的 byte 数组
byte[] rowData = new byte[rowStride];

// 解析 Bayer 数据(以 BGGR 8 位为例)
int width = image.getWidth();
int height = image.getHeight();

// 逐行读取处理
for (int y = 0; y < height; y++) {
buffer.position(y * rowStride);
buffer.get(rowData, 0, rowStride);
// 注意:只处理前 width 个字节(忽略 padding)
// 根据 Bayer 模式分离 R, G1, G2, B 通道
}

image.close();
};

3.3 将 RAW 保存为 DNG

DNG(Digital Negative)是 Adobe 的开放 RAW 格式,Android 提供 DngCreator 类来将 Camera2 的 RAW 数据和元数据封装为 DNG 文件:

void saveRawAsDng(Image rawImage, CaptureResult captureResult, CameraCharacteristics chars) {
try {
DngCreator dngCreator = new DngCreator(chars, captureResult);

// 设置 DNG 参数
dngCreator.setOrientation(Orientation.ORIENTATION_0);
// 设置描述信息(可选)
dngCreator.setDescription("Captured by MyCamera");

// 写入文件
File dngFile = new File(getExternalFilesDir(null),
"raw_" + System.currentTimeMillis() + ".dng");
FileOutputStream fos = new FileOutputStream(dngFile);

dngCreator.writeImage(fos, rawImage);

fos.close();
dngCreator.close();
rawImage.close();

Log.d(TAG, "DNG 保存成功: " + dngFile.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
}

3.4 RAW 应用场景:手动 HDR

// 伪代码:拍摄三张不同 EV 的 RAW,合成 HDR
void captureHDREvBracketing() {
// 获取当前测光结果
long baseExposure = getCurrentExposureTime();
int baseIso = getCurrentIso();

// EV -2: 曝光时间减半两次
captureRaw(baseExposure / 4, baseIso, "ev-2.dng");
// EV 0
captureRaw(baseExposure, baseIso, "ev0.dng");
// EV +2: 曝光时间翻倍两次
captureRaw(baseExposure * 4, baseIso, "ev+2.dng");

// 后续在桌面/云端用这些 RAW 合成 HDR
}

四、高速连拍(Burst Capture)与高帧率(HFR)

4.1 高速连拍

Camera2 支持连续快速的 captureBurst,用于运动摄影、连拍合成等:

void captureBurst() {
List<CaptureRequest> requests = new ArrayList<>();

try {
for (int i = 0; i < 10; i++) {
CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE);
builder.addTarget(mImageReader.getSurface());
builder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);

// 调整序号(元数据中携带,便于后期排序)
// 注意:并非所有设备都支持
requests.add(builder.build());
}

// 高速连续发送请求
mCaptureSession.captureBurst(requests, mCaptureCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

4.2 高帧率录制(HFR, High Frame Rate)

用于慢动作视频(120fps、240fps 等)。Camera2 通过 CONFIG_HIGH_SPEED 会话类型实现:

void setupHighSpeedRecording() {
try {
// 1. 查询设备支持的高速 FPS 配置
for (String cameraId : mCameraManager.getCameraIdList()) {
CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(cameraId);

StreamConfigurationMap map = chars.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

// 检查是否支持高速录制
Size[] highSpeedSizes = map.getHighSpeedVideoSizes();
if (highSpeedSizes != null && highSpeedSizes.length > 0) {
for (Size size : highSpeedSizes) {
Range<Integer>[] fpsRanges = map.getHighSpeedVideoFpsRangesFor(size);
for (Range<Integer> fpsRange : fpsRanges) {
Log.d(TAG, "HFR: " + size + " @ " + fpsRange);
// 例如: 1920x1080 @ [120, 120] (120fps)
// 例如: 1280x720 @ [240, 240] (240fps)
}
}
}
}

// 2. 创建高速会话
List<Size> highSpeedSizes = new ArrayList<>();
highSpeedSizes.add(new Size(1920, 1080));
highSpeedSizes.add(new Size(1280, 720));

// 创建 MediaRecorder 或 MediaCodec 的 Surface
Surface recorderSurface = mMediaRecorder.getSurface();
Surface previewSurface = mPreviewSurface;

// 创建高速捕获会话
mCameraDevice.createConstrainedHighSpeedCaptureSession(
Arrays.asList(previewSurface, recorderSurface),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mCaptureSession = session;
// 构建高速请求列表(必须批处理)
createHighSpeedRequestList();
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {}
},
mBackgroundHandler
);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

void createHighSpeedRequestList() {
try {
List<CaptureRequest> requests = new ArrayList<>();

// 高速模式下,每批次至少 8 个请求(具体由设备决定)
// 且所有请求的 targets 必须相同
CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_RECORD);
builder.addTarget(mRecorderSurface);
builder.addTarget(mPreviewSurface);

for (int i = 0; i < 8; i++) {
requests.add(builder.build()); // 每个请求完全相同或略有调整
}

mCaptureSession.setRepeatingBurst(requests, null, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

五、多摄像头(Multi-Camera)

Android 9(API 28)引入了多摄像头 API,允许同时从两个或多个物理摄像头发送数据流,用于实现景深、光学变焦、双摄虚化等效果。

5.1 逻辑相机与物理相机

  • 逻辑相机(Logical Camera):对外呈现为一个相机设备,但内部包含多个物理子相机。
  • 物理相机(Physical Camera):实际的硬件传感器。
void checkMultiCameraSupport() throws CameraAccessException {
for (String cameraId : mCameraManager.getCameraIdList()) {
CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(cameraId);

// 获取物理相机 ID 列表(空列表表示单摄)
Set<String> physicalIds = chars.getPhysicalCameraIds();
if (physicalIds != null && !physicalIds.isEmpty()) {
Log.d(TAG, "逻辑相机 " + cameraId + " 包含物理相机: " + physicalIds);
// 例如: "0" → ["0", "2"] 表示逻辑相机 0 由物理相机 0 和 2 组成
}

// 检查是否支持逻辑多摄像头(API 28+)
int[] capabilities = chars.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
boolean logicalMultiCamera = false;
for (int cap : capabilities) {
if (cap == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA) {
logicalMultiCamera = true;
break;
}
}
}
}

5.2 为物理相机创建独立流

在某些场景下(如:主摄取景 + 长焦拍照),可以为不同的物理相机创建独立的 CaptureRequest:

void createPhysicalStreamForZoom() {
try {
String logicalId = "0"; // 逻辑相机 ID
String physicalTeleId = "2"; // 长焦物理相机 ID

// 为主摄创建预览流
CaptureRequest.Builder previewBuilder = mCameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW);
previewBuilder.addTarget(mPreviewSurface);

// 为长焦物理相机创建拍照流
ImageReader teleReader = ImageReader.newInstance(1920, 1080, ImageFormat.JPEG, 1);
CaptureRequest.Builder teleBuilder = mCameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_STILL_CAPTURE,
new HashSet<>(Collections.singletonList(physicalTeleId))); // 指定物理相机
teleBuilder.addTarget(teleReader.getSurface());

// 设置物理相机的变焦 (ZOOM_RATIO)
teleBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, 2.0f);

// 同时发送两个请求
mCaptureSession.captureBurst(
Arrays.asList(previewBuilder.build(), teleBuilder.build()),
mCaptureCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}

5.3 双摄同时预览(真正的多流)

Android 11(API 30)引入了 isSessionConfigurationSupported,用于在创建会话前验证两个相机是否可以被同时打开:

@RequiresApi(api = Build.VERSION_CODES.R)
void checkConcurrentCameraSupport() throws CameraAccessException {
Set<String> cameraIds = new HashSet<>();
cameraIds.add("0"); // 广角
cameraIds.add("1"); // 超广角

// 查询并发会话支持
boolean supported = mCameraManager.isConcurrentSessionConfigurationSupported(
new CameraManager.ConcurrentCameraIds(cameraIds));

Log.d(TAG, "双摄同时开启: " + (supported ? "支持" : "不支持"));
}

六、CameraX —— Jetpack 相机封装

CameraX 是 Android Jetpack 的相机库,它在 Camera2 之上提供了生命周期感知的简化 API。适用于不需要极端手动控制的场景(扫码、拍照、预览等)。

6.1 基本架构

CameraX 的核心概念:

  • Preview:取景器预览(绑定到 PreviewView)。
  • ImageCapture:拍照(JPEG 输出)。
  • ImageAnalysis:帧分析(YUV_420_888 或 RGBA,用于计算机视觉)。
  • VideoCapture:视频录制(CameraX 1.3+)。
  • ProcessCameraProvider:绑定用例到生命周期。
// build.gradle
dependencies {
def camerax_version = "1.4.0"
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
implementation "androidx.camera:camera-view:${camerax_version}"
}

// 在 Activity 中
public class CameraXActivity extends AppCompatActivity {
private PreviewView mPreviewView;
private ImageCapture mImageCapture;
private ImageAnalysis mImageAnalysis;
private ProcessCameraProvider mCameraProvider;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera_x);
mPreviewView = findViewById(R.id.previewView);

ListenableFuture<ProcessCameraProvider> future =
ProcessCameraProvider.getInstance(this);
future.addListener(() -> {
try {
mCameraProvider = future.get();
bindCameraUseCases();
} catch (Exception e) {
e.printStackTrace();
}
}, ContextCompat.getMainExecutor(this));
}

void bindCameraUseCases() {
// 1. 选择相机(默认后置)
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build();

// 2. 预览用例
Preview preview = new Preview.Builder()
.setTargetResolution(new Size(1080, 1920))
.build();
preview.setSurfaceProvider(mPreviewView.getSurfaceProvider());

// 3. 拍照用例
mImageCapture = new ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
.setTargetResolution(new Size(4000, 3000))
.setFlashMode(ImageCapture.FLASH_MODE_AUTO)
.build();

// 4. 分析用例(实时帧处理)
mImageAnalysis = new ImageAnalysis.Builder()
.setTargetResolution(new Size(640, 480))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
.build();
mImageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this), imageProxy -> {
// 处理每一帧
processFrame(imageProxy);
imageProxy.close(); // 必须 close,否则阻塞后续帧
});

// 5. 绑定到生命周期
mCameraProvider.unbindAll();
mCameraProvider.bindToLifecycle(
this, cameraSelector, preview, mImageCapture, mImageAnalysis);
}

void takePhoto() {
ImageCapture.OutputFileOptions outputOptions =
new ImageCapture.OutputFileOptions.Builder(
new File(getExternalFilesDir(null),
"photo_" + System.currentTimeMillis() + ".jpg"))
.build();
mImageCapture.takePicture(outputOptions,
ContextCompat.getMainExecutor(this),
new ImageCapture.OnImageSavedCallback() {
@Override
public void onImageSaved(@NonNull ImageCapture.OutputFileResults output) {
Log.d(TAG, "照片已保存: " + output.getSavedUri());
}
@Override
public void onError(@NonNull ImageCaptureException exception) {
Log.e(TAG, "拍照失败", exception);
}
});
}
}

6.2 ImageAnalysis 的背压策略

ImageAnalysis 通过 setBackpressureStrategy 控制当帧处理速度跟不上帧到达速度时的行为:

策略 行为
STRATEGY_KEEP_ONLY_LATEST 丢弃未处理的旧帧,只交付最新一帧
STRATEGY_BLOCK_PRODUCER 阻塞相机输出直到前一帧处理完毕(保证不丢帧但不推荐实时场景)

6.3 CameraX 的手动控制

CameraX 通过 Camera2Interop.Extender 桥接 Camera2 的手动控制能力:

void setManualExposureCameraX() {
Preview preview = new Preview.Builder()
.build();

// 通过 Camera2Interop 扩展访问底层 Camera2 API
new Camera2Interop.Extender<>(preview)
.setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_OFF)
.setCaptureRequestOption(CaptureRequest.SENSOR_EXPOSURE_TIME, 10_000_000L) // 10ms
.setCaptureRequestOption(CaptureRequest.SENSOR_SENSITIVITY, 800);
}

七、YUV_420_888 数据访问

7.1 YUV_420_888 格式

ImageFormat.YUV_420_888 是 Android 相机中最通用的格式之一,兼顾效率和灵活性。它是一种多平面 YUV 格式:

  • Y 平面(亮度):全分辨率(width x height),每个像素一个字节。
  • U 平面(色度蓝):半分辨率(width/2 x height/2),每 4 个像素共享一个 U 值。
  • V 平面(色度红):半分辨率(width/2 x height/2),每 4 个像素共享一个 V 值。

7.2 正确读取 YUV_420_888

YUV_420_888 最棘手的地方在于 pixelStride(像素步幅)和 rowStride(行步幅)可能与直觉不同:

void readYUV420888(Image image) {
Image.Plane[] planes = image.getPlanes();

// Y 平面
ByteBuffer yBuffer = planes[0].getBuffer();
int yRowStride = planes[0].getRowStride();
int yPixelStride = planes[0].getPixelStride(); // 通常 = 1

// U 平面
ByteBuffer uBuffer = planes[1].getBuffer();
int uRowStride = planes[1].getRowStride();
int uPixelStride = planes[1].getPixelStride(); // 可能是 1 或 2

// V 平面
ByteBuffer vBuffer = planes[2].getBuffer();
int vRowStride = planes[2].getRowStride();
int vPixelStride = planes[2].getPixelStride(); // 可能是 1 或 2

int width = image.getWidth();
int height = image.getHeight();

// 逐像素读取(转为 RGB 或直接处理 YUV)
byte[] yRow = new byte[yRowStride];
byte[] uRow = new byte[uRowStride];
byte[] vRow = new byte[vRowStride];

for (int row = 0; row < height; row++) {
// 读取 Y 行
yBuffer.position(row * yRowStride);
yBuffer.get(yRow, 0, yRowStride);

// 读取 U/V 行(半高度)
int uvRowIndex = row / 2;
uBuffer.position(uvRowIndex * uRowStride);
uBuffer.get(uRow, 0, uRowStride);
vBuffer.position(uvRowIndex * vRowStride);
vBuffer.get(vRow, 0, vRowStride);

for (int col = 0; col < width; col++) {
// 注意:使用 pixelStride 而非直接 index
int y = yRow[col * yPixelStride] & 0xFF;
int uvColIndex = (col / 2) * uPixelStride;
int u = uRow[uvColIndex] & 0xFF;
int v = vRow[uvColIndex] & 0xFF;

// YUV → RGB 转换(ITU-R BT.601)
int y1 = Math.max(0, y - 16);
int r = (int)(1.164 * y1 + 1.596 * (v - 128));
int g = (int)(1.164 * y1 - 0.813 * (v - 128) - 0.391 * (u - 128));
int b = (int)(1.164 * y1 + 2.018 * (u - 128));

r = clamp(r, 0, 255);
g = clamp(g, 0, 255);
b = clamp(b, 0, 255);
}
}
}

int clamp(int value, int min, int max) {
return Math.max(min, Math.min(max, value));
}

7.3 YUV_420_888 的最优读取模式

根据 pixelStride 的不同,有三种读取模式:

void efficientYUVRead(Image image, byte[] outputNV21) {
Image.Plane[] planes = image.getPlanes();

int yPixelStride = planes[0].getPixelStride();
int uvPixelStride = planes[1].getPixelStride();

if (yPixelStride == 1 && uvPixelStride == 2) {
// 模式 A: 标准 NV12/NV21,半平面交错
// U 和 V 交替存储: U0, V0, U1, V1, ...
// 可以用 memcpy 批量复制(最快)
copyNV12ToNV21(planes, outputNV21);
} else if (yPixelStride == 1 && uvPixelStride == 1) {
// 模式 B: 分离平面,每个 U/V 独立连续存储
// 需要手动交叉复制
copyPlanarToNV21(planes, outputNV21);
} else {
// 模式 C: 含 padding,必须逐像素处理
copyWithSubsampling(planes, outputNV21);
}
}

// NV12 转 NV21(交换 U 和 V 平面)
void copyNV12ToNV21(Image.Plane[] planes, byte[] output) {
int width = image.getWidth();
int height = image.getHeight();

// 复制 Y
ByteBuffer yBuf = planes[0].getBuffer();
yBuf.get(output, 0, width * height);

// 交换 UV → VU
ByteBuffer uBuf = planes[1].getBuffer();
ByteBuffer vBuf = planes[2].getBuffer();

int uvLen = width * height / 4;
byte[] uData = new byte[uvLen];
byte[] vData = new byte[uvLen];

uBuf.get(uData, 0, uvLen);
vBuf.get(vData, 0, uvLen);

// 交错写入 VU(NV21 格式)
int offset = width * height;
for (int i = 0; i < uvLen; i++) {
output[offset + i * 2] = vData[i]; // V
output[offset + i * 2 + 1] = uData[i]; // U
}
}

7.4 使用 RenderScript / GPU 加速 YUV 转换

在需要实时处理时,应避免在 CPU 上逐像素转换 YUV。更好的选择:

// 方案 1:使用 RenderScript YUV → RGBA(Android 5.0-11,已废弃但仍在用)
// ScriptIntrinsicYuvToRGB yuvToRgb = ScriptIntrinsicYuvToRGB.create(rs, Element.RGBA_8888(rs));
// yuvToRgb.setInput(yuvAllocation);
// yuvToRgb.forEach(rgbaAllocation);

// 方案 2:使用 OpenGL ES shader(推荐,跨平台)
// Fragment Shader 中用 samplerExternalOES 直接采样 YUV 纹理
// 硬件采样器自动处理 YUV→RGB 转换

// 方案 3:直接使用 Image 的 YUV_420_888 → RGBA_8888 转换
// 通过 ImageWriter 将 YUV 写入,ImageReader 以 RGBA 读出

八、AHardwareBuffer 零拷贝

8.1 什么是 AHardwareBuffer

AHardwareBuffer 是 Android 8.0(API 26)引入的跨进程共享内存机制。它允许不同组件(Camera、GPU、CPU、MediaCodec、NN API)共享同一块物理内存,无需拷贝。

// 使用 HardwareBuffer 创建 ImageReader(API 33+)
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
void setupHardwareBufferReader() {
HardwareBufferUsage usage = new HardwareBufferUsage(
HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE |
HardwareBuffer.USAGE_CPU_READ_OFTEN);

ImageReader reader = ImageReader.newInstance(
width, height,
ImageFormat.YUV_420_888,
4, // maxImages
usage.toLong() // HardwareBuffer 使用标志
);

reader.setOnImageAvailableListener(imageReader -> {
Image image = imageReader.acquireNextImage();
// 获取底层 HardwareBuffer
HardwareBuffer hwb = image.getHardwareBuffer();
if (hwb != null) {
// 零拷贝传递给 GPU(OpenGL / Vulkan)
// 或传递给 Neural Networks API
// 或传递给 MediaCodec

// 使用完毕后关闭(不 close 可能导致缓冲区泄漏)
hwb.close();
}
image.close();
}, mBackgroundHandler);
}

8.2 AHardwareBuffer 到 OpenGL 纹理(C++ NDK)

#include <android/hardware_buffer.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES3/gl3.h>

// 从 AHardwareBuffer 创建 EGLImage,再绑定到 GL 纹理
GLuint createTextureFromAHardwareBuffer(AHardwareBuffer* buffer) {
// 获取 AHardwareBuffer 描述
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(buffer, &desc);

// 创建 EGLImage
EGLint eglAttribs[] = {
EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
EGL_NONE
};

EGLDisplay display = eglGetCurrentDisplay();
EGLImageKHR eglImage = eglCreateImageKHR(
display,
EGL_NO_CONTEXT,
EGL_NATIVE_BUFFER_ANDROID,
eglGetNativeClientBufferANDROID(buffer),
eglAttribs
);

if (eglImage == EGL_NO_IMAGE_KHR) {
LOGE("eglCreateImageKHR 失败: 0x%x", eglGetError());
return 0;
}

// 绑定到 GL 纹理
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture);
glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, eglImage);

// 纹理参数
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

// 注意:eglImage 需要在纹理使用完毕后销毁
// 保留 eglImage 引用,在适当时机调用 eglDestroyImageKHR(display, eglImage)
return texture;
}

8.3 AHardwareBuffer 零拷贝管线

完整的零拷贝管线示意:

Camera (Camera2)
│ ImageReader (HardwareBuffer backed)
│ AHardwareBuffer (共享内存)
├────────────────────────────────────────────┐
│ │
▼ ▼
OpenGL ES / Vulkan MediaCodec
(实时滤镜/特效/AR) (硬件编码 H.264/H.265)
│ │
└──────────────────┬─────────────────────────┘


Surface / ANativeWindow
(显示或进一步处理)

九、Camera2 常见问题与解决方案

9.1 预览拉伸/变形

问题:预览画面拉伸变形。解决:根据屏幕宽高比和相机输出尺寸选择最佳预览尺寸:

Size chooseOptimalSize(Size[] choices, int width, int height) {
// 选择与目标宽高比最接近的尺寸
double targetRatio = (double) height / width; // 注意:通常是竖屏比例
Size bestSize = null;
double minDiff = Double.MAX_VALUE;

for (Size size : choices) {
double ratio = (double) size.getWidth() / size.getHeight();
double diff = Math.abs(ratio - targetRatio);
if (diff < minDiff) {
// 在同宽高比中尽量选择大的
if (bestSize == null || (diff < minDiff * 0.9) || size.getWidth() > bestSize.getWidth()) {
bestSize = size;
minDiff = diff;
}
}
}
return bestSize;
}

9.2 3A 状态管理

Camera2 使用状态机管理 3A(AF、AE、AWB)。需要正确处理状态转换:

private final CameraCaptureSession.CaptureCallback mCaptureCallback =
new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);

// AF 状态检查
if (afState != null) {
switch (afState) {
case CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED:
Log.d(TAG, "自动对焦已锁定");
mAfLocked = true;
break;
case CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:
Log.d(TAG, "自动对焦失败(未对焦锁定)");
break;
case CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN:
Log.d(TAG, "正在扫描对焦");
break;
case CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED:
Log.d(TAG, "被动对焦已就绪");
break;
}
}

// AE 状态检查
if (aeState != null && aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
Log.d(TAG, "自动曝光已收敛");
mAEConverged = true;
}
}
};

9.3 权限处理

从 Android 10(API 29)起,相机权限可能受限:

// 必需权限(AndroidManifest.xml)
// <uses-permission android:name="android.permission.CAMERA" />
// <uses-feature android:name="android.hardware.camera" android:required="true" />
// <uses-feature android:name="android.hardware.camera.autofocus" />

// 运行时权限申请
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA_PERMISSION);
}

9.4 方向处理

Android 设备的相机传感器通常有固定的方向(水平),而设备可能处于任意方向。正确处理 JPEG 和预览方向:

int getJpegOrientation() {
// 获取设备旋转角度
int rotation = getWindowManager().getDefaultDisplay().getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0: degrees = 0; break;
case Surface.ROTATION_90: degrees = 90; break;
case Surface.ROTATION_180: degrees = 180; break;
case Surface.ROTATION_270: degrees = 270; break;
}

// 获取传感器方向
int sensorOrientation = mCameraCharacteristics.get(
CameraCharacteristics.SENSOR_ORIENTATION);

// 后置相机
return (sensorOrientation - degrees + 360) % 360;

// 前置相机(需要镜像)
// return (sensorOrientation + degrees) % 360;
}

十、总结

Android 相机进阶开发的知识体系:

  1. Camera2 管线:CameraManager → CameraDevice → CaptureSession → CaptureRequest → CaptureResult,理解每一步的职责和状态转换。
  2. 手动控制:关闭 3A 自动模式后可以精确控制曝光时间(纳秒)、ISO、对焦距离(屈光度)、白平衡增益等参数。
  3. RAW 捕获:通过 ImageFormat.RAW_SENSOR 获取 Bayer 原始数据,使用 DngCreator 包装为 DNG,保留最大后期空间。
  4. 高速连拍与 HFRcaptureBurst 实现连拍,createConstrainedHighSpeedCaptureSession 实现 120/240fps 慢动作。
  5. 多摄像头:逻辑相机封装物理相机,可以为特定物理相机创建独立的流,用于光学变焦切换、景深等。
  6. CameraX:生命周期感知的高级封装,通过 Preview、ImageCapture、ImageAnalysis 三个核心用例快速搭建相机应用,复杂需求通过 Camera2Interop 桥接。
  7. YUV_420_888 访问:正确处理 pixelStriderowStride,在 NV12/NV21 和分离平面格式间转换。
  8. AHardwareBuffer 零拷贝:实现相机帧在 CPU / GPU / MediaCodec / NN API 之间的无拷贝传递。

在生产级相机应用中,Camera2 提供极致控制,CameraX 提供开发效率,两者互通(通过 Camera2Interop)的设计使得可以在绝大多数场景下满足需求。

参考资料

打赏
  • 微信
  • 支付宝

评论