一、引言

在短视频、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.pswp.cn/web/90188.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/90188.shtml
英文地址,请注明出处:http://en.pswp.cn/web/90188.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android NDK与JNI深度解析

核心概念定义:NDK (Native Development Kit): 是什么: 一套由 Google 提供的工具集合。目的: 允许 Android 开发者使用 C 和 C 等原生(Native)语言来实现应用程序的部分功能。包含内容: 交叉编译器&#xf…

Golang各版本特性

1. Go各版本特性 | FeelingLife 2. https://chatgpt.com/share/68808f58-ae5c-800a-8153-5358098f301b 3.https://tonybai.com/2024/11/14/go-map-use-swiss-table/

HTML 转 Word API 接口

HTML 转 Word API 接口 支持网页转 Word,高效转换为 Word,提供永久链接。 1. 产品功能 超高性能转换效率;支持将传递的 HTML 转换为 Word,支持 HTML 中的 CSS 格式在 Word 文档中的呈现;支持传递网站的 URL&#xff…

Lucid Search: 极简、隐私友好的问答式搜索引擎技术解析

Lucid Search: 极简、隐私友好的问答式搜索引擎技术解析 产品定位与价值主张 Lucid Search 是一款革命性的问答式搜索引擎,其核心价值在于: 极简体验:无账户、无广告、前端完全静态隐私保护:不写入 Cookie、不记录 IP、无追踪即…

卷积神经网络:模型评估标准

一、分类模型评价指标在模型评估中,有多个标准用于衡量模型的性能,这些标准包括准确率(Accuracy)、精确率(Precision)、召回率(Recall)、F1 分数(F1-Score)等…

【前端工程化】前端开发中想做好发布管理可以从哪些方面着手?

在企业级后台系统中,发布管理是整个开发流程的最终环节,也是最为关键的一环。它不仅涉及代码构建完成后的部署操作,还包括版本控制、灰度发布、回滚机制等保障系统稳定性的措施。 本文主要围绕发布流程设计、版本控制、部署方式、灰度策略和回…

替分布式=成本下降50% !

在数字化转型的浪潮中,数据库作为医疗信息系统的“心脏”,其稳定性与效率直接关乎医疗服务的质量。2024年10月30日,绵阳市第三人民医院集成平台的CDR数据库成功从分布式数据库Citus切换为国产集中式数据库KingbaseES,并稳定运行至…

【Linux系统编程】基础指令

基础指令1. adduser指令&&passwd指令2. userdel指令3. pwd指令4. ls指令5. cd指令6. tree指令7. touch指令8. mkdir指令9. rmdir指令&&rm指令10. man指令11. cp指令12. mv指令13. cat指令14. more指令15. less指令16. head指令17. tail指令18. date指令19. cal…

区块链之以太坊Hardhat开发框架——部署在windows为例

Hardhat 提供了一个灵活且易于使用的开发环境,可以轻松地编写、测试和部署智能合约。Hardhat还内置了Hardhat 网络(Hardhat Node),它是为开发而设计的本地以太坊网络。 下面是hardhat的官方文档 https://hardhat.org/hardhat-ru…

Ubuntu 1804 编译ffmpeg qsv MediaSDK libva 遇到的问题记录

之前都是 用的xeon服务器的cpu 不支持intel QSV 硬件加速 最近把自己的 14年买的pc机装上了ubuntu 1804 然后准备开启ffmpeg qsv 硬件加速功能 CPU i3-4170 内存DDR3 16G 硬盘机械盘500G 主板ASUS B85M-G首先安装vainfo工具apt install vainfo装完提示如下出错了 网上说是…

Elasticsearch(ES)介绍和安装

目录 一、Elasticsearch(ES)介绍 1.为什么需要单独的搜索服务 2.全文检索 3.Elasticsearch简介 1.Elasticsearch的特点 2.应用场景 3.ElasticSearch数据的存储和搜索原理 二、Elasticsearch(ES)安装 1、拉取镜像 2、创建目录并给目录赋权 3、创建并编辑配置文件 4、…

