安卓CameraX的使用

06-02 1333阅读

如果要在安卓应用中操作相机,有三个库可以选:

  1. Camera(已废弃):Camera是安卓最早的包,目前已废弃,在Android 5.0(API 级别 21)的设备上操作相机可以选择该包,保证兼容性;
  2. Camera2:Camera2在Android 5.0(API 级别 21)开始提供,用于替代Camera,相比Camera,在性能、灵活性等方面有优势,但是使用起来更复杂;
  3. CameraX:CameraX是对Camera2的封装,是Jetpack的一个库,支持Android 5.0(API 级别 21)及更高版本。CameraX降低了操作相机的难度,大多数情况下,使用CameraX已足以完成需求,建议使用CameraX。

注:Jetpack是一个由多个库组成的套件,可帮助开发者遵循最佳做法、减少样板代码并编写可在各种Android版本和设备中一致运行的代码,让开发者可将精力集中于真正重要的编码工作。

1. CameraX常见用例

您可以使用CameraX,借助名为“用例”的抽象概念与设备的相机进行交互。提供的用例如下:

  • 预览(Preview):在屏幕上查看相机画面;
  • 图片分析(ImageAnalysis):逐帧处理相机捕获到的每一帧画面;
  • 图片拍摄(ImageCapture):拍照;
  • 视频拍摄(VideoCapture):拍视频(和音频)。

    CameraX允许同时使用Preview、VideoCapture、ImageAnalysis和ImageCapture各一个实例。此外:

    • 每个用例都可以单独使用。例如,应用可以在不使用预览的情况下录制视频;
    • 启用扩展后,只能保证能够使用ImageCapture和Preview的组合。根据OEM实现情况,可能无法同时添加ImageAnalysis;无法为VideoCapture用例启用扩展。如需了解详情,请参阅扩展参考文档;
    • 对于某些相机而言,在较低分辨率模式下可以支持的组合,在较高的分辨率下将无法支持,这具体取决于相机的功能;
    • 在相机硬件级别为FULL或更低的设备上,组合使用Preview、VideoCapture和ImageCapture或ImageAnalysis可能会迫使CameraX为Preview和VideoCapture复制相机的PRIV数据流。这种重复(称为数据流共享)可让您同时使用这些功能,但代价是增加了处理需求。因此,您可能会遇到略长的延迟时间和缩短的电池续航时间。

      2. 代码实现

      在build.gradle中添加相关依赖:

      // camerax
      def camerax_version = "1.3.0"
      implementation "androidx.camera:camera-core:${camerax_version}"
      implementation "androidx.camera:camera-camera2:${camerax_version}"
      implementation "androidx.camera:camera-lifecycle:${camerax_version}"
      implementation "androidx.camera:camera-view:${camerax_version}"
      implementation "androidx.camera:camera-video:${camerax_version}"
      // 扩展,本文代码未使用
      // implementation "androidx.camera:camera-extensions:${camerax_version}"
      

      在AndroidManifest.xml添加相关权限:

      
      
      

      添加布局文件activity_camerax_preview.xml:

      
          
          
              
                  
                      
                      
                  
                  
                      
                      
                      
                  
              
          
      
      

      activity类:

      package com.example.study.activities;
      import android.Manifest;
      import android.content.ContentValues;
      import android.os.Build;
      import android.os.Bundle;
      import android.provider.MediaStore;
      import android.util.Log;
      import android.util.Size;
      import android.view.View;
      import android.widget.Button;
      import android.widget.Toast;
      import androidx.activity.ComponentActivity;
      import androidx.annotation.NonNull;
      import androidx.annotation.Nullable;
      import androidx.camera.core.Camera;
      import androidx.camera.core.CameraSelector;
      import androidx.camera.core.ImageAnalysis;
      import androidx.camera.core.ImageCapture;
      import androidx.camera.core.ImageCaptureException;
      import androidx.camera.core.Preview;
      import androidx.camera.core.TorchState;
      import androidx.camera.lifecycle.ProcessCameraProvider;
      import androidx.camera.video.MediaStoreOutputOptions;
      import androidx.camera.video.Quality;
      import androidx.camera.video.QualitySelector;
      import androidx.camera.video.Recorder;
      import androidx.camera.video.Recording;
      import androidx.camera.video.VideoCapture;
      import androidx.camera.view.PreviewView;
      import androidx.core.content.ContextCompat;
      import com.google.common.util.concurrent.ListenableFuture;
      import com.example.study.R;
      import com.example.study.utils.PermissionUtils;
      import java.text.SimpleDateFormat;
      import java.util.Arrays;
      import java.util.Date;
      import java.util.List;
      import java.util.concurrent.ExecutionException;
      import java.util.concurrent.Executor;
      public class CameraxPreviewActivity extends ComponentActivity {
          private static final String TAG = "CameraxPreviewActivity";
          private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHmmssSSS");
          private static final Size SIZE = new Size(1080, 1920);
          private static final List CAMERAS = Arrays.asList(CameraSelector.DEFAULT_BACK_CAMERA,
                  CameraSelector.DEFAULT_FRONT_CAMERA);
          private static final String START_RECORD = "开始录制";
          private static final String STOP_RECORD = "结束录制";
          private static final String PAUSE_RECORD = "暂停录制";
          private static final String RESUME_RECORD = "恢复录制";
          private PreviewView previewView;
          private Executor executor;
          private Preview preview;
          private ImageAnalysis imageAnalysis;
          private ImageCapture imageCapture;
          private VideoCapture videoCapture;
          private Recording videoRecording;
          private ProcessCameraProvider cameraProvider;
          private Camera camera;
          private boolean isRecordingStart = false;
          private boolean isRecordingPause = false;
          private int cameraIndex = 0;
          private Button flashButton;
          private Button pauseResumeVideoButton;
          private Button startShopVideoButton;
          private Button switchCameraButton;
          @Override
          protected void onCreate(@Nullable Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              this.setContentView(R.layout.activity_camerax_preview);
              PermissionUtils.checkPermission(this, Manifest.permission.CAMERA,
                      Manifest.permission.RECORD_AUDIO,
                      Manifest.permission.WRITE_EXTERNAL_STORAGE);
              previewView = findViewById(R.id.camerax_preview_view);
              executor = ContextCompat.getMainExecutor(this);
              initCamerax();
              initButton();
          }
          private void initCamerax() {
              // 可以将相机生命周期绑定到activity,从而免去打开、关闭相机的任务
              ListenableFuture cameraProviderListenableFuture = ProcessCameraProvider.getInstance(this);
              cameraProviderListenableFuture.addListener(() -> {
                  try {
                      cameraProvider = cameraProviderListenableFuture.get();
                      // 预览
                      preview = new Preview.Builder().build();
                      preview.setSurfaceProvider(previewView.getSurfaceProvider());
                      // 图片分析
                      imageAnalysis = new ImageAnalysis.Builder()
                              // 设置输出图片格式
                              .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
                              // 非阻塞模式,只取最新的图片。若需要每帧图片都处理,使用阻塞模式 ImageAnalysis.STRATEGY_BLOCK_PRODUCER
                              .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                              .setTargetResolution(SIZE)
                              .build();
                      imageAnalysis.setAnalyzer(executor, (imageProxy) -> {
                          // close后才认为处理完成当前帧,所以放到try-with-resources语句中
                          try (imageProxy) {
                              // 在此处处理图片,如对图片进行人脸、条码识别等
                              Log.i(TAG, "接收到一帧图片" + SIMPLE_DATE_FORMAT.format(new Date()));
                          } catch (Exception exception) {
                              Log.w(TAG, exception.getMessage());
                          }
                      });
                      // 拍照
                      imageCapture = new ImageCapture.Builder()
                              .setFlashMode(ImageCapture.FLASH_MODE_AUTO)
                              .setTargetResolution(SIZE)
                              .build();
                      // 录制
                      Recorder recorder = new Recorder.Builder()
                              .setQualitySelector(QualitySelector.from(Quality.FHD))
                              .build();
                      videoCapture = VideoCapture.withOutput(recorder);
                      cameraProvider.unbindAll();
                      camera = cameraProvider.bindToLifecycle(this, CAMERAS.get(cameraIndex), preview,
                              imageAnalysis, imageCapture, videoCapture);
                  } catch (ExecutionException e) {
                      throw new RuntimeException(e);
                  } catch (InterruptedException e) {
                      throw new RuntimeException(e);
                  }
              }, executor);
          }
          private void initButton() {
              initFlashButton();
              initSwitchButton();
              initImageCaptureButton();
              initVideoCaptureButtons();
          }
          /**
           * 视频操作按钮
           */
          private void initVideoCaptureButtons() {
              startShopVideoButton = findViewById(R.id.camerax_video_capture_button);
              pauseResumeVideoButton = findViewById(R.id.camerax_video_capture_pause_button);
              // 开始/停止录制视频
              startShopVideoButton.setOnClickListener(view -> {
                  if (videoCapture == null || executor == null) {
                      return;
                  }
                  if (isRecordingStart) {
                      videoRecording.stop();
                      videoRecording = null;
                      isRecordingStart = false;
                      startShopVideoButton.setText(START_RECORD);
                      // 视频录制停止后不显示"暂停/恢复录制"按钮
                      pauseResumeVideoButton.setVisibility(View.GONE);
                      // 录制视频完成后显示切换摄像头按钮
                      switchCameraButton.setVisibility(View.VISIBLE);
                      return;
                  }
                  PermissionUtils.checkPermission(this, Manifest.permission.RECORD_AUDIO);
                  videoRecording = videoCapture.getOutput()
                          .prepareRecording(this, getMediaStoreOutputOptions())
                          .withAudioEnabled()
                          .start(executor, videoRecordEvent -> {
                          });
                  isRecordingStart = true;
                  startShopVideoButton.setText(STOP_RECORD);
                  pauseResumeVideoButton.setText(PAUSE_RECORD);
                  // 视频录制开始后显示"暂停/恢复录制"按钮
                  pauseResumeVideoButton.setVisibility(View.VISIBLE);
                  // 录制视频期间不允许切换摄像头,切换摄像头会终止录制
                  switchCameraButton.setVisibility(View.GONE);
              });
              // 暂停/恢复录制视频
              pauseResumeVideoButton.setOnClickListener(view -> {
                  if (videoCapture == null || executor == null || videoRecording == null || !isRecordingStart) {
                      return;
                  }
                  if (isRecordingPause) {
                      isRecordingPause = false;
                      videoRecording.resume();
                      pauseResumeVideoButton.setText(PAUSE_RECORD);
                  } else {
                      videoRecording.pause();
                      isRecordingPause = true;
                      pauseResumeVideoButton.setText(RESUME_RECORD);
                  }
              });
          }
          /**
           * 拍照按钮
           */
          private void initImageCaptureButton() {
              findViewById(R.id.camerax_image_capture_button).setOnClickListener(view -> {
                  if (imageCapture == null || executor == null) {
                      return;
                  }
                  imageCapture.takePicture(getImageOutputFileOptions(), executor,
                          new ImageCapture.OnImageSavedCallback() {
                              @Override
                              public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
                                  Log.i(TAG, "save picture success");
                                  Toast.makeText(CameraxPreviewActivity.this, "success", Toast.LENGTH_SHORT).show();
                              }
                              @Override
                              public void onError(@NonNull ImageCaptureException exception) {
                                  Log.w(TAG, "error" + exception.getMessage());
                                  Toast.makeText(CameraxPreviewActivity.this, "fail", Toast.LENGTH_SHORT).show();
                              }
                          });
              });
          }
          /**
           * 切换摄像头按钮
           */
          private void initSwitchButton() {
              switchCameraButton = findViewById(R.id.camerax_switch_camera);
              switchCameraButton.setOnClickListener((view -> {
                  if (cameraProvider == null) {
                      return;
                  }
                  if (++cameraIndex >= CAMERAS.size()) {
                      cameraIndex = 0;
                  }
                  cameraProvider.unbindAll();
                  cameraProvider.bindToLifecycle(this, CAMERAS.get(cameraIndex), preview, imageAnalysis,
                          imageCapture, videoCapture);
                  resetButtonStatus(cameraIndex);
              }));
          }
          /**
           * 闪光灯按钮
           */
          private void initFlashButton() {
              flashButton = findViewById(R.id.camerax_flash);
              flashButton.setOnClickListener(view -> {
                  if (camera == null) {
                      return;
                  }
                  boolean isTorchOff = camera.getCameraInfo().getTorchState().getValue() == TorchState.OFF;
                  camera.getCameraControl().enableTorch(isTorchOff);
                  flashButton.setText(camera.getCameraInfo().getTorchState().getValue() == TorchState.ON ? "关灯" : "开灯");
              });
          }
          /**
           * 重设按钮状态
           */
          private void resetButtonStatus(int cameraIndex) {
              flashButton.setText("开灯");
              flashButton.setVisibility(cameraIndex == 0 ? View.VISIBLE : View.GONE);
              startShopVideoButton.setText(START_RECORD);
              pauseResumeVideoButton.setText(PAUSE_RECORD);
              pauseResumeVideoButton.setVisibility(View.GONE);
          }
          private ImageCapture.OutputFileOptions getImageOutputFileOptions() {
              return new ImageCapture
                      .OutputFileOptions.Builder(getContentResolver(), MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                      getContentValues("image/png"))
                      .build();
          }
          private MediaStoreOutputOptions getMediaStoreOutputOptions() {
              return new MediaStoreOutputOptions.Builder(getContentResolver(),
                      MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
                      .setContentValues(getContentValues("video/mp4"))
                      .build();
          }
          private ContentValues getContentValues(String mimeType) {
              String name = SIMPLE_DATE_FORMAT.format(new Date());
              ContentValues contentValues = new ContentValues();
              contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name);
              contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
              if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
                  contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/Study");
              }
              return contentValues;
          }
      }
      

      参考文章

      1. 开始使用Android相机
免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

目录[+]

取消
微信二维码
微信二维码
支付宝二维码