大家好,我是來(lái)自RingCentral鈴盛的阮景雄(Bear)。

今天分享的主題是《高性能且靈活的iOS視頻剪輯與特效開(kāi)源框架VideoLab》。

今天的分享主要包含以上六個(gè)方面,首先會(huì )對個(gè)人及公司做個(gè)簡(jiǎn)介,其次會(huì )介紹VideoLab是什么,第三點(diǎn)會(huì )介紹VideoLab的技術(shù)選型,第四點(diǎn)介紹AVFoundation框架,接著(zhù)會(huì )介紹VideoLab的設計與實(shí)現,最后會(huì )介紹VideoLab后續的計劃。
1. 個(gè)人及公司簡(jiǎn)介
首先,先介紹下個(gè)人及公司。

我2020年7月份入職了RingCentral,職位是移動(dòng)端架構師。之前的任職工作期間,在視頻處理、視頻采集、視頻播放、直播、社區等幾個(gè)領(lǐng)域都有涉獵。在RingCentral,負責的業(yè)務(wù)主要是移動(dòng)端的架構和iOS平臺模塊化的演進(jìn)。

接著(zhù)簡(jiǎn)單介紹下RingCentral鈴盛,從右邊的圖可以看出我們公司主要包含 Message/Video/Phone三塊業(yè)務(wù)。Message是最右下角的IM聊天,Video是視頻會(huì )議,Phone是最早的打電話(huà)功能。我們公司連續七年獲得統一通信及服務(wù)的領(lǐng)導者,總部位于美國硅谷,鈴盛中國成立于 2011,目前在杭州,廈門(mén),香港均有辦公室。
2. VideoLab 是什么?
接著(zhù)讓我們來(lái)聊下VideoLab是什么?

首先,來(lái)看一些關(guān)鍵字:高性能,靈活,視頻剪輯,特效,開(kāi)源框架,iOS,Swift,Metal,AVFoundation。這些關(guān)鍵字組合成一塊就解釋了VideoLab是什么。
讓我們來(lái)看下當前已有的一些Feature,當前已經(jīng)支持了高性能實(shí)時(shí)剪輯與導出,高自由度組合視頻、圖片、音頻,支持音頻音高設置、音量調節,支持CALayer矢量動(dòng)畫(huà)及復雜的文字動(dòng)畫(huà),支持關(guān)鍵幀動(dòng)畫(huà),支持類(lèi)似于A(yíng)fter Effect的預合成,支持轉場(chǎng),支持自定義各類(lèi)特效。

讓我們來(lái)看下一些Gif的示例,第一張圖片是多圖層的一個(gè)示例,組合了三個(gè)視頻,一張圖片。第二張是一個(gè)拆字的文字動(dòng)畫(huà)。第三張是漸隱漸顯加Transform的關(guān)鍵幀動(dòng)畫(huà)。第四張是類(lèi)似AE里預合成的動(dòng)畫(huà)。第五張是zoom blur轉場(chǎng)的示例。
3. 框架技術(shù)選型
接著(zhù)讓我們來(lái)談?wù)効蚣艿募夹g(shù)選型。

說(shuō)到框架選型,難免提到一些友商。Videoleap是業(yè)界的標桿了,它是國外的一家公司,它的母公司出了非常多剪輯和圖片處理的APP。剪映是頭條出品,目前在國內做的也是非常好。VN 也是國內的,整體體驗還不錯。
在體驗完競品之后,對它們做了個(gè)逆向,Videoleap使用的是AVFoundation + OpenGL,剪映主要是AVFoundation + GPUImage,VN 是AVFoundation + CoreImage。再看我之前工作的美拍,它最早是用的AVFoundation + GPUImage,因為那會(huì )時(shí)間比較早,所以都是直接用AVAssetReader + AVAssetWriter,后面轉成了FFmpeg + OpenGL。
在有了這些備選方案之后,我在想要做一個(gè)沉淀,要寫(xiě)一個(gè)框架,能不能有一些其他選擇?然后我就選了AVFoundation + Metal,Metal是蘋(píng)果自家的渲染引擎,也是蘋(píng)果這幾年力推的主力之一,每年的WWDC都可以看到蘋(píng)果關(guān)于Metal的topic。
4. AVFoundation框架
接著(zhù)我們來(lái)介紹下AVFoundation視頻剪輯的框架。

