CameraX进阶:视频录制、CameraController、CameraSelector、分析用例、实战:自定义相机
各位同学,咱们今天聊点硬核的。CameraX 基础用法你肯定已经掌握了,但真正要把相机玩出花来,还得看进阶功能。我个人习惯把 CameraX 的进阶内容分成四个模块:视频录制、CameraController、CameraSelector 和分析用例。最后咱们再串起来,搞一个自定义相机的实战。
嗯,先别急着写代码。咱们先理清楚这几个东西之间的关系。你想想看,CameraX 本质上是一套抽象层,它把底层的 Camera2 API 包了一层,让你用更少的代码干更多的活。但抽象层也有它的脾气——有些坑,你不踩一遍是不知道的。
视频录制:不只是 start/stop
视频录制这块,CameraX 提供了 Recorder 和 PendingRecording 两个核心类。说白了,Recorder 负责配置编码参数,PendingRecording 负责把录制任务提交出去。
我在项目中遇到过一个问题:录出来的视频总是卡顿。排查了半天,发现是 VideoCapture 的 bitRate 设得太高了,手机编码器扛不住。嗯,这里要注意,不同设备的编码能力差异很大,建议先查一下 CamcorderProfile。
核心代码片段:视频录制初始化
val recorder = Recorder.Builder()
.setQualitySelector(QualitySelector.from(Quality.HIGHEST))
.build()
val videoCapture = VideoCapture.withOutput(recorder)
// 绑定到生命周期
cameraProvider.bindToLifecycle(
lifecycleOwner,
cameraSelector,
preview,
videoCapture
)
// 开始录制
val mediaStoreOutput = MediaStoreOutputOptions.Builder(
contentResolver, MediaStore.Video.Media.EXTERNAL_CONTENT_URI
).build()
val pendingRecording = videoCapture.output
.prepareRecording(context, mediaStoreOutput)
.withAudioEnabled()
val recording = pendingRecording.start(
ContextCompat.getMainExecutor(context)
) { recordingEvent ->
// 处理录制事件
}
注意: 调用 withAudioEnabled() 之前,记得先申请 RECORD_AUDIO 权限。我曾经因为忘了这个,在测试时录了一堆无声视频,还以为是麦克风坏了。
CameraController:更高级的抽象
如果你觉得 bindToLifecycle 那一套还是太啰嗦,那 CameraController 就是为你准备的。它把 Preview、ImageCapture、VideoCapture 和 ImageAnalysis 全部封装在一起,你只需要一个 LifecycleOwner 就能搞定。
说白了,CameraController 就是一个「超级遥控器」。你想想看,以前你要分别控制预览、拍照、录像、分析,现在一个对象全包了。
个人经验: 如果你的 App 只需要一个简单的相机界面,用 CameraController 能省一半代码。但如果你需要精细控制(比如手动曝光、自定义对焦区域),那还是老老实实用 bindToLifecycle 吧。
val cameraController = LifecycleCameraController(context)
cameraController.bindToLifecycle(lifecycleOwner)
// 拍照
cameraController.takePicture(
ContextCompat.getMainExecutor(context),
object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(image: ImageProxy) {
// 处理图片
}
}
)
// 录像
cameraController.startRecording(
outputFile,
ContextCompat.getMainExecutor(context)
) { recordingEvent ->
// 录制状态回调
}
CameraSelector:选对摄像头
CameraSelector 的作用就是帮你选摄像头。默认有 DEFAULT_BACK_CAMERA 和 DEFAULT_FRONT_CAMERA,但实际项目中往往需要更灵活的选择。
我记得有一次做双摄 App,需要同时打开前后摄像头。但 CameraX 1.x 版本不支持同时绑定两个摄像头,只能通过 CameraSelector 切换。后来升级到 1.4 才支持了 ConcurrentCamera。
自定义 CameraSelector 示例:
val externalCameraSelector = CameraSelector.Builder()
.addCameraFilter { cameras ->
// 过滤出外部摄像头(比如 USB 摄像头)
cameras.filter { it.lensFacing == null }
}
.build()
| 属性 | 说明 | 常见值 |
|---|---|---|
| LENS_FACING_FRONT | 前置摄像头 | 自拍、人脸识别 |
| LENS_FACING_BACK | 后置摄像头 | 拍照、扫码 |
| LENS_FACING_EXTERNAL | 外部摄像头 | USB 摄像头、外接设备 |
分析用例:不只是扫码
ImageAnalysis 是 CameraX 里最灵活的一个用例。很多人以为它只能做二维码扫描,其实它能做的事情多着呢——人脸检测、物体追踪、颜色分析、甚至实时滤镜。
我做过一个项目,需要实时分析视频帧中的运动物体。当时用 ImageAnalysis 配合 ML Kit,每帧处理时间控制在 10ms 以内,效果还不错。
避坑指南: 分析用例的 setAnalyzer 方法需要传入一个 Executor。我曾经直接传了 mainExecutor,结果 UI 线程被分析任务卡死,界面直接 ANR。建议用 Executors.newSingleThreadExecutor() 开个独立线程。
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
imageAnalysis.setAnalyzer(executor) { imageProxy ->
// 处理每一帧
val buffer = imageProxy.planes[0].buffer
// ... 你的分析逻辑
imageProxy.close() // 记得关闭!
}
实战:自定义相机
好了,理论说完了,咱们来点实际的。下面是一个自定义相机的核心骨架,它整合了预览、拍照、录像和分析四个功能。
自定义相机核心代码:
class CustomCameraFragment : Fragment() {
private lateinit var cameraController: LifecycleCameraController
private lateinit var previewView: PreviewView
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
previewView = view.findViewById(R.id.previewView)
cameraController = LifecycleCameraController(requireContext())
// 配置分析用例
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
imageAnalysis.setAnalyzer(executor) { imageProxy ->
// 实时分析
imageProxy.close()
}
// 绑定
cameraController.imageAnalysis = imageAnalysis
cameraController.bindToLifecycle(viewLifecycleOwner)
// 设置预览
previewView.controller = cameraController
}
fun takePhoto() {
cameraController.takePicture(...)
}
fun startVideo() {
cameraController.startRecording(...)
}
}
这个自定义相机看起来简单,但实际项目中要考虑的事情很多:屏幕旋转、摄像头切换、权限处理、文件存储路径、录制时长限制……嗯,这些细节才是真正考验功力的地方。
我个人习惯把相机相关的逻辑封装成一个 CameraManager 单例,这样 Fragment 或 Activity 只需要调用几个简单的方法就行。代码复用性高,也方便单元测试。
最后提醒一句: CameraX 虽然简化了很多东西,但底层的 Camera2 特性它并没有完全暴露出来。如果你的需求超出了 CameraX 的能力范围,别硬撑,该用 Camera2 就用 Camera2。工具是死的,人是活的。