Camera2学习笔记

Camera2学习笔记

Camera API2 使用流程图

Camera2主要API
CameraManager 相机的管理类通过Context.getSystemService获取
1. getCameraCharacteristrics  查询Camera有哪些功能,这个对象是不可改变的
2. getCameraExtensionCharacteristics  查询某个Camera的Extension能力
3. getConcurrentCameraIds  获取可以同时配置会话(Session)的Camera的ID
4. getCameraIdList  获取当前可用的Camera的ID
5. openCamera  打开指定ID的Camera
6. registerAvailabilityCallback  注册一个监听回调来监听Camera的状态,是否可用
7. unregisterAvailabilityCallback  注销监听回调
CameraCharacteristics 记录了相机的详细信息,比如支持的图片格式,支持聚焦功能等
1. get(Key<T> key) 根据Key获取Value值
2. getKeys() 获取所有CameraCharacteristics支持的Key集合
3. getAvailableCaptureRequestKes() 获取所有CaptureRequest支持的Key集合
4. getAvailableCaptureResultKeys() 获取所有CaptureResult支持的Key集合
5. getPhysicalCameraIds() 获取当前逻辑Camera对应的物理Camera集合(我们手机后面实实在在的摄像头就是物理Camera,我们把任意几个物理Camera组合叫做逻辑Camera)
CameraDevice 是已打开的Camera设备
1. close 以最快的速度关闭Camera
2. getId 获取当前Camera对应的ID
3. createCaptureRequest(int templateType) 根据templateType创建CaptureRequest.Builder
4. createCaptureSession(SessionConfiguration config) 根据config创建CameraCaptureSession
5. setCameraAudioRestriction(int mode) 设置音频限制模式,比如屏蔽notifications的震动和声音
6. getCameraAudioRestriction 获取当前使用的音频限制模式
CameraDevice.StateCallback 一个接收相机更新状态的回调
这些状态更新包括有关设备完成启动,设备断开、关闭、设备错误的通知
1. ERROR_CAMERA_DEVICE	表示摄像头设备遇到了致命错误
2. ERROR_CAMERA_DISABLED 表示由于设备策略而无法打开摄像头设备
3.	ERROR_CAMERA_IN_USE	表示摄像头设备已经在使用中
4. ERROR_CAMERA_SERVICE	表示摄像头服务遇到了致命错误
5. ERROR_MAX_CAMERAS_IN_USE 因为打开了太多摄像头设备导致无法打开该设备
CameraCaptureSession 这是一个相机会话,与Camera设备建立通道,后面对Camera设备的控制都是通过这个通道
通过 CameraDevice 类的 createCaptureSession() 方法创建,并在回调的 onConfigured(CameraCaptureSession session) 方法中获取实例

1. capture 发送一个请求到Camera底层,参数Handler指定回调线程
2. captureSingleRequest 发送一个请求到C底层,参数Executor指定回调线程
3. captureBurst 发送一组请求到Camera底层,参数Handler指定回调线程
4. captureBurstRequests 发送一组请求到Camera底层,参数Executor指定回调线程
5. setRepeatingRequest 发送一组重复请求到Camera底层,参数Handler指定回调线程
6. setSingleRepeatingRequest 发送一组重复请求到Camera底层,参数Executor指定回调线程
7. setRepeatingBurst 发送一组重复请求到Camera底层,参数Handler指定回调线程
8. setRepeatingBurstRequest 发送一组重复请求到Camera底层,参数Executo指定回调线程
9. stopRepeating 通知相机服务不要向HAL发送请求了
10. abortCaptures 通知相机服务不要向HAL发送请求了,并且让HAL放弃还没有处理完的请
11. supportsOfflineProcessing(Surface surface) 判断surface是否支持离线处理模式
12. switchToOffline 将会话切换到离线处理模式
13. isReprocessable 判断是否支持重新处理
CameraCaptureSession.StateCallback 当相机捕获图像的状态发生变化时,会回调这个方法
其中只有 onConfigured 和 onConfigureFailed 两个方法是抽象的(必须重写),其余均有空实现

1. onConfigureFailed(CameraCaptureSession session)	会话无法按照相应的配置发起请求时回调
2. onConfigured(CameraCaptureSession session)	相机设备完成配置并开始处理捕捉请求时回调
3. onActive(CameraCaptureSession session)	在 onConfigured 后执行,即开始真的处理捕捉请求了
4. onCaptureQueueEmpty(CameraCaptureSession session)	当相机设备的捕捉队列为空时回调
5. onClosed(CameraCaptureSession session)	当事务关闭时回调
6. onReady(CameraCaptureSession session)	每当没有捕捉请求处理时都会回调该方法,该方法会回调多次
CameraCaptureSession.CaptureCallback 当一个捕捉请求发送给相机设备时,这个方法会监听图像捕捉的进度
所有方法均有默认的空实现,根据需求重写相应的方法

