关于Camera2 API的使用方法

Camera2 API的使用方法

一、开发环境

  1. Windows10
  2. Android Studio 4.0.1
  3. JDK 1.8
  4. 谷歌官方API手册 https://developer.android.com/reference/android/hardware/camera2/package-summary

二、开发过程

新建测试工程 在布局文件中添加TextureView用于显示Camera预览画面 清单文件中添加必要权限

    <TextureView
        android:id="@+id/texture_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    </TextureView>

<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

完整布局代码 添加三个按钮

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextureView
        android:id="@+id/texture_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    </TextureView>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal">
        <Button
            android:id="@+id/switch_camera"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="切换相机">
        </Button>
        <Button
            android:id="@+id/capture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="拍照">
        </Button>
        <Button
            android:id="@+id/ratio"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="比例">
        </Button>
    </LinearLayout>
</RelativeLayout>

在Activity中实现接口TextureView.SurfaceTextureListener 复写四个方法

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

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

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

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {

    }

初始化相机

用到 CameraManager CameraDevice CameraCaptureSession CaptureRequest.Builder CameraCharacteristics这几个类 详解在https://www.cnblogs.com/fuyaozhishang/p/9752581.html  架构图也很清晰

首先获取相机服务

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

获取相机属性 currentCamera为String类型 自行定义 默认后置摄像头为0,前置为1

CameraCharacteristics characteristics = manager.getCameraCharacteristics(currentCamera);

获取相机的预览尺寸和拍照尺寸 我设定的宽高比例为4:3 也可设置为16:9

StreamConfigurationMap scmap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        previewSizes = scmap.getOutputSizes(ImageReader.class);
        for (int i = 0; i < previewSizes.length; i++) {
            if (previewSizes[i].getHeight() == ScreenWidth) {
                if ((previewSizes[i].getWidth() * 1.0 / previewSizes[i].getHeight() * 1.0) == 4.0 / 3.0) {
                    WIDTH = previewSizes[i].getHeight();
                    HEIGHT = previewSizes[i].getWidth();
                }
            }
        }
ImageReader类用来绑定预览数据 设置照片分辨率 以及设置预览数据回调 以及最后的保存照片
ImageReader mImageReader = ImageReader.newInstance(previewSizes[0].getWidth(), previewSizes[0].getHeight(), ImageFormat.JPEG, 1);
        mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, null);


    private ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader reader) {
            showImage(reader);
        }
    };

    private void showImage(ImageReader reader) {
        int rotation = getWindowManager().getDefaultDisplay().getRotation();
        Matrix matrix = new Matrix();
        matrix.postRotate(ORIENTATION.get(rotation));//前后摄像头方向处理
        if (currentCamera.equals("1")) {//处理前置摄像头镜像问题
            matrix.postScale(-1, 1);
        }
        // 拿到图片数据
        Image image = reader.acquireLatestImage();
        // 获取字节缓冲
        ByteBuffer buffer = image.getPlanes()[0].getBuffer();
        // 创建与缓冲区相同的字节数组
        byte[] bytes = new byte[buffer.remaining()];
        // 将数据读取字节数组
        buffer.get(bytes);
        // 创建图片
        Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
        Bitmap newBM = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        saveBitmap(newBM);
        image.close();
    }

设置TextureView的宽高

RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(WIDTH, HEIGHT);
        mTextureView.setLayoutParams(layoutParams);
        mTextureView.setSurfaceTextureListener(this);

回调SurfaceTextureListener接口,在onSurfaceTextureAvailable方法中申请权限,如果申请权限的时机错误,则会出现第一次进入app后授权成功,但是相机预览画面依然空白的情况。这里使用PermissionX申请权限框架,感谢郭霖大神。