- 第一步,創(chuàng )建一個(gè)或多個(gè)AVAsset。AVAsset是有時(shí)間的,模擬音視頻實(shí)體的對象。
- 第二步,創(chuàng )建AVComposition、AVVideoComposition以及 AVAudioMix。其中AVComposition指定了音視頻軌道的時(shí)間對齊,AVVideoComposition 指定了視頻軌道在任何給定時(shí)間點(diǎn)的幾何變換與混合,AVAudioMix管理音頻軌道的混合參數。這三個(gè)對象是視頻剪輯最主要的三個(gè)類(lèi),可以把第一個(gè)類(lèi)的作用理解為擺放音視頻軌道,第二個(gè)類(lèi)處理視頻混合,第三個(gè)類(lèi)處理音頻混合。
- 第三步,我們可以使用這三個(gè)對象來(lái)創(chuàng )建AVPlayerItem,并從中創(chuàng )建一個(gè)AVPlayer來(lái)播放編輯效果。
- 此外,我們也可以使用這三個(gè)對象來(lái)創(chuàng )建AVAssetExportSession,用來(lái)將編輯結果寫(xiě)入文件。

接下來(lái),讓我們看下AVComposition,AVComposition是一個(gè)或多個(gè)AVCompositionTrack音視頻軌道的集合。其中AVCompositionTrack 又可以包含來(lái)自多個(gè) AVAsset 的AVAssetTrack。右圖的例子,將兩個(gè)AVAsset中的音視頻 AVAssetTrack 組合到AVComposition的音視頻AVCompositionTrack中。

設想圖中所示的場(chǎng)景,AVComposition包含兩個(gè) AVCompositionTrack。我們在T1 時(shí)間點(diǎn)需要混合兩個(gè) AVCompositionTrack的圖像。為了達到這個(gè)目的,我們需要使用 AVVideoComposition。

AVVideoComposition可以用來(lái)指定渲染大小、渲染縮放以及幀率。圖中紫色的部分包含了一組指令,這些指令存儲了混合的參數。有了這些混合的參數之后,可以通過(guò)自定義的 Compositor 來(lái)混合對應的圖像幀。
整體工作流如圖所示,接受指令,把原視頻幀通過(guò)合成器,生成合成后的幀,輸出給播放器或者導出器。讓我們聚焦到合成器,我們有多個(gè)原始幀,需要處理并輸出新的一幀。
流程可分解為:
- AVAsynchronousVideoCompositionRequest綁定了當前時(shí)間的一系列原始幀,以及當前時(shí)間所在的 Instruction。
- 收到startVideoCompositionRequest: 回調,并接收到這個(gè) Request。
- 根據原始幀及Instruction 相關(guān)混合參數,渲染得到合成的幀。
- 調用finishWithComposedVideoFrame,交付渲染后的幀。

AVAudioMix在A(yíng)VComposition的音頻軌道上處理音頻。包含一組AVAudioMixInputParameters,每個(gè)Parameters對應一個(gè)音頻的 AVCompositionTrack。右邊的圖片是一個(gè)示例,可以看到AVCompositionTrack和AVAudioMixInputParameters是一一對應的。
5. VideoLab 設計與實(shí)現
前面我們介紹了AVFoundation視頻剪輯流程,接下來(lái)我們介紹下VideoLab框架的設計與實(shí)現。

先簡(jiǎn)要介紹下AE(Adobe After Effect),AE是特效設計師常用的動(dòng)態(tài)圖形和視覺(jué)效果軟件。AE 通過(guò)“層”控制視頻、音頻及靜態(tài)圖片的合成,每個(gè)媒體(視頻、音頻及靜態(tài)圖片)對象都有自己獨立的軌道。
圖片是在 AE 中合成兩個(gè)視頻的示例。在左上角Project區域內,有名為Comp1類(lèi)型為Composition 的一個(gè)合成。在 AE 中合成可以認為是一個(gè)作品,可以播放導出,也可以設置寬高值、幀率、背景色等參數。在下面Timeline Control 區域內,包含了兩個(gè)圖層,源分別為video1.MOV與video2.MOV。我們可以自由的設置圖層參數,如Transform,Audio,也可以在右邊區域自由的移動(dòng)圖層,達到靈活的組合效果。針對每個(gè)圖層,AE里還可以添加一組特效。
讓我們提取一些關(guān)鍵字:Composition合成,Layer圖層,Transform變換,Audio音頻和Source來(lái)源。

