MediaRecorder 实战:视频录制与参数设置
好,咱们今天来聊聊 Camera1 实战中最核心的一块——视频录制。
说白了,就是怎么用 MediaRecorder 把摄像头采集的画面,加上麦克风的声音,一起录成一个视频文件。
我记得刚入行那会儿,第一次做视频录制功能,以为就是调几个 API 就完事了。结果呢?录出来的视频要么没声音,要么画面卡顿,甚至直接崩溃。嗯,这里面的坑,我今天一次性给你讲透。
MediaRecorder 的基本工作流程
MediaRecorder 的使用,其实有一套固定的「套路」。你按这个顺序来,基本不会出大问题。
- 创建 MediaRecorder 实例
- 配置音频源和视频源
- 设置输出格式和编码器
- 设置输出文件路径
- 设置预览 Surface(可选)
- 调用 prepare()
- 调用 start() 开始录制
- 调用 stop() 停止录制
- 调用 release() 释放资源
这个流程,我建议你背下来。为什么?因为顺序错了,prepare() 就会抛异常。我在项目中遇到过好几次,同事把 setOutputFile 放在了 setVideoEncoder 之前,结果死活跑不起来。
核心要点:MediaRecorder 的状态机非常严格。你必须按照「配置 → prepare → start → stop → release」的顺序来操作,跳步或乱序都会导致 IllegalStateException。
视频参数设置:别让默认值坑了你
很多人觉得参数设置随便填就行,其实不然。我见过太多人直接用默认值,结果录出来的视频要么分辨率不对,要么帧率低得可怜。
咱们来看看几个关键参数:
| 参数 | 方法 | 说明 | 我的建议 |
|---|---|---|---|
| 视频编码器 | setVideoEncoder() | H.264 / H.265 / VP8 等 | 兼容性最好的是 H.264 |
| 音频编码器 | setAudioEncoder() | AAC / AMR_NB 等 | AAC 音质好,文件小 |
| 输出格式 | setOutputFormat() | MPEG_4 / THREE_GPP 等 | MP4 通用性最强 |
| 视频分辨率 | setVideoSize() | 宽 x 高 | 建议匹配 Camera 预览尺寸 |
| 视频比特率 | setVideoEncodingBitRate() | 单位 bps | 1080p 建议 8-12 Mbps |
| 视频帧率 | setVideoFrameRate() | 单位 fps | 30fps 是主流 |
你想想看,如果你设置的视频分辨率是 1920x1080,但 Camera 预览只支持 1280x720,那 MediaRecorder 会怎么处理?它会自动缩放,但画质会下降。所以我个人习惯是:先查询 Camera 支持的预览尺寸,再设置 MediaRecorder 的视频尺寸,两者保持一致。
小技巧:比特率不是越高越好。比特率太高,文件体积大,而且低端设备可能编码不过来。我一般用这个公式:比特率 = 分辨率宽度 × 分辨率高度 × 帧率 × 0.07。比如 1920x1080@30fps,算出来大约是 4.3 Mbps,实际我会取 8 Mbps,留点余量。
录制状态监听:别让用户干等
录制过程中,用户需要知道当前状态。是正在录制?还是出错了?还是存储空间不够了?
MediaRecorder 提供了几个监听器:
- OnInfoListener:录制过程中的信息回调,比如录制时间达到最大限制、文件大小达到最大限制等。
- OnErrorListener:录制出错时的回调,比如编码器崩溃、存储空间不足等。
我曾经在一个项目中,没有监听 OnErrorListener,结果用户录了半小时,最后发现文件没保存成功。用户直接炸了。从那以后,我每次都会加上错误监听,并且在 UI 上给出明确的提示。
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();
// 通知用户
showToast("录制出错,请重试");
}
});
注意:OnErrorListener 和 OnInfoListener 必须在 prepare() 之前设置,否则可能收不到回调。嗯,这个坑我踩过,当时 debug 了半天才发现是顺序问题。
录制文件保存:路径与命名策略
文件保存看似简单,但里面有不少讲究。
首先,存储路径。我建议使用 getExternalFilesDir() 或者 Environment.getExternalStoragePublicDirectory()。前者是应用私有目录,卸载时自动删除;后者是公共目录,用户可以在文件管理器中看到。
其次,文件命名。我习惯用时间戳 + 随机数的方式,避免重名:
private File createVideoFile() {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
String fileName = "VID_" + timeStamp + "_" + (int)(Math.random() * 1000) + ".mp4";
File storageDir = getExternalFilesDir(Environment.DIRECTORY_MOVIES);
return new File(storageDir, fileName);
}
你可能会问:为什么要加随机数?因为同一秒内可能录制多个视频,纯时间戳会重名。我遇到过用户快速点击录制按钮,结果第二个视频把第一个覆盖了。加上随机数,基本能避免这个问题。
完整代码示例
好了,理论说完了,咱们来看一个完整的录制流程。这个代码我实际项目里用过,稍微简化了一下:
private MediaRecorder mediaRecorder;
private boolean isRecording = false;
private void startRecording() {
if (isRecording) return;
mediaRecorder = new MediaRecorder();
// 1. 设置音频源和视频源
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
// 2. 设置输出格式和编码器
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
// 3. 设置视频参数
mediaRecorder.setVideoSize(1920, 1080);
mediaRecorder.setVideoEncodingBitRate(8 * 1000 * 1000); // 8 Mbps
mediaRecorder.setVideoFrameRate(30);
mediaRecorder.setAudioEncodingBitRate(128 * 1000); // 128 kbps
mediaRecorder.setAudioSamplingRate(44100);
// 4. 设置输出文件
File outputFile = createVideoFile();
mediaRecorder.setOutputFile(outputFile.getAbsolutePath());
// 5. 设置预览 Surface(从 Camera 获取)
mediaRecorder.setPreviewDisplay(mCameraPreview.getHolder().getSurface());
// 6. 设置监听器
mediaRecorder.setOnInfoListener(mInfoListener);
mediaRecorder.setOnErrorListener(mErrorListener);
try {
mediaRecorder.prepare();
mediaRecorder.start();
isRecording = true;
} catch (IOException e) {
e.printStackTrace();
releaseMediaRecorder();
}
}
private void stopRecording() {
if (!isRecording) return;
mediaRecorder.stop();
mediaRecorder.release();
mediaRecorder = null;
isRecording = false;
}
private void releaseMediaRecorder() {
if (mediaRecorder != null) {
mediaRecorder.release();
mediaRecorder = null;
}
isRecording = false;
}
这段代码看起来不长,但每一行都有它的意义。你想想看,如果漏掉了 setPreviewDisplay(),录制时预览画面会黑屏。如果漏掉了 setAudioSource(),录出来的视频就没有声音。
知识体系总览
为了让你更直观地理解 MediaRecorder 的整个工作流程,我画了一张图:
这张图把整个流程分成了四个阶段。你按这个顺序来,基本不会出错。我个人习惯是把这些步骤封装成一个工具类,这样在多个页面复用时,代码不会散落得到处都是。
最后提醒一句:别忘了在 AndroidManifest.xml 中声明 RECORD_AUDIO 和 CAMERA 权限,并且在运行时动态申请。嗯,这个我刚开始也经常忘,录出来的视频没声音才想起来。
好了,MediaRecorder 实战的核心内容就这些。你把这些吃透了,视频录制功能基本就能稳稳拿下了。