Camera2实现基础相机功能

时间:2021-10-19 作者:qvyue

先上效果图:

Camera2实现基础相机功能
img111.jpg

文章推荐,没看的可以去看下,将会对Camera的认识更加深刻:
1.Camera开发知识储备
2.了解Matrix
3.Camera1基础开发
4.基于Camera实现人脸检测

Camera2

Camera2相对于Camera1引入了管道的概念:

Camera2的架构图:
Camera2实现基础相机功能
Camera2架构图.png
Camera2拍照流程图,同时也是API调用顺序的图:
Camera2实现基础相机功能
image.png

流程:
1.创建Camera线程和该线程的handler,绑定并开启线程
2.添加TextureView的监听
3.初始化Sureface,指定cameraId,创建ImageReader并设置监听
4.打开相机
5.创建相机预览会话
6.拍照

多的注解文字我就不写了,直接贴示例代码好了:

/**
 * Camera2
 */
public class Camera2Helper {

    private static final String TAG = "Camera2Helper";
    //默认使用后置摄像头
    private int mFacing = CameraCharacteristics.LENS_FACING_BACK;

    private int PREVIEW_WIDTH = 720;          //预览的宽度
    private int PREVIEW_HEIGHT = 1280;        //预览的高度
    private int PIC_WIDTH = 720;             //保存图片的宽度
    private int PIC_HEIGHT = 1280;           //保存图片的高度
    //相机的信息
    private CameraCharacteristics mCameraCharacteristics;
    //相机ID
    private String cameraID;

    private HandlerThread handlerThread = new HandlerThread("CameraThread");

    private Activity activity;
    private TextureView textureView;
    private Integer mCameraSensorOrientation;
    private StreamConfigurationMap streamConfigurationMap;
    private ImageReader mImageReader;
    private Handler cameraHandler;
    private CameraManager cameraManager;

    private CameraDevice cameraDevice;
    private CameraCaptureSession cameraCaptureSession;
    private boolean canExchangeCamera;
    private boolean canTakePic;

    public Camera2Helper(Activity activity, TextureView textureView) {
        this.activity = activity;
        this.textureView = textureView;
        init();
    }

    public void init() {
        handlerThread.start();
        //将handler绑定到指定线程
        cameraHandler = new Handler(handlerThread.getLooper());
        textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                Log.i(TAG, "    onSurfaceTextureAvailable");
                //初始化相机资源
                initCamera();
            }

