当前位置: 首页 > news >正文

“抖音”式的酷炫短视频开发进阶

本文来源 : LiveVideoStack

2017年短视频应用的爆发,再次改变了人们,尤其是年轻人的生活习惯,快手、抖音等应用也逐渐融入到日常生活中。短视频App各种各样的酷炫效果让人爱不释手,也把视频内容玩出了新花样。LiveVideoStack邀请了全民快乐研发高级总监展晓凯,与我们线上分享了短视频酷炫特效的实现设计架构、解决思路和开发经验,本文是直播分享的内容整理。

分享 / 展晓凯

整理 / LiveVideoStack

开始前先跟大家分享一个视频,这个Demo是基于iOS平台实现的,我们今天的分享也将聚焦在视频中多种特效的实现方法和经验总结。

应该如何实现

在实现Demo中特效前,我借鉴了funimate产品,利用它提供的功能生成视频,对它进行逐帧分析,并从中找出可能的实现方法。在视频分析中,使用了FFmpeg将视频分解成一帧一帧的图片从而进行分析。观察具体某一帧或者某几帧使用了怎样的特效。

ffmpeg –i output.mp4 –r 0.25 frames_%04d.png

具体到技术实现手段,第一种实现方式是把视频每一帧解码出的YUV,利用libyuv库来操作,甚至可以用RGBA来操作,这是通过CPU操作转换YUV来实现;第二种实现方法性能会更好,但开发成本可能也会相对较高,就是在GPU上操作纹理来实现。

由于我们需要在移动平台实现,而在移动平台使用CPU是很难满足需求的,要考虑到性能、耗电、实时观看体验等等因素,因此我们需要使用GPU来实现。

Demo场景设计

我们想要实现这样一个Demo或者简单的App,首先我们需要预览视频的界面,然后给出多种特效的选择菜单,当用户选择其中某种特效时会实时显示该特效的预览效果,并且将特效的开始作用时间和作用时长记录到内存的结构体中,最后当用户点击保存按钮时,可以离线保存为视频文件。

基于现有框架的开发

那么我们需要用到哪些已有的框架或者已有的项目来完成这个功能呢?可以思考下,既然有预览界面,则一定需要视频播放器。播放器的基本功能包括了解码和音视频的渲染,此外再加上逻辑控制、音视频对齐就可以成为一个视频播放器。

视频播放器中视频解码模块是非常重要的,通过它可以将视频文件解码为视频帧,并且输出到解码纹理队列中,接下来就是本App最核心的工作——处理,视频处理模块会按照时间戳将对应的纹理进行处理,并放入到渲染队列,最后输出模块会将渲染队列中的纹理输出到屏幕上,而在离线保存场景下,则是将渲染队列中的纹理编码输出到本地,也就是封装成mp4或者flv等等格式写入本地磁盘。

鉴于处理模块是本App的核心,而我们今天所讲的特效也都是在该模块中完成的,因此接下来我们一起来看下它的具体实现方法。

视频处理

  • 镜像

首先跟大家分享一个最简单的特效——镜像,先生成一个16:9的屏幕比例的画布,将它分割为四部分,每部分画一个相同的视频帧,因为屏幕被分割为4部分,我们的物体坐标在渲染时就不能设定为全屏的。在OpenGL中物体坐标,左下角为(-1,-1),右上角为(1,1),这样我们就可以分别计算出4部分的物体坐标。

确认好物体坐标后,我们接下来就要确认画什么?也就是将视频帧以什么样的方式画在物体坐标上,这时就需要控制纹理坐标,我们可以看到OpenGL的纹理坐标定义:从左下角(0,0)到右上角(1,1),实际画的时候左上角是我们完整的纹理,右上角我们需要做镜像处理,左下角需要做横向翻转,右下角则是针对右上角视频帧做横向翻转,这样就可以实现简单的镜像效果。

  • 镜像模糊

