引言:预览,不只是"看个大概"
打开相机App的瞬间,你看到的那一帧实时画面,背后经历了什么?
很多开发者以为相机预览就是"把摄像头的数据显示出来"——听起来简单,做起来却暗藏玄机。一个60fps的流畅预览背后,涉及HAL层数据采集、BufferQueue生产者/消费者模型、SurfaceFlinger合成渲染、以及AF/AE/AWB三大自动控制算法的持续收敛,任何一个环节出问题都可能导致画面卡顿、曝光忽明忽暗、焦点游移不定。
本文将带你完整走一遍Camera2 API的预览流程,从Surface的创建到每一帧图像最终出现在屏幕上,把中间的每个技术环节都说清楚。如果你是相机开发的新手,看完本文你会对预览流程有系统性认识;如果你是老手,也许能从3A控制和性能优化部分找到一些新收获。
一、预览的起点:Surface与SurfaceTexture
在Camera2中,预览的本质是将相机数据输出到一个Surface。Surface是Android图形系统的核心抽象,它代表一块可以被渲染的缓冲区队列。预览用的Surface通常来自两个来源:TextureView和SurfaceView。
1.1 TextureView vs SurfaceView
这是Camera开发中最常见的选择题,两者各有利弊:
| 特性 | TextureView | SurfaceView |
|---|---|---|
| 渲染方式 | 在View树中渲染(主线程合成) | 独立Surface,SurfaceFlinger直接合成 |
| 动画支持 | ✅ 支持旋转、缩放、透明度动画 | ❌ 不支持普通View动画 |
| 性能开销 | 较高(需要GPU合成到View树) | 较低(硬件直接合成) |
| 圆角/裁剪 | 容易实现 | 需要额外处理 |
| 适用场景 | 需要复杂UI效果的预览 | 性能敏感场景、大尺寸预览 |
// 方案一:使用 TextureView
public class CameraPreviewActivity extends AppCompatActivity
implements TextureView.SurfaceTextureListener {
private TextureView mTextureView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTextureView = new TextureView(this);
mTextureView.setSurfaceTextureListener(this);
setContentView(mTextureView);
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface,
int width, int height) {
// SurfaceTexture 就绪,可以开始打开相机
openCamera(width, height);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface,
int width, int height) {
// 尺寸变化,需要重新配置预览(如旋转后)
configureTransform(width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
// Surface 销毁,需要关闭相机
closeCamera();
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
// 每帧更新时回调,可用于帧率统计
}
}
1.2 SurfaceTexture的内部机制
TextureView内部持有一个SurfaceTexture,这是整个预览管道的关键组件。SurfaceTexture从BufferQueue拿到相机输出的图像帧后,会将其作为OpenGL ES纹理附着到GPU,再由TextureView渲染到屏幕。
相机HAL → BufferQueue → SurfaceTexture → OpenGL纹理 → TextureView → SurfaceFlinger → 屏幕
SurfaceTexture的核心接口是updateTexImage(),调用后会将最新的帧更新为当前的OpenGL纹理。TextureView会在每次onSurfaceTextureUpdated()回调时自动调用这个方法,你通常不需要手动调用——但如果你需要精确控制帧的消费时机(比如做帧率控制),可以手动接管这个流程。
1.3 选择合适的预览尺寸
这是一个容易踩坑的地方。相机支持的预览尺寸由CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP决定,你需要从中选出最合适的尺寸:
private Size chooseOptimalSize(Size[] choices, int textureViewWidth,
int textureViewHeight, Size aspectRatio) {
// 收集满足最小尺寸要求的候选项
List<Size> bigEnough = new ArrayList<>();
List<Size> notBigEnough = new ArrayList<>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices) {
// 宽高比必须匹配(允许小误差)
if (option.getHeight() == option.getWidth() * h / w) {
if (option.getWidth() >= textureViewWidth &&
option.getHeight() >= textureViewHeight) {
bigEnough.add(option);
} else {
notBigEnough.add(option);
}
}
}
// 优先选择满足条件的最小尺寸(节省带宽),否则选最大的
if (!bigEnough.isEmpty()) {
return Collections.min(bigEnough, new CompareSizesByArea());
} else if (!notBigEnough.isEmpty()) {
return Collections.max(notBigEnough, new CompareSizesByArea());
} else {
Log.e(TAG, "找不到合适的预览尺寸");
return choices[0];
}
}
关键原则:预览尺寸的宽高比必须与拍照尺寸的宽高比保持一致,否则预览画面会出现拉伸变形。这是一个让很多新手困惑的问题——"我的预览怎么看起来胖了?"——十有八九就是宽高比没对齐。
二、配置预览管道:CaptureSession的建立
有了Surface之后,下一步是建立CameraCaptureSession,这是Camera2中代表"相机配置状态"的核心类。
2.1 打开相机设备
private void openCamera(int width, int height) {
// 权限检查(必须在运行时申请 CAMERA 权限)
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
requestCameraPermission();
return;
}
// 设置预览尺寸(宽高比对齐拍照尺寸)
setUpCameraOutputs(width, height);
// 调整TextureView的变换矩阵,处理旋转问题
configureTransform(width, height);
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
// 获取相机ID(通常0是后置,1是前置)
String cameraId = manager.getCameraIdList()[0];
// 异步打开相机,结果通过 CameraDevice.StateCallback 回调
manager.openCamera(cameraId, mStateCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private final CameraDevice.StateCallback mStateCallback =
new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
// 相机已打开,保存设备引用,开始创建预览会话
mCameraDevice = cameraDevice;
createCameraPreviewSession();
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
cameraDevice.close();
mCameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
cameraDevice.close();
mCameraDevice = null;
// 错误码:ERROR_CAMERA_IN_USE, ERROR_MAX_CAMERAS_IN_USE 等
Log.e(TAG, "相机打开失败,错误码:" + error);
}
};
2.2 创建CaptureSession
createCameraPreviewSession()是配置预览的核心方法。Camera2采用"会话"模型:你告诉相机"我要输出到这些Surface",相机硬件会据此配置数据通路:
private void createCameraPreviewSession() {
try {
SurfaceTexture texture = mTextureView.getSurfaceTexture();
// 设置默认缓冲区尺寸(必须!否则缓冲区大小是默认值)
texture.setDefaultBufferSize(mPreviewSize.getWidth(),
mPreviewSize.getHeight());
// 从 SurfaceTexture 创建 Surface(相机输出目标)
Surface previewSurface = new Surface(texture);
// 构建预览请求(使用 TEMPLATE_PREVIEW 模板)
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(previewSurface);
// 创建 CaptureSession,传入输出 Surface 列表
// 注意:此处可以同时传入预览Surface和拍照Surface,一次性配置
mCameraDevice.createCaptureSession(
Arrays.asList(previewSurface, mImageReader.getSurface()),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
if (mCameraDevice == null) return;
mCaptureSession = session;
// Session 配置完成,启动预览
startPreview();
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
// 配置失败,通常是 Surface 格式/尺寸不被支持
Log.e(TAG, "CaptureSession 配置失败");
}
},
mBackgroundHandler
);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
重要细节:createCaptureSession()传入的Surface列表决定了相机的工作模式,这个配置一旦完成就不能修改——如果你需要增减输出目标(比如开始录像),必须关闭当前Session并重新创建一个新的。这也是为什么在Camera2中,同时配置好预览Surface和拍照Surface是最佳实践。
2.3 预览数据流架构图
下图展示了从App层到最终显示的完整数据流路径:
整个预览管道可以分为五层:
- App层:TextureView/Surface 作为数据接收端
- CameraService层:负责Session管理和请求调度
- HAL3层:相机硬件抽象层,驱动实际传感器
- BufferQueue层:生产者/消费者模型的图像缓冲队列
- Display层:SurfaceFlinger负责最终的屏幕合成
三、启动连续预览:setRepeatingRequest详解
3.1 TEMPLATE_PREVIEW模板
Camera2提供了多个CaptureRequest模板,TEMPLATE_PREVIEW是专为预览场景优化的:
private void startPreview() {
try {
// 使用 TEMPLATE_PREVIEW 模板,已预设适合预览的参数
// - 自动曝光模式:CONTROL_AE_MODE_ON
// - 自动白平衡:CONTROL_AWB_MODE_AUTO
// - 自动对焦:CONTROL_AF_MODE_CONTINUOUS_PICTURE(大多数设备)
// - 噪点消除:NOISE_REDUCTION_MODE_FAST
// 设置AF模式为连续自动对焦
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 设置闪光灯自动(适用于有闪光灯的设备)
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// 构建请求
mPreviewRequest = mPreviewRequestBuilder.build();
// 启动连续预览(重复请求)
mCaptureSession.setRepeatingRequest(
mPreviewRequest,
mCaptureCallback, // 每帧的回调
mBackgroundHandler // 回调线程
);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
setRepeatingRequest()和capture()的区别:
capture():单次拍摄,执行一次后停止setRepeatingRequest():持续重复,直到调用stopRepeating()或Session关闭setRepeatingBurst():持续重复一个请求序列(常用于连拍)
3.2 CaptureCallback:预览帧的元数据
每帧预览完成后,CameraService会回调CaptureCallback,传递该帧的元数据(注意:不是图像数据,图像数据通过Surface直接传输):
private final CameraCaptureSession.CaptureCallback mCaptureCallback =
new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureProgressed(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull CaptureResult partialResult) {
// 部分结果(Partial Result):某些参数提前到达,如AF/AE状态
// 可用于更快响应AF状态变化,减少延迟
processPreviewResult(partialResult);
}
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
// 完整结果:该帧所有元数据都已可用
processPreviewResult(result);
}
@Override
public void onCaptureFailed(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull CaptureFailure failure) {
// 帧处理失败(可能因为硬件临时故障或资源不足)
Log.w(TAG, "预览帧处理失败,原因:" + failure.getReason());
}
};
private void processPreviewResult(CaptureResult result) {
// 读取AF状态
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
// 读取AE状态
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
// 读取AWB状态
Integer awbState = result.get(CaptureResult.CONTROL_AWB_STATE);
// 可以根据这些状态更新UI(如显示对焦框)
}
四、3A自动控制:让每一帧都"好看"
3A控制(AF自动对焦、AE自动曝光、AWB自动白平衡)是相机预览质量的核心。三个算法协同工作,持续分析每一帧图像,动态调整相机参数。
4.1 AF自动对焦
AF(Auto Focus)的核心任务是驱动镜头运动,使目标物体在传感器上成像最清晰。
常用AF模式:
// 连续对焦(预览阶段推荐):相机持续检测并调整焦点
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 单次对焦:触发一次后锁定
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_AUTO);
// 手动对焦:直接指定镜头位置(0.0f=无穷远,≥10.0f=近距离)
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_OFF);
mPreviewRequestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, 5.0f);
AF状态机:
AF系统是一个状态机,关键状态包括:
CONTROL_AF_STATE_INACTIVE:未激活CONTROL_AF_STATE_PASSIVE_SCAN:连续对焦扫描中CONTROL_AF_STATE_PASSIVE_FOCUSED:连续对焦已收敛(焦点稳定)CONTROL_AF_STATE_ACTIVE_SCAN:单次对焦执行中CONTROL_AF_STATE_FOCUSED_LOCKED:对焦成功并锁定CONTROL_AF_STATE_NOT_FOCUSED_LOCKED:对焦失败并锁定(画面可能模糊)
实现点击对焦:
// 手指触摸屏幕时,将触摸区域转换为传感器坐标,设置AF测量区域
private void tapToFocus(MotionEvent event) {
// 获取传感器活跃区域
Rect sensorArraySize = mCameraCharacteristics.get(
CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
// 将触摸坐标转换为传感器坐标
int x = (int)(event.getX() / mTextureView.getWidth() * sensorArraySize.width());
int y = (int)(event.getY() / mTextureView.getHeight() * sensorArraySize.height());
// 创建50x50像素的测量区域,权重为MeteringRectangle.METERING_WEIGHT_MAX
int halfTouchWidth = 50;
int halfTouchHeight = 50;
MeteringRectangle focusArea = new MeteringRectangle(
Math.max(x - halfTouchWidth, 0),
Math.max(y - halfTouchHeight, 0),
halfTouchWidth * 2,
halfTouchHeight * 2,
MeteringRectangle.METERING_WEIGHT_MAX - 1
);
// 取消之前的连续对焦,切换到单次对焦模式
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_AUTO);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_REGIONS,
new MeteringRectangle[]{focusArea});
// 发送AF触发指令
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_START);
try {
mCaptureSession.capture(mPreviewRequestBuilder.build(),
mAfCaptureCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
4.2 AE自动曝光
AE(Auto Exposure)控制传感器曝光时间(快门速度)、ISO感光度和镜头光圈(如果是可变光圈镜头),让画面亮度保持在合适范围。
AE模式和测光区域:
// 自动曝光,不带闪光灯
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON);
// 自动曝光补偿:-3 到 +3 EV(具体范围由 AE_COMPENSATION_RANGE 决定)
// 1步 = 1/3 EV(由 AE_COMPENSATION_STEP 决定)
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 3); // +1 EV
// 设置AE测光区域(与AF区域类似,但权重影响曝光计算)
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_REGIONS,
new MeteringRectangle[]{aeArea});
// 锁定AE(拍照前常用,防止拍摄瞬间曝光突变)
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
AE状态机关键状态:
CONTROL_AE_STATE_CONVERGED:曝光已收敛(画面亮度稳定)CONTROL_AE_STATE_SEARCHING:正在搜索合适曝光值(画面可能闪烁)CONTROL_AE_STATE_LOCKED:曝光已锁定CONTROL_AE_STATE_FLASH_REQUIRED:环境太暗,建议触发闪光
4.3 AWB自动白平衡
AWB(Auto White Balance)分析画面中的白色/灰色区域,推断当前光源的色温,并调整各颜色通道增益,使白色物体看起来真正是白色。
// 自动白平衡(绝大多数场景推荐)
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AWB_MODE,
CaptureRequest.CONTROL_AWB_MODE_AUTO);
// 预设场景模式(用于特殊光源场景)
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AWB_MODE,
CaptureRequest.CONTROL_AWB_MODE_DAYLIGHT); // 日光(5500K)
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AWB_MODE,
CaptureRequest.CONTROL_AWB_MODE_INCANDESCENT); // 白炽灯(3000K)
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AWB_MODE,
CaptureRequest.CONTROL_AWB_MODE_FLUORESCENT); // 荧光灯(4000K)
// 锁定AWB(与AE一起锁定,保持拍摄前后颜色一致)
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true);
4.4 3A收敛状态的监控
在进行拍照前,通常需要等待3A收敛完成,否则可能拍出模糊或曝光不正确的照片:
// 综合检查3A是否已收敛
private boolean is3AConverged(CaptureResult result) {
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
// AF收敛条件:已对焦锁定、或者连续对焦稳定
boolean afReady = (afState == null) ||
afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED ||
afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED;
// AE收敛条件:已收敛或已锁定
boolean aeReady = (aeState == null) ||
aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED ||
aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED ||
aeState == CaptureResult.CONTROL_AE_STATE_LOCKED;
return afReady && aeReady;
}
五、预览帧的图像数据获取
上文提到,CaptureCallback只携带元数据,实际图像数据通过Surface直接传输。如果你需要访问预览帧的像素数据(比如做实时滤镜、二维码扫描、AI推理),需要使用ImageReader。
5.1 配置ImageReader
// 创建 ImageReader,指定格式和最大帧缓冲数量
// YUV_420_888 是Camera2原生格式,CPU处理效率高
mImageReader = ImageReader.newInstance(
mPreviewSize.getWidth(),
mPreviewSize.getHeight(),
ImageFormat.YUV_420_888,
2 // 最多缓冲2帧(过多会增加延迟,过少可能丢帧)
);
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener,
mBackgroundHandler);
// 将 ImageReader 的 Surface 添加到 CaptureSession 的输出目标
// (需要在 createCaptureSession() 时一起传入)
mPreviewRequestBuilder.addTarget(mImageReader.getSurface());
5.2 处理YUV图像帧
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener =
new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
// 必须调用 acquireLatestImage(),且必须在 try-with-resources 中关闭
// 忘记关闭会导致缓冲区耗尽,预览卡死
try (Image image = reader.acquireLatestImage()) {
if (image == null) return;
// YUV_420_888 格式:Y平面(亮度) + U平面(色度) + V平面(色度)
Image.Plane[] planes = image.getPlanes();
ByteBuffer yBuffer = planes[0].getBuffer(); // Y分量
ByteBuffer uBuffer = planes[1].getBuffer(); // U分量
ByteBuffer vBuffer = planes[2].getBuffer(); // V分量
int ySize = yBuffer.remaining();
int uSize = uBuffer.remaining();
int vSize = vBuffer.remaining();
byte[] nv21 = new byte[ySize + uSize + vSize];
yBuffer.get(nv21, 0, ySize);
vBuffer.get(nv21, ySize, vSize); // NV21: V在U前面
uBuffer.get(nv21, ySize + vSize, uSize);
// 将 NV21 转换为 Bitmap(如果需要显示或进一步处理)
YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21,
image.getWidth(), image.getHeight(), null);
// 进行图像分析(如二维码识别、人脸检测等)
analyzeFrame(yuvImage, image.getWidth(), image.getHeight());
} catch (Exception e) {
Log.e(TAG, "处理预览帧时出错", e);
}
}
};
注意:ImageReader的帧获取有两个方法:
acquireLatestImage():丢弃所有旧帧,只获取最新一帧(推荐!适合实时处理)acquireNextImage():按顺序获取,可能造成处理延迟堆积
5.3 预览与ImageReader共存的性能代价
同时使用TextureView预览和ImageReader获取帧,意味着相机数据会被复制两份(分别发到两个Surface)。对于高分辨率或高帧率场景,这会显著增加内存带宽和CPU负载。
常见优化方案:
- 降低ImageReader分辨率:分析任务不需要全分辨率,
640x480往往够用 - 降低ImageReader帧率:只分析每N帧,通过时间戳过滤跳过多余帧
- GPU加速:使用OpenGL/RenderScript直接在GPU上处理SurfaceTexture的纹理
六、性能优化:追求流畅与低延迟
6.1 帧率控制
Camera2可以通过AE_TARGET_FPS_RANGE精确控制帧率:
// 查询支持的帧率范围
Range<Integer>[] fpsRanges = mCameraCharacteristics.get(
CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
// 选择最高帧率(通常是[30,30]固定30fps或[15,30]可变帧率)
Range<Integer> targetFps = null;
for (Range<Integer> range : fpsRanges) {
if (range.getUpper() >= 30) {
if (targetFps == null ||
range.getLower() > targetFps.getLower()) {
targetFps = range;
}
}
}
// 设置目标帧率
if (targetFps != null) {
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
targetFps);
}
固定帧率(如[30,30])vs 可变帧率(如[15,30])的选择:
- 固定30fps:画面更稳定,视觉体验更好,但弱光下可能曝光不足
- 可变帧率:弱光时降低帧率以获得更长曝光时间,画面亮度更好,但可能出现帧率抖动
6.2 减少预览延迟
预览延迟(从光线进入镜头到屏幕显示的时间)影响用户体验。主要优化点:
1. 使用SurfaceView代替TextureView:SurfaceView绕过View树,直接由SurfaceFlinger合成,延迟约少1个VSYNC周期(16ms @60fps)。
2. 合理设置BufferQueue深度:ImageReader的maxImages参数控制缓冲队列深度,值越小延迟越低,但丢帧风险越高。预览场景推荐设置为2。
3. 避免主线程处理:所有CameraService回调都应在后台线程执行,避免阻塞UI线程:
// 创建专用的HandlerThread(相机后台线程)
private HandlerThread mBackgroundThread;
private Handler mBackgroundHandler;
private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
private void stopBackgroundThread() {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
6.3 预览旋转处理
这是Camera2开发中最令人头疼的问题之一。相机传感器有自己的"自然方向"(通常横屏),而手机屏幕默认竖屏显示,加上前摄还有镜像问题,不处理好就会出现预览画面旋转90°或左右镜像的情况。
private void configureTransform(int viewWidth, int viewHeight) {
// 获取相机传感器方向(相对于设备自然方向的旋转角度)
int sensorOrientation = mCameraCharacteristics.get(
CameraCharacteristics.SENSOR_ORIENTATION);
// 获取当前屏幕旋转角度
int rotation = getWindowManager().getDefaultDisplay().getRotation();
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0,
mPreviewSize.getHeight(), mPreviewSize.getWidth());
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
bufferRect.offset(centerX - bufferRect.centerX(),
centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max(
(float) viewHeight / mPreviewSize.getHeight(),
(float) viewWidth / mPreviewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
}
mTextureView.setTransform(matrix);
}
七、调试工具与常见问题排查
7.1 Camera2调试利器
1. CameraManager.openCamera() 失败原因分析:
| 错误码 | 原因 | 解决方案 |
|---|---|---|
ERROR_CAMERA_IN_USE | 另一个应用正在使用相机 | 等待其释放或提高App优先级 |
ERROR_MAX_CAMERAS_IN_USE | 打开的相机数量超出系统限制 | 关闭不需要的相机设备 |
ERROR_CAMERA_DISABLED | 相机被设备策略禁用 | 检查企业设备策略 |
ERROR_CAMERA_DEVICE | 相机硬件发生致命错误 | 重新打开相机 |
ERROR_CAMERA_SERVICE | CameraService崩溃 | 系统级问题,通常需要重启 |
2. 预览卡顿排查:
# 查看相机相关进程状态
adb shell ps -A | grep camera
# 抓取Camera相关日志
adb logcat -s CameraDevice CameraService Camera2 | grep -v "DEBUG"
# 查看SurfaceFlinger帧率统计(是否有丢帧)
adb shell dumpsys SurfaceFlinger | grep "FPS"
# 查看相机服务状态
adb shell dumpsys media.camera
3. 3A状态追踪:
开发阶段可以在onCaptureCompleted()中打印3A状态,观察收敛过程:
private void logCaptureResult(CaptureResult result) {
String[] AF_STATES = {"INACTIVE", "PASSIVE_SCAN", "PASSIVE_FOCUSED",
"ACTIVE_SCAN", "FOCUSED_LOCKED", "NOT_FOCUSED_LOCKED"};
String[] AE_STATES = {"INACTIVE", "SEARCHING", "CONVERGED",
"LOCKED", "FLASH_REQUIRED", "PRECAPTURE"};
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
if (afState != null && aeState != null) {
Log.d(TAG, String.format("AF: %s | AE: %s",
AF_STATES[afState], AE_STATES[aeState]));
}
}
7.2 常见坑位汇总
-
预览画面拉伸变形:预览尺寸宽高比与SurfaceTexture/TextureView比例不匹配,调用
configureTransform()修正。 -
预览黑屏:忘记调用
texture.setDefaultBufferSize()设置SurfaceTexture的缓冲区尺寸,默认1x1显示黑屏。 -
ImageReader满了,预览卡住:
acquireLatestImage()获取到Image后忘记调用close(),缓冲区耗尽后新帧无法入队。 -
相机Session重建后闪屏:拍照或切换录像模式时需要重建Session,新旧Session切换瞬间会短暂黑屏,可以在TextureView上盖一个渐变动画掩盖。
-
前置摄像头预览左右镜像:前摄传感器通常有镜像标志(
LENS_FACING_FRONT),需要在configureTransform()中额外处理水平翻转。
八、实战案例:实现完整的相机预览
综合以上内容,下面是一个完整的、可运行的相机预览示例:
public class CameraPreviewFragment extends Fragment {
private static final String TAG = "CameraPreview";
private TextureView mTextureView;
private CameraDevice mCameraDevice;
private CameraCaptureSession mCaptureSession;
private CaptureRequest.Builder mPreviewRequestBuilder;
private HandlerThread mBackgroundThread;
private Handler mBackgroundHandler;
private Size mPreviewSize;
// === 生命周期管理 ===
@Override
public void onResume() {
super.onResume();
startBackgroundThread();
if (mTextureView.isAvailable()) {
openCamera(mTextureView.getWidth(), mTextureView.getHeight());
} else {
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}
}
@Override
public void onPause() {
closeCamera();
stopBackgroundThread();
super.onPause();
}
// === TextureView 监听器 ===
private final TextureView.SurfaceTextureListener mSurfaceTextureListener =
new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture st, int w, int h) {
openCamera(w, h);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture st, int w, int h) {
configureTransform(w, h);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture st) {
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture st) {}
};
// === 相机打开/关闭 ===
private void openCamera(int width, int height) {
if (ContextCompat.checkSelfPermission(requireContext(),
Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
return;
}
CameraManager manager = (CameraManager)
requireContext().getSystemService(Context.CAMERA_SERVICE);
try {
String cameraId = manager.getCameraIdList()[0];
CameraCharacteristics characteristics =
manager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
Size[] outputSizes = map.getOutputSizes(SurfaceTexture.class);
mPreviewSize = chooseOptimalSize(outputSizes, width, height,
outputSizes[0]); // 简化:用第一个尺寸的宽高比
manager.openCamera(cameraId, new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice camera) {
mCameraDevice = camera;
createCaptureSession();
}
@Override
public void onDisconnected(@NonNull CameraDevice camera) {
camera.close();
}
@Override
public void onError(@NonNull CameraDevice camera, int error) {
camera.close();
Log.e(TAG, "相机错误: " + error);
}
}, mBackgroundHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "相机访问异常", e);
}
}
private void createCaptureSession() {
try {
SurfaceTexture texture = mTextureView.getSurfaceTexture();
texture.setDefaultBufferSize(mPreviewSize.getWidth(),
mPreviewSize.getHeight());
Surface surface = new Surface(texture);
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(surface);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
mCameraDevice.createCaptureSession(
Collections.singletonList(surface),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession session) {
mCaptureSession = session;
try {
mCaptureSession.setRepeatingRequest(
mPreviewRequestBuilder.build(),
null, mBackgroundHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "启动预览失败", e);
}
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession session) {
Log.e(TAG, "Session 配置失败");
}
}, mBackgroundHandler);
} catch (CameraAccessException e) {
Log.e(TAG, "创建 CaptureSession 失败", e);
}
}
private void closeCamera() {
if (mCaptureSession != null) {
mCaptureSession.close();
mCaptureSession = null;
}
if (mCameraDevice != null) {
mCameraDevice.close();
mCameraDevice = null;
}
}
}
总结
本文完整梳理了Camera2 API预览流程的核心技术链路:
-
Surface创建:TextureView/SurfaceView各有适用场景,SurfaceTexture是连接相机数据和GPU渲染的桥梁。预览尺寸的宽高比对齐是最容易忽略的细节。
-
CaptureSession配置:Session是Camera2的核心配置容器,创建时需要一次性传入所有输出Surface。任何对输出目标的修改都需要重建Session。
-
setRepeatingRequest与TEMPLATE_PREVIEW:持续预览的本质是持续发送重复的CaptureRequest,TEMPLATE_PREVIEW提供了优化过的默认参数,按需覆盖即可。
-
3A控制协同:AF/AE/AWB三个算法共享输入帧,相互独立地收敛,通过CaptureResult反馈状态。实现点击对焦、曝光补偿等功能需要理解各自的状态机。
-
性能优化关键点:后台线程处理相机回调、合理的ImageReader缓冲深度、正确处理旋转变换是保证流畅预览体验的基础。
掌握了预览流程,你已经具备了Camera2开发的核心基础。下一篇我们将在预览的基础上,深入探讨静态图像拍摄流程——从预捕获的3A锁定、到RAW/JPEG格式选择、再到拍照结果的完整处理链路。