11、视频录制:MediaRecorder基础、录制参数配置、录制与预览同步、录制状态回调
好,咱们今天来聊聊视频录制。说实话,在Android上做视频录制,MediaRecorder是绕不开的核心组件。我最早接触它的时候,觉得这玩意儿挺简单的——不就是开始、停止、保存文件吗?结果真上手才发现,坑多着呢。尤其是录制和预览的同步问题,搞不好就是黑屏、花屏、音画不同步。
今天这一章,我会把MediaRecorder的底裤扒干净。从基础用法到参数配置,再到录制与预览的同步,最后是状态回调的监听。嗯,咱们一步步来。
11.1 MediaRecorder基础
MediaRecorder是Android官方提供的音视频录制API。它可以直接从Camera和麦克风采集数据,编码后写入文件。说白了,它就是个“录制流水线”——采集、编码、封装一气呵成。
它的基本使用流程是这样的:
- 创建MediaRecorder实例
- 配置音频源、视频源、输出格式、编码器、文件路径等
- 调用prepare()准备录制
- 调用start()开始录制
- 调用stop()停止录制
- 调用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()。
正确的同步流程是这样的:
- 打开Camera,配置参数
- 调用camera.unlock()解锁
- 创建MediaRecorder,调用setCamera()绑定Camera
- 配置MediaRecorder的其他参数
- 调用mediaRecorder.prepare()
- 调用camera.lock()重新锁定Camera(可选)
- 调用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虽然看起来简单,但真正用好它,需要你对它的状态机、参数配置和同步机制有深入理解。下一章我们会继续深入,聊聊更高级的录制技巧。