Android开发Android开发探索Camera

Android Camera2 简介

2018-08-18  本文已影响9人  幽客

Camera2简介

在Google 推出Android 5.0的时候, Android Camera API 版本升级到了API2(android.hardware.camera2), 之前使用的API1(android.hardware.camera)就被标为 Deprecated 了. Camera API2相较于API1有很大不同, 并且API2是为了配合HAL3进行使用的, API2有很多API1不支持的特性, 比如:

  1. 更先进的API架构
  2. 可以获取更多的帧(预览/拍照)信息以及手动控制每一帧的参数
  3. 对Camera的控制更加完全(比如支持调整focus distance, 剪裁预览/拍照图片)
  4. 支持更多图片格式(yuv/raw)以及高速连拍
    ......

上面列举的只是一部分, 并且实际功能支持情况要看HAL层是否完成了相关功能, 也就是说API有很多功能来满足拍照/录像需求, 但实际是否能用和具体设备有关.

基本架构

在API架构方面, Camera2和之前的Camera有很大区别, APP和底层Camera之前可以想象成用管道方式连接, 如下图:

38487b61-571f-4b9e-908f-acd99d69f4f1.png

如上图所示, Camera APP 通过CameraCaptureSession发送CaptureRequest, CameraDevices收到请求后返回对应数据到对应的Surface,预览数据一般都是到TextureView, 拍照数据则在ImageReader中, 整体来说就是一个请求--响应过程, 请求完成后, 可以在回调中查询到相应的请求参数和CameraDevice当前状态, 总的来说, Camera2中预览/拍照/录像数据统一由Surface来接收, CaptureRequest代表请求控制的Camera参数, CameraMetadata(CaptureResult)则表示当前返回帧中Camera使用的参数以及当前状态.

使用流程

API使用流程大体如下:

8c75b772-d214-4d2f-94ca-7b4ad877d17f.png
  1. 通过context.getSystemService(Context.CAMERA_SERVICE) 获取CameraManager.
  2. 调用CameraManager .open()方法在回调中得到CameraDevice.
  3. 通过CameraDevice.createCaptureSession() 在回调中获取CameraCaptureSession.
  4. 构建CaptureRequest, 有三种模式可选 预览/拍照/录像.
  5. 通过 CameraCaptureSession发送CaptureRequest, capture表示只发一次请求, setRepeatingRequest表示不断发送请求.
  6. 拍照数据可以在ImageReader.OnImageAvailableListener回调中获取, CaptureCallback中则可获取拍照实际的参数和Camera当前状态.

查询Camera2功能支持情况

上面说过, 不是所以手机都支持完整的Camera2功能, 现在都2018了, Camera2出来都有4年左右了, 但估计还有些中低端手机使用的HAL1, 使用HAL1就会导致Camera2一些高级功能都没法使用了, 下面讲一下如何查询设备对应Camera2的支持情况.
INFO_SUPPORTED_HARDWARE_LEVEL
硬件层面支持的Camera2功能等级, 主要分为5个等级:

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底层坐标不是对应的, 基本关系如下图:


AF_Coordinate.jpg

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, 目前没有录像功能, 后续会加上.

上一篇下一篇

猜你喜欢

热点阅读