前言

从本文开始,笔者将开启一个新的项目,使用camera2接口来做一个简单的相机app。它的主要功能包含图像的拍摄,预览和保存,并在此基础上封装HDRD和HDR算法。

项目GitHub链接:Camera2DEMO

Camera2 API 相关知识

有关camera2框架内部一些细节可以参考这篇博客:Android Camera2教程

简单来说,一次拍照流程:

1、用户发出拍照请求CaptureRequest。CaptureRequest内部包括了本次拍照的参数配置和接收图像数据的内存空间Surface。CaptureRequest 可以配置的信息非常多,包括图像格式、图像分辨率、传感器控制、闪光灯控制、3A 控制等等。

2、Camera2框架是一个Pipeline结构,获取了CaptureRequest的Pipeline实例称为CaptureRequestSession。一个 CameraDevice一次只能开启一个CameraCaptureSession,CameraCaptureSession将拍照请求传递给硬件,最后得到本次拍照结果CaptureResult和图像Image。CaptureResult 是每一次 Capture 操作的结果,里面包括了很多状态信息,包括闪光灯状态、对焦状态、时间戳等等,图像则存入Surface。
在这里插入图片描述

代码实现

实现一个基于Camera2 API的相机一般分为如下几步:

1、获取相机权限:这一步要在AndroidManifest.xml文件中添加相机权限。

2、设置相机管理器:创建相机管理器实例并获取相机设备的ID。

3、打开相机:通过相机管理器打开相机设备。

4、创建预览会话:创建相机预览会话以显示实时预览图像。

5、实时预览显示:将相机预览的图像输出到指定的视图上。

但是为了方便后续图像处理算法的封装,我们会再上述步骤的基础上做些许调整。

可以看下总体流程,具体实现细节我们会慢慢描述:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.fragment_camera2_basic);
        textureView = findViewById(R.id.texture);
        btn_photo = findViewById(R.id.picture);
        btn_photo.setOnClickListener(OnClick);
    }
    @Override
    protected void onResume() {
        super.onResume();

        //开启相机线程
        startCameraThread();
        //textureView回调
        if (!textureView.isAvailable()) {
            textureView.setSurfaceTextureListener(mTextureListener);
        } else {
            startPreview();//开启预览
        }
    }

一、首先初始化要用到的变量

这里大概浏览下即可,后续用到时候还会介绍:

private static final String TAG = "Phildebug";
private TextureView textureView;//继承自View,必须在硬件加速窗口中
private HandlerThread handlerThread;//运行不应该被阻塞UI的任务的附加线程
private Handler mCameraHandler;//用于在后台运行任务
private CameraManager cameraManager;//摄像头管理器,用于检测打开摄像头
private Size previewSize;//最佳的预览尺寸
private Size mCaptureSize;//最佳的拍照尺寸
private String mCameraId;//设备ID
private CameraDevice cameraDevice;//该实例代表摄像头
private CaptureRequest.Builder captureRequestBuilder;
private CaptureRequest.Builder previewRequestBuilder;
private CaptureRequest captureRequest;//捕获请求
private CaptureRequest previewRequest;//捕获请求
private CameraCaptureSession mCameraCaptureSession;//预览拍照先通过它创建session
private CameraCaptureSession mCameraPreviewSession;//预览HDR先通过它创建session
private Button btn_photo;//拍照按钮
private ImageReader imageCaptureReader;//用于图像保存
private ImageReader imagePreviewReader;//用于图像HDRD
private static final SparseArray ORIENTATION = new SparseArray();//图片旋转方向
static {
    ORIENTATION.append(Surface.ROTATION_0, 90);
    ORIENTATION.append(Surface.ROTATION_90, 0);
    ORIENTATION.append(Surface.ROTATION_180, 270);
    ORIENTATION.append(Surface.ROTATION_270, 180);
}
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static String[] PERMISSIONS_STORAGE = {//对手机存储权限
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE
};
private int exposure = 0;//记录图像输入HDR函数的顺序

二、建立TextView监听器来初始化相机

SurfaceTexture是Android上做渲染的核心组件,它是 Surface 和 OpenGL ES 纹理的组合,用于提供输出到 GLES 纹理的 Surface。
在这里插入图片描述

SurfaceTexture的最常见应用场景是作为相机或者视频解码器的输出,如下:

 /***
     * TextureView生命周期响应
     */
    TextureView.SurfaceTextureListener mTextureListener = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) {
            //SurfaceTexture组件可用的时候,设置相机参数,并打开摄像头
            //设置摄像头参数
            setUpCamera(width, height);
            //打开摄像头,默认后置
            openCamera();
        }

        @Override
        public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) {
            //尺寸发生变化的时候
        }

        @Override
        public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
            //组件被销毁的时候
            return false;
        }

        @Override
        public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
            //组件更新的时候
        }
    };