html结构解析

<!DOCTYPE html>&#xff1a;声明为 HTML5 文档 <html lang"zh-CN">&#xff1a;根元素&#xff0c;指定页面语言为中文 <meta charset"UTF-8">&#xff1a;设置字符编码&#xff0c;确保中文正常显示 <meta name"viewport"…

面试150 最大子数组和

思路 贪心法&#xff1a;设定最小标志result为float(‘-inf’),遍历一次数组元素进行求和&#xff0c;如果当前元素大于result&#xff0c;则更新result的值&#xff0c;如果sum小于0&#xff0c;则重新置0进行计算&#xff0c;最后返回result class Solution:def maxSubArray(…

MyBatis动态SQL实战:告别硬编码,拥抱智能SQL生成

MyBatis动态SQL实战&#xff1a;告别硬编码&#xff0c;拥抱智能SQL生成在电商平台的用户管理模块中&#xff0c;需要面对多种不同的用户查询组合条件。当使用传统的硬编码SQL方式时&#xff0c;代码膨胀到了2000多行&#xff0c;维护成本极高。而引入MyBatis动态SQL后&#xf…

Web前端开发:JavaScript遍历方法详解与对比

1. 传统 for 循环const arr [10, 20, 30]; for (let i 0; i < arr.length; i) {console.log(索引 ${i}: 值 ${arr[i]}); } // 输出&#xff1a; // 索引 0: 值 10 // 索引 1: 值 20 // 索引 2: 值 30特点&#xff1a;最基础的循环&#xff0c;可通过索引精准控制适用场景&…

Python 爬虫(一):爬虫伪装

目录 1 简介2 伪装策略 2.1 Request Headers 问题2.2 IP 限制问题 3 总结 1 简介 对于一些有一定规模或盈利性质比较强的网站&#xff0c;几乎都会做一些防爬措施&#xff0c;防爬措施一般来说有两种&#xff1a;一种是做身份验证&#xff0c;直接把虫子挡在了门口&#xff…

TODAY()-WEEKDAY(TODAY(),2)+1

这个Excel公式 TODAY()-WEEKDAY(TODAY(),2)1 用于计算 当前周的周一日期。下面详细解释它的逻辑和用法&#xff1a;公式解析TODAY()返回当前日期&#xff08;例如今天是2023年12月20日&#xff0c;则 TODAY() 2023/12/20&#xff09;。WEEKDAY(TODAY(), 2)计算当前日期是星期几…

Fast Frequency Estimation Algorithm by Least Squares Phase Unwrapping

I. 引言 单个含噪正弦信号的频率估计是一个研究已久的问题&#xff0c;并有多种应用[1]。在高斯白噪声假设下&#xff0c;最大似然(ML)频率估计器是Rife和Boorstyn [2]中提出的周期图估计器&#xff0c;其中傅里叶变换用于搜索周期图的最大值。周期图估计器被广泛认为是单频估计…

C语言常见的预定符号常量

C语言常见的预定符号常量C 语言提供了丰富的预定义符号常量&#xff0c;分布在不同头文件中&#xff0c;用于获取编译信息、数值范围、浮点特性等关键信息。以下是常见预定义符号常量的分类总结&#xff1a;一、预定义宏&#xff08;编译时信息&#xff09;由编译器自动定义&am…

【2025】使用vue构建一个漂亮的天气卡片

1. 核心框架&#xff1a;Vue Vue 以其轻量、易用、响应式数据绑定的特点&#xff0c;非常适合快速构建这类小型界面组件。即使是直接通过 CDN 引入&#xff0c;也能高效开发&#xff0c;降低项目复杂度&#xff0c;无需搭建完整工程化环境 。 2. 网络请求&#xff1a;Axios 用于…