2. CameraX 视频录制与音频采集
CameraX 视频录制与音频采集:用例绑定与录制控制
CameraX 的 VideoCapture 用例负责将相机帧编码为视频,并通过 Recorder 同时采集麦克风音频,最终封装为 MP4 文件。与例 1 的 Preview + ImageCapture 组合相比,本节的核心变化在于:多了一个音频输入源,且录制过程是一个有状态的生命周期(准备 → 录制中 → 停止 → 文件落盘),必须正确处理启停与输出。
1. 用例绑定结构
CameraX 通过 ProcessCameraProvider 将多个用例绑定到同一个 LifecycleOwner。视频录制场景需要三个用例协同:
- Preview:取景器显示,绑定到
PreviewView。 - VideoCapture:由
Recorder构建,负责视频编码与音频采集。 - Audio 输入:由
Recorder.Builder()内部通过AudioConfig启用,无需单独绑定用例。
关键约束:VideoCapture 与 ImageCapture 不能同时绑定(CameraX 限制),因此录制场景下应移除拍照用例。
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中停止录制,防止后台占用麦克风。