CameraX 架构

此页面介绍 CameraX 的架构,包括其结构、如何使用 API、如何处理生命周期以及如何组合用例。

CameraX 结构

您可以使用 CameraX 通过称为用例的抽象与设备的相机进行交互。以下用例可用

  • 预览:接受用于显示预览的表面,例如 PreviewView

  • 图像分析:提供可供 CPU 访问的缓冲区进行分析,例如机器学习。
  • 图像捕获:捕获并保存照片。
  • 视频捕获:使用 VideoCapture 捕获视频和音频。

用例可以组合并同时激活。例如,一个应用程序可以让用户使用预览用例查看相机看到的图像,使用图像分析用例来确定照片中的人是否在微笑,并在他们微笑时包含一个图像捕获用例来拍照。

API 模型

要使用该库,您需要指定以下内容

  • 所需的用例以及配置选项。
  • 通过附加监听器来处理输出数据。
  • 预期的流程,例如何时启用相机以及何时生成数据,方法是将用例绑定到 Android 架构生命周期

有两种编写 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 的默认 UseCasePreviewImageCaptureImageAnalysis。要关闭 ImageCaptureImageAnalysis,或打开 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 提供方法调用和回调以监视进度。

组合用例 中所述,您可以将某些用例组合绑定到单个生命周期。当您的应用程序需要支持无法组合的用例时,您可以执行以下操作之一

  • 将兼容的用例组合到多个 片段 中,然后在片段之间切换
  • 创建一个自定义生命周期组件并使用它手动控制相机生命周期

如果您将视图和相机用例的生命周期所有者分离(例如,如果您使用自定义生命周期或 保留片段),则必须确保通过使用 ProcessCameraProvider.unbindAll() 或单独取消绑定每个用例来确保所有用例都已从 CameraX 中取消绑定。或者,当您将用例绑定到生命周期时,您可以让 CameraX 管理打开和关闭捕获会话以及取消绑定用例。

如果您的所有相机功能都对应于单个生命周期感知组件(例如 AppCompatActivityAppCompat 片段)的生命周期,则在绑定所有所需用例时使用该组件的生命周期将确保相机功能在生命周期感知组件处于活动状态时准备就绪,并在其他情况下安全地释放,不会消耗任何资源。

自定义 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));
}

CameraX 允许同时使用一个 PreviewVideoCaptureImageAnalysisImageCapture 的实例。此外,

  • 每个用例都可以独立工作。例如,应用程序可以在不使用预览的情况下录制视频。
  • 启用扩展后,仅保证 ImageCapturePreview 组合有效。根据 OEM 实现,可能无法添加 ImageAnalysis;无法为 VideoCapture 用例启用扩展。有关详细信息,请查看 扩展参考文档
  • 根据相机功能,某些相机可能在较低分辨率模式下支持该组合,但在某些较高分辨率下可能不支持同一组合。
  • 在相机硬件级别为 FULL 或更低的设备上,组合 PreviewVideoCaptureImageCaptureImageAnalysis 可能会迫使 CameraX 为 PreviewVideoCapture 复制相机的 PRIV 流。这种称为流共享的复制使这些功能能够同时使用,但会增加处理需求。因此,您可能会遇到稍微更高的延迟和更低的电池续航时间。

可以从 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\<CameraInfo\> 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;
}

权限

您的应用程序将需要 CAMERA 权限。要将图像保存到文件,它还需要 WRITE_EXTERNAL_STORAGE 权限,除了在运行 Android 10 或更高版本的设备上。

有关配置应用程序权限的更多信息,请阅读 请求应用程序权限

要求

CameraX 具有以下最低版本要求

  • Android API 级别 21
  • Android 架构组件 1.1.1

对于生命周期感知活动,请使用 FragmentActivityAppCompatActivity

声明依赖项

要添加对 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.5.0-alpha03"
  // 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.5.0-alpha03"
    // 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 实现中的属性。有关完整详细信息,请参阅 Interop 包

有关 CameraX 如何配置 Camera2 属性的更多信息,请使用 Camera2CameraInfo 读取底层的 CameraCharacteristics。您还可以选择通过以下两种途径之一写入底层 Camera2 属性

以下代码示例使用流用例来优化视频通话。使用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 的更多信息,请参阅以下其他资源。

代码实验室

  • CameraX 入门
  • 代码示例

  • CameraX 示例应用