配置选项

您可以配置每个 CameraX 用例以控制用例操作的不同方面。

例如,对于图像捕捉用例,您可以设置目标纵横比和闪光模式。以下代码显示了一个示例

Kotlin

val imageCapture = ImageCapture.Builder()
    .setFlashMode(...)
    .setTargetAspectRatio(...)
    .build()

Java

ImageCapture imageCapture =
    new ImageCapture.Builder()
        .setFlashMode(...)
        .setTargetAspectRatio(...)
        .build();

除了配置选项之外,某些用例还公开了 API,以便在创建用例后动态更改设置。有关特定于各个用例的配置信息,请参阅 实施预览分析图像图像捕捉

CameraXConfig

为了简化,CameraX 具有默认配置,例如适用于大多数使用场景的内部执行器和处理程序。但是,如果您的应用程序有特殊要求或希望自定义这些配置,CameraXConfig 是为此目的提供的接口。

使用 CameraXConfig,应用程序可以执行以下操作

使用模型

以下步骤描述了如何使用 CameraXConfig

  1. 使用自定义配置创建一个 CameraXConfig 对象。
  2. 在您的 Application 中实现 CameraXConfig.Provider 接口,并在 getCameraXConfig() 中返回您的 CameraXConfig 对象。
  3. 将您的 Application 类添加到您的 AndroidManifest.xml 文件中,如 此处 所述。

例如,以下代码示例将 CameraX 日志记录限制为仅错误消息

Kotlin

class CameraApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
           .setMinimumLoggingLevel(Log.ERROR).build()
   }
}

如果您的应用程序需要在设置后了解 CameraX 配置,请保留 CameraXConfig 对象的本地副本。

相机限制器

在第一次调用 ProcessCameraProvider.getInstance() 时,CameraX 会枚举并查询设备上可用相机的特性。由于 CameraX 需要与硬件组件通信,因此此过程对于每个相机可能需要花费相当长的时间,尤其是在低端设备上。如果您的应用程序仅使用设备上的特定相机,例如默认的前置摄像头,您可以设置 CameraX 以忽略其他相机,这可以减少应用程序使用的相机的启动延迟。

如果传递给 CameraXConfig.Builder.setAvailableCamerasLimiter()CameraSelector 过滤掉了某个相机,CameraX 会将其视为不存在。例如,以下代码将应用程序限制为仅使用设备的默认后置摄像头

Kotlin

class MainApplication : Application(), CameraXConfig.Provider {
   override fun getCameraXConfig(): CameraXConfig {
       return CameraXConfig.Builder.fromConfig(Camera2Config.defaultConfig())
              .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA)
              .build()
   }
}

线程

CameraX 所构建的许多平台 API 都需要与硬件进行阻塞式进程间通信 (IPC),有时响应需要数百毫秒。出于此原因,CameraX 仅从后台线程调用这些 API,以便主线程不会被阻塞并且 UI 保持流畅。CameraX 在内部管理这些后台线程,以便此行为看起来是透明的。但是,某些应用程序需要严格控制线程。 CameraXConfig 允许应用程序通过 CameraXConfig.Builder.setCameraExecutor()CameraXConfig.Builder.setSchedulerHandler() 设置使用的后台线程。

相机执行器

相机执行器用于所有内部 Camera 平台 API 调用,以及来自这些 API 的回调。CameraX 分配并管理一个内部 Executor 来执行这些任务。但是,如果您的应用程序需要更严格地控制线程,请使用 CameraXConfig.Builder.setCameraExecutor()

调度程序处理程序

调度程序处理程序用于以固定的时间间隔安排内部任务,例如在相机不可用时重试打开相机。此处理程序不执行作业,而仅将它们分派给相机执行器。它有时也用于需要 Handler 进行回调的旧版 API 平台。在这些情况下,回调仍然仅直接分派给相机执行器。CameraX 分配并管理一个内部 HandlerThread 来执行这些任务,但您可以使用 CameraXConfig.Builder.setSchedulerHandler() 覆盖它。

