11、视频录制:MediaRecorder基础、录制参数配置、录制与预览同步、录制状态回调

好,咱们今天来聊聊视频录制。说实话,在Android上做视频录制,MediaRecorder是绕不开的核心组件。我最早接触它的时候,觉得这玩意儿挺简单的——不就是开始、停止、保存文件吗?结果真上手才发现,坑多着呢。尤其是录制和预览的同步问题,搞不好就是黑屏、花屏、音画不同步。

今天这一章,我会把MediaRecorder的底裤扒干净。从基础用法到参数配置,再到录制与预览的同步,最后是状态回调的监听。嗯,咱们一步步来。

11.1 MediaRecorder基础

MediaRecorder是Android官方提供的音视频录制API。它可以直接从Camera和麦克风采集数据,编码后写入文件。说白了,它就是个“录制流水线”——采集、编码、封装一气呵成。

它的基本使用流程是这样的:

  1. 创建MediaRecorder实例
  2. 配置音频源、视频源、输出格式、编码器、文件路径等
  3. 调用prepare()准备录制
  4. 调用start()开始录制
  5. 调用stop()停止录制
  6. 调用release()释放资源

我在项目中遇到过不少新手,上来就start(),结果直接崩溃。为什么?因为MediaRecorder的状态机非常严格,你必须在正确的状态下调用正确的方法。比如,没有调用prepare()就直接start(),那肯定报错。

核心要点:MediaRecorder是一个状态机驱动的组件。它的状态包括:Initial、Initialized、DataSourceConfigured、Prepared、Recording、Released等。每个方法只能在特定状态下调用。

来看一个最基础的录制代码:

MediaRecorder mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mediaRecorder.setOutputFile("/sdcard/test.mp4");
mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());

try {
    mediaRecorder.prepare();
    mediaRecorder.start();
} catch (IOException e) {
    e.printStackTrace();
}

这段代码看起来简单,但有几个细节要注意。第一,setPreviewDisplay()必须在prepare()之前调用。第二,输出文件路径必须可写。第三,音频源和视频源的配置顺序不能乱——我个人习惯先配音频源,再配视频源。

11.2 录制参数配置

参数配置是MediaRecorder的灵魂。配置得好,录出来的视频清晰流畅;配置得不好,要么文件巨大,要么画质感人。

常用的配置参数包括:

参数 方法 说明
视频编码 setVideoEncoder() H264、H265、MPEG_4_SP等
音频编码 setAudioEncoder() AAC、AMR_NB、AMR_WB等
输出格式 setOutputFormat() MPEG_4、THREE_GPP、WEBM等
视频分辨率 setVideoSize() 例如1920x1080
视频帧率 setVideoFrameRate() 通常15-30fps
视频比特率 setVideoEncodingBitRate() 决定画质和文件大小
音频比特率 setAudioEncodingBitRate() 决定音质
音频采样率 setAudioSamplingRate() 44100Hz是标准

这里我特别想说说比特率。你想想看,同样的分辨率,比特率越高画质越好,但文件也越大。我曾经做过一个项目,客户要求录1080p视频,但文件大小不能超过50MB。我算了一下,比特率设到8Mbps,录一分钟大概60MB,超了。最后降到4Mbps,画质勉强能接受。

我的建议:对于普通场景,720p视频用4Mbps比特率,1080p用8Mbps。如果对画质要求高,可以适当提高。但别超过20Mbps,很多低端手机扛不住。

还有一个容易被忽略的参数——setCaptureRate()。这个参数控制视频的采集帧率。如果你设了30fps的帧率,但采集帧率设成15,那实际录出来的视频就是慢动作效果。嗯,这个在特殊场景下挺有用的。

11.3 录制与预览同步

录制与预览的同步,说白了就是让用户看到的画面和录进去的画面保持一致。这个问题看似简单,但坑不少。

首先,预览和录制用的是同一个Camera实例。你调用Camera的startPreview()开始预览,然后通过setPreviewDisplay()把预览画面显示到SurfaceView上。录制时,MediaRecorder会从Camera获取数据,编码后写入文件。

