短视频APP架构设计与实现

本文来自全民快乐研发高级总监展晓凯在LiveVideoStackCon 2018讲师热身分享,并由LiveVideoStack整理而成。分享中展晓凯介绍了短视频APP场景中视频录制、编辑、保存模块的相关技术与实践,以及变速不变调算法W-SOLA的实现。

大家好,我是展晓凯,今天与大家分享的内容是短视频APP的架构设计。2016~2017年短视频App呈现爆发式增长,并在大多数人的生活中扮演着十分重要的角色。短视频APP的用户停留时间长、黏性大、使用频次高,其次日留存、7日留存甚至是月留存同样也非常高,而大多数公司在进行自己产品APP的开发时,也会在其中集成垂直分类或一些社区化的短视频功能。接下来将介绍如何设计一个短视频APP架构并在对于关键细节进行分析与实现。

  1. 短视频APP的场景

一个短视频产品应该有哪几部分组成呢?第一个模块也是最重要的,是包括视频录制、剪辑、保存在内的视频生产功能。短视频APP势必需要为生产者表现其优秀的工具属性——让使用者以更低成本生产短视频;有视频生产就必须有视频消费,这也是短视频APP的第二个模块。这里的消费是指用户消费内容而非真正地花钱或送礼,我们期待的是用户点击某一个短视频,在看完短视频的内容后认为这是一个非常不错的作品并为其点赞、评论、分享甚至打赏,这个过程相当于消费者与视频的生产者产生了一定的关注关系,可以为让App具有社交属性;而在服务器端,包含运营后台(运营后台可以让内容运营人员会对视频内容进行标注、推荐从而有助于视频精准推荐与精品推荐)、人物画像,智能推荐系统用于分发Feed流、视频播放(从Feed流中跳转到一个短视频详情播放页面,其中的秒开与流畅程度依赖于CDN与播放器等组件的配合)、送礼打赏(包括消费与充值提现系统)、评论转发(短视频APP消费属性之体现,包括转发内容至第三方APP等,可有效提高内容以及用户转化率)。

而在业务服务器端,当生产者生产出一个视频并将其上传至业务服务器后,系统会入库这条短视频的Matedata并存储到数据源中,真正的短视频文件则会安放在存储中。这里需要注意的是,一些小运营商的DNS缓存会降低上传成功率,我们需要通过技术优化来克服此类问题。当某用户消费此短视频时,通过使用CDN对整体过程进行加速来提升消费体验,同时CDN也帮我们提高缓存命中率节省带宽成本。而用户经常消费的一些高质量视频内容离不开可靠的运营后台,运营后台可帮助视频生产者完成编辑、推荐、分类等工作。除此之外还有人物画像与智能推荐,平时我们使用的各种软件,无论是用来购买商品的淘宝、京东还是用以获取新闻的今日头条、网易新闻,都会根据用户行为构造人物画像并通过人工标注或算法生成等方式将这些内容进行标注,从而获知用户感兴趣的内容并进行精准化推荐,人物画像与智能推荐系统对大多数APP来说都是一个非常重要的系统,可有效增大用户黏性。

  1. 短视频生产

短视频生产包含视频录制、视频编辑、视频保存三大模块。视频录制包括视频预览、伴奏播放(例如学猫叫等需要用户对着伴奏完成手势或模仿嘴形的玩法,让用户以比较低的成本生产出一些趣味十足的视频)、视频录制/暂停(例如闪现、瞬间换衣等玩法)、倍速/半速录制(在一些特别场景中需要用户录制视频时加快或放慢背景音乐使其能够跟上节奏完成录制动作,而后再恢复原始速度从而达到流畅表演的效果);视频编辑模块包括视频播放、视频特效、音频特效、快放/慢放处理(用于倍速/半速录制的还原);视频保存模块的功能是按照视频播放的时间戳选择视频特效与音频特效并生成一个特效时间Model,然后按照此特效时间Model进行特效处理、编解码(一般选用H.264+AAC,最终以MP4格式直接上传到服务器,服务器需要经过转码从而保证视频播放的兼容性,而对于时间较长的视频可以考虑分片上传的做法,降低转码时长)、封装格式IO等。在选择编解码器时,一两分钟以上的长视频会通过分片方式上传,这里介绍一种比较不错的分片上传模式:假设一个GOP为一片,系统以每两秒一片的速度完成上传后服务端即可进行转码,最终一个视频文件上传完成后,只剩余两秒也就是一个分片未转码,系统可在完成这两秒转码工作后,即可生成一个用户可播放的完整视频文件,从而以最快速度让其他人在平台上看到制作者的作品。此思路对于一分钟以上的长视频而言具有明显意义。

