移动端音视频领域自从抖音火爆之后开始成为一个备受开发者关注的行业。抖音主推音视频编辑工具“剪映”当之无愧为行业中的佼佼者,放眼整个移动APP音视频市场,算是功能最强大的音视频编辑工具。今天就来讲讲如何在iOS上实现一个类似的音视频编辑引擎。
工欲善其事,必先利其器。我们需要对苹果提供的标准API进行一些个性化定制抽象。
一、AVFoundation 视频编辑API
1、音视频资源组装
在 AVFoundation 中,视频和音频数据可以用 AVAsset 表示,AVAsset 里面包含了 AVAssetTrack 数据。
比如:一个视频文件里面包含了一个视频 track 和两个音频 track。
图:AVAsset 及其子类结构
通过上图发现可以使用 AVComposition 对 track 进行裁剪和变速等操作,也可以把多段 track 拼接到 AVComposition 里面。
例如将多段视频合并为一个视频文件
在处理完 track 的拼接和修改后,得到最终的 AVComposition,它是 AVAsset 的子类,也就是说可以把它传递到 AVPlayer、AVAssetImageGenerator、AVExportSession 和 AVAssetReader 里面作为数据源,把 AVComposition 当成是一个视频数据进行处理。
2、视频画面拼接/混音处理
AVFoundation 提供了 AVVideoComposition 对象和 AVVideoCompositing 协议用于处理视频的画面帧。
图:AVVideoComposition 结构图
2.1AVVideoComposition 支持特性:
设置帧率;
设置输出画面尺寸;
设置不同的 video track 应用何种编辑操作以及可以将视频画面嵌套在 CALayer 中;
设置video track 的编辑(通过设置 AVVideoComposition 里的 instructions 属性实现,它是一个 AVVideoCompositionInstruction 协议数组,AVVideoCompositionInstruction 内定义了处理的时间范围、需要处理的 track ID 有哪些等)
图:AVVideoCompositionInstruction 在时间轴中的组成
重点关注 timerange 20s~30s区间。
将视频画面嵌套在 CALayer 内可以通过设置 AVVideoCompositionCoreAnimationTool 给 AVVideoComposition。
AVVideoCompositionCoreAnimationTool 有两种使用场景:
一是可以添加一个 CALayer 做一个独立的 track 渲染到视频画面上;
二是可以设置一个 parentLayer,然后把视频 layer 放置在这个 parentLayer 上,并且还可以放入其它的 layer。
图:AVVideoCompositionCoreAnimationTool 设置的 layer 层级
通过上述的动画配置可以实现一些简单的画面融合及动画效果,如果想实现类似抖音中丰富的画面效果,我们可能就需要使用AVVideoCompositing了。
AVVideoCompositing 这个协议可以接管视频画面的渲染。
实现了AVVideoCompositing 协议的类中,AVComposition 处理到某一时间点的视频时,会向AVVideoCompositing 发起请求,AVVideoCompositing 内根据请求包含的视频帧、时间信息、画布大小信息等,根据具体的业务逻辑进行处理,最后将处理后的视频数据返回。
例如 介绍AVVideoCompositionInstruction中timerange:20s~30s这个时间区间,AVVideoCompositing会同时接收到videoTrack1和videoTrack2的画面解码,然后对画面做一些复杂的图层效果处理(包括滤镜处理)。
2.2 AVFoundation 中的音频处理
AVFoundation 提供了 AVAudioMix 用于处理音频数据。 AVAudioMix 这个类很简单,只有一个 inputParameters 属性,它是一个 AVAudioMixInputParameters 数组。具体的音频处理都在 AVAudioMixInputParameters 里进行配置。
不同于 AVVideoComposition 的 instructions,AVVideoCompositionInstruction 可以传入多个 trackID 方便之后多个视频画面进行合成。AVAudioMixInputParameters 只能绑定单个 AVAssetTrack 的音频数据。
AVAudioMixInputParameters 内可以设置音量,支持分段设置音量,以及设置两个时间点的音量变化,比如 0 - 1 秒,音量大小从 0 - 1.0 线性递增。
AVAudioMixInputParameters 内还有个 audioTapProcessor 属性,他是一个 MTAudioProcessingTap 类。这个属性提供了接口用于实时处理音频数据。
总结一下 AVFoundation中常用的几个类,避免混淆
AVAsset 所有媒体资源的承载类。可以是音频,视频,图像。
AVMutableComposition 用于组合AVAsset的音视频轨道。
AVMutableCompositionTrack 分为视频轨道,音频轨道,它由AVAsset内部解码生成。
AVMutableVideoComposition 视频编辑指令管理类,它是整个视频编辑里动画指令组合。
AVMutableVideoCompositionInstruction 视频视图编辑指令,可以用它来设置视频的背景颜色等。它可以包含一组AVVideoCompositionLayerInstruction。
AVMutableVideoCompositionLayerInstruction 视频层编辑指令,可以用它设置视频的transform,opacity等。
二、音视频编辑框架
通过对AVFoundation常用API的了解,你一定也想到通过对AVVideoCompositing作为突破口,只要获取解码后实时视频数据,我们就可以很方便的对进行画面效果处理。但是这个API只支持视频的输出,那么当我们希望将多张图片/图片+视频合成为视频的时候该如何处理呢?
由于AVFoundation的图层特效能力有限、不支持图片等数据流进行视频合成,所以我们需要自己进行图片和视频的基于时间线进行排布并进行驱动的引擎框架,来实现音视频数据从解码到画面特效应用,最终写入文件或者进行预览整个流程。
2.1 音视频处理基本流程
工作流程:
对视频/图片资源进行时间线排布以及视频特效和音频特效描述 (资源出现顺序、转场信息、音频混合、音频声音大小控制,画面特效)
将视频或者图片进行解码得到相应的原始画面
应用相应的画面Transform
应用相应的视频特效
得到最终画面和最终音频数据
写入文件或者预览输出音频播放
其中视频资源解码使用AVAssetReader ,文件写入使用AVAssetWriter, 中间画面特效部分可以使用OpenGL ES或者Metal API进行GPU处理
2.2跨平台支持
考虑到框架可以很方便的应用到安卓/服务端/PC,做到跨平台。游戏平台有一个很不错的跨平台轻型引擎 cocos2d,整体处理流程和我们要做的事情有点相似。
Cocos2d采用场景树结构来管理游戏对象,把一个游戏划分为不同的场景,场景又可以分为不同的层,一个层又可以拥有任意个可见的游戏对象.游戏对象又可以执行Action来修改其属性.每一个时刻都有一个场景在独立运行,通过切换不同的场景来完成一个游戏流程.cocos2d还采用了引用计数的方式来管理内存,基本上所有的类都派生于拥有引用计数的机制的CCObject.
总结起来调用关系简化为CCDirector→CCScene→CCLayer→CCSprite→CCAction.
音视频引擎需要对不同的特效进行分层,每个特效都对应一个Action,每一个特效在时间线中是变化的,对应为一个Node及Node的参数列表和动画列表.
架构图如下
VdieoEngine:VE引擎核心,构建原始资源(图片/视频/音频)时间线排布, 根据特效描述文件Node创建相关的Action,及不同分层的LayerTree.
CompostionDirector:类似cocos2d中的CCDirector,负责VE中所有资源的管理及调度
Schedule:时间驱动器 更新视频帧及图片帧时间戳,并应用对应时间戳的特效
ActionBuilder:Action实例化工厂类
FilterLayer:不同特效分层
2.3 特效树
特效及资源节点总的来说可以描述为下面的树状结构
2.4 整体架构图
特效资源描述文件包:
字体包(用于特定字体显示)
mp3文件(用于背景音乐)
图片(用于特效混合效果)
视频(用于特效混合效果)
glsl文件(定制特效shader)
Splice.xml文件(描述拼接)
Fitler.xml文件(描述全局特效)
Transition.xml文件(描述转场)
资源动态下载后,通过资源管理模块进行资源解析,传入VideoEngine,进行画面预览及最终视频画面导出。
2.4示例代码
本项目部分代码已开源,欢迎下载体验,https://github.com/guolai/BBZVideoEngine (由于项目已用于shopee 音视频编辑项目,因此部分重要功能并未开源)
创建model
BBZVideoModel *videoModel = [[BBZVideoModel alloc] init];
加入图片资源
NSString *path = [[NSBundle mainBundle] pathForResource:@"IMG_7305" ofType:@"HEIC" inDirectory:@"Resource"];
[videoModel addImageSource:path];
加入视频资源
NSString *path = [[NSBundle mainBundle] pathForResource:@"douyin3" ofType:@"mp4" inDirectory:@"Resource"];
[videoModel addVideoSource:path];
加入背景音乐
NSString *path = [[NSBundle mainBundle] pathForResource:@"jimoshazhouleng" ofType:@"mp3" inDirectory:@"Resource"];
[videoModel addAudioSource:path];
指定转场资源路径
NSString *path = [NSString stringWithFormat:@"%@/Resource/demo2", [[NSBundle mainBundle] bundlePath]];
[videoModel addTransitionGroup:path];
加入视频或者图集 支持加入背景图片,并对内容进行缩放和旋转
NSString *path = [[NSBundle mainBundle] pathForResource:@"IMG_7305" ofType:@"HEIC" inDirectory:@"Resource"];
NSData *data = [NSData dataWithContentsOfFile:path];
UIImage *bgImage = [UIImage imageWithData:data];
videoModel.bgImage = bgImage;
BBZTransformItem *transformItem = [[BBZTransformItem alloc] init];
transformItem.scale = 0.8;
transformItem.angle = 45.0;
videoModel.transform = transformItem;
视频水印支持动态图片序列帧
NSMutableArray *multiArray = [NSMutableArray array];
for (int i = 1; i < 10; i++) {
NSString *strName = [NSString stringWithFormat:@"00%d@2x", i];
NSString *icon = [[NSBundle mainBundle] pathForResource:strName ofType:@"png" inDirectory:@"Resource/icon"];
NSData *data = [NSData dataWithContentsOfFile:icon];
UIImage *image = [UIImage imageWithData:data];
[multiArray addObject:image];
}
videoModel.maskImage = multiArray;
启动合成任务
BBZExportTask *task = [BBZExportTask taskWithModel:videoModel];
task.completeBlock= ^(BOOL sucess, NSError *error) {
};
task.progressBlock = ^(CGFloat progress) {
};
[task start];