关于编解码,FFMpeg不香吗,为什么要吊死在Android的MediaCodec上?对于这个问题,我也很无奈,FFMpeg很香,但是因为包体积、效率等问题引发的工作业务的需要,使我不得不在Android MediaCodec的摧残下苟且偷生。MediaCodec的api比较简单,用来写demo毫无难度,让人痛不欲生的是它的兼容性问题。使用MediaCodec遇到的问题,往往都是和机型、版本、某类媒体文件相关的问题。从开始使用MediaCodec到现在,遇到了许多问题,很多解决了后过了许久又忘记了,这篇博客作为一个记录,也为后来不得不趟坑的人提供一点点经验吧。

编码器或者解码器Config时候崩溃

这个问题对于MediaCodec的使用者来说应该是一个比较普遍的问题了,在MediaCodec进行config崩溃,往往会给出具体的崩溃信息,比较常见的是格式不支持,最多的是宽高不支持或者编解码器数量限制。

一个手机能够解码或者编码视频能力是有限的,在手机的/etc/media_codec.xml中是有说明的,比如使用一个15年左右的低端机型去解码1080p的视频,大概率会失败。当然,时代发展,限制的低端机型也大多支持1080p,那不妨解码4k视频试试。

另外,在现在的一些能够支持1080p解码的机型上,使用MediaCodec同时解码两路1080p视频也会出现config失败的问题,比如SM-J250f、SM-J700f等手机。这种情况,就只能避免这么使用了,引入软解,或者把1080p视频先转成小视频等。

解码视频,渲染出来的图像异常

这个一般是新手可能会遇到的问题,不是MediaCodec的问题,而是使用者的问题。有时候为了加速解码或者是其他的原因,使用者在进行解码的时候,会对一些帧进行丢弃,但是如果丢弃后,解码帧不是从关键帧开始的话,到下一个关键帧之前,解码出来的图像,都会是花屏的图像。所以需要做的就是要保证每次解码的流程开始时,塞入给解码的数据要从关键帧开始,并且不要随意丢输入帧。否则不会只有MediaCodec解码会花屏,FFMpeg同样无解。

在使用MediaCodec解码到ByteBuffer中,然后拿出来渲染,没有丢帧也有可能会遇到这个问题。这种情况下一般也不是解码器的问题,而是渲染的问题,比如stride和width不匹配,在渲染的时候需要对数据进行处理。或者是颜色空间不正确,导致了渲染的色彩不正确等问题。

解码Mp4中的音频,seek到0后解码,解码出的音频数据不是从头开始的

遇到了使用MediaCodec进行Mp4音频解码,MediaExtractor已经选中音频轨道,并且seek到0后,播放出来的音频并不是从头开始的问题。并且同样的视频,在有的手机上是好的,有的手机上又有问题,不仅仅是使用MediaCodec,使用MediaPlayer直接来播放也会遇到同样的问题。调试后发现,解码出来的数据前面有很多帧给出来的pts是对的,但是数据的size一直为0,而塞入的数据又的确没啥问题。

在有问题的手机上,使用另外一个不存在问题的视频,和存在问题的视频对比了下,解码器解码时候的MediaFormat,发现有问题的视频带有"encoder-delay"标记,值为1,而正常的视频是没有的。google了下发现罪魁祸首果然是它,Android P此问题存在,Android Q版本进行了修复。本地解决方案将"encoder-delay"标记值设置为0即可修复音频不是从头开始的问题。

音频解码无数据

三星J200G解码mp4中的音频,格式为mp4a-latm时遇到的偶现的问题。MediaCodec未返回错误,dequeueInputBuffer返回值正常,可以请求到buffer,但是dequeueOutputBuffer一直无法获得输出。log中有error信息:

4-18 12:27:47.926 32443-2244/com.uc.vmate E/ACodec: OMXCodec::onEvent, OMX_ErrorStreamCorrupt04-18 12:27:47.931 2175-2266/? E/SEC_AAC_DEC: saacd_decode() failed ret_val: -5, Indata 0x 21 1b 94 a5, length : 132604-18 12:27:47.931 2175-2266/? E/SEC_AAC_DEC: ASI 0x 11, 90 00 0004-18 12:27:47.931 32443-2244/com.uc.vmate E/ACodec: OMXCodec::onEvent, OMX_ErrorStreamCorrupt04-18 12:27:47.936 2175-2266/? E/SEC_AAC_DEC: saacd_decode() failed ret_val: -5, Indata 0x 21 1b 94 bd, length : 1314

