16、视频录制进阶:MediaRecorder与MediaCodec混合方案、分段录制、断点续录

各位好,我是老张。今天聊点硬核的——视频录制的进阶玩法。

如果你只是用 MediaRecorder 录个短视频,那确实简单。但一旦遇到「录到一半内存不够」、「用户想暂停继续」、「需要自定义编码参数」这些场景,MediaRecorder 就有点力不从心了。这时候,就得请出 MediaCodec 这位老大哥。

核心思路:MediaRecorder 负责「简单录制」,MediaCodec 负责「精细控制」。两者混搭,才是生产环境下的正确姿势。

视频录制进阶方案 录制核心 MediaRecorder + MediaCodec 混合 分段录制 断点续录 编码参数自定义 Surface 输入 文件大小限制 时间戳拼接 暂停/恢复状态机 三种方案可组合使用,满足不同业务场景

16.1 为什么需要混合方案?

MediaRecorder 封装得太好了。你只需要设置音频源、视频源、输出格式、输出文件,然后 start() 就完事。但问题也出在这里——你没法精细控制编码参数,比如码率、帧率、关键帧间隔。

MediaCodec 呢?它给了你完全的控制权。但代价是代码量翻倍,还得自己处理音视频同步、文件封装。说实话,我早期做项目时也踩过这个坑——用纯 MediaCodec 写了个录制模块,结果音视频不同步,调试了整整两天。

所以,我的建议是:用 MediaRecorder 做壳,用 MediaCodec 做核。什么意思?就是让 MediaRecorder 负责文件封装和音视频同步,但通过自定义的 Surface 把视频数据喂给 MediaCodec 做编码。

我的经验:在小米、华为等机型上,纯 MediaRecorder 的编码参数往往被厂商魔改过。用混合方案可以绕过这些限制,保证编码一致性。

16.2 MediaRecorder + MediaCodec 混合方案实战

具体怎么做?核心就三步:

  1. 创建 MediaCodec 编码器,配置编码参数(H.264、码率、帧率等)
  2. 从 MediaCodec 获取 Surface,传给 MediaRecorder 的 setInputSurface()
  3. MediaRecorder 负责封装,MediaCodec 负责编码

看代码:

// 创建 MediaCodec 编码器
MediaCodec codec = MediaCodec.createEncoderByType(MediaFormat.MIME_TYPE_AVC);
MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIME_TYPE_AVC, 1920, 1080);
format.setInteger(MediaFormat.KEY_BIT_RATE, 8_000_000);  // 8Mbps
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);  // 关键帧间隔1秒
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

// 获取输入 Surface
Surface inputSurface = codec.createInputSurface();

// 配置 MediaRecorder
MediaRecorder recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);  // 关键!
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
recorder.setOutputFile(outputPath);
recorder.setInputSurface(inputSurface);  // 传入编码器的 Surface
recorder.prepare();
recorder.start();

// 开始编码
codec.start();

嗯,这里要注意:setVideoSource(SURFACE) 是 MediaRecorder 在 API 21 之后才支持的。如果你还在用老版本,那只能走纯 MediaCodec 路线了。

我曾经踩过的坑:MediaRecorder 的 start() 和 MediaCodec 的 start() 调用顺序不能错。必须先 start MediaRecorder,再 start MediaCodec。否则会出现「Surface 未就绪」的诡异崩溃。

16.3 分段录制:解决「录到一半内存不够」

分段录制,说白了就是把一个长视频切成多个小片段。为什么要这么做?

  • 内存限制:Android 对单个文件大小有限制(比如 4GB),超过就写不进去了
  • 容错:如果录制过程中崩溃,至少前面的片段已经保存了
  • 编辑方便:分段后可以单独处理每一段,比如删除某一段

实现思路其实很简单:监听 MediaRecorder 的 MaxDurationReachedMaxFileSizeReached 回调,然后停止当前录制,创建新的 MediaRecorder 实例继续录。

recorder.setOnInfoListener((mr, what, extra) -> {
    if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
        || what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
        // 停止当前录制
        mr.stop();
        mr.release();
        
        // 创建新实例继续录
        startNewSegment();
    }
});

但这里有个细节:分段后的视频文件,时间戳是独立的。如果你想把它们合并成一个完整的视频,需要在后期用 MediaMuxer 重新封装,或者用 FFmpeg 拼接。

我的建议:分段录制时,每个片段的文件名里带上时间戳(比如 segment_20240101_120000.mp4),方便后期排序和拼接。

16.4 断点续录:用户暂停后还能继续

断点续录,就是用户点击「暂停」后,录制暂停;点击「继续」后,在同一个文件里接着录。

MediaRecorder 本身不支持暂停。你想想看,它只有 start() 和 stop(),没有 pause() 和 resume()。那怎么办?

有两种方案:

方案 原理 优点 缺点
方案A:分段+拼接 暂停时停止当前片段,继续时新建片段,最后合并 实现简单,兼容性好 需要后期合并,有短暂黑帧
方案B:MediaCodec 接管 用 MediaCodec 编码,暂停时停止输入数据,继续时恢复 无缝衔接,无黑帧 代码复杂,需要自己处理封装

我个人更推荐方案A,因为简单可靠。方案B虽然效果更好,但代码量至少翻三倍,而且容易出音视频同步问题。

方案A的具体实现:

// 暂停时
private void pauseRecording() {
    if (isRecording) {
        currentRecorder.stop();
        currentRecorder.release();
        isRecording = false;
        // 记录暂停时间点
        pauseTimestamp = System.currentTimeMillis();
    }
}

// 继续时
private void resumeRecording() {
    if (!isRecording) {
        // 创建新的 MediaRecorder 实例
        currentRecorder = new MediaRecorder();
        // 配置参数...
        currentRecorder.setOutputFile(getNextSegmentPath());
        currentRecorder.prepare();
        currentRecorder.start();
        isRecording = true;
    }
}

合并的时候,用 MediaMuxer 把所有片段的视频轨道和音频轨道按时间顺序写进去。注意要调整每个片段的时间戳偏移量。

一个小技巧:分段录制时,每个片段的起始时间戳不要从0开始,而是从上一个片段的结束时间戳继续。这样合并时就不需要重新计算时间戳了。

16.5 避坑指南

最后,分享几个我实际项目中踩过的坑:

  • Surface 生命周期:MediaCodec 的 Surface 必须在 MediaRecorder 启动前创建好,否则会报 IllegalStateException
  • 音频同步:分段录制时,音频和视频的采样率、声道数必须保持一致,否则合并后音画不同步
  • 文件权限:Android 11 以上,分段录制需要提前申请 MANAGE_EXTERNAL_STORAGE 权限,否则写文件会失败
  • 内存泄漏:MediaRecorder 和 MediaCodec 都是重量级对象,记得在 onDestroy() 里释放,否则会内存泄漏

嗯,以上就是视频录制进阶的全部内容。混合方案、分段录制、断点续录,这三招用好了,大部分录制场景都能搞定。如果你在实际项目中遇到什么问题,欢迎交流。


公众号:蓝海资料掘金营,微信deep3321