LearningAVFoundation之视频合成+转场过渡动画

栏目: IOS · 发布时间: 5年前

内容简介:)如题所示,本文的目标是将5段独立的小视频合成一段完整的视频,各视频间穿插溶解消失、从右往左推的转场过渡效果。涉及到的类在AVFoundation框架中的关系如图所示,可知要达成开头的目标,核心是要构建出两个类,AVCompostion和AVVideoComposition。(这两个类虽然从名字上看有某种关系,但事实上并不存在继承或什么关系)
LearningAVFoundation之视频合成+转场过渡动画
(加载不出预览的,地址: user-gold-cdn.xitu.io/2018/11/16/…

)

如题所示,本文的目标是将5段独立的小视频合成一段完整的视频,各视频间穿插溶解消失、从右往左推的转场过渡效果。

整体脉络

LearningAVFoundation之视频合成+转场过渡动画

涉及到的类在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交叉的思路。如果只是单纯的视频拼接,完全可以放到同一条视频轨道中。

LearningAVFoundation之视频合成+转场过渡动画

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之视频合成+转场过渡动画》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

断层地带

断层地带

杰弗里·摩尔 (Geoffrey A.moore) 著 / 季晓楠,凌炜 译 / 机械工业出版社 / 2009-1 / 33.00元

从股东权益谈起,并把“股东权益管理”作为根本立场,明确阐述了公司市场价值的内涵:在竞争环境中,公司的市场价值等于在当前和计划的生产经营活动下,未来可预计的收益用风险系数贴现后的现值。断层地带:经济中的危险境地,在这里勇敢的创新者给市场带来了惨烈的竞争,撕杀得天昏地暗。每家公司都处在这样的环境中,但是没有一个管理者能够控制它。一起来看看 《断层地带》 这本书的介绍吧!

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具