Camera2简介
在Google 推出Android 5.0的时候, Android Camera API 版本升级到了API2(android.hardware.camera2), 之前使用的API1(android.hardware.camera)就被标为 Deprecated 了. Camera API2相较于API1有很大不同, 并且API2是为了配合HAL3进行使用的, API2有很多API1不支持的特性, 比如:
- 更先进的API架构
- 可以获取更多的帧(预览/拍照)信息以及手动控制每一帧的参数
- 对Camera的控制更加完全(比如支持调整focus distance, 剪裁预览/拍照图片)
- 支持更多图片格式(yuv/raw)以及高速连拍
......
上面列举的只是一部分, 并且实际功能支持情况要看HAL层是否完成了相关功能, 也就是说API有很多功能来满足拍照/录像需求, 但实际是否能用和具体设备有关.
基本架构
在API架构方面, Camera2和之前的Camera有很大区别, APP和底层Camera之前可以想象成用管道方式连接, 如下图:
如上图所示, Camera APP 通过CameraCaptureSession发送CaptureRequest, CameraDevices收到请求后返回对应数据到对应的Surface,预览数据一般都是到TextureView, 拍照数据则在ImageReader中, 整体来说就是一个请求--响应过程, 请求完成后, 可以在回调中查询到相应的请求参数和CameraDevice当前状态, 总的来说, Camera2中预览/拍照/录像数据统一由Surface来接收, CaptureRequest代表请求控制的Camera参数, CameraMetadata(CaptureResult)则表示当前返回帧中Camera使用的参数以及当前状态.
使用流程
API使用流程大体如下:
- 通过
context.getSystemService(Context.CAMERA_SERVICE)
获取CameraManager
. - 调用
CameraManager .open()
方法在回调中得到CameraDevice
. - 通过
CameraDevice.createCaptureSession()
在回调中获取CameraCaptureSession
. - 构建
CaptureRequest
, 有三种模式可选 预览/拍照/录像. - 通过
CameraCaptureSession
发送CaptureRequest
, capture表示只发一次请求, setRepeatingRequest表示不断发送请求. - 拍照数据可以在
ImageReader.OnImageAvailableListener
回调中获取,CaptureCallback
中则可获取拍照实际的参数和Camera当前状态.
查询Camera2功能支持情况
上面说过, 不是所以手机都支持完整的Camera2功能, 现在都2018了, Camera2出来都有4年左右了, 但估计还有些中低端手机使用的HAL1, 使用HAL1就会导致Camera2一些高级功能都没法使用了, 下面讲一下如何查询设备对应Camera2的支持情况.
INFO_SUPPORTED_HARDWARE_LEVEL
硬件层面支持的Camera2功能等级, 主要分为5个等级:
- INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
- INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
- INFO_SUPPORTED_HARDWARE_LEVEL_FULL
- INFO_SUPPORTED_HARDWARE_LEVEL_3
- INFO_SUPPORTED_HARDWARE_LEVEL_EXTERNAL
LEVEL_LEGACY: 向后兼容模式, 如果是此等级, 基本没有额外功能, HAL层大概率就是HAL1(我遇到过的都是)
LEVEL_LIMITED: 有最基本的功能, 还支持一些额外的高级功能, 这些高级功能是LEVEL_FULL的子集
LEVEL_FULL: 支持对每一帧数据进行控制,还支持高速率的图片拍摄
LEVEL_3: 支持YUV后处理和Raw格式图片拍摄, 还支持额外的输出流配置
LEVEL_EXTERNAL: API28中加入的, 应该是外接的摄像头, 功能和LIMITED类似
各个等级从支持的功能多少排序为: LEGACY < LIMITED < FULL < LEVEL_3
获取代码如下:
// CameraCharacteristics 可通过 CameraManager.getCameraCharacteristics() 获取
private int isHardwareSupported(CameraCharacteristics characteristics) {
Integer deviceLevel = characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
if (deviceLevel == null) {
Log.e(TAG, "can not get INFO_SUPPORTED_HARDWARE_LEVEL");
return -1;
}
switch (deviceLevel) {
case CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL:
Log.w(TAG, "hardware supported level:LEVEL_FULL");
break;
case CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY:
Log.w(TAG, "hardware supported level:LEVEL_LEGACY");
break;
case CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3:
Log.w(TAG, "hardware supported level:LEVEL_3");
break;
case CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED:
Log.w(TAG, "hardware supported level:LEVEL_LIMITED");
break;
}
return deviceLevel;
}
REQUEST_AVAILABLE_CAPABILITIES
上面讲的几个等级是从整体上说明Camera2支持情况, 我们还可以查询更多细节功能,REQUEST_AVAILABLE_CAPABILITIES 则可以知道具体支持哪些实际功能, 具体有如下功能:
BACKWARD_COMPATIBLE | READ_SENSOR_SETTINGS |
---|---|
MANUAL_SENSOR | BURST_CAPTURE |
MANUAL_POST_PROCESSING | YUV_REPROCESSING |
RAW | DEPTH_OUTPUT |
PRIVATE_REPROCESSING | CONSTRAINED_HIGH_SPEED_VIDEO |
各个功能具体含义请参考官网的解释 :官网 的解释, 我没有深入研究.
大多数支持等级为 LEGACY 的设备, 只支持 BACKWARD_COMPATIBLE , 也就是说前面提到的Camera2新功能都不支持......
Camera2 AE/AF Region
在使用Camera2 API过程中, 设置测光和对焦区域这部分刚开始一直不知道该怎么写代码, 后面折腾了一番, 终于找到正确的方法了, 在此记录一下.
AE/AF 区域需要通过用户在屏幕上的点击位置来进行设置, 因此我们要做的就是将屏幕点击点映射到底层对焦和测光位置点, 同时发送请求触发对焦这个动作. 首先要知道一点就是, 屏幕点击点坐标和Camera底层坐标不是对应的, 基本关系如下图:
x,y坐标系表示屏幕坐标, x1,y1表示Camera对应坐标
可见屏幕点击和底层有个90度旋转关系, 实际设置AF/AE区域步骤如下:
1.首先获取SENSOR_INFO_ACTIVE_ARRAY_SIZE
, 即底层Camera坐标点的范围, API中通过一个Rect
表示
characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)
得到``Rect```通常为支持的最大图片尺寸, 比如(0, 0, 4160, 3120)
2.坐标映射
我们需要将屏幕点击点映射到 SENSOR_INFO_ACTIVE_ARRAY_SIZE
对应的Rect
中, 从图中可以很容易看出, 坐标转换关系为 : x1 = y , y1 = previewWidth - x
, 坐标转换完成后只需乘以 图片宽度/预览宽度
的比例即可得到转换后的坐标, 然后以坐标点为中心生成一个矩形, 这个矩形就是我们要的结果, 实际代码片段如下:
private MeteringRectangle calcTapAreaForCamera2(CameraCharacteristics c, int areaSize, int
weight) {
// 获取Size
Rect rect = c.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
Log.d(TAG, "active Rect:" + rect.toString());
Rect newRect;
int leftPos, topPos;
// 坐标转换
float newX = currentY;
float newY = previewWidth - currentX;
// 大小转换
leftPos = (int) ((newX / previewHeight) * rect.right);
topPos = (int) ((newY / previewWidth) * rect.bottom);
// 以坐标点为中心生成一个矩形, 需要防止上下左右的值溢出
int left = clamp(leftPos - areaSize, 0, rect.right);
int top = clamp(topPos - areaSize, 0, rect.bottom);
int right = clamp(leftPos + areaSize, leftPos, rect.right);
int bottom = clamp(topPos + areaSize, topPos, rect.bottom);
newRect = new Rect(left, top, right, bottom);
Log.d(TAG, newRect.toString());
// 构造MeteringRectangle
return new MeteringRectangle(newRect, weight);
}
private int clamp(int x, int min, int max) {
if (x > max) {
return max;
}
if (x < min) {
return min;
}
return x;
}
注: 此处坐标映射可以通过Matrix进行, 熟悉Matrix的同学可以尝试下
3.设置AE/AF区域并触发对焦, 代码片段如下
public void startControlAFRequest(MeteringRectangle rect,
CameraCaptureSession.CaptureCallback captureCallback) {
MeteringRectangle[] rectangle = new MeteringRectangle[]{rect};
// 对焦模式必须设置为AUTO
mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_AUTO);
//AE
mPreviewBuilder.set(CaptureRequest.CONTROL_AE_REGIONS,rectangle);
//AF 此处AF和AE用的同一个rect, 实际AE矩形面积比AF稍大, 这样测光效果更好
mPreviewBuilder.set(CaptureRequest.CONTROL_AF_REGIONS,rectangle);
try {
// AE/AF区域设置通过setRepeatingRequest不断发请求
mSession.setRepeatingRequest(mPreviewBuilder.build(), null, mHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
//触发对焦
mPreviewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,CaptureRequest.CONTROL_AF_TRIGGER_START);
try {
//触发对焦通过capture发送请求, 因为用户点击屏幕后只需触发一次对焦
mSession.capture(mPreviewBuilder.build(), captureCallback, mHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
总结
Camera2 API和旧的Camera API区别很大, 刚开始用可能会很不习惯, 但Camera2有很多优势, 提供了非常多的参数供我们控制, 后面API1可能会被移除, 所以可以尽早将项目用Camera2重写, 另外如果对API和HAL版本对应关系不清楚的, 可以参考我之前写的文章 Android Camera API和HAL版本对应关系.
基于Camera2 API, 我写了个Demo, 功能还算完善, 有兴趣的可以看下: Github: Camera2, 目前没有录像功能, 后续会加上.