CameraX 1.1 有哪些新的特性发布?

CameraX 是一个 Jetpack 支持库,旨在帮助您简化相机应用的开发工作。它提供一致且易用的 API 接口,适用于大多数 Android 设备,并可向后兼容至 Android 5.0 (API 级别 21)。我们将在本文中介绍 CameraX 1.1 的多项功能,比如视频功能。

如果您更喜欢通过视频了解此内容,请在此处查看:

△ CameraX 1.1 有哪些新的特性发布?

CameraX 概览

CameraX 是一个为了简化编写相机应用而设计的支持库,它所提供的高级 API 可以让开发者专注于和用户交互而非相机的内部实现。我们一直在探索并修复其背后复杂的兼容性问题,让每个新版本都得以在更多的设备上稳定运行。

何时使用 CameraX 或 Camera2,这取决于您期望更快的开发速度或是想要更高的自定义程度。

  • CameraX 可以很方便地实现普通照片视频的拍摄功能,而 Camera2 则可以对拍摄流程进行特殊控制,例如实现多重曝光或全手动捕获;
  • CameraX 旨在消除不同设备间的差异并在不同设备上进行了测试,而 Camera2 则需要应用来管理不同设备间的差异并测试其行为;
  • CameraX 提升了代码开发速度,让您更专注于用户界面和体验流程,而 Camera2 则用于更深入地开发以创造基于相机的定制功能;
  • CameraX 发布新版本频繁,而 Camera2 则随着 Android 的版本而更新;
  • CameraX 可以在您不熟悉相机的情况下也能够进行开发,而 Camera2 则需要您对相机的专业知识有更深层次的了解。

CameraX 基于主要的使用场景来构建,比如实时预览相机、检索缓冲区以进行分析和拍摄照片,在 CameraX 1.1 版本中还加入了视频拍摄功能。我们来看一个简单的 CameraX 示例:

fun bindPreview(cameraProvider : ProcessCameraProvider) {
    // 使用 CameraX 创建 Preview 用例
    var preview : Preview = Preview.Builder().build()

    // 创建 cameraSelector,它会在设备上搜索所需的相机
    var cameraSelector : CameraSelector = CameraSelector.Builder()
        // 在本例中,我们选择搜索后置相机
        .requireLensFacing(CameraSelector.LENS_FACING_BACK).build()

    // 从 CameraX 的 CameraView 包中获取 previewView 的句柄
    // 利用此方法可以轻松的将相机内容添加到视图上
    preview.setSurfaceProvider(previewView.getSurfaceProvider())

    // 将 preview 与其生命周期绑定
    var camera = cameraProvider.bindToLifecycle(this as LifecycleOwner,
                                cameraSelector, preview)
}

△ CameraX 代码示例

CameraX 是生命周期感知型组件,这意味着它将自动处理应用的生命周期事件来实现开始、停止、暂停和恢复。现在,应用启动时屏幕上便会显示实时预览。

我们已于 2021 年 5 月发布了 1.0 稳定版本,目前正在开发 1.1 Alpha 版本并且很快将会进入 Beta 阶段。并且我们一如既往地不断为新增设备推出兼容性修复程序,例如 1.0.1 和 1.0.2。

在 CameraX 1.1 版本中我们新增了开发者呼声很高的功能,具体而言,在本文中我们将重点介绍:

  • 视频拍摄
  • YUV 到 RGB 的转换
  • Beta 版 Extensions API
  • 一些需要了解的其它功能

视频拍摄

在 CameraX 1.1 版本中我们加入了视频拍摄功能,视频拍摄 API (尚处于 Alpha 阶段,细节可能会发生变化,但整体结构基本会保持不变) 提供了录制到文件等基本功能、可自动适配每台设备的 Quality Setting API,以及 Lifecycle Management API。接下来我们先来了解如何设定视频拍摄功能,代码示例如下:

// 创建 Recorder
val recorder = Recorder.Builder()
                       // 我们可以在此处使用 setQualitySelector 设置视频质量
                       .setQualitySelector(...)
                       .build()

// 使用新创建的 Recorder 创建 VideoCapture
val videoCapture = VideoCapture.withOutput(recorder)

// 将其与生命周期绑定
cameraProvider.bindToLifecycle(
    this, CameraSelector.DEFAULT_BACK_CAMERA, preview, videoCapture)