1. onCaptureStarted	当相机设备开始为请求捕捉并输出图像时回调
2. onCaptureProgressed	当图像捕获部分进行时就会回调该方法,此时一些(但不是全部)结果是可用的
3. onCaptureCompleted	当图像捕获完成时,并且结果可用时回调
4. onCaptureFailed	对应 onCaptureCompleted 方法,当相机设备产生 TotalCaptureResult 失败时就回调
5. onCaptureBufferLost	当捕获的数据没有被送到目标 surface 时被回调
CaptureRequest 这是一个图像请求
1. getKeys 获取当前CaptureRequest包含的Key集合
2. getTag 获取CaptureRequest的Tag,每个CaptureRequest都有一个Tag,代表一个标识
3. get 获取Key对应的值
4. isReprocess 判断是否是一个再处理的请求(CaptureRequest)
5. CaptureResult 这是一个请求最终生成的图象信息
6. getKeys 获取当前CaptureReult所包含的Key集合
7. get 获取当前Key对应的值
8. getRequest 获取当前CaptureReeult对应的CaptureRequest
9. getCameraId  获取Camera的ID
CaptureResult 这是捕获单个图像的结果集
捕获结果是由摄像头在对捕获请求进行处理完成后产生,CaptureResult 对象是不可变的,常使用的子类是 TotalCaptureResult
我们在 CameraCaptureSession.CaptureCallback 类的回调方法 onCaptureProgressed() 和 onCaptureCompleted() 中都可以拿到 CaptureResult 的对象

1. get(Key key) 获取 CaptureResult 中指定 key 的值
2. getFrameNumber() 获取该结果申请的帧的数量。
3. getKeys() 返回所有 Key 的列表。
4. getRequest() 返回这个结果对应的 CaptureRequest 对象。
Camera2基本流程

  1. 创建TextureView布局
  2. 在activity中获取TextureView对象并设置监听
  3. 在onSurfaceTextureAvailable中开始进行摄像头操作
  4. 创建子线程,用于监听和处理数据
  5. 通过context.getSystemService(Context.CAMERA_SERVICE)获取CameraManager对象
  6. 通过manager.getCameraCharacteristics(“0”)获取后置摄像头配置信息类,一般前置为 “1” 后置为 “0”
  7. 调用CameraManager.openCamera()方法在回调中获取CameraDevice对象
  8. 通过CameraDevice.createCaptureSession()方法在回调中获取CameraCaptureSession对象
  9. 构建CaptureRequest请求, 其中参数有三种模式分别是
  • 预览 TEMPLATE_PREVIEW
  • 拍照 TEMPLATE_STILL_CAPTURE
  • 录像 TEMPLATE_RECORD
  1. 接着是通过CameraCaptureSession发送CaptureRequest, capture是只发一次请求, setRepeatingRequest则是不断发送请求.
  2. 摄像头捕获的数据即拍照数据可以在ImageReader.OnImageAvailableListener回调中获取,保存图片就是在这里开一个子线程进行保存, CaptureCallback中可以获取拍照实际的参数和当前Camera的状态.
Camera2实践
CameraDemo的总流程图

拍照流程

摄像头切换流程

实现思路:这里只是简单的前后摄像头的切换,首先通过 cameraManager.getCameraIdList() 获取当前设备摄像头ID列表,然后遍历该列表,通过当前Camera的ID来判断当前摄像头是前置还是后置,如果是前置则查询到后置摄像头ID并将其赋值为当前Camera的ID,反之亦然,拿到要切换摄像头ID后就是去调用 manager.openCamera 方法打开指定ID的摄像头,再调用此方法之前要把当前摄像头关闭即调用closeCamera,再调用 reopenCamera,主要实现代码如下

