一文搞懂 Android 音视频编解码器 MediaCodec

在 Android 平台上,我们经常需要处理音视频数据,比如播放视频、录制音频等。为了高效处理这些数据,Android 提供了 MediaCodec 类,它允许我们对音视频进行编解码操作。

什么是 MediaCodec?

MediaCodec 是 Android 提供的一个音视频编解码器,它允许应用程序对音频和视频数据进行编码(压缩)和解码(解压缩)。通过 MediaCodec,我们可以实现音视频的播放、录制、转码等功能。

下面以编码器为例子进行说明,如下图所示,MediaCodec存在两个缓冲区,输入缓冲区(input)和输出缓冲区(output)。编码视频流时即不断从输入缓冲区中读取原始YUV数据,经过Codec编码后,从输出缓冲区获取压缩后的H264数据。

MediaCodec用法

1. 创建 MediaCodec 实例

首先,我们需要创建一个 MediaCodec 实例。对于视频编码,我们可以使用如下代码:

ini 复制代码
MediaCodec codec = MediaCodec.createEncoderByType("video/avc");

2. 配置 MediaCodec

接下来,我们需要配置 MediaCodec。对于编码器,我们需要设置输入数据的格式和输出数据的格式:

scss 复制代码
MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height); // 设置宽高
format.setInteger(MediaFormat.KEY_BIT_RATE, 500000); // 设置码率
format.setInteger(MediaFormat.KEY_FRAME_RATE, 15); // 设置帧率
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 60); // 设置关键帧间隔
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); // 设置颜色格式
format.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR); // 设置码率控制模式
format.setInteger(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline); // 设置profile
format.setInteger(MediaFormat.KEY_LEVEL, MediaCodecInfo.CodecProfileLevel.AVCLevel31); // 设置level
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

Android支持三种码控模式:

  1. BITRATE_MODE_CQ:0,不控制码率,尽最大可能保证图像质量
  2. BITRATE_MODE_VBR:1,可变码率,根据场景的变化幅度和复杂度动态调整输出数据的比特率
  3. BITRATE_MODE_CBR:2,固定码率,输出数据的比特率尽量保持在设定值

Profile支持三种等级:

  1. Baseline: 支持 I/P 帧,只支持无交错和 CAVLC,一般用于低阶或需要额外容错的应用,比如视频通话、手机视频等
  2. Main :提供 I/P/B 帧,支持无交错和交错,同样提供对于 CAVLC 和 CABAC 的支持,用于主流消费类电子产品规格如低解码的 mp4、便携的视频播放器、PSP 和 Ipod 等
  3. High :在 Main 的基础上增加了 8x8 内部预测、自定义量化、无损视频编码和更多的 YUV 格式(如 4:4:4),用于广播及视频碟片 存储(蓝光影片),高清电视的应用

Level等级确认:

以360*640为例 水平宏块数 = ceil(视频宽度/16) = ceil(640/16) = ceil(40.0) = 40 垂直宏块数 = ceil(视频高度/16) = ceil(360/16) = ceil(22.5) = 23 每帧宏块数 = 水平宏块数 * 垂直宏块数 = 40 * 23 = 920 查表可知支持每帧宏块数920的最低级别是2.2。级别2.2所允许的每秒最大宏块数是20250。20250 / 920 = 22,即最高支持每秒22帧。 注:ceil(x)是向上舍入函数,返回的是大于等于x的最小整数。

3. 处理输入数据

一旦配置完成,我们就可以开始向编码器输入数据或从解码器接收数据了。对于编码器,我们使用 codec.queueInputBuffer() 方法向编码器输入原始数据:

ini 复制代码
int inputBufferIndex = codec.dequeueInputBuffer(timeout);
if (inputBufferIndex >= 0) {
    ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferIndex);
    inputBuffer.put(data);
    codec.queueInputBuffer(inputBufferIndex, 0, data.length, presentationTimeUs, 0);
}

4. 处理输出数据

最后,需要处理输出的数据。对于编码器,我们使用 codec.dequeueOutputBuffer() 方法获取编码后的数据:

ini 复制代码
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, timeout);
if (outputBufferIndex >= 0) {
    ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferIndex);
    // 处理编码后的数据
    codec.releaseOutputBuffer(outputBufferIndex, false);
}

踩过的坑

  1. Android机型数据必须以16位对齐: 摄像头采集360x640的图像实际上获取到的是384x640,采集180x320的图像获取到的是192x320,这就会导致编码图像绿屏的情况。因此数据在传入编码器之前需要先进行裁剪。
  2. 部分机型编码画面出现大量马赛克: presentationTimeUs需设定为实际时间戳,时间戳用于指示音视频数据的播放或处理时间,错误的时间戳会导致输出码率与设定值不符的情况。可以使用System.nanoTime()获取纳秒级别的系统时间。
  3. 部分鸿蒙机型编码出现绿条: 通常情况下Android机型数据都是以16位对齐,但鸿蒙海思芯片不支持,需要单独适配。可以创建指定编码器OMX.google.h264.encoder,或采用NV12编码
相关推荐
dvlinker1 天前
【音视频开发】使用支持硬件加速的D3D11绘图遇到的绘图失败与绘图崩溃问题的记录与总结
音视频开发·c/c++·视频播放·d3d11·d3d11绘图模式
音视频牛哥6 天前
Android平台GB28181实时回传流程和技术实现
音视频开发·视频编码·直播
音视频牛哥8 天前
RTMP、RTSP直播播放器的低延迟设计探讨
音视频开发·视频编码·直播
音视频牛哥12 天前
电脑共享同屏的几种方法分享
音视频开发·视频编码·直播
x007xyz2 个月前
前端纯手工绘制音频波形图
前端·音视频开发·canvas
音视频牛哥2 个月前
Android摄像头采集选Camera1还是Camera2?
音视频开发·视频编码·直播
九酒2 个月前
【harmonyOS NEXT 下的前端开发者】WAV音频编码实现
前端·harmonyos·音视频开发
音视频牛哥2 个月前
结合GB/T28181规范探讨Android平台设备接入模块心跳实现
音视频开发·视频编码·直播
哔哩哔哩技术2 个月前
自研点直播转码核心
音视频开发