六.音视频编辑-创建视频过渡-概述

news2024/11/16 3:15:10

引言

目前我的应用已经实现了视频的编辑,音频的混合处理。随着时间的推进,两个不同场景的视频快速的切换,其中没有任何过渡效果。通常画面在时间轴上出现明显的变化时,两个场景间会使用一些动画的过渡效果。比如渐隐,溶解,擦除等等。本篇博客就来介绍一下AV Foundation中有关创建视频过渡的方法,后续会把它应用到我们的项目当中,来丰富视频编辑应用的功能。

相关类

AV Foundation对于这一功能的支持具有很高的可靠性,不过同时也被认为是学习媒体编辑API中最具有挑战性的一个领域。这个功能的整个框架中文档介绍最少的几个功能之一,这一部分主要包括如何使用这个稍微复杂一点的API(而且较难调试)。本节内容会逐步进行讲解让你对这个问题有一个比较深入的认识,避免一些常见的陷阱。下面从学习几个创建视频过渡的类开始学习吧。

AVVideoComposition

AVVideoComposition是视频过渡类API中最核心的类。这个类对两个或多个视频轨道组合在一起的方法给出了一个总体描述。它由一组时间范围和描述组合行为的介绍内容组成,这些信息出现在组合资源内的任意时间点。除了包含描述输入视频层组合的信息之外,还提供了配置视频组合的渲染尺寸、缩放和帧时长等信息。视频组合的配置确定了委托对象处理时AVComposition的呈现方式,这里的委托对象比如AVPlayer或AVAssetImagesGenerator。

AVVideoCompositionInstruction

这个类中最关键的数据就是组合对象时间轴内的时间范围信息,这一时间范围是在某一组合形式出现时的时间范围。要执行的组合特质是通过它的layerInstrucations集合定义的。AVVideoComposition中的时间范围信息便是由AVVideoCompositionInstruction提供。

AVVideoCompositionLayerInstruction

AVVideoCompositionLayerInstruction用于定义视频轨道应用的模糊,渐变,变形等效果。它提供了一些方法用于在特定的时间点或在一个时间范围内对这些值进行修改。在一段时间内对这些值应用渐变操作可以让我们创建出动态的过渡效果,比如溶解和渐淡效果。

AVVideoComposition与前面音频混合提到的AVAudioMix类似,它并不直接和AVCompistion相关。而是和类似AVPlayerItem的客户端关联。

概念

在做这款媒体编辑应用的时候我们所使用的一直都是一个单独的视频轨道,其中按时间轴循序排列了一些列的视频,如下图所示:

这一轨道的排列在目前的应用中没有任何问题。但是当需要在独立的视频分段间实现一个动态过渡效果这个排列就不能满足要求了。

接下来我们把实现过渡的方案分解成一个个相互关联的小步骤,当把每一个小步骤都处理正确,那么视频的过渡也就实现了。

1.部署组合轨道

要在两个视频片段之间添加过渡,首先需要将两个视频资源的轨道重新部署一下。这一步骤可以稍微参考一下之前我们的多个音频轨道,用同样的方式创建两个视频轨道。大多数情况,两个轨道就已经足够了,当然也可以创建多个,但需要主要添加过多的轨道会对性能产生负面影响。

带有两个视频轨道的AVComposition

上图中的组合代码表示如下:

        //1. 创建2个组合轨道
        let compostion = AVMutableComposition()
        let trackA = compostion.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
        let trackB = compostion.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
        let trancks = [trackA,trackB]

2.部署视频轨道

上面的代码创建了一个可变的组合并且添加了两个.video类型的组合轨道。将两个轨道放置到了一个轨道集合中,那么接下来我们需要将两个视频轨道错开部署到两个组合轨道,如下图所示:

