内容简介:)如题所示,本文的目标是将5段独立的小视频合成一段完整的视频,各视频间穿插溶解消失、从右往左推的转场过渡效果。涉及到的类在AVFoundation框架中的关系如图所示,可知要达成开头的目标,核心是要构建出两个类,AVCompostion和AVVideoComposition。(这两个类虽然从名字上看有某种关系,但事实上并不存在继承或什么关系)
)
如题所示,本文的目标是将5段独立的小视频合成一段完整的视频,各视频间穿插溶解消失、从右往左推的转场过渡效果。
整体脉络
涉及到的类在AVFoundation框架中的关系如图所示,可知要达成开头的目标,核心是要构建出两个类,AVCompostion和AVVideoComposition。(这两个类虽然从名字上看有某种关系,但事实上并不存在继承或什么关系)
AVComposition是AVAsset的子类,从概念上可以理解为AVAsset是资源的宏观整体描述,AVCompostion,组合,更偏向于微观的概念。组合,顾名思义,可以将几段视频、几段音频、字幕等等组合排列成可播放可导出的媒体资源。
AVVideoComposion,视频组合,描述了终端该如何处理、显示AVCompostion中的多个视频轨道。画面(AVCompostion) + 如果显示(AVVideoCompostion) = 最终效果
实现细节
总流程
此处建议配合代码食用,效果更佳。 demo
override func viewDidLoad() {
super.viewDidLoad()
prepareResource()
buildCompositionVideoTracks()
buildCompositionAudioTracks()
buildVideoComposition()
export()
}
复制代码
可以看到代码思路和上一节是一致的:
- 从Bundle中读取视频片段;
- 创建空白的视频片段composition,提取资源视频片段中的视频轨道和音频轨道,插入到compostion中的相应轨道中;
- 构建视频组合描述对象,实现转场动画效果;
- 合成导出为新的视频片段;
创建视频轨道
func buildCompositionVideoTracks() {
//使用invalid,系统会自动分配一个有效的trackId
let trackId = kCMPersistentTrackID_Invalid
//创建AB两条视频轨道,视频片段交叉插入到轨道中,通过对两条轨道的叠加编辑各种效果。如0-5秒内,A轨道内容alpha逐渐到0,B轨道内容alpha逐渐到1
guard let trackA = composition.addMutableTrack(withMediaType: .video, preferredTrackID: trackId) else {
return
}
guard let trackB = composition.addMutableTrack(withMediaType: .video, preferredTrackID: trackId) else {
return
}
let videoTracks = [trackA,trackB]
//视频片段插入时间轴时的起始点
var cursorTime = CMTime.zero
//转场动画时间
let transitionDuration = CMTime(value: 2, timescale: 1)
for (index,value) in videos.enumerated() {
//交叉循环A,B轨道
let trackIndex = index % 2
let currentTrack = videoTracks[trackIndex]
//获取视频资源中的视频轨道
guard let assetTrack = value.tracks(withMediaType: .video).first else {
continue
}
do {
//插入提取的视频轨道到 空白(编辑)轨道的指定位置中
try currentTrack.insertTimeRange(CMTimeRange(start: .zero, duration: value.duration), of: assetTrack, at: cursorTime)
//光标移动到视频末尾处,以便插入下一段视频
cursorTime = CMTimeAdd(cursorTime, value.duration)
//光标回退转场动画时长的距离,这一段前后视频重叠部分组合成转场动画
cursorTime = CMTimeSubtract(cursorTime, transitionDuration)
} catch {
}
}
}
复制代码
具体代码含义都有对应注释,需要解释的是A、B双轨道的思路,如下图所示。
AVVideoCompostion对象的layerInstruction数组属性,会按排列顺序显示对应轨道的画面。我们可以通过自定义共存区的显示逻辑来塑造出不同的转场效果。以1,2共存区为例,在duration内,1画面alpha逐渐到0,2画面alpha逐渐到1,就会有溶解的效果;
因此,为了实现转场效果,在构造视频轨道时,就采用了AB交叉的思路。如果只是单纯的视频拼接,完全可以放到同一条视频轨道中。
buildCompositionAudioTracks() 和构造视频轨道思路一致,不再赘述。
筛选多片段共存区域
/// 设置videoComposition来描述A、B轨道该如何显示
func buildVideoComposition() {
//创建默认配置的videoComposition
let videoComposition = AVMutableVideoComposition.init(propertiesOf: composition)
self.videoComposition = videoComposition
filterTransitionInstructions(of: videoComposition)
}
/// 过滤出转场动画指令
func filterTransitionInstructions(of videoCompostion: AVMutableVideoComposition) -> Void {
let instructions = videoCompostion.instructions as! [AVMutableVideoCompositionInstruction]
for (index,instruct) in instructions.enumerated() {
//非转场动画区域只有单轨道(另一个的空的),只有两个轨道重叠的情况是我们要处理的转场区域
guard instruct.layerInstructions.count > 1 else {
continue
}
var transitionType: TransitionType
//需要判断转场动画是从A轨道到B轨道,还是B-A
var fromLayerInstruction: AVMutableVideoCompositionLayerInstruction
var toLayerInstruction: AVMutableVideoCompositionLayerInstruction
//获取前一段画面的轨道id
let beforeTrackId = instructions[index - 1].layerInstructions[0].trackID;
//跟前一段画面同一轨道的为转场起点,另一轨道为终点
let tempTrackId = instruct.layerInstructions[0].trackID
if beforeTrackId == tempTrackId {
fromLayerInstruction = instruct.layerInstructions[0] as! AVMutableVideoCompositionLayerInstruction
toLayerInstruction = instruct.layerInstructions[1] as! AVMutableVideoCompositionLayerInstruction
transitionType = TransitionType.Dissolve
}else{
fromLayerInstruction = instruct.layerInstructions[1] as! AVMutableVideoCompositionLayerInstruction
toLayerInstruction = instruct.layerInstructions[0] as! AVMutableVideoCompositionLayerInstruction
transitionType = TransitionType.Push
}
setupTransition(for: instruct, fromLayer: fromLayerInstruction, toLayer: toLayerInstruction,type: transitionType)
}
}
复制代码
这段代码通过已经构建好音视频轨道的composition对象来初始化对应的VideoCompostion描述对象,再从中筛选出我们关心的描述重叠区域的指令,通过修改指令来达到自定义显示效果的目标。
添加转场效果
func setupTransition(for instruction: AVMutableVideoCompositionInstruction, fromLayer: AVMutableVideoCompositionLayerInstruction, toLayer: AVMutableVideoCompositionLayerInstruction ,type: TransitionType) {
let identityTransform = CGAffineTransform.identity
let timeRange = instruction.timeRange
let videoWidth = self.videoComposition.renderSize.width
if type == TransitionType.Push{
let fromEndTranform = CGAffineTransform(translationX: -videoWidth, y: 0)
let toStartTranform = CGAffineTransform(translationX: videoWidth, y: 0)
fromLayer.setTransformRamp(fromStart: identityTransform, toEnd: fromEndTranform, timeRange: timeRange)
toLayer.setTransformRamp(fromStart: toStartTranform, toEnd: identityTransform, timeRange: timeRange)
}else {
fromLayer.setOpacityRamp(fromStartOpacity: 1.0, toEndOpacity: 0.0, timeRange: timeRange)
}
//重新赋值
instruction.layerInstructions = [fromLayer,toLayer]
}
复制代码
在这里我们可以看到,经过AVFoundation的抽象,我们描述视频画面的动画和平时构建UIView动画的思路是一致,据此可以构建出各式各样的转场动画效果。
合成导出
func export(){
guard let session = AVAssetExportSession.init(asset: composition.copy() as! AVAsset, presetName: AVAssetExportPreset640x480) else {
return
}
session.videoComposition = videoComposition
session.outputURL = CompositionViewController.createTemplateFileURL()
session.outputFileType = AVFileType.mp4
session.exportAsynchronously(completionHandler: {[weak self] in
guard let strongSelf = self else {return}
let status = session.status
if status == AVAssetExportSession.Status.completed {
strongSelf.saveToAlbum(atURL: session.outputURL!, complete: { (success) in
DispatchQueue.main.async {
strongSelf.showSaveResult(isSuccess: success)
}
})
}
})
}
复制代码
以上所述就是小编给大家介绍的《LearningAVFoundation之视频合成+转场过渡动画》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
编写可读代码的艺术
Boswell, D.、Foucher, T. / 尹哲、郑秀雯 / 机械工业出版社 / 2012-7-10 / 59.00元
细节决定成败,思路清晰、言简意赅的代码让程序员一目了然;而格式凌乱、拖沓冗长的代码让程序员一头雾水。除了可以正确运行以外,优秀的代码必须具备良好的可读性,编写的代码要使其他人能在最短的时间内理解才行。本书旨在强调代码对人的友好性和可读性。 本书关注编码的细节,总结了很多提高代码可读性的小技巧,看似都微不足道,但是对于整个软件系统的开发而言,它们与宏观的架构决策、设计思想、指导原则同样重要。编......一起来看看 《编写可读代码的艺术》 这本书的介绍吧!