            @Override
            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
                Log.i(TAG, "    onSurfaceTextureSizeChanged");
            }

            @Override
            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                Log.i(TAG, "    onSurfaceTextureDestroyed");
                //释放相机资源
                releaseCamera();
                return true;
            }

            @Override
            public void onSurfaceTextureUpdated(SurfaceTexture surface) {
                Log.i(TAG, "    onSurfaceTextureUpdated");
                if (cameraDevice == null) {
                    initCamera();
                }
            }
        });
    }

    /**
     * 初始化相机
     */
    public void initCamera() {
        try {
            cameraManager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
            if (cameraManager != null) {
                String[] cameraIdList = cameraManager.getCameraIdList();
                if (cameraIdList.length == 0) {
                    Toast.makeText(activity, "暂无相机可用", Toast.LENGTH_SHORT).show();
                    return;
                }
                for (String id : cameraIdList) {
                    CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(id);
                    Integer facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);
                    // TODO: 2020/3/19 如何分析后置摄像头的类型
                    if (mFacing == facing) {
                        mCameraCharacteristics = cameraCharacteristics;
                        if (mFacing == CameraCharacteristics.LENS_FACING_BACK && id.equals("2")) {
                            cameraID = id;
                            break;
                        } else
                            cameraID = id;
                    }
                    Log.i(TAG, "摄像头的ID:" + cameraID);
                }

                Integer supportLevel = mCameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
                if (supportLevel != null && supportLevel ==
                        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
                    Toast.makeText(activity, "相机硬件不支持新特性", Toast.LENGTH_SHORT).show();
                }
                //获取手机的方向
                int screenRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
                //获取摄像头的方向
                mCameraSensorOrientation = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
                //获取StreamConfigurationMap,它是管理摄像头支持的所有输出格式和尺寸
                streamConfigurationMap = mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

                //获取支持的图片尺寸
                Size[] pictureSizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG);
                //获取支持的预览尺寸
                Size[] previewSizes = streamConfigurationMap.getOutputSizes(SurfaceTexture.class);
                //是否需要调换宽高
                boolean exchange = exchangeWidthAndHeight(screenRotation, mCameraSensorOrientation);
                Size size;
                if (exchange) {
                    size = new Size(textureView.getHeight(), textureView.getWidth());
                } else {
                    size = new Size(textureView.getWidth(), textureView.getHeight());
                }
                Log.i(TAG, "    textureView.getWidth() :" + textureView.getWidth()
                        + "   textureView.getHeight():" + textureView.getHeight());
                Size pictureSize = getBestSize(exchange ? PIC_HEIGHT : PIC_WIDTH,
                        exchange ? PIC_WIDTH : PIC_HEIGHT, size, pictureSizes);
                Size previewSize = getBestSize(exchange ? PREVIEW_HEIGHT : PREVIEW_WIDTH,
                        exchange ? PREVIEW_WIDTH : PREVIEW_HEIGHT, size, previewSizes);

                textureView.getSurfaceTexture().setDefaultBufferSize(previewSize.getWidth(),
                        previewSize.getHeight());

                mImageReader = ImageReader.newInstance(pictureSize.getWidth(), pictureSize.getHeight(),
                        ImageFormat.JPEG, 1);
                mImageReader.setOnImageAvailableListener(imageAvailableListener, cameraHandler);

                //打开相机
                openCamera();

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

    private ImageReader.OnImageAvailableListener imageAvailableListener =
            new ImageReader.OnImageAvailableListener() {
                @Override
                public void onImageAvailable(ImageReader reader) {
                    //接收图片的数据
                    if (reader != null) {
                        Log.i(TAG, "Image Available!   当前线程:" + Thread.currentThread().getName());
                        Image image = reader.acquireLatestImage();
//                        Image image = reader.acquireNextImage();
//                        new Thread(new ImageSaver(activity, image, mFacing)).start();

                        ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                        byte[] data = new byte[buffer.remaining()];
                        buffer.get(data);
                        String dirPath = activity.getExternalFilesDir(Environment.DIRECTORY_PICTURES).getPath()
                                + "/Picture/";
                        File dirFile = new File(dirPath);
                        if (!dirFile.exists())
                            dirFile.mkdirs();
                        String picturePath = dirPath + "picture.jpeg";
                        File picFile = new File(picturePath);
                        if (picFile.exists())
                            picFile.delete();

                        transform(data, image, picFile);
                    }
                }
            };

    public void transform(byte[] data, Image image, File filePath) {
        //给图片变换为视觉角度
        if (data != null && data.length > 0) {
            Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
            Matrix matrix = new Matrix();
            if (mFacing == CameraCharacteristics.LENS_FACING_FRONT) {
                //如果时前置摄像头,镜像;因为时镜像,所以旋转角度是360-rotation
                matrix.postScale(-1, 1);
            }
            Bitmap bitmap1 = Bitmap.createBitmap(bitmap, 0, 0,
                    bitmap.getWidth(), bitmap.getHeight(), matrix, true);
            saveBmp(bitmap1, filePath);
            bitmap1.recycle();
            image.close();//这里要关闭image,否则界面会卡
        }
    }

    /**
     * 存储图片
     */
    private void saveBmp(Bitmap bmp, File picFile) {
        try {
            FileOutputStream fos = new FileOutputStream(picFile);
            bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos);
            fos.flush();
            fos.close();
            activity.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(activity, "图片保存成功", Toast.LENGTH_SHORT).show();
                }
            });
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            bmp.recycle();
        }
    }

    /**
     * 开启相机
     */
    private void openCamera() {
        if (cameraManager != null) {
            if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA)
                    == PackageManager.PERMISSION_GRANTED) {
                try {
                    cameraManager.openCamera(cameraID, new CameraDevice.StateCallback() {
                        @Override
                        public void onOpened(@NonNull CameraDevice camera) {
                            Log.i(TAG, "    onOpened()");
                            cameraDevice = camera;
                            //创建一个相机预览的会话
                            createCaptureSession(camera);
                        }

                        @Override
                        public void onDisconnected(@NonNull CameraDevice camera) {
                            Log.i(TAG, "    onDisconnected()");
                            //防止切换相机应用时,当前应用占用了相机导致预览会话创建失败
                            releaseCamera();
                        }

                        @Override
                        public void onError(@NonNull CameraDevice camera, int error) {
                            Log.i(TAG, "    相机打开失败:" + error);
                        }
                    }, cameraHandler);
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }
            } else {
                Toast.makeText(activity, "请开启相机权限", Toast.LENGTH_SHORT).show();
            }

        }
    }

    /**
     * 释放相机资源
     */
    public void releaseCamera() {
        cameraCaptureSession.close();
        cameraCaptureSession = null;

        cameraDevice.close();
        cameraDevice = null;

        mImageReader.close();
        mImageReader = null;

        canExchangeCamera = false;
    }

    /**
     * 退出线程
     */
    public void releaseThread() {
        handlerThread.quitSafely();
    }

    /**
     * 创建一个相机预览的会话
     */
    private void createCaptureSession(CameraDevice cameraDevice) {
        try {
            if (cameraDevice != null) {
                CaptureRequest.Builder captureRequestBuilder =
                        cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                Surface surface = new Surface(textureView.getSurfaceTexture());
                // 将CaptureRequest的构建器与Surface对象绑定在一起
                captureRequestBuilder.addTarget(surface);
                // 禁用闪光灯
                captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                        CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                // 添加自动对焦
                captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                        CaptureRequest.CONTROL_AF_STATE_FOCUSED_LOCKED);
                CaptureRequest captureRequest = captureRequestBuilder.build();
                //创建会话
                cameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
                        new CameraCaptureSession.StateCallback() {
                            @Override
                            public void onConfigured(@NonNull CameraCaptureSession session) {
                                try {
                                    cameraCaptureSession = session;
                                    session.setRepeatingRequest(captureRequest,
                                            captureCallback, cameraHandler);
                                } catch (CameraAccessException e) {
                                    e.printStackTrace();
                                }
                            }

                            @Override
                            public void onConfigureFailed(@NonNull CameraCaptureSession session) {

                            }
                        }, cameraHandler);

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

    private CameraCaptureSession.CaptureCallback captureCallback =
            new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureStarted(@NonNull CameraCaptureSession session,
                                             @NonNull CaptureRequest request,
                                             long timestamp, long frameNumber) {
                    super.onCaptureStarted(session, request, timestamp, frameNumber);
                }

                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session,
                                               @NonNull CaptureRequest request,
                                               @NonNull TotalCaptureResult result) {
                    super.onCaptureCompleted(session, request, result);
//                    unlockFocus();//恢复预览
                    canExchangeCamera = true;
                    canTakePic = true;
                }

                @Override
                public void onCaptureFailed(@NonNull CameraCaptureSession session,
                                            @NonNull CaptureRequest request,
                                            @NonNull CaptureFailure failure) {
                    super.onCaptureFailed(session, request, failure);
                    Toast.makeText(activity, "预览会话创建失败", Toast.LENGTH_SHORT).show();
                }
            };

    /**
     * 拍照
     */
    public void takePhoto() {
        if (cameraDevice == null || !textureView.isAvailable() || !canTakePic) return;
        try {
            CaptureRequest.Builder captureRequest = cameraDevice.
                    createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            captureRequest.addTarget(mImageReader.getSurface());
            // 自动对焦
            captureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            // 闪光灯
            captureRequest.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
            //根据摄像头方向对保存的照片进行旋转,使其为"自然方向"
            captureRequest.set(CaptureRequest.JPEG_ORIENTATION, mCameraSensorOrientation);
            //拍照
            CaptureRequest build = captureRequest.build();
            cameraCaptureSession.capture(build, captureCallback, cameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 切换相机
     */
    public void exchangeCamera() {
        if (cameraDevice == null || !canExchangeCamera || !textureView.isAvailable()) return;
        if (mFacing == CameraCharacteristics.LENS_FACING_FRONT)
            mFacing = CameraCharacteristics.LENS_FACING_BACK;
        else
            mFacing = CameraCharacteristics.LENS_FACING_FRONT;
        releaseCamera();
        initCamera();
    }

    /**
     * 根据手机的方向和相机传感器的方向确定是否需要调换宽高
     *
     * @param screenRotation           手机方向
     * @param mCameraSensorOrientation 相机传感器的方向
     */
    private boolean exchangeWidthAndHeight(int screenRotation, int mCameraSensorOrientation) {
        boolean exchange = false;
        if (screenRotation == Surface.ROTATION_0 || screenRotation == Surface.ROTATION_180) {
            if (mCameraSensorOrientation == 90 || mCameraSensorOrientation == 270) {
                exchange = true;
            }
        } else if (screenRotation == Surface.ROTATION_90 || screenRotation == Surface.ROTATION_270) {
            if (mCameraSensorOrientation == 0 || mCameraSensorOrientation == 180) {
                exchange = true;
            }
        }
        return exchange;
    }

    /**
     * 计算出最佳的尺寸
     *
     * @param targetWidth  targetWidth   图片或预览指定的尺寸
     * @param targetHeight targetHeight   图片或预览指定的尺寸
     * @param size         textureView的尺寸
     * @param supportSizes 支持的尺寸
     * @return
     */
    private Size getBestSize(float targetWidth, float targetHeight, Size size, Size[] supportSizes) {
        //宽高大的Size列表
        ArrayList bigList = new ArrayList();
        //宽高小的Size列表
        ArrayList smallList = new ArrayList();
        for (Size supportSize : supportSizes) {
            //宽= targetWidth && supportSize.getHeight() >= targetHeight) {
                    bigList.add(supportSize);
                } else {
                    smallList.add(supportSize);
                }
            }
            Log.i(TAG, "系统支持的尺寸: w " + supportSize.getWidth() + " h "
                    + supportSize.getHeight() + " 比例 :"
                    + supportSize.getWidth() / supportSize.getHeight());
        }
//        Log.i(TAG, "最大尺寸 :w:" + size.getWidth() + "  h: "
//                + size.getHeight() + ", 比例 :" + (size.getWidth() / size.getHeight()));
//        Log.i(TAG, "目标尺寸 :w:" + targetWidth + " h:" +
//                targetHeight + " 比例 :" + (targetWidth / targetHeight));

        //选择bigEnough中最小的值  或 notBigEnough中最大的值
        if (bigList.size() > 0) {
            Size min = Collections.min(bigList, new CompareSizeByArea());
            return min;
        }
        if (smallList.size() > 0) {
            Size max = Collections.max(smallList, new CompareSizeByArea());
            return max;
        } else {
            return supportSizes[0];
        }
    }

    private class CompareSizeByArea implements Comparator {

        @Override
        public int compare(Size o1, Size o2) {
            return Long.signum((long) o1.getWidth() * o1.getHeight()
                    - (long) o2.getWidth() * o2.getHeight());
        }
    }

}

