“抖音”式的酷炫短視頻開發進階

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:據說“大師兄”劉歧也會去哦~
更多詳情點擊【閱讀原文】。
