七.音视频编辑-创建视频过渡-应用
引言
在上一篇博客中,我们已经介绍了创建视频过渡的实现方案,步骤非常繁琐,在生成AVMutableVideoCompositionInstruction和AVMutableVideoCompositionLayerInstruction的计算也十分复杂,但其实还有一个创建视频组合的捷径。不过我们还是需要理解上一篇博客中我们所讨论的步骤,只有理解了那些步骤,才能发现学习这些对应的使用变得容易许多。
创建组合的捷径
AVVideoComposition定义了一个十分便捷的初始化方法init(propertiesOf asset: AVAsset),我们可以将AVCompostion作为参数来创建一个AVVideoComposition。该方法会为我们创建一个带有如下配置的AVVideoComposition对象:
- instructions 属性包含一组完整的基于组合视频轨道(以及其中包含的片段空间布局)的组合和层指令。
- renderSize 属性被设置为AVComposition对象的naturalSize,或者如果没有设置,则使用能够满足组合视频轨道中最大视频维度的尺寸值。
- frameDuration 设置为组合视频轨道中最大nominalFrameRate的值。如果所有轨道的nominalFrameRate值都为0,则frameDuration设置成默认1/30秒(30FPS)。
- renderScale 始终设置为1.0。
创建视频过渡
下面我们开始着手创建视频过渡,和之前的实现方案一样,先创建一个遵循PHComposition协议名为PHTransitionComposition的类,以及遵循PHCompositionBuilder协议名为PHTransitionCompositionBuilder的类。
PHTransitionComposition负责构建视频的可播放和可导出版本。
PHTransitionCompositionBuilder负责构建PHTransitionComposition,里面会创建用于视频编辑的AVMutableComposition,AVMutableAudioMix以及AVMutableVideoComposition。
PHTransitionComposition
代码实现如下:
import UIKit
import AVFoundationclass PHTransitionComposition: NSObject,PHComposition {/// 组合轨道var composition:AVMutableComposition!/// 视频轨道var videoComposition:AVMutableVideoComposition?/// 音频混合var audioMix:AVMutableAudioMix?init(composition: AVMutableComposition!, videoComposition: AVMutableVideoComposition?, audioMix: AVMutableAudioMix?) {self.composition = compositionself.videoComposition = videoCompositionself.audioMix = audioMix}func makePlayerItem() -> AVPlayerItem? {let playerItem = AVPlayerItem(asset: composition.copy() as! AVAsset)playerItem.videoComposition = videoCompositionplayerItem.audioMix = audioMixreturn playerItem}func makeAssetExportSession() -> AVAssetExportSession? {let exportSession = AVAssetExportSession(asset: composition.copy() as! AVAsset, presetName: AVAssetExportPresetHighestQuality)exportSession?.videoComposition = videoCompositionexportSession?.audioMix = audioMixreturn exportSession!}}
代码较以前的类相比多了一个AVMutableVideoComposition属性,用来实现视频的过渡效果。
PHTransitionCompositionBuilder
该类中的代码和以往一样主要目的是构建PHComposition,分成三个部分,添加视频到组合轨道,添加音频到组合轨道,添加背景音乐到组合轨道。
let defaultTransitionDuration = CMTime(value: 2, timescale: 1)class PHTransitionCompositionBuilder: NSObject,PHCompositionBuilder {/// 资源模型var timeLine:PHTimeLine!/// 组合轨道let composition = AVMutableComposition()init(timeLine: PHTimeLine!) {self.timeLine = timeLine}func buildComposition() -> PHComposition? {// 添加视频到组合轨道addVideoCompositionTrack()// 创建AVVideoCompositionlet videoComposition = buildVideoComposition()// 添加音频到组合轨道addAudioCompositionTrack()// 添加背景音乐到组合轨道let audioMix = addMusicCompositionTrack()return PHTransitionComposition(composition: composition, videoComposition: videoComposition, audioMix: audioMix)}/// 添加视频到组合轨道func addVideoCompositionTrack() {....}/// 创建AVVideoCompositionfunc buildVideoComposition() -> AVMutableVideoComposition? {....}/// 添加音频到组合轨道func addAudioCompositionTrack() {let _ = addCompositionTrack(mediaType: .audio, mediaItems: timeLine.audioItems)}/// 添加背景音乐到组合轨道func addMusicCompositionTrack() -> AVMutableAudioMix?{// 添加背景音乐var audioMix:AVMutableAudioMix? = nilif timeLine.musicItem != nil {let musicCompositionTrack = addCompositionTrack(mediaType: .audio, mediaItems: [timeLine.musicItem!])let musicAudioMix = buildAudioMixWithTrack(track: musicCompositionTrack)audioMix = musicAudioMix}return audioMix}/// 私有方法-添加媒体资源轨道/// - Parameters:/// - mediaType: 媒体类型/// - mediaItems: 媒体媒体资源数组/// - Returns: 返回一个AVCompositionTrackprivate func addCompositionTrack(mediaType:AVMediaType,mediaItems:[PHMediaItem]?) -> AVMutableCompositionTrack? {if PHIsEmpty(array: mediaItems) {return nil}let trackID = kCMPersistentTrackID_Invalidguard let compositionTrack = composition.addMutableTrack(withMediaType: mediaType, preferredTrackID: trackID) else { return nil }//设置起始时间var cursorTime = CMTime.zeroguard let mediaItems = mediaItems else { return nil }for item in mediaItems {//这里默认时间都是从0开始guard let asset = item.asset else { continue }guard let assetTrack = asset.tracks(withMediaType: mediaType).first else { continue }do {try compositionTrack.insertTimeRange(item.timeRange, of: assetTrack, at: cursorTime)} catch {print("addCompositionTrack error")}cursorTime = CMTimeAdd(cursorTime, item.timeRange.duration)}return compositionTrack}/// 创建音频混合器/// - Parameters:/// - musicTrack: 音乐轨道func buildAudioMixWithTrack(track:AVMutableCompositionTrack?) -> AVMutableAudioMix? {guard let track = track else { return nil }guard let musicItem = timeLine.musicItem else { return nil }let audioMix = AVMutableAudioMix()let audioMixParam = AVMutableAudioMixInputParameters(track: track)for volumeAutomaition in musicItem.volumeAutomations {audioMixParam.setVolumeRamp(fromStartVolume: volumeAutomaition.startVolume, toEndVolume: volumeAutomaition.endVolume, timeRange: volumeAutomaition.timeRange)}audioMix.inputParameters = [audioMixParam]return audioMix}}
关于添加音频和背景音乐以及创建AVAudioMix的部分,我们在前面的博客中已经进行过介绍,在这里就不再重复解释了,把重点放到添加视频到组合轨道以及创建AVVideoComposition上。
下面看一下添加视频到组合轨道的实现:
/// 添加视频到组合轨道func addVideoCompositionTrack() {let compositionTrackA = self.composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)let compositionTrackB = self.composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)let videoTracks = [compositionTrackA,compositionTrackB]let transitionDuration = defaultTransitionDuration// 初始时间var cursorTime = CMTime.zerolet videoItems = timeLine.videoItmesfor (index,item) in videoItems.enumerated() {let trackIndex = index % 2let currentTrack = videoTracks[trackIndex]guard let asset = item.asset else { continue }guard let assetTrack = asset.tracks(withMediaType: .video).first else { continue }do {try currentTrack?.insertTimeRange(item.timeRange, of: assetTrack, at: cursorTime) } catch {print("insertTimeRange error")}// 更新cursorTimecursorTime = CMTimeAdd(cursorTime, item.timeRange.duration)cursorTime = CMTimeSubtract(cursorTime, transitionDuration)}}
- 首先从当前的AVMutableComposition中创建两个新的AVMutableCompositionTrack对象,两者都是.video类型,并添加到videoTracks数组中,提供所需的A-B轨道排列。
- 遍历视频资源,将视频资源交替插入到AB两个轨道中。
- 计算cursorTime,需要考虑到减去动画过程的时间,因为这段时间两个视频媒体是重叠的。
创建AVVideoComposition的实现如下:
/// 创建AVVideoCompositionfunc buildVideoComposition() -> AVMutableVideoComposition? {let videoComposition = AVMutableVideoComposition(propertiesOf: self.composition)videoComposition.renderSize = CGSize(width: 1920, height: 1080)/// 获取instructionslet instructions = videoComposition.instructionsvar layerInstructionIndex = 1for instruction in instructions {guard let videoCompositionInstruction = instruction as? AVMutableVideoCompositionInstruction else { continue }let layerInstructions = videoCompositionInstruction.layerInstructions// 判断是否有两个layerInstructionsguard layerInstructions.count == 2 else { continue }// 创建过渡效果let fromeLayerInstruction = layerInstructions[1 - layerInstructionIndex] as! AVMutableVideoCompositionLayerInstructionlet toLayerInstruction = layerInstructions[layerInstructionIndex] as! AVMutableVideoCompositionLayerInstruction// 设置动画fromeLayerInstruction.setOpacityRamp(fromStartOpacity: 1.0, toEndOpacity: 0.0, timeRange: videoCompositionInstruction.timeRange)layerInstructionIndex = layerInstructionIndex == 1 ? 0 : 1}return videoComposition}
- 使用init(propertiesOf asset: AVAsset)创建一个新的AVVideoComposition实例,这个方法会自动创建所需的组合对象和层指令。并设置renderSize、renderScale、frameDuration熟悉为相应的值。(renderSize需要满足组合视频轨道中最大视频维度的尺寸值)
- 遍历videoComposition的instructions属性,判断videoCompositionInstruction的layerInstructions个数等于2的情况提取重叠的两个AVMutableVideoCompositionLayerInstruction。
- 为两个AVMutableVideoCompositionLayerInstruction添加一个渐变的过渡动画。
- 返回AVVideoComposition实例。
除了上面列出的最简单的渐隐过渡方式,还支持很多其它的过渡方式。
推入过渡效果:
// 推入过渡效果let identityTransform = CGAffineTransform.identitylet videoWidth = videoComposition.renderSize.widthlet fromeTransform = CGAffineTransform(translationX: -videoWidth, y: 0)let toTransform = CGAffineTransform(translationX: videoWidth, y: 0)fromeLayerInstruction.setTransformRamp(fromStart: identityTransform, toEnd: fromeTransform, timeRange: videoCompositionInstruction.timeRange)toLayerInstruction.setTransformRamp(fromStart: toTransform, toEnd: identityTransform, timeRange: videoCompositionInstruction.timeRange)
擦除过渡效果:
// 擦除过渡效果let width = videoComposition.renderSize.widthlet height = videoComposition.renderSize.heightlet startRect = CGRect(x: 0, y: 0, width: width, height: height)let endRect = CGRect(x: width, y: height, width: width, height: 0.0)fromeLayerInstruction.setCropRectangleRamp(fromStartCropRectangle: startRect, toEndCropRectangle: endRect, timeRange: videoCompositionInstruction.timeRange)
播放
创建PHTransitionCompositionBuilder实例,构建视频的可播放版本,进行播放。
func player() {guard let delegate = self.delegate else { return }let compositionBuilder = PHTransitionCompositionBuilder(timeLine: timeLine)let composition = compositionBuilder.buildComposition()let playerItem = composition?.makePlayerItem()delegate.replaceCurrentItem(playerItem: playerItem)}
结语
代码中我们把工作的重点集中到了A-B轨道的方式交替添加视频资源,以及构建AVMutableVideoComposition并创建过渡效果上面。但事实上我们还需要注意到播放进度的显示,创建过渡后两个视频之间会有重叠部分,在呈现的时候需要减去重叠时间。
需要注意其它轨道的长度需要小于视频组合轨道的轨道长度。
另外最需要注意的是AVMutableVideoComposition的renderSize熟悉,一定要使用能够满足组合视频轨道中最大视频维度的尺寸值。
相关文章:
七.音视频编辑-创建视频过渡-应用
引言 在上一篇博客中,我们已经介绍了创建视频过渡的实现方案,步骤非常繁琐,在生成AVMutableVideoCompositionInstruction和AVMutableVideoCompositionLayerInstruction的计算也十分复杂,但其实还有一个创建视频组合的捷径。不过我…...
Android11 InputManagerService启动流程分析
InputManagerService在systemserver进程中被启动 //frameworks\base\services\java\com\android\server\SystemServer.java t.traceBegin("StartInputManagerService"); inputManager new InputManagerService(context);//1 t.traceEnd(); //省略 //注册服务 Servi…...
【计算机网络篇】数据链路层(8)共享式以太网的退避算法和信道利用率
文章目录 🛸共享式以太网的退避算法🥚截断二进制指数算法 🍔共享式以太网的信道利用率 🛸共享式以太网的退避算法 在使用CSMA/CD协议的共享总线以太网中,正在发送帧的站点一边发送帧一边检测碰撞,当检测到…...
wordpress主题 7B2 PRO主题5.4.2免授权直接安装
内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 WordPress 资讯、资源、社交、商城、圈子、导航等多功能商用主题:B2 PRO 其设计风格专业且时尚,功能十分强大,包括多栏布局、自定义页面、强大的主…...
Dubbo基本使用
Dubbo基本使用 1.项目介绍2.开发步骤2.1 启动注册中心2.2 初始化项目2.3 添加 Maven 依赖2.3.1 父pom.xml2.3.1 consumer模块和provider模块pom.xml 2.4 定义服务接口2.5 定义服务端的实现2.6 配置服务端 Yaml 配置文件2.7 配置消费端 Yaml 配置文件2.8 基于 Spring 配置服务端…...
JS解密之新js加密实战(二)
前言 上次发了一篇关于新加密的,只解了前边两层,这中间家里各种事情因素影响,没有继续进一步研究,今天百忙之中抽空发布第二篇,关于其中的一小段加密片段,我认为分割成多个小片段是更容易被理解的。逻辑相…...
tsconfig 备忘清单
前言 ❝ Nealyang/blog0 使用 ts 已多年,但是貌似对于 tsconfig 总是记忆不清,每次都是 cv 历史项目,所以写了这篇备忘录,希望能帮助到大家。 本文总结整理自 Matt Pocock 的一篇文章3,加以个人理解,并做了…...
jmeter后置处理器提取到的参数因为换行符导致json解析错误
现象: {"message":"JSON parse error: Illegal unquoted character ((CTRL-CHAR, code 10)): has to be escaped using backslash to be included in string value; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Ill…...
栈与队列的实现
前言 本次博客将要实现一下栈和队列,好吧 他们两个既可以使用动态数组也可以使用链表来实现 本次会有详细的讲解 栈的实现 栈的基础知识 什么是栈呢? 栈的性质是后进先出 来画个图来理解 当然可不可以出一个进一个呢,当然可以了 比如…...
线性集合:ArrayList,LinkedList,Vector/Stack
共同点:都是线性集合 ArrayList ArrayList 底层是基于数组实现的,并且实现了动态扩容(当需要添加新元素时,如果 elementData 数组已满,则会自动扩容,新的容量将是原来的 1.5 倍),来…...
llama3 发布!大语言模型新选择 | 开源日报 No.251
meta-llama/llama Stars: 53.0k License: NOASSERTION llama 是用于 Llama 模型推理的代码。 提供了预训练和微调的 Llama 语言模型,参数范围从 7B 到 70B。可以通过下载脚本获取模型权重和 tokenizer。支持在本地快速运行推理,并提供不同规格的模型并…...
SpringBoot 具体是做什么的?
Spring Boot是一个用于构建独立的、生产级别的、基于Spring框架的应用程序的开源框架。它的目标是简化Spring应用程序的开发和部署过程,通过提供一种快速、便捷的方式来创建Spring应用程序,同时保持Spring的灵活性和强大特性。 1. 简化Spring应用程序开…...
Debian常用命令
Debian是一个开源的Unix-like操作系统,提供了大量的软件包供用户安装和使用。在Debian系统中,命令行界面(CLI)是用户与系统进行交互的重要工具。以下是Debian中一些常用的命令及其详细解释: 文件和目录操作命令&#x…...
常见的前端框架
常用的前端框架有以下几种: 模型 React:由Facebook开发的一款前端框架,采用虚拟DOM的概念,可高效地更新页面。Vue.js:一款轻量级的前端框架,易学易用,支持组件化开发和双向数据绑定。AngularJ…...
初学者如何选择ARM开发硬件?
在开始前我有一些资料,是我根据网友给的问题精心整理了一份「ARM的资料从专业入门到高级教程」, 点个关注在评论区回复“888”之后私信回复“888”,全部无偿共享给大家!!!如果你没有ARM开发经验࿰…...
Mysql 多表查询,内外连接
内连接: 隐式内连接 使用sql语句直接进行多表查询 select 字段列表 from 表1 , 表2 where 条件 … ; 显式内连接 将‘,’改为 inner join 连接两个表的 on select 字段列表 from 表1 [ inner ] join 表2 on 连接条件 … ; select emp.id, emp.name, …...
【C语言】函数
目录 一、函数的概念 二、库函数 2.1 ❥ 标准库 2.2 ❥ 库函数的使用方法 三、自定义函数 四、形参和实参 4.1 ❥ 实参(实际参数) 4.2 ❥ 形参(形式参数) 五、return语句 六、函数的调用 6.1 ❥ 传值调用 6.2 ❥ 传址调…...
【LeetCode】每日一题 2024_5_13 腐烂的橘子(经典多源 BFS)
文章目录 LeetCode?启动!!!题目:找出不同元素数目差数组题目描述代码与解题思路 每天进步一点点 LeetCode?启动!!! 好久没写每日一题题解了,今天重新起航 干…...
【Linux系统编程】第十七弹---进程理解
✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、进程的基本概念 2、描述进程-PCB 2.1、什么是PCB 2.2、为什么要有PCB 3、task_ struct 3.1、启动进程 3.2、创建进程…...
【网络安全入门】你必须要有的学习工具(附安装包)零基础入门到进阶,看这一篇就够了!
工欲善其事必先利其器 在新入门网络安全的小伙伴而言。这些工具你必须要有所了解。本文我们简单说说这些网络安全工具吧! Web安全类 Web类工具主要是通过各种扫描工具,发现web站点存在的各种漏洞如sql注入、xss等。从而获取系统权限,常用的…...
JavaSec-RCE
简介 RCE(Remote Code Execution),可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景:Groovy代码注入 Groovy是一种基于JVM的动态语言,语法简洁,支持闭包、动态类型和Java互操作性,…...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
vulnyx Blogger writeup
信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面,gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress,说明目标所使用的cms是wordpress,访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...
力扣热题100 k个一组反转链表题解
题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...