在Activity中使用:

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        TextureView textureView = view.findViewById(R.id.textureView);
        ImageView ivSetting = view.findViewById(R.id.ivSetting);
        ImageButton btnTakePic = view.findViewById(R.id.btnTakePic);
        //录制视频的按钮
        ImageView btnStart = view.findViewById(R.id.btnStart);
        ImageView btnStop = view.findViewById(R.id.btnStop);
        //切换相机摄像头的按钮
        ImageView ivExchange = view.findViewById(R.id.ivExchange);

        camera2Helper = new Camera2Helper(getActivity(), textureView);

        ivSetting.setOnClickListener(this);
        btnTakePic.setOnClickListener(this);
        btnStart.setOnClickListener(this);
        btnStop.setOnClickListener(this);
        ivExchange.setOnClickListener(this);
    }

    @Override
    public void onResume() {
        super.onResume();

    }

    @Override
    public void onPause() {
        super.onPause();
        camera2Helper.releaseCamera();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        camera2Helper.releaseThread();
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btnTakePic) {
            //拍照
            camera2Helper.takePhoto();
        } else if (v.getId() == R.id.ivExchange) {
            //切换相机
            camera2Helper.exchangeCamera();
        } else if (v.getId() == R.id.ivSetting) {
            //相机设置

        } else if (v.getId() == R.id.btnStart) {
            //开始录制视频

        } else if (v.getId() == R.id.btnStop) {
            //停止录制视频

        }
    }
声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。