配置选项

您可以配置每个 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,从而避免主线程发生阻塞,使界面保持流畅。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 日志文档。您可以使用 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:提供元数据输出,以便使用 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。

对于具有可配置宽高比的用例,可让应用根据界面设计来指定所需的宽高比。CameraX 会按照请求的宽高比生成输出,并尽可能匹配设备支持的宽高比。如果没有任何支持的完全匹配分辨率,则选择满足最多条件的分辨率。也就是说,应用会决定摄像头在应用中的显示方式,CameraX 则会决定最佳摄像头分辨率设置,以满足不同设备的具体要求。

例如,应用可以执行以下任一操作:

  • 为用例指定 4:3 或 16:9 的目标分辨率
  • 指定自定义分辨率,CameraX 会尝试查找与该分辨率最接近的分辨率
  • ImageCapture 指定剪裁宽高比

CameraX 会自动选择内部 Camera2 界面的分辨率。下表显示了这些分辨率:

用例 内部界面分辨率 输出数据分辨率
预览 宽高比:使目标与设置最相符的分辨率。 内部界面分辨率。通过提供元数据,可让视图针对目标宽高比进行剪裁、缩放和旋转。
默认分辨率:最高的预览分辨率,或与预览宽高比匹配的最高设备首选分辨率。
最大分辨率:预览大小,指的是与设备的屏幕分辨率或 1080p (1920x1080)(以较低者为准)匹配的最佳尺寸。
图片分析 宽高比:使目标与设置最相符的分辨率。 内部界面分辨率。
默认分辨率:默认目标分辨率设置为 640x480。同时调整目标分辨率和相应的宽高比可获得支持的最佳分辨率。
最大分辨率:从 StreamConfigurationMap.getOutputSizes() 中检索到的 YUV_420_888 格式的摄像头设备最大输出分辨率。 目标分辨率默认设置为 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 操作。如果您停止或销毁用于绑定摄像头实例的 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 会使用所支持的最大数量的 MeteringPoint,并按测光点的添加顺序依次使用。对于在超出支持的最大数量之外添加的所有 MeteringPoint,CameraX 会一律忽略。例如,如果您在仅支持 2 个 MeteringPoint 的平台上为 FocusMeteringAction 提供 3 个 MeteringPoint,那么 CameraX 只会使用前 2 个 MeteringPoint,并忽略最后一个 MeteringPoint

曝光补偿

当应用需要对自动曝光 (AE) 输出结果以外的曝光值 (EV) 进行微调时,曝光补偿很有用。CameraX 将按以下方式组合曝光补偿值,以确定当前图片条件下所需的曝光:

Exposure = ExposureCompensationIndex * ExposureCompensationStep

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 论坛