Camera2、MediaCodec录制mp4

躬行之

共 4273字,需浏览 9分钟

 · 2021-01-18

PS:做想做的事,每天至少做一点,不求做多少,慢慢改变。
了解了音视频的相关知识,可以先阅读下面两篇文章:
本文的主要内容是通过 Android 原生的硬编解码框架 MediaCodec 和复用器 MediaMuxer 实现 mp4 视频文件的录制,视频数据源由 Camera2 来提供,这里重点是编码、复用的这个过程而不是 mp4 的录制,如果仅仅是视频录制,可以选择更方便的 MediaRecorder,按照惯例还是以案列的形式学习 MediaCodec,其更多用法将在后续的文章中介绍,本文主要内容如下:
  1. Camera2的使用

  2. MediaCodec输入方式

  3. MediaCodec编码Camera2数据

  4. 录制流程

  5. 录制效果

Camera2的使用

Camera2 是从 Android 5.0 开始推出的新的相机 API,最新的是 CameraX,CameraX 基于 Camera2,相较 Camera2 提供了更好用的 API,后文中涉及到的相关的 API 可以直接参考下面这张示意图,这也是 Camera2 的使用示意图,如下:
来自 smewise
具体使用可查看文末源码。

MediaCodec的输入方式

为了能够使用 MediaCodec 进行编码操作,就需要将相机的数据输入到编码器 MediaCodec 中,可以通过两种方式将数据写入 MediaCodec,具体如下:
  • Surface:使用 Surface 作为编码器 MediaCodec 的输入,即将 MediaCodec 创建的 Surface 作为其输入,这个 Surface 由 MediaCodec 的 createInputSurface 方法创建,当相机将会将有效数据渲染到该 Surface 中,MediaCodec 就可以直接输出编码后的数据了。

  • InputBuffer:使用输入缓冲区作为编码器 MediaCodec 的输入,这里需要填充的数据就是原始帧数据,对应 Camera2 来说可直接通过 ImageReader 来进行帧数据的获取,获取到的 Image 包含了宽度、高度、格式、时间戳及 YUV 数据分量等信息,可控程度更高。

MediaCodec编码Camera2数据

简单说一下 MediaCodec 的数据处理方式,Android 5.0 之前只支持 ByteBuffer[] 的同步方式,之后推荐使用 ByteBuffer 的同步、异步方式,这里使用 ByteBuffer 的同步方式,涉及的流程主要是视频数据编码和复用,因为前面提到 MediaCodec 的输入是通过 Surface 完成的,所以这里只需要获取的已经编码好的数据和使用复用器 MediaMuxer 来实现 Mp4 文件的生成,关键代码如下:
1// 返回已成功编码的输出缓冲区的索引
2var outputBufferId: Int = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0)
3if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
4    // 添加视频轨道
5    mTrackIndex = mMediaMuxer.addTrack(mMediaCodec.outputFormat)
6    mMediaMuxer.start()
7    mStartMuxer = true
8else {
9    while (outputBufferId >= 0) {
10        if (!mStartMuxer) {
11            Log.i(TAG, "MediaMuxer not start")
12            continue
13        }
14        // 获取有效数据
15        val outputBuffer = mMediaCodec.getOutputBuffer(outputBufferId) ?: continue
16        outputBuffer.position(bufferInfo.offset)
17        outputBuffer.limit(bufferInfo.offset + bufferInfo.size)
18        if (pts == 0L) {
19            pts = bufferInfo.presentationTimeUs
20        }
21        bufferInfo.presentationTimeUs = bufferInfo.presentationTimeUs - pts
22        // 将数据写入复用器以生成文件
23        mMediaMuxer.writeSampleData(mTrackIndex, outputBuffer, bufferInfo)
24        Log.d(
25            TAG,
26            "pts = ${bufferInfo.presentationTimeUs / 1000000.0f} s ,${pts / 1000} ms"
27        )
28        mMediaCodec.releaseOutputBuffer(outputBufferId, false)
29        outputBufferId = mMediaCodec.dequeueOutputBuffer(bufferInfo, 0)
30    }
31}

录制流程

这里使用 Surface 作为编码器 MediaCodec 的输入,在 MediaCodec 进入配置状态才可以创建 Surface,也就是 createInputSurface 只能在 configure 与 start之间调用,参考如下:
1// 配置状态
2mMediaCodec.configure(mediaFormat, nullnull, MediaCodec.CONFIGURE_FLAG_ENCODE)
3// 创建Surface作为MediaCodec的输入,createInputSurface只能在configure与start之间调用创建Surface
4mSurface = mMediaCodec.createInputSurface()
5// start ...
将其添加到 SessionConfiguration 的输出 Surface 列表中,参考如下:
1// 创建CaptureSession
2@RequiresApi(Build.VERSION_CODES.P)
3private suspend fun createCaptureSession(): CameraCaptureSession = suspendCoroutine { cont ->
4    val outputs = mutableListOf()
5    // 预览Surface                                                                              
6    outputs.add(OutputConfiguration(mSurface))
7    // 添加MediaCodec用作输入的Surface                                                                               
8    outputs.add(OutputConfiguration(EncodeManager.getSurface()))
9    val sessionConfiguration = SessionConfiguration(
10        SessionConfiguration.SESSION_REGULAR,
11        outputs, mExecutor, ...)
12    mCameraDevice.createCaptureSession(sessionConfiguration)
13}
然后发起 CaptureRequest 开启预览和接收 Surface 输出,同时开启编码,参考如下:
1// 添加预览的Surface和生成Image的Surface
2mCaptureRequestBuild = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
3val sur = EncodeManager.getSurface()
4mCaptureRequestBuild.addTarget(sur)
5mCaptureRequestBuild.addTarget(mSurface)
6
7// 设置各种参数
8mCaptureRequestBuild.set(CaptureRequest.CONTROL_VIDEO_STABILIZATION_MODE, 1// 视频稳定功能是否激活
9// 发送CaptureRequest
10mCameraCaptureSession.setRepeatingRequest(
11    mCaptureRequestBuild.build(),
12    null,
13    mCameraHandler
14)
15// 开始编码
16EncodeManager.startEncode()

录制效果

可在公众号后台回复关键字【record】获取源码。
推荐阅读:
浏览 25
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报