稀有猿诉

十年磨一剑,历炼出锋芒,说话千百句,不如码二行。

Camera2之录像

前面的文章都是集中在拍照模式,对于相机来说拍照与录像是两个最为基础的功能,这篇文章来看一下使用Camera2如何实现一个简单的录像功能。

录像原理和流程

录像相较于拍照来说,业务逻辑要稍简单一些,因为大部分功能的实现要靠MediaRecorder。Camera2的API界线划分比较清楚,camera只负责输出图像帧,至于如何编码成为视频则是多媒体部分(也即MediaRecorder)的事情。

所以,实现录像功能,需要提供一个MediaRecorder,把其Surface作为camera的目标输出,同时作为MediaRecorder的输入,这就建立了它们的连接。

录像的主要流程

有以下几个关键的步骤需要做:

1. 选择要使用的分辨率

从CameraAgent中读取支持的分辨率。简单起见,可以用全高清来当默认值width=1920,height=1080,fps=30。

2. 准备目标Surface

可以用MediaCodec来创建一个Surface,用createPersistentInputSurface)方法,这个会传给CameraAgent用作配置session。

3. 准备MediaRecorder

创建实例,用步骤1作为video配置,步骤2 的Surface当作input surface,然后还要prepare,否则一些资源不会生效如Surface的buffer。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
private void setupMediaRecorder(VideoOutputConfig mode) {
        recorderSurface = MediaCodec.createPersistentInputSurface();

        mediaRecorder = createRecorder(mode.width, mode.height, mode.fps);
        try {
            mediaRecorder.prepare();
        } catch (IOException e) {
            Log.w(LOG_TAG, "failed to prepare MediaRecorder: " + e.getMessage());
        }
    }
}

private MediaRecorder createRecorder(int width, int height, int fps) {
    MediaRecorder recorder;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        recorder = new MediaRecorder(context);
    } else {
        recorder = new MediaRecorder();
    }

    recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    recorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
    recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
    recorder.setOutputFile(emptyVideoFile(context));
    recorder.setVideoEncodingBitRate(1_0_000_000);
    recorder.setVideoSize(width, height);
    recorder.setVideoFrameRate(fps);
    recorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
    recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
    recorder.setInputSurface(recorderSurface);

    recorder.setOnErrorListener(this);
    recorder.setOnErrorListener(this);
    return recorder;
}

到此,Surface和MediaRecorder就处于ready状态了

4. 使用常规view finder的Surface和第2步的Surface来启动预览

就是启动常规的预览即可:

1
cameraAgent.startPreview(Arrays.asList(previewSurface, recorderSurface));

5. 现在就处于Ready状态了,可以随时录像

6. 调用MediaRecorder#start/stop/pause/resume来录像

录像的分辨率选择

尺寸比例可以固定在16:9,因为这是较为通用的比例尺寸:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public List<VideoOutputConfig> calculateVideoRecordMode() {
    StreamConfigurationMap configurationMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    List<Size> supportedSizes = Arrays.asList(configurationMap.getOutputSizes(MediaRecorder.class));
    return supportedSizes.stream()
            .filter(size -> {
                final float ratio = (float) size.getWidth() / (float) size.getHeight();
                return Math.abs(ratio - Config.PictureRatio.WIDE.ratio) <= CameraSize.EPS;
            })
            .map(size -> {
                float rfps = (float) configurationMap.getOutputMinFrameDuration(MediaRecorder.class, size) / 1_000_000_000.0f;
                int fps = rfps > 0f ? (int) (1.0f / rfps) : 0;
                return new VideoOutputConfig(size.getWidth(), size.getHeight(), fps);
            }).filter(vrm -> vrm.fps > 0)
            .collect(Collectors.toList());
}

录像之闪光灯

闪光灯主要是用于拍照,但录像也是支持闪光灯的,当然还是要看硬件的配置,如果characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE)为true,那就可以在录像中使用闪光灯,不过一般情况下,只有OFF和TORCH两种模式,因为录像是一个持续的输出帧的过程,其余的模式没有意义。

开始录像之后的流程

录像准备妥当后,准备目标文件,监听用户事件,按下快门后开始录像,再次点击后结束录像,然后释放MediaRecorder,最后把文件写入媒体数据库。

Comments