相对于前面的特效,这个特效只需要做一对镜像,但他的背景是需要做高斯模糊的,如果用CPU来做,通过两个大的“for”循环就可以实现,对于GPU也是相同的,不过代码会相对复杂一些。假如我们要计算中间25这个点的高斯模糊,我们需要先得出下图中的像素值,乘上各自点的高斯权重,然后做加权平均,最终把高斯模糊的效果放在下面成为背景,然后再将镜像的纹理画在上面就可以实现了。

  • 电击效果

在了解了两个简单的特效实现之后,我们一起来看一些复杂特效的实现方法,首先是电击效果,实际上它的实现就是反选的处理,只需要使用下面代码就可以:

gl_FragColor  = vec4((1.0 - texture.rgb), texture.w);

但想要达到一个很好的效果,其中还是有一些小技巧,也就是需要把握好节奏。假如我们现在有250ms运动的视频帧,再排上180ms静止的反选视频帧就可以实现了,如下方动图演示:假设50ms为一帧,那么对于10帧总时间为500ms的视频帧来说,前5帧都不变,依旧是正常的效果,从第6帧开始我们做反选并且保证画面是静止的,也就是说第7、8、9帧同样放第6帧,而第10帧时我们渲染正常的第10帧,这样周而复始就可以实现电击效果。

  • 灵魂出窍

这个特效就是人影有一个向外扩散的效果,同样它的节奏也是非常重要的,尤其是能与音乐的配合才能达到一个完美的效果。那么它的实现过程如下:首先我们每隔15帧拷贝一帧作为“灵魂”并且按照比例放大,这里特别需要提到的是SRT(Scale/Rotate/Translate),基于这三个的组合我们可以写一个TransformEffect,它可以利用通用的SRT矩阵变化纹理。

在得到放大后的“灵魂”(拷贝帧),我们就需要考虑把“灵魂”和“肉体”(原本视频帧)混合起来,这里需要用到GLES的一个内嵌Mix函数将两个纹理进行mix即可。那么同理,我们还可以实现眩晕、影随的效果:眩晕是将每一帧向两侧做位移再与本帧进行mix,而影随则是将之前的帧缓存下来,以一定的间隔和当前帧做mix。

  • 动态晕影

其实晕影效果在GPUImage中也有设置,它的实现首先需要构造一个纯黑色的图片,然后再与原始视频帧做mix就可以,在处理过程中有两点需要注意:首先交界处要做平滑处理,然后非常重要的依旧是节奏,我们Demo中的节奏时间列表如下:

  • 木头人

木头人效果就是在视频中有一个bar——彩色且可动的区域,在bar区域以外则是静止且高斯模糊的,实现方法是每隔一定时间(Demo中是1.5s)冷冻一帧做高斯模糊处理,并且取灰度值放在后面,按照移动的边框距离将两帧进行mix。

  • 九宫格

九宫格效果中想要实现9个画面的效果可以参考第一个镜像特效的处理,而如何保证移动、放大、缩小时效果的平滑变化是最关键的,首先我们需要构建一个大纹理——相比原画长、宽分别扩大3倍,然后我们通过TransformEffect来进行位移、缩放。

  • 旋转木马

最后为大家介绍旋转木马特效,这也是本次分享中最复杂的,因为它的处理不再是简单的链式结构,而是graph。那么旋转木马特效其实就是四个画面中只有一个画面是彩色且可动的,其余三个都是黑白、静止的。我们假设左上角为1-3帧,右上角为4-6帧,左下角为7-9帧,右下角为10-12帧依次排列,那么在第1帧时,四个画面分别会显示1,4,7,10帧,而此时只有第一帧为彩色的,其余是黑白的,同时除左上角外其余三个画面都是冰冻状态。当左上角画面变为第3帧时,左上角画面变为黑白、静止,右上角的画面变为彩色、可动的,以此类推。

如上述视频所示,它的实现方法如下:首先每个画面都包含一个队列,然后我们把解码出来的视频帧以此按照左上、右上、左下、右下的顺序填充,当然在实现中可能以时间为依据会更加合理,当右下队列中有了第一帧时,我们才会绘制出第一帧效果也就是说特效才会开始,此时视频中显示的是第1,5,9,13帧,当左上绘制出第二帧时,解码器会将解码好的第14帧给到右下的队列中,以此类推。而当左上画面绘制出第4帧后,右上的队列开始绘制,同时解码器解码出来的视频帧将填充到左上的队列中,周而复始就能达到旋转木马的效果。