日志记录

CameraX 日志记录允许应用程序过滤 logcat 消息,因为避免在生产代码中使用冗长的消息是一种良好的实践。CameraX 支持四个日志记录级别,从最详细到最严重

  • Log.DEBUG(默认)
  • Log.INFO
  • Log.WARN
  • Log.ERROR

有关这些日志级别的详细说明,请参阅 Android Log 文档。使用 CameraXConfig.Builder.setMinimumLoggingLevel(int) 为您的应用程序设置适当的日志记录级别。

自动选择

CameraX 自动提供特定于您的应用程序正在运行的设备的功能。例如,如果您未指定分辨率或您指定的分辨率不受支持,CameraX 会自动确定要使用的最佳分辨率。所有这些都由库处理,无需您编写特定于设备的代码。

CameraX 的目标是成功初始化相机会话。这意味着 CameraX 会根据设备功能对分辨率和纵横比进行折衷。折衷可能会发生,因为

  • 设备不支持请求的分辨率。
  • 设备存在兼容性问题,例如需要特定分辨率才能正常运行的旧版设备。
  • 在某些设备上,某些格式仅在某些纵横比下可用。
  • 设备更喜欢 JPEG 或视频编码的“最接近 mod16”。有关更多信息,请参阅 SCALER_STREAM_CONFIGURATION_MAP

尽管 CameraX 创建和管理会话,但始终检查代码中用例输出上的返回图像大小并进行相应调整。

旋转

默认情况下,相机旋转设置为在创建用例期间与默认显示器的旋转匹配。在此默认情况下,CameraX 生成输出以使应用程序与您期望在预览中看到的匹配。您可以将旋转更改为自定义值以支持多显示器设备,方法是在配置用例对象时或在创建后动态传递当前显示方向。

您的应用程序可以使用配置设置设置目标旋转。然后,它可以通过使用用例 API 中的方法(例如 ImageAnalysis.setTargetRotation())更新旋转设置,即使生命周期处于运行状态。您可能在应用程序锁定为纵向模式时使用此功能(因此在旋转时不会发生重新配置),但照片或分析用例需要了解设备的当前旋转。例如,可能需要旋转感知,以便面部在面部检测中正确方向,或照片设置为横向或纵向。

捕获图像的数据可能存储在没有旋转信息的情况下。Exif 数据包含旋转信息,以便图库应用程序在保存后以正确的方向显示图像。

要以正确的方向显示预览数据,您可以使用来自 Preview.PreviewOutput() 的元数据输出创建变换。

以下代码示例显示了如何在方向事件上设置旋转

Kotlin

override fun onCreate() {
    val imageCapture = ImageCapture.Builder().build()

    val orientationEventListener = object : OrientationEventListener(this as Context) {
        override fun onOrientationChanged(orientation : Int) {
            // Monitors orientation values to determine the target rotation value
            val rotation : Int = when (orientation) {
                in 45..134 -> Surface.ROTATION_270
                in 135..224 -> Surface.ROTATION_180
                in 225..314 -> Surface.ROTATION_90
                else -> Surface.ROTATION_0
            }

            imageCapture.targetRotation = rotation
        }
    }
    orientationEventListener.enable()
}

Java

@Override
public void onCreate() {
    ImageCapture imageCapture = new ImageCapture.Builder().build();

    OrientationEventListener orientationEventListener = new OrientationEventListener((Context)this) {
       @Override
       public void onOrientationChanged(int orientation) {
           int rotation;

           // Monitors orientation values to determine the target rotation value
           if (orientation >= 45 && orientation < 135) {
               rotation = Surface.ROTATION_270;
           } else if (orientation >= 135 && orientation < 225) {
               rotation = Surface.ROTATION_180;
           } else if (orientation >= 225 && orientation < 315) {
               rotation = Surface.ROTATION_90;
           } else {
               rotation = Surface.ROTATION_0;
           }

           imageCapture.setTargetRotation(rotation);
       }
    };

    orientationEventListener.enable();
}

