安卓CameraX的使用
如果要在安卓应用中操作相机,有三个库可以选:
- Camera(已废弃):Camera是安卓最早的包,目前已废弃,在Android 5.0(API 级别 21)的设备上操作相机可以选择该包,保证兼容性;
- Camera2:Camera2在Android 5.0(API 级别 21)开始提供,用于替代Camera,相比Camera,在性能、灵活性等方面有优势,但是使用起来更复杂;
- 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; } }
参考文章
- 开始使用Android相机
免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。