You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android 8下使用CameraX无预览录制视频失败问题求助

Android 8 下 CameraX 无预览录制视频失败问题

我需要在Android 8系统中使用CameraX实现无预览界面的视频录制,但当前代码无法正常工作,日志显示录制状态从PENDING_RECORDING回退到INITIALIZING,没有生成有效视频文件。

现有代码

private fun Recording() {
    val selector = QualitySelector
        .from(
            Quality.UHD,
            FallbackStrategy.higherQualityOrLowerThan(Quality.SD)
        )
    // build a recorder, which can:
    //   - record video/audio to MediaStore(only shown here), File, ParcelFileDescriptor
    //   - be used create recording(s) (the recording performs recording)
    val recorder = Recorder.Builder()
        .setQualitySelector(selector)
        .build()
    videoCapture = VideoCapture.withOutput(recorder)
    val name = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)?.absolutePath + "/recording.mp4"
    val contentValues = ContentValues().apply {
        put(MediaStore.Video.Media.DISPLAY_NAME, name)
    }
    Log.i("WEE",contentValues.toString())
    val mediaStoreOutput = MediaStoreOutputOptions.Builder(
        contentResolver,
        MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
        .setContentValues(contentValues)
        .build()

    // configure Recorder and Start recording to the mediaStoreOutput.
    currentRecording = videoCapture.output.prepareRecording(applicationContext,mediaStoreOutput).withAudioEnabled().start(mainThreadExecutor, captureListener)
    Log.i("TAG", "Recording started")
    sleep(4000)
    currentRecording!!.stop()
    currentRecording = null
}

private val captureListener = Consumer<VideoRecordEvent> { event ->
    // cache the recording state
    Log.i("TAG",event.toString())
    if(event is VideoRecordEvent.Start){
    }
}

日志信息

2022-08-23 14:46:53.838 3348-3348/com.example.simplecamerax D/Recorder: Transitioning Recorder internal state: INITIALIZING --> PENDING_RECORDING
2022-08-23 14:46:53.838 3348-3348/com.example.simplecamerax I/TAG: Recording started
2022-08-23 14:46:57.838 3348-3348/com.example.simplecamerax D/Recorder: Transitioning Recorder internal state: PENDING_RECORDING --> INITIALIZING

问题原因及解决方法

1. CameraX 未绑定生命周期且未启动相机会话

CameraX 的 VideoCapture 必须绑定到生命周期所有者(如 Activity/Fragment),并通过 ProcessCameraProvider 启动相机会话,否则录制流程无法正常初始化。无预览场景下,相机会话的启动是录制的前提。

修复代码:

private lateinit var cameraProvider: ProcessCameraProvider
private lateinit var videoCapture: VideoCapture<Recorder>
private var currentRecording: Recording? = null

private fun initCameraAndRecord() {
    val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
    cameraProviderFuture.addListener({
        cameraProvider = cameraProviderFuture.get()
        
        // 配置录制器
        val selector = QualitySelector.from(
            Quality.UHD,
            FallbackStrategy.higherQualityOrLowerThan(Quality.SD)
        )
        val recorder = Recorder.Builder()
            .setQualitySelector(selector)
            .build()
        videoCapture = VideoCapture.withOutput(recorder)

        // 绑定相机到生命周期,选择后置相机
        val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
        try {
            cameraProvider.unbindAll()
            // 绑定VideoCapture,启动相机会话
            cameraProvider.bindToLifecycle(this, cameraSelector, videoCapture)
            
            // 相机会话启动后开始录制
            startRecording()
        } catch(exc: Exception) {
            Log.e("TAG", "相机绑定失败", exc)
        }
    }, ContextCompat.getMainExecutor(this))
}

private fun startRecording() {
    // 使用FileOutputOptions简化私有目录存储
    val outputFile = File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "recording.mp4")
    val outputOptions = FileOutputOptions.Builder(outputFile).build()

    currentRecording = videoCapture.output
        .prepareRecording(this, outputOptions)
        .withAudioEnabled()
        .start(mainThreadExecutor, captureListener)
    
    Log.i("TAG", "Recording started")
    // 用Handler替代sleep,避免阻塞主线程
    Handler(Looper.getMainLooper()).postDelayed({
        currentRecording?.stop()
        currentRecording = null
        // 录制完成后解绑相机
        cameraProvider.unbindAll()
    }, 4000)
}

2. 主线程被 sleep() 阻塞导致录制异常

原代码中 sleep(4000) 会直接阻塞主线程,而 CameraX 的状态回调、相机操作都依赖主线程运行,这会打断录制流程,导致状态回退。

修复方法:
使用 Handler.postDelayed() 替代 sleep(),保证主线程正常处理相机事件。

3. MediaStoreOutputOptions 路径配置错误

原代码把完整文件路径作为 DISPLAY_NAME 传入,不符合 MediaStore 的规范——DISPLAY_NAME 应该仅为文件名,而非完整路径,这会导致输出目标无法正确创建。

修复方案:

  • 若存储到应用私有目录,直接用 FileOutputOptions(如上述代码)更简单可靠;
  • 若需存储到公共目录,修正 ContentValues:
val contentValues = ContentValues().apply {
    put(MediaStore.Video.Media.DISPLAY_NAME, "recording.mp4")
    put(MediaStore.Video.Media.MIME_TYPE, "video/mp4")
    put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
}

4. 缺少必要权限

Android 8 下录制视频需要 CAMERA、RECORD_AUDIO 权限,存储到公共目录还需 WRITE_EXTERNAL_STORAGE 权限。确保清单文件声明权限,并在运行时请求。

清单文件权限声明:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />

<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

总结

核心问题是相机未绑定生命周期、主线程被阻塞、路径配置错误三者共同导致录制流程中断。按照上述步骤修复后,即可实现 Android 8 下无预览的 CameraX 视频录制。

内容的提问来源于stack exchange,提问作者Gregor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 16:09:16