性能比肩美拍秒拍的Android视频录制编辑特效解决方案

               


图片



前言

众所周知,Android平台开发分为Java层和C++层,即Android SDK和Android NDK。常规产品功能只须要涉及到Java层便可,除非特殊须要是不须要引入NDK的。但若是是进行音视频开发呢?java

Android系统Java层API对音视频的支持在MediaCodec以前,还停留在很是抽象API的级别(即只提供简单的参数和方法,能够控制的行为少,得不到中间数据,不能进行复杂功能的开发,更谈不上扩展)。而在MediaCodec在推出以后,也未能完全解决问题,缘由有这些:一、MediaCodec出现的Android版本并不低,使用则没法兼容低版本机器和系统版本;二、因为Android的开源和定制特性,各大厂商实现的MediaCodec也不尽相同,也致使同一段代码A机器跑着是这个样,B机器跑着就是另外一个样了。因此程序员童鞋们就把目光转向了NDK,可是NDK里面谷/歌并无提供什么关于音视频处理的API(好比解析生成文件,编解码帧),因而童鞋们又想着使用开源的C/C++框架,首当其冲的固然是最出名的ffmpeg、x26四、mp3lame、faac这些了。问题又来了,ffmpeg最先对x86支持是最好的,arm平台或者mips平台支持就不这么好了(笔者调研ffmpeg2.0之后状况有所好转)。那就只能使用软解软编,速度跟不上是个什么体验亲们知道吗?举个栗子,假设要录制640x480的视频,音频视频所有使用软编码,x264若是纯软编码加上手机CPU的处理性能50毫秒甚至100毫秒一帧都说不定,总之就是慢,还要算上音频还要压缩编码。若是想录制25帧率的视频,一帧的编码时间是不能超过40毫秒的,不然速度就跟不上了,算上其余业务功能花的时间,这个时间起码要降到30毫秒如下,而后再使用多线程异步编码的方式优化一下应该勉强能达到边获取画面边生成视频文件。正是由于有这样那样的不方便,笔者才通过几个月的研究,找到了一个还不算太完美的解决方案供你们参考,本文将全面介绍各个环节的技术实现方案,最后并附上工程源码。顺便声明一下,笔者在进行这项工做以前Android开发经验基本上算是1(不是0是由于之前写过helloworld),可是C/C++,Java都已经掌握,还在ios上使用objc开发过项目,因此我想Android也差别不大,语言不同,平台不同,API不同,系统机制不同,其余应该就同样了。linux

NDK有哪些API可用?

先把NDK的include打开,普查一下到底NDK提供了哪些接口能够用。谷/歌还算是有人性,其实除了linux系统级的API外,其实仍是有一些音视频相关的API的。android

OpenSL,能够直接在C++层操做音频采集和播放设备,进行录音和播放声音,从API9开始支持。ios

图片

EGL,能够在C++层建立OpenGL的绘制环境,用于视频图像渲染,还能够用于一些图像处理如裁剪、拉伸、旋转,甚至更高级的滤镜特效处理也是能够的。另外不得不说在C++本身建立OpenGL的渲染环境比使用Java层的GLSurfaceView灵活性、可控性、扩展性方面直接提高好几个数量级。而EGL在API9也已经支持了。git

图片

OpenGL(ES), NDK在Java层提供了OpenGL接口,而在NDK层也提供了更原生的OpenGL头文件,而要使用GLSL那就必需要有OpenGLES2.0+了,还好NDK也很早就支持了,OpenGLES2.0在API5就开始支持了,万幸!!程序员

图片

OpenMAXAL,这是普查过程当中发现的一个让人不爽的库,由于从它的接口定义来看它有例如以比较抽象接口方式提供的播放视频的功能和打开摄像头的功能。播放视频就用不到了,后面本身编解码本身渲染实现,看到这个打开摄像头的接口,心中当时是欣喜了一把的,结果是个人MX3竟然告诉我该接口没实现。那结果就必须从Java层传摄像头的数据到C++层了。不过OpenMAXIL,前者的兄弟,却是个好东西,惋惜谷/歌暂时没有开放接口。github

图片

这样一来,图像采集就必须从Java层打开Camera,而后获取到数据以后经过JNI传递到C++层了。渲染图像的View也要从java层建立SurfaceView而后传递句柄到C++层进而使用EGL来初始化OpenGL的渲染环境。声音的采集和播放就和Java不要紧了,底层就能够直接处理完了。redis

