一、背景介绍随着4G网络的推广和网络带宽的提高,视频已经成为网民的主要消费载体,用户通过短视频分享和浏览信息。因此,视频的编辑功能变得越来越重要和普遍。视频剪辑应用也如雨后春笋般涌现。为了更好地推动得物App社区业务的发展,得物还自主研发了符合得物需求的视频剪辑工具。我们致力于打造“更快、更强”的视频编辑工具。2、视频剪辑工具介绍为了让大家更好的了解得物App的视频剪辑工具,我们先简单介绍一下视频剪辑工具的主要功能。以下是得物App视频剪辑工具的主要功能:视频剪辑工具的要点如下:视频剪辑工具需要操作的资源:文字:包括普通文字、特殊艺术字、花体字、ETC。;图片:包括静态图片,如JPEG/PNG等,包括动态图片,如HEIC/GIF??;Video:包括各种视频(各种编码和封装格式),主流格式一般有MP4封装格式、H264视频编码格式、AACAudio编码格式等;audio:包括各种audio(各种编码和封装格式),当然video也包含音轨。视频编辑工具的主要操作方法:操作图片和视频帧:我们知道视频是由图片一帧一帧组成的,所以操作视频帧和操作图片是一样的。我们为图片和视频帧添加一些特殊效果,实现一些有趣的效果来吸引用户。操作音频:主流的操作音频方式,如倍速、调节音量、变调等,是现在玩短视频的主要方式。视频编辑工具最终生成的是新视频,它对特定资源应用一些特效来生成新视频。下面这张流程图可以很容易的让大家了解视频剪辑的工作流程。为了方便,我们输入一个视频,添加一些特效,生成一个新的视频。从上面的过程可以看出,对原始视频A.mp4进行解封装,分离出音轨和视频轨,解码后对音频数据进行音效处理,对视频帧数据进行视频效果处理,然后进行编码并封装合成一个新的视频。当然解码和编码都是用一个队列来控制的,流程图上已经标明了,这里不深入开发,大家可以理解。经过上面的介绍,大家对视频剪辑工具有了一个大概的了解。其实衡量一个视频剪辑工具好不好,我们主要从以下几个方面入手:内存占用、导出视频的速度、导出视频的清晰度。下面就从这三个方面来详细讲解一下优化得物App视频剪辑工具的心路历程。3、内存优化性能是衡量一个程序好坏的首要指标。再强大的工具,如果一下子崩溃了,或者内存暴涨应用卡顿了,估计这个应用就称不上优秀的应用了。具体说一下视频剪辑工具的优化检测方案。优化内存从良好的编码习惯开始,尤其是对内存要求非常高的音视频应用。例如一个1080*1920的视频,解码后的原始数据一帧大小也是1080*1920,占用内存为1080*1920*(8*3)/8=5.93MB。一个视频帧占用这么大,一般1秒30帧的话,会占用177.9MB。如果不加以控制,再高性能的手机也经不起这样的折腾。希望下面的内存检测和优化方案能给大家带来一些帮助。3.1队列的合理设计上面我们在介绍视频编辑过程的视频中谈到了解码队列和编码队列的概念。事实上,队列的概念在音视频中使用的非常频繁。正是因为内存的限制,引入了队列的控制方式。你可能还有些迷茫,但看完下面的流程图,相信你会豁然开朗。我们只选择解码部分来分析队列的重要应用。视频剪辑工具中有几个重要的队列:解码过程中:VideoPacketQueue:视频解码前存放Packet的队列,一般推荐队列大小为100AudioPacketQueue:音频解码前存放Packet的队列,一般recommendedqueue大小为150VideoFrameQueue:视频解码后存放Frame的队列,一般推荐队列大小为3AudioFrameQueue:音频解码后存放Frame的队列,一般推荐队列大小为8编码时:EncodeVideoPacketQueue:Videoencoded之后,Packet存储的队列一般建议为100EncodeAudioPacketQueue:音频编码后的Packet存储的queue,一般建议为150。根据上面的方法,queue的大小可以在保证正常功能的情况下最大限度的进行设计。减少内存使用,提升用户体验。3.2内存泄漏排查Android上有很多方法可以解决内存泄漏问题。这里介绍两个:AsandetectionProfiledetectionAsan的全称是AddressSanitizer,是一个基于编译器的快速检测工具,用于检测本地代码中的内存错误。Asan可以解决以下四个核心问题:栈和堆缓冲区溢出、下溢释放后堆重用、栈使用超出范围、重复释放、错误释放更多介绍:https://github.com/google/sanitizers/wiki/AddressSanitizer关于Profile的使用,如果需要检测Native内存占用,需要满足API>=29,使用时需要非常小心。下面是阿三在demo中抓取的栈:20042-20042/?A/调试:***************************************************20042-20042/?A/DEBUG:构建指纹:'samsung/t2qzcx/t2q:11/RP1A.200720.012/G9960ZCU2AUGE:user/release-keys'20042-20042/?A/DEBUG:修订:'13'20042-20042/?A/DEBUG:ABI:'arm64'20042-20042/?A/DEBUG:时间戳:2021-09-1700:32:31+080020042-20042/?A/DEBUG:pid:19946,tid:20011,名称:AudioTrack>>>com.jeffmony.audioplayer<<<20042-20042/?A/DEBUG:uid:1035020042-20042/?A/DEBUG:信号6(SIGABRT),代码-1(SI_QUEUE),故障地址------2021-09-1700:32:31.15720042-20042/?A/DEBUG:中止消息:'====================================================================19946==错误:AddressSanitizer:堆在地址0x004ac1e41080上释放后使用,地址为0x007157f69580bp0x00705c0bb350sp0x00705c0bab08在2ac08(AudioTrack)#00x7157f6957c(/data/app/~~G094WKQQj)读取大小1792read3087KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libclang_rt.asan-aarch64-android.so+0x9f57c)#10x706549c228(/data/app/~~G094WKQQj7KZvdhvGYeffDLlayer==/audiopDLlayer==.com.je-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x14228)#20x706549bcd4(/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ3==/lib/armio1c64./libltp40)#30x70654994f0(/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x114f0)#40x70654a9cbc(/data/app/~~G094WKZvDQdQvQd==com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x21cbc)#50x70654a91d4(/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDlib/CarmJDO4==/.so+0x211d4)#60x715af9d188(/system/lib64/libwilhelm.so+0x1c188)#70x71570ea290(/system/lib64/libaudioclient.so+0x8b290)#80x71570e9480(/system/lib64/libaudioclient.so+0x8a480)#90x7156b664d4(/system/lib64/libutils.so+0x154d4)#100x71593e9974(/system/lib64/libandroid_runtime.so4+0xa597)#110x7156b65db0(/system/lib64/libutils.so+0x14db0)#120x7156ace234(/apex/com.android.runtime/lib64/bionic/libc.so+0xb6234)#130x7156a68e64(/apex/com.android.runtime/lib64/bionic/libc.so+0x50e64)0x004ac1e41080位于1792字节区域内的0字节[0x004ac1e41080,0x004ac1e41780)由线程T32(AudioTrack)释放:#00x7157f74c64(/data/app/~~G094KYKQd=G094KYKQd=/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libclang_rt.asan-aarch64-android.so+0xaac64)#10x70654a6d2c(/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-CBIJQDRJDxDx==/lib/arm64/libltpaudio.so+0x1ed2c)#20x70654a6af0(/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x1eaf0)#30x706549bf4c(/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzp/libltpaudio.so0)+0x~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x114f0)#60x70654a9cbc(/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x21cbc)#70x70654a91d4(/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x211d71)#5af0系统/lib64/libwilhelm.so+0x1c188)#90x71570ea290(/system/lib64/libaudioclient.so+0x8b290)显示消息是:heap-use-after-freeonaddress0x004ac1e41080意味着已经释放的内存被使用。我们继续看内存释放到哪里了?0x004ac1e41080islocated0bytesinside1792-byteregion[0x004ac1e41080,0x004ac1e41780)Asan的一大优势是它可以跟踪内存释放的路径,以防止内存泄漏和野指针问题,尤其是野指针,这些问题极难排查.简直是C++开发的噩梦。希望大家用好工具,养成良好的C++编码习惯。3.3优化线程另一个影响内存的重要因素是线程。视频编辑工具涉及到很多线程,线程的使用必须遵循一些基本规则。原则:尽量少创建线程,尽量少用pthread_mutex_t。使用线程本着功能隔离的原则。能同步就不要异步。以编辑模块为例。以下是我们使用的所有线程的列表:GL处理线程视频解封装线程视频中的视频轨道解码线程视频音频轨道解码线程缩略图提取线程音频编码线程视频编码线程视频封装线程如果插入一个单独的音频文件,另外两个需要添加的线程:音乐文件播放线程音乐文件解码线程上面列出的是视频编辑工具正常工作所需的最小线程数。如果您的视频编辑工具中的线程较多,建议您适当优化。毕竟少一个线程可以节省一分的开销和一分的线程同步。工作。我们还根据Android底层的消息机制,在C++层重写了一套消息分发SDK。以后我们会另发一篇文章来讲解我们定制的消息分发SDK,就到此为止吧。4.提高导出视频的速度我们使用视频编辑工具,最终希望能够导出一个视频。如果导出过程很慢,肯定是受不了的。从上面的介绍我们知道,视频的导出需要经过“解码——应用特效——编码”的过程,其中解码和编码这两个过程对速度至关重要。因为解码和编码视频需要一个很多资源,目前主要有两种方式——“软解码/编码”和“硬解码/编码”。如果你使用过FFmpeg或者其他使用CPU处理视频的视频编解码器,你可能遇到过速度慢的问题处理速度。这主要是因为CPU用于软编解码,CPU处理视频的速度远低于DSP芯片;计算工作是原始的处理方式,当然需要很长时间;“硬解码/编码”是由GPU处理的,GPU是专门针对视频解码和编码优化的专用图形处理芯片,所以编码和解码都非常快。Android使用MediaCodec实现“硬解码/编码”,iOS使用VideoToolBox实现“硬解码/编码”。这里着重介绍Android上编解码的速度优化。从上面的过程中,我们可以看出编码在解码之后。一段时长为60s(30fps)的视频需要解码1800帧,再对1800帧视频进行编码,才能完整生成另一个视频。这种串行等待很耗时。主要原因。这时我们参考多线程方案,将一个60s的视频分成两段,然后对两段视频同时解码,生成并导出两个30s的临时缓存视频文件,然后将两个30s合并videos对于一个60s的B.mp4视频,删除最后的临时缓存文件,这样我们只需要同时处理900帧数据,理论上可以将导出速度提高一倍。这就是平行出口。以下是得物App平行出口的基本流程。首先我们要明确一点,导出视频是需要消耗资源的。这个资源就是MediaCodec,最后会送到GPU去处理。手机中的MediaCodec实例是有限的。一般情况下,一部手机最多可以提供16个MediaCodec实例。如果当前使用的MediaCodec实例超过16个,电话将无法正常工作。MediaCodec资源是手机所有应用共享的。因此,并行段的数量并不是越高越好。只有一段,需要两个MediaCodec(一个解码视频,一个编码视频)。注意:音频解码和编码不能使用MediaCodec。毕竟,音频花费的时间要少得多,而且不是瓶颈。分成两段需要四个MediaCodec,分成三段需要六个MediaCodec,分成四段需要八个MediaCodec,以此类推。以下是并行导出的测试结果:两个并行阶段的速度提升了50%到70%,内存增加了20%。三个并行阶段的速度提高了60%到90%,内存增加了80%。向上。我们建议并行两个阶段,在一些性能好的模型上并行三个阶段。如果还有同学对视频导出过程中的文件操作有疑惑,下面的示意图可以清楚的看到并行导出和操作本地文件的过程:并行导出过程中,会生成两个临时文件。并行导出完成后,将这两个临时文件合并为一个新文件,同时删除这两个临时生成的文件(节省用户宝贵的存储空间)。原始文件jeffmony_out.mp4没有被删除/修改。Tips:目前我们在加工过程中生成临时文件,文件和最终适配文件都会保存在/sdcard/Pictures/duapp/Compile/下,加工后的临时文件清理过程会触发部分机型的保护机制。建议调整为App的私有内容。下面是正常导出和并行导出的测试速度对比:可以明显看出,同样的视频(同样的滤镜),左边的并行导出速度几乎是右边正常导出的两倍.可以看出并行导出对视频导出速度有明显的提升。当然,还有其他提高导出速度的建议。比如在视频帧特效处理的过程中,我们建议:尽量使用FBO/EBO/ABO来处理texture纹理。如果太大,则严禁使用glFinish()。这些方法都是我们在视频编辑开发过程中的实践经验,希望能给大家带来一些帮助。5.提高导出视频的清晰度一个视频编辑功能是否足够好,重要的指标之一就是导出的视频在同等条件下是否足够清晰。一般来说,衡量视频是否清晰有两种方式:主观标准:找一些用户观看不同的视频,根据用户的感受输出视频清晰度对比结果。用户一般根据颜色、屏幕亮度、柔和度来评价清晰度。客观标准:使用算法计算视频图像的质量得分。目前推荐使用Netflix推出的开源库VMAF来计算视频帧的质量分数。其实主观标准是比较准确的,但是可操作性比较差,尤其是在处理海量视频的时候,需要大量的人力,不能有效的进行。因此,在日常工作中,建议海量计算采用客观标准,关键判断采用主观标准。具体可以根据业务的重要性进行。以下是我们结合实际工作具体提高视频清晰度的方法:视频基本编码信息优化Profile优化:Profile分为三个级别,分别为Baseline、Main、High,其中BaselineProfile对应最低清晰度,版本对应Android3.0之后都支持。MainProfile的分辨率比BaselineProfile的分辨率要好,但是Android7.0之后才支持。HighProfile分辨率最高,Android7.0之后才支持。在设置EncoderProfileLevel之前,我们需要判断当前是否支持。比特率设置:视频比特率是视频数据传输过程中单位时间内传输的数据比特数。单位是kbps,顾名思义,码率越大,单位时间内填充的数据越多,视频质量就越高。但是码率并不是越大越好,如果超过了必要的限度,视频质量的提升并不明显,所以建议适当调整码率。Bitrate=width*height*frameRate*factor,其中factor=0.15。BitrateMode:共有三种通过编码模式——VBR(可变比特率)、CBR(固定比特率)和ABR(平均比特率),其中ABR是最好的方式,可以兼顾质量和视频大小.B帧设置:视频由I帧、P帧、B帧组成。其中I帧最大,P帧次之,B帧最小。我们尽量在编码时设置尽可能多的B帧(在合理范围内),它不会降低清晰度,但是可以大大减小视频的尺寸,这样我们就可以相应地提高码率,最后达到提高定义的目的。HEVC编码优化:使用HEVC编码可以保证在不增加文件大小的情况下,视频的清晰度得到大幅提升。在相同画质下,HEVC编码的视频比H.264编码的视频少40%左右。视频画面看起来“更清晰”。超分辨率算法:采用ESRGAN算法,利用机器学习的优势,对图片和视频进行去模糊、resize、降噪、锐化等处理,重构图片,实现图片的超分辨率处理。特征提取:计算噪声非线性映射:放大,模糊噪声图像重建:差异,平滑,去噪大量噪声,更亮的图片,更平滑的过渡。如果想了解视频分辨率优化的技术细节,可以参考得物科技发表的文章公众号—视频清晰度优化指南VI。总结工具优化的三个维度:优化内存占用,提升视频导出速度,提升导出视频的清晰度。在“提高视频导出速度”中,着重介绍了“并行导出”的技术方案。从最终的结果来看,视频导出速度的提升是非常明显的,也很清楚的解释了为什么“平行导出”的过程中会产生临时文件?为什么导出完成后需要删除临时文件?尽最大努力为用户带来更好的体验。最后,《提升导出视频的清晰度》中提到的超分辨率算法的应用效果得到了显着提升。与原始帧图像相比,超分辨率后的视频帧更清晰,噪点更少,细节更真实。
