美文网首页Camera
Camera2 实现相机预览

Camera2 实现相机预览

作者: 毛先森 | 来源:发表于2021-03-06 15:46 被阅读0次
    QQ图片20210306105325.png

    目录

    • 为什么要使用 Camera2
    • 重要功能

    为什么要使用 Camera2

    相比 Camera1 ,2 的 api 功能更全,控制的粒度更细,单论获取摄像头采集的原始数据,Camera1 获取 NV21 格式的 YUV 数据,而 Camera2 获取的数据给是 YUV420,这是因为在底层会将 NV21 做格式转换,为开发者省去一步操作

    最低兼容到 Android 5.0,使用 Camera2 的实现的代码量也大大增加了,所以 Google 后面又推出了 CameraX (Jetpack)来简化 api

    相机 app 大多使用 Camera2 来开发

    基础功能

    • 开启摄像头
    • 获取预览

    开启摄像头

    • CameraManager 系统层相机服务
    • CameraCharacteristics 摄像头参数
    CameraCharacteristics.LENS_FACING :
    获取摄像头方向。前置摄像头(LENS_FACING_FRONT)或 后置摄像头(LENS_FACING_BACK)
    CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL:
    获取当前设备支持的相机特性
    CameraCharacteristics.SENSOR_ORIENTATION:
    获取摄像头方向
    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP:
    获取StreamConfigurationMap,它是管理摄像头支持的所有输出格式和尺寸
    CameraCharacteristics.FLASH_INFO_AVAILABLE:
    是否支持闪光灯
    CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT:
    同时检测到人脸的数量
    CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES:
    相机支持的人脸检测模式
    
    
    

    获取预览

    • ImageReader 获得预览数据回调
    • CaptureRequest.Builder 设置摄像头参数,如自动对焦、开启闪光灯,建立会话前设置
    • CameraCaptureSession 摄像头会话,获取数据

    CameraCaptureSession

    1. capture() 获取一次,常用于单张拍照
    2. setRepeatingRequest() 是重复请求获取图像数据,常用于预览或连拍

    实践代码

    
    public class Camera2Helper {
    
        private final static String TAG = "Camera2Helper";
        private static Camera2Helper INSTANCE;
        private Application mGlobalContext;
        private TextureView mTextureView;
        private Size mPreviewSize;
        private CameraDevice.StateCallback mDeviceStateCallback;
        private CameraDevice mCameraDevice;
        private HandlerThread mBgThread;
        private Handler mBgHandler;
        private Point mPreviewViewSize;
        private CaptureRequest.Builder mQuestBuidler;
        private CameraCaptureSession mCaptureSession;
        private CameraCaptureSession.StateCallback mSessionCallback;
        private ImageReader mImageReader;
        private ICamera2Callback mICamera2Callback;
    
        private Camera2Helper(){
        }
    
        public static Camera2Helper getInstance() {
            if (INSTANCE == null){
                INSTANCE = new Camera2Helper();
            }
            return INSTANCE;
        }
    
        public synchronized void openCamera(Application applicationContext, TextureView textureView,ICamera2Callback camera2Callback) {
            mTextureView = textureView;
            mGlobalContext = applicationContext;
            mICamera2Callback = camera2Callback;
            // 获取摄像头服务
            CameraManager cameraManager = (CameraManager) applicationContext.getSystemService(Context.CAMERA_SERVICE);
            // 获取摄像头参数
            try {
                String[] cameraIds =  cameraManager.getCameraIdList();
                if (cameraIds != null){
                    CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraIds[0]);
                    // 摄像头支持的所有输出格式和尺寸
                    StreamConfigurationMap map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                    // 获取摄像头方向
                    int cameraDirection = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);
                    if (cameraDirection == CameraMetadata.LENS_FACING_FRONT){
                        Log.e(TAG,"前置摄像头");
                    }else {
                        Log.e(TAG,"后置摄像头");
                    }
                    // 最佳预览尺寸
                    mPreviewSize = getBestSupportedSize(new ArrayList<Size>(Arrays.asList(map.getOutputSizes(SurfaceTexture.class))));
                    if (ActivityCompat.checkSelfPermission(applicationContext, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                        return;
                    }
                    mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(),mPreviewSize.getHeight(), ImageFormat.YUV_420_888,2);
                    mBgThread = new HandlerThread(TAG);
                    mBgThread.start();
                    mBgHandler = new Handler(mBgThread.getLooper());
                    // 设置 ImageReader 回调监听
                    mImageReader.setOnImageAvailableListener(new OnImageAvailableListenerImpl(),mBgHandler);
                    cameraManager.openCamera(cameraIds[0], getStateCallback(), mBgHandler);
                }
            } catch (Exception e) {
                Log.e(TAG,e.getMessage());
            }
        }
    
        private CameraDevice.StateCallback getStateCallback() {
            mDeviceStateCallback = new CameraDevice.StateCallback() {
                @Override
                public void onOpened(@NonNull CameraDevice camera) {
                    mCameraDevice = camera;
                    // 此时可以创建预览 Session
                    Log.e(TAG,"摄像头打开");
                    preview();
                }
    
                @Override
                public void onDisconnected(@NonNull CameraDevice camera) {
                    mCameraDevice.close();
                    mCameraDevice = null;
                    Log.e(TAG,"onDisconnected");
                }
    
                @Override
                public void onError(@NonNull CameraDevice camera, int error) {
                    mCameraDevice.close();
                    mCameraDevice = null;
                    Log.e(TAG,"onError");
                }
            };
            return mDeviceStateCallback;
        }
    
    
        // 预览
        private void preview() {
    
            if (mTextureView == null){
                return;
            }
    
            SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();
            surfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(),mPreviewSize.getHeight());
            Surface surface = new Surface(surfaceTexture);
            //建立会话
            try {
                mQuestBuidler = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                // 自动对焦
                mQuestBuidler.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                mQuestBuidler.addTarget(surface);
                mQuestBuidler.addTarget(mImageReader.getSurface());
                mCameraDevice.createCaptureSession(Arrays.asList(surface,mImageReader.getSurface()),getSessionCallback(),mBgHandler);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
        private CameraCaptureSession.StateCallback getSessionCallback() {
            mSessionCallback = new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    //会话建立,可以开始拿摄像头的数据
                    mCaptureSession = session;
                    try {
                        mCaptureSession.setRepeatingRequest(mQuestBuidler.build(),new CameraCaptureSession.CaptureCallback() {
                        },mBgHandler);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }
    
                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {
    
                }
            };
            return mSessionCallback;
        }
    
    
        private Size getBestSupportedSize(List<Size> sizes) {
            Point maxPreviewSize = new Point(1920, 1080);
            Point minPreviewSize = new Point(1280, 720);
            Size defaultSize = sizes.get(0);
            Size[] tempSizes = sizes.toArray(new Size[0]);
            Arrays.sort(tempSizes, new Comparator<Size>() {
                @Override
                public int compare(Size o1, Size o2) {
                    if (o1.getWidth() > o2.getWidth()) {
                        return -1;
                    } else if (o1.getWidth() == o2.getWidth()) {
                        return o1.getHeight() > o2.getHeight() ? -1 : 1;
                    } else {
                        return 1;
                    }
                }
            });
            sizes = new ArrayList<>(Arrays.asList(tempSizes));
            for (int i = sizes.size() - 1; i >= 0; i--) {
                if (maxPreviewSize != null) {
                    if (sizes.get(i).getWidth() > maxPreviewSize.x || sizes.get(i).getHeight() > maxPreviewSize.y) {
                        sizes.remove(i);
                        continue;
                    }
                }
                if (minPreviewSize != null) {
                    if (sizes.get(i).getWidth() < minPreviewSize.x || sizes.get(i).getHeight() < minPreviewSize.y) {
                        sizes.remove(i);
                    }
                }
            }
            if (sizes.size() == 0) {
                return defaultSize;
            }
            Size bestSize = sizes.get(0);
            float previewViewRatio;
            if (mPreviewViewSize != null) {
                previewViewRatio = (float) mPreviewViewSize.x / (float) mPreviewViewSize.y;
            } else {
                previewViewRatio = (float) bestSize.getWidth() / (float) bestSize.getHeight();
            }
    
            if (previewViewRatio > 1) {
                previewViewRatio = 1 / previewViewRatio;
            }
    
            for (Size s : sizes) {
                if (Math.abs((s.getHeight() / (float) s.getWidth()) - previewViewRatio) < Math.abs(bestSize.getHeight() / (float) bestSize.getWidth() - previewViewRatio)) {
                    bestSize = s;
                }
            }
            return bestSize;
        }
    
    
    
    
        private class OnImageAvailableListenerImpl implements ImageReader.OnImageAvailableListener {
            private byte[] y;
            private byte[] u;
            private byte[] v;
    
            @Override
            public void onImageAvailable(ImageReader reader) {
                // 可以提取 Iamge 对象了
                if (mICamera2Callback == null){
                    return;
                }
                Image image= reader.acquireNextImage();
                Image.Plane[] planes =  image.getPlanes();
                if (y == null) {
    //                new  了一次
    //                limit  是 缓冲区 所有的大小     position 起始大小
                    y = new byte[planes[0].getBuffer().limit() - planes[0].getBuffer().position()];
                    u = new byte[planes[1].getBuffer().limit() - planes[1].getBuffer().position()];
                    v = new byte[planes[2].getBuffer().limit() - planes[2].getBuffer().position()];
                }
                if (image.getPlanes()[0].getBuffer().remaining() == y.length) {
    //                分别填到 yuv
    
                    planes[0].getBuffer().get(y);
                    planes[1].getBuffer().get(u);
                    planes[2].getBuffer().get(v);
    //                yuv 420
                }
                mICamera2Callback.onPreview(y, u, v, mPreviewSize, planes[0].getRowStride());
                image.close();
    
            }
        }
    
    
    }
    
    

    遇到的问题

    1. 无法获取到 IamgerReader 中的 YUV 数据
     
    原因: 在创建会话时,只添加了 Surface 作为一路输入 Target,未添加 ImageReader.getSurface() 作为输出
    
    解决方案:
    
    (CaptureRequest.Builder)mRequestBuilder.addTarget(mImageReader.getSurface());
    
    
    1. 报异常 IllegalArgumentException: CaptureRequest contains unconfigured Input/Output Surface!


      ~D`3AD$0Q7V)BPV~CBUO@T1.png

    相关链接

    相关文章

      网友评论

        本文标题:Camera2 实现相机预览

        本文链接:https://www.haomeiwen.com/subject/safsqltx.html