Life was like a box of chocolates, you never know what you’re gonna get.
生命就像一盒巧克力,你永远无法知道下一个是什么味道的。
-
Android Camera系列(一):SurfaceView+Camera
-
Android Camera系列(二):TextureView+Camera
-
Android Camera系列(三):GLSurfaceView+Camera
-
Android Camera系列(四):TextureView+OpenGL ES+Camera
-
Android Camera系列(五):Camera2
-
Android Camera系列(六):MediaCodec视频编码上-编码YUV
-
Android Camera系列(七):MediaCodec视频编码中-OpenGL ES多线程渲染
-
Android Camera系列(八):MediaCodec视频编码下-OpenGL ES离屏渲染
本系列主要讲述Android开发中Camera的相关操作、预览方式、视频录制等。项目结构简单、代码耦合性低,旨在帮助大家能从中有所收获(方便copy :) ),对于个人来说也是一个总结的好机会。
Android5.0开始Google推荐使用Camera2
替代android.hardware.Camera
,于是便有了这篇文章。这篇文章我们基于Android Camera系列(一):SurfaceView+Camera中定义好的ICameraManager接口来封装Camera2。
一.概述
Camera2 API是Android 5.0(Lollipop)之后引入的新版相机API。与早期的Camera API相比,Camera2提供了更多的功能和对摄像头硬件的更深入的控制。这使得开发者可以实现更复杂、更高级的摄像头功能,如实时预览、拍照、录像、对焦、闪光灯控制等。
camera2对于camera来说是一套全新的API,这就需要我们先了解下Camera2的整体架构,如下图:
Google重新设计Android Camera API 的目的在于大幅提高应用对于 Android 设备上的相机子系统的控制能力,同时重新组织 API,提高其效率和可维护性。
在CaptureRequest中设置不同的Surface用于接收不同的图片数据,最后从不同的Surface中获取到图片数据和包含拍照相关信息的CaptureResult。
Camera2的核心类和开发步骤请看下图:
二. 相关类
1. CameraManager
CameraManager 是一个负责查询和建立相机连接的系统服务,功能如下:
- 获取当前设备中可用的相机列表
getCameraIdList
- 根据摄像头id返回该摄像头的相关信息
getCameraCharacteristics(String cameraId)
- 根据指定的相机 ID 连接相机设备
- 提供将闪光灯设置成手电筒模式的快捷方式
2. CameraDevice
描述系统摄像头,类似于android.hardware.Camera
- 根据指定的参数创建 CameraCaptureSession。
- 根据指定的模板创建 CaptureRequest。
- 关闭相机设备。
- 监听相机设备的状态,例如断开连接、开启成功和开启失败等。
Camera1和CameraDevice很类似,但又不同。Camera 类几乎负责了所有相机的操作,而 CameraDevice 的功能则十分的单一,就是只负责建立相机连接的事务,而更加细化的相机操作则交给了稍后会介绍的 CameraCaptureSession。
3. CameraCaptureSession
当需要拍照、预览等功能时,需要先创建该类的实例,然后通过该实例里的方法进行控制。
一个 CameraDevice 一次只能开启一个 CameraCaptureSession,绝大部分的相机操作都是通过向 CameraCaptureSession 提交一个 Capture 请求实现的,例如拍照、连拍、设置闪光灯模式、触摸对焦、显示预览画面等等。
4. CameraCharacteristics
CameraCharacteristics 是一个只读的相机信息提供者,其内部携带大量的相机信息,包括代表相机朝向的 LENS_FACING
;判断闪光灯是否可用的 FLASH_INFO_AVAILABLE
;获取所有可用 AE 模式的 CONTROL_AE_AVAILABLE_MODES
等等。CameraCharacteristics 有点像 Camera1 的 Camera.CameraInfo
或者 Camera.Parameters
。
5. CaptureRequest
CaptureRequest 是向 CameraCaptureSession 提交 Capture 请求时的信息载体,其内部包括了本次 Capture 的参数配置和接收图像数据的 Surface。CaptureRequest 可以配置的信息非常多,包括图像格式、图像分辨率、传感器控制、闪光灯控制、3A 控制等等,可以说绝大部分的相机参数都是通过 CaptureRequest 配置的。值得注意的是每一个 CaptureRequest 表示一帧画面的操作,这意味着你可以精确控制每一帧的 Capture 操作。
6. CaptureResult
CaptureResult 是每一次 Capture 操作的结果,里面包括了很多状态信息,包括闪光灯状态、对焦状态、时间戳等等。例如你可以在拍照完成的时候,通过 CaptureResult 获取本次拍照时的对焦状态和时间戳。需要注意的是,CaptureResult 并不包含任何图像数据,前面我们在介绍 Surface 的时候说了,图像数据都是从 Surface 获取的。
7. TotalCaptureResult
TotalCaptureResult继承自CaptureResult,功能类似
8. StreamConfigurationMap
获取输出流配置,如:可支持的预览尺寸
9. Image
一个完整的图片缓存,可从该对象中获取YUV、JPEG、RGBA等数据
10. ImageReader
用于从相机打开的通道中读取需要的格式的原始图像数据,可以设置多个ImageReader。
三. 开发步骤
我们按照ICameraManager
定义的接口实现Camera2的API,我们这里只讲关键步骤
1. 打开摄像头
- 获取想要打开的CameraId
- 配置Camera参数,如:预览尺寸、拍照尺寸
- 打开Camera
public void openCamera() {
Log.v(TAG, "openCamera");
if (mCameraDevice != null) {
return;
}
mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
// 相机ID
String cameraId = setUpCameraOutputs(mCameraManager);
if (cameraId == null) return;
startBackgroundThread(); // 对应 releaseCamera() 方法中的 stopBackgroundThread()
mOrientationEventListener.enable();
try {
mCameraCharacteristics = mCameraManager.getCameraCharacteristics(cameraId);
// 每次切换摄像头计算一次就行,结果缓存到成员变量中
initDisplayRotation();
initZoomParameter();
mFacing = mCameraCharacteristics.get(CameraCharacteristics.LENS_FACING);
Logs.i(TAG, "facing:" + mFacing);
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
return;
}
// 打开摄像头
mCameraManager.openCamera(cameraId, mStateCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
选择想要打开的CameraId
private String setUpCameraOutputs(CameraManager cameraManager) {
String cameraId = null;
try {
// 获取相机ID列表
String[] cameraIdList = cameraManager.getCameraIdList();
for (String id : cameraIdList) {
// 获取相机特征
CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(id);
int facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);
if (mCameraId == 0 && facing == CameraCharacteristics.LENS_FACING_BACK) {
cameraId = id;
break;
} else if (mCameraId == 1 && facing == CameraCharacteristics.LENS_FACING_FRONT) {
cameraId = id;
break;
}
}
if (cameraId == null) {
onOpenError(CAMERA_ERROR_NO_ID, "Camera id:" + mCameraId + " not found.");
return null;
}
if (!configCameraParams(cameraManager, cameraId)) {
onOpenError(CAMERA_ERROR_OPEN, "Config camera error.");
return null;
}
} catch (CameraAccessException e) {
onOpenError(CAMERA_ERROR_OPEN, e.getMessage());
return null;
} catch (NullPointerException e) {
onOpenError(CAMERA_ERROR_OPEN, e.getMessage());
return null;
}
return cameraId;
}
配置预览和拍照尺寸
private boolean configCameraParams(CameraManager manager, String cameraId) throws CameraAccessException {
CameraCharacteristics characteristics
= manager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
return false;
}
Size previewSize = getSuitableSize(new ArrayList<>(Arrays.asList(map.getOutputSizes(SurfaceTexture.class))));
Logs.i(TAG, "previewSize: " + previewSize);
mPreviewSize = previewSize;
mPreviewWidth = mPreviewSize.getWidth();
mPreviewHeight = mPreviewSize.getHeight();
Size[] supportPictureSizes = map.getOutputSizes(<