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

(十九)深入了解 AVFoundation-编辑:使用 AVMutableVideoComposition 实现视频加水印与图层合成(上)——理论篇

一、引言

在短视频、Vlog、剪辑工具日益流行的今天,给视频添加 Logo、水印、时间戳或动态贴纸,已经成为非常常见的功能需求。这类效果看似简单,其实背后都涉及到“图层合成”的处理:如何将一个静态或动态的图层(如文字、图片、动画)与原始视频内容进行有效叠加,并最终导出成可播放的视频文件?

在 AVFoundation 中,这类功能主要依赖两个关键能力:

  • AVMutableVideoComposition:用于控制视频渲染过程,包括输出尺寸、帧率、图层结构等;
  • AVVideoCompositionCoreAnimationTool:负责将 Core Animation 中的 CALayer 图层渲染到视频帧中。

借助这套机制,我们不仅可以给视频打水印、添加动态文字,还可以实现富有表现力的贴纸动画,甚至是一些 UI 动效。

本篇文章,我们将从理论出发,深入讲解 AVFoundation 图层合成的实现原理与关键组件;在下一篇中,我们将结合 Demo,动手实现一个视频添加水印与贴纸的完整流程。

二、AVMutableVideoComposition 简介

在 AVFoundation 中,AVMutableVideoComposition 是一个非常重要的类,它描述了如何将一个或多个视频轨道中的帧,渲染成最终输出的视频帧序列。如果说 AVComposition 管理的是时间线与素材轨道的关系,那么 AVMutableVideoComposition 就是对最终“视觉输出效果”的控制。

简单来说,它负责解决两个问题:

  1. 输出的视频画面长什么样?(尺寸、帧率、背景、变换等)
  2. 每一帧的渲染顺序与合成逻辑是什么?(比如加滤镜、加图层)

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 渲染到每一帧视频中。

总结起来,视频图层合成的核心步骤包括:

  1. 使用 AVMutableVideoComposition 配置输出尺寸与帧率;
  2. 构建 parentLayer 图层树,添加视频层、图像层、文本层等;
  3. 通过 AVVideoCompositionCoreAnimationTool 将图层合成绑定到视频;
  4. 根据需要添加 CABasicAnimation 或 CAKeyframeAnimation 实现动效;
  5. 最终配合 AVAssetExportSession 导出合成后的视频文件。

虽然过程看起来略显繁琐,但一旦理解其中原理,就能灵活实现各种视觉叠加效果,为视频内容增添专业感与表现力。

下一篇文章中,我们将结合实战 Demo,实现一个支持添加动态水印与字幕动画的导出工具,欢迎继续关注~

http://www.dtcms.com/a/291047.html

相关文章:

  • 【每日算法】专题四_前缀和
  • 算法-比较排序
  • Redis入门教程(一):基本数据类型
  • ppp实验
  • BEVformer个人理解与解读
  • 2025暑期—02卷积与滤波-边缘检测
  • 180页PPT烟草集团物流数字化架构设计咨询指南
  • 牛客网题解 | 单词识别
  • 宝塔访问lnmp项目,跳转不到项目根目录问题解决
  • Spring关于依赖注入的几种方式和Spring配置文件的标签
  • 大模型后训练——SFT实践
  • (SAM)Segment Anything论文精读(逐段解析)
  • 磁悬浮轴承振动的智能克星:自适应陷波器设计与DSP实现全解析
  • 有关Spring的总结
  • 解决 Ant Design v5.26.5 与 React 19.0.0 的兼容性问题
  • CMake与catkin_make的find_package()命令使用说明
  • 《使用Qt Quick从零构建AI螺丝瑕疵检测系统》——1. 启航:你的第一个工业视觉应用
  • C/C++ 详谈结构体大小计算(内存对齐)
  • 基于 HAProxy 搭建 EMQ X 集群
  • vscode创建vue项目报错
  • 如何判断自己的电脑或主机是否支持DDR5内存?
  • Android 默认图库播放视频没有自动循环功能,如何添加2
  • MVC模式
  • vcruntime140_1.dll文件丢失?终极修复指南:从错误分析到修复全流程
  • Ubuntu 22.04 使用 Docker 安装 Redis 5 (安装包形式)
  • linux定时器使用
  • AD域控制器虚拟化的安全加固最佳实践
  • 从IR到DS的转化过程中,如何确保各阶段需求不偏离用户原始场景?有哪些验证方法?
  • 吴恩达 机器学习cs229-学习笔记-更新中
  • 动静态库原理与实战详解