当SurfaceTexture组件可用时候,就可以对相机进行初始化,然后打开相机。

三、初始化相机

使用相机之前,我们需要创建相机管理器实例,然后获取到相机设备的ID和相机的相关参数。

CameraCharacteristics类是使用Camera2 API时用于描述相机设备的特性信息的类。它提供了关于相机设备的各种参数和功能的元数据。以下是一些常见的CameraCharacteristics包含的信息:

  1. 相机ID(Camera ID):相机设备的唯一标识符,用于在多个相机设备中进行区分。
  2. 摄像头方向(Camera Facing):描述相机设备的方向,包括前置摄像头、后置摄像头等。
  3. 传感器尺寸(Sensor Size):相机传感器的物理尺寸,通常以毫米为单位表示。
  4. 最大图像输出尺寸(Max Image Output Size):相机设备能够生成的最大图像输出尺寸,通常以像素为单位表示。
  5. 支持的图像格式(Supported Image Formats):相机设备支持的图像格式,例如JPEG、YUV等。
  6. 支持的预览分辨率(Supported Preview Resolutions):相机设备支持的预览图像分辨率列表。
  7. 支持的拍照分辨率(Supported Capture Resolutions):相机设备支持的拍照图像分辨率列表。
  8. 自动对焦模式(Auto Focus Modes):相机设备支持的自动对焦模式,例如连续自动对焦、自动对焦单次触发等。
  9. 支持的曝光模式(Exposure Modes):相机设备支持的曝光模式,例如自动曝光、手动曝光等。
  10. 支持的闪光灯模式(Flash Modes):相机设备支持的闪光灯模式,例如自动闪光、打开闪光灯等。
  11. 支持的帧率范围(Supported Frame Rates):相机设备支持的帧率范围,用于预览和录制视频。
  12. 图像稳定化支持(Image Stabilization Support):相机设备是否支持图像稳定化功能。

检查是否支持手动曝光模式,是因为我们后续使用HDR算法时候,需要申请三张不同曝光度的照片,而这个曝光度需要手动设置。

为了使相机拍摄图像可以正确在手机上预览,我们还需要使用map.getOutputSizes()获取到相机设备支持的输出尺寸列表,以便应用程序可以选择最合适的尺寸来配置相机设备的输出。

设置好相机参数之后,我们还需要建立两个imageReader来分别存储和预览照片。

 /***
     * 设置摄像头参数
     */
    private void setUpCamera(int width, int height) {
        cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        //拿到摄像头的id
        try {
            for (String cameraId : cameraManager.getCameraIdList()) {
                //得到摄像头的参数
                CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId);

                // 检查是否支持手动控制曝光模式
                int[] availableAEModes = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES);
                boolean isManualExposureSupported = false;
                for (int aeMode : availableAEModes) {
                    if (aeMode == CameraCharacteristics.CONTROL_AE_MODE_OFF) {
                        isManualExposureSupported = true;
                        break;
                    }
                }

                // 检查是否支持曝光补偿
                boolean isExposureCompensationSupported = cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_COMPENSATION_RANGE) != null;

                // 输出支持情况
                Log.d(TAG, "手动曝光支持: " + isManualExposureSupported);
                Log.d(TAG, "曝光补偿支持: " + isExposureCompensationSupported);

                Integer facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);
                if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
                    continue;
                }
                StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                if (map != null) { //找到摄像头能够输出的,最符合我们当前屏幕能显示的最小分辨率
                    previewSize = new Size(1920, 1080);
                    mCaptureSize = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.YUV_420_888)), new Comparator<Size>() {
                        @Override
                        public int compare(Size o1, Size o2) {
                            return Long.signum(o1.getWidth() * o1.getHeight() - o2.getWidth() * o2.getHeight());
                        }
                    });
                }
                //建立ImageReader来存储和预览照片
                setUpCaptureReader();
                setUpPreviewReader();

                mCameraId = cameraId;
                break;
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

四、打开相机

打开相机时候,检查是否有写入手机外部存储的权限,不然到时候APP会闪退。相机成功打开之后,调用预览方法,让画面实时出现在手机屏幕上。

 /***
     * 打开摄像头
     */
    private void openCamera() {
        if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.CAMERA, android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
            return;
        }
        try {
            cameraManager.openCamera(mCameraId, mStateCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
    CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) { //摄像头打开
            cameraDevice = camera;
            startPreview();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) { //摄像头关闭
            cameraDevice.close();
            cameraDevice = null;
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {//摄像头出现错误
            cameraDevice.close();
            cameraDevice = null;
        }
    };

关于预览的实现,我们下篇文章再见,如果您喜欢本专栏文章,还请点赞关注多多支持,下期见!

Logo

欢迎加入我们的广州开发者社区,与优秀的开发者共同成长!

更多推荐