Android CameraX使用,预览、拍照、获取静态图像、录制视频

1.想要了解介绍的参考官网:

https://developer.android.google.cn/training/camerax/architecture

2.实现预览

效果如图

2.1第一步:引入依赖

(1)添加Google Maven 代码库

buildscript {
    
    repositories {
        google()
        jcenter()
    }
}

(2)添加java1.8

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    // For Kotlin projects
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

(3)添加camerax相关库,最新的版本号可以去maven 库官网查看

//CameraX
def camerax_version = "1.1.0-alpha06"
// CameraX core library using camera2 implementation
implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle Library
implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View class
implementation "androidx.camera:camera-view:1.0.0-alpha24"

2.2第二步:添加权限

<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

其中第一个是检查设备摄像头硬件的,.any代表前置或后置都可以

最后一个是录像要用,不录像的话可以不加,后面的动态权限也就不用询问了。

2.3第三步:6.0后动态权限

注:在询问权限回调中,如果用户选择

允许——grantResults == 0

始终允许——后续则不需要再询问权限了

禁止——grantResults == -1

禁止不再询问——其实你代码里还是询问了,只是他直接返回了grantResults == -1

/**
 * 检查是否拥有权限
 */
private void checkPermission(){
    if (Build.VERSION.SDK_INT >= 23) {//6.0以上才用动态权限
        boolean cameraPermission = hasPermission(Manifest.permission.CAMERA);
        boolean recordAudio = hasPermission(Manifest.permission.RECORD_AUDIO);
        if (cameraPermission && recordAudio) {
            startCamera();
        } else {
            requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO},CAMERA_PERMISSION_REQUEST_CODE);
        }
    }
}


/**
*询问权限回调
*/
@Override
public void onRequestPermissionsResult(int requestCode, 
    @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
            for (int i : grantResults) {
                if (i == -1) {
                    //被禁止
                    Toast.makeText(this,"获取相机或录像权限失败,请重新进入或手动设置权限!",Toast.LENGTH_SHORT).show();
                    finish();
                }
            }
            startCamera();
        }
}

2.4 第四步:使用PreviewView作为预览控件

<androidx.camera.view.PreviewView
    android:id="@+id/act_cameraTest_pv_cameraPreview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

2.5 第五步:开始预览

/**
 * 开始预览
 */
