CameraX 视频捕获架构

捕获系统通常会录制视频和音频流,对其进行压缩,将两个流进行混合,然后将生成的流写入磁盘。

conceptual diagram for a video and audio capturing system
图 1. 视频和音频捕获系统的概念图。

在 CameraX 中,视频捕获的解决方案是 VideoCapture 用例

conceptual diagram that shows how camera x handles the
         video capture use case
图 2. 显示 CameraX 如何处理 VideoCapture 用例的概念图。

如图 2 所示,CameraX 视频捕获包含一些高级架构组件

  • SurfaceProvider 用于视频源。
  • AudioSource 用于音频源。
  • 两个编码器用于对视频/音频进行编码和压缩。
  • 一个媒体混合器用于混合两个流。
  • 一个文件保存器用于写入结果。

VideoCapture API 抽象了复杂的捕获引擎,并为应用程序提供了一个更简单、更直接的 API。

VideoCapture API 概述

VideoCapture 是一个 CameraX 用例,可以独立使用,也可以与其他用例结合使用。具体支持的组合取决于相机硬件功能,但 PreviewVideoCapture 是所有设备上有效的用例组合。

VideoCapture API 包含以下与应用程序通信的对象

  • VideoCapture 是顶级用例类。 VideoCapture 使用 CameraSelector 和其他 CameraX 用例绑定到 LifecycleOwner。有关这些概念和用法的更多信息,请参阅 CameraX 架构
  • 一个 Recorder 是 VideoOutput 的实现,与 VideoCapture 密切相关。 Recorder 用于执行视频和音频捕获。应用程序创建来自 Recorder 的录制。
  • 一个 PendingRecording 配置录制,提供启用音频和设置事件监听器等选项。您必须使用 Recorder 创建 PendingRecordingPendingRecording 不会录制任何内容。
  • 一个 Recording 执行实际录制。您必须使用 PendingRecording 创建 Recording

图 3 显示了这些对象之间的关系

diagram showing the interactions that occur in a video
         capture use case
图 3. 显示在 VideoCapture 用例中发生的交互的图。

图例

  1. 使用 QualitySelector 创建 Recorder
  2. 使用 OutputOptions 之一配置 Recorder
  3. 如果需要,使用 withAudioEnabled() 启用音频。
  4. 使用 VideoRecordEvent 监听器调用 start() 以开始录制。
  5. 使用 pause()/resume()/stop()Recording 上控制录制。
  6. 在您的事件监听器中响应 VideoRecordEvents

详细的 API 列表位于 源代码中的 current.txt 文件中

使用 VideoCapture API

要将 CameraX VideoCapture 用例集成到您的应用中,请执行以下操作

  1. 绑定 VideoCapture
  2. 准备和配置录制。
  3. 启动和控制运行时录制。

以下部分概述了您可以在每个步骤中执行的操作以获得端到端录制会话。

绑定 VideoCapture

要绑定 VideoCapure 用例,请执行以下操作

  1. 创建一个 Recorder 对象。
  2. 创建一个 VideoCapture 对象。
  3. 绑定到 Lifecycle

CameraX VideoCapture API 遵循构建器设计模式。应用程序使用 Recorder.Builder 创建 Recorder。您还可以通过 QualitySelector 对象配置 Recorder 的视频分辨率。

CameraX Recorder 支持以下预定义的 Qualities 用于视频分辨率

  • Quality.UHD 用于 4K 超高清视频尺寸 (2160p)
  • Quality.FHD 用于全高清视频尺寸 (1080p)
  • Quality.HD 用于高清视频尺寸 (720p)
  • Quality.SD 用于标清视频尺寸 (480p)

请注意,CameraX 也可以在应用程序授权时选择其他分辨率。

每个选择的精确视频尺寸取决于相机和编码器的功能。有关更多信息,请参阅 CamcorderProfile 的文档。

