2024年最全Android音视频——MediaCodec编码mp4踩坑记录,关于Flutter文本组件Widget的全面解读

最后

写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。

image

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 录像时长缩水(play too fast)

注:低端的 Android 设备硬件条件有多差呢?大概就是 2014 年 Android4.x 手机那种水平吧,CPU 处理速度很感人,对此,唯有硬编码才是王道。

一、录像变色

在探究该问题前,先来了解一下 MediaCodec 的两种编码模式:

  • ByteBuffer 模式(手动档):
  • 格式:COLOR_FORMAT 对应的值是 MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar(图像格式 NV21)。
  • 操作:通过 MediaCodec.dequeueInputBuffer() 获取数据输入缓冲区,再通过 MediaCodec.queueInputBuffer() 手动将 YUV 图像传给 MediaCodec
  • Surface 模式(自动档):
  • 格式:COLOR_FORMAT 对应的值是 MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
  • 操作:通过 MediaCodec.createInputSurface() 创建编码数据源 Surface,再通过 OpenGL 纹理,将相机预览图像绘制到该 Surface 上。

1、现象

相机预览正常,但是录制出来的 mp4 视频颜色很阴间。

说明:就跟 YUV 图像把 u/v 颠倒之后的效果一样。

2、分析

ByteBuffer 模式 下,从相机处获取到原始的 NV21 图像,交给设置了 COLOR_FORMATCOLOR_FormatYUV420SemiPlanarMediaCodec,结果在不同的 Android 设备上,有的正常,有的不正常(少数),刚开始以为是个别设备上不支持这种 COLOR_FORMAT,但事实并非如此。stackoverflow 某个歪果仁对此问题的解释如下:

The YUV formats used by Camera output and MediaCodec input have their U/V planes swapped.

If you are able to move the data through a Surface you can avoid this issue; however, you lose the ability to examine the YUV data. An example of recording to a .mp4 file from a camera can be found on bigflake.

Some details about the colorspaces and how to swap them is in this answer.

注:stackoverflow 文章链接:stackoverflow.com/questions/1…

所以说,这是 MediaCodec 本身的 bug,它自己会对输入的 YUV 图像的 u/v 进行交换,解决的方案有 2 种:

  • 使用 ByteBuffer 模式,在把 NV21 图像传给 MediaCodec 之前,先把 NV21 转成 NV12(毕竟这俩货仅仅只是 u/v 相反而已),但前面已经提到了,只是少数设备会有这种情况,适配起来估计有够呛的。不推荐
  • 使用 Surface 模式,可以完美避免这种情况,但同时会丧失对原 YUV 图像的处理能力,不过可以使用 OpenGL 方式来处理图像。推荐

3、实现

大致步骤如下:

  • 一方面,使用 OpenGL 纹理创建纹理,并包装为 SurfaceTexture 给相机作为 preview 窗口,这样相机图像就会呈现在纹理上。
  • 另一方面,使用 mMediaCodec.createInputSurface() 作为 MediaCodec 的编码数据源。
  • 最后,在相机预览的同时,让纹理上的图像绘制到 inputSurface

说明:Camera —> TextureId(OpenGL) —> InputSurface(MediaCodec)

具体实现可以在 bigflake 的 Demo(CameraToMpegTest) 中获取:www.bigflake.com/mediacodec/…

二、录像时长缩水(丢帧)

解决该问题有两个关键:

  • 时间戳对齐:
  • ByteBuffer 模式:通过 MediaCodec.queueInputBuffer() 手动将 YUV 图像传给 MediaCodec 的同时,需要传递当前的时间戳,注意时间单位是微秒(us)。
  • Surface 模式:通过 MediaCodec.createInputSurface() 创建出来的 inputSurface,会有与之对比的 mEGLDisplay、mEGLSurface,在执行 EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface) 之前,通过 EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs) 对 MediaCodec 的 inputSurface 的数据设置时间戳。
  • 媒体格式配置:
  • MediaFormat 的关键帧间隔(KEY_I_FRAME_INTERVAL) 与 帧率(KEY_FRAME_RATE) 必须配置得当。

说明:这里是核心总结,可先跳过往下看,之后再回过头来看,会比较好理解。

1、现象

录制一段 10s 的视频,从设备上提取出来后,使用播放器播放观察。发现有的设备正常,个别设备录制出来的视频,时长仅仅只有一半,这也就是网上都在说的 play too fast 问题。

安利:OnlyStopWatch_x64.exe 这是一个计时器小工具,对于视频录制、直播这种需要观察时间快慢的场景很实用。画面丢失、播放太快等问题,都很容易看出来。

2、分析

前面提到的 stackoverflow 问答中,那个歪果仁同时也表达了他对 使用MediaCodec录制出来的视频播放太快 这个问题的解释:

There is no timestamp information in the raw H.264 elementary stream. You need to pass the timestamp through the decoder to MediaMuxer or whatever you’re using to create your final output. If you don’t, the player will just pick a rate, or possibly play the frames as fast as it can.

注:stackoverflow 文章链接:stackoverflow.com/questions/1…

他认为 H.264 不包含时间戳信息,你需要把时间戳通过编码器(MediaCodec)给到媒体复用器(MediaMuxer),否则,播放器会选择一个速率,尽快地播放帧。

3、实现

如果是 ByteBuffer 模式,则核心代码实现如下:

private void feedMediaCodecData(byte[] data) {
if (!isEncoderStart)
return;
int bufferIndex = -1;
try {
bufferIndex = mMediaCodec.dequeueInputBuffer(0);
} catch (IllegalStateException e) {
e.printStackTrace();
}
if (bufferIndex >= 0) {
ByteBuffer buffer = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
try {
buffer = mMediaCodec.getInputBuffer(bufferIndex);
} catch (Exception e) {
e.printStackTrace();
}
} else {
if (inputBuffers != null) {
buffer = inputBuffers[bufferIndex];
}
}
if (buffer != null) {
buffer.clear();

尾声

最后,我再重复一次,如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。

Android进阶学习资料库

一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618156601)**

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 13
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值