Android使用Camera2实现多路摄像头预览,拍照,配置参数,拔插监听功能

目录

  • 前言
  • 效果图
  • 关键代码
  • 结语

前言

之前用过camera1做过多路摄像头预览,但是在监听camera usb这功能出现了问题,因为热拔插原因,拔出camera usb时camera id可能会出现错乱问题,而camera1获取id方法只能通过for循环递增方法获取;而camera2可通过CameraManager.getCameraIdList()获取到所有的cameraId.而且camera1能实现的功能camera2也可以.所有最终决定用camera2开发.
我这里主要给配置功能的关键代码,剩余的只要对camera2了解的,基本都能实现; ,如果遇到什么困难的,可以在评论区问我,我都会帮忙解决.

效果图

  1. 主界面:
    Android使用Camera2实现多路摄像头预览,拍照,配置参数,拔插监听功能_第1张图片
  2. 配置
    Android使用Camera2实现多路摄像头预览,拍照,配置参数,拔插监听功能_第2张图片

关键代码

对camera2实现单个摄像头预览拍照的过程有了解的话,实现起来的话还是比较简单的.如果不是清楚的,建议先自己用camera2实现单摄像头的预览拍照.

  1. 首先创建摄像头的实体类:因为camra2不能像camera1那样通过Camera就能获取所有到相关变量,所有都需要保存起来,camera2创建相关对象时,通过实体类保存起来,使用时也要通过实体类去获取,这样每个摄像头才互不影响.
public class Camera2Information {

    private String camera2Id;
    private CameraDevice cameraDevice;//相机设备
    private CaptureRequest.Builder previewBuilder;//捕获请求(捕获请求模式:预览,拍照等)
    private CameraCaptureSession cameraCaptureSession;//捕获会话的管理(开启或停止预览)
    private ImageReader imageReader;//预览,拍照数据回调

    private int previewFormat;//预览格式
    private Size previewSize;//预览尺寸
    private Size pictureSize;//图片尺寸
    private Range<Integer> previewFps;//FPS
    private String previewOrientation;//预览方向

