欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android开发中关于Camera2 API的具体使用方法

程序员文章站 2022-07-05 22:21:12
Camera2 API的使用方法一、开发环境Windows10Android Studio 4.0.1JDK 1.8谷歌官方API手册https://developer.android.com/reference/android/hardware/camera2/package-summary二、开发过程新建测试工程 在布局文件中添加TextureView用于显示Camera预览画面 清单文件中添加必要权限

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

本文地址:https://blog.csdn.net/sata21_21/article/details/107782365