写点什么

得物视频编辑工具优化全指南

作者:得物技术
  • 2022-11-29
    上海
  • 本文字数:7193 字

    阅读完需:约 24 分钟

得物视频编辑工具优化全指南

一、背景介绍

随着 4G 网络的推广和网络带宽的提升,视频成为互联网用户主要的消费载体,用户通过短视频来分享和浏览信息。由此视频的编辑功能越来越重要、越来越普遍。视频编辑的 App 也如雨后春笋般涌现。


为更好地推动得物 App 社区业务的发展,得物也自研符合得物需求的视频编辑工具。我们致力于打造一个“更快、更强”的视频编辑工具。

二、视频编辑工具介绍

为了让大家更好地了解得物 App 的视频编辑工具,我们先简单介绍一下视频编辑工具的主要功能。



下面是得物 App 视频编辑工具的主要功能:



视频编辑工具的重点如下:


  • 视频编辑工具需要操作的资源:

  • 文字:包括普通的文字、特殊的艺术字、花字等等;

  • 图片:包括静态图,如 JPEG/PNG 等等,也包括 HEIC/GIF 等动态图;

  • 视频:包括各种各样的视频(各种编码和封装格式),主流的格式一般是 MP4 的封装格式、H264 视频编码格式、AAC 音频编码格式等等;

  • 音频:包括各种各样的音频(各种编码和封装格式),当然视频当然也是包含音频轨道的。

  • 视频编辑工具主要的操作方式:

  • 操作图片、视频帧:我们知道视频是一帧一帧的图片组成的,所以操作视频帧和操作图片是一样的道理,我们通过添加一些特效在图片和视频帧上面,实现一些有趣的效果来吸引用户。

  • 操作音频:主流的操作音频方式如倍速、调整音量、变调等等,都是现今短视频的主要玩法。

  • 视频编辑工具最终生成的是一个新的视频,这个视频将特定的资源应用一些特效生成一个新的视频。


下面的流程图可以很方便地让大家了解视频编辑的工作流程。为了方便,我们输入一个视频,加上一些特效,生成一个新的视频。



从上面的流程可以看出来,原始视频 A.mp4 经过解封装分离出音频轨道和视频轨道,对它们解码之后,对音频数据应用音频特效、对视频帧数据应用视频特效,然后编码封装合成一个新的视频。当然解码和编码都是有一个队列控制的,流程图上标注了,没有深入展开,大家了解即可。


经过上面的介绍,大家对视频编辑工具有了大概得了解,其实衡量一个视频编辑工具做得好不好,主要从下面这几个方面着手:


  • 内存占用情况

  • 导出视频的速度如何

  • 导出视频的清晰度如何


下面从这三方面详细展开给大家阐述得物 App 的视频编辑工具优化的心路历程。

三、内存优化

性能是所有程序好不好的首要指标,一个工具即使功能再强大,但是一点就崩溃,或者用着用着内存暴涨、应用卡死,估计这个应用不能称为一个优秀的应用,下面我们具体谈一谈视频编辑工具的优化检测方案。


优化内存从良好的编码习惯开始,尤其对音视频这种对内存需求非常高的应用而言。例如一个 1080 * 1920 的视频,解码出来原始数据一帧图片大小也是 1080 * 1920,占用内存是 1080 * 1920 * (8 * 3 ) / 8 = 5.93 MB,一个视频帧就占用这么大,1 秒一般有 30 帧,那得占用 177.9MB,如果不加控制,那不管多高性能的手机也经不住这样的折腾。希望下面的内存检测和优化方案可以给你带来一些帮助。

3.1 合理设计队列

上面我们在介绍视频编辑流程的视频谈到了解码队列和编码队列的概念。其实队列这个概念在音视频中使用非常频繁,正是因为内存的限制,所以才引入队列这个控制方式。大家可能还有点懵,但是看完下面的流程图,我相信你一定会豁然开朗。


我们仅选取解码的部分来分析一下队列的重要应用。



在视频编辑工具中有几个重要的队列:


  • 解码过程中:

  • Video Packet Queue:视频解码之前 Packet 存放的队列,一般建议的队列大小是 100

  • Audio Packet Queue:音频解码之前 Packet 存放的队列,一般建议的队列大小是 150

  • Video Frame Queue:视频解码之后 Frame 存放的队列,一般建议的队列大小是 3

  • Audio Frame Queue:音频解码之后 Frame 存放的队列,一般建议的队列大小是 8

  • 编码过程中:

  • Encode Video Packet Queue:视频编码之后 Packet 存放的队列,一般建议的大小是 100

  • Encode Audio Packet Queue:音频编码之后的 Packet 存放的队列,一般建议的大小是 150


