Android中的相机功能一直以来比较难用,截止目前出现了Camera,Camera2,Camera2支持API 21以上设备,要支持5.0一下还得需要Camera,Camera2比Camera更加复杂,更加灵活和解耦,但是然后用起来比较麻烦,因此CameraX就出现了,目前CameraX还是刚刚起步,还不是很稳定,版本还是1.0.0。CameraX 由两个概念来完成实现 Camera View 和 Camera Core。Camera View 可被单独用于处理基本的相机要求,比如拍照,录视频,生命周期管理以及相机切换等。而核心库能够搭配 Camera View 处理更复杂的 CameraX 实现。
一、Camrea整体架构
- 预览:接受用于显示预览的 Surface,例如
PreviewView
。 - 图片分析:提供 CPU 可访问的缓冲区以进行分析(例如进行机器学习)。
- 图片拍摄:拍摄并保存照片。
不同用例可以相互组合使用,也可以同时处于活动状态。例如,应用中可以加入预览用例供用户查看进入相机视野的画面,加入图片分析用例来确定照片里的人物是否在微笑,以及包含一个图片拍摄用例以便在人物微笑时拍摄照片。
二、配置
1、依赖
google官方文档最新的CameraX稳定版本是:1.0.0-beta04,这里集成最新的稳定版。
implementation "androidx.camera:camera-core:1.0.0-beta04"
implementation "androidx.camera:camera-camera2:1.0.0-beta04"
implementation "androidx.camera:camera-view:1.0.0-alpha11"
implementation "androidx.camera:camera-lifecycle:1.0.0-beta04"
implementation "androidx.camera:camera-extensions:1.0.0-alpha09"
2、添加布局
<androidx.camera.view.PreviewView
android:id="@+id/preview_view"
android:layout_width="match_parent"
android:layout_height="240dp"
app:scaleType="fillCenter" />
app:scaleType表示相机宽高尺寸设置无法满足硬件本身显示条件时候的显示样式,这里是fillCenter表示居中。
previewView = findViewById(R.id.preview_view);
previewView.post(new Runnable() {
@Override
public void run() {
//动态获取宽高
width = previewView.getWidth();
height = previewView.getHeight();
}
});
3、初始化
private void init() {
cameraProviderFuture = ProcessCameraProvider.getInstance(MainActivity.this);
cameraSelector = new CameraSelector.Builder()
//设置前后摄像头
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build();
preview = new Preview.Builder()
//设置成像宽高,需要和previewView保持一致
.setTargetAspectRatio(aspectRatio(width, height))
//旋转角度
.setTargetRotation(previewView.getDisplay().getRotation())
.build();
try {
cameraProvider = cameraProviderFuture.get();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
这块在preview中还可以设置帧率,线程等等内容。
三、预览
在向应用添加预览时,请使用 PreviewView,这是一种可以剪裁、缩放和旋转以确保正确显示的 View
。当相机处于活动状态时,图片预览会流式传输到 PreviewView
中的 Surface。
private void startPreview() {
cameraProviderFuture.addListener(new Runnable() {
@Override
public void run() {
try {
//预览之前一定要取消
cameraProvider.unbindAll();
//绑定当前Activity的生命周期
cameraProvider.bindToLifecycle(MainActivity.this, cameraSelector, preview);
//设置预览
preview.setSurfaceProvider(previewView.createSurfaceProvider());
} catch (Exception e) {
e.printStackTrace();
}
}
//预览线程,主线程
}, ContextCompat.getMainExecutor(MainActivity.this));
}
四、拍照
图片拍摄用例旨在拍摄高分辨率的优质照片,不仅提供简单的相机手动控制功能,还提供自动白平衡、自动曝光和自动对焦 (3A) 功能。调用程序负责决定如何使用拍摄的照片,具体包括以下选项:
- takePicture(Executor, OnImageCapturedCallback):此方法为拍摄的图片提供内存缓冲区。
- takePicture(OutputFileOptions, Executor, OnImageSavedCallback):此方法将拍摄的图片保存到提供的文件位置。运行 ImageCapture 的可自定义执行程序有两种类型:回调执行程序和 IO 执行程序。
回调执行程序是 takePicture 方法的参数。它用于执行用户提供的 OnImageCapturedCallback。如果调用方选择将图片保存到文件位置,您可以指定执行程序以执行 IO。如需设置 IO 执行程序,请调用 ImageCapture.Builder.setIoExecutor(Executor)。如果执行程序不存在,则默认 CameraX 为任务的内部 IO 执行程序。
public void takePhoto() {
ImageCapture imageCapture = new ImageCapture.Builder()
//旋转角度
.setTargetRotation(previewView.getDisplay().getRotation())
//缩短照相时间
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
//宽高
.setTargetAspectRatio(aspectRatio(width, height))
.build();
//图像分析,返回每一帧,CameraX 会生成 YUV_420_888 格式的图片。
ImageAnalysis imageAnalyzer = new ImageAnalysis.Builder()
.setTargetAspectRatio(aspectRatio(width, height))
.setTargetRotation(previewView.getDisplay().getRotation())
.build();
imageAnalyzer.setAnalyzer(cameraBackExecutor, image -> {
Log.d(TAG, image.getImageInfo().getTimestamp() + "");
image.close();
}
);
//拍照前必须解绑
cameraProvider.unbindAll();
//绑定当前Activity生命周期
cameraProvider.bindToLifecycle(this, cameraSelector, imageCapture, imageAnalyzer);
//拍照图片保存路径
File file = getFile(".jpg");
//配置路径参数
ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(file).build();
//开启拍照
imageCapture.takePicture(outputFileOptions, ContextCompat.getMainExecutor(this), new ImageCapture.OnImageSavedCallback() {
@Override
public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
Toast.makeText(MainActivity.this, file.getAbsolutePath(), Toast.LENGTH_SHORT).show();
}
@Override
public void onError(@NonNull ImageCaptureException exception) {
Toast.makeText(MainActivity.this, exception.getMessage(), Toast.LENGTH_LONG).show();
Log.e(TAG, "Photo capture failed: ${exc.message}", exception);
}
});
}
参数:setCaptureMode:
-
CAPTURE_MODE_MINIMIZE_LATENCY:如需缩短照片拍摄的延迟时间。
-
CAPTURE_MODE_MAXIMIZE_QUALITY:优化照片质量。
五、录像
private void takeVideo() {
VideoCapture videoCapture = new VideoCaptureConfig.Builder()
//设置宽高
.setTargetAspectRatio(aspectRatio(width, height))
//设置旋转角度
.setTargetRotation(previewView.getDisplay().getRotation())
.build();
//录像前必须解绑
cameraProvider.unbindAll();
//开启相机预览
preview.setSurfaceProvider(previewView.createSurfaceProvider());
//绑定生命周期,这里如果没有参数preview,则只录像,不显示画面
cameraProvider.bindToLifecycle(this, cameraSelector,preview, videoCapture);
//视频路径
File file = getFile(".mp4");
//开始录像
videoCapture.startRecording(file, ContextCompat.getMainExecutor(MainActivity.this), new VideoCapture.OnVideoSavedCallback() {
@Override
public void onVideoSaved(@NonNull File file) {
Toast.makeText(MainActivity.this, file.getAbsolutePath(), Toast.LENGTH_SHORT).show();
}
@Override
public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) {
Log.d(TAG, "onError: " + message);
}
});
//停止录像,并且回调OnVideoSavedCallback
btn4.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
videoCapture.stopRecording();
preview.clear();
}
});
}
最后记得动态申请权限,否则会出现录像,拍照失败等。权限代码如下:
<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO},
100);
return;
}
运行效果如文章刚刚开始时候所示。