选择开源框架

ffmpeg: 文件解析,图像拉伸,像素格式转换,大多数解码器,笔者选用的2.7.5版本,有针对ARM的很多优化,解码速度还算好。ubuntu

x264:  H264的编码器,新的版本也对ARM有不少优化,若是使用多线程编码一帧640x480能够低至3-4毫秒。缓存

mp3lame:  MP3的编码器,其实测试工程里面没用到(测试工程使用的MP4(H264+AAC)的组合),只是习惯性强迫症编译了加进编码器列表里

faac: AAC的编码器,也是好久没更新了,编码速度上算是拖后腿的,因此后面才有个曲线救国的设计来解决音频编码的问题。

完整解决方案图

图片

音频编码慢的问题

x264和ffmpeg都下载比较新的版本,而后开启asm,neon等优化选项编译以后,编解码速度还能接受。但是FAAC的编码速度着实仍是有点慢。笔者因而乎想到个办法,就是存储临时文件,在录制的时候视频数据直接调用x264编码,不走ffmpeg中转(这样能够更灵活配置x264参数,达到更快的目的),而音频数据就直接写入文件。这样录制的临时文件其实和正儿八经的视频文件大小差距不大,不会形成磁卡写入速度慢的瓶颈问题,同时还可解决编辑播放的时候拖动进度条的准确度问题,同时解决关键帧抽帧的问题,由于临时文件都是本身写的,文件里什么内容均可以本身掌控。不得不说一个问题就是定义的抽象视频文件读取写入接口Reader和Writer,而读取写入正式MP4文件的实现和读取写入临时文件的实现都是实现这个Reader和Writer的,因此往后想改为直接录制的时候就生成MP4只须要初始化的时候new另外一个对象便可。还有一招来解决速度慢的问题就是多线程异步写入,采集线程拿到数据以后丢给另外一个线程来进行编码写入,只要编码写入的平均速度跟得上帧率就能够知足需求。

引入OpenGL2D/3D引擎

当在C++层使用EGL建立了OpenGL的渲染环境以后,就可使用任何C/C++编写的基于OpenGL框架了。笔者这里引入了COCOS2D-X来给视频加一些特效,好比序列帧,粒子效果等。COCOS2D-X自己有本身的渲染线程和OpenGL渲染环境,须要把这些代码干掉以后,写一部分代码让COCOS2D-X渲染到你本身建立的EGL环境上。另外COCOS2D-X的对象回收机制是模拟的Objective-C的引用计数和自动回收池方式,工程源码中的COCOS2D-X回收机制笔者也进行了简化修改,说实话我的以为它的引用计数模拟的还能够,和COM差很少的原理,统一基类就能够实现,可是自动回收池就不用彻底照搬Objective-C了,不必搞回收池压栈了,全局一个回收池就够用了嘛。(纯属我的观点)

主副线程模式

OpenGL的glMakeCurrent是线程敏感的,你们都知道。和OpenGL相关的全部操做都是线程敏感的,即文理加载,glsl脚本编译连接,context建立,glDraw操做都要求在同一个线程内。而Android平台没有相似iOS上自带的MainOperationQueue的方式,因此笔者本身设计了一个主副线程模式(我本身取的名字),即主线程就是Android的UI线程,负责UI绘制的响应按钮Action。而后其余全部操做都交给副线程来作。也就说每一种用户的操做的响应函数都不直接干事,而是学习MFC的方式,post一个消息和数据到副线程。那么副线程就必然要用单线程调度消息循环和多任务的方式了,消息循环不说了,MFC的模式。单线程调度多任务可能好多童鞋没接触过,其实就是将传统的单线程处理的任务,分红不少个时间片,让线程每次只处理一个时间片,而后缓存处理状态,到下一次轮到它的时候再继续处理。

好比任务接口是  IMission {bool onMissionStart();  bool onMissionStep(); void onMissionStop();}   调度线程先执行一次onMissionStart若是返回false则执行onMissionStop结束任务;若是前者返回true,则不断的调用onMissionStep,直到返回false,再执行onMissionStop,任务结束。具体的处理都要封装成任务接口的实现类,而后丢进任务列表。

