18、音视频录制流程:MediaRecorder 架构、编码器配置、文件封装(MP4/TS)
大家好,我是你们的老朋友。今天我们来聊聊 Android 上最常用的录制 API——MediaRecorder。说实话,这个组件我用了快十年了,从最早的 2.3 时代一路跟到现在的 14、15 版本。它的封装程度很高,但正因为高,很多人只停留在「调个接口就能录」的层面。一旦遇到画质不对、音画不同步、封装格式选错这类问题,就抓瞎了。
这一章,我会带你深入 MediaRecorder 的内部架构,看看它到底是怎么工作的。然后我们聊聊编码器的选择与配置,最后再对比一下 MP4 和 TS 这两种封装格式的适用场景。嗯,内容不少,我们慢慢来。
18.1 MediaRecorder 的整体架构
先看一张图,这是我画的 MediaRecorder 核心架构图。它把整个录制流程分成了三层:应用层、框架层、底层引擎。
从这张图你能看到,MediaRecorder 并不是一个简单的「录制器」,它背后是一整套流水线。我个人习惯把这条流水线分成三个阶段:
- 数据采集阶段:从麦克风(AudioSource)和摄像头(CameraSource)获取原始数据。
- 编码阶段:通过 MediaCodec 将原始音视频数据压缩成 H.264/H.265、AAC 等格式。
- 封装阶段:由 MPEG4Writer 或 TSWriter 将编码后的数据写入文件容器。
这里有个关键点:MediaRecorder 内部其实是在 StagefrightRecorder 这个 C++ 类里完成所有调度的。它负责协调音视频源的启动时间、编码器的配置、以及封装器的写入节奏。我在项目中遇到过一个问题——录制出来的视频前几帧是黑的,后来发现是摄像头启动比编码器慢,导致编码器拿到了空数据。解决办法是在 start() 之前加一个短暂的延迟,或者用 setCaptureRate() 做缓冲。
18.2 编码器配置:选对参数很重要
编码器配置是录制质量的关键。MediaRecorder 提供了几个 setter 方法,但很多人只用了默认值。我建议你至少关注以下三个参数:
| 参数 | 方法 | 推荐值 | 说明 |
|---|---|---|---|
| 视频编码格式 | setVideoEncoder() |
H.264 (AVC) 或 H.265 (HEVC) | H.264 兼容性最好,H.265 压缩率更高 |
| 视频比特率 | setVideoEncodingBitRate() |
2~20 Mbps(视分辨率而定) | 1080p 建议 8~12 Mbps,4K 建议 20~40 Mbps |
| 帧率 | setVideoFrameRate() |
24 / 30 / 60 fps | 30 fps 是主流,60 fps 需要硬件支持 |
| 音频编码格式 | setAudioEncoder() |
AAC (LC) 或 AAC (HE) | AAC LC 兼容性最好,HE-AAC 适合低码率 |
| 音频采样率 | setAudioSamplingRate() |
44100 或 48000 Hz | 48000 Hz 是视频录制标准 |
| 音频比特率 | setAudioEncodingBitRate() |
128~256 kbps | 128 kbps 够用,192 kbps 以上更好 |
另外,编码器的配置顺序也有讲究。我习惯这样写:
MediaRecorder recorder = new MediaRecorder();
// 1. 先设置音视频源
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
// 2. 再设置输出格式
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
// 3. 然后设置编码参数
recorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
recorder.setVideoEncodingBitRate(10_000_000); // 10 Mbps
recorder.setVideoFrameRate(30);
recorder.setVideoSize(1920, 1080);
recorder.setAudioSamplingRate(48000);
recorder.setAudioEncodingBitRate(192_000);
// 4. 最后设置输出文件
recorder.setOutputFile("/sdcard/record.mp4");
// 5. 准备并启动
recorder.prepare();
recorder.start();
为什么顺序这么重要?因为 MediaRecorder 内部的状态机是严格分阶段的。如果你在 setOutputFormat 之前调了 setVideoEncoder,有些设备会直接抛异常。嗯,这是 Android 文档里没写清楚的坑。
18.3 文件封装:MP4 vs TS
MediaRecorder 支持多种输出格式,但最常用的就是 MP4 和 TS。很多人觉得「反正都是视频文件,随便选一个就行」,其实不然。这两种格式的设计目标完全不同。
MP4(MPEG-4 Part 14)
MP4 是互联网上最通用的容器格式。它的特点是:
- 元数据集中在文件头:moov box 包含所有索引信息。这意味着文件必须完整写入后才能正常播放。
- 随机访问友好:支持快进、快退、章节跳转。
- 流式传输不友好:如果文件没写完(比如录制中断),moov box 可能损坏,导致整个文件无法播放。
我在项目中遇到过最典型的问题:用户录制到一半,手机没电了,结果录制的 MP4 文件打不开。后来我们加了一个「边录边修复」机制——每 5 秒写一次 moov box 的临时索引,但说实话,这只能降低损失,不能完全避免。
TS(MPEG-2 Transport Stream)
TS 是广播电视领域的老将。它的特点是:
- 数据包化:每个 TS 包 188 字节,包含独立的 PES(Packetized Elementary Stream)。
- 抗丢包能力强:即使中间有数据丢失,前后包依然可以解码。
- 实时流式传输:不需要等待文件写完,边录边播。
- 文件体积略大:因为每个包都有头部开销。
什么时候用 TS?我个人建议:
- 直播推流场景(RTMP/HLS 底层就是 TS)
- 长时间录制(比如监控摄像头,录制 24 小时不断)
- 对文件完整性要求不高的场景(允许丢帧)
什么时候用 MP4?
- 短视频录制(15 秒到 5 分钟)
- 需要后期编辑的场景(剪辑软件对 MP4 支持最好)
- 上传到社交平台(平台几乎都要求 MP4)
| 特性 | MP4 | TS |
|---|---|---|
| 元数据结构 | 集中式(moov box) | 分布式(每个包独立) |
| 抗损坏能力 | 弱(文件头损坏则全损) | 强(可部分播放) |
| 流式传输 | 不友好 | 原生支持 |
| 文件大小 | 较小 | 略大(约 5%~10% 开销) |
| 兼容性 | 极广(所有平台) | 较广(直播/广电领域) |
18.4 避坑指南:我踩过的几个雷
最后,分享几个我在 MediaRecorder 开发中踩过的坑,希望能帮你少走弯路。
我曾经天真地以为可以在录制中直接调用
switchCamera()。结果呢?MediaRecorder 直接崩溃。后来查源码发现,它内部没有做摄像头切换的同步处理。正确做法是:先 stop(),释放旧摄像头,再 start() 新摄像头。虽然会有一个短暂的黑屏,但至少不会崩溃。
有一次录出来的视频,声音比画面快了 0.5 秒。排查后发现是音频源(MIC)的启动延迟比摄像头短。解决办法是在
setAudioSource() 之后,手动加一个 Thread.sleep(200),让音频等一等摄像头。或者用 setAudioCaptureRate() 做缓冲。
有个客户反馈,他们录制的视频在 Windows 上打不开。我一看代码,用的是
OutputFormat.THREE_GPP。这个格式虽然也是 MP4 的变种,但很多播放器不支持。后来改成 OutputFormat.MPEG_4,问题解决。记住:除非你有特殊需求,否则永远用 MPEG_4。
好了,关于 MediaRecorder 的架构、编码器配置和文件封装,我们就聊到这里。这些内容看起来零散,但其实是录制流程中环环相扣的三个环节。你只要把架构图记在脑子里,再结合我提到的那些坑,基本上就能应对大部分录制场景了。
公众号:蓝海资料掘金营,微信deep3321