按照上面的方式设计队列的大小,可以在保证功能正常的情况下最大程度的降低内存占用,提升用户体验。

3.2 排查内存泄漏

Android 上排查内存泄漏的方式有很多,这里介绍两种:


  • Asan 检测

  • Profile 检测


Asan 全称是 AddressSanitizer 是一种基于编译器的快速检测的工具,用于检测原生代码中的内存错误问题,Asan 可以解决如下四种核心问题:


  • 堆栈和堆缓冲区上溢、下溢

  • 释放之后堆重新使用问题

  • 超过范围的堆栈使用情况

  • 重复释放、错误释放问题


Asan 的使用方式建议参考 google 官方文档,这儿就不多作介绍了:https://github.com/google/sanitizers/wiki/AddressSanitizer


关于 Profile 的使用,如果需要检测 Native 内存使用情况,需要满足 API>=29,大家在使用的时候需要非常注意。



下面是我们在 demo 中应用 Asan 抓取的堆栈:


20042-20042/? A/DEBUG: *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***20042-20042/? A/DEBUG: Build fingerprint: 'samsung/t2qzcx/t2q:11/RP1A.200720.012/G9960ZCU2AUGE:user/release-keys'20042-20042/? A/DEBUG: Revision: '13'20042-20042/? A/DEBUG: ABI: 'arm64'20042-20042/? A/DEBUG: Timestamp: 2021-09-17 00:32:31+080020042-20042/? A/DEBUG: pid: 19946, tid: 20011, name: AudioTrack  >>> com.jeffmony.audioplayer <<<20042-20042/? A/DEBUG: uid: 1035020042-20042/? A/DEBUG: signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------2021-09-17 00:32:31.157 20042-20042/? A/DEBUG: Abort message: '=================================================================    ==19946==ERROR: AddressSanitizer: heap-use-after-free on address 0x004ac1e41080 at pc 0x007157f69580 bp 0x00705c0bb350 sp 0x00705c0bab08    READ of size 1792 at 0x004ac1e41080 thread T32 (AudioTrack)        #0 0x7157f6957c  (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libclang_rt.asan-aarch64-android.so+0x9f57c)        #1 0x706549c228  (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x14228)        #2 0x706549bcd4  (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x13cd4)        #3 0x70654994f0  (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x114f0)        #4 0x70654a9cbc  (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x21cbc)        #5 0x70654a91d4  (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x211d4)        #6 0x715af9d188  (/system/lib64/libwilhelm.so+0x1c188)        #7 0x71570ea290  (/system/lib64/libaudioclient.so+0x8b290)        #8 0x71570e9480  (/system/lib64/libaudioclient.so+0x8a480)        #9 0x7156b664d4  (/system/lib64/libutils.so+0x154d4)        #10 0x71593e9974  (/system/lib64/libandroid_runtime.so+0xa5974)        #11 0x7156b65db0  (/system/lib64/libutils.so+0x14db0)        #12 0x7156ace234  (/apex/com.android.runtime/lib64/bionic/libc.so+0xb6234)        #13 0x7156a68e64  (/apex/com.android.runtime/lib64/bionic/libc.so+0x50e64)

0x004ac1e41080 is located 0 bytes inside of 1792-byte region [0x004ac1e41080,0x004ac1e41780) freed by thread T32 (AudioTrack) here: #0 0x7157f74c64 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libclang_rt.asan-aarch64-android.so+0xaac64) #1 0x70654a6d2c (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x1ed2c) #2 0x70654a6af0 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x1eaf0) #3 0x706549bf4c (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x13f4c) #4 0x706549bcd4 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x13cd4) #5 0x70654994f0 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x114f0) #6 0x70654a9cbc (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x21cbc) #7 0x70654a91d4 (/data/app/~~G094WKQQj7KZvdhvGYDLDA==/com.jeffmony.audioplayer-kcu1nmgzpBIQDRJDxCJDOQ==/lib/arm64/libltpaudio.so+0x211d4) #8 0x715af9d188 (/system/lib64/libwilhelm.so+0x1c188) #9 0x71570ea290 (/system/lib64/libaudioclient.so+0x8b290)
复制代码


