CameraX 架构

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

CameraX 结构

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

  • 预览:接受用于显示预览的 Surface,例如 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 的更多用法,请参阅 QR 码扫描器示例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 管理捕获会话的打开和关闭以及用例的解绑。

如果您所有的相机功能都对应于单个生命周期感知组件(例如 AppCompatActivityAppCompat 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));
}

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

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

可以从 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

对于生命周期感知型 Activity,请使用 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-beta01"
  // 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-beta01"
    // 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 属性:

以下代码示例使用流用例来优化视频通话。使用 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 示例应用