试想,这样的设计架构下,是否是全部的操做都在同一个线程里了,OpenGL的调用也都在同一个线程里了,还有附带的效果就是妈妈不再用担忧多线程并发处理处处加锁致使的性能问题和bug问题了,不要怀疑它的性能,由于就算多线程到CPU那一级也变成了单线程了。redis不就是单线程的么,速度快的杠杠的。

总结一下

  • 使用OpenSL录音和播音

  • 使用EGL在C++层建立OpenGL环境

  • 改造COCOS2D-X,使用本身建立的OpenGL环境

  • 直接使用x264而非ffmpeg中转,按最快的编码方式配置参数,必定记得开启x264的多线程编码。

x264和ffmpeg都要下载比较新的,而且编译的时候使用asm,neon等选项。(笔者是在ubuntu上跨平台编译的)

若是录制的时候直接编码视频和音频速度跟不上就写入临时文件,图像编码,声音直接存PCM。

除了Android主线程外,另外只开一个副线程用于调度,具体小模块耗时的任务就单独开线程,框架主体上只存在两个线程,一主一副。

完整工程源码

使用的API15开发,实际上是能够低到API9的。

源码地址:http://download.csdn.net/detail/yangyk125/9416064

操做演示:http://www.tudou.com/programs/view/PvY9MMugbRw/

图片

图片

渲染完生成视频的位置:/SD卡/e4fun/video/*.mp4

须要说明一下的是:

  • 一、com.android.video.camera.EFCameraView类 最前面两个private字段定义当前选用的摄像头分辨率宽度和高度,要求当前摄像头支持这个分辨率。

  • 二、jni/WORKER/EFRecordWorker.cpp的createRecordWorker函数内,定义当前录制视频的各类基本参数,请根据测试机器的性能自由配置。

  • 三、jni/WORKER/EFRecordWorker.cpp的on_create_worker函数内,有个设置setAnimationInterval调用,设置OpenGL绘制帧率,和视频帧率是两回事,请酌情设置。

感谢一位读了这篇博客的网友,给我指出了其中能够优化的地方

  • 一、若是使用ffmpeg开源方案处理音视频,那么AAC应该使用fdk_aac而不该该使用好久没更新的faac。

  • 二、glReadPixels回读数据效率低下,笔者正在尝试升级到gles3.0看看能不能有什么办法快速获取渲染结果图像,若是您知道,请在后面留言,谢谢啦!

在Android上作音视频处理,若是还想要更快的编解码,若是是Java层则逃不开MediaCodec,若是是C++层,能够向下研究,好比OpenMAXIL等等。

后记:

通过半年努力,解决了其中部分有效率问题的地方

(1)编解码部分

编解码部分以前文章采用的X264+FFMPEG的开源方案,而继续学习以后,找到了android上特有的实现方案。

版本<4.4:x264+ffmpeg or 私有API(libstagefright.so)。

版本=4.4:jni反调android.media.MediaCodec or 或者在java层开发。

版本>4.4:NdkMediaCodec(android.media.MediaCodec 的 jni接口)。

(2)AAC更优开源方案

AAC开源方案FDKAAC一直在更新,效率有提高,而faac早就不更新了。so…你懂的。

AAC也可使用MediaCodec或者NdkMediaCodec

(3)OpenGL之framebuffer数据的回读

GLES版本<3.0:使用glReadPixels 或者 EGLImageKHR(eglCreateImageKHR,glEGLImageTargetTexture2DOES)

GLES版本=3.0:Pixel Pack Buffer + glMapBufferRange。

Android版本>=4.2:还有一个android平台化的回读FrameBuffer的方案,那就是新建SurfaceTexture和Surface,而后新建立一个OpenGL Context,一比一再渲染一次,便可将FrameBuffer渲染到这个SurfaceTexture上面,surface还能够做为编码器的输入。这样不只能够快速从渲染结果传递数据到编码器,还能实现跨线程传递纹理数据,属于android平台自己提供的功能,非opengl自带能力。之因此是4.2,是由于SurfaceTexture在4.2之后才基本完善,以前各类不稳定。

https://github.com/yangyk125/AndroidVideo

原创做者:花岗岩是甜的 ,原文连接:https://blog.csdn.net/yangyk125/article/details/50571304 

相关文章
相关标签/搜索