基于前面對 AE 的分析,我們可以設計相似的描述方式:
- RenderComposition,對應AE中的合成。包含一組RenderLayer。此外,RenderComposition還包含BackgroundColor、FrameDuration、RenderSize,分別對應背景色、幀率及渲染大小等剪輯相關(guān)參數。
- RenderLayer,對應AE中的層。包含了Source、TimeRange、Transform、AudioConfiguration、Operations,分別對應素材來(lái)源、在時(shí)間軸的時(shí)間區間、變換(位置、旋轉、縮放)、音頻配置及特效操作組。
- RenderLayerGroup,對應 AE 的預合成。這個(gè)Group繼承自RenderLayer,包含一組RenderLayer。可以理解成先把一組視頻或圖片處理完,再去做合成。
KeyframeAnimation,對應 AE 的關(guān)鍵幀動(dòng)畫(huà)。包含了KeyPath、Values、KeyTimes、緩動(dòng)函數數組。
從上面的圖示可以看到,我們可以靈活自由的放置這些區域。

前面介紹了RenderComposition、RenderLayer、RenderLayerGroup 以及KeyframeAnimation。從前面的AVFoundation 介紹可知,我們需要生成AVPlayerItem與AVAssetExportSession 用于播放與導出。因此,我們需要有一個(gè)對象可以解析這幾個(gè)描述對象,并用AVFoundation 的方法生成AVPlayerItem 與AVAssetExportSession。框架將這個(gè)對象命名為VideoLab,可以理解成這是一個(gè)實(shí)驗室。
可以看到新的流程,把AVComposition/AVVideoComposition/AVAudioMix都封裝在了VideoLab內。這樣做極大的簡(jiǎn)化了開(kāi)發(fā)對AVFoundation的認知,現在和AE比較相似,可以非常方便的組合這些圖層。流程就轉變?yōu)椋?/div>
- 創(chuàng )建一個(gè)或多個(gè)RenderLayer。
- 創(chuàng )建RenderComposition,設置其BackgroundColor、FrameDuration、RenderSize,以及RenderLayer 數組。
- 使用創(chuàng )建的RenderComposition創(chuàng )建 VideoLab。
- 使用創(chuàng )建的VideoLab生成AVPlayerItem或AVAssetExportSession。

那VideoLab是如何把這些描述對象轉換為AVFoundation的三大對象的呢?
先來(lái)看下AVComposition,我們需要給AVComposition分別添加視頻軌道與音頻軌道。如圖所示,這個(gè)RenderComposition包含6個(gè)RenderLayer,其中一個(gè)是RenderLayerGroup。
第一步是將RenderLayer轉換VideoRenderLayer,VideoRenderLayer 是框架內部對象,包含一個(gè)RenderLayer,主要負責將RenderLayer的視頻軌道添加到AVComposition中。可轉換為VideoRenderLayer的 RenderLayer包含以下幾類(lèi):1. Source包含視頻軌道;2. Source為圖片類(lèi)型;3. 特效操作組不為空(Operations)。

轉化為VideoRenderLayer后的第二步是將VideoRenderLayer視頻軌道添加到AVComposition中。從上圖中的例子可以看到,我們有3個(gè)視頻軌道,還有一個(gè)Blank Video Track。 這里的空視頻是指視頻軌道是黑幀且不包含音頻軌道的視頻,為image或只有Operation的VideoRenderLayer服務(wù)。
從圖中能看到VideoRenderLayer1和VideoRenderLayer5共用的一個(gè)視頻軌道,這是因為蘋(píng)果對視頻軌道有限制,我們需要盡量的重用,每條視頻軌道對應一個(gè)解碼器,當解碼器數量超出系統限制時(shí),會(huì )出現無(wú)法解碼的錯誤。框架視頻軌道重用的原則是,如果要放入的 VideoRenderLayer 與之前視頻軌道的VideoRenderLayer在時(shí)間上沒(méi)有交集,則可以重用這個(gè)視頻軌道,所有視頻軌道都重用不了則新增一個(gè)視頻軌道。當然這些其實(shí)都不重要,因為都封裝在了VideoLab里面。