根据设置的旋转,每个用例要么直接旋转图像数据,要么为非旋转图像数据的使用者提供旋转元数据。

  • 预览:提供元数据输出,以便使用 Preview.getTargetRotation() 了解目标分辨率的旋转。
  • 图像分析:提供元数据输出,以便图像缓冲区坐标相对于显示坐标是已知的。
  • 图像捕获:图像 Exif 元数据、缓冲区或缓冲区和元数据都会被更改以记录旋转设置。更改的值取决于 HAL 实现。

裁剪矩形

默认情况下,裁剪矩形是完整的缓冲区矩形。您可以使用 ViewPortUseCaseGroup 自定义它。通过对用例进行分组并设置视口,CameraX 保证组中所有用例的裁剪矩形指向相机传感器中的同一区域。

以下代码片段显示了如何使用这两个类

Kotlin

val viewPort =  ViewPort.Builder(Rational(width, height), display.rotation).build()
val useCaseGroup = UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build()
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup)

Java

ViewPort viewPort = new ViewPort.Builder(
         new Rational(width, height),
         getDisplay().getRotation()).build();
UseCaseGroup useCaseGroup = new UseCaseGroup.Builder()
    .addUseCase(preview)
    .addUseCase(imageAnalysis)
    .addUseCase(imageCapture)
    .setViewPort(viewPort)
    .build();
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, useCaseGroup);

ViewPort 定义了最终用户可见的缓冲区矩形。然后 CameraX 根据视口和附加用例的属性计算最大的可能裁剪矩形。通常,为了实现所见即所得的效果,您可以根据预览用例配置视口。获取视口的一种简单方法是使用 PreviewView

以下代码片段显示了如何获取 ViewPort 对象

Kotlin

val viewport = findViewById<PreviewView>(R.id.preview_view).viewPort

Java

ViewPort viewPort = ((PreviewView)findViewById(R.id.preview_view)).getViewPort();

在前面的示例中,应用程序从 ImageAnalysisImageCapture 获取的内容与最终用户在 PreviewView 中看到的内容匹配,假设 PreviewView 的缩放类型设置为默认值 FILL_CENTER。在将裁剪矩形和旋转应用于输出缓冲区后,所有用例中的图像都是相同的,尽管可能具有不同的分辨率。有关如何应用变换信息的更多信息,请参阅 变换输出

相机选择

CameraX 会自动为您的应用程序的要求和用例选择最佳的相机设备。如果您希望使用与为您选择的设备不同的设备,则有一些选择

以下代码示例说明如何创建CameraSelector来影响设备选择

Kotlin

fun selectExternalOrBestCamera(provider: ProcessCameraProvider):CameraSelector? {
   val cam2Infos = provider.availableCameraInfos.map {
       Camera2CameraInfo.from(it)
   }.sortedByDescending {
       // HARDWARE_LEVEL is Int type, with the order of:
       // LEGACY < LIMITED < FULL < LEVEL_3 < EXTERNAL
       it.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
   }

   return when {
       cam2Infos.isNotEmpty() -> {
           CameraSelector.Builder()
               .addCameraFilter {
                   it.filter { camInfo ->
                       // cam2Infos[0] is either EXTERNAL or best built-in camera
                       val thisCamId = Camera2CameraInfo.from(camInfo).cameraId
                       thisCamId == cam2Infos[0].cameraId
                   }
               }.build()
       }
       else -> null
    }
}

// create a CameraSelector for the USB camera (or highest level internal camera)
val selector = selectExternalOrBestCamera(processCameraProvider)
processCameraProvider.bindToLifecycle(this, selector, preview, analysis)

同时选择多个摄像头