    private HandlerThread handlerThread;
    private Handler handler;
.....
}
  1. 获取能读取到cameraId,创建recyclerView.adapter的数据集.
 private List<Camera2Information> camera2InfoList;
    private String[] CameraIdArray = null;
    
    public void initData() {
        camera2InfoList = new ArrayList<>();
        CameraIdArray = Camera2Utils.getCameraId(this);
        for (String cameraId : CameraIdArray) {
            Camera2Information cameraInfo = new Camera2Information();
            cameraInfo.setCamera2Id(cameraId);
            camera2InfoList.add(cameraInfo);
        }
    }
    ......
     cameraCaseAdapter = new CameraCaseAdapter(this, camera2InfoList);
  1. 使用TextureView作为预览画面,在onSurfaceTextureAvailable处做打开相机设备操作;
        //surfaceTexture的状态监听
        private TextureView.SurfaceTextureListener mSTListener = new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {     
                int position = (int) itemView.getTag();
                //默认全部打开所有摄像头
                mOpenBtn.setText(R.string.camera_case_btn_close);
                mOpenBtn.setActivated(false);
                openCamera(mCameraManager, camera2InfoList.get(position));
            }

            @Override
            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
            }

            @Override
            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                return false;
            }

            @Override
            public void onSurfaceTextureUpdated(SurfaceTexture surface) {
            }
        };
        
    private void openCamera(CameraManager mCameraManager, Camera2Information info) {
            try {
                if (mCameraManager != null) {
                    String camera2Id = info.getCamera2Id();
                    //创建HandlerThread,用"CAMERA2"标记
                    mThreadHandler = new HandlerThread("CAMERA2");
                    mThreadHandler.start();
                    mHandler = new Handler(mThreadHandler.getLooper());
                    info.setHandler(mHandler);
                    info.setHandlerThread(mThreadHandler);
                    //获取摄像头相关属性
                    getCameraDefaultAttr(info);
                    if (mContext.checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                        LogUtils.e("TAG", "权限不足!");
                        return;
                    }
                    //打开相机设备
                    mCameraManager.openCamera(camera2Id, new CameraDeviceStateCallback(info), info.getHandler());
                }
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }

4.在 getCameraDefaultAttr(info);设置默认相机参数:预览格式,预览尺寸,预览方向,FPS;
(1). 预览格式:camera2的官方文档说明了:适合预览的格式为YUV_420_888,而ImageFormat.JPEG格式渲染JPEG数据量过大,预览时会导致掉帧.不适合作为预览格式,适合拍照.

    /**
     * 选择合适的预览格式:camera2合适的预览格式为YUV_420_888
     * 如果摄像头不支持,应选非ImageFormat.JPEG的格式
     * 当只有ImageFormat.JPEG格式,选择该预览格式
     *
     * @param mContext
     * @param camera2Id
     * @param defaultFormat
     * @return
     */
    public static int getBestPreviewFormat(Context mContext, String camera2Id, int defaultFormat) {

        CameraManager mCameraManager = (CameraManager) mContext.getSystemService(CAMERA_SERVICE);
        CameraCharacteristics characteristics = null;
        StreamConfigurationMap map = null;
        try {
            if (mCameraManager != null) {
                characteristics = mCameraManager.getCameraCharacteristics(camera2Id);
                map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        int bestPreviewFormat = 0;

        if (map != null) {
            int[] mPreviewFormat = map.getOutputFormats();

            //是否支持defaultFormat
            for (int format : mPreviewFormat) {
                if (format == defaultFormat) {
                    bestPreviewFormat = format;
                    return bestPreviewFormat;
                }
            }

            //不支持defaultFormat时,选ImageFormat.YUV_420_888格式
            for (int format : mPreviewFormat) {
                if (format == ImageFormat.YUV_420_888) {
                    bestPreviewFormat = format;
                    return bestPreviewFormat;
                }
            }

            //不支持ImageFormat.YUV_420_888时,选非ImageFormat.JPEG的格式
            for (int format : mPreviewFormat) {
                if (format != ImageFormat.JPEG) {
                    bestPreviewFormat = format;
                    return bestPreviewFormat;
                }
            }

            //只支持ImageFormat.JPEG时
            for (int format : mPreviewFormat) {
                bestPreviewFormat = format;
                return bestPreviewFormat;
            }
        }

        return bestPreviewFormat;
    }
    //使用
      mImageReader = ImageReader.newInstance(info.getPictureSize().getWidth(),
                        info.getPictureSize().getHeight(),
                        info.getPreviewFormat(), maxImages);

(2). 预览尺寸: 为了不让预览画面变形,预览尺寸比要等于且接近surface的宽高比;而camera2默认屏幕方向为横屏,所以surface的宽高和PreviewSizes刚好相反.

    /**
     * 设置最合适的预览尺寸
     * 默认竖屏,surface的宽高和PreviewSizes刚好相反
     * 尺寸比等于或者接近surface比才不会变形.
     *
     * @param surfaceWidth
     * @param surfaceHeight
     * @return
     */
    public static Size getBestPreviewSize(Context mContext, String camera2Id, int surfaceWidth, int surfaceHeight) {
        CameraManager mCameraManager = (CameraManager) mContext.getSystemService(CAMERA_SERVICE);
        CameraCharacteristics characteristics = null;
        StreamConfigurationMap map = null;
        try {
            if (mCameraManager != null) {
                characteristics = mCameraManager.getCameraCharacteristics(camera2Id);
                map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        int diffs = Integer.MAX_VALUE;
        Size bestPreviewSize = null;
        List<Size> sizeList = new ArrayList<>();
        boolean flag = false;

        if (map != null) {
            Size[] sizeArray = map.getOutputSizes(SurfaceTexture.class);
            if (sizeArray.length == 0) {
                return null;
            }

            for (Size size : sizeArray) {
                //保存比例相等的值,相机默认为横屏,size宽高和屏幕相反
                if ((size.getWidth() * surfaceWidth / surfaceHeight) == size.getHeight()) {
                    sizeList.add(size);
                    flag = true;
                }
            }

            if (!flag) {
                sizeList = new ArrayList<>(Arrays.asList(sizeArray));
            }
            //在比例相等的值集合中选差值最小
            for (Size s : sizeList) {
                int newDiffs = Math.abs(s.getHeight() - surfaceWidth) + Math.abs(s.getWidth() - surfaceHeight);
                if (newDiffs <= diffs) {
                    bestPreviewSize = s;
                    diffs = newDiffs;
                }
            }
        }
        return bestPreviewSize;
    }
    //使用
    texture.setDefaultBufferSize(info.getPreviewSize().getWidth(), info.getPreviewSize().getHeight());

(3). 预览方向:因为camera2没有像camera1直接设置预览方向的方法,只能通过旋转TextureView,我这里只适配了前置和后置摄像头的预览方向,第三个摄像头可能需要手动旋转.注意:mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, angle);该方法是旋转拍照后图片的方向,而且只有当预览格式为ImageFormat.JPEG时才会生效,官方建议预览用YUV_420_888,拍照用ImageFormat.JPEG格式.

 public static int getPreviewOrientation(Context mContext, String camera2Id) {
        CameraManager mCameraManager = (CameraManager) mContext.getSystemService(CAMERA_SERVICE);
        CameraCharacteristics characteristics = null;
        try {
            if (mCameraManager != null) {
                characteristics = mCameraManager.getCameraCharacteristics(camera2Id);
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        int result = 0;
        if (characteristics != null) {
            Integer mCameraOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
            WindowManager wm = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE));
            if (wm != null) {
                int rotation = wm.getDefaultDisplay().getRotation();

                if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraMetadata.LENS_FACING_FRONT) {
                    result = (mCameraOrientation + ORIENTATIONS.get(rotation)) % 360;
                    result = (360 - result) % 360;
                } else {
                    result = (mCameraOrientation - ORIENTATIONS.get(rotation) + 360) % 360;
                }
            }

        }
        return result;
    }
    //使用
                if (info.getPreviewOrientation() == null) {
                    int angle = Camera2Utils.getPreviewOrientation(mContext, info.getCamera2Id());
                    info.setPreviewOrientation(String.valueOf(angle));
                }
                mPreviewTv.setRotation((float) Integer.parseInt(info.getPreviewOrientation()));


(4). FPS:一般来说,预览适合的FPS为30,人眼看起来才不会卡顿.

 /**
     * int [min,max]
     * 设置最合适的FPS:默认 defaultFps,没有则接近 defaultFps,优先选> defaultFps
     *
     * @param mContext
     * @param camera2Id
     * @param defaultFps
     * @return
     */
    public static Range<Integer> getBestPreviewFps(Context mContext, String camera2Id, int defaultFps) {

        CameraManager mCameraManager = (CameraManager) mContext.getSystemService(CAMERA_SERVICE);
        CameraCharacteristics characteristics = null;
        try {
            if (mCameraManager != null) {
                characteristics = mCameraManager.getCameraCharacteristics(camera2Id);
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        Range<Integer>[] FpsRanges = null;
        if (characteristics != null) {
            FpsRanges = characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
        }

        int minDiffs = Integer.MAX_VALUE;
        int maxDiffs = Integer.MAX_VALUE;
        Range<Integer> bestFps = null;

        if (FpsRanges != null) {
            for (int i = 0; i < FpsRanges.length; i++) {
                Range<Integer> fps = FpsRanges[i];
                if (defaultFps == fps.getLower() && defaultFps == fps.getUpper()) {
                    bestFps = fps;
                    return bestFps;
                }
                //min
                if (fps.getLower() >= defaultFps) {
                    int value = Math.abs(fps.getLower() - defaultFps);
                    if (value < minDiffs) {
                        bestFps = fps;
                        minDiffs = value;
                    }
                    continue;
                }
                //max
                if (fps.getUpper() <= defaultFps) {
                    int value = Math.abs(fps.getUpper() - defaultFps);
                    if (value <= maxDiffs) {
                        bestFps = fps;
                        maxDiffs = value;
                    }
                }
            }
        }
        return bestFps;
    }
    
//使用
mPreviewBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, info.getPreviewFps());
  1. 接下来跟单摄像头预览步骤一样了,只要在创建实体类对象的通过set保存起来,需要用到时,通过实体类get方法获取.就能实现主要功能了

结语

先写到这样吧,后面在补充拍照相关的设置:图片大小,图片方向,镜像翻转,部分格式拍照;以及camera usb的拔插监听.

你可能感兴趣的:(Camera2)