Android音视频——MediaCodec编码mp4踩坑记录,2024Android大厂面试经验

欢迎关注微信公众号:FSA全栈行动 👋

项目需要在低端 Android 设备上驱动相机获取 YUV 图像,同时,还需要进行录像,YUV 图像的获取与处理之前已经趟过去了,总体感觉只要掌握了相机与 YUV 原理等知识点后,结合 libyuv 这个牛逼的库基本就没什么了,而录像这一块则是使用 MediaCodec + MediaMuxer 来处理,本篇就是我在使用原生 MediaCodec 编码 mp4 文件的踩杭记要,主要有两个问题:

  • 录像变色 (video wrong color)
  • 录像时长缩水(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 模式,则核心代码实现如下:

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

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

尾声

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

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

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

Android进阶学习资料库

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

大厂面试真题

PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

《2019-2021字节跳动Android面试历年真题解析》

常见算法题汇总。)

[外链图片转存中…(img-HtGszsbk-1711742439276)]

《2019-2021字节跳动Android面试历年真题解析》

[外链图片转存中…(img-PrBaclmY-1711742439276)]

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值