从 CameraX 1.3 开始,您还可以同时选择多个摄像头。例如,您可以绑定到前后摄像头,以同时从两个角度拍摄照片或录制视频。

使用并发摄像头功能时,设备可以同时操作两个具有不同朝向镜头的摄像头,或同时操作两个后置摄像头。以下代码块显示了如何在调用bindToLifecycle时设置两个摄像头,以及如何从返回的ConcurrentCamera对象中获取这两个 Camera 对象。

Kotlin

// Build ConcurrentCameraConfig
val primary = ConcurrentCamera.SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val secondary = ConcurrentCamera.SingleCameraConfig(
    secondaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
)

val concurrentCamera = cameraProvider.bindToLifecycle(
    listOf(primary, secondary)
)

val primaryCamera = concurrentCamera.cameras[0]
val secondaryCamera = concurrentCamera.cameras[1]

Java

// Build ConcurrentCameraConfig
SingleCameraConfig primary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

SingleCameraConfig secondary = new SingleCameraConfig(
    primaryCameraSelector,
    useCaseGroup,
    lifecycleOwner
);

ConcurrentCamera concurrentCamera =  
    mCameraProvider.bindToLifecycle(Arrays.asList(primary, secondary));

Camera primaryCamera = concurrentCamera.getCameras().get(0);
Camera secondaryCamera = concurrentCamera.getCameras().get(1);

摄像头分辨率

您可以选择让 CameraX 根据设备功能、设备支持的硬件级别、用例和提供的纵横比的组合来设置图像分辨率。或者,您可以在支持该配置的用例中设置特定的目标分辨率或特定的纵横比。

自动分辨率

CameraX 可以根据cameraProcessProvider.bindToLifecycle()中指定的用例自动确定最佳分辨率设置。在可能的情况下,请在单个bindToLifecycle()调用中指定需要并发运行的所有用例。CameraX 通过考虑设备支持的硬件级别并考虑设备特定的差异(设备超出或不满足可用的流配置)来根据绑定的用例集确定分辨率。目的是让应用程序在各种设备上运行,同时最大限度地减少设备特定的代码路径。

图像捕获和图像分析用例的默认纵横比为 4:3。

用例具有可配置的纵横比,允许应用程序根据 UI 设计指定所需的纵横比。CameraX 输出会尽可能地匹配请求的纵横比,具体取决于设备的支持情况。如果不存在完全匹配的分辨率,则会选择最符合条件的分辨率。因此,应用程序决定了摄像头在应用程序中的显示方式,而 CameraX 则确定在不同设备上满足该显示方式的最佳摄像头分辨率设置。

例如,应用程序可以执行以下任何操作

  • 为用例指定 4:3 或 16:9 的目标分辨率
  • 指定自定义分辨率,CameraX 会尝试找到最接近的匹配项
  • ImageCapture指定裁剪纵横比

CameraX 自动选择内部 Camera2 表面分辨率。下表显示了分辨率

用例 内部表面分辨率 输出数据分辨率
预览 纵横比:最适合目标设置的分辨率。 内部表面分辨率。提供元数据以允许 View 针对目标纵横比进行裁剪、缩放和旋转。
默认分辨率:最高预览分辨率,或与预览纵横比匹配的最高设备首选分辨率。
最大分辨率:预览大小,指的是与设备屏幕分辨率或 1080p(1920x1080)最匹配的大小,取两者中较小的一个。
图像分析 纵横比:最适合目标设置的分辨率。 内部表面分辨率。
默认分辨率:默认目标分辨率设置为 640x480。调整目标分辨率和相应的纵横比将得到最佳支持的分辨率。
最大分辨率:摄像头设备的 YUV_420_888 格式的最大输出分辨率,从StreamConfigurationMap.getOutputSizes()中检索。目标分辨率默认设置为 640x480,因此,如果需要大于 640x480 的分辨率,则必须使用setTargetResolution()setTargetAspectRatio()从支持的分辨率中获取最接近的分辨率。
图像捕捉 纵横比:最适合设置的纵横比。 内部表面分辨率。
默认分辨率:最高可用分辨率,或与 ImageCapture 纵横比匹配的最高设备首选分辨率。
最大分辨率:摄像头设备的 JPEG 格式最大输出分辨率。使用StreamConfigurationMap.getOutputSizes()检索此分辨率。