//切换摄像头
public void switchCamera() {
    //获取摄像头的管理者
    CameraManager cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE);
    try {
        for (String id : cameraManager.getCameraIdList()) {
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
            if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_BACK)) && characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {
                mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_FRONT);
                closeCamera();
                reopenCamera();
                break;
            } else if (mCameraId.equals(String.valueOf(CameraCharacteristics.LENS_FACING_FRONT)) && characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK) {
                mCameraId = String.valueOf(CameraCharacteristics.LENS_FACING_BACK);
                closeCamera();
                reopenCamera();
                break;
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

//关闭摄像头,释放相关资源,这里采用了锁防止出现线程并发的问题
private void closeCamera() {
        try {
            mCameraOpenCloseLock.acquire();
            synchronized (mCameraStateLock) {

            mPendingUserCaptures = 0;
            mState = STATE_CLOSED;
            if (null != mCaptureSession) {
                mCaptureSession.close();
                mCaptureSession = null;
            }
            if (null != mCameraDevice) {
                mCameraDevice.close();
                mCameraDevice = null;
            }
            if (null != mJpegImageReader) {
                mJpegImageReader.close();
                mJpegImageReader = null;
            }

        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        mCameraOpenCloseLock.release();
    }
}

//重新打开摄像头
public void reopenCamera() {
    if (mTextureView.isAvailable()) {
        openCamera();
    } else {
        mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
    }
}
拍照比例流程

实现思路:我的目标是实现 4:3、16:9、全屏这三种拍照比例,所以我通过 Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)) 来获取当前摄像头支持的Size列表,当然这只是一种方式,我是拍照所以选择JPEG格式,然后遍历该列表找出最接近4:3、16:9、全屏这三种拍照比例的Size并把它们存到sizeList中,其中全屏我是获取手机屏幕宽高比来进行相应的匹配的,最后就是当我按切换拍照比例的按钮时进行循环切换拍照比例,我其中configureTransform是当我们选择相应的拍照比例时,TextureView宽高比进行自适应,当然这样只是预览比例变化了,拍出来的图片还是之前的比例,所以我们还需要进行 closeCamera 和 reopenCamera,主要实现代码如下

