配置选项

您可以配置每个 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 设置为忽略其他摄像头,这可以减少您的应用所用摄像头的启动延迟。

如果传递给 CameraSelectorCameraXConfig.Builder.setAvailableCamerasLimiter() 滤除了某个摄像头,则 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() 设置所使用的后台线程。

摄像头执行器

摄像头执行器用于所有内部摄像头平台 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() 了解目标分辨率的旋转。
  • ImageAnalysis:提供了元数据输出,以便图像缓冲区坐标相对于显示坐标可知。
  • ImageCapture:图像 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 会根据视口的属性和附加的用例计算出最大的可能裁剪区域。通常,为实现所见即所得 (WYSIWYG) 的效果,您可以根据预览用例配置视口。获取视口的一种简单方法是使用 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 Surface 分辨率。下表显示了这些分辨率:

用例 内部 Surface 分辨率 输出数据分辨率
预览 宽高比: 最能适应目标设置的分辨率。 内部 Surface 分辨率。提供元数据以便 View 可以针对目标宽高比进行裁剪、缩放和旋转。
默认分辨率: 最高预览分辨率,或与预览宽高比匹配的最高设备首选分辨率。
最大分辨率: 预览尺寸,指与设备屏幕分辨率最匹配的最佳尺寸,或 1080p (1920x1080),取两者中较小者。
图像分析 宽高比: 最能适应目标设置的分辨率。 内部 Surface 分辨率。
默认分辨率: 默认目标分辨率设置为 640x480。调整目标分辨率和相应的宽高比会得到最受支持的分辨率。
最大分辨率: 摄像头设备的 YUV_420_888 格式的最大输出分辨率,从 StreamConfigurationMap.getOutputSizes() 中检索。目标分辨率默认为 640x480,因此如果您想要大于 640x480 的分辨率,则必须使用 setTargetResolution()setTargetAspectRatio() 从支持的分辨率中获取最接近的一个。
图像捕获 宽高比: 最能适应设置的宽高比。 内部 Surface 分辨率。
默认分辨率: 最高可用分辨率,或与 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 操作。停止或销毁用于绑定摄像头实例的 activity 后,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 测光区域来触发自动对焦和曝光测光。这通常用于实现许多相机应用中的“点按对焦”功能。

测光点 (MeteringPoint)

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

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

以下代码演示了如何创建 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 个 MeteringPoints 的平台提供了包含 3 个 MeteringPoints 的 FocusMeteringAction,则只使用前 2 个 MeteringPoints。最后一个 MeteringPoint 会被 CameraX 忽略。

曝光补偿

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

曝光 = 曝光补偿索引 * 曝光补偿步长

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 讨论组