2. CameraX 视频录制与音频采集

CameraX 视频录制与音频采集 学习卡片

CameraX 视频录制与音频采集:用例绑定与录制控制

CameraX 的 VideoCapture 用例负责将相机帧编码为视频,并通过 Recorder 同时采集麦克风音频,最终封装为 MP4 文件。与例 1 的 Preview + ImageCapture 组合相比,本节的核心变化在于:多了一个音频输入源,且录制过程是一个有状态的生命周期(准备 → 录制中 → 停止 → 文件落盘),必须正确处理启停与输出。

1. 用例绑定结构

CameraX 通过 ProcessCameraProvider 将多个用例绑定到同一个 LifecycleOwner。视频录制场景需要三个用例协同:

  • Preview:取景器显示,绑定到 PreviewView。
  • VideoCapture:由 Recorder 构建,负责视频编码与音频采集。
  • Audio 输入:由 Recorder.Builder() 内部通过 AudioConfig 启用,无需单独绑定用例。

关键约束:VideoCapture 与 ImageCapture 不能同时绑定(CameraX 限制),因此录制场景下应移除拍照用例。

CameraX 视频录制用例绑定与数据流 LifecycleOwner (Activity / Fragment) ProcessCamera Provider bindToLifecycle Preview 取景器显示 VideoCapture Recorder 构建 AudioConfig 麦克风采集 MP4 文件 FileOutputOptions 编码封装 音频轨 录制状态机: IDLE RECORDING FINALIZED ERROR start() stop() 异常

2. 关键实现:Recorder 构建与录制控制

以下代码展示完整的 VideoCapture 绑定与录制启停逻辑。注意 Recorder 必须通过 QualitySelector 指定分辨率,并通过 FileOutputOptions 指定输出路径。

// 1. 构建 Recorder(含音频采集)
val recorder = Recorder.Builder()
    .setQualitySelector(
        QualitySelector.from(
            Quality.FHD,                          // 优先 1080p
            FallbackStrategy.lowerQualityOrHigherThan(Quality.HD)
        )
    )
    .build()

val videoCapture = VideoCapture.withOutput(recorder)

// 2. 绑定用例(注意:不能同时绑定 ImageCapture)
val cameraProvider = ProcessCameraProvider.getInstance(this).get()
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
    this,
    CameraSelector.DEFAULT_BACK_CAMERA,
    preview,          // 例 1 中已创建的 Preview
    videoCapture
)

// 3. 准备输出文件(应用私有目录)
val videoFile = File(
    getExternalFilesDir(Environment.DIRECTORY_MOVIES),
    "camera_rec_${System.currentTimeMillis()}.mp4"
)

// 4. 构建待处理的录制对象
val outputOptions = FileOutputOptions.Builder(videoFile).build()
val pendingRecording = videoCapture.output
    .prepareRecording(this, outputOptions)
    .withAudioEnabled()   // 启用麦克风采集

// 5. 启动录制
val activeRecording = pendingRecording.start(ContextCompat.getMainExecutor(this)) { event ->
    when (event) {
        is VideoRecordEvent.Start -> { /* 更新 UI:录制中 */ }
        is VideoRecordEvent.Finalize -> {
            if (event.hasError()) {
                // 处理错误:event.error 返回错误码
                videoFile.delete()
            } else {
                // 录制成功,videoFile 即为 MP4
            }
        }
    }
}

// 6. 停止录制
activeRecording.stop()
权限与生命周期:录制视频需要 CAMERA 和 RECORD_AUDIO 两个运行时权限。若未授予 RECORD_AUDIO,withAudioEnabled() 会抛出异常。建议在 onDestroy 中调用 activeRecording.stop() 避免资源泄漏。

3. 录制事件与错误处理

VideoRecordEvent 是录制过程的核心回调,包含以下关键事件:

事件类型 触发时机 关键字段
Start 录制正式开始 无
Status 录制中周期性回调 recordedDurationNanos、numBytesRecorded
Finalize 录制结束(正常或异常) hasError()、error、outputResults

常见错误码及处理方式:

  • ERROR_NO_VALID_DATA:录制时间过短或编码器未产生有效帧,删除空文件。
  • ERROR_RECORDER_ERROR:编码器内部错误,检查分辨率是否被设备支持。
  • ERROR_FILE_SIZE_LIMIT_REACHED:文件大小超限,需重新开始录制。
  • ERROR_INSUFFICIENT_STORAGE:存储空间不足,提示用户清理。
验证方法:录制 5 秒后停止,检查 getExternalFilesDir(Environment.DIRECTORY_MOVIES) 下是否生成 .mp4 文件,且文件大小 > 0。用系统播放器打开确认画面与声音均正常。

4. 与例 1 的差异对照

维度 例 1(预览 + 拍照) 本节(视频录制)
核心用例 Preview + ImageCapture Preview + VideoCapture
音频采集 无 AudioConfig 启用麦克风
输出格式 JPEG MP4(H.264 + AAC)
状态管理 单次拍照,无状态机 IDLE → RECORDING → FINALIZED
权限 CAMERA CAMERA + RECORD_AUDIO

5. 实操检查清单

  • 在 AndroidManifest.xml 中声明 RECORD_AUDIO 权限。
  • 运行时请求 CAMERA 与 RECORD_AUDIO,两者均授予后再绑定用例。
  • 使用 QualitySelector 指定目标分辨率,并提供降级策略。
  • 输出文件放在应用私有目录,避免 Android 10+ 的分区存储限制。
  • 在 Finalize 事件中判断 hasError(),失败时删除残留文件。
  • 在 onDestroy 中停止录制,防止后台占用麦克风。