NDK开发-Android下摄像头YUV数据获取与H264编码(FFmpeg、x264)总结

版权声明: https://blog.csdn.net/github_37209555/article/details/84800952

涉及知识点:

  • Camera2 API使用
  • YUV420P与YUV420SP(NV21)格式转换
  • h264文件格式
  • FFmpeg工程
  • x264解码器

这次就先记录一下开发过程,因为牵涉到的很多技术问题都不太清楚,Android的知识都不太记得了,还有一些知识牵扯到这些开源工程的内部实现,待以后慢慢学习再写。
这个小demo就是做一个摄像头数据的获取与编码存储,由于这个demo的目的是为了给学习直播技术打基础,所以对编码速度有一定要求。
指标:编码耗费时间必须在33.3ms以下
指标说明:由于摄像头采集速度一般在30fps左右,所以编码速度不能小于这个值,否则就会出现编码速度跟不上视频获取速度的情况,造成帧的积压与丢失。

很久没有写过Android代码了,Android开发的资料还是丰富,官方文档比较详细,再看FFmpeg的文档,非核心的函数也就有个返回值类型的说明了

  • Camera 2 API
    https://developer.android.google.cn/reference/android/hardware/camera2/package-summary
    Camera 2 API是Android API level 21以后添加的摄像头相关库,取代了Camera 1,使用方法有区别,Camera 1其实看起来更清晰,用起来很方便,但是据说Camera 2的优化更好,更省电。

  • YUV420P与NV21格式
    YUV420P格式和NV21都是对YUV数据进行采样得到的,NV21别名YUV420SP,其中Y是针对亮度(灰度)取样的值,U与V都是色度取样的值,420是YUV的采样比,四个Y分量对应一个U分量和一个V分量,那为什么不写成411格式呢,YUV411与YUV420的区别在于他们的采样方式不同,如图:
    在这里插入图片描述
    420格式,U的取样在水平方向上的步长是2,在竖直方向上也是2,V同理
    411格式,U、V的取样在竖直方向上分辨率与Y一样,水平方向上是Y的1/4

了解了YUV420采样方式后,来看一下它的储存方式,NV21与YUV420P的差别也在于存储方式
YUV420P的存储方式是先把所有Y存起来(YYYYYYYY),再存U(UU),再存V(VV)
而NV21的存储方式是先存Y(YYYYYYYY),再将UV交叉存储(VUVU)
在这里插入图片描述
所以NV21与YUV420P的转换也就很清晰了
安卓手机获取的格式一般是NV21,而解码器一般支持的格式是YUV420P,所以,解码之前要进行一下转换。

注:我在做的时候,有一个小问题,利用Camera 2 和imagereader获取到的YUV数据,虽然我知道是NV21格式的,但是发现它用三个数组将NV21数据存起来。
按理说既然UV交叉存储那一般应该用两个数组表示即可,而且Y数据的数量应该是像素总数,比如1920×1080的数据,数组大小就应该是2073600,UV数据总和应该是Y的1/2,也就是1036800
但是我发现它三个数组大小分别是2073600,1036800,1036800,为什么凭空多了一组1/2大小的数据

在这里插入图片描述
于是将数据输出后发现,第三个数组与第二个数组其实是相同的,不过是错位的,第三个数组相比第二个数组整体右移了一个字节,最前面用了一个值来填充
有意思的是我一开始进行转换的时候,编码后我打开视频发现我的颜色有的不对,蓝色变为橙色,非常有趣,后将VU数据调换,按正常输出,得知是由于VU数据搞反了,有空可以了解一下具体的原理。

  • h264文件格式

https://blog.csdn.net/h514434485/article/details/52064945

  • FFmpeg工程

之前通过雷神的博客了解到FFmpeg工程,用来做过摄像头获取到的H264数据的解码
海迪康ipcamera客户端开发纪实
FFmpeg是一款非常强大的音视频处理工程,C语言编写,写的非常漂亮,可读性很强,下一步继续阅读FFplay的源码,和FFmpeg的编码部分源码
这次一开始用FFmpeg做H264的编码工作,首先利用NDK在linux平台上对FFmpeg与x264进行了交叉编译,使之可在arm架构下运行,接下来的工作就是对FFmpeg进行参数调优。但是效果不理想后直接试用x264工程。

  • 编码器调优总结

一开始使用ffmpe编码(ffmpe的264视频编码也是基于X264库),编码一帧的时间非常不稳定,但都不满足要求(一帧处理时间应达到33ms以下),一般在80ms以上,偶尔100-200ms
此时的ffmpeg设置是,ultrafast,zerolatency(无帧缓存),线程数3-6差距都不大(手机4核处理器,设置6是因为看到有说线程数实际要除以1.5),与1比稍有提升
处理时间包括:

  1. 将NV21数据转换成YUV420P的时间
  2. ffmpeg与X264编码器之间的调用时间
  3. x264编码器编码的时间
  4. 写文件的时间

写文件时间占用非常少,所以推测瓶颈在于ffmpeg这步调用,由于不是内部编码器,所以可能要进行内存之间的复制
重新编译了x264库,配置了AS,重写了一个调用264库的类,封装好给java层调用,编码速度有了质的提升。
然后优化了一下转换函数,缩减到10ms以下,编码时间在20ms左右,基本上能满足编码需求,在多线程操作时,可以看出编码速度已经可以匹配获取图像的速度,但是发现慢慢会落后待编码帧,查找原因在于有时解码速度会变成40-50ms一帧,这就造成慢慢拉大差距,我猜测是由于多线程导致,处理器没有分配到。

各种参数调优后,发现编码器的多线程编码采用自适应比写死好,比如帧编码的并行数,片编码的并行数,将其设为自动

扫描二维码关注公众号,回复: 5830903 查看本文章
param.i_threads = 0; //帧编码并行数,0为自动
param.b_sliced_threads = 0; // 片编码并行数,0为自动

但是在性能提升后还是存在帧的堆积,说明解码线程还是经常没有被调度,导致时间拉长,所以决定在java层动手,干涉CPU的调度,之前用的是thread类的线程优先级设置方式,发现不适用于android,后采用android.os.Process类的设置优先级函数setThreadPriority(int )
与thread类的优先级衡量数值不同(10为最高),查android文档发现适合视频线程的数值是-10,值越小优先级越高,直到-19。

这样以后x264在编码速度的优化上已经达到需求了。下一步进行编码质量的优化(对码率进行调优,缩小码率,不影响质量),以及查看FFmpeg的源码,看一看编码流程,到底时间消耗在哪里,且为什么有些设置不起作用,加上音频的编码功能,以及做一下音视频同步及封装。

猜你喜欢

转载自blog.csdn.net/github_37209555/article/details/84800952