Android MediaCodec硬编码H264文件(四),真香定律

  • 码率

  • 码率控制模式

  • 帧率

  • I 帧间隔

其中,码率就是指单位传输时间传送的数据位数,一般用 kbps 即千位每秒来表示。而帧率就是指每秒显示的帧数。

其实对于码率有三种模式可以控制:

  • BITRATE_MODE_CQ

  • 表示不控制码率,尽最大可能保证图像质量

  • BITRATE_MODE_VBR

  • 表示 MediaCodec 会根据图像内容的复杂度来动态调整输出码率,图像负责则码率高,图像简单则码率低

  • BITRATE_MODE_CBR

  • 表示 MediaCodec 会把输出的码率控制为设定的大小

对于颜色格式,由于是将 YUV 数据编码成 H264,而 YUV 格式又有很多,这又涉及到机型兼容性问题。在对相机编码时要做好格式的处理,比如相机使用的是 NV21 格式,MediaFormat 使用的是 COLOR_FormatYUV420SemiPlanar,也就是 NV12 模式,那么就得做一个转换,把 NV21 转换到 NV12 。

对于 I 帧间隔,也就是隔多久出现一个 H264 编码中的 I 帧。

完整 MediaFormat 设置示例:

MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);

mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);

// 马率

mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 5);

// 调整码率的控流模式

mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);

// 设置帧率

mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);

// 设置 I 帧间隔

mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);

当开始编解码操作时,开启编解码线程,处理相机预览返回的 YUV 数据。

在这里用到了相机的一个封装库:

https://github.com/glumes/EzCameraKit

编解码操作

编解码操作代码如下:

while (isEncoding) {

// YUV 颜色格式转换

if (!mEncodeDataQueue.isEmpty()) {

input = mEncodeDataQueue.poll();

byte[] yuv420sp = newbyte[mWidth * mHeight * 3 / 2];

NV21ToNV12(input, yuv420sp, mWidth, mHeight);

input = yuv420sp;

}

if (input != null) {

try {

// 从输入缓冲区队列中拿到可用缓冲区,填充数据,再入队

ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();

ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers();

int inputBufferIndex = mMediaCodec.dequeueInputBuffer(-1);

if (inputBufferIndex >= 0) {

// 计算时间戳

pts = computePresentationTime(generateIndex);

ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];

inputBuffer.clear();

inputBuffer.put(input);

mMediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, pts, 0);

generateIndex += 1;

}

MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();

int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);

// 从输出缓冲区队列中拿到编码好的内容,对内容进行相应处理后在释放

while (outputBufferIndex >= 0) {

ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];

byte[] outData = newbyte[bufferInfo.size];

outputBuffer.get(outData);

// flags 利用位操作,定义的 flag 都是 2 的倍数

if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { // 配置相关的内容,也就是 SPS,PPS

mOutputStream.write(outData, 0, outData.length);

} elseif ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) { // 关键帧

mOutputStream.write(outData, 0, outData.length);

} else {

// 非关键帧和SPS、PPS,直接写入文件,可能是B帧或者P帧

mOutputStream.write(outData, 0, outData.length);

}

mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);

outputBufferIndex = mMediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);

}

} catch (IOException e) {

Log.e(TAG, e.getMessage());

}

} else {

try {

Thread.sleep(500);

} catch (InterruptedException e) {

Log.e(TAG, e.getMessage());

}

}

}

首先,要把要把相机的 NV21 格式转换成 NV12 格式,然后 通过 dequeueInputBuffer 方法去从可用的输入缓冲区队列中出队取出缓冲区,填充完数据后再通过 queueInputBuffer 方法入队。

dequeueInputBuffer 返回缓冲区索引,如果索引小于 0 ,则表示当前没有可用的缓冲区。它的参数 timeoutUs 表示超时时间 ,毕竟用的是 MediaCodec 的同步模式,如果没有可用缓冲区,就会阻塞指定参数时间,如果参数为负数,则会一直阻塞下去。

