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

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

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

查看所有标签

猜你喜欢:

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

Learning JavaScript

Learning JavaScript

Shelley Powers / Oreilly & Associates Inc / 2006-10-17 / $29.99

As web browsers have become more capable and standards compliant, JavaScript has grown in prominence. JavaScript lets designers add sparkle and life to web pages, while more complex JavaScript has led......一起来看看 《Learning JavaScript》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码