2.1 视频录制模块

视频录制由视频预览、伴奏播放、视频录制/暂停、倍速/半速录制四部分组成。

(1)视频预览

在Android&IOS平台我们可以使用自己的Camera+OpenGL ES进行视频的预览,这里的预览过程一般就是从相机获取纹理ID并进行诸如美颜、贴纸等特效的处理,之后再绘制到相应的View上。

(2)伴奏播放

伴奏播放是指将一个伴奏文件以m4a或mp3形式下载到本地,随后对文件进行解码。最常见的解码方案是FFmpeg。由于经过解码获取的PCM或WAV文件可被直接读取,伴奏播放的同时我们可将解码后的伴奏PCM文件写入磁盘中以方便后续编辑阶段更高效率的使用。

(3)视频录制暂停

开始录制之后系统将预览的视频帧编码并写入文件中,这相当于为录制开辟一条旁路。当得到一张图像后系统会将其送入编码器,此时这里的纹理ID保持不变并可再让其绘制到我们的View上。如果用户点击暂停则停止编码,当用户继续则将视频帧进行再次编码。这里需要注意的一点是关键帧的处理,如果不处理关键帧那么在手机端播放时就有可能会出现马赛克问题。

(4)倍速/半速录制

对短视频APP来说这一步骤十分重要,常用的方法是抽帧或者插帧操作。例如用户录制一段打篮球的视频并希望在扣篮或上篮时放慢动作,首先需要以正常速度录制而后再拉长视频以实现慢放效果。倍速与半速录制的关键是背景声处理,这里需要对背景声进行变速不变调处理。在FFmpeg的AVfilter模块中的Audiofilter里面有一个被称为Tempo的滤波器,而在SOX中也有tempo.c可实现类似功能。

2.2 变速不变调处理Tempo之W-SOLA实现

(1)基本概念

实现Tempo变速不变调处理的方法,比较常用的是SOLA。当然也存在很多改进算法,这里我们简单介绍一下W-SOLA。假设处理的样本为单声道、44.1k采样率的样本数据,其中有三个核心概念:Segment、Overlap、Search。Segment是指需要处理的音频数据窗口大小,例如规定以40毫秒作为处理音频数据的窗口大小;Overlap是指音频的重叠区域,一个Segment是40毫秒,而Overlap可覆盖的区域则可能为16毫秒;Segment是40毫秒,Overlap的一个覆盖范围是16毫秒,我们需要拿出这16毫秒并成功拼接才可加长。这里需要找寻相似度最高的位置进行拼接,并且要做fade_in和fade_out的拼接,否则就会出现杂音。我们可以设定为了查找相似度最高的地方而进行查找的范围Search为10毫秒,以上是Tempo中的三个核心基本概念。

(2)算法实现

算法实现如下:首先把送进来的数据保存到一个全局缓冲区中并定义为input_buffer ,并每次以Segment进行一个数据单位的处理。先在Search区域中搜索出与Overlap最佳匹配的位置,也就是波形的相似程度最高(overlap与搜索位置每个对应采样点相减的平方之和最小)的位置;从最佳匹配位置开始和Overlap中的数据进行Mix放入到全局的out_buffer中,Mix规则按照Overlap进行fade_out ,而原始数据进行fade_in并混合;从input_buffer中拷贝segment-2*overlap个采样到output_buffer中(中间位置保持不变);根据factor计算出skip的采样点个数并在input_buffer中进行skip操作。重复上述步骤,从而完成整个操作。