显示 message 是:heap-use-after-free on address 0x004ac1e41080 说明是使用了已经释放掉的内存了,再继续看,这个内存具体在什么地方被释放的?0x004ac1e41080 is located 0 bytes inside of 1792-byte region [0x004ac1e41080,0x004ac1e41780) Asan 一个很大的优势就是可以追踪内存释放的路径,防止出现内存泄漏和野指针问题,特别是野指针,一旦出现特别难排查,简直是 C++开发的噩梦,希望大家用好工具,同时培养良好的 C++编码习惯。

3.3 优化线程

另一个影响内存的重要因素是线程,视频编辑工具涉及到的线程非常多,线程的使用得遵循一些基本的原则:


  • 尽量少创建线程

  • 尽量少使用 pthread_mutex_t

  • 本着功能隔绝原则使用线程

  • 能同步就别异步


以编辑模块为例,这儿列一下我们使用到的所有线程:


  • GL 处理线程

  • 视频解封装线程

  • 视频中视频轨道解码线程

  • 视频音频轨道解码线程

  • 抽取缩略图线程

  • 音频编码线程

  • 视频编码线程

  • 视频封装线程


如果插入了独立的音频文件,还需要添加两个额外的线程:


  • 音乐文件播放线程

  • 音乐文件解码线程


上面列出的是一个视频编辑工具能正常工作所必备的最少线程,如果你的视频编辑工具中多了什么线程,我们建议可以适当优化一下,毕竟少一个线程,可以少一分开销,而且少一分线程同步的工作。


我们在底层也按照 Android 的消息机制重写了一套 C++层的消息分发 SDK,这个我们后续会另外分享文章阐释我们定制的消息分发 SDK,这儿点到为止。

四、提升导出视频的速度

我们使用视频编辑工具,最终是希望导出一个视频,如果这个导出的过程很慢,那肯定是无法忍受的,从上面的介绍我们已知视频的导出需要经过“解码——应用特效——编码”的过程,其中解码和编码这两个过程对速度的影响至关重要。因为解码和编码视频需要耗费大量的资源,目前主要有两种方式——“软解/编码”和“硬解/编码”。


如果你使用过 FFmpeg 或者其他使用 CPU 进行视频编解码的来处理视频的话,你可能已经遇到了处理速度慢的问题。这主要是因为软编码和软解码使用 CPU 进行运算,而 CPU 在处理视频上的速度远低于 DSP 芯片;简而言之“软解/编码”主要通过 CPU 来工作,通过 CPU 来主导大量的计算工作,是原始的处理方式,当然耗费的时间也比较长;“硬解/编码”是通过 GPU 来处理,GPU 是专用的图形处理芯片,对视频的解码和编码有专门的优化,所以编码和解码的速度非常快。


Android 上使用 MediaCodec 来实现“硬解/编码”,iOS 上使用 VideoToolBox 来实现“硬解/编码”,这里着重介绍 Android 上编码解码的速度优化。



从上面的流程我们可以看出,编码在解码的后面,一个时长 60s(30fps)的视频,需要解码 1800 帧,然后编码 1800 帧视频才能完整生成另外一个视频,这样串行的等待是耗时的主要原因。


这时候我们参考多线程方案,将一个 60s 的视频均分为两段,然后这两段视频同时进行解码操作,生成导出了两个 30s 的临时缓存视频文件,随后将这两个 30s 的视频合并为一个 60s 的 B.mp4 视频,最后删除临时缓存文件,这样我们只需要同时处理 900 帧的数据,理论上可以提升一倍的导出速度。


这就是并行导出,下面是得物 App 并行导出的基本流程。



首先我们要明确导出视频是需要消耗资源的,这个资源就是 MediaCodec,最终是送入到 GPU 中处理,一个手机中的 MediaCodec 实例是有限的,正常情况下,一个手机可以提供的 MediaCodec 实例最多有 16 个,如果当前使用的 MediaCodec 实例超过 16 个,那么手机将无法正常工作。MediaCodec 资源是手机中的所有 App 共同持有。所以并行分段的个数不是越多越好。


  • 只有一段,需要两个 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()


这些做法都是我们在视频编辑开发过程中的切实经验,希望能给大家带来一些帮助。

五、提升导出视频的清晰度

