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));
}

以下配置组合保证受支持(当需要预览或视频捕获但不能同时两者都存在时)

预览或视频捕获 图像捕捉 分析 描述
为用户提供预览或录制视频、拍照以及分析图像流。
  拍照并分析图像流。
  为用户提供预览或录制视频并拍照。
  为用户提供预览或录制视频并分析图像流。

当同时需要预览和视频捕获时,以下用例组合将有条件地受支持

预览 视频捕捉 图像捕捉 分析 特殊要求
    所有相机均保证
  LIMITED(或更好)相机设备。
  LEVEL_3(或更好)相机设备。

此外,

  • 每个用例都可以独立工作。例如,应用可以在不使用预览的情况下录制视频。
  • 启用扩展后,仅保证ImageCapturePreview组合有效。根据 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;
}

权限

您的应用将需要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-alpha01"
  // 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-alpha01"
    // 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 的更多信息,请参阅以下其他资源。

Codelab

  • CameraX 入门
  • 代码示例

  • CameraX 示例应用