private void startCamera() {
    ListenableFuture<ProcessCameraProvider> cameraProviderFuture =         
         ProcessCameraProvider.getInstance(this);
    cameraProviderFuture.addListener(new Runnable() {
        @SuppressLint("RestrictedApi")
        @Override
        public void run() {
            try {
                //将相机的生命周期和activity的生命周期绑定,camerax 会自己释放
                ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
                Preview preview = new Preview.Builder().build();
                //创建图片的 capture
                mImageCapture = new ImageCapture.Builder()
                        .setFlashMode(ImageCapture.FLASH_MODE_OFF)
                        .build();
                //选择前置摄像头
                CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_FRONT).build();
                // Unbind use cases before rebinding
                cameraProvider.unbindAll();

                // Bind use cases to camera
                //参数中如果有mImageCapture才能拍照,否则会报下错
                //Not bound to a valid Camera [ImageCapture:androidx.camera.core.ImageCapture-bce6e930-b637-40ee-b9b9-
                mCamera = cameraProvider.bindToLifecycle(CameraTestActivity.this, cameraSelector, preview,mImageCapture);
                preview.setSurfaceProvider(pvCameraPreview.getSurfaceProvider());
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }, ContextCompat.getMainExecutor(this));
}

3.实现拍照(只获取静态图片+拍照保存到本地)

官方提供了两个拍照方法,下面是源码中的代码

第一个是获取相机预览图像的静态图片

第二个是直接保存到文件,形成一个.jpg照片的,比上面多一个参数

 3.1只获取静态图片

/**
 * 获取静态图片
 */
public void takeStaticPhoto(View view) {
    if (mImageCapture != null) {
        //开始拍照
        mImageCapture.takePicture(ContextCompat.getMainExecutor(this), new     
             ImageCapture.OnImageCapturedCallback() {
            @Override
            public void onCaptureSuccess(ImageProxy image) {
                super.onCaptureSuccess(image);
                //ImageProxy 转 Bitmap
                mBitmap = BaseImageUtils.imageProxyToBitmap(image);
                imgShowStaticPhoto.setBackground(new 
                     BitmapDrawable(getApplicationContext().getResources(),mBitmap));
                //使用完image关闭
                image.close();
            }

            @Override
            public void onError(ImageCaptureException exception) {
                super.onError(exception);
                Log.d(TAG, "onError: ");
            }
        });
    }
}

3.2 拍照保存到本地

    /**
     * 拍照并存到存储空间
     * @param view
     */
    public void takeFilePhoto(View view) {
        if (mImageCapture != null) {
            File dir = new File(savePath);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            //创建文件
            File file = new File(savePath,"CameraXPhoto.jpg");
            if (file.exists()) {
                file.delete();
            }
            //创建包文件的数据,比如创建文件
            ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(file).build();

            //开始拍照
            mImageCapture.takePicture(outputFileOptions, ContextCompat.getMainExecutor(this), new ImageCapture.OnImageSavedCallback() {
                @Override
                public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
                    //    Uri savedUri = outputFileResults.getSavedUri();
                    Toast.makeText(CameraTestActivity.this, "照片保存成功:保存位置-我的手机/Android/data/com.test.cameraxdemo/files/photo ", Toast.LENGTH_SHORT).show();
                }

                @Override
                public void onError(@NonNull ImageCaptureException exception) {
                    Toast.makeText(CameraTestActivity.this, "照片保存失败", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }

4.实现录像

4.1第一步:创建VideoCapture

需要在上面的startCamera()方法中创建VideoCapture并绑定到Lifecycle中,与ImageCapture绑定的方式一样。即在cameraProvider.bindToLifecycle方法之前创建mVideoCapture,并在该方法中最后一个参数UserCase,加入mVideoCapture,如下所示:

//创建图片的 capture
mImageCapture = new ImageCapture.Builder()
    .setFlashMode(ImageCapture.FLASH_MODE_OFF)
    .build();
mVideoCapture = new VideoCapture.Builder().build();
//选择前置摄像头
CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_FRONT).build();
// Unbind use cases before rebinding
cameraProvider.unbindAll();

// Bind use cases to camera
//参数中如果有mImageCapture才能拍照,否则会报下错
//Not bound to a valid Camera [ImageCapture:androidx.camera.core.ImageCapture-bce6e930-b637-40ee-b9b9-
mCamera = cameraProvider.bindToLifecycle(CameraTestActivity.this, cameraSelector, preview,mImageCapture,mVideoCapture);

4.2 开始录像

用法跟拍照方法类似

public void startVideo(View view) {
    if (TextUtils.equals("开始录制",btnRecord.getText().toString())) {
        btnRecord.setText("停止录制");
        startRecord();

    }else {
        btnRecord.setText("开始录制");
        stopRecord();
    }
}

@SuppressLint({"MissingPermission", "RestrictedApi"})
private void startRecord() {
    if (mVideoCapture != null) {
        File dir = new File(savePath);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        //创建文件
        File file = new File(savePath,"CameraXVideo.mp4");
        if (file.exists()) {
            file.delete();
        }
        VideoCapture.OutputFileOptions build = new VideoCapture.OutputFileOptions.Builder(file).build();
        mVideoCapture.startRecording(build, CameraXExecutors.mainThreadExecutor(), new VideoCapture.OnVideoSavedCallback() {
            @Override
            public void onVideoSaved(@NonNull VideoCapture.OutputFileResults outputFileResults) {
                Toast.makeText(CameraTestActivity.this, "视频保存成功:保存位置-我的手机/Android/data/com.test.cameraxdemo/files/camera ", Toast.LENGTH_LONG).show();
            }

            @Override
            public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) {
                Log.e(TAG, "onError: " + message);
            }
        });
    }
}

4.3 停止录像

调用mVideoCapture.stopRecording()方法,

注:因为VideoCapture绑定到了Lifecycle,所以如果你在调用前关闭了页面,它也会自动调用到该方法,并正常停止录像,生成mp4文件。

@SuppressLint("RestrictedApi")
private void stopRecord() {
    mVideoCapture.stopRecording();
}

5.问题处理

5.1 Not bound to a valid Camera [ImageCapture:androidx.camera.core.ImageCapture-

问题现象:

调用拍照方法报错

原因:

没有绑定ImageCapture

解决方法:

在startCamera()方法中有一段绑定到Lifecycle的代码

 

 可以看到如果我把最后一个参数去掉代码不会报错也可以正常跑起来,因为这个方法源码是下图这样的,所以传参的时候传三个到多个都是可以的,如果报上面的错,应该是这里的ImageCapture参数没有传,加上之后可以解决这个问题。

5.2 CameraX 连续拍照两次后拍照回调方法一直无响应

问题现象:

连续调用两次mImageCapture.takePicture()后发现再调用就一直没有回应,如果你退出会进入onError回调,提示找不到camera。

这种情况下会发现控制台有两行提示语如下:

D/ImageCapture: Send image capture request [current, pending] = [0, 1]
W/ImageCapture: Too many acquire images. Close image to be able to process next.

D/ImageCapture: Send image capture request [current, pending] = [0, 1]
W/ImageCapture: Too many acquire images. Close image to be able to process next.

分析原因:

捕获的image太多了,需要关闭才能向下执行,那么去看下 ImageCapture 源码搜索下image.close(),发现一共有四处:两处是在catch的时候调用的,两处是在判断否的时候调用的,也就是说正常情况下拍照并成功返回之后并没有close,所以会造成这个问题。

 解决办法:

在mImageCapture.takePicture()的成功回调函数中,等你使用完image之后手动把它关闭,这个问题就可以解决了。

mImageCapture.takePicture(ContextCompat.getMainExecutor(this), new ImageCapture.OnImageCapturedCallback() {
     @Override
       public void onCaptureSuccess(ImageProxy image) {
          super.onCaptureSuccess(image);
          //ImageProxy 转 Bitmap
          Bitmap bitmap = imageProxyToBitmap(image);
          //使用完image关闭
          image.close();
       }

      @Override
      public void onError(ImageCaptureException exception) {
           super.onError(exception);
           Log.d(TAG, "onError: ");
       }
});

需要源码的可以点个赞,点个关注,然后私信我。

猜你喜欢

转载自blog.csdn.net/qq_37980878/article/details/120060170