PermissionX.init(Camera2Activity.this)
                .permissions(Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE)
                .explainReasonBeforeRequest()
                .onExplainRequestReason(new ExplainReasonCallback() {
                    @Override
                    public void onExplainReason(ExplainScope scope, List<String> deniedList) {
                        scope.showRequestReasonDialog(deniedList, "即将申请的权限是程序必须依赖的权限", "我已明白", "取消");
                    }
                })
                .onForwardToSettings(new ForwardToSettingsCallback() {
                    @Override
                    public void onForwardToSettings(ForwardScope scope, List<String> deniedList) {
                        scope.showForwardToSettingsDialog(deniedList, "您需要去应用程序设置当中手动开启权限", "我已明白", "取消");
                    }
                })
                .request(new RequestCallback() {
                    @SuppressLint("MissingPermission")
                    @Override
                    public void onResult(boolean allGranted, List<String> grantedList, List<String> deniedList) {
                        if (allGranted) {
                            try {
                                manager.openCamera(currentCamera, mCameraStateCallBack, null);//开启相机,回调开启状态
                            } catch (CameraAccessException e) {
                                e.printStackTrace();
                            }
                        } else {
                            Toast.makeText(Camera2Activity.this, "拒绝授权", Toast.LENGTH_SHORT).show();
                        }
                    }
                });
    private CameraDevice.StateCallback mCameraStateCallBack = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            mCameraDevice = camera;
            openPreview(HEIGHT, WIDTH);//开启相机预览,传入宽高
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {

        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {

        }
    };

    public void openPreview(int height, int width) {
        SurfaceTexture texture = mTextureView.getSurfaceTexture();
        texture.setDefaultBufferSize(height, width);
        Surface surface = new Surface(texture);//创建预览视图
        try {
            mPreviewRequestBuilder
                    = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);//相机设置为预览模式

            mPreviewRequestBuilder.addTarget(surface);
            mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), mCameraCaptureCallBack, null);//将预览数据绑定到TextureView
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

开始预览,回调方法

    private CameraCaptureSession.StateCallback mCameraCaptureCallBack = new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession session) {
            try {
                mCameraCaptureSession = session;
                mPreviewRequestBuilder= mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                mPreviewRequestBuilder.addTarget(mPreviewSurface);
                mCameraCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, null);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }

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

        }
    };

拍照

        try {
            //首先我们创建请求拍照的CaptureRequest
            final CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            //获取屏幕方向
            int rotation = getWindowManager().getDefaultDisplay().getRotation();
            mCaptureBuilder.addTarget(mImageReader.getSurface());
            //设置拍照方向
            mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation));
            //停止预览
            mCameraCaptureSession.stopRepeating();
            //开始拍照,然后回调上面的接口重启预览,因为mCaptureBuilder设置ImageReader作为target,所以会自动回调ImageReader的onImageAvailable()方法保存图片
            CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {

                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                    Toast.makeText(Camera2Activity.this, "拍照成功!", Toast.LENGTH_SHORT).show();
                    try {
                        mCameraCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(), null, null);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }
            };
            mCameraCaptureSession.capture(mCaptureBuilder.build(), captureCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

切换摄像头

switch (currentCamera) {
                    case "0":
                        try {
                            mCameraCaptureSession.close();
                            mCameraDevice.close();
                            currentCamera = "1";
                            initCamera();
                            manager.openCamera(currentCamera, mCameraStateCallBack, null);
                            front();//矫正传感器方向
                        } catch (CameraAccessException e) {
                            e.printStackTrace();
                        }
                        break;
                    case "1":
                        try {
                            mCameraCaptureSession.close();
                            mCameraDevice.close();
                            currentCamera = "0";
                            initCamera();
                            manager.openCamera(currentCamera, mCameraStateCallBack, null);
                            rear();//矫正传感器方向
                        } catch (CameraAccessException e) {
                            e.printStackTrace();
                        }
                        break;
                }

还有一些小问题比如按home键返回桌面再次进入程序的时候相机卡住,别的程序占用相机导致相机卡住等等问题都已经解决,源代码我会开源到github参考。地址 https://github.com/sata21-lab/Camera2

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值