2.3 视频编辑模块

在视频编辑模块中,我们需要一个集成时间轴的可正常预览视频的标准视频播放器从而让用户更精确地控制每一帧使用什么特效;视频特效方面可以添加贴纸或者实现灵魂出窍,井格,九宫格等特效;音频特效方面我们可以添加背景音乐、视频静音、添加声音特效等;也可进行快放慢放处理,如果在此之前我们已经有了快放慢放的数据,那么即可直接进行恢复;如果用户选择快放或慢放处理并记录时间点,那么在视频抽帧或帧复制后即可重新生成一段完整视频。

2.4 音频效果器之混响器的实现

无论是SOX还是大家常用的其他效果器开源库,基本都是基于施罗德混响器模型来实现多种功能。

施罗德混响模型使用四个并联的梳状滤波器与两个串联的全通滤波器建立混响模型。我们将混响看作是一个脉冲,那么前期声音被称为早期反射声。完成混响后的声音将会受到一连串长尾效应的影响,梳状滤波器的作用是提供混响效果中延迟较长的回声从而减弱长尾效应。而延时较短的全通滤波器则起到了增加反射声波密度的作用,从而实现更加保真的混响效果。但是传统滤波器在面对反射声不足或反射声时差过大的问题时容易显得力不从心,我可以在后期处理混响后,在混响脉冲之中加入一些echo增强反射声的效果。

3. 视频保存模块

最后一个需要介绍的便是视频保存模块。视频保存实际上就是将视频编辑阶段记录下来的模型数据在离线状态下重新运算一次并将效果用于目标片段上,完成音视频的编解码与读写文件过程。

(1)特效时间Model

当我们在视频编辑阶段点击保存按纽时,特效时间Model就已建立了,视频编辑阶段会把视频特效作用的起始时间和终止时间记录到Model中,在视频保存阶段的对应时间段内进行特效应用。

(2)特效处理

无论音频特效还是视频特效都会按照特效时间Model进行对应的特效处理,处理后的音频PCM或视频帧会被送至编码器(这里的编码器一般为H.264或AAC),一般我们会选择硬件编码器从而实现高效编解码处理过程。

(3)编解码器

解码器解码而成的原始数据可被解析为纹理ID和PCM,随后这些数据会被交给Processor进行处理, 最终交给编码器并编码为H264与AAC数据。

(4)封装格式IO

最终我们会将H264与AAC封装成MP4并存储到本地文件中,整体就是视频保存模块。

这里需要强调一下视频保存模块的输出部分与视频预览模块之间的差别。二者的差别在于输出对象,后者输出对象是耳机、屏幕等,前者则输出于统一的文件内。但其中的解码部分与处理部分则完全一致,只不过前者的应用环境为离线,而后者可直接上传至对应位置的CDN或一些云存储厂商。

Q&A:

Q:如何优化在视频播放器中一边拖动进度条一边预览的卡顿状况?

A:我们曾经尝试解决过这个问题,我们试图在拖动到某个位置就解码出一帧或几帧并直接放至一个Buffer中,而在此之前我们集成了一个视频输出模块,此模块从队列中不断往外获取视频并进行展示。这样的话无论用户拖动多快播放器都可从队列中拿出用来展示的数据,队列中也就是存放3~4帧。之前我们进行开发实践时手机的性能还没有现在这么高,但在安卓平台上测试也没有什么卡顿。

Q:如何实现预加载ViewPager下一页视频?

A:这个取决于视频文件的存储格式。如果是以MP4存储那么并不易实现预加载,而如果使用HLS加载第一个分片则很容易实现。

Q:如何应对人脸识别+特效视觉情景下的丢帧问题?

A:人脸识别需要注意以下几点:第一点是需要将人脸识别基于异步线程进行开发而非预览线程。第二点是除非需要对人脸进行追踪,没有必要对每一帧视频进行人脸识别,我们可以规定两帧或三帧进行一次识别。第三点是为需要人脸识别的帧视频打上时间戳,这样当后续使用它时,对比当前时间戳跟检测出来的数据时间戳超过某一阈值即可判断非机主访问。