讓我們接著(zhù)聊下添加音頻軌道,添加音頻軌道第一步是將RenderLayer 轉換為AudioRenderLayer,AudioRenderLayer是框架內部對象,包含一個(gè)RenderLayer,主要負責將RenderLayer的音頻軌道添到AVComposition中。可轉換為AudioRenderLayer的RenderLayer只需滿(mǎn)足一個(gè)條件:Source包含音頻軌道。轉換AudioRenderLayer之后如右圖所示。

添加音頻軌道的第二步,將AudioRenderLayer視頻軌道添加到AVComposition中,對于RenderLayer的Source包含音頻軌道的AudioRenderLayer,從Source中獲取音頻AVAssetTrack,添加到AVComposition。
如右圖所示,不同于視頻軌道的重用,音頻的每個(gè)AudioRenderLayer都對應一個(gè)音頻軌道。這是由于一個(gè)AVAudioMixInputParameters與一個(gè)音頻的軌道一一對應,而其音高設置(audioTimePitchAlgorithm)作用于整個(gè)音頻軌道。如果重用的話(huà),會(huì )存在一個(gè)音頻軌道有多個(gè)AudioRenderLayer的情況,這樣會(huì )導致所有的AudioRenderLayer都要配置同樣的音高,這顯然是不合理的。

接下來(lái)介紹一下關(guān)于渲染的實(shí)現。從前面的AVFoundation介紹可知,AVVideoComposition可以用來(lái)指定渲染大小和渲染縮放,以及幀率。此外,還有一組存儲了混合參數的指令。有了這些指令后,AVVideoComposition可以通過(guò)自定義混合器來(lái)混合對應的圖像幀。
第一步是創(chuàng )建指令,我們會(huì )在時(shí)間軸上標記每個(gè)VideoRenderLayer的起始時(shí)間點(diǎn)與結束時(shí)間點(diǎn)。然后為每個(gè)時(shí)間間隔創(chuàng )建一個(gè)Instruction,與時(shí)間間隔有交集的VideoRenderLayer,都作為Instruction的混合參數。
然后我們對前面的Compositor工作流程做一個(gè)更新,將混合參數更新為與Instruction有交集的VideoRenderLayer組。對于混合規則的話(huà),是按層級渲染,從下往上。如當前層級有紋理則先處理自己的紋理,再混合進(jìn)前面的紋理。

從前面的AVFoundation介紹可知,AVAudioMix用于處理音頻。包含一組的AVAudioMixInputParameters,可以設置實(shí)時(shí)處理音頻,指定音高算法。音頻混合比較簡(jiǎn)單,只要為每個(gè)AudioRenderLayer創(chuàng )建了一個(gè)AVAudioMixInputParameters即可。
6. VideoLab后續計劃
前面介紹了VideoLab的設計與實(shí)現。當然要做一個(gè)好的開(kāi)源框架還需要不斷的完善,接下來(lái)介紹一些VideoLab后續的計劃。

首先是支持OpenGL,GL還是目前大多數公司選擇渲染引擎的首選,VideoLab 的規劃是能同時(shí)支持Metal + OpenGL,使用方?jīng)Q定渲染引擎使用Metal或Open GL。其次會(huì )持續完善特性,如變速、更便捷的轉場(chǎng)使用方式。接下來(lái)會(huì )開(kāi)始寫(xiě)有UI交互的Demo,這樣可能會(huì )更直接一些。最后,當然期望VideoLab是可以跨平臺,期望是上層能有統一的C++封裝API,統一設計思路,底下用各自的平臺優(yōu)勢,比如iOS用AVFoundation 做編解碼,Android用 FFmpeg;iOS用Metal/GL,Android用Vulkan/GL。
最后也是期望能有更多的人參與維護,畢竟一個(gè)人的能力比較有限,大家一起維護能有更多的未來(lái)暢想。
這里附帶上Github地址:https://github.com/ruanjx/VideoLab
以上是我的全部分享,謝謝大家。
【免責聲明】本文僅代表作者本人觀(guān)點(diǎn),與CTI論壇無(wú)關(guān)。CTI論壇對文中陳述、觀(guān)點(diǎn)判斷保持中立,不對所包含內容的準確性、可靠性或完整性提供任何明示或暗示的保證。請讀者僅作參考,并請自行承擔全部責任。
相關(guān)閱讀:
- ·專(zhuān)訪(fǎng)RingCentral鈴盛陳志豪:未來(lái)被重新定義,企業(yè)該如何應對2021-12-15 14:26:01