一、引言
在短視頻、Vlog、剪輯工具日益流行的今天,給視頻添加 Logo、水印、時間戳或動態貼紙,已經成為非常常見的功能需求。這類效果看似簡單,其實背后都涉及到“圖層合成”的處理:如何將一個靜態或動態的圖層(如文字、圖片、動畫)與原始視頻內容進行有效疊加,并最終導出成可播放的視頻文件?
在 AVFoundation 中,這類功能主要依賴兩個關鍵能力:
- AVMutableVideoComposition:用于控制視頻渲染過程,包括輸出尺寸、幀率、圖層結構等;
- AVVideoCompositionCoreAnimationTool:負責將 Core Animation 中的?CALayer?圖層渲染到視頻幀中。
借助這套機制,我們不僅可以給視頻打水印、添加動態文字,還可以實現富有表現力的貼紙動畫,甚至是一些 UI 動效。
本篇文章,我們將從理論出發,深入講解 AVFoundation 圖層合成的實現原理與關鍵組件;在下一篇中,我們將結合 Demo,動手實現一個視頻添加水印與貼紙的完整流程。
二、AVMutableVideoComposition 簡介
在 AVFoundation 中,AVMutableVideoComposition?是一個非常重要的類,它描述了如何將一個或多個視頻軌道中的幀,渲染成最終輸出的視頻幀序列。如果說?AVComposition?管理的是時間線與素材軌道的關系,那么?AVMutableVideoComposition?就是對最終“視覺輸出效果”的控制。
簡單來說,它負責解決兩個問題:
- 輸出的視頻畫面長什么樣?(尺寸、幀率、背景、變換等)
- 每一幀的渲染順序與合成邏輯是什么?(比如加濾鏡、加圖層)
2.1 主要屬性解析
??renderSize
- 指定最終輸出的視頻畫面尺寸(例如:1080x1920)。
- 所有圖層都必須在這個坐標系統內進行布局。
- 如果設置錯誤,可能導致圖層不顯示、導出失敗等問題。
??frameDuration
- 控制每一幀的時間間隔,常見為?CMTime(value: 1, timescale: 30),表示 30fps。
- 若設置與素材幀率不符,可能會影響播放流暢度。
??instructions
- 類型為?[AVVideoCompositionInstructionProtocol],用于指定視頻軌道在不同時間段的渲染邏輯。
- 每個?AVVideoCompositionInstruction?可以配置一個或多個?AVVideoCompositionLayerInstruction。例如:視頻的縮放、旋轉、透明度變化;多視頻軌的合成順序。
2.2 在視頻編輯流程中的作用
可以把?AVMutableVideoComposition?理解為“渲染層上的指揮官”:
- 它并不直接操作素材,而是告訴 AVFoundation:“請按這個尺寸、順序和方式來渲染畫面。”
- 你可以在其上套用濾鏡、疊加文字、添加動畫圖層等。
配合使用?AVVideoCompositionCoreAnimationTool,它甚至可以將 UIKit/CoreAnimation 的圖層(如?CALayer、CATextLayer、CAShapeLayer)渲染進每一幀視頻中,從而實現豐富的視覺效果。
2.3 一個最簡單的使用例子(代碼預覽)
let videoComposition = AVMutableVideoComposition()
videoComposition.renderSize = CGSize(width: 1080, height: 1920)
videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
videoComposition.instructions = [mainInstruction]
這個對象可以作為參數傳給?AVAssetExportSession,用于控制導出時的畫面合成方式。
三、添加圖層的關鍵機制:Core Animation Tool
雖然?AVMutableVideoComposition?能夠控制視頻渲染的尺寸和幀率,但它本身并不負責圖層的繪制。如果我們想在視頻中疊加水印、文字、貼紙甚至動畫,必須借助 AVFoundation 提供的圖層合成機制 ——?AVVideoCompositionCoreAnimationTool。
這個工具類是連接 AVFoundation 與 Core Animation 的橋梁,它允許我們把?CALayer?圖層樹渲染到每一幀視頻畫面上,從而實現豐富的視覺效果。
3.1 什么是 AVVideoCompositionCoreAnimationTool?
AVVideoCompositionCoreAnimationTool?是?AVVideoComposition?的一個可選屬性,用于在視頻導出時,把你設置的圖層渲染到輸出幀中。
其典型用途包括:
- 添加圖片水印(如 Logo)
- 添加文本(標題、時間戳)
- 添加動畫貼紙、表情
- 使用 CAAnimation 實現復雜動畫效果(如移動、淡入淡出等)
一句話總結:它讓你能“畫在視頻上”。
3.2 如何使用 Core Animation Tool?
使用它的方式非常固定,關鍵是構造一個圖層結構并設置:
videoComposition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer,in: parentLayer
)
- videoLayer:承載視頻幀的圖層,系統會把每幀畫面渲染到這個圖層上。
- parentLayer:容器圖層,包含?videoLayer?以及其他你希望疊加的圖層(如水印圖層、文字圖層等)。
最終,整個圖層結構被合成渲染到每一幀輸出畫面中。
3.3 圖層結構示意圖
推薦使用如下結構(從上到下是層級):
parentLayer
├── videoLayer ? ? ?(負責承載視頻幀)
├── watermarkLayer ?(圖片水印)
├── textLayer ? ? ? (文字/字幕)
└── animationLayer ?(動態貼紙等)
?? 注意:所有圖層的尺寸都應該與?videoComposition.renderSize?完全一致,否則可能出現錯位、無法渲染等問題。
3.4 坐標系說明(易錯點)
- CALayer?使用的是?左上角為 (0, 0)?的坐標系(與 UIKit 相反)
- 所有位置、尺寸都要基于?renderSize?計算,比如:
watermarkLayer.frame = CGRect(x: renderSize.width - 100, y: renderSize.height - 100, width: 80, height: 80)
- 圖層默認透明背景,疊加時會自動覆蓋下方內容
3.5 添加動畫圖層
由于?CALayer?支持?CAAnimation,你可以給圖層添加任意動畫,例如:
let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 1.0
animation.beginTime = AVCoreAnimationBeginTimeAtZero + 2.0
animation.isRemovedOnCompletion = false
animation.fillMode = .forwards
watermarkLayer.add(animation, forKey: "fadeIn")
結合?beginTime,你甚至可以控制水印在第幾秒出現,第幾秒消失,做出“動態水印”效果。
3.6 常見問題與陷阱
問題 | 原因 |
---|---|
圖層不顯示 | 坐標錯誤 / 圖層尺寸不匹配 / 未正確加入 parentLayer |
圖層變形 | renderSize 與原素材尺寸不一致 / 圖層未正確拉伸 |
動畫無效 | 沒設置?beginTime?/ 忘記設置?fillMode?/ 圖層動畫未添加成功 |
導出失敗 | 圖層中含有不支持的動畫類型(建議使用基本動畫) |
四、視頻圖層合成的基本結構
在上一節中我們了解了?AVVideoCompositionCoreAnimationTool?的作用和基本用法。接下來我們來具體拆解:如何構建圖層結構,將多個內容合成到視頻畫面中。
在 AVFoundation 的圖層合成中,最常見的操作就是:將原始視頻幀作為底層圖層,并在其上疊加其他視覺元素,例如圖片水印、文本信息、動畫貼紙等。
這背后依賴的是 Core Animation 的圖層樹結構。
4.1 推薦圖層結構
通常我們推薦使用如下的分層方式:
let parentLayer = CALayer()
let videoLayer = CALayer()
let watermarkLayer = CALayer()
let textLayer = CATextLayer()
// 可選:更多圖層(如動態貼紙、時間戳)parentLayer.frame = CGRect(origin: .zero, size: renderSize)
videoLayer.frame = parentLayer.frame
watermarkLayer.frame = CGRect(x: ..., y: ..., width: ..., height: ...)
textLayer.frame = CGRect(x: ..., y: ..., width: ..., height: ...)// 添加順序很關鍵
parentLayer.addSublayer(videoLayer) // 視頻在底層
parentLayer.addSublayer(watermarkLayer) // 水印在上
parentLayer.addSublayer(textLayer) // 文字層
然后將這個?parentLayer?和?videoLayer?一起交給?AVVideoCompositionCoreAnimationTool:
videoComposition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer,in: parentLayer
)
4.2 圖層尺寸與坐標系說明
構建圖層結構時,最容易出錯的是尺寸和坐標系:
屬性 | 要點說明 |
---|---|
尺寸(frame) | 所有圖層尺寸必須與?renderSize?匹配,否則位置和縮放會異常 |
坐標系 | Core Animation 的坐標原點在左上角,y 值向下增長(與 UIKit 相反) |
圖片縮放 | 圖層內容如圖片需要根據目標尺寸進行適配,否則可能拉伸或被裁剪 |
4.3 各類圖層添加方式
? 圖片水印(Logo)
let image = UIImage(named: "logo")!
let watermarkLayer = CALayer()
watermarkLayer.contents = image.cgImage
watermarkLayer.frame = CGRect(x: renderSize.width - 100, y: 20, width: 80, height: 80)
watermarkLayer.opacity = 0.8
? 文本圖層(如標題、用戶名)
let textLayer = CATextLayer()
textLayer.string = "演示視頻 by Pang"
textLayer.fontSize = 24
textLayer.foregroundColor = UIColor.white.cgColor
textLayer.alignmentMode = .center
textLayer.frame = CGRect(x: 0, y: 20, width: renderSize.width, height: 40)
textLayer.contentsScale = UIScreen.main.scale
? 動態圖層(貼紙/動畫)
let stickerLayer = CALayer()
stickerLayer.contents = UIImage(named: "star")?.cgImage
stickerLayer.frame = CGRect(x: 30, y: 30, width: 50, height: 50)// 添加簡單動畫(如旋轉)
let rotation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.fromValue = 0
rotation.toValue = Double.pi * 2
rotation.duration = 2
rotation.repeatCount = .infinity
stickerLayer.add(rotation, forKey: "rotate")
4.4 小貼士:透明背景與抗鋸齒
- CALayer?默認背景是透明的,無需特殊設置
- 為避免文字模糊,textLayer.contentsScale?建議設置為?UIScreen.main.scale
- 所有圖層請避免使用?masksToBounds = true,以免意外裁剪動畫或子圖層
4.5 圖層生命周期說明
這些圖層的渲染,僅在視頻導出(或播放合成 AVPlayerItem 時)被一次性處理。它們在導出完成后就“燒錄”進視頻文件中,無法再修改或交互。因此:
- 不支持用戶拖動、點擊圖層
- 動畫必須提前規劃好時間、路徑、透明度等
五、動態內容支持
前面我們已經構建好了圖層結構,添加了靜態的水印圖像和文字圖層。但在實際項目中,用戶往往希望能看到**“動”的效果**:
比如水印淡入淡出、字幕逐行滾動、貼紙旋轉跳動……這些都需要借助 Core Animation 來實現動態圖層合成。
AVFoundation 本身并不負責動畫邏輯,而是通過?AVVideoCompositionCoreAnimationTool?把 Core Animation 的動畫“燒錄”進每一幀輸出畫面中。因此,我們完全可以使用 Core Animation 的動畫能力,來制作動態效果圖層。
5.1 常見的動態圖層形式
動態效果 | 實現方式 |
---|---|
水印淡入淡出 | CABasicAnimation?作用于?opacity |
貼紙旋轉 | CABasicAnimation?作用于?transform.rotation.z |
圖層移動 | CABasicAnimation?作用于?position |
路徑動畫 | CAKeyframeAnimation?配合貝塞爾曲線路徑 |
動畫序列幀 | 定時切換?contents?或使用?CAKeyframeAnimation |
動態文本滾動 | 修改?position.y?并設置線性動畫 |
5.2 控制動畫時機的關鍵參數
每個動畫圖層必須明確告訴系統:什么時候開始動、動多久。這依賴幾個重要參數:
??beginTime
表示動畫的起始時間(相對于視頻時間的 0 秒)
- 通常設置為:AVCoreAnimationBeginTimeAtZero + 1.0(表示從第 1 秒開始)
- 如果不設置,動畫可能不會生效
??duration
動畫持續時長(單位為秒)
??fillMode
控制動畫結束后的狀態(常用?.forwards)
??isRemovedOnCompletion
設置為?false?可讓動畫結束后保持最終狀態(比如淡入后常駐)
5.3 示例:水印淡入
let fadeIn = CABasicAnimation(keyPath: "opacity")
fadeIn.fromValue = 0
fadeIn.toValue = 1
fadeIn.duration = 1.0
fadeIn.beginTime = AVCoreAnimationBeginTimeAtZero + 2.0
fadeIn.isRemovedOnCompletion = false
fadeIn.fillMode = .forwards
watermarkLayer.add(fadeIn, forKey: "fadeIn")
此動畫表示:第 2 秒開始,1 秒內從透明漸變為可見。
5.4 示例:貼紙旋轉
let rotate = CABasicAnimation(keyPath: "transform.rotation.z")
rotate.fromValue = 0
rotate.toValue = Double.pi * 2
rotate.duration = 2
rotate.repeatCount = .infinity
stickerLayer.add(rotate, forKey: "rotate")
這段代碼會讓貼紙圖層無限循環地旋轉。
5.5 示例:沿路徑移動
let move = CAKeyframeAnimation(keyPath: "position")
move.path = UIBezierPath(ovalIn: CGRect(x: 100, y: 100, width: 200, height: 200)).cgPath
move.duration = 4.0
move.beginTime = AVCoreAnimationBeginTimeAtZero + 1.0
move.repeatCount = 1
move.fillMode = .forwards
move.isRemovedOnCompletion = false
animatedLayer.add(move, forKey: "orbit")
你甚至可以讓圖層沿橢圓路徑飛行!
5.6 動態文本:標題/字幕動效
let titleLayer = CATextLayer()
titleLayer.string = "AVFoundation 視頻合成演示"
titleLayer.fontSize = 28
titleLayer.foregroundColor = UIColor.white.cgColor
titleLayer.alignmentMode = .center
titleLayer.frame = CGRect(x: 0, y: renderSize.height, width: renderSize.width, height: 40)let scroll = CABasicAnimation(keyPath: "position.y")
scroll.fromValue = renderSize.height + 20
scroll.toValue = renderSize.height - 80
scroll.duration = 2
scroll.beginTime = AVCoreAnimationBeginTimeAtZero + 1.0
scroll.fillMode = .forwards
scroll.isRemovedOnCompletion = false
titleLayer.add(scroll, forKey: "scrollIn")
讓標題文字從屏幕底部“滑入”到中間位置,很適合視頻片頭效果。
5.7 動態圖層注意事項
注意點 | 說明 |
---|---|
圖層必須添加到?parentLayer?中 | 否則不會渲染 |
動畫必須設置?beginTime?和?fillMode | 防止動畫不播放或一閃而過 |
所有動畫基于 Core Animation 離屏渲染 | 導出時性能消耗較高,建議控制動畫數量和復雜度 |
導出時間可能顯著增加 | 動畫越復雜,合成時間越長 |
六、結語
本文我們圍繞?AVMutableVideoComposition?和?AVVideoCompositionCoreAnimationTool,深入講解了視頻圖層合成的核心機制。無論是靜態水印、動態貼紙,還是滑入滑出的字幕效果,其本質都是通過構建一個完整的?CALayer樹,并借助 AVFoundation 渲染到每一幀視頻中。
總結起來,視頻圖層合成的核心步驟包括:
- 使用?AVMutableVideoComposition?配置輸出尺寸與幀率;
- 構建?parentLayer?圖層樹,添加視頻層、圖像層、文本層等;
- 通過?AVVideoCompositionCoreAnimationTool?將圖層合成綁定到視頻;
- 根據需要添加?CABasicAnimation?或?CAKeyframeAnimation?實現動效;
- 最終配合?AVAssetExportSession?導出合成后的視頻文件。
雖然過程看起來略顯繁瑣,但一旦理解其中原理,就能靈活實現各種視覺疊加效果,為視頻內容增添專業感與表現力。
下一篇文章中,我們將結合實戰 Demo,實現一個支持添加動態水印與字幕動畫的導出工具,歡迎繼續關注~