指定分辨率

您可以在使用setTargetResolution(Size resolution)方法构建用例时设置特定分辨率,如下面的代码示例所示

Kotlin

val imageAnalysis = ImageAnalysis.Builder()
    .setTargetResolution(Size(1280, 720))
    .build()

Java

ImageAnalysis imageAnalysis =
  new ImageAnalysis.Builder()
    .setTargetResolution(new Size(1280, 720))
    .build();

您不能在同一个用例上同时设置目标纵横比和目标分辨率。这样做会在构建配置对象时抛出IllegalArgumentException

在旋转目标旋转的支持大小后,在坐标系中表达分辨率Size。例如,自然目标旋转中具有纵向自然方向的设备请求纵向图像可以指定 480x640,而旋转 90 度并以横向方向为目标的同一设备可以指定 640x480。

目标分辨率尝试为图像分辨率建立一个最小边界。实际图像分辨率是在大小上最接近且不小于目标分辨率的可用分辨率,由摄像头实现确定。

但是,如果不存在等于或大于目标分辨率的分辨率,则会选择小于目标分辨率的最接近的可用分辨率。具有与提供的Size相同纵横比的分辨率优先于不同纵横比的分辨率。

CameraX 根据请求应用最合适的分辨率。如果主要需要满足纵横比,则仅指定setTargetAspectRatio,CameraX 会根据设备确定合适的确切分辨率。如果应用程序的主要需要是指定分辨率以提高图像处理效率(例如,根据设备处理能力使用小型或中型图像),则使用setTargetResolution(Size resolution)

如果您的应用程序需要确切的分辨率,请参阅createCaptureSession()中的表格以确定每个硬件级别支持哪些最大分辨率。要检查当前设备支持的特定分辨率,请参阅StreamConfigurationMap.getOutputSizes(int)

如果您的应用程序在 Android 10 或更高版本上运行,则可以使用isSessionConfigurationSupported()来验证特定的SessionConfiguration

控制摄像头输出

除了允许您根据需要为每个单独的用例配置摄像头输出外,CameraX 还实现了以下接口以支持所有绑定用例共有的摄像头操作

  • CameraControl允许您配置常见的摄像头功能。
  • CameraInfo允许您查询这些常见摄像头功能的状态。

以下是 CameraControl 支持的摄像头功能

  • 缩放
  • 闪光灯
  • 对焦和测光(点触对焦)
  • 曝光补偿

获取 CameraControl 和 CameraInfo 的实例

使用ProcessCameraProvider.bindToLifecycle()返回的Camera对象检索CameraControlCameraInfo的实例。以下代码显示了一个示例

Kotlin

val camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
val cameraControl = camera.cameraControl
// For querying information and states.
val cameraInfo = camera.cameraInfo

Java

Camera camera = processCameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// For performing operations that affect all outputs.
CameraControl cameraControl = camera.getCameraControl()
// For querying information and states.
CameraInfo cameraInfo = camera.getCameraInfo()

例如,您可以在调用bindToLifecycle()后提交缩放和其他CameraControl操作。在停止或销毁用于绑定摄像头实例的活动后,CameraControl将无法再执行操作,并返回失败的ListenableFuture

缩放

