热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

篇首语:本文由编程笔记#小编为大家整理,主要介绍了性能比肩美拍秒拍的Android视频录制编辑特效解决方案相关的知识,希望对你有一定的参考价值。前言

篇首语:本文由编程笔记#小编为大家整理,主要介绍了性能比肩美拍秒拍的Android视频录制编辑特效解决方案相关的知识,希望对你有一定的参考价值。


前言

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

Android系统Java层API对音视频的支持在MediaCodec之前,还停留在非常抽象API的级别(即只提供简单的参数和方法,可以控制的行为少,得不到中间数据,不能进行复杂功能的开发,更谈不上扩展)。而在MediaCodec在推出之后,也未能彻底解决问题,原因有这些:1、MediaCodec出现的Android版本并不低,使用则无法兼容低版本机器和系统版本;2、由于Android的开源和定制特性,各大厂商实现的MediaCodec也不尽相同,也导致同一段代码A机器跑着是这个样,B机器跑着就是另一个样了。所以程序员童鞋们就把目光转向了NDK,但是NDK里面谷歌并没有提供什么关于音视频处理的API(比如解析生成文件,编解码帧),于是童鞋们又想着使用开源的C/C++框架,首当其冲的当然是最出名的ffmpeg、x264、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不一样,系统机制不一样,其他应该就一样了。

NDK有哪些API可用?

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

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

技术图片

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

技术图片

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

技术图片

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

技术图片

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

选择开源框架

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

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

需要说明一下的是:

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

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

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

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

1、如果使用ffmpeg开源方案处理音视频,那么AAC应该使用fdk_aac而不应该使用很久没更新的faac。

2、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

技术图片

欢迎关注我的微信公众号「码农突围」,分享Python、Java、大数据、机器学习、人工智能等技术,关注码农技术提升?职场突围?思维跃迁,20万+码农成长充电第一站,陪有梦想的你一起成长。


推荐阅读
  • 在 Linux 环境下,多线程编程是实现高效并发处理的重要技术。本文通过具体的实战案例,详细分析了多线程编程的关键技术和常见问题。文章首先介绍了多线程的基本概念和创建方法,然后通过实例代码展示了如何使用 pthreads 库进行线程同步和通信。此外,还探讨了多线程程序中的性能优化技巧和调试方法,为开发者提供了宝贵的实践经验。 ... [详细]
  • 本指南介绍了如何在ASP.NET Web应用程序中利用C#和JavaScript实现基于指纹识别的登录系统。通过集成指纹识别技术,用户无需输入传统的登录ID即可完成身份验证,从而提升用户体验和安全性。我们将详细探讨如何配置和部署这一功能,确保系统的稳定性和可靠性。 ... [详细]
  • CSS3 @font-face 字体应用技术解析与实践
    在Web前端开发中,HTML教程和CSS3的结合使得网页设计更加多样化。长期以来,Web设计师受限于“web-safe”字体的选择。然而,CSS3中的`@font-face`规则允许从服务器端加载自定义字体,极大地丰富了网页的视觉效果。通过这一技术,设计师可以自由选择和使用各种字体,提升用户体验和页面美观度。本文将深入解析`@font-face`的实现原理,并提供实际应用案例,帮助开发者更好地掌握这一强大工具。 ... [详细]
  • 今天我开始学习Flutter,并在Android Studio 3.5.3中创建了一个新的Flutter项目。然而,在首次尝试运行时遇到了问题,Gradle任务 `assembleDebug` 执行失败,退出状态码为1。经过初步排查,发现可能是由于依赖项配置不当或Gradle版本不兼容导致的。为了解决这个问题,我计划检查项目的 `build.gradle` 文件,确保所有依赖项和插件版本都符合要求,并尝试更新Gradle版本。此外,还将验证环境变量配置是否正确,以确保开发环境的稳定性。 ... [详细]
  • IOS Run loop详解
    为什么80%的码农都做不了架构师?转自http:blog.csdn.netztp800201articledetails9240913感谢作者分享Objecti ... [详细]
  • 解决Only fullscreen opaque activities can request orientation错误的方法
    本文介绍了在使用PictureSelectorLight第三方框架时遇到的Only fullscreen opaque activities can request orientation错误,并提供了一种有效的解决方案。 ... [详细]
  • 多线程基础概览
    本文探讨了多线程的起源及其在现代编程中的重要性。线程的引入是为了增强进程的稳定性,确保一个进程的崩溃不会影响其他进程。而进程的存在则是为了保障操作系统的稳定运行,防止单一应用程序的错误导致整个系统的崩溃。线程作为进程的逻辑单元,多个线程共享同一CPU,需要合理调度以避免资源竞争。 ... [详细]
  • Framework7:构建跨平台移动应用的高效框架
    Framework7 是一个开源免费的框架,适用于开发混合移动应用(原生与HTML混合)或iOS&Android风格的Web应用。此外,它还可以作为原型开发工具,帮助开发者快速创建应用原型。 ... [详细]
  • 秒建一个后台管理系统?用这5个开源免费的Java项目就够了
    秒建一个后台管理系统?用这5个开源免费的Java项目就够了 ... [详细]
  • 在软件开发过程中,经常需要将多个项目或模块进行集成和调试,尤其是当项目依赖于第三方开源库(如Cordova、CocoaPods)时。本文介绍了如何在Xcode中高效地进行多项目联合调试,分享了一些实用的技巧和最佳实践,帮助开发者解决常见的调试难题,提高开发效率。 ... [详细]
  • MySQL的查询执行流程涉及多个关键组件,包括连接器、查询缓存、分析器和优化器。在服务层,连接器负责建立与客户端的连接,查询缓存用于存储和检索常用查询结果,以提高性能。分析器则解析SQL语句,生成语法树,而优化器负责选择最优的查询执行计划。这一流程确保了MySQL能够高效地处理各种复杂的查询请求。 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • Android中将独立SO库封装进JAR包并实现SO库的加载与调用
    在Android开发中,将独立的SO库封装进JAR包并实现其加载与调用是一个常见的需求。本文详细介绍了如何将SO库嵌入到JAR包中,并确保在外部应用调用该JAR包时能够正确加载和使用这些SO库。通过这种方式,开发者可以更方便地管理和分发包含原生代码的库文件,提高开发效率和代码复用性。文章还探讨了常见的问题及其解决方案,帮助开发者避免在实际应用中遇到的坑。 ... [详细]
  • 本文深入解析了Java 8并发编程中的`AtomicInteger`类,详细探讨了其源码实现和应用场景。`AtomicInteger`通过硬件级别的原子操作,确保了整型变量在多线程环境下的安全性和高效性,避免了传统加锁方式带来的性能开销。文章不仅剖析了`AtomicInteger`的内部机制,还结合实际案例展示了其在并发编程中的优势和使用技巧。 ... [详细]
  • 深入解析HTTP网络请求API:从基础到进阶的全面指南
    本文全面解析了HTTP网络请求API,从基础到进阶,详细介绍了Android平台上的两种原生API——HttpUrlConnection和HttpClient。这两种API通过对底层Socket的封装,提供了高效、灵活的网络通信功能。文章不仅涵盖了基本的使用方法,还深入探讨了性能优化、错误处理和安全性等方面的高级主题,帮助开发者更好地理解和应用这些工具。 ... [详细]
author-avatar
lql
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有