代码如下:

        //2. 将视频轨道添加到组合轨道
        guard let url1 = Bundle.main.url(forResource: "01_nebula", withExtension: "mp4") else { return nil}
        guard let url2 = Bundle.main.url(forResource: "04_quasar", withExtension: "mp4") else { return nil}
        let videoAsset1 = AVURLAsset(url: url1)
        let videoAsset2 = AVURLAsset(url: url2)

        let videoAssets = [videoAsset1,videoAsset2]
        var cursorTime = CMTime.zero
        for i in 0..<videoAssets.count {
            let videoAsset = videoAssets[i]
            guard let track = trancks[i % 2] else { continue }
            if let videoTrack = videoAsset.tracks(withMediaType: .video).first {
                let timeRange = CMTimeRange(start: CMTime.zero, duration: videoAsset.duration)
                do {
                    try track.insertTimeRange(timeRange, of: videoTrack, at: cursorTime)
                    cursorTime = CMTimeAdd(cursorTime, timeRange.duration)
                } catch {
                    print("insertTimeRange error")
                }
            }
        }

示例中展示了一个两段视频的排列,事实上所有的视频剪辑都可以遵循这个A-B模式。当视频片段按这种方式排列后,每个组合轨道都会出现一些空白的片段,它们其实就是普通的AVCompositionTrackSegment实例,与视频或者音频一样,不过它们不包含任何媒体数据。

我们使用了迭代的方式来将视频资源添加到组合轨道,每次迭代获取不同的迭代轨道,并将视频轨道从视频资源中提取出来,插入到当前获取的track中,更新cursorTime。

轨道的部署就已经完成了,不过现在视频轨道虽然在两个组合轨道上交错排列,但事实上,每个片段的开始还是都紧跟着上一个片段的结尾,那么两个视频之间就没有任何我们进行剪辑的空间。

下一步我们来解决这个问题。

3.设置重叠区域

想要在两个视频片段中创建过渡,首先需要根据过渡的持续时长来设置两个视频轨道的重叠情况。我们需要将计算cursorTime的方式做一下修改。

        //3. 定义重叠区域
        guard let url1 = Bundle.main.url(forResource: "01_nebula", withExtension: "mp4") else { return nil}
        guard let url2 = Bundle.main.url(forResource: "04_quasar", withExtension: "mp4") else { return nil}
        let videoAsset1 = AVURLAsset(url: url1)
        let videoAsset2 = AVURLAsset(url: url2)
        let videoAssets = [videoAsset1,videoAsset2]
        var cursorTime = CMTime.zero
        //设置转场时间
        let transitionDuration = CMTimeMake(value: 2, timescale: 1)
        for i in 0..<videoAssets.count {
            let videoAsset = videoAssets[i]
            guard let track = trancks[i % 2] else { continue }
            if let videoTrack = videoAsset.tracks(withMediaType: .video).first {
                let timeRange = CMTimeRange(start: CMTime.zero, duration: videoAsset.duration)
                do {
                    try track.insertTimeRange(timeRange, of: videoTrack, at: cursorTime)
                    cursorTime = CMTimeAdd(cursorTime, timeRange.duration)
                    // 将插入时间向前移动转场时间
                    cursorTime = CMTimeSubtract(cursorTime, transitionDuration)
                } catch {
                    print("insertTimeRange error")
                }
            }
        }

和第2步骤中的代码几乎相同,只是在每次循环结束时将cusortime向前移动过渡时间transitionDuration,我们将过渡时长定义为了2s。修改之后的视频布局将变化为如下图所示:

这个组合我们构建出来出来之后其实是可以进行播放的,第一个视频的画面会正常显示,当播放到重叠区域的时候会发现什么都没有,一直到组合播放结束。还有一点我们发现这样进行布局之后整个组合媒体的播放时间实际上是减小了n-1个过渡时间。

如何解决从重叠区域开始没有画面的问题呢?这就需要我们来定义过渡的时间范围并向组合方法说明这两个轨道应该如何进行组合。

4.计算正常播放和重叠播放时间范围

        // 4.计算正常播放和过渡的时间范围
        cursorTime = .zero
        var normalTimeRanges = [CMTimeRange]()
        var transitionTimeRanges = [CMTimeRange]()
        for i in 0 ..< videoAssets.count {
            let asset = videoAssets[i]
            var timeRange = CMTimeRange(start: cursorTime, duration: asset.duration)
            if i > 0 {
                // 不是第一组 有转场,开始时间需要向后移动转场时间 ,时长也需要减少
                timeRange.start = CMTimeAdd(timeRange.start, transitionDuration)
                timeRange.duration = CMTimeSubtract(timeRange.duration, transitionDuration)
            }
            if i < videoAssets.count - 1 {
                // 不是最后一组 有转场,时长需要减少
                timeRange.duration = CMTimeSubtract(timeRange.duration, transitionDuration)
            }
            // 正常播放视频时间
            normalTimeRanges.append(timeRange)
            
            // 起始时间增加正常播放时间再减去转场时间
            cursorTime = CMTimeAdd(cursorTime, asset.duration)
            cursorTime = CMTimeSubtract(cursorTime, transitionDuration)
            if i < videoAssets.count - 1 {
                // 不是最后一组 计算过渡的timeRange
                timeRange = CMTimeRange(start: cursorTime, duration: transitionDuration)
                transitionTimeRanges.append(timeRange)
            }
        }

