18、音视频录制流程:MediaRecorder 架构、编码器配置、文件封装(MP4/TS)

大家好,我是你们的老朋友。今天我们来聊聊 Android 上最常用的录制 API——MediaRecorder。说实话,这个组件我用了快十年了,从最早的 2.3 时代一路跟到现在的 14、15 版本。它的封装程度很高,但正因为高,很多人只停留在「调个接口就能录」的层面。一旦遇到画质不对、音画不同步、封装格式选错这类问题,就抓瞎了。

这一章,我会带你深入 MediaRecorder 的内部架构,看看它到底是怎么工作的。然后我们聊聊编码器的选择与配置,最后再对比一下 MP4 和 TS 这两种封装格式的适用场景。嗯,内容不少,我们慢慢来。

18.1 MediaRecorder 的整体架构

先看一张图,这是我画的 MediaRecorder 核心架构图。它把整个录制流程分成了三层:应用层、框架层、底层引擎。

MediaRecorder 录制架构图 应用层 (Application Layer) MediaRecorder API → setAudioSource() / setVideoSource() / setOutputFormat() / prepare() / start() 开发者直接调用的 Java/Kotlin 接口 框架层 (Framework Layer) MediaRecorderClient (Binder) → MediaRecorderService StagefrightRecorder → 音视频源管理 / 编码器调度 / 封装器控制 C++ 层实现,通过 Binder 与应用层通信 底层引擎 (Media Engine) AudioSource (AudioFlinger) / CameraSource (Camera HAL) MediaCodec (OMX/Codec2) → 编码 MPEG4Writer / TSWriter → 封装输出

从这张图你能看到,MediaRecorder 并不是一个简单的「录制器」,它背后是一整套流水线。我个人习惯把这条流水线分成三个阶段:

  1. 数据采集阶段:从麦克风(AudioSource)和摄像头(CameraSource)获取原始数据。
  2. 编码阶段:通过 MediaCodec 将原始音视频数据压缩成 H.264/H.265、AAC 等格式。
  3. 封装阶段:由 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 以上更好
我的经验: 比特率不是越高越好。我曾经在一个项目里把 1080p 的比特率设到了 20 Mbps,结果录了 5 分钟就卡死了。后来发现是存储写入速度跟不上。你想想看,20 Mbps 意味着每秒要写 2.5 MB 数据,如果用的是低速 SD 卡,肯定扛不住。建议根据目标设备的存储性能做适配。

另外,编码器的配置顺序也有讲究。我习惯这样写:

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