MediaRecorder 实战:视频录制与参数设置

好,咱们今天来聊聊 Camera1 实战中最核心的一块——视频录制。

说白了,就是怎么用 MediaRecorder 把摄像头采集的画面,加上麦克风的声音,一起录成一个视频文件。

我记得刚入行那会儿,第一次做视频录制功能,以为就是调几个 API 就完事了。结果呢?录出来的视频要么没声音,要么画面卡顿,甚至直接崩溃。嗯,这里面的坑,我今天一次性给你讲透。

MediaRecorder 的基本工作流程

MediaRecorder 的使用,其实有一套固定的「套路」。你按这个顺序来,基本不会出大问题。

  1. 创建 MediaRecorder 实例
  2. 配置音频源和视频源
  3. 设置输出格式和编码器
  4. 设置输出文件路径
  5. 设置预览 Surface(可选)
  6. 调用 prepare()
  7. 调用 start() 开始录制
  8. 调用 stop() 停止录制
  9. 调用 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 的整个工作流程,我画了一张图:

MediaRecorder 视频录制核心流程 阶段一:配置参数 设置音频源 · 视频源 · 输出格式 · 编码器 · 分辨率 · 比特率 · 帧率 设置输出文件路径 · 设置预览 Surface · 注册监听器 prepare() 阶段三:录制中 start() → 持续采集音视频数据 → 编码 → 写入文件 监听 OnInfo / OnError 回调,处理异常情况 stop() → release() 状态机严格 顺序不可乱 监听器必须在 prepare 前注册

这张图把整个流程分成了四个阶段。你按这个顺序来,基本不会出错。我个人习惯是把这些步骤封装成一个工具类,这样在多个页面复用时,代码不会散落得到处都是。

最后提醒一句:别忘了在 AndroidManifest.xml 中声明 RECORD_AUDIOCAMERA 权限,并且在运行时动态申请。嗯,这个我刚开始也经常忘,录出来的视频没声音才想起来。

好了,MediaRecorder 实战的核心内容就这些。你把这些吃透了,视频录制功能基本就能稳稳拿下了。

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