CameraControl 提供了两种更改缩放级别的方法

  • setZoomRatio()通过缩放比例设置缩放。

    该比例必须在CameraInfo.getZoomState().getValue().getMinZoomRatio()CameraInfo.getZoomState().getValue().getMaxZoomRatio()的范围内。否则,该函数将返回失败的ListenableFuture

  • setLinearZoom()使用 0 到 1.0 的线性缩放值设置当前缩放。

    线性缩放的优点是它使视野 (FOV) 随着缩放的变化而缩放。这使其非常适合与Slider视图一起使用。

CameraInfo.getZoomState()返回当前缩放状态的 LiveData。当摄像头初始化或使用setZoomRatio()setLinearZoom()设置缩放级别时,该值会发生变化。调用任一方法都会设置支持ZoomState.getZoomRatio()ZoomState.getLinearZoom()的值。如果您想在滑块旁边显示缩放比例文本,这将非常有用。只需观察ZoomState LiveData即可进行更新,而无需进行转换。

两个 API 返回的ListenableFuture都提供了让应用程序在完成具有指定缩放值的重复请求时收到通知的选项。此外,如果在先前操作仍在执行时设置新的缩放值,则先前缩放操作的ListenableFuture将立即失败。

闪光灯

CameraControl.enableTorch(boolean)启用或禁用闪光灯(也称为手电筒)。

CameraInfo.getTorchState()可用于查询当前闪光灯状态。您可以检查CameraInfo.hasFlashUnit()返回的值以确定闪光灯是否可用。如果没有,则调用CameraControl.enableTorch(boolean)会导致返回的ListenableFuture立即完成并返回失败的结果,并将闪光灯状态设置为TorchState.OFF

启用闪光灯后,无论闪光模式设置如何,它都将在拍摄照片和视频时保持开启状态。ImageCapture中的flashMode仅在禁用闪光灯时有效。

对焦和测光

CameraControl.startFocusAndMetering() 通过根据给定的 FocusMeteringAction 设置 AF/AE/AWB 测光区域来触发自动对焦和曝光测光。这通常用于在许多相机应用程序中实现“点触对焦”功能。

测光点

首先,使用 MeteringPointFactory.createPoint(float x, float y, float size) 创建一个 MeteringPoint。一个 MeteringPoint 代表相机 Surface 上的一个点。它以标准化形式存储,以便可以轻松地转换为传感器坐标以指定 AF/AE/AWB 区域。

MeteringPoint 的大小范围为 0 到 1,默认大小为 0.15f。当调用 MeteringPointFactory.createPoint(float x, float y, float size) 时,CameraX 会创建一个以 (x, y) 为中心,大小为 size 的矩形区域。

以下代码演示了如何创建一个 MeteringPoint

Kotlin