应用程序可以通过创建一个 QualitySelector 来配置分辨率。您可以使用以下方法之一创建 QualitySelector

  • 使用 fromOrderedList() 提供一些首选分辨率,并包含一个回退策略以防不支持任何首选分辨率。

    CameraX 可以根据所选相机的功能确定最佳回退匹配,有关更多详细信息,请参阅 QualitySelectorFallbackStrategy 规范。例如,以下代码请求录制时支持的最高分辨率,如果请求的分辨率都不支持,则授权 CameraX 选择最接近 Quality.SD 分辨率的分辨率

    val qualitySelector = QualitySelector.fromOrderedList(
             listOf(Quality.UHD, Quality.FHD, Quality.HD, Quality.SD),
             FallbackStrategy.lowerQualityOrHigherThan(Quality.SD))
    
  • 首先查询相机功能,然后使用 QualitySelector::from() 从支持的分辨率中进行选择

    val cameraInfo = cameraProvider.availableCameraInfos.filter {
        Camera2CameraInfo
        .from(it)
        .getCameraCharacteristic(CameraCharacteristics.LENS\_FACING) == CameraMetadata.LENS_FACING_BACK
    }
    
    val supportedQualities = QualitySelector.getSupportedQualities(cameraInfo[0])
    val filteredQualities = arrayListOf (Quality.UHD, Quality.FHD, Quality.HD, Quality.SD)
                           .filter { supportedQualities.contains(it) }
    
    // Use a simple ListView with the id of simple_quality_list_view
    viewBinding.simpleQualityListView.apply {
        adapter = ArrayAdapter(context,
                               android.R.layout.simple_list_item_1,
                               filteredQualities.map { it.qualityToString() })
    
        // Set up the user interaction to manually show or hide the system UI.
        setOnItemClickListener { _, _, position, _ ->
            // Inside View.OnClickListener,
            // convert Quality.* constant to QualitySelector
            val qualitySelector = QualitySelector.from(filteredQualities[position])
    
            // Create a new Recorder/VideoCapture for the new quality
            // and bind to lifecycle
            val recorder = Recorder.Builder()
                .setQualitySelector(qualitySelector).build()
    
             // ...
        }
    }
    
    // A helper function to translate Quality to a string
    fun Quality.qualityToString() : String {
        return when (this) {
            Quality.UHD -> "UHD"
            Quality.FHD -> "FHD"
            Quality.HD -> "HD"
            Quality.SD -> "SD"
            else -> throw IllegalArgumentException()
        }
    }
    
    

    请注意,从 QualitySelector.getSupportedQualities() 返回的功能保证适用于 VideoCapture 用例或 VideoCapturePreview 用例的组合。当与 ImageCaptureImageAnalysis 用例一起绑定时,当请求的相机上不支持所需的组合时,CameraX 仍然可能会导致绑定失败。

获得 QualitySelector 后,应用程序可以创建 VideoCapture 对象并执行绑定。请注意,此绑定与其他用例相同

val recorder = Recorder.Builder()
    .setExecutor(cameraExecutor).setQualitySelector(qualitySelector)
    .build()
val videoCapture = VideoCapture.withOutput(recorder)

try {
    // Bind use cases to camera
    cameraProvider.bindToLifecycle(
            this, CameraSelector.DEFAULT_BACK_CAMERA, preview, videoCapture)
} catch(exc: Exception) {
    Log.e(TAG, "Use case binding failed", exc)
}

请注意,bindToLifecycle() 返回一个 Camera 对象。有关控制相机输出(如缩放和曝光)的更多信息,请参阅 本指南

Recorder 选择最适合系统的格式。最常见的视频编解码器是 H.264 AVC),容器格式为 MPEG-4

配置和创建录制

Recorder 中,应用程序可以创建录制对象以执行视频和音频捕获。应用程序通过执行以下操作创建录制

  1. 使用 prepareRecording() 配置 OutputOptions
  2. (可选) 启用音频录制。
  3. 使用 start() 注册 VideoRecordEvent 监听器,并开始视频捕获。

当您调用 start() 函数时,Recorder 会返回一个 Recording 对象。您的应用程序可以使用此 Recording 对象来完成捕获或执行其他操作,例如暂停或恢复。

一个 Recorder 每次只支持一个 Recording 对象。在您对上一个 Recording 对象调用 Recording.stop()Recording.close() 后,您可以开始新的录制。

让我们更详细地了解这些步骤。首先,应用程序使用 Recorder.prepareRecording() 为 Recorder 配置 OutputOptionsRecorder 支持以下类型的 OutputOptions

  • FileDescriptorOutputOptions 用于捕获到 FileDescriptor 中。
  • FileOutputOptions 用于捕获到 File 中。
  • MediaStoreOutputOptions 用于捕获到 MediaStore 中。

所有 OutputOptions 类型都允许您使用 setFileSizeLimit() 设置最大文件大小。其他选项特定于各个输出类型,例如 FileDescriptorOutputOptionsParcelFileDescriptor