关键点在于:预览和录制的数据流是独立的。预览走的是Camera的预览回调,录制走的是MediaRecorder的内部通道。如果两者不同步,就会出现“预览看到的是人脸,录出来的是后脑勺”这种诡异情况。

为什么会这样?因为Camera的预览和录制可能使用不同的缓冲区。我记得有一次调试一个项目,预览画面正常,但录出来的视频总是偏色。查了半天,发现是预览的ColorFormat和录制的ColorFormat不一致。

注意:在调用MediaRecorder的setCamera()之前,必须先解锁Camera。否则MediaRecorder无法获取Camera的控制权。解锁方法是camera.unlock()。

正确的同步流程是这样的:

  1. 打开Camera,配置参数
  2. 调用camera.unlock()解锁
  3. 创建MediaRecorder,调用setCamera()绑定Camera
  4. 配置MediaRecorder的其他参数
  5. 调用mediaRecorder.prepare()
  6. 调用camera.lock()重新锁定Camera(可选)
  7. 调用mediaRecorder.start()开始录制

这里有个小技巧:如果你在录制过程中需要切换摄像头,必须先停止录制,释放MediaRecorder,然后重新走一遍流程。别想着热切换,MediaRecorder不支持。

11.4 录制状态回调

MediaRecorder提供了几个回调接口,用于监听录制状态。最常用的是OnInfoListener和OnErrorListener。

OnInfoListener会告诉你一些信息性的事件,比如:

  • MEDIA_RECORDER_INFO_UNKNOWN:未知信息
  • MEDIA_RECORDER_INFO_MAX_DURATION_REACHED:达到最大录制时长
  • MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED:达到最大文件大小
  • MEDIA_RECORDER_INFO_COMPLETION_DURATION_TRACKED:录制完成

OnErrorListener则处理错误事件,比如:

  • MEDIA_RECORDER_ERROR_UNKNOWN:未知错误
  • MEDIA_RECORDER_ERROR_SERVER_DIED:MediaServer挂了

我在项目中遇到过最头疼的问题就是MEDIA_RECORDER_ERROR_SERVER_DIED。这个错误通常意味着MediaServer进程崩溃了,原因可能是资源耗尽、编码器冲突、或者系统负载过高。一旦出现这个错误,MediaRecorder就废了,必须重新创建。

避坑指南:我曾经在一个低端手机上测试录制,连续录制10次后,第11次直接报MEDIA_RECORDER_ERROR_SERVER_DIED。后来发现是MediaRecorder的release()没有在finally块中调用,导致资源泄漏。所以,一定要确保每次录制结束后都正确释放资源。

来看一个完整的状态回调示例:

mediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
    @Override
    public void onInfo(MediaRecorder mr, int what, int extra) {
        switch (what) {
            case MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED:
                // 达到最大时长,停止录制
                stopRecording();
                break;
            case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED:
                // 达到最大文件大小,停止录制
                stopRecording();
                break;
        }
    }
});

mediaRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() {
    @Override
    public void onError(MediaRecorder mr, int what, int extra) {
        // 处理错误,比如重启录制
        releaseMediaRecorder();
        startRecording();
    }
});

这里要注意,onError回调中不要做耗时操作,否则可能阻塞MediaRecorder的内部线程。我一般会在onError中设置一个标志位,然后在主线程中处理重启逻辑。

11.5 知识体系总览

为了让你更直观地理解MediaRecorder的整个工作流程,我画了一张图:

MediaRecorder 视频录制知识体系 MediaRecorder 基础使用 参数配置 预览同步 状态回调 创建 → 配置 → prepare → start → stop → release 编码器、分辨率、比特率、帧率 Camera.unlock → setCamera → 预览显示 OnInfoListener / OnErrorListener 核心原则:状态机驱动、资源及时释放、预览与录制数据流同步 输出:MP4 / 3GP 视频文件

这张图把MediaRecorder的核心知识点串起来了。你从中间开始看,四个分支分别对应我们今天讲的基础、参数、同步和回调。最下面是最终输出的视频文件。

好了,这一章的内容就到这里。MediaRecorder虽然看起来简单,但真正用好它,需要你对它的状态机、参数配置和同步机制有深入理解。下一章我们会继续深入,聊聊更高级的录制技巧。


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