这是一个比较通用的计算方式,适合任何个数的资源进行组合。代码中对组合的视频资源集合进行了遍历,对每个视频都创建了一个初始时间范围,然后再根据时长计算出过渡的时间范围以及下一组正常播放的时间范围。

我们在进行时间计算时,必须是没有任何空隙或者重叠。

此外我们还需要考虑到如果组合中其它轨道,那么最好它们也要遵循目前的视频轨道时间轴来修改它们的持续时间。

如果组合过程中有计算出现偏差,组合对象可能仍然可以播放,不过视频内容不会被渲染,只会显示一个黑屏。

5.创建组合和层指令

创建AVVideoCompositionInstruction和AVVideoCompositionLayerInstruction实例,设置视频组合方式所执行的指令。

        //5.创建组合指令
        
        var compositionInstructions = [AVMutableVideoCompositionInstruction]()
        let tracks = compostion.tracks(withMediaType: .video)
        
        for i in 0 ..< normalTimeRanges.count {
            let trackIndex = i % 2
            /// 创建正常播放部分播放指令
            let currentTrack = tracks[trackIndex]
            let instruction = AVMutableVideoCompositionInstruction()
            instruction.timeRange = normalTimeRanges[i]
            let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: currentTrack)
            instruction.layerInstructions = [layerInstruction]
            compositionInstructions.append(instruction)
            if i < transitionTimeRanges.count {
                /// 创建过渡部分播放指令
                let foregroundTrack = tracks[trackIndex]
                let backgroundTrack = tracks[1 - trackIndex]
                let instruction = AVMutableVideoCompositionInstruction()
                instruction.timeRange = transitionTimeRanges[i]
                let fromLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: backgroundTrack)
                let toLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: foregroundTrack)
                instruction.layerInstructions = [fromLayerInstruction, toLayerInstruction]
                compositionInstructions.append(instruction)
            }
        }
  1. 遍历所有正常播放的时间范围,循环创建正常播放的指令以及过渡播放的指令。
  2. 创建正常播放指令,创建一个新的AVMutableVideoCompositionInstruction并将正常播放的的当前CMTimeRange赋值给它。
  3. 创建一个新的AVMutableVideoCompositionLayerInstruction,并将其包装成集合,设置给AVMutableVideoCompositionInstruction的layerInstructions属性。正常播放的组合没只有单视频轨道所以我们只需要创建一个指令。
  4. 为过渡部分创建指令时,我们需要获取重叠的两个轨道,创建一个AVMutableVideoCompositionInstruction实例,为每个轨道分别创建一个AVMutableVideoCompositionLayerInstruction。
  5. 将两个指令都添加到AVMutableVideoCompositionLayerInstruction的layerInstructions属性中。

现在所有需要的组合和层指令都创建完成了,需要继续完成最后一步,创建和配置AVVideoComposition。

6.创建和配置AVVideoComposition

        //6.创建和配置AVAVideoComposition
        let videoComposition = AVMutableVideoComposition()
        videoComposition.instructions = compositionInstructions
        videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
        videoComposition.renderSize = CGSize(width: 1280, height: 720)
        videoComposition.renderScale = 1.0
  1. instructions:用户设置我们在上面创建的组合指令,这些指令向组合器描述时间范围和执行组合的种类。
  2. frameDuration:用户设置帧率。
  3. renderSize:定义组合应该被渲染的尺寸。
  4. renderScale:定义了视频组合应用的缩放。

到此,关于创建视频过渡的所有方法就都已经介绍完成了,只是我们还没有详细的介绍具体的过渡方法,这个方案我们将会在下一篇应用过渡的博客中详细讲解。

