“抖音”式的酷炫短視訊開發進階
2017年短視訊應用的爆發,再次改變了人們,尤其是年輕人的生活習慣,快手、抖音等應用也逐漸融入到日常生活中。短視訊App各種各樣的酷炫效果讓人愛不釋手,也把視訊內容玩出了新花樣。LiveVideoStack邀請了全民快樂研發高階總監展曉凱,與我們線上分享了短視訊酷炫特效的實現設計架構、解決思路和開發經驗,本文是直播分享的內容整理。
分享 / 展曉凱
整理 / LiveVideoStack
開始前先跟大家分享一個視訊,這個Demo是基於iOS平臺實現的,我們今天的分享也將聚焦在視訊中多種特效的實現方法和經驗總結。
應該如何實現
在實現Demo中特效前,我借鑑了funimate產品,利用它提供的功能生成視訊,對它進行逐幀分析,並從中找出可能的實現方法。
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:據說“大師兄”劉歧也會去哦~
更多詳情點選【閱讀原文】。