queueInputBuffer 方法将数据入队时,除了要传递出队时的索引值,然后还需要传入当前缓冲区的时间戳 presentationTimeUs 和当前缓冲区的一个标识 flag 。

其中,时间戳通常是缓冲区渲染的时间,而标识则有多种标识,标识当前缓冲区属于那种类型:

  • BUFFER_FLAG_CODEC_CONFIG

  • 标识当前缓冲区携带的是编解码器的初始化信息,并不是媒体数据

  • BUFFER_FLAG_END_OF_STREAM

  • 结束标识,当前缓冲区是最后一个了,到了流的末尾

最后

文章所有资料全部已经打包整理好,免费分享给有需要的人,另外小编手头上整理了大量Android架构师全套学习资料,Android核心高级技术PDF文档+全套高级学习资料+视频+2021 BAT 大厂面试真题解析,都是免费分享给大家的,全部都已整理在GitHub上,有需要的朋友可以点击前往领取更多。

资料展示:

image

image

image

image

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

957103321)]
[外链图片转存中…(img-ZIO8ZEQS-1710957103321)]
[外链图片转存中…(img-mAIDdtZV-1710957103322)]
[外链图片转存中…(img-2c4DCon8-1710957103322)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-aMYfJhf2-1710957103322)]

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FFmpeg是一个开源的跨平台音视频处理工具库,其中包括了多种编解码器,包括了支持硬编码的编解码器。而MediaCodecAndroid SDK提供的一个用于音视频编解码的API,也支持硬编码。可以通过FFmpeg调用MediaCodec实现Android平台上的硬编码。 在使用FFmpeg进行硬编码时,需要将FFmpeg配置为支持MediaCodec。具体操作步骤如下: 1. 配置FFmpeg FFmpeg需要在编译时开启MediaCodec的支持。可以使用以下命令进行编译: ``` ./configure --enable-mediacodec ``` 2. 设置编码器 使用FFmpeg进行硬编码时,需要设置编码器。可以使用以下命令进行设置: ``` AVCodec *codec = avcodec_find_encoder_by_name("h264_mediacodec"); ``` 其中,h264_mediacodec是FFmpeg支持的硬编码器之一,也可以使用其他支持的硬编码器。 3. 配置编码器参数 设置好编码器后,需要对编码进行参数配置。可以使用以下命令进行配置: ``` AVCodecContext *codecCtx = avcodec_alloc_context3(codec); codecCtx->codec_id = codec->id; codecCtx->codec_type = AVMEDIA_TYPE_VIDEO; codecCtx->bit_rate = bit_rate; codecCtx->width = width; codecCtx->height = height; codecCtx->time_base = (AVRational){1, fps}; codecCtx->framerate = (AVRational){fps, 1}; codecCtx->gop_size = gop_size; ``` 其中,bit_rate为比特率,width和height为视频宽度和高度,fps为帧率,gop_size为关键帧间隔。 4. 初始化编码器 参数配置完成后,需要对编码进行初始化。可以使用以下命令进行初始化: ``` avcodec_open2(codecCtx, codec, NULL); ``` 5. 编码数据 编码器初始化完成后,可以对数据进行编码。可以使用以下命令进行编码: ``` AVPacket *pkt = av_packet_alloc(); AVFrame *frame = av_frame_alloc(); av_frame_get_buffer(frame, 0); // 填充frame数据 avcodec_send_frame(codecCtx, frame); while (avcodec_receive_packet(codecCtx, pkt) == 0) { // 处理编码后的数据 } ``` 其中,av_frame_get_buffer用于分配一块空间存储数据,avcodec_send_frame用于将数据送入编码进行编码,avcodec_receive_packet用于从编码器中获取编码后的数据。 6. 释放资源 编码完成后,需要释放资源。可以使用以下命令进行释放: ``` av_packet_free(&pkt); av_frame_free(&frame); avcodec_free_context(&codecCtx); ``` 以上就是在Android平台上使用FFmpeg进行硬编码的基本步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值