• Camera2必知必会


    引言

    一切源于在项目过程中的一个Bug:我的需求是在MainActivity 实现自动预览,也可以点击跳到签到SignedActivity去实现拍照签到,第一次进入界面的时候都是正常的,但是有时候返回来的时候预览失败,即从MainActivity跳转到SignedActivity偶尔预览失败和从SignedActivity返回MainActivity偶尔失败,都是报(CAMERA_IN_USE)ERRO=1的错误,奇怪的是的的确确做了完全释放操作,加上以前用的更多的是Camera api 对于Camer2 的机制没有完整去研究过,一下子懵了,于是乎先去找了Stack Overflow,查到一个解决方案是:"我弃用了新API,换回旧API",ORZ,找了其他的也没有答案,可是我不服呀,我就把官方的文档全部啃了一遍,于是乎便有了以下的理解,我想如果你不懂得怎么使用Camera2的话,这篇绝对值得你去阅读,你会发现Camera2 并非像大多数说得那样使用起来很复杂。

    一、Camera2架构概述

    全新的android.hardware.Camera2 。Android 5.0对拍照API进行了全新的设计,新增了全新设计的Camera 2 API,这些API不仅大幅提高了Android系统拍照的功能,还能支持RAW照片输出,甚至允许程序调整相机的对焦模式、曝光模式、快门等。

     

    如上图所示,Camera2 API相比原来android.hardware.Camera API 在架构上有了很大的改变,虽然让手机拍照功能更加强大,但同时也增加了开发复杂度了,从以下的Camera2 架构图中所有参与类角色不能看出。
     

    引入了管道的概念将安卓设备和摄像头之间联系起来,系统向摄像头发送 Capture 请求,而摄像头会返回 CameraMetadata,这一切建立在一个叫作 CameraCaptureSession 的会话中

    二、Camera2架构主要的类角色说明

    在Camera2 架构在核心参与类角色有:CameraManagerCameraDeviceCameraCharacteristicsCameraRequest与CameraRequest.BuilderCameraCaptureSession以及CaptureResult

    1、CameraManager

    位于android.hardware.camera2.CameraManager下,也是Android 21(5.0)添加的,和其他系统服务一样通过 Context.getSystemService(CameraManager.class ) 或者Context.getSystemService(Context.CAMERA_SERVICE) 来完成初始化,主要用于管理系统摄像头:

    • 通过getCameraIdList()方法获取Android设备的摄像头列表

    • getCameraCharacteristics(String cameraId)获取摄像头的详细参数和支持的功能

    • ** openCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler)打开指定Id的摄像头**

    CameraManager manager = (CameraManager)context.getSystemService(Context.CAMERA_SERVICE);
    

    2、CameraDevice

    CameraDevice是Camera2中抽象出来的一个对象,直接与系统硬件摄像头相联系。因为不可能所有的摄像头都会支持高级功能(即摄像头功能可被分为limit 和full 两个级别),当摄像头处于limited 级别时候,此时Camera2和早期的Camera功能差不多,除此之外在Camera2架构中,CameraDevice还承担其他两项重要任务:

    • 通过CameraDevice.StateCallback监听摄像头的状态(主要包括onOpened、onClosed、onDisconnected、onErro四种状态)

    • 管理CameraCaptureSession,-通过方法createCaptureSession(List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler)方法和createReprocessableCaptureSession(InputConfiguration inputConfig, List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler)方法创建会话 (其中第三个参数: The handler on which the callback should be invoked, or null to use the current thread's looper.),通常会在CameraDevice.StateCallback中调用对应方法创建预览会话。

    • 管理CaptureRequest,主要包括通过createCaptureRequest(int templateType)创建捕获请求,在需要预览、拍照、再次预览的时候都需要通过创建请求来完成。

    3、CameraCaptureSession

    正如前面所说,系统向摄像头发送 Capture 请求,而摄像头会返回 CameraMetadata,这一切都是在由对应的CameraDevice创建的CameraCaptureSession 会话完成,当程序需要预览、拍照、再次预览时,都需要先通过会话。(A configured capture session for a CameraDevice, used for capturing images from the camera or reprocessing images captured from the camera in the same session previously.A CameraCaptureSession is created by providing a set of target output surfaces to createCaptureSession, or by providing an InputConfiguration and a set of target output surfaces to createReprocessableCaptureSession for a reprocessable capture session. Once created, the session is active until a new session is created by the camera device, or the camera device is closed.)CameraCaptureSession一旦被创建,直到对应的CameraDevice关闭才会死掉。虽然CameraCaptureSession会话用于从摄像头中捕获图像,但是只有同一个会话才能再次从同一摄像头中捕获图像。另外,创建会话是一项耗时的异步操作,可能需要几百毫秒,因为它需要配置相机设备的内部管道并分配内存缓冲区以将图像发送到所需的目标,因而createCaptureSession和createReprocessableCaptureSession会将随时可用的CameraCaptureSession发送到提供的监听器的onConfigured回调中。如果无法完成配置,则触发onConfigureFailed回调,并且会话将不会变为活动状态。最后需要注意的是,如果摄像头设备创建了一个新的会话,那么上一个会话是被关闭的,并且会回调与其关联的onClosed,如果不处理好,当会话关闭之后再次调用会话的对应方法那么所有方法将会跑出IllegalStateException异常。关闭的会话清除任何重复的请求(和调用了stopRepeating()方法类似),但是在新创建的会话接管并重新配置摄像机设备之前,关闭的会话仍然会正常完成所有正在进行的捕获请求。简而言之,在Camera2中CameraCaptureSession承担很重要的角色:

    • 管理CameraCaptureSession.StateCallback状态回调,用于接收有关CameraCaptureSession状态的更新的回调对象,主要回调方法有两个当CameraDevice 完成配置,对应的会话开始处理捕获请求时触发onConfigured(CameraCaptureSession session)方法,反之配置失败时候触发onConfigureFailed(CameraCaptureSession session)方法。

    • 管理CameraCaptureSession.CaptureCallback捕获回调,用于接收捕获请求状态的回调,当请求触发捕获已启动时;捕获完成时;在捕获图像时发生错误的情况下;都会触发该回调对应的方法。

    • 通过调用方法capture(CaptureRequest request, CameraCaptureSession.CaptureCallback listener, Handler handler)提交捕获图像请求(Submit a request for an image to be captured by the camera device.)即拍照,其中该请求定义了捕获单个图像的所有参数,包括传感器,镜头,闪光灯和后处理参数,每一次请求的结果将产生一个CaptureResult,可以为一个或多个Surface生成新的帧,然后通过CaptureRequest.Builder的addTarget(Surface)方法附着到对应的Surface上显示,而且这个参数Surface必须是会话创建时候的一个子集,会话一次可以处理多个常规和重新处理请求。但如果只有常规请求或重新处理请求在进行,则以先进先出的顺序处理它们;如果两者都在进行中则分别以各自的先进先出顺序处理他们;然而,处理常规请求和重新处理请求的顺序并不是特定的,换言之,一个常规请求在下一个常规请求提交前被处理,同理重新处理请求也一样,但是一个常规请求不一定是在下一个重新处理请求提交之前被处理。通过capture方法提交的请求处理优先级比通过其他方式( setRepeatingRequest(CaptureRequest, CameraCaptureSession.CaptureCallback, Handler) 或者setRepeatingBurst(List, CameraCaptureSession.CaptureCallback, Handler))提交的请求的处理优先级高,一旦当前的repeat / repeatBurst处理完成,就会被处理。最后一点,所有CaptureSession可用于从相机捕获图像,但只有由createReprocessableCaptureSession创建的会话才可以提交重新处理捕获请求,
      将重新处理请求提交到常规捕获会话将导致IllegalArgumentException。

    • 通过调用方法setRepeatingRequest(CaptureRequest request, CameraCaptureSession.CaptureCallback listener, Handler handler)请求不断重复捕获图像,即实现预览

    • 通过方法调用stopRepeating()实现停止捕获图像,即停止预览

     
    这里写图片描述

    4、CameraCharacteristics

    描述Cameradevice属性的对象,可以使用CameraManager通过getCameraCharacteristics(String cameraId)进行查询。

    5、CameraRequest和CameraRequest.Builder

    CameraRequest代表了一次捕获请求,而CameraRequest.Builder用于描述捕获图片的各种参数设置,包含捕获硬件(传感器,镜头,闪存),对焦模式、曝光模式,处理流水线,控制算法和输出缓冲区的配置。,然后传递到对应的会话中进行设置,CameraRequest.Builder则负责生成CameraRequest对象。当程序调用setRepeatingRequest()方法进行预览时,或调用capture()方法进行拍照时,都需要传入CameraRequest参数。CameraRequest可以通过CameraRequest.Builder来进行初始化,通过调用createCaptureRequest来获得。

    6、CaptureResult

    CaptureRequest描述是从图像传感器捕获单个图像的结果的子集的对象。(CaptureResults are produced by a CameraDevice after processing a CaptureRequest)当CaptureRequest被处理之后由CameraDevice生成。

    7、Camera2 主要角色之间的联系

    CameraManager处于顶层管理位置负责检测获取所有摄像头及其特性传入指定的CameraDevice.StateCallback回调打开指定摄像头CameraDevice是负责管理抽象对象,包括监听Camera 的状态回调CameraDevice.StateCallback创建CameraCaptureSession和CameraRequestCameraCaptureSession用于描述一次图像捕获操作,主要负责监听自己会话的状态回调CameraCaptureSession.StateCallbackCameraCaptureSession.CaptureCallback捕获回调,还有发送处理CameraRequestCameraRequest则可以看成是一个"JavaBean"的作用用于描述希望什么样的配置来处理这次请求;最后三个回调用于监听对应的状态。

    三、Camera2 使用步骤

    • 初始化并启动HandlerThread(因为创建会话是好时的操作不宜放在主线程)
    private void startBackgroundThread() {
        LogUtil.showModelLog("摄像头"+"启动HandlerThread");
        mBackgroundThread = new HandlerThread("CameraBackground");
        mBackgroundThread.start();
        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
    }
    
    • 实现相关Surface的回调(是为了能在相关Surface可以用的时候自动开启预览)
        private final TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {
    
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
                LogUtil.showModelLog("摄像头"+"当TextureView 可用时");
                //3.在TextureView可用的时候尝试打开摄像头
                openCamera(width, height);
            }
    
            @Override
            public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
                configureTransform(width, height);
            }
    
            @Override
            public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
                return true;
            }
    
            @Override
            public void onSurfaceTextureUpdated(SurfaceTexture texture) {
            }
        };
    
    • 实现CameraDevice.StateCallback回调

    • 初始化CameraManager对象,进行一些参数设置工作之后,使用CameraManager实例打开Camera

    manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
    
    • 当在CameraDevice.StateCallback回调中监听到Camera成功被打开时,初始化CameraDevice并通过CameraDevice创建捕获图像请求并把对应的Surface附着到请求上,完成CameraRequest的创建。

    • 实现CameraCaptureSession.StateCallbackCameraCaptureSession.CaptureCallback回调

        private CameraCaptureSession.CaptureCallback mCaptureCallback
                = new CameraCaptureSession.CaptureCallback() {
    
            private void process(CaptureResult result) {
                switch (mState) {
                    case STATE_PREVIEW: {
                        LogUtil.showModelLog("摄像头"+"在CameraCaptureSession.CaptureCallback捕获回调处理CaptureResult,在process里预览成功工作");
                        // We have nothing to do when the camera preview is working normally.
                        break;
                    }
                    case STATE_WAITING_LOCK: {
    
                        Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
                        if (afState == null) {
                            captureStillPicture();
                        } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
                                CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
                            // CONTROL_AE_STATE can be null on some devices
                            Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                            if (aeState == null ||
                                    aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
                                mState = STATE_PICTURE_TAKEN;
                                captureStillPicture();
                            } else {
                                runPrecaptureSequence();
                            }
                        }
                        break;
                    }
                    case STATE_WAITING_PRECAPTURE: {
    
                        // CONTROL_AE_STATE can be null on some devices
                        Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                        if (aeState == null ||
                                aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
                                aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
                            mState = STATE_WAITING_NON_PRECAPTURE;
                        }
                        break;
                    }
                    case STATE_WAITING_NON_PRECAPTURE: {
                        // CONTROL_AE_STATE can be null on some devices
                        Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
                        if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
                            mState = STATE_PICTURE_TAKEN;
                            captureStillPicture();
                        }
                        break;
                    }
                    default:
                        break;
                }
            }
    
            @Override
            public void onCaptureProgressed(@NonNull CameraCaptureSession session,
                                            @NonNull CaptureRequest request,
                                            @NonNull CaptureResult partialResult) {
                process(partialResult);
                LogUtil.showDebugLog("onCaptureProgressed");
            }
    
            @Override
            public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                           @NonNull CaptureRequest request,
                                           @NonNull TotalCaptureResult result) {
                process(result);
                LogUtil.showModelLog("摄像头"+"在CameraCaptureSession.CaptureCallback捕获回调处理CaptureResult");
    
            }
    
        };
    
    • 再通过CameraDevice创建CameraCaptureSession,在会话配置完成并开始处理请求时候会触发CameraCaptureSession.StateCallback的onConfiged方法,在完成请求的参数配置后发送CameraRequest
        private void createCameraPreviewSession() {
            try {
                SurfaceTexture texture = cameraViewSigned.getSurfaceTexture();
                assert texture != null;
    
                // We configure the size of default buffer to be the size of camera preview we want.
                texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
    
                // This is the output Surface we need to start preview.
                Surface surface = new Surface(texture);
    
                LogUtil.showModelLog("摄像头"+"通过调用CameraDevice.createCaptureRequest方法创建mPreviewRequestBuilder预览请求,并把要附着的Surface通过addTarget 封到请求中");
                // We set up a CaptureRequest.Builder with the output Surface.
                mPreviewRequestBuilder= mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                mPreviewRequestBuilder.addTarget(surface);
                LogUtil.showModelLog("摄像头"+"通过调用CameraDevice.createCaptureSession方法并传入CameraCaptureSession.StateCallbac创建预览会话mCaptureSession");
                // Here, we create a CameraCaptureSession for camera preview.
                mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
                        new CameraCaptureSession.StateCallback() {
    
                            @Override
                            public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
                                // The camera is already closed
                                if (null == mCameraDevice) {
                                    return;
                                }
                                LogUtil.showModelLog("摄像头"+"当预览会话完成配置并开始处理请求时候,把闪光灯,模式等参数封装到预览请求");
                                // When the session is ready, we start displaying the preview.
                                mCaptureSession = cameraCaptureSession;
                                try {
                                    // Auto focus should be continuous for camera preview.
                                    mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                                            CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                                    // Flash is automatically enabled when necessary.
                                    setAutoFlash(mPreviewRequestBuilder);
    
                                    // Finally, we start displaying the camera preview.
                                    mPreviewRequest = mPreviewRequestBuilder.build();
                                    LogUtil.showModelLog("摄像头"+"接着把预览请求设置到预览会话mCaptureSession,传入CameraCaptureSession.CaptureCallback捕获回调并发出不断捕获图像的请求");
                                    mCaptureSession.setRepeatingRequest(mPreviewRequest,
                                            mCaptureCallback, mBackgroundHandler);
                                } catch (CameraAccessException e) {
                                    e.printStackTrace();
                                }
                            }
    
                            @Override
                            public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
                                showToast("拍照失败");
                            }
                        }, null
                );
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    
    • 此时CameraCaptureSession.CaptureCallback监听被激活,基本完成预览流程

    • 然后拍照的流程就很简单了,本质上就是改变了CameraCaptureSession.CaptureCallback里状态常量的值,然后再次发送请求,然后触发了回调,此时状态常量已经改变,就会执行不同的逻辑,太简单了不想多说,最后附上一张Camera2 预览的完整流程日志。

     
    这里写图片描述

    CameraManager处于顶层管理位置负责检测检测获取所有摄像头并设置输出参数,传入指定的CameraDevice.StateCallback回调,然后打开指定摄像头,并触发CameraDevice.StateCallback中的onOpened方法,并在onOpened方法里开始通过调用创建预览会话,,CameraDevice负责创建请求CameraCharacteristicsCameraRequest与CameraRequest.BuilderCameraCaptureSession以及CaptureResult则可以看成是一个JavaBean的作用用于描述以什么样的配置来处理这次请求。



  • 相关阅读:
    数1的个数
    找水王2
    书店促销
    返回一个二维整数数组中最大联通子数组的和
    敏捷软件开发读书笔记(三)
    软件工程团队开发——第一次冲刺会议总结
    返回一个二维整数数组中最大联通子数组的和
    结对项目开发电梯调度
    《最后期限》——读书笔记03
    最后期限——阅读笔记2
  • 原文地址:https://www.cnblogs.com/fuyaozhishang/p/9752581.html
Copyright © 2020-2023  润新知