最后发现问题不是出在解码器上,而是出在MediaExtractor上,MediaExtractor使用的时候,内部可能出错,导致MediaExtractor解封装的时候给的数据存在异常。

视频解码时,获取输入输出Buffer始终为-1

在Oppo A37wf 5.1.1系统的手机上进行视频解码的时候,遇到了一个问题。同时解码两路1080p视频,结果一路正常,一路解码出一帧后,dequeueInputBuffer和dequeueOutputBuffer始终返回-1,无报错。而看grafika是可以同时解码的。对比了下我的使用和grafika中的示例,发现最终的原因是我在使用MediaCodec进行解码的时候,并不是dequeueOutputBuffer后就会releaseOutputBuffer,保证同时只会持有一帧数据。而是同时持有了两帧的outputBufer。

把代码改成只持有一个outputBuffer就正常了。回头再看了下Android cts中MediaCodec相关代码,并没有对同时多路持有多个Buffer进行测试,所以最终只能认命了,避免掉持有多帧不立即释放的情况。这里也就不得不提一个建议了,要想使用MediaCodec时,代码无bug,使用尽量不超出Android cts的范围,超出范围的使用就要做好面对一堆bug的准备了。

借助SurfaceTexture解码,图像方向不正确

MediaCodec解码视频可以直接将视频图像解码到Surface上,而Surface又可以通过SurfaceTexture来进行构建,也就是说MediaCodec可以直接将图像解码到SurfaceTexture上,当我们需要对解码后的视频进行图像处理时,通常会选择这样的方式。

对于带有旋转或者裁边信息的视频,我们通常需要在渲染SurfaceTexture的纹理时,将其纹理矩阵信息通过getTransformMatrix拿到,然后去做渲染处理。但是在一些情况下,我们通过getTransformMatrix并不能拿到正确的矩阵。依旧是在Oppo A37wf这个手机上,使用两路1080p视频解码,会遇到一路视频正常,一路视频的SurfaceTexture无法通过getTransformMatrix获得正确的Matrix的情况。在grafika上也存在同样的问题。

进一步跟进,发现在出错的那一路解码器中,codec.getOutputFormat()得到的Format中有一个using-sw-renderer字段被设为1了,所以根本原因就是这个手机用Surface的方式同时硬解两路1080p视频,会有一路渲染使用的软件渲染的方式进行渲染,而这个垃圾手机软件渲染又有bug。 在MediaCodec.cpp可以找到设置和处理相关源码,但是找到可以强制改回使用硬件渲染的方式。

我使用的解决方案是在发现解码使用的是软件渲染的OutputFormat时,对获取的矩阵进行判断,如果矩阵信息和OutputFormat中的信息不匹配的话,重新计算一个渲染矩阵出来,然后渲染时候使用重新计算的矩阵。计算方式可以参考native层源码GLConsumer.cpp中的计算。直接copy过来就能用了。


欢迎转载,转载请保留文章出处。湖广午王的博客[http://blog.csdn.net/junzia/article/details/106036509]


更多相关文章

  1. Android(安卓)选择图片与视频
  2. Android(安卓)MediaRecorder视频录制,多分辨率调节
  3. Android视频通话Java代码
  4. Android中播放视频的三种方式
  5. Android(安卓)音频开发之 MediaPlayer
  6. android转IOS开发学习计划
  7. 使用Android(安卓)OpenGL ES 2.0绘图之五:添加运动
  8. Android(安卓)开发技术周报 Issue#294
  9. 一个关于Android视频流的Github项目

随机推荐

  1. Android 返回键的处理
  2. Android客户端和php+mysql+apache搭建的
  3. Android实现自定义滑动式抽屉效果菜单
  4. PopupWindow软键盘弹出,上移
  5. android中常用的弹出提示框
  6. Ubuntu 11.04 64位 编译 Android(安卓)2.
  7. Android客户端使用HttpClient发起web数据
  8. Android官方框架DataBinding
  9. Android之TextView的样式类Span的使用详
  10. Android 8.0 中如何读取内部和外部存储以