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 请求 (元数据)
|
每一步详细说明:
- CameraManager:系统服务,枚举所有相机设备,获取相机特性(CameraCharacteristics)。
- CameraDevice:代表已打开的物理或逻辑相机,是相机操作的入口。
- CameraCaptureSession:将 CaptureRequest 的流水线输出绑定到一组 Surface(预览、拍照、录像等)。
- CaptureRequest:单次/重复拍照请求,包含所有控制参数(曝光、焦点、ISO、白平衡等)。
- CaptureResult:每次拍照返回的元数据(时间戳、焦距状态、自动曝光状态等)。
1.2 打开相机的完整流程
public class Camera2Manager { private CameraManager mCameraManager; private CameraDevice mCameraDevice; private CameraCaptureSession mCaptureSession; private String mCameraId;
void init(Context context) { mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); }
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; }
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() { List<Surface> surfaces = new ArrayList<>();
SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture(); surfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); Surface previewSurface = new Surface(surfaceTexture); surfaces.add(previewSurface);
mImageReader = ImageReader.newInstance(mPhotoSize.getWidth(), mPhotoSize.getHeight(), ImageFormat.JPEG, 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());
builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); 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); Range<Integer> isoRange = chars.get( CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE); Integer maxAnalogIso = chars.get( CameraCharacteristics.SENSOR_MAX_ANALOG_SENSITIVITY); float aperture = chars.get(CameraCharacteristics.LENS_INFO_AVAILABLE_APERTURES)[0]; } }
|
2.2 手动曝光控制
曝光由两个参数决定:曝光时间(exposure time, 单位纳秒 ns)和 ISO(感光度)。Camera2 中的关系:
EV(曝光值)∝ exposure_time × ISO
|
void setManualExposure(CaptureRequest.Builder builder, long exposureTimeNs, int iso) { builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF); builder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, exposureTimeNs); builder.set(CaptureRequest.SENSOR_SENSITIVITY, iso); long frameDuration = 33_333_333L; builder.set(CaptureRequest.SENSOR_FRAME_DURATION, frameDuration); }
|
2.3 手动对焦
void setManualFocus(CaptureRequest.Builder builder, float focusDistance) { builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_OFF); builder.set(CaptureRequest.LENS_FOCUS_DISTANCE, focusDistance); }
|
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 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); }
|
2.5 手动曝光补偿(在自动模式下微调)
void setExposureCompensation(CaptureRequest.Builder builder, int evSteps) { builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); builder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, evSteps); }
|
2.6 锁定 3A
在拍摄前锁定 3A 可避免画质波动,特别是拍夜景或特定场景:
void lock3A(CaptureRequest.Builder builder) { builder.set(CaptureRequest.CONTROL_AE_LOCK, true); builder.set(CaptureRequest.CONTROL_AWB_LOCK, true); builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_START); }
|
三、RAW 图像捕获
3.1 RAW_SENSOR 格式
RAW 图像是图像传感器输出的原始未经处理的数据,通常是 Bayer 排列(一种颜色滤波阵列,每个像素只记录 R、G、B 中的一种,按特定模式排布)。RAW 的优势在于:
- 高动态范围:10-14 位深度 vs JPEG 的 8 位。
- 无损后期:可在后期任意调整白平衡、曝光、去噪、色调。
- 科学/计算摄影:多帧合成、HDR+、夜景模式等算法需要 RAW 作为输入。
void setupRawCapture(CameraCharacteristics chars) { 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; } int bayerPattern = chars.get(CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT); String[] bayerNames = {"RGGB", "GRBG", "GBRG", "BGGR"}; Log.d(TAG, "Bayer pattern: " + bayerNames[bayerPattern]); Integer whiteLevel = chars.get(CameraCharacteristics.SENSOR_INFO_WHITE_LEVEL); Log.d(TAG, "White level: " + whiteLevel); 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(); Image.Plane rawPlane = planes[0]; ByteBuffer buffer = rawPlane.getBuffer(); int pixelStride = rawPlane.getPixelStride(); int rowStride = rawPlane.getRowStride(); byte[] rowData = new byte[rowStride]; int width = image.getWidth(); int height = image.getHeight(); for (int y = 0; y < height; y++) { buffer.position(y * rowStride); buffer.get(rowData, 0, rowStride); } 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); 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
void captureHDREvBracketing() { long baseExposure = getCurrentExposureTime(); int baseIso = getCurrentIso(); captureRaw(baseExposure / 4, baseIso, "ev-2.dng"); captureRaw(baseExposure, baseIso, "ev0.dng"); captureRaw(baseExposure * 4, baseIso, "ev+2.dng"); }
|
四、高速连拍(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 { 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); } } } } List<Size> highSpeedSizes = new ArrayList<>(); highSpeedSizes.add(new Size(1920, 1080)); highSpeedSizes.add(new Size(1280, 720)); 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<>(); 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); Set<String> physicalIds = chars.getPhysicalCameraIds(); if (physicalIds != null && !physicalIds.isEmpty()) { Log.d(TAG, "逻辑相机 " + cameraId + " 包含物理相机: " + physicalIds); } 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"; String physicalTeleId = "2"; 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()); 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:绑定用例到生命周期。
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}" }
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() { CameraSelector cameraSelector = new CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK) .build();
Preview preview = new Preview.Builder() .setTargetResolution(new Size(1080, 1920)) .build(); preview.setSurfaceProvider(mPreviewView.getSurfaceProvider());
mImageCapture = new ImageCapture.Builder() .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY) .setTargetResolution(new Size(4000, 3000)) .setFlashMode(ImageCapture.FLASH_MODE_AUTO) .build();
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(); });
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(); new Camera2Interop.Extender<>(preview) .setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF) .setCaptureRequestOption(CaptureRequest.SENSOR_EXPOSURE_TIME, 10_000_000L) .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(); ByteBuffer yBuffer = planes[0].getBuffer(); int yRowStride = planes[0].getRowStride(); int yPixelStride = planes[0].getPixelStride(); ByteBuffer uBuffer = planes[1].getBuffer(); int uRowStride = planes[1].getRowStride(); int uPixelStride = planes[1].getPixelStride(); ByteBuffer vBuffer = planes[2].getBuffer(); int vRowStride = planes[2].getRowStride(); int vPixelStride = planes[2].getPixelStride(); int width = image.getWidth(); int height = image.getHeight(); byte[] yRow = new byte[yRowStride]; byte[] uRow = new byte[uRowStride]; byte[] vRow = new byte[vRowStride]; for (int row = 0; row < height; row++) { yBuffer.position(row * yRowStride); yBuffer.get(yRow, 0, yRowStride); 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++) { int y = yRow[col * yPixelStride] & 0xFF; int uvColIndex = (col / 2) * uPixelStride; int u = uRow[uvColIndex] & 0xFF; int v = vRow[uvColIndex] & 0xFF; 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) { copyNV12ToNV21(planes, outputNV21); } else if (yPixelStride == 1 && uvPixelStride == 1) { copyPlanarToNV21(planes, outputNV21); } else { copyWithSubsampling(planes, outputNV21); } }
void copyNV12ToNV21(Image.Plane[] planes, byte[] output) { int width = image.getWidth(); int height = image.getHeight(); ByteBuffer yBuf = planes[0].getBuffer(); yBuf.get(output, 0, width * height); 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); int offset = width * height; for (int i = 0; i < uvLen; i++) { output[offset + i * 2] = vData[i]; output[offset + i * 2 + 1] = uData[i]; } }
|
7.4 使用 RenderScript / GPU 加速 YUV 转换
在需要实时处理时,应避免在 CPU 上逐像素转换 YUV。更好的选择:
八、AHardwareBuffer 零拷贝
8.1 什么是 AHardwareBuffer
AHardwareBuffer 是 Android 8.0(API 26)引入的跨进程共享内存机制。它允许不同组件(Camera、GPU、CPU、MediaCodec、NN API)共享同一块物理内存,无需拷贝。
@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, usage.toLong() );
reader.setOnImageAvailableListener(imageReader -> { Image image = imageReader.acquireNextImage(); HardwareBuffer hwb = image.getHardwareBuffer(); if (hwb != null) { 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>
GLuint createTextureFromAHardwareBuffer(AHardwareBuffer* buffer) { AHardwareBuffer_Desc desc; AHardwareBuffer_describe(buffer, &desc);
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; }
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);
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); 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; } } if (aeState != null && aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { Log.d(TAG, "自动曝光已收敛"); mAEConverged = true; } } };
|
9.3 权限处理
从 Android 10(API 29)起,相机权限可能受限:
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;
}
|
十、总结
Android 相机进阶开发的知识体系:
- Camera2 管线:CameraManager → CameraDevice → CaptureSession → CaptureRequest → CaptureResult,理解每一步的职责和状态转换。
- 手动控制:关闭 3A 自动模式后可以精确控制曝光时间(纳秒)、ISO、对焦距离(屈光度)、白平衡增益等参数。
- RAW 捕获:通过
ImageFormat.RAW_SENSOR 获取 Bayer 原始数据,使用 DngCreator 包装为 DNG,保留最大后期空间。
- 高速连拍与 HFR:
captureBurst 实现连拍,createConstrainedHighSpeedCaptureSession 实现 120/240fps 慢动作。
- 多摄像头:逻辑相机封装物理相机,可以为特定物理相机创建独立的流,用于光学变焦切换、景深等。
- CameraX:生命周期感知的高级封装,通过 Preview、ImageCapture、ImageAnalysis 三个核心用例快速搭建相机应用,复杂需求通过
Camera2Interop 桥接。
- YUV_420_888 访问:正确处理
pixelStride 和 rowStride,在 NV12/NV21 和分离平面格式间转换。
- AHardwareBuffer 零拷贝:实现相机帧在 CPU / GPU / MediaCodec / NN API 之间的无拷贝传递。
在生产级相机应用中,Camera2 提供极致控制,CameraX 提供开发效率,两者互通(通过 Camera2Interop)的设计使得可以在绝大多数场景下满足需求。
参考资料: