内容简介:)如题所示,本文的目标是将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之视频合成+转场过渡动画》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。