// 设定 VideoRecordEvent 监听器
val videoRecordEventListener = Consumer<VideoRecordEvent>{
    when (it) {
        is VideoRecordEvent.Start -> {}
        is VideoRecordEvent.Finalize -> {}
        is VideoRecordEvent.Status -> {
            // status 事件将会在录制时持续更新
            val stats: RecordingStats = it.recordingStats
            // RecordingStats 中包含录制文件的尺寸和时长
        }
        is VideoRecordEvent.Pause -> {}
        is VideoRecordEvent.Resume -> {}

// 指定输出
val mediaStoreOutput = MediaStoreOutputOptions.Builder(this.contentResolver,
        MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
    .setContentValues(contentValues)
    .build()

// 准备录制
val activeRecording = videoCapture.output.prepareRecording(this, mediaStoreOutput)
    // 关联事件监听器
    .withEventListener(ContextCompat.getMainExecutor(this), videoRecordEventListener)
    // 启用音频 (前提是此应用已获得音频权限)
    .withAudioEnabled()
    // 开始录制
    .start()

△ 视频拍摄示例

videoCapture 会在应用启动时就绪,应用可以使用 videoRecordEventListener 响应开始、结束、暂停和恢复等拍摄事件,其中 Status 事件会提供包括文件大小和持续时间的 RecordingStats。视频拍摄可以输出到 File、FileDescriptorMediaStore,在本例中我们选择 MediaStore。如果选择启用音频,则需要此应用已经获得音频权限。调用 start() 开始录制为我们提供了 activeRecording 句柄,它可以用来暂停、恢复或停止录制。您可以在 1.1 版本中试用这些 API。

YUV 至 RGB 的转换

另一个呼声很高的功能是 YUV 到 RGB 的转换,我们来了解一下此功能。

△ YUV 格式 (图左) 转换至 RGB 格式 (图右)

相机通常以 YUV420 格式生成数据,其中包括明亮度 (Luminance, Y)、色度 (Chroma, U, V) 和一些填充字节以将各行与有效的内存步幅对齐。但是这种格式的图像处理起来可能很麻烦,而现在 CameraX 可以将 ImageAnalysis 的输出转换为大家更熟悉的 RGBA 以方便处理。接下来我们看一个示例:

val imageAnalysis = ImageAnalysis.Builder()
        .setTargetResolution(Size(1280, 720))
        .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
        .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
        .build()

△ 从 ImageAnalysis 获取 RGB 输出

在示例代码中,我们创建了 ImageAnalysis 实例,为图像缓冲区指定了所需的分辨率和背压策略,并调用新的 setOutputImageFormat 方法以请求 RGBA 8888 格式的输出。现在,ImageAnalysis输出的帧为 RGBA 8888 数据而不再是 YUV 格式。

CameraX 中 YUV 到 RGB 的转换基于 libyuv。此外,在 CameraX 1.1 版本中数据本身可以转换到目标分辨率。在中端设备上对图像大小为 640x480 至 1080p 的数据进行转换大约需要 5~10 毫秒,具体性能因设备而异。此外 APK 会略微增加 50KB 左右。

修复单像素漂移

YUV 转换还修复了部分设备上存在的单像素漂移问题。在这些设备上,YUV 输出经过桶形移位一个像素,导致最右边的一列数据出现在图像的左边缘。在已知会发生这种情况的设备上,进行 YUV 到 RGB 的转换及输出 YUV 或 RGB 都会被修复,并且 CameraX 将会持续对更多有需要的设备进行修复。

△ 修复单像素漂移

△ 修复单像素漂移

如需了解更多,请参阅我们之前的推文《为 CameraX ImageAnalysis 进行 YUV 到 RGB 的转换》。

CameraX Extensions API

相机特效

在 CameraX 1.1 中的 CameraX Extensions API 可以更为充分地发挥设备强大的功能。

CameraX Extensions 包括一些最常见的内置相机特效:

  • BOKEH (焦外虚化) : 在人像模式下拍摄照片时,让前景人物更清晰。
  • HDR (高动态范围) : 拍照时使用不同的自动曝光 (AE) 配置,以获得最佳效果。
  • NIGHT (夜间) : 在低照度环境下 (通常是在夜间) 捕获最佳静态图像。
  • FACE RETOUCH (脸部照片修复) : 拍摄静态图像时,修饰脸部肤色、轮廓等。
  • AUTO (自动) : 根据周围的景色自动调整最终图像。

我们来看看如何使用 CameraX Extensions API:

// 获取后置相机列表
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

// 检查所有的后置相机中是否有支持焦外虚化
if (extensionsManager.isExtensionAvailable(
    cameraProvider,
    cameraSelector,
    ExtensionMode.BOKEH
)) {
    // 创建扩展 cameraSelector,我们提供了相机并指定焦外虚化模式
    // 它将开始在后台搜索支持焦外虚化的后置相机
    val bokehCameraSelector = extensionsManager.getExtensionCameraSelector(
        cameraProvider,
        cameraSelector,
        ExtensionMode.BOKEH
    )

    // 创建 imageCapture 和 preview
    val imageCapture = ImageCapture.Builder().builder()
    val preview = Preview.Builder().build()

    // 使用 bokehCameraSelector 将它们绑定到生命周期
    cameraProvider.bindToLifecycle(lifecycleOwner,
        bokehCameraSelector,
        imageCapture,
        preview
    )
}

△ 以 BOKEH 效果捕捉并预览图像

在上面的例子中,imageCapture 输出的图像将会具有焦外虚化效果,如果设备支持的话,preview 也将预览焦外虚化效果。

如需详细了解,请参阅我们之前的推文《使用 CameraX Extensions API 将特效应用到照片上》。

曝光补偿

CarmeraX 1.1 中还添加了曝光补偿 API,此功能可以帮助用户更好地捕捉过度曝光或者曝光不足的区域。

如图所示我们所处的场景窗外很明亮而室内很昏暗,此时则可以调整曝光补偿来更好地捕捉明亮的室外或昏暗的室内场景。我们来看一个例子:

// 创建变量来跟踪 exposureIndex 值
var exposureIndex = 0
// 使用 cameraSelector 将 imageCapture 和 preview 绑定到生命周期
val camera = cameraProvider.bindToLifecycle(
    lifecycleOwner,
    getCameraSelector(),
    preview,
    imageCapture
)
 
// 为视图中的按钮添加点击事件
evButton.setOnclickListener {
 
    // 检查有效的范围以防止可能的异常
    val range = camera.cameraInfo.exposureState.exposureCompensationRange
 
    if (range.contains(exposureIndex + 1)) {
        // 调用 camera.cameraControl 的 setExposureCompenstation() 方法来设置曝光补偿
        camera.cameraControl.setExposureCompenstation(++exposureIndex)
        // 使用 exposureCompensationStep 来实现从 index 到 EV 到转换
        val ev = camera.cameraInfo.exposureState.exposureCompensationStep.toFloat() * exposureIndex
        Log.i("CameraXLog", "EV: $ev")
    }
}

△ 通过按钮调整曝光

其中 exposureIndex 是一个与设备无关的数字,它将以硬件允许的最小步长递增或递减曝光值,因此可以在不同的设备上以类似的方式运作。如果您想向用户展示 EV 值,可以获取 exposureCompensationStep 来实现转换。

如需了解 CameraX 中曝光补偿 API 的应用背景和调用方法,请参阅我们之前的推文《CameraX 曝光补偿 API 入门指南》。

平滑缩放

在 CameraX 1.1 中,我们还增加了平滑缩放功能。有一些设备有包括广角和长焦在内的多个镜头,CameraX 可以检测这些设备是否支持 SMOOTH_ZOOM 框架,在受支持的设备上使用 CameraX 的缩放控件时,会自动使用所有的相机来实现更大的缩放范围。如果您已经在使用这个缩放控件,那当您使用 1.1 版本进行编译时,您的应用应该就可以访问这些设备上的所有相机。

CameraX 1.1 的更多功能

接下来介绍我们在 1.1 中添加的更多功能。

CameraState API 现在可以提供诸如另一个应用正在使用相机或者正处于勿扰模式等更多有关相机状态的信息,使得应用能够围绕不同的相机时间来设计更好的界面和用户体验流程。Image Analysis 现在可以提供超过 1080p 的图像。Logging API 可以更详细的调试日志并改善了错误报告。Coordinate Transformation API 可以将不同用例间的坐标关联起来,如果您在 imageAnalysis缓冲区中定位了兴趣点,便可以方便地在图像捕捉的输出或预览中轻松找到它。您可以使用 CameraFilter API 来指定详细的规则以选择合适的相机。如果应用只需要前置或者后置相机,可以使用 AvailableCamerasLimiter 来加快启动时间。CameraControllerInfo 中可提供相机功能的更多详细信息。

设备兼容性

CameraX 会持续关注设备兼容性,以便应用在众多设备上都能够良好运行。我们修复了很多诸如图像拉伸、缩放不正确、图像颠倒及关闭相机时意外输出了绿色图形等问题。每个 CameraX 的发布版本或补丁版本中都会添加此类修复,最新的稳定版为 1.0.2。

您可以在 版本记录 中看到每个版本中的详细变更,还可以在 问题跟踪器 中看已经修复的问题。

更多信息

希望对 CameraX 1.1 版本的简要介绍对大家有所帮助,非常期待看到大家使用 CameraX 构建的功能!

欢迎您 点击这里 向我们提交反馈,或分享您喜欢的内容、发现的问题。您的反馈对我们非常重要,感谢您的支持!