20、录制与回放:本地录制MediaRecorder、云端录制方案、录制文件管理

录制功能,说白了就是给视频会议装上“记忆”能力。我做过不少会议APP,发现用户对录制的需求其实很朴素——能录、能看、别丢数据。但真做起来,坑还真不少。

这一章,咱们就聊聊Android端怎么把录制这件事做扎实。从本地MediaRecorder到云端方案,再到文件怎么管,一条线讲透。

20.1 本地录制:MediaRecorder的正确打开方式

Android自带的MediaRecorder,用起来不算复杂。但你要想录WebRTC的画面和声音,就得注意几个关键点。

核心思路:把WebRTC的VideoTrack和AudioTrack,喂给MediaRecorder。但MediaRecorder不吃Track,它吃Surface和AudioSource。所以得做个“转接头”。

关键步骤

  • 从VideoTrack获取Surface
  • 把Surface塞给MediaRecorder的setInputSurface()
  • 音频用AudioRecord采集PCM,再编码写入

嗯,这里要注意:MediaRecorder的音频输入不支持直接传PCM。我早期踩过这个坑,录出来的视频没声音。后来改用MediaCodec + AudioRecord自己拼,才搞定。

来个代码片段,看看核心流程:

// 初始化MediaRecorder
MediaRecorder recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
recorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
recorder.setVideoSize(1280, 720);
recorder.setVideoFrameRate(30);

// 关键:获取Surface
Surface surface = recorder.getSurface();
// 把这个surface传给VideoTrack的Sink
videoTrack.addSink(new VideoSink() {
    @Override
    public void onFrame(VideoFrame frame) {
        // 内部会渲染到surface上
    }
});

recorder.prepare();
recorder.start();

我曾经遇到过一个问题:录制过程中切换摄像头,画面会卡住。原因是MediaRecorder的Surface不能动态换。解决方案是停止录制,重新创建MediaRecorder。虽然有点粗暴,但稳定。

20.2 云端录制:把压力丢给服务器

本地录制有个硬伤——手机存储有限,而且耗电。用户开个两小时会议,手机能烫得煎鸡蛋。所以很多场景下,云端录制是更好的选择。

云端录制的本质:客户端把音视频流推到服务器,服务器负责混流、编码、存储。客户端只负责推流,不负责写文件。

我个人习惯用SFU(Selective Forwarding Unit)架构。客户端推一路流到SFU,SFU再转发给其他参会者。同时,SFU可以单独拉一路流去做录制。

这样好处很明显:

  • 客户端零负担,不耗电不占存储
  • 录制质量可控,服务器性能强
  • 支持合流录制,所有参会者在一个画面里

但云端录制也有坑。我记得有个项目,用户反馈录制文件声音和画面不同步。查了半天,发现是服务器端音频采样率和视频帧率没对齐。后来加了时间戳校准才解决。

我建议:云端录制一定要做“录制状态回调”。客户端要知道什么时候开始录、什么时候结束、文件是否生成成功。不然用户点了录制按钮,结果文件没存上,体验就很糟糕。

20.3 录制文件管理:别让文件变成垃圾

录完的文件怎么管?这问题看着简单,实际挺头疼。用户录了100个会议,每个1GB,手机很快就满了。

文件管理三要素

  1. 存储策略:本地还是云端?本地存多久?云端存多久?
  2. 文件命名:别用时间戳,用户看不懂。用“会议主题_日期_时长”这种格式。
  3. 清理机制:自动删除过期文件,或者提示用户手动清理。

我做过一个方案,供你参考:

存储位置 保留策略 清理方式
本地 保留7天 超过7天自动删除,或移入“已过期”目录
云端 保留30天 30天后转为冷存储,90天后删除
收藏 永久保留 用户手动取消收藏后按默认策略处理

嗯,这里要注意:文件删除前一定要给用户通知。我见过一个APP,悄无声息把用户的重要会议录删了,用户直接炸毛。所以,删除前弹个确认框,或者发个通知,都是必要的。

文件命名规范

会议名称_日期_开始时间_时长.mp4
示例:项目评审_2025-03-20_14-30_01h23m.mp4

20.4 整体架构图

下面这张图,把本地录制、云端录制、文件管理的关系画清楚了。你一看就明白。

录制与回放整体架构 WebRTC 音视频流 本地录制 MediaRecorder + Surface 云端录制 SFU + 服务器混流 录制文件管理 存储策略 | 命名规范 | 清理机制 回放播放器 客户端 服务端

从图里能看出来,本地录制和云端录制是两条并行的路。文件管理是它们的“汇合点”。回放则是最终的用户体验。

20.5 避坑指南

做录制功能,我踩过的坑不少。挑几个典型的说说:

  • 录制文件损坏:录制过程中APP被杀死,文件没正常关闭。解决方案是加个“录制中”状态标记,恢复时检查并修复。
  • 音频回声:本地录制时,扬声器声音又被麦克风录进去。我建议用耳机,或者开启回声消除。
  • 存储空间不足:录制前检查剩余空间,低于500MB就提示用户。别等到写文件失败才报错。
  • 权限问题:Android 11以后,存储权限变严了。记得用MediaStore API,别直接写文件路径。

我个人习惯:录制开始前,先做一次“环境检查”。检查存储空间、权限、网络状态(如果是云端录制)。全部通过才允许用户点击录制按钮。这样能避免90%的录制失败问题。

好了,录制与回放这块,核心就是这些。本地录制适合轻量场景,云端录制适合正式会议。文件管理则是用户体验的最后一公里。把这三点做好,录制功能就稳了。


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