AndroidX Jetpack CameraX使用介绍
CameraX介绍
CameraX 是一个 Jetpack 库,旨在帮助更简便的开发相机应用。如果你要开发新应用,建议您从 CameraX 开始。它提供了一个一致且易于使用的 API,该 API 适用于绝大多数
Android 设备,并向后兼容 Android 5.0(API 级别 21)
CameraX 结构
- 预览:用于显示预览的 Surface,例如 PreviewView
- 图片分析:提供 CPU 可访问的缓冲区
- 图片拍摄:拍摄并保存照片
- 视频拍摄:通过 VideoCapture 拍摄视频和音频(视频拍摄有兴趣的可以自己进行研究一下)
使用介绍
库引用
kotlin
dependencies{
var camerax_version="1.3.0-alpha04"
//可忽略,因为核心库是由camera-camera2间接包含
implementation("androidx.camera:camera-core:${camerax_version}")
//核心库是由camera-camera2
implementation("androidx.camera:camera-camera2:${camerax_version}")
//CameraX生命周期库
implementation("androidx.camera:camera-lifecycle:${camerax_version}")
//视频库
implementation("androidx.camera:camera-video:${camerax_version}")
//预览View库
implementation("androidx.camera:camera-view:${camerax_version}")
//CameraX视觉
implementation("androidx.camera:camera-mlkit-vision:${camerax_version}")
//CameraX扩展库
implementation("androidx.camera:camera-extensions:${camerax_version}")
}
在使用CameraX的时候与Camera一样需要权限
ini
//摄像头权限
<uses-permission android:name="android.permission.CAMERA" />
预览实现
使用PreviewView进行预览,这是一种可以剪裁、缩放和旋转以确保正确显示的 View,当相机处于活动状态时,图片预览会流式传输到 PreviewView 中的 Surface
ini
<androidx.camera.view.PreviewView
android:id="@+id/preview_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
布局创建完成后需获取ProcessCameraProvider,在创建View的时候需检查。
ini
public ListenableFuture<ProcessCameraProvider> getInstance(Context context) {
if (instance == null) {
instance = ProcessCameraProvider.getInstance(context);
}
return instance;
}
我是在Application去创建的ProcessCameraProvider,在Activity中的View创建时获取ProcessCameraProvider对象,进行预览设置,生命周期绑定
scss
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(mActivityMainBinding.getRoot());
//预览View
previewView = mActivityMainBinding.previewView;
//获取ProcessCameraProvider对象
instance = CameraUtils.getInstance().getInstance(this);
instance.addListener(new Runnable() {
@Override
public void run() {
//创建 Preview
Preview preview = CameraUtils.getInstance().getPreview();
try {
//解绑
instance.get().unbindAll();
//绑定生命周期、图片分析、拍照
instance.get().bindToLifecycle(MainActivity.this, CameraUtils.getInstance().getCameraSelector(), preview
, CameraUtils.getInstance().getImageCapture() /*创建 ImageCapture*/
, CameraUtils.getInstance().getImageAnalysis()/*创建 ImageAnalysis*/);
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//绑定预览
preview.setSurfaceProvider(previewView.getSurfaceProvider());
}
}, ContextCompat.getMainExecutor(this));
}
bindToLifecycle() 会返回一个 Camera 对象。如需详细了解如何控制相机输出(如变焦和曝光)可进行设置
PreviewView进行预览是还可实现渲染模式切换(默认模式PERFORMANCE)与视频缩放,具体可以去官方的实现预览API看看
图像分析
图像分析为应用提供 CPU 访问的图像,可以对这些图像执行图像处理、计算机视觉或机器学习推断。应用会实现对每个帧运行的 analyze() 方法
图像分析视频帧输出有两种模式:非阻塞(默认模式)与阻塞
非阻塞模式:在该模式下,执行器始终会将最新的图像缓存到图像缓冲区(与深度为 1 的队列相似),与此同时,应用会分析上一个图像
阻塞模式:在该模式下,内部执行器可以向内部图像队列添加多个图像,并仅在队列已满时才开始丢帧
图像分析实现
构建图像分析用例ImageAnalysis
scss
public ImageAnalysis getImageAnalysis() {
if (mImageAnalysis == null) {
mImageAnalysis = new ImageAnalysis.Builder()
// .setBackgroundExecutor(executor -> {
//
// })
.setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST)//输出流策略,
.setImageQueueDepth(STRATEGY_BLOCK_PRODUCER)
.setOutputImageRotationEnabled(true) //允许输出流旋转
// .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888) //输出流格式
// .setTargetAspectRatio(AspectRatio.RATIO_4_3) //设置宽高比
// .setTargetName("" + System.currentTimeMillis()) //设置名称
.setTargetResolution(new Size(800, 1280)) //设置输出的分辨率
.setTargetRotation(ROTATION_270) //输出流成像方向
.build();
}
return mImageAnalysis;
}
在构建ImageAnalysis的时候还可依据自己的需求进行相关配置,例如输出流成像方向,输出流格式等等
构建的ImageAnalysis绑定生命周期,同时创建ImageAnalysis.Analyzer,获取ImageProxy
less
CameraUtils.getInstance().getImageAnalysis().setAnalyzer(CameraXExecutors.mainThreadExecutor(), new ImageAnalysis.Analyzer() {
@Override
public void analyze(@NonNull ImageProxy image) {
f (image.getFormat() == YUV_420_888) {
Log.d(TAG, "onCreate: YUV_420_888");
}
//创建位图
Bitmap bgBitmap = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888);
//获取YuvToRgbConverter对象
YuvToRgbConverter yuvToRgbConverter = new YuvToRgbConverter(MyApplication.mContext);
//填充位图,获取Bitmap
yuvToRgbConverter.yuvToRgb(Objects.requireNonNull(image.getImage()),bgBitmap);
//释放当前帧
image.close();
}
});
ImageProxy同时可转换出需要的数据量,下面是NV21的例子
ini
//CPU占用10%
public static byte[] yuv420ToNv21(ImageProxy image) {
ImageProxy.PlaneProxy[] planes = image.getPlanes();
ByteBuffer yBuffer = planes[0].getBuffer();
ByteBuffer uBuffer = planes[1].getBuffer();
ByteBuffer vBuffer = planes[2].getBuffer();
int ySize = yBuffer.remaining();
int uSize = uBuffer.remaining();
int vSize = vBuffer.remaining();
int size = image.getWidth() * image.getHeight();
byte[] nv21 = new byte[size * 3 / 2];
yBuffer.get(nv21, 0, ySize);
vBuffer.get(nv21, ySize, vSize);
byte[] u = new byte[uSize];
uBuffer.get(u);
//每隔开一位替换V,达到VU交替
int pos = ySize + 1;
for (int i = 0; i < uSize; i++) {
if (i % 2 == 0) {
nv21[pos] = u[i];
pos += 2;
}
}
return nv21;
}
图片拍摄
图片拍摄用例旨在拍摄高分辨率的优质照片,不仅提供简单的相机手动控制功能,还提供自动白平衡、自动曝光和自动对焦 (3A) 功能
- takePicture(Executor, OnImageCapturedCallback):此方法为拍摄的图片提供内存缓冲区
- takePicture(OutputFileOptions, Executor, OnImageSavedCallback):此方法将拍摄的图片保存到提供的文件位置
图片拍摄实现
构建ImageCapture用例
scss
public ImageCapture getImageCapture() {
if (mImageCapture == null) {
mImageCapture = new ImageCapture.Builder()
// .setCaptureMode(CAPTURE_MODE_MAXIMIZE_QUALITY) //设置拍照捕获模式
// .setFlashMode(FLASH_MODE_OFF) //设置补光模式
// .setIoExecutor(command -> {
//
// })
.setJpegQuality(100) //设置图像质量
// .setTargetAspectRatio(AspectRatio.RATIO_4_3) //设置图像宽高比
// .setTargetName("" + System.currentTimeMillis()) //设置截图名称
.setTargetResolution(new Size(800, 1280)) //设置图像分辨率
.setTargetRotation(ROTATION_270) //设置图像旋转
.build();
}
return mImageCapture;
}
在构建ImageCapture的时候还可依据自己的需求进行相关配置,例如图像旋转角度,图像质量等等
构建的ImageCapture需要绑定生命周期
less
public void takePic() {
File file = new File(VIDEOCALL_PICTURE_PATH, VIDEOCALL_PICTURE_NAME);
ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(file).build();
Uri uri = Uri.fromFile(file);
CameraXOpen.getInstance().getImageCapture().takePicture(outputFileOptions,
CameraXExecutors.mainThreadExecutor(),
new ImageCapture.OnImageSavedCallback() {
@Override
public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
Log.d(TAG, "outputFileResults:"+ outputFileResults.getSavedUri() + "\nuri = " + uri);
}
@Override
public void onError(@NonNull ImageCaptureException exception) {
Log.d(TAG, "onError:" + exception.getImageCaptureError());
}
});
}
视频拍摄,我这边不做介绍,有兴趣的可以自行了解学习下