一个视频编辑功能是否足够优秀,其中的一个重要指标就是同等条件下导出的视频是否足够清楚,通常而言,衡量视频是否清晰的有两种方式:


  • 主观标准:找一些用户观看不同的视频,根据用户的观感输出视频清晰度的对比结果,用户一般根据色彩、画面亮度、柔和度等来评估清晰度。

  • 客观标准:利用算法计算视频画面质量分,目前比较推荐 Netflix 推出的开源库 VMAF 来计算视频帧的质量分。


实际上主观标准是比较准确的,但是可操作性比较差,特别是处理海量视频的时候,需要大量的人力,无法有效开展,因此日常工作中还是推荐客观标准进行海量计算,主观标准进行重点判断。具体的可以结合业务的重要程度来开展。


下面结合我们实际的工作给出具体提升视频清晰度的方式:


  • 视频基础编码信息优化

  • Profile 优化:Profile 有三种 Level,分别是 Baseline、Main、High,其中 Baseline Profile 对应清晰度最低,Android 3.0 之后的版本都支持的,Main Profile 清晰度比 Baseline Profile 清晰度要好,但是从 Android 7.0 之后才支持,High Profile 清晰度最高,也是从 Android 7.0 之后才支持。我们在设置 Encoder Profile Level 之前,需要判断一下当前是否支持。

  • Bitrate 码率 设置: 视频码率是视频数据传输时单位时间内传送的数据位数。单位是 kbps,望文生义,码率越大,单位时间填充的数据就越多,视频质量就越高。但码率也不是设置的越大越好,超过必要限度,对视频画质的提升已不明显,建议采用合适的 factor 来调整码率。Bitrate = width * height * frameRate * factor,其中 factor=0.15。

  • Bitrate Mode: 有三种通过的编码模式——VBR(可变码率)、CBR(固定码率)、ABR(平均码率),其中 ABR 是最好的方式,可以兼顾质量和视频大小。

  • B 帧设置: 视频有 I 帧、P 帧、B 帧构成,其中 I 帧最大,P 帧次之,B 帧最小,我们在编码时尽量多设置 B 帧(在合理的范围内),并不会降低清晰度,但是可以大大降低视频的大小,这样我们就可以相应地调大码率,最终实现了提升清晰度的目标。

  • HEVC 编码优化: 使用 HEVC 编码,可以保证在不增加文件大小的情况下,大大提升视频的清晰度。在相同的图像质量下,HEVC 编码的视频比 H.264 编码的视频约减少 40%

  • 色彩调优

  • 综合调整亮度、对比度、色温、饱和度、锐度等颜色参数,进而优化整体的视频画面,让视频画面看上去“更清晰”。

  • 超分算法 采用 ESRGAN 算法,利用机器学习的优势对图片和视频进行去模糊、Resize、降噪、锐化等处理,重建图片,实现对图片的超分辨率处理。

  • 特征提取:计算噪点

  • 非线性映射:放大,模糊化噪点

  • 图像重建:差分,平滑过度,去噪

  • 下面是使用超分算法处理前后的对比图,可以很明显地看出右边的图更加清晰,少了很多噪点、图片更亮、过度更平滑。



如果大家想了解视频清晰度优化的技术细节,可以参考文章--视频清晰度优化指南

六、总结

本文开篇从介绍得物 App 的主要功能展开,提出了视频编辑工具优化的三个维度:


  • 优化内存占用

  • 提升视频导出速度

  • 提升导出视频的清晰度


其中在“提升视频导出速度”时重点谈到了“并行导出”的技术方案,从最终的结果来看,视频导出速度的提升非常明显,同时也非常清楚地解释了“并行导出”过程中为什么生成临时文件?为什么有必要在导出完成之后删除临时文件?尽力给用户带来较好的体验。


最后在“提升导出视频的清晰度”中重点提到的超分算法应用效果提升明显,超分之后的视频帧相比原帧图更加清晰、噪点更少,而且细节部分更加真实。


后续我们还会结合 AR 特效输出更多有意义的技术分享,敬请期待。


* /Jeff Mony


关注得物技术,每周一三五晚 18:30 更新技术干货

要是觉得文章对你有帮助的话,欢迎评论转发点赞~

发布于: 刚刚阅读数: 4
用户头像

得物技术

关注

得物APP技术部 2019-11-13 加入

关注微信公众号「得物技术」

评论

发布
暂无评论
得物视频编辑工具优化全指南_性能优化_得物技术_InfoQ写作社区