prepareRecording() 返回一个 PendingRecording 对象,这是一个中间对象,用于创建相应的 Recording 对象。 PendingRecording 是一个瞬态类,在大多数情况下应该不可见,并且很少被应用程序缓存。

应用程序可以进一步配置录制,例如

  • 使用 withAudioEnabled() 启用音频。
  • 使用 start(Executor, Consumer<VideoRecordEvent>) 注册一个监听器以接收视频录制事件。
  • 允许录制在附加到的 VideoCapture 重新绑定到另一个摄像头时持续录制,使用 PendingRecording.asPersistentRecording()

要开始录制,请调用 PendingRecording.start()。CameraX 将 PendingRecording 转换为 Recording,将录制请求排队,并将新创建的 Recording 对象返回给应用程序。一旦在相应的摄像头设备上开始录制,CameraX 将发送 VideoRecordEvent.EVENT_TYPE_START 事件。

以下示例演示了如何将视频和音频录制到 MediaStore 文件中

// Create MediaStoreOutputOptions for our recorder
val name = "CameraX-recording-" +
        SimpleDateFormat(FILENAME_FORMAT, Locale.US)
                .format(System.currentTimeMillis()) + ".mp4"
val contentValues = ContentValues().apply {
   put(MediaStore.Video.Media.DISPLAY_NAME, name)
}
val mediaStoreOutput = MediaStoreOutputOptions.Builder(this.contentResolver,
                              MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
                              .setContentValues(contentValues)
                              .build()

// 2. Configure Recorder and Start recording to the mediaStoreOutput.
val recording = videoCapture.output
                .prepareRecording(context, mediaStoreOutput)
                .withAudioEnabled()
                .start(ContextCompat.getMainExecutor(this), captureListener)

虽然默认情况下前置摄像头的摄像头预览是镜像的,但VideoCapture录制的视频默认情况下不会镜像。使用 CameraX 1.3,现在可以镜像视频录制,以便前置摄像头的预览和录制的视频匹配。

有三个 MirrorMode 选项:MIRROR_MODE_OFF、MIRROR_MODE_ON 和 MIRROR_MODE_ON_FRONT_ONLY。为了与摄像头预览对齐,Google 建议使用 MIROR_MODE_ON_FRONT_ONLY,这意味着后置摄像头未启用镜像,而前置摄像头已启用镜像。有关 MirrorMode 的更多信息,请参阅 MirrorMode 常量

此代码片段演示了如何使用 MIRROR_MODE_ON_FRONT_ONLY 调用 VideoCapture.Builder.setMirrorMode()。有关更多信息,请参阅 setMirrorMode()

Kotlin

val recorder = Recorder.Builder().build()

val videoCapture = VideoCapture.Builder(recorder)
    .setMirrorMode(MIRROR_MODE_ON_FRONT_ONLY)
    .build()

useCases.add(videoCapture);

Java

Recorder.Builder builder = new Recorder.Builder();
if (mVideoQuality != QUALITY_AUTO) {
    builder.setQualitySelector(
        QualitySelector.from(mVideoQuality));
}
  VideoCapture<Recorder> videoCapture = new VideoCapture.Builder<>(builder.build())
      .setMirrorMode(MIRROR_MODE_ON_FRONT_ONLY)
      .build();
    useCases.add(videoCapture);

控制活动录制

您可以使用以下方法暂停、恢复和停止正在进行的 Recording

  • pause 暂停当前活动录制。
  • resume() 恢复已暂停的活动录制。
  • stop() 完成录制并刷新任何相关的录制对象。
  • mute() 静音或取消静音当前录制。

请注意,您可以调用 stop() 来终止 Recording,而不管录制是否处于暂停或活动录制状态。

如果您已使用 PendingRecording.start() 注册了 EventListener,则 Recording 使用 VideoRecordEvent 进行通信。

  • VideoRecordEvent.EVENT_TYPE_STATUS 用于录制统计信息,例如当前文件大小和录制时间跨度。
  • VideoRecordEvent.EVENT_TYPE_FINALIZE 用于录制结果,包括最终文件 URI 以及任何相关错误的信息。

一旦您的应用收到指示录制会话成功的 EVENT_TYPE_FINALIZE,您就可以从 OutputOptions 中指定的位置访问捕获的视频。

其他资源

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