结语

整个过程有一点复杂,但只要将这些步骤分开处理,你会发现每个步骤倒也并不复杂,其中比较关键的部分仍然是对CMTime的处理。

下一篇博客中我们将使用本篇博客的内容,并应用到视频编辑项目中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1630701.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

麦肯锡报告:《在实现量子优势方面稳步推进》

2024年4月24日&#xff0c;麦肯锡一年一度的Quantum Technology Monitor发布了其最新的2024年研究报告&#xff0c;提供对全球量子技术&#xff08;QT&#xff09;、投资、生态系统等发展现状的见解。 此次&#xff0c;麦肯锡为第三届年度Quantum Technology Monitor报告所做的…

[C++]22:C++11_part_one

C11 一.列表初始化&#xff1a;1.{}初始化&#xff1a;2.C11扩大了列表初始化的范围&#xff1a;3.std::initializer_list1.简单类型的列表初始化&#xff1a;2.复杂类型的列表初始化3.实现vector的列表初始化4.实现list的列表初始化&#xff1a;5.不支持列表初始化&#xff1a…

多用户商城系统哪个好,2024多用户商城系统这样选

在2024年选择适合的多用户商城系统是一项至关重要的决策&#xff0c;因为一个优秀的商城系统不仅可以提升用户体验&#xff0c;还能够帮助企业实现业务目标并取得长期成功。然而&#xff0c;在众多的选择中挑选出最适合的一个并不容易&#xff0c;需要综合考虑各种因素&#xf…

网页模版如何用

现在的网页模版已经得到了许多人的喜爱和使用。随着人们对互联网的需求不断增加&#xff0c;更多的公司和组织需要拥有自己的网站&#xff0c;以推广他们的品牌和服务。而网页模版为他们提供了一个简单而高效的方法来创建自己的网站。 网页模版是预先设计好的网站模板&#xff…

docker容器通俗理解

前言 如果大家没使用过Docker,就在电脑上下载一个VMware Workstation Pro&#xff0c;创建一个虚拟机安装一个windows操作一下感受一下,为什么我的电脑上还以再安装一台windows主机&#xff1f;其实你可以理解为Docker就是Linux系统的一个虚拟机软件。 我的Windows也可以安装…

(学习日记)2024.05.08:UCOSIII第六十二节:常用的结构体(os.h文件)第一部分

之前的章节都是针对某个或某些知识点进行的专项讲解&#xff0c;重点在功能和代码解释。 回到最初开始学μC/OS-III系统时&#xff0c;当时就定下了一个目标&#xff0c;不仅要读懂&#xff0c;还要读透&#xff0c;改造成更适合中国宝宝体质的使用方式。在学完野火的教程后&a…

Java基础教程- 1 Java 简介

更好的阅读体验&#xff1a;点这里 &#xff08; www.doubibiji.com &#xff09; 1 Java 简介 1.1 Java语言概述 1.1.1 Java是什么 &#xff08;了解一下即可&#xff0c;对学习没影响&#xff0c;总得了解一下它是怎么来滴~&#xff09; Java是由Sun公司&#xff08;已…

五一景点预约怎么预约 预约时间用备忘录设置提醒不怕错过

五一小长假即将来临&#xff0c;相信很多人和我一样&#xff0c;已经跃跃欲试&#xff0c;准备踏上旅途&#xff0c;去探索那些心仪已久的风景名胜。但在这个旅游高峰期&#xff0c;不少热门景点都需要提前预约购票。那么&#xff0c;怎么预约才能确保顺利游览呢&#xff1f; …

在智慧城市的建设中智能电表发挥什么作用

在智慧城市的建设中&#xff0c;智能电表扮演着至关重要的角色。智慧城市是一个利用信息技术手段提升城市运行效率和质量的新型城市模式&#xff0c;旨在通过信息和通信技术的应用&#xff0c;提高城市管理、公共服务、环境保护等方面的质量和效率&#xff0c;促进城市的可持续…

金价大跳水,美梦变噩梦!2024真正适合普通人的靠谱创业项目!2024适合30-40岁轻资产小生意