在绘制阶段有两个关键点:第一,对于活动区域而言,我们需要取出活动队列中的视频帧进行绘制,同时非活动区域取出队列中首帧进行灰度绘制;第二,对于填充区域来说,我们要按照当前时间戳与第一帧时间戳计算出填充区域,并且将当前帧入队到填充区域的队列中。

以上是针对demo中特效实现的讲解,非常感谢。

相关书籍推荐:

移动音视频开发进阶线下分享会

如果大家觉得还不过瘾,或者想与讲师面对面交流沟通,我们将在下周六(1月27日)下午举办线下沙龙以及新书分享会,除展晓凯老师,我们还请到了暴风影音首席架构师鲍金龙,Hulu全球高级研发经理傅德良与我们一同分享移动音视频开发实践经验。ps:据说“大师兄”刘歧也会去哦~

更多详情点击【阅读原文】

相关文章:

  • 2018 编程语言流行度趋势:Java 很稳,Python 潜力股
  • 程序员和用户
  • 程序员必定会爱上的10款软件
  • 福利丨好书申请免费送【1.26】
  • 线程的基本概念 / 计算机程序的思维逻辑
  • 区块链要去中心化么
  • 黑帽黑客历史盘点:这群人到底厉害到什么程度?
  • CPU说:这个世界慢!死!了!
  • 福利丨好书申请免费送【2.2】
  • ARCore vs ARKit-热核战争的重启?
  • 纯干货丨PHP实现购物车的构建
  • February丨月度书单
  • 计算机科学丛书20周年——20本跨世经典 夯筑科技基石
  • TIOBE 2月编程语言排行榜:Java稳居第一,Go 还在跌!
  • 十年程序员用眼告诉你 2018 PHP 不一样
  • android图片蒙层
  • C++入门教程(10):for 语句
  • java8-模拟hadoop
  • leetcode-27. Remove Element
  • Redis提升并发能力 | 从0开始构建SpringCloud微服务(2)
  • Web标准制定过程
  • yii2中session跨域名的问题
  • 如何使用 JavaScript 解析 URL
  • 实战|智能家居行业移动应用性能分析
  • 用jQuery怎么做到前后端分离
  • Unity3D - 异步加载游戏场景与异步加载游戏资源进度条 ...
  • #我与Java虚拟机的故事#连载05:Java虚拟机的修炼之道
  • (1)(1.8) MSP(MultiWii 串行协议)(4.1 版)
  • (done) 两个矩阵 “相似” 是什么意思?
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (附源码)ssm教师工作量核算统计系统 毕业设计 162307
  • (论文阅读笔记)Network planning with deep reinforcement learning
  • (排序详解之 堆排序)
  • (三分钟了解debug)SLAM研究方向-Debug总结
  • (四)JPA - JQPL 实现增删改查
  • (一)WLAN定义和基本架构转
  • (原創) 物件導向與老子思想 (OO)
  • (转)Groupon前传:从10个月的失败作品修改,1个月找到成功
  • ./configure、make、make install 命令
  • .apk文件,IIS不支持下载解决
  • .NET 4.0中的泛型协变和反变
  • .NET Core 成都线下面基会拉开序幕
  • .Net Core和.Net Standard直观理解
  • .net 程序发生了一个不可捕获的异常
  • .NET多线程执行函数
  • .NET微信公众号开发-2.0创建自定义菜单
  • .net之微信企业号开发(一) 所使用的环境与工具以及准备工作
  • .vimrc php,修改home目录下的.vimrc文件,vim配置php高亮显示
  • @data注解_一枚 架构师 也不会用的Lombok注解,相见恨晚
  • @DateTimeFormat 和 @JsonFormat 注解详解
  • @media screen 针对不同移动设备
  • @Service注解让spring找到你的Service bean
  • [ 云计算 | AWS 实践 ] Java 如何重命名 Amazon S3 中的文件和文件夹
  • [100天算法】-x 的平方根(day 61)