// Use PreviewView.getMeteringPointFactory if PreviewView is used for preview.
previewView.setOnTouchListener((view, motionEvent) ->  {
val meteringPoint = previewView.meteringPointFactory
    .createPoint(motionEvent.x, motionEvent.y)
…
}

// Use DisplayOrientedMeteringPointFactory if SurfaceView / TextureView is used for
// preview. Please note that if the preview is scaled or cropped in the View,
// it’s the application's responsibility to transform the coordinates properly
// so that the width and height of this factory represents the full Preview FOV.
// And the (x,y) passed to create MeteringPoint might need to be adjusted with
// the offsets.
val meteringPointFactory = DisplayOrientedMeteringPointFactory(
     surfaceView.display,
     camera.cameraInfo,
     surfaceView.width,
     surfaceView.height
)

// Use SurfaceOrientedMeteringPointFactory if the point is specified in
// ImageAnalysis ImageProxy.
val meteringPointFactory = SurfaceOrientedMeteringPointFactory(
     imageWidth,
     imageHeight,
     imageAnalysis)

startFocusAndMetering 和 FocusMeteringAction

要调用 startFocusAndMetering(),应用程序必须构建一个 FocusMeteringAction,它包含一个或多个 MeteringPoints,以及来自 FLAG_AFFLAG_AEFLAG_AWB 的可选测光模式组合。以下代码演示了此用法

Kotlin

val meteringPoint1 = meteringPointFactory.createPoint(x1, x1)
val meteringPoint2 = meteringPointFactory.createPoint(x2, y2)
val action = FocusMeteringAction.Builder(meteringPoint1) // default AF|AE|AWB
      // Optionally add meteringPoint2 for AF/AE.
      .addPoint(meteringPoint2, FLAG_AF | FLAG_AE)
      // The action is canceled in 3 seconds (if not set, default is 5s).
      .setAutoCancelDuration(3, TimeUnit.SECONDS)
      .build()

val result = cameraControl.startFocusAndMetering(action)
// Adds listener to the ListenableFuture if you need to know the focusMetering result.
result.addListener({
   // result.get().isFocusSuccessful returns if the auto focus is successful or not.
}, ContextCompat.getMainExecutor(this)

如前面的代码所示,startFocusAndMetering() 获取一个 FocusMeteringAction,该动作包含一个用于 AF/AE/AWB 测光区域的 MeteringPoint 和另一个仅用于 AF 和 AE 的 MeteringPoint。

在内部,CameraX 将其转换为 Camera2 MeteringRectangles,并将相应的 CONTROL_AF_REGIONS / CONTROL_AE_REGIONS / CONTROL_AWB_REGIONS 参数设置为捕获请求。

由于并非所有设备都支持 AF/AE/AWB 和多个区域,因此 CameraX 会尽力执行 FocusMeteringAction。CameraX 使用支持的最大 MeteringPoints 数量,并按照添加点的顺序使用。超过最大数量后添加的所有 MeteringPoints 都会被忽略。例如,如果在仅支持 2 个点的平台上为 FocusMeteringAction 提供了 3 个 MeteringPoints,则仅使用前 2 个 MeteringPoints。CameraX 会忽略最后一个 MeteringPoint

曝光补偿

当应用程序需要微调曝光值 (EV) 超出自动曝光 (AE) 输出结果时,曝光补偿非常有用。曝光补偿值以以下方式组合以确定当前图像条件所需的曝光

曝光 = 曝光补偿指数 * 曝光补偿步长

CameraX 提供了 Camera.CameraControl.setExposureCompensationIndex() 函数来设置曝光补偿作为索引值。

正索引值使图像更亮,而负索引值使图像变暗。应用程序可以通过下一节中描述的 CameraInfo.ExposureState.exposureCompensationRange() 查询支持的范围。如果该值受支持,则当该值在捕获请求中成功启用时,返回的 ListenableFuture 将完成;如果指定的索引超出支持范围,则 setExposureCompensationIndex() 会导致返回的 ListenableFuture 立即完成并失败。

CameraX 仅保留最新的未完成的 setExposureCompensationIndex() 请求,并且在先前请求执行之前多次调用该函数会导致其取消。

以下代码段设置曝光补偿索引,并在曝光更改请求执行后注册回调

Kotlin

camera.cameraControl.setExposureCompensationIndex(exposureCompensationIndex)
   .addListener({
      // Get the current exposure compensation index, it might be
      // different from the asked value in case this request was
      // canceled by a newer setting request.
      val currentExposureIndex = camera.cameraInfo.exposureState.exposureCompensationIndex
      …
   }, mainExecutor)

例如,以下代码使用当前的 ExposureState 值初始化曝光 SeekBar 的设置

Kotlin

val exposureState = camera.cameraInfo.exposureState
binding.seekBar.apply {
   isEnabled = exposureState.isExposureCompensationSupported
   max = exposureState.exposureCompensationRange.upper
   min = exposureState.exposureCompensationRange.lower
   progress = exposureState.exposureCompensationIndex
}

其他资源

要了解有关 CameraX 的更多信息,请查阅以下其他资源。

Codelab

  • CameraX 入门
  • 代码示例

  • CameraX 示例应用程序
  • 开发者社区

    Android CameraX 讨论组