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. 调用 start() 并传入 VideoRecordEvent 监听器以开始录制。
  5. Recording 上使用 pause()/resume()/stop() 来控制录制。
  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 specification。例如,以下代码请求录制支持的最高分辨率,如果不支持任何请求的分辨率,则授权 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 constants

此代码片段演示了如何使用 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 的更多信息,请参阅以下其他资源