CameraX 视频捕获架构

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

视频和音频捕获系统概念图
图 1. 视频和音频捕获系统概念图。

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

展示 CameraX 如何处理 VideoCapture 用例的概念图
图 2. 展示 CameraX 如何处理 VideoCapture 用例的概念图。

如图 2 所示,CameraX 视频捕获包括几个高级架构组件:

  • SurfaceProvider,表示视频来源。
  • AudioSource,表示音频来源。
  • 用于对视频/音频进行编码和压缩的两个编码器。
  • 用于对两个流进行多路复用的媒体复用器。
  • 用于写出结果的文件保存器。

VideoCapture API 会对复杂的捕获引擎进行抽象化处理,为应用提供更加简单且直观的 API。

VideoCapture API 概述

VideoCapture 是一种 CameraX 用例,既可以单独使用,也可以与其他用例搭配使用。受支持的具体组合取决于相机硬件功能,不过 PreviewVideoCapture 这一用例组合适用于所有设备。

VideoCapture API 包含可与应用通信的以下对象:

  • VideoCapture 是顶级用例类。VideoCapture 通过 CameraSelector 和其他 CameraX 用例绑定到 LifecycleOwner。如需详细了解这些概念和用法,请参阅 CameraX 架构
  • Recorder 是与 VideoCapture 紧密耦合的 VideoOutput 实现。 Recorder 用于执行视频和音频捕获操作。应用通过 Recorder 创建录制对象。
  • PendingRecording 会配置录制对象,同时提供启用音频和设置事件监听器等选项。您必须使用 Recorder 来创建 PendingRecordingPendingRecording 不会录制任何内容。
  • Recording 会执行实际录制操作。您必须使用 PendingRecording 来创建 Recording

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

展示 VideoCapture 用例中发生的交互的示意图
图 3. 展示 VideoCapture 用例中发生的交互的示意图。

图例

  1. 使用 QualitySelector 创建 Recorder
  2. 使用其中一个 OutputOptions 配置 Recorder
  3. 如果需要,使用 withAudioEnabled() 启用音频。
  4. 使用 VideoRecordEvent 监听器调用 start() 以开始录制。
  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() 来设置文件大小上限。其他选项特定于单个输出类型,例如 ParcelFileDescriptor 特定于 FileDescriptorOutputOptions

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

应用可以进一步配置录制对象,例如:

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

要开始录制,请调用 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() 注册了 EventListenerRecording 会使用 VideoRecordEvent 进行通信。

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

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

其他资源

如需详细了解 CameraX,请参阅下面列出的其他资源: