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

如何使用CameraX VideoCapture暂停/恢复视频录制?

Android 视频录制暂停/恢复功能实现方案

Android原生的MediaRecorder或Camera2 API并没有提供直接的pause()/resume()方法来暂停视频录制,常用的实现思路是分段录制多个视频文件,最后将这些片段合并成一个完整视频。以下是具体实现步骤:

核心思路

  • 暂停录制时,调用stopRecording()结束当前片段录制并保存为临时文件
  • 恢复录制时,重新初始化MediaRecorder,开始录制新的片段
  • 录制完成后,将所有临时视频片段合并为一个最终视频文件

关键代码实现

(1)定义核心变量

private var mediaRecorder: MediaRecorder? = null
private var isRecording = false
private var isPaused = false
private val videoSegments = mutableListOf<File>() // 存储所有录制片段
private var currentSegmentFile: File? = null

(2)开始/恢复录制

fun startOrResumeRecording() {
    if (isPaused) {
        // 恢复录制:创建新临时片段文件
        currentSegmentFile = createTempVideoFile()
        initMediaRecorder(currentSegmentFile!!)
        mediaRecorder?.start()
        isPaused = false
        isRecording = true
    } else if (!isRecording) {
        // 首次启动录制
        currentSegmentFile = createTempVideoFile()
        initMediaRecorder(currentSegmentFile!!)
        mediaRecorder?.start()
        isRecording = true
    }
}

// 创建临时视频文件
private fun createTempVideoFile(): File {
    val storageDir = getExternalFilesDir(Environment.DIRECTORY_MOVIES)
    return File.createTempFile(
        "RECORD_SEGMENT_${System.currentTimeMillis()}",
        ".mp4",
        storageDir
    ).apply {
        videoSegments.add(this)
    }
}

// 初始化MediaRecorder(根据你的Camera配置调整参数)
private fun initMediaRecorder(outputFile: File) {
    mediaRecorder?.release()
    mediaRecorder = MediaRecorder().apply {
        setAudioSource(MediaRecorder.AudioSource.MIC)
        setVideoSource(MediaRecorder.VideoSource.SURFACE)
        setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
        setOutputFile(outputFile.absolutePath)
        setVideoEncoder(MediaRecorder.VideoEncoder.H264)
        setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
        // 配置分辨率、帧率等参数
        setVideoSize(1280, 720)
        setVideoFrameRate(30)
        prepare()
    }
}

(3)暂停录制

fun pauseRecording() {
    if (isRecording && !isPaused) {
        mediaRecorder?.stop()
        mediaRecorder?.release()
        mediaRecorder = null
        isPaused = true
        isRecording = false
    }
}

(4)停止录制并合并片段

fun stopAndMergeRecording() {
    if (isRecording) {
        mediaRecorder?.stop()
        mediaRecorder?.release()
        mediaRecorder = null
        isRecording = false
    }

    when (videoSegments.size) {
        0 -> return
        1 -> videoSegments[0].renameTo(getFinalOutputFile())
        else -> mergeVideoSegments(videoSegments, getFinalOutputFile())
    }

    // 清理临时片段
    videoSegments.forEach { it.delete() }
    videoSegments.clear()
    isPaused = false
}

// 获取最终输出文件
private fun getFinalOutputFile(): File {
    val storageDir = getExternalFilesDir(Environment.DIRECTORY_MOVIES)
    return File(storageDir, "FINAL_RECORD_${System.currentTimeMillis()}.mp4")
}

(5)原生API实现视频合并

使用Android自带的MediaMuxer完成片段合并,无需第三方库:

private fun mergeVideoSegments(segments: List<File>, outputFile: File) {
    val mediaMuxer = MediaMuxer(outputFile.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
    var videoTrackIndex = -1
    var audioTrackIndex = -1
    val buffer = ByteBuffer.allocate(1024 * 1024)

    segments.forEach { segment ->
        val extractor = MediaExtractor()
        extractor.setDataSource(segment.absolutePath)

        // 定位视频、音频轨道
        for (i in 0 until extractor.trackCount) {
            val format = extractor.getTrackFormat(i)
            val mime = format.getString(MediaFormat.KEY_MIME)
            when {
                mime?.startsWith("video/") == true && videoTrackIndex == -1 -> {
                    videoTrackIndex = mediaMuxer.addTrack(format)
                    extractor.selectTrack(i)
                }
                mime?.startsWith("audio/") == true && audioTrackIndex == -1 -> {
                    audioTrackIndex = mediaMuxer.addTrack(format)
                    extractor.selectTrack(i)
                }
            }
        }

        // 写入片段数据
        if (videoTrackIndex != -1 || audioTrackIndex != -1) {
            mediaMuxer.start()
            var sampleSize: Int
            while (extractor.readSampleData(buffer, 0).also { sampleSize = it } != -1) {
                val trackIndex = if (extractor.getTrackFormat(extractor.selectedTrackIndex)
                        .getString(MediaFormat.KEY_MIME)?.startsWith("video/") == true
                ) videoTrackIndex else audioTrackIndex
                
                mediaMuxer.writeSampleData(
                    trackIndex,
                    buffer,
                    MediaCodec.BufferInfo().apply {
                        offset = 0
                        size = sampleSize
                        presentationTimeUs = extractor.sampleTime
                        flags = extractor.sampleFlags
                    }
                )
                extractor.advance()
            }
        }
        extractor.release()
    }

    mediaMuxer.stop()
    mediaMuxer.release()
}

注意事项

  • 确保申请必要权限:RECORD_AUDIO、CAMERA,Android 10+无需WRITE_EXTERNAL_STORAGE,使用Scoped Storage即可
  • 临时文件需及时清理,避免占用冗余存储空间
  • 合并视频时需保证音视频同步,可通过调整presentationTimeUs参数优化过渡效果
  • 若使用Camera2 API,需适配MediaRecorder的Surface配置,配合Camera2的预览流程

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 20:20:28