8、视频录制:Camera+MediaRecorder录制视频、Camera2+MediaCodec录制视频

视频录制这块,说实话是播放器开发里最容易被低估的环节。很多人觉得「不就是调个API嘛」,结果真上手就发现坑一个接一个。我自己最早做录制功能时,也踩过不少雷——比如录出来的视频没声音、画面卡顿、甚至直接闪退。嗯,今天咱们就把这两条路都走一遍。

8.1 两条技术路线,怎么选?

Android上录制视频,主流就两条路:

  • Camera + MediaRecorder:老牌组合,API简单,适合快速开发
  • Camera2 + MediaCodec:新架构,灵活度高,适合定制化需求

我个人习惯是:如果只是做个简单的录制功能,比如拍个短视频上传,用MediaRecorder就够了。但如果你需要实时滤镜、自定义分辨率、或者要对接直播推流,那Camera2+MediaCodec才是正解。

核心区别一句话总结:

MediaRecorder是「黑盒」,你只管开始和停止,中间它自己搞定;MediaCodec是「白盒」,每一帧数据你都能拿到手,想怎么处理都行。

视频录制技术路线对比 Camera + MediaRecorder API 简单,5分钟上手 系统自动处理编码/封装 无法干预中间帧数据 适合:简单录制、快速验证 Camera2 + MediaCodec 需要理解编码器状态机 可获取每一帧原始数据 支持滤镜、裁剪、水印 适合:直播、特效、定制录制 根据需求选方案,不要盲目追求「新」

8.2 Camera + MediaRecorder:五分钟跑通录制

先说说最简单的方案。MediaRecorder这个类,说白了就是Google帮你封装好的一套录制管线。你只需要告诉它「从哪里取画面」「从哪里取声音」「输出到哪里」,剩下的它自己搞定。

核心步骤其实就这几步:

  1. 打开Camera,设置预览
  2. 创建MediaRecorder实例
  3. 配置音视频源、输出格式、编码器
  4. 调用start()开始录制
  5. 调用stop()结束录制

代码写起来大概这样:

// 配置 MediaRecorder
MediaRecorder recorder = new MediaRecorder();

// 必须先解锁Camera再关联
camera.unlock();
recorder.setCamera(camera);

// 设置音视频源
recorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

// 设置输出格式和编码器
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
recorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);

// 设置输出文件
recorder.setOutputFile(outputPath);
recorder.setVideoSize(1280, 720);
recorder.setVideoFrameRate(30);

// 准备并开始
recorder.prepare();
recorder.start();

注意:MediaRecorder的配置顺序是有讲究的。我曾经遇到过setCamera之后忘记先unlock,结果一直报错。另外,setVideoSize必须在setVideoEncoder之后调用,否则会默默忽略——这个坑我查了整整一下午。

8.3 Camera2 + MediaCodec:自己掌控每一帧

好,现在说进阶方案。Camera2配合MediaCodec,说白了就是你自己当导演——Camera2负责采集画面,MediaCodec负责编码压缩,中间的数据流你说了算。

为什么会需要这个方案?举个例子:你想在录视频时加个时间水印,或者做个美颜滤镜。MediaRecorder根本插不进手,但用MediaCodec,你可以在编码前对每一帧做处理。

核心流程是这样的:

  • Camera2通过ImageReader输出原始帧(YUV格式)
  • 你把原始帧传给MediaCodec的输入队列
  • MediaCodec编码后输出H.264/H.265数据
  • 最后用MediaMuxer封装成MP4文件

代码骨架大概长这样:

// 1. 配置 MediaCodec 编码器
MediaFormat format = MediaFormat.createVideoFormat(
    "video/avc", 1280, 720);
format.setInteger(MediaFormat.KEY_BIT_RATE, 2_000_000);
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);

MediaCodec encoder = MediaCodec.createEncoderByType("video/avc");
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
encoder.start();

// 2. 从 ImageReader 获取帧数据
ImageReader reader = ImageReader.newInstance(1280, 720,
    ImageFormat.YUV_420_888, 2);
reader.setOnImageAvailableListener(reader1 -> {
    Image image = reader1.acquireLatestImage();
    if (image != null) {
        // 这里可以加滤镜、水印等处理
        byte[] data = yuv420888ToNv21(image);
        feedToEncoder(encoder, data, presentationTimeUs);
        image.close();
    }
}, backgroundHandler);

// 3. 将编码后的数据写入 MediaMuxer
MediaMuxer muxer = new MediaMuxer(outputPath,
    MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
// ... 在编码器输出回调中不断写入 track

个人经验:MediaCodec的输入输出都是异步的,新手最容易犯的错就是「同步思维」。你往输入队列塞一帧,它不会立刻输出编码后的数据。正确的做法是:在编码器的输出回调里不断取数据,同时持续往输入队列塞帧。这个循环一旦断了,视频就会卡顿。

8.4 避坑指南:我踩过的那些雷

做录制功能,有几个坑是绕不开的。我一个个说:

问题 原因 解决方案
录出来的视频没声音 AudioSource配置不对,或权限没申请 检查RECORD_AUDIO权限,确认麦克风可用
视频画面是绿的 YUV格式不匹配 Camera输出格式和Codec输入格式必须一致
录制几秒后卡死 MediaCodec输入队列满了 控制帧率,不要无限制塞帧
文件无法播放 MediaMuxer没有正确stop 确保所有track写入完毕后再stop

我曾经在一个项目里,录出来的视频总是比实际时长短几秒。查了半天,发现是MediaMuxer的stop调用时机不对——必须在所有编码数据都写入之后才能stop,否则文件会被截断。

8.5 性能优化:别让手机变成暖手宝

视频录制是典型的高功耗场景。你想想看,摄像头一直在采集,CPU/GPU一直在编码,能不热吗?

几个优化方向:

  • 控制分辨率:1080p够用就别上4K,功耗差好几倍
  • 合理设置帧率:24fps和30fps肉眼差别不大,但编码压力差不少
  • 使用Surface输入:Camera2可以直接输出到Surface,省去内存拷贝
  • 及时释放资源:录制结束后记得release Camera和MediaCodec

我的建议:如果只是做普通录制,优先用MediaRecorder。它内部做了很多优化,比你自己手写MediaCodec要稳定得多。只有当你需要定制化处理时,才值得去碰Camera2+MediaCodec这条路线。

嗯,视频录制这块内容就这些。两条路线各有优劣,关键看你的业务场景。MediaRecorder省心,MediaCodec灵活——选哪个,你自己掂量。


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