Android NDK开发详解相机之CameraX 架构
本页介绍了 CameraX 的架构,包括其结构、如何与 API 搭配使用、如何与生命周期配合使用以及如何组合各种用例。
CameraX 结构
您可以使用 CameraX,借助名为“用例”的抽象概念与设备的相机进行交互。提供的用例如下:
预览:接受用于显示预览的 Surface,例如 PreviewView。
图片分析:为分析(例如机器学习)提供 CPU 可访问的缓冲区。
图片拍摄:拍摄并保存照片。
视频拍摄:通过 VideoCapture 拍摄视频和音频
不同用例可以组合使用,也可以同时处于活跃状态。例如,应用中可以加入预览用例,以便让用户查看进入相机视野的画面;加入图片分析用例,以确定照片里的人物是否在微笑;还可以加入图片拍摄用例,以在人物微笑时拍摄照片。
API 模型
如需使用该库,请指定以下内容:
具有配置选项的所需用例。
通过附加监听器来指定如何处理输出数据。
通过将用例绑定到 Android 架构生命周期来指定目标流程,例如何时启用相机及何时生成数据。
您可以通过 2 种方式编写 CameraX 应用:CameraController(如果您希望通过最简单的方式使用 CameraX,这非常适合)或 CameraProvider(如果需要更高的灵活性,这非常适合)。
CameraController
CameraController 在单个类中提供大多数 CameraX 核心功能。它只需少量设置代码,并且可自动处理相机初始化、用例管理、目标旋转、点按对焦、双指张合缩放等操作。扩展 CameraController 的具体类为 LifecycleCameraController。
Kotlin
val previewView: PreviewView = viewBinding.previewView
var cameraController = LifecycleCameraController(baseContext)
cameraController.bindToLifecycle(this)
cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
previewView.controller = cameraController
Java
PreviewView previewView = viewBinding.previewView;
LifecycleCameraController cameraController = new LifecycleCameraController(baseContext);
cameraController.bindToLifecycle(this);
cameraController.setCameraSelector(CameraSelector.DEFAULT_BACK_CAMERA);
previewView.setController(cameraController);
CameraController 的默认 UseCase 为 Preview、ImageCapture 和 ImageAnalysis。如需关闭 ImageCapture 或 ImageAnalysis,或者开启 VideoCapture,请使用 setEnabledUseCases() 方法。
如需了解 CameraController 的更多用法,请参阅二维码扫描器示例或 CameraController 基础知识视频。
CameraProvider
CameraProvider 仍然易于使用,但由于应用开发者会处理更多设置,因此有更多机会自定义配置,例如在 ImageAnalysis 中启用输出图片旋转或设置输出图像格式。您还可以使用自定义 Surface 进行相机预览以提高灵活性,而对于 CameraController,您需要使用 PreviewView。如果现有的 Surface 代码已是应用的其他部分的输入,则使用该代码会非常有用。
您可以使用 set() 方法配置用例,并使用 build() 方法完成这些用例。每个用例对象都提供一组特定于该用例的 API。例如,图片拍摄用例会提供 takePicture() 方法调用。
应用没有在 onResume() 和 onPause() 中放置具体的启动和停止方法调用,而是使用 cameraProvider.bindToLifecycle() 指定要与相机关联的生命周期。之后,该生命周期会告知 CameraX 何时配置相机拍摄会话并确保相机状态随生命周期的转换相应地变化。
如需了解每个用例的实现步骤,请参阅实现预览、分析图片、图片拍摄和视频拍摄
预览用例会与 Surface 互动以展示预览。应用使用以下代码创建具有配置选项的用例:
Kotlin
val preview = Preview.Builder().build()
val viewFinder: PreviewView = findViewById(R.id.previewView)
// The use case is bound to an Android Lifecycle with the following code
val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)
// PreviewView creates a surface provider and is the recommended provider
preview.setSurfaceProvider(viewFinder.getSurfaceProvider())
Java
Preview preview = new Preview.Builder().build();
PreviewView viewFinder = findViewById(R.id.view_finder);
// The use case is bound to an Android Lifecycle with the following code
Camera camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview);
// PreviewView creates a surface provider, using a Surface from a different
// kind of view will require you to implement your own surface provider.
preview.previewSurfaceProvider = viewFinder.getSurfaceProvider();
如需查看更多示例代码,请参阅官方 CameraX 示例应用。
CameraX 生命周期
CameraX 会按照生命周期确定何时打开相机、何时创建拍摄会话以及何时停止和关闭。用例 API 提供方法调用和回调来监控进度情况。
如组合用例中所述,您可以将多个用例一起绑定到单个生命周期。当您的应用需要支持无法组合的用例时,您可以执行以下操作之一:
将兼容的用例划分到多个 Fragment 中,然后在 Fragment 之间进行切换
创建自定义生命周期组件并使用该组件来手动控制相机生命周期
如果您将视图用例和相机用例的生命周期所有者分开(例如,如果您使用自定义生命周期或保留 Fragment),则必须通过使用 ProcessCameraProvider.unbindAll() 或分别取消绑定各个用例来确保所有用例均未绑定 CameraX。或者,当您将用例绑定到生命周期时,可以让 CameraX 管理拍摄会话的开启和关闭操作以及用例的取消绑定操作。
如果所有相机功能都对应于单个生命周期感知型组件(例如 AppCompatActivity 或 AppCompat Fragment)的生命周期,那么在绑定全部所需用例时使用该组件的生命周期,可确保相机功能在该组件处于活动状态时准备就绪,并在该组件进入非活动状态时获得安全处置,而不会消耗任何资源。
自定义 LifecycleOwner
对于高级用例,您可以创建自定义 LifecycleOwner,以使您的应用显式控制 CameraX 会话生命周期,而不是将其绑定到标准 Android LifecycleOwner。
以下代码示例展示了如何创建简单的自定义 LifecycleOwner:
Kotlin
class CustomLifecycle : LifecycleOwner {
private val lifecycleRegistry: LifecycleRegistry
init {
lifecycleRegistry = LifecycleRegistry(this);
lifecycleRegistry.markState(Lifecycle.State.CREATED)
}
...
fun doOnResume() {
lifecycleRegistry.markState(State.RESUMED)
}
...
override fun getLifecycle(): Lifecycle {
return lifecycleRegistry
}
}
Java
public class CustomLifecycle implements LifecycleOwner {
private LifecycleRegistry lifecycleRegistry;
public CustomLifecycle() {
lifecycleRegistry = new LifecycleRegistry(this);
lifecycleRegistry.markState(Lifecycle.State.CREATED);
}
...
public void doOnResume() {
lifecycleRegistry.markState(State.RESUMED);
}
...
public Lifecycle getLifecycle() {
return lifecycleRegistry;
}
}
借助此 LifecycleOwner,您的应用可以将状态转换放置在代码中所需的位置。如需详细了解如何在应用中实现此功能,请参阅实现自定义 LifecycleOwner。
并发用例
多个用例可以同时运行。虽然可以将多个用例依序绑定到一个生命周期,但最好通过对 CameraProcessProvider.bindToLifecycle() 的一次调用来绑定所有用例。如需详细了解配置更改的最佳做法,请参阅处理配置更改。
在以下代码示例中,应用指定了要创建并同时运行的两个用例,还指定了要同时用于这两个用例的生命周期,以便这两个用例都按照该生命周期启动和停止。
Kotlin
private lateinit var imageCapture: ImageCapture
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener(Runnable {
// Camera provider is now guaranteed to be available
val cameraProvider = cameraProviderFuture.get()
// Set up the preview use case to display camera preview.
val preview = Preview.Builder().build()
// Set up the capture use case to allow users to take photos.
imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.build()
// Choose the camera by requiring a lens facing
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_FRONT)
.build()
// Attach use cases to the camera with the same lifecycle owner
val camera = cameraProvider.bindToLifecycle(
this as LifecycleOwner, cameraSelector, preview, imageCapture)
// Connect the preview use case to the previewView
preview.setSurfaceProvider(
previewView.getSurfaceProvider())
}, ContextCompat.getMainExecutor(this))
}
Java
private ImageCapture imageCapture;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PreviewView previewView = findViewById(R.id.previewView);
ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
ProcessCameraProvider.getInstance(this);
cameraProviderFuture.addListener(() -> {
try {
// Camera provider is now guaranteed to be available
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
// Set up the view finder use case to display camera preview
Preview preview = new Preview.Builder().build();
// Set up the capture use case to allow users to take photos
imageCapture = new ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.build();
// Choose the camera by requiring a lens facing
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(lensFacing)
.build();
// Attach use cases to the camera with the same lifecycle owner
Camera camera = cameraProvider.bindToLifecycle(
((LifecycleOwner) this),
cameraSelector,
preview,
imageCapture);
// Connect the preview use case to the previewView
preview.setSurfaceProvider(
previewView.getSurfaceProvider());
} catch (InterruptedException | ExecutionException e) {
// Currently no exceptions thrown. cameraProviderFuture.get()
// shouldn't block since the listener is being called, so no need to
// handle InterruptedException.
}
}, ContextCompat.getMainExecutor(this));
}
保证可以支持以下配置组合(当单独需要“预览”或“视频拍摄”用例时):
如果同时需要“预览”和“视频拍摄”用例,则可以附条件地支持以下用例组合:
此外,
每个用例都可以单独使用。例如,应用可以在不使用预览的情况下录制视频。
启用扩展后,只能保证能够使用 ImageCapture 和 Preview 的组合。根据 OEM 实现情况,可能无法同时添加 ImageAnalysis;无法为 VideoCapture 用例启用扩展。如需了解详情,请参阅扩展参考文档。
对于某些相机而言,在较低分辨率模式下可以支持的组合,在较高的分辨率下将无法支持,这具体取决于相机的功能。
所支持的硬件级别可以从 Camera2CameraInfo 中检索。例如,以下代码可检查默认的后置摄像头是否是 LEVEL_3 设备:
Kotlin
@androidx.annotation.OptIn(ExperimentalCamera2Interop::class)
fun isBackCameraLevel3Device(cameraProvider: ProcessCameraProvider) : Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return CameraSelector.DEFAULT_BACK_CAMERA
.filter(cameraProvider.availableCameraInfos)
.firstOrNull()
?.let { Camera2CameraInfo.from(it) }
?.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
}
return false
}
Java
@androidx.annotation.OptIn(markerClass = ExperimentalCamera2Interop.class)
Boolean isBackCameraLevel3Device(ProcessCameraProvider cameraProvider) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
List\ filteredCameraInfos = CameraSelector.DEFAULT_BACK_CAMERA
.filter(cameraProvider.getAvailableCameraInfos());
if (!filteredCameraInfos.isEmpty()) {
return Objects.equals(
Camera2CameraInfo.from(filteredCameraInfos.get(0)).getCameraCharacteristic(
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL),
CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3);
}
}
return false;
}
注意:如果创建的用例组合不兼容,则在首次调用 createCaptureSession() 时会抛出运行时错误。如果将其他用例添加到正在运行的会话中,可能需要进行重新配置,这可能会导致可见的故障。
权限
您的应用需要 CAMERA 权限。如需将图片保存到文件中,除非所用设备搭载 Android 10 或更高版本,否则应用还需要 WRITE_EXTERNAL_STORAGE 权限。
如需详细了解如何为应用配置权限,请参阅请求应用权限。
要求
CameraX 具有以下最低版本要求:
Android API 级别 21
Android 架构组件 1.1.1
对于能够感知生命周期的 Activity,请使用 FragmentActivity 或 AppCompatActivity。
声明依赖项
要添加 CameraX 的依赖项,您必须将 Google Maven 代码库添加到项目中。
打开项目的 settings.gradle 文件并添加 google() 代码库,如下所示:
Groovy
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
Kotlin
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
将以下内容添加到 Android 代码块的末尾:
Groovy
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
// For Kotlin projects
kotlinOptions {
jvmTarget = "1.8"
}
}
Kotlin
android {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
// For Kotlin projects
kotlinOptions {
jvmTarget = "1.8"
}
}
将以下内容添加到应用的每个模块的 build.gradle 文件中:
Groovy
dependencies {
// CameraX core library using the camera2 implementation
def camerax_version = "1.3.0-alpha04"
// The following line is optional, as the core library is included indirectly by camera-camera2
implementation "androidx.camera:camera-core:${camerax_version}"
implementation "androidx.camera:camera-camera2:${camerax_version}"
// If you want to additionally use the CameraX Lifecycle library
implementation "androidx.camera:camera-lifecycle:${camerax_version}"
// If you want to additionally use the CameraX VideoCapture library
implementation "androidx.camera:camera-video:${camerax_version}"
// If you want to additionally use the CameraX View class
implementation "androidx.camera:camera-view:${camerax_version}"
// If you want to additionally add CameraX ML Kit Vision Integration
implementation "androidx.camera:camera-mlkit-vision:${camerax_version}"
// If you want to additionally use the CameraX Extensions library
implementation "androidx.camera:camera-extensions:${camerax_version}"
}
Kotlin
dependencies {
// CameraX core library using the camera2 implementation
val camerax_version = "1.3.0-alpha04"
// The following line is optional, as the core library is included indirectly by camera-camera2
implementation("androidx.camera:camera-core:${camerax_version}")
implementation("androidx.camera:camera-camera2:${camerax_version}")
// If you want to additionally use the CameraX Lifecycle library
implementation("androidx.camera:camera-lifecycle:${camerax_version}")
// If you want to additionally use the CameraX VideoCapture library
implementation("androidx.camera:camera-video:${camerax_version}")
// If you want to additionally use the CameraX View class
implementation("androidx.camera:camera-view:${camerax_version}")
// If you want to additionally add CameraX ML Kit Vision Integration
implementation("androidx.camera:camera-mlkit-vision:${camerax_version}")
// If you want to additionally use the CameraX Extensions library
implementation("androidx.camera:camera-extensions:${camerax_version}")
}
如需详细了解如何配置应用以满足上述要求,请参阅声明依赖项。
CameraX 与 Camera2 的互操作性
CameraX 基于 Camera2 构建而成,并且 CameraX 提供了在 Camera2 实现中读取甚至写入属性的方式。如需了解完整详情,请参阅互操作性软件包。
如需详细了解 CameraX 如何配置 Camera2 属性,请使用 Camera2CameraInfo 读取底层 CameraCharacteristics。您还可以选择采用以下两个途径之一来写入底层 Camera2 属性:
使用 Camera2CameraControl,它让您可以在底层 CaptureRequest 上设置属性,例如自动对焦模式。
使用 Camera2Interop.Extender 扩展 CameraX UseCase。这样您就可以在 CaptureRequest 上设置属性,就像使用 Camera2CameraControl 一样。它还提供了一些额外的控件,例如设置数据流用例以根据您的使用场景优化相机。有关信息,请参阅使用数据流用例提高性能。
注意:在 CameraX 中,设置底层 Camera2 属性会被标记为“实验性”,因为 Google 希望开发者能了解其使用情况。您设置的值会替换 CameraX 所设置的任何值。我们建议您仅在绝对必要时执行此操作,也建议在您这一端进行其他测试。
以下代码示例使用数据流用例来优化视频通话。使用 Camera2CameraInfo 可提取视频通话流用例是否可用。然后,使用 Camera2Interop.Extender 设置底层数据流用例。
Kotlin
// Set underlying Camera2 stream use case to optimize for video calls.
val videoCallStreamId =
CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong()
// Check available CameraInfos to find the first one that supports
// the video call stream use case.
val frontCameraInfo = cameraProvider.getAvailableCameraInfos()
.first { cameraInfo ->
val isVideoCallStreamingSupported = Camera2CameraInfo.from(cameraInfo)
.getCameraCharacteristic(
CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES
)?.contains(videoCallStreamId)
val isFrontFacing = (cameraInfo.getLensFacing() ==
CameraSelector.LENS_FACING_FRONT)
(isVideoCallStreamingSupported == true) && isFrontFacing
}
val cameraSelector = frontCameraInfo.cameraSelector
// Start with a Preview Builder.
val previewBuilder = Preview.Builder()
.setTargetAspectRatio(screenAspectRatio)
.setTargetRotation(rotation)
// Use Camera2Interop.Extender to set the video call stream use case.
Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId)
// Bind the Preview UseCase and the corresponding CameraSelector.
val preview = previewBuilder.build()
camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)
Java
// Set underlying Camera2 stream use case to optimize for video calls.
Long videoCallStreamId =
CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong();
// Check available CameraInfos to find the first one that supports
// the video call stream use case.
List<CameraInfo> cameraInfos = cameraProvider.getAvailableCameraInfos();
CameraInfo frontCameraInfo = null;
for (cameraInfo in cameraInfos) {
Long[] availableStreamUseCases = Camera2CameraInfo.from(cameraInfo)
.getCameraCharacteristic(
CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES
);
boolean isVideoCallStreamingSupported = Arrays.List(availableStreamUseCases)
.contains(videoCallStreamId);
boolean isFrontFacing = (cameraInfo.getLensFacing() ==
CameraSelector.LENS_FACING_FRONT);
if (isVideoCallStreamingSupported && isFrontFacing) {
frontCameraInfo = cameraInfo;
}
}
if (frontCameraInfo == null) {
// Handle case where video call streaming is not supported.
}
CameraSelector cameraSelector = frontCameraInfo.getCameraSelector();
// Start with a Preview Builder.
Preview.Builder previewBuilder = Preview.Builder()
.setTargetAspectRatio(screenAspectRatio)
.setTargetRotation(rotation);
// Use Camera2Interop.Extender to set the video call stream use case.
Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId);
// Bind the Preview UseCase and the corresponding CameraSelector.
Preview preview = previewBuilder.build()
Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)
其他资源
要详细了解 CameraX,请参阅下面列出的其他资源。
Codelab
CameraX 使用入门
代码示例
CameraX 示例应用