//初始化拍照比例
private void initPictureSize() {

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

    try {
        CameraCharacteristics characteristics
                = manager.getCameraCharacteristics(mCameraId);
        StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        List<Size> outputSize = Arrays.asList(map.getOutputSizes(ImageFormat.JPEG));
        //寻找一个最合适的尺寸与TextrueView适配

        //定义List用来存全屏、16:9、4:3的尺寸
        sizeList = new ArrayList<>();
        //全屏
        int mWidth = outputSize.get(0).getWidth();
        int mHeight = outputSize.get(0).getHeight();
        //16:9
        int mWidtha = outputSize.get(0).getWidth();
        int mHeighta = outputSize.get(0).getHeight();
        //4:3
        int mWidthb = outputSize.get(0).getWidth();
        int mHeightb = outputSize.get(0).getHeight();

        //TODO 如何计算各个拍照比例,需要把怎么确定index 0是4:3比例,2是16:9, 3是全屏的计算过程代码加入进来
        //获取手机屏幕宽高
        //方法三:应用程序显示区域指定可能包含应用程序窗口的显示部分,不包括系统装饰
        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
        int screenHeight = displayMetrics.widthPixels;
        int screenWidth = displayMetrics.heightPixels;

        Log.i(TAG, "updateCameraPreview: ---" + screenWidth);
        Log.i(TAG, "updateCameraPreview: ---" + screenHeight);
        //寻找与手机屏幕宽高最接近的预览尺寸
        double screenRatioa = (double) screenWidth / (double) screenHeight;
        //寻找最接近手机屏幕尺寸的预览尺寸
        double screenRatiob = Math.abs(screenRatioa - (double) outputSize.get(0).getWidth() / (double) outputSize.get(0).getHeight());

        //寻找最接近16:9的预览尺寸
        double ratioa = Math.abs(16.0 / 9.0 - (double) outputSize.get(0).getWidth() / (double) outputSize.get(0).getHeight());
        //寻找最接近4:3的预览尺寸
        double ratiob = Math.abs(4.0 / 3.0 - (double) outputSize.get(0).getWidth() / (double) outputSize.get(0).getHeight());

        //遍历尺寸
        for (int i = 1; i < outputSize.size() - 1; i++) {
            int w = outputSize.get(i).getWidth();
            int h = outputSize.get(i).getHeight();
            Log.i(TAG, "updateCameraPreview: w:  " + w + "   h:" + h);
            //全屏
            double ratios = Math.abs(screenRatioa - (double) w / (double) h);
            if (ratios < screenRatiob) {
                screenRatiob = ratios;
                mWidth = w;
                mHeight = h;
            }
            //16:9
            double ratio = Math.abs(16.0 / 9.0 - (double) w / (double) h);
            if (ratio < ratioa) {
                ratioa = ratio;
                mWidtha = w;
                mHeighta = h;
            }
            //4:3
            double ratiox = Math.abs(4.0 / 3.0 - (double) w / (double) h);
            if (ratiox < ratiob) {
                ratiob = ratiox;
                mWidthb = w;
                mHeightb = h;
            }
        }
        Log.i(TAG, "updateCameraPreview: 4:3  " + mWidthb + "  " + mHeightb);
        Log.i(TAG, "updateCameraPreview: 16:9  " + mWidtha + "  " + mHeighta);
        Log.i(TAG, "updateCameraPreview: 全屏  " + mWidth + "  " + mHeight);
        sizeList.add(new Size(mWidthb, mHeightb));//4:3
        sizeList.add(new Size(mWidtha, mHeighta));//16:9
        sizeList.add(new Size(mWidth, mHeight));//全屏

        mCurrentPictureSize.setText("4:3"); //默认拍照比例
        largestJpeg = sizeList.get(0);

    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

//切换拍照比例
private void switchPictureSize() {
    isSwitchPictureSize = false;
    if (sizeIndex < sizeList.size() - 1) {
        sizeIndex++;
    } else {
        sizeIndex = 0;
    }
    switch (sizeIndex) {
        case 0:
            largestJpeg = sizeList.get(0);
            mCurrentPictureSize.setText("4:3");
            break;
        case 1:
            largestJpeg = sizeList.get(1);
            mCurrentPictureSize.setText("16:9");
            break;
        case 2:
            largestJpeg = sizeList.get(2);
            mCurrentPictureSize.setText("全屏");
            break;
    }
    configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
    closeCamera();
    reopenCamera();
}
闪光模式切换流程

实现思路:

闪光模式一般有四个状态分别是:关闭、拍照打闪、长亮、自动,这里我只实现了关闭、拍照打闪、自动这三种状态,实现如下

关闭:

FLASH_MODE:FLASH_MODE_OFF
CONTROL_AE_MODE:CONTROL_AE_MODE_ON/CONTROL_AE_MODE_OFF

拍照打闪:

FLASH_MODE:FLASH_MODE_SINGLE (不管AE的结果,拍照时Flash都会打闪,这种模式下拍照前一定要通过 android.control.aePrecaptureTrigger进行预测光,不然拍下来的图可能会过曝或者全黑)
CONTROL_AE_MODE:CONTROL_AE_MODE_ON/CONTROL_AE_MODE_OFF

或者

FLASH_MODE:任意
CONTROL_AE_MODE:CONTROL_AE_MODE_ON_ALWAYS_FLASH

长亮:

FLASH_MODE:FLASH_MODE_TORCH
CONTROL_AE_MODE:CONTROL_AE_MODE_ON/CONTROL_AE_MODE_OFF

自动:

FLASH_MODE:任意
CONTROL_AE_MODE:CONTROL_AE_MODE_ON_AUTO_FLASH/CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE

通过我的CameraDemo流程图可以看出,闪光模式切换流程是
switchFlashMode -> CameraCaptureSession.CaptureCallback(mPreCatureCallback) -> process(打闪处理) -> captureStillPictureLcked(对图像再次处理) -> setUp3AControlsLocked -> CameraCaptureSession.CaptureCallback(mCatureCallback) ->handleCompletionLocked(开启异步线程池进行保存图像)

拍照之前要调用 setFlashMode,其中setUp3AControlsLocked方法是进行当前闪光模式判断然后进行相应调整

//切换闪光模式
private void switchFlashMode() {
    switch (mFlashMode) {
        case 0:
            mFlashMode = 1;
            mFlashBtn.setImageResource(R.drawable.ic_flash_auto);
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
            try {
                mCaptureSession.setRepeatingRequest(
                        mPreviewRequestBuilder.build(),
                        mPreCaptureCallback, mBackgroundHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
                return;
            }

            break;
        case 1:
            mFlashMode = 2;
            mFlashBtn.setImageResource(R.drawable.ic_flash_open);
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
            try {
                mCaptureSession.setRepeatingRequest(
                        mPreviewRequestBuilder.build(),
                        mPreCaptureCallback, mBackgroundHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
                return;
            }
            break;
        case 2:
            mFlashMode = 0;
            mFlashBtn.setImageResource(R.drawable.ic_flash_close);
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
            mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
            try {
                mCaptureSession.setRepeatingRequest(
                        mPreviewRequestBuilder.build(),
                        mPreCaptureCallback, mBackgroundHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
                return;
            }
            break;
    }
}

//设置闪光模式参数
private void setFlashMode() {
    switch (mFlashMode) {
        case 0:
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
            mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
            break;
        case 1:
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
            break;
        case 2:
            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
            break;
    }
}
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值