4月22日晚间&#xff0c;向上“狂飙”了一个多月的金价突然就“大跳水”。当日&#xff0c;每克金价均下调14块。在这次跳水中&#xff0c;有人欢喜有人愁&#xff1a;有投资者自报做空金价一夜狂赚14万&#xff0c;也有投资者哭诉&#xff0c;头晚进货到早上就净亏损2万&#…

中电金信:GienTech动态| 获奖、合作、与伙伴共谋数字化转型…

—— —— GienTech动态 —— —— 中电金信携“源启”亮相第十二届中国电子信息博览会 4月11日&#xff0c;为期三天的“第十二届中国电子信息博览会”在深圳顺利闭幕。中国电子信息博览会是中国规模最大、最具国际影响力的电子信息产业盛会之一。本届大会以“全球视野&#x…

AI预测福彩3D第9套算法实战化测试第6弹2024年4月28日第6次测试

今天继续进行新算法的测试&#xff0c;今天是第6次测试。好了&#xff0c;废话不多说了&#xff0c;直接上图上结果。 2024年4月28日福彩3D预测结果 6码定位方案如下&#xff1a; 百位&#xff1a;5、4、9、3、1、0 十位&#xff1a;2、3、5、6、1、7 个位&#xff1a;4、5、0、…

Goby 漏洞发布|禅道 /api.php/v1/users 未授权访问漏洞

漏洞名称&#xff1a;禅道 /api.php/v1/users 未授权访问漏洞 English Name&#xff1a;Zen Road /api.php/v1/users Unauthorized Access Vulnerability CVSS core: 9.8 影响资产数&#xff1a;69265 漏洞描述&#xff1a; 禅道是一款开源的项目管理软件&#xff0c;旨在…

Leetcode—1017. 负二进制转换【中等】(string列表初始化、反向迭代器)

2024每日刷题&#xff08;120&#xff09; Leetcode—1017. 负二进制转换 实现代码 class Solution { public:string baseNeg2(int n) {string ans;while(n ! 0) {ans to_string(n & 1);n -(n >> 1);}return ans.empty() ? "0": string{ans.rbegin(),…

Linux安装Matlab运行时

一般而言&#xff0c;安装Matlab的linux系统是带桌面版的&#xff0c;如果没带&#xff0c;不在本教程范围内。 一、下载Matlab 下载地址&#xff1a;MATLAB Runtime - MATLAB Compiler - MATLAB 本教程使用R2020b(9.9) 二、linux系统中进行解压 将zip传入linux系统&#xf…

vue2主体页面进行拆分

目录 一.组件化 二.新建Header.vue页面 三.Aside.vue代码 四.Main.vue代码如下 五.Home.vue代码如下 六.index.js代码如下&#xff1a; 七.项目效果图 在Vue.js 2中&#xff0c;将主体页面进行拆分是一种常见的做法&#xff0c;它有助于提高代码的可维护性和可读性。页面…

Halcon 3D 使用3D ROI截取模型

Halcon 3D 使用3D ROI截取模型 链接:https://pan.baidu.com/s/1UfFyZ6y-EFq9jy0T_DTJGA 提取码:ewdi * 1.读取图片 ****************

新装电脑Flutter环境部署坑汇总(持续更新)

1.本地安装&#xff0c;安装fvm的坑 本人电脑使用windows &#xff0c;安装fvm则一般使用choco安装&#xff0c;那么首先需要安装choco,打开powershell/或者cmd运行以下命令&#xff1a; Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager…

word启动缓慢之Baidu Netdisk Word Addin

word启动足足花了7秒钟&#xff0c;你知道我这7秒是怎么过来的吗&#xff1f; 原因就是我们可爱的百度网盘等APP&#xff0c;在我们安装客户端时&#xff0c;默认安装了Office加载项&#xff0c;不仅在菜单栏上加上了一个丑陋的字眼&#xff0c;也拖慢了word启动速度........ 解…

STM32H750片外QSPI下载算法文件(stldr)生成

STM32H750片外QSPI下载算法文件&#xff08;stldr&#xff09;生成 &#x1f33f;相关篇《STM32H750片外QSPI启动配置简要》&#x1f4cc;参考实现资料&#xff1a; https://github.com/lchnu/STM32H750XBH_ARTPIQSPI_W25Q64JV